diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 1d044f31..343e7cf5 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -14,5 +14,5 @@ jobs: env: SSH_PRIVATE_KEY: ${{ secrets.SYNC_GITEE_PRI_KEY }} with: - source-repo: "git@github.com:itcharge/LeetCode-Py.git" - destination-repo: "git@gitee.com:itcharge/LeetCode-Py.git" + source-repo: "git@github.com:itcharge/AlgoNote.git" + destination-repo: "git@gitee.com:itcharge/AlgoNote.git" diff --git a/Assets/Images/algo-book-dark.png b/Assets/Images/algo-book-dark.png deleted file mode 100644 index d5d39abf..00000000 Binary files a/Assets/Images/algo-book-dark.png and /dev/null differ diff --git a/Assets/Images/algo-book-light.png b/Assets/Images/algo-book-light.png deleted file mode 100644 index 6bfc6191..00000000 Binary files a/Assets/Images/algo-book-light.png and /dev/null differ diff --git a/Assets/Images/itcharge-qr-code.png b/Assets/Images/itcharge-qr-code.png deleted file mode 100644 index 10af1597..00000000 Binary files a/Assets/Images/itcharge-qr-code.png and /dev/null differ diff --git a/Assets/Origins/Categories-List.md b/Assets/Origins/Categories-List.md deleted file mode 100644 index 91cd4e1b..00000000 --- a/Assets/Origins/Categories-List.md +++ /dev/null @@ -1,338 +0,0 @@ -## 01. 数组 - -### [数组基础题目](../../Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) - -#### 数组操作题目 - -###### 0189. 轮转数组、0066. 加一、0724. 寻找数组的中心下标、0485. 最大连续 1 的个数、0238. 除自身以外数组的乘积 - -#### 二维数组题目 - -###### 0498. 对角线遍历、0048. 旋转图像、0073. 矩阵置零、0054. 螺旋矩阵、0059. 螺旋矩阵 II、0289. 生命游戏 - -### [排序算法题目](../../Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) - -#### 冒泡排序题目 - -###### 剑指 Offer 45. 把数组排成最小的数、0283. 移动零 - -#### 选择排序题目 - -###### 0215. 数组中的第K个最大元素 - -#### 插入排序题目 - -###### 0075. 颜色分类 - -#### 希尔排序题目 - -###### 0912. 排序数组、0506. 相对名次 - -#### 归并排序题目 - -###### 0912. 排序数组、0088. 合并两个有序数组、剑指 Offer 51. 数组中的逆序对、0315. 计算右侧小于当前元素的个数 - -#### 快速排序题目 - -###### 0912. 排序数组、0169. 多数元素 - -#### 堆排序题目 - -###### 0912. 排序数组、0215. 数组中的第K个最大元素、剑指 Offer 40. 最小的k个数 - -#### 计数排序题目 - -###### 0912. 排序数组、1122. 数组的相对排序 - -#### 桶排序题目 - -###### 0912. 排序数组、0220. 存在重复元素 III、0164. 最大间距 - -#### 基数排序题目 - -###### 0164. 最大间距、0561. 数组拆分 - -#### 其他排序题目 - -###### 0217. 存在重复元素、0136. 只出现一次的数字、0056. 合并区间、0179. 最大数、0384. 打乱数组、剑指 Offer 45. 把数组排成最小的数 - - -### [二分查找题目](../../Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) - -#### 二分下标题目 - -###### 0704. 二分查找、0374. 猜数字大小、0035. 搜索插入位置、0034. 在排序数组中查找元素的第一个和最后一个位置、0167. 两数之和 II - 输入有序数组、0153. 寻找旋转排序数组中的最小值、0154. 寻找旋转排序数组中的最小值 II、0033. 搜索旋转排序数组、0081. 搜索旋转排序数组 II、0278. 第一个错误的版本、0162. 寻找峰值、0852. 山脉数组的峰顶索引、1095. 山脉数组中查找目标值、0744. 寻找比目标字母大的最小字母、0004. 寻找两个正序数组的中位数、0074. 搜索二维矩阵、0240. 搜索二维矩阵 II - -#### 二分答案题目 - -###### 0069. x 的平方根、0287. 寻找重复数、0050. Pow(x, n)、0367. 有效的完全平方数、1300. 转变数组后最接近目标值的数组和、0400. 第 N 位数字 - -#### 复杂的二分查找问题 - -###### 0875. 爱吃香蕉的珂珂、0410. 分割数组的最大值、0209. 长度最小的子数组、0658. 找到 K 个最接近的元素、0270. 最接近的二叉搜索树值、0702. 搜索长度未知的有序数组、0349. 两个数组的交集、0350. 两个数组的交集 II、0287. 寻找重复数、0719. 找出第 K 小的数对距离、0259. 较小的三数之和、1011. 在 D 天内送达包裹的能力、1482. 制作 m 束花所需的最少天数 - -### [双指针题目](../../Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) - -#### 对撞指针题目 - -###### 0167. 两数之和 II - 输入有序数组、0344. 反转字符串、0345. 反转字符串中的元音字母、0125. 验证回文串、0011. 盛最多水的容器、0611. 有效三角形的个数、0015. 三数之和、0016. 最接近的三数之和、0018. 四数之和、0259. 较小的三数之和、0658. 找到 K 个最接近的元素、1099. 小于 K 的两数之和、0075. 颜色分类、0360. 有序转化数组、0977. 有序数组的平方、0881. 救生艇、0042. 接雨水、0443. 压缩字符串 - -#### 快慢指针题目 - -###### 0026. 删除有序数组中的重复项、0080. 删除有序数组中的重复项 II、0027. 移除元素、0283. 移动零、0845. 数组中的最长山脉、0088. 合并两个有序数组、0719. 找出第 K 小的数对距离、0334. 递增的三元子序列、0978. 最长湍流子数组、剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 - -#### 分离双指针题目 - -###### 0350. 两个数组的交集 II、0925. 长按键入、0844. 比较含退格的字符串、1229. 安排会议日程、0415. 字符串相加 - -### [滑动窗口题目](../../Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -#### 固定长度窗口题目 - -###### 1343. 大小为 K 且平均值大于等于阈值的子数组数目、0643. 子数组最大平均数 I、1052. 爱生气的书店老板、1423. 可获得的最大点数、1456. 定长子串中元音的最大数目、0567. 字符串的排列、1100. 长度为 K 的无重复字符子串、1151. 最少交换次数来组合所有的 1、1176. 健身计划评估、0438. 找到字符串中所有字母异位词、0995. K 连续位的最小翻转次数、0683. K 个关闭的灯泡、0220. 存在重复元素 III、0239. 滑动窗口最大值、0480. 滑动窗口中位数 - -#### 不定长度窗口题目 - -###### 0674. 最长连续递增序列、0485. 最大连续 1 的个数、0487. 最大连续1的个数 II、0076. 最小覆盖子串、0718. 最长重复子数组、0209. 长度最小的子数组、0862. 和至少为 K 的最短子数组、1004. 最大连续1的个数 III、1658. 将 x 减到 0 的最小操作数、0424. 替换后的最长重复字符、0003. 无重复字符的最长子串、1695. 删除子数组的最大得分、1208. 尽可能使字符串相等、1493. 删掉一个元素以后全为 1 的最长子数组、0727. 最小窗口子序列、0159. 至多包含两个不同字符的最长子串、0340. 至多包含 K 个不同字符的最长子串、0795. 区间子数组个数、0992. K 个不同整数的子数组、0713. 乘积小于 K 的子数组、0904. 水果成篮、1358. 包含所有三种字符的子字符串数目、0467. 环绕字符串中唯一的子字符串、1438. 绝对差不超过限制的最长连续子数组 - -## 02. 链表 - -### [链表经典题目](../../Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) - -###### 0707. 设计链表、0083. 删除排序链表中的重复元素、0082. 删除排序链表中的重复元素 II、0206. 反转链表、0092. 反转链表 II、0025. K 个一组翻转链表、0203. 移除链表元素、0328. 奇偶链表、0234. 回文链表、0430. 扁平化多级双向链表、0138. 复制带随机指针的链表、0061. 旋转链表 - -### [链表排序题目](../../Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) - -###### 0148. 排序链表、0021. 合并两个有序链表、0023. 合并 K 个升序链表、0147. 对链表进行插入排序 - -### [链表双指针题目](../../Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - -###### 0141. 环形链表、0142. 环形链表 II、0160. 相交链表、0019. 删除链表的倒数第 N 个结点、0876. 链表的中间结点、剑指 Offer 22. 链表中倒数第k个节点、0143. 重排链表、0002. 两数相加、0445. 两数相加 II - -## 03. 堆栈 - -### [堆栈基础题目](../../Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) - -###### 1047. 删除字符串中的所有相邻重复项、0155. 最小栈、0020. 有效的括号、0227. 基本计算器 II、0739. 每日温度、0150. 逆波兰表达式求值、0232. 用栈实现队列、剑指 Offer 09. 用两个栈实现队列、0394. 字符串解码、0032. 最长有效括号、0946. 验证栈序列、剑指 Offer 06. 从尾到头打印链表、0071. 简化路径 - -### [单调栈](../../Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -###### 0739. 每日温度、0496. 下一个更大元素 I、0503. 下一个更大元素 II、0901. 股票价格跨度、0084. 柱状图中最大的矩形、0316. 去除重复字母、0042. 接雨水、0085. 最大矩形 - -## 04. 队列 - -### [队列基础题目](../../Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) - -###### 0622. 设计循环队列、0346. 数据流中的移动平均值、0225. 用队列实现栈 - -### [优先队列题目](../../Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -###### 0703. 数据流中的第 K 大元素、0347. 前 K 个高频元素、0451. 根据字符出现频率排序、0973. 最接近原点的 K 个点、1296. 划分数组为连续数字的集合、0239. 滑动窗口最大值、0295. 数据流的中位数、0023. 合并 K 个升序链表、0218. 天际线问题 - -## 05. 哈希表 - -### [哈希表题目](../../Contents/05.Hash-Table/02.Hash-Table-List.md) - -###### 0705. 设计哈希集合、0706. 设计哈希映射、0217. 存在重复元素、0219. 存在重复元素 II、0220. 存在重复元素 III、1941. 检查是否所有字符出现次数相同、0136. 只出现一次的数字、0383. 赎金信、0349. 两个数组的交集、0350. 两个数组的交集 II、0036. 有效的数独、0001. 两数之和、0015. 三数之和、0018. 四数之和、0454. 四数相加 II、0041. 缺失的第一个正数、0128. 最长连续序列、0202. 快乐数、0242. 有效的字母异位词、0205. 同构字符串、0442. 数组中重复的数据、剑指 Offer 61. 扑克牌中的顺子、0268. 丢失的数字、剑指 Offer 03. 数组中重复的数字、0451. 根据字符出现频率排序、0049. 字母异位词分组、0599. 两个列表的最小索引总和、0387. 字符串中的第一个唯一字符、0447. 回旋镖的数量、0149. 直线上最多的点数、0359. 日志速率限制器、0811. 子域名访问计数 - -## 06. 字符串 - -### [字符串基础题目](../../Contents/06.String/01.String-Basic/02.String-Basic-List.md) - -###### 0125. 验证回文串、0005. 最长回文子串、0003. 无重复字符的最长子串、0344. 反转字符串、0557. 反转字符串中的单词 III、0049. 字母异位词分组、0415. 字符串相加、0151. 反转字符串中的单词、0043. 字符串相乘、0014. 最长公共前缀 - -### [单模式串匹配题目](../../Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) - -###### 0028. 找出字符串中第一个匹配项的下标、0459. 重复的子字符串、0686. 重复叠加字符串匹配、1668. 最大重复子字符串、0796. 旋转字符串、1408. 数组中的字符串匹配、2156. 查找给定哈希值的子串 - -### [字典树题目](../../Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) - -###### 0208. 实现 Trie (前缀树)、0677. 键值映射、0648. 单词替换、0642. 设计搜索自动补全系统、0211. 添加与搜索单词 - 数据结构设计、0421. 数组中两个数的最大异或值、0212. 单词搜索 II、0425. 单词方块、0336. 回文对、1023. 驼峰式匹配、0676. 实现一个魔法字典、0440. 字典序的第K小数字 - -## 07. 树 - -### [二叉树的遍历题目](../../Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - -###### 0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0145. 二叉树的后序遍历、0102. 二叉树的层序遍历、0103. 二叉树的锯齿形层序遍历、0107. 二叉树的层序遍历 II、0104. 二叉树的最大深度、0111. 二叉树的最小深度、0124. 二叉树中的最大路径和、0101. 对称二叉树、0112. 路径总和、0113. 路径总和 II、0236. 二叉树的最近公共祖先、0199. 二叉树的右视图、0226. 翻转二叉树、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0116. 填充每个节点的下一个右侧节点指针、0117. 填充每个节点的下一个右侧节点指针 II、0297. 二叉树的序列化与反序列化、0114. 二叉树展开为链表 - -### [二叉树的还原题目](../../Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) - -###### 0105. 从前序与中序遍历序列构造二叉树、0106. 从中序与后序遍历序列构造二叉树、0889. 根据前序和后序遍历构造二叉树 - -### [二叉搜索树题目](../../Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) - -###### 0098. 验证二叉搜索树、0173. 二叉搜索树迭代器、0700. 二叉搜索树中的搜索、0701. 二叉搜索树中的插入操作、0450. 删除二叉搜索树中的节点、0703. 数据流中的第 K 大元素、剑指 Offer 54. 二叉搜索树的第k大节点、0230. 二叉搜索树中第K小的元素、0235. 二叉搜索树的最近公共祖先、0426. 将二叉搜索树转化为排序的双向链表、0108. 将有序数组转换为二叉搜索树、0110. 平衡二叉树 - -### [线段树题目](../../Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) - -#### 单点更新题目 - -###### 0303. 区域和检索 - 数组不可变、0307. 区域和检索 - 数组可修改、0354. 俄罗斯套娃信封问题 - -#### 区间更新题目 - -###### 0370. 区间加法、1109. 航班预订统计、1450. 在既定时间做作业的学生人数、0673. 最长递增子序列的个数、1310. 子数组异或查询、1851. 包含每个查询的最小区间 - -#### 区间合并题目 - -###### 0729. 我的日程安排表 I、0731. 我的日程安排表 II、0732. 我的日程安排表 III - -#### 扫描线问题 - -###### 0218. 天际线问题、0391. 完美矩形、0850. 矩形面积 II - -### [树状数组题目](../../Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) - -###### 0303. 区域和检索 - 数组不可变、0307. 区域和检索 - 数组可修改、0315. 计算右侧小于当前元素的个数、1450. 在既定时间做作业的学生人数、0354. 俄罗斯套娃信封问题、0673. 最长递增子序列的个数、1310. 子数组异或查询、1893. 检查是否区域内所有整数都被覆盖 - -### [并查集题目](../../Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) - -###### 0990. 等式方程的可满足性、0547. 省份数量、0684. 冗余连接、1319. 连通网络的操作次数、0765. 情侣牵手、0399. 除法求值、0959. 由斜杠划分区域、1631. 最小体力消耗路径、0778. 水位上升的泳池中游泳、1202. 交换字符串中的元素、0947. 移除最多的同行或同列石头、0803. 打砖块、0128. 最长连续序列 - -## 08. 图论 - -### [图的深度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - -###### 0797. 所有可能的路径、0200. 岛屿数量、0695. 岛屿的最大面积、0133. 克隆图、0494. 目标和、0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0145. 二叉树的后序遍历、0589. N 叉树的前序遍历、0590. N 叉树的后序遍历、0124. 二叉树中的最大路径和、0199. 二叉树的右视图、0543. 二叉树的直径、0662. 二叉树最大宽度、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0111. 二叉树的最小深度、0841. 钥匙和房间、0129. 求根节点到叶节点数字之和、0323. 无向图中连通分量的数目、0684. 冗余连接、0802. 找到最终的安全状态、0785. 判断二分图、0886. 可能的二分法、0323. 无向图中连通分量的数目、0130. 被围绕的区域、0417. 太平洋大西洋水流问题、1020. 飞地的数量、1254. 统计封闭岛屿的数目、1034. 边界着色、剑指 Offer 13. 机器人的运动范围、0529. 扫雷游戏 - -### [图的广度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - -###### 0797. 所有可能的路径、0286. 墙与门、0200. 岛屿数量、0752. 打开转盘锁、0279. 完全平方数、0133. 克隆图、0733. 图像渲染、0542. 01 矩阵、0322. 零钱兑换、0323. 无向图中连通分量的数目、剑指 Offer 13. 机器人的运动范围、0199. 二叉树的右视图、0662. 二叉树最大宽度、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0111. 二叉树的最小深度、剑指 Offer 32 - III. 从上到下打印二叉树 III - -### [图的拓扑排序题目](../../Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) - -###### 0207. 课程表、0210. 课程表 II、1136. 并行课程、2050. 并行课程 III、0802. 找到最终的安全状态、0851. 喧闹和富有 - -### [图的最小生成树题目](../../Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) - -###### 1584. 连接所有点的最小费用、1631. 最小体力消耗路径、0778. 水位上升的泳池中游泳 - -### [单源最短路径题目](../../Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) - -###### 0407. 接雨水 II、0743. 网络延迟时间、0787. K 站中转内最便宜的航班、1631. 最小体力消耗路径、1786. 从第一个节点出发到最后一个节点的受限路径数 - -### [多源最短路径题目](../../Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) - -###### 0815. 公交路线、1162. 地图分析 - -### [次短路径题目](../../Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) - -###### 2045. 到达目的地的第二短时间 - -### [差分约束系统](../../Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) - -###### 0995. K 连续位的最小翻转次数、1109. 航班预订统计 - -### [二分图基础题目](../../Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) - -###### 0785. 判断二分图 - -### [二分图最大匹配题目](../../Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) - -###### LCP 04. 覆盖、1947. 最大兼容性评分和、1595. 连通两组点的最小成本 - -## 09. 基础算法 - -### [枚举算法题目](../../Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) - -###### 0001. 两数之和、0204. 计数质数、1925. 统计平方和三元组的数目、1450. 在既定时间做作业的学生人数、1620. 网络信号最好的坐标、剑指 Offer 57 - II. 和为s的连续正数序列、0800. 相似 RGB 颜色、0221. 最大正方形、0560. 和为 K 的子数组 - -### [递归算法题目](../../Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) - -###### 0344. 反转字符串、0024. 两两交换链表中的节点、0118. 杨辉三角、0119. 杨辉三角 II、0206. 反转链表、0092. 反转链表 II、0021. 合并两个有序链表、0509. 斐波那契数、0070. 爬楼梯、0104. 二叉树的最大深度、0124. 二叉树中的最大路径和、0226. 翻转二叉树、0050. Pow(x, n)、0779. 第K个语法符号、0095. 不同的二叉搜索树 II、剑指 Offer 62. 圆圈中最后剩下的数字 - -### [分治算法题目](../../Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) - -###### 0004. 寻找两个正序数组的中位数、0023. 合并 K 个升序链表、0053. 最大子数组和、0241. 为运算表达式设计优先级、0169. 多数元素、0050. Pow(x, n)、0014. 最长公共前缀、剑指 Offer 33. 二叉搜索树的后序遍历序列 - -### [回溯算法题目](../../Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) - -###### 0046. 全排列、0047. 全排列 II、0037. 解数独、0022. 括号生成、0017. 电话号码的字母组合、0784. 字母大小写全排列、0039. 组合总和、0040. 组合总和 II、0078. 子集、0090. 子集 II、0473. 火柴拼正方形、1593. 拆分字符串使唯一子字符串的数目最大、1079. 活字印刷、0093. 复原 IP 地址、0079. 单词搜索、0679. 24 点游戏 - -### [贪心算法题目](../../Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) - -###### 0455. 分发饼干、0860. 柠檬水找零、0056. 合并区间、0435. 无重叠区间、0452. 用最少数量的箭引爆气球、0055. 跳跃游戏、0045. 跳跃游戏 II、0392. 判断子序列、0122. 买卖股票的最佳时机 II、0561. 数组拆分、1710. 卡车上的最大单元数、1217. 玩筹码、1247. 交换字符使得字符串相同、1400. 构造 K 个回文字符串、0921. 使括号有效的最少添加、1029. 两地调度、1605. 给定行和列的和求可行矩阵、0135. 分发糖果、0134. 加油站、0053. 最大子数组和、0376. 摆动序列、0738. 单调递增的数字、0402. 移掉 K 位数字、0861. 翻转矩阵后的得分、0670. 最大交换 - -### [位运算题目](../../Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) - -###### 0504. 七进制数、0405. 数字转换为十六进制数、0190. 颠倒二进制位、1009. 十进制整数的反码、0191. 位1的个数、0371. 两整数之和、0089. 格雷编码、0201. 数字范围按位与、0338. 比特位计数、0136. 只出现一次的数字、0137. 只出现一次的数字 II、0260. 只出现一次的数字 III、0268. 丢失的数字、1349. 参加考试的最大学生数、0645. 错误的集合、0078. 子集、0090. 子集 II - -## 10. 动态规划 - -### [动态规划基础题目](../../Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) - -###### 0509. 斐波那契数、0070. 爬楼梯、0062. 不同路径 - -### [记忆化搜索题目](../../Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) - -###### 1137. 第 N 个泰波那契数、0375. 猜数字大小 II、0494. 目标和、0576. 出界的路径数、0087. 扰乱字符串、0403. 青蛙过河、0552. 学生出勤记录 II、0913. 猫和老鼠、0329. 矩阵中的最长递增路径 - -### [线性 DP 题目](../../Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) - -#### 单串线性 DP 问题 - -###### 0300. 最长递增子序列、0673. 最长递增子序列的个数、0354. 俄罗斯套娃信封问题、0053. 最大子数组和、0152. 乘积最大子数组、0918. 环形子数组的最大和、0198. 打家劫舍、0213. 打家劫舍 II、0740. 删除并获得点数、1388. 3n 块披萨、0873. 最长的斐波那契子序列的长度、1027. 最长等差数列、1055. 形成字符串的最短路径、0368. 最大整除子集、0032. 最长有效括号、0413. 等差数列划分、0091. 解码方法、0639. 解码方法 II、0132. 分割回文串 II、1220. 统计元音字母序列的数目、0338. 比特位计数、0801. 使序列递增的最小交换次数、0871. 最低加油次数、0045. 跳跃游戏 II、0813. 最大平均值和的分组、0887. 鸡蛋掉落、0256. 粉刷房子、0265. 粉刷房子 II、1473. 粉刷房子 III、0975. 奇偶跳、0403. 青蛙过河、1478. 安排邮筒、1230. 抛掷硬币、0410. 分割数组的最大值、1751. 最多可以参加的会议数目 II、1787. 使所有区间的异或结果为零、0121. 买卖股票的最佳时机、0122. 买卖股票的最佳时机 II、0123. 买卖股票的最佳时机 III、0188. 买卖股票的最佳时机 IV、0309. 最佳买卖股票时机含冷冻期、0714. 买卖股票的最佳时机含手续费 - -#### 双串线性 DP 问题 - -###### 1143. 最长公共子序列、0712. 两个字符串的最小ASCII删除和、0718. 最长重复子数组、0583. 两个字符串的删除操作、0072. 编辑距离、0044. 通配符匹配、0010. 正则表达式匹配、0097. 交错字符串、0115. 不同的子序列、0087. 扰乱字符串 - -#### 矩阵线性 DP 问题 - -###### 0118. 杨辉三角、0119. 杨辉三角 II、0120. 三角形最小路径和、0064. 最小路径和、0174. 地下城游戏、0221. 最大正方形、0931. 下降路径最小和、0576. 出界的路径数、0085. 最大矩形、0363. 矩形区域不超过 K 的最大数值和、面试题 17.24. 最大子矩阵、1444. 切披萨的方案数 - -#### 无串线性 DP 问题 - -###### 1137. 第 N 个泰波那契数、0650. 只有两个键的键盘、0264. 丑数 II、0279. 完全平方数、0343. 整数拆分 - -### [背包问题题目](../../Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) - -#### 0-1 背包问题 - -###### 0416. 分割等和子集、0494. 目标和、1049. 最后一块石头的重量 II - -#### 完全背包问题 - -###### 0279. 完全平方数、0322. 零钱兑换、0518. 零钱兑换 II、0139. 单词拆分、0377. 组合总和 Ⅳ、0638. 大礼包、1449. 数位成本和为目标值的最大数字 - -#### 多重背包问题 - -#### 分组背包问题 - -###### 1155. 掷骰子等于目标和的方法数、2585. 获得分数的方法数 - -#### 多维背包问题 - -###### 0474. 一和零、0879. 盈利计划、1995. 统计特殊四元组 - -### [区间 DP 题目](../../Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) - -###### 0486. 预测赢家、0312. 戳气球、0877. 石子游戏、1000. 合并石头的最低成本、1547. 切棍子的最小成本、0664. 奇怪的打印机、1039. 多边形三角剖分的最低得分、0546. 移除盒子、0375. 猜数字大小 II、0678. 有效的括号字符串、0005. 最长回文子串、0516. 最长回文子序列、0730. 统计不同回文子序列、2104. 子数组范围和 - -### [树形 DP 题目](../../Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) - -#### 固定根的树形 DP 题目 - -###### 0543. 二叉树的直径、0124. 二叉树中的最大路径和、1245. 树的直径、2246. 相邻字符不同的最长路径、0687. 最长同值路径、0337. 打家劫舍 III、0333. 最大 BST 子树、1617. 统计子树中城市之间最大距离、2538. 最大价值和与最小价值和的差值、1569. 将子数组重新排序得到同一个二叉搜索树的方案数、1372. 二叉树中的最长交错路径、1373. 二叉搜索子树的最大键值和、0968. 监控二叉树、1273. 删除树节点、1519. 子树中标签相同的节点数 - -#### 不定根的树形 DP 题目 - -###### 0310. 最小高度树、0834. 树中距离之和、2581. 统计可能的树根数目 - -### [状态压缩 DP 题目](../../Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) - -###### 1879. 两个数组最小的异或值之和、2172. 数组的最大与和、1947. 最大兼容性评分和、1595. 连通两组点的最小成本、1494. 并行课程 II、1655. 分配重复整数、1986. 完成任务的最少工作时间段、1434. 每个人戴不同帽子的方案数、1799. N 次操作后的最大分数和、1681. 最小不兼容性、0526. 优美的排列、0351. 安卓系统手势解锁、0464. 我能赢吗、0847. 访问所有节点的最短路径、0638. 大礼包、1994. 好子集的数目、1349. 参加考试的最大学生数、0698. 划分为k个相等的子集、0943. 最短超级串、0691. 贴纸拼词、0982. 按位与为零的三元组 - -### [计数 DP 题目](../../Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) - -###### 0062. 不同路径、0063. 不同路径 II、0343. 整数拆分、0096. 不同的二叉搜索树、1259. 不相交的握手、0790. 多米诺和托米诺平铺、0070. 爬楼梯、0746. 使用最小花费爬楼梯、0509. 斐波那契数、1137. 第 N 个泰波那契数 - -### [数位 DP 题目](../../Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) - -###### 2376. 统计特殊整数、0357. 统计各位数字都不同的数字个数、1012. 至少有 1 位重复的数字、0902. 最大为 N 的数字组合、0788. 旋转数字、0600. 不含连续1的非负整数、0233. 数字 1 的个数、2719. 统计整数数目、0248. 中心对称数 III、1088. 易混淆数 II、1067. 范围内的数字计数、1742. 盒子中小球的最大数量、面试题 17.06. 2出现的次数 - -### [概率 DP 题目](../../Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) - -###### 0688. 骑士在棋盘上的概率、0808. 分汤、0837. 新 21 点、1230. 抛掷硬币、1467. 两个盒子中球的颜色数相同的概率、1227. 飞机座位分配概率、1377. T 秒后青蛙的位置、剑指 Offer 60. n个骰子的点数 - -### [动态规划优化题目](../../Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) diff --git a/Assets/Origins/Interview-100-List.md b/Assets/Origins/Interview-100-List.md deleted file mode 100644 index ba902907..00000000 --- a/Assets/Origins/Interview-100-List.md +++ /dev/null @@ -1,194 +0,0 @@ -## 01. 数组 - -### [数组基础题目](../../Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) - -###### 0054. 螺旋矩阵、0048. 旋转图像 - -### [排序算法题目](../../Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) - -#### 选择排序题目 - -###### 0215. 数组中的第K个最大元素 - -#### 希尔排序题目 - -###### 0912. 排序数组 - -#### 归并排序题目 - -###### 0912. 排序数组、0088. 合并两个有序数组 - -#### 快速排序题目 - -###### 0912. 排序数组、0169. 多数元素 - -#### 堆排序题目 - -###### 0912. 排序数组、0215. 数组中的第K个最大元素 - -#### 计数排序题目 - -###### 0912. 排序数组 - -#### 桶排序题目 - -###### 0912. 排序数组 - -#### 其他排序题目 - -###### 0136. 只出现一次的数字、0056. 合并区间、0179. 最大数 - -### [二分查找题目](../../Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) - -#### 二分下标题目 - -###### 0704. 二分查找、0034. 在排序数组中查找元素的第一个和最后一个位置、0153. 寻找旋转排序数组中的最小值、0033. 搜索旋转排序数组、0162. 寻找峰值、0004. 寻找两个正序数组的中位数、0240. 搜索二维矩阵 II - -#### 二分答案题目 - -###### 0069. x 的平方根 - -### [双指针题目](../../Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) - -#### 对撞指针题目 - -###### 0015. 三数之和 - -#### 快慢指针题目 - -###### 0283. 移动零、0088. 合并两个有序数组 - -#### 分离双指针题目 - -###### 0415. 字符串相加 - -### [滑动窗口题目](../../Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -#### 固定长度窗口题目 - -###### 0239. 滑动窗口最大值 - -#### 不定长度窗口题目 - -###### 0003. 无重复字符的最长子串、0076. 最小覆盖子串、0718. 最长重复子数组 - -## 02. 链表 - -### [链表经典题目](../../Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) - -###### 0083. 删除排序链表中的重复元素、0082. 删除排序链表中的重复元素 II、0206. 反转链表、0092. 反转链表 II、0025. K 个一组翻转链表、0234. 回文链表 - -### [链表排序题目](../../Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) - -###### 0148. 排序链表、0021. 合并两个有序链表、0023. 合并 K 个升序链表 - -### [链表双指针题目](../../Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - -###### 0141. 环形链表、0142. 环形链表 II、0160. 相交链表、0019. 删除链表的倒数第 N 个结点、剑指 Offer 22. 链表中倒数第k个节点、0143. 重排链表、0002. 两数相加 - -## 03. 堆栈 - -### [堆栈基础题目](../../Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) - -###### 0155. 最小栈、0020. 有效的括号、0227. 基本计算器 II、0232. 用栈实现队列、0394. 字符串解码、0032. 最长有效括号 - -### [单调栈](../../Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -###### 0042. 接雨水 - -## 04. 队列 - -### [队列基础题目](../../Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) - -###### 0225. 用队列实现栈 - -### [优先队列题目](../../Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -###### 0023. 合并 K 个升序链表、0239. 滑动窗口最大值 - -## 05. 哈希表 - -### [哈希表题目](../../Contents/05.Hash-Table/02.Hash-Table-List.md) - -###### 0001. 两数之和、0015. 三数之和、0041. 缺失的第一个正数、0128. 最长连续序列、0136. 只出现一次的数字 - -## 06. 字符串 - -### [字符串基础题目](../../Contents/06.String/01.String-Basic/02.String-Basic-List.md) - -###### 0003. 无重复字符的最长子串、0005. 最长回文子串、0415. 字符串相加、0151. 反转字符串中的单词、0043. 字符串相乘、0014. 最长公共前缀 - -## 07. 树 - -### [二叉树的遍历题目](../../Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - -###### 0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0102. 二叉树的层序遍历、0103. 二叉树的锯齿形层序遍历、0236. 二叉树的最近公共祖先、0104. 二叉树的最大深度、0112. 路径总和、0113. 路径总和 II、0101. 对称二叉树、0124. 二叉树中的最大路径和、0199. 二叉树的右视图、0226. 翻转二叉树 - -### [二叉树的还原题目](../../Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) - -###### 0105. 从前序与中序遍历序列构造二叉树 - -### [二叉搜索树题目](../../Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) - -###### 0098. 验证二叉搜索树、0110. 平衡二叉树 - -### [并查集题目](../../Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) - -###### 0128. 最长连续序列 - -## 08. 图论 - -### [图的深度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - -###### 0200. 岛屿数量、0695. 岛屿的最大面积、0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0129. 求根节点到叶节点数字之和、0124. 二叉树中的最大路径和、0199. 二叉树的右视图、0543. 二叉树的直径、0662. 二叉树最大宽度 - -### [图的广度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - -###### 0200. 岛屿数量、0322. 零钱兑换、0199. 二叉树的右视图、0662. 二叉树最大宽度 - -## 09. 基础算法 - -### [枚举算法题目](../../Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) - -###### 0001. 两数之和、0078. 子集、0221. 最大正方形 - -### [递归算法题目](../../Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) - -###### 0024. 两两交换链表中的节点、0206. 反转链表、0092. 反转链表 II、0021. 合并两个有序链表、0070. 爬楼梯、0104. 二叉树的最大深度、0124. 二叉树中的最大路径和、0226. 翻转二叉树 - -### [分治算法题目](../../Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) - -###### 0053. 最大子数组和、0023. 合并 K 个升序链表、0004. 寻找两个正序数组的中位数、0169. 多数元素、0014. 最长公共前缀 - -### [回溯算法题目](../../Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) - -###### 0046. 全排列、0022. 括号生成、0078. 子集、0039. 组合总和、0093. 复原 IP 地址 - -### [贪心算法题目](../../Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) - -###### 0053. 最大子数组和、0056. 合并区间、0122. 买卖股票的最佳时机 II - -### [位运算题目](../../Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) - -###### 0136. 只出现一次的数字 - -## 10. 动态规划 - -### [动态规划题目](../../Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) - -###### 0070. 爬楼梯、0121. 买卖股票的最佳时机、0322. 零钱兑换、0300. 最长递增子序列、1143. 最长公共子序列、0718. 最长重复子数组、0064. 最小路径和、0072. 编辑距离、0032. 最长有效括号、0221. 最大正方形、0062. 不同路径、0152. 乘积最大子数组、0198. 打家劫舍 - - -## 11. 补充题目 - -#### 设计数据结构题目 - -###### 0146. LRU 缓存 - -#### 模拟题目 - -###### 0008. 字符串转换整数 (atoi)、0165. 比较版本号、0468. 验证IP地址 - -#### 思维锻炼题目 - -###### 0031. 下一个排列、0470. 用 Rand7() 实现 Rand10() diff --git a/Assets/Origins/Interview-200-List.md b/Assets/Origins/Interview-200-List.md deleted file mode 100644 index cf0aec64..00000000 --- a/Assets/Origins/Interview-200-List.md +++ /dev/null @@ -1,233 +0,0 @@ -## 01. 数组 - -### [数组基础题目](../../Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) - -###### 0189. 轮转数组、0498. 对角线遍历、0048. 旋转图像、0054. 螺旋矩阵、0059. 螺旋矩阵 II - -### [排序算法题目](../../Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) - -#### 冒泡排序题目 - -###### 0283. 移动零 - -#### 选择排序题目 - -###### 0215. 数组中的第K个最大元素 - -#### 插入排序题目 - -###### 0075. 颜色分类 - -#### 希尔排序题目 - -###### 0912. 排序数组 - -#### 归并排序题目 - -###### 0912. 排序数组、0088. 合并两个有序数组、剑指 Offer 51. 数组中的逆序对 - -#### 快速排序题目 - -###### 0912. 排序数组、0169. 多数元素 - -#### 堆排序题目 - -###### 0912. 排序数组、0215. 数组中的第K个最大元素、剑指 Offer 40. 最小的k个数 - -#### 计数排序题目 - -###### 0912. 排序数组 - -#### 桶排序题目 - -###### 0912. 排序数组 - -#### 基数排序题目 - -###### 0164. 最大间距 - -#### 其他排序题目 - -###### 0136. 只出现一次的数字、0056. 合并区间、0179. 最大数、0384. 打乱数组、剑指 Offer 45. 把数组排成最小的数 - -### [二分查找题目](../../Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) - -#### 二分下标题目 - -###### 0704. 二分查找、0034. 在排序数组中查找元素的第一个和最后一个位置、0153. 寻找旋转排序数组中的最小值、0154. 寻找旋转排序数组中的最小值 II、0033. 搜索旋转排序数组、0162. 寻找峰值、0004. 寻找两个正序数组的中位数、0074. 搜索二维矩阵、0240. 搜索二维矩阵 II - -#### 二分答案题目 - -###### 0069. x 的平方根、0287. 寻找重复数、0050. Pow(x, n)、0400. 第 N 位数字 - -#### 复杂的二分查找问题 - -###### 0209. 长度最小的子数组、0349. 两个数组的交集 - -### [双指针题目](../../Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) - -#### 对撞指针题目 - -###### 0611. 有效三角形的个数、0015. 三数之和、0016. 最接近的三数之和、0125. 验证回文串、0011. 盛最多水的容器、0075. 颜色分类、剑指 Offer 21. 调整数组顺序使奇数位于偶数前面、0443. 压缩字符串 - -#### 快慢指针题目 - -###### 0026. 删除有序数组中的重复项、0283. 移动零、0088. 合并两个有序数组 - -#### 分离双指针题目 - -###### 0415. 字符串相加 - -### [滑动窗口题目](../../Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -#### 固定长度窗口题目 - -###### 0239. 滑动窗口最大值 - -#### 不定长度窗口题目 - -###### 0003. 无重复字符的最长子串、0076. 最小覆盖子串、0718. 最长重复子数组、0209. 长度最小的子数组、0862. 和至少为 K 的最短子数组、1004. 最大连续1的个数 III - -## 02. 链表 - -### [链表经典题目](../../Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) - -###### 0083. 删除排序链表中的重复元素、0082. 删除排序链表中的重复元素 II、0206. 反转链表、0092. 反转链表 II、0025. K 个一组翻转链表、0328. 奇偶链表、0234. 回文链表、0138. 复制带随机指针的链表、0061. 旋转链表 - -### [链表排序题目](../../Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) - -###### 0148. 排序链表、0021. 合并两个有序链表、0023. 合并 K 个升序链表 - -### [链表双指针题目](../../Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - -###### 0141. 环形链表、0142. 环形链表 II、0160. 相交链表、0019. 删除链表的倒数第 N 个结点、剑指 Offer 22. 链表中倒数第k个节点、0143. 重排链表、0002. 两数相加、0445. 两数相加 II - -## 03. 堆栈 - -### [堆栈基础题目](../../Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) - -###### 1047. 删除字符串中的所有相邻重复项、0155. 最小栈、0020. 有效的括号、0224. 基本计算器、0227. 基本计算器 II、0232. 用栈实现队列、剑指 Offer 09. 用两个栈实现队列、0394. 字符串解码、0032. 最长有效括号、0739. 每日温度、0071. 简化路径 - -### [单调栈](../../Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -###### 0739. 每日温度、0503. 下一个更大元素 II、0042. 接雨水、0085. 最大矩形 - -## 04. 队列 - -### [队列基础题目](../../Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) - -###### 0225. 用队列实现栈 - -### [优先队列题目](../../Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -###### 0347. 前 K 个高频元素、0239. 滑动窗口最大值、0295. 数据流的中位数、0023. 合并 K 个升序链表 - -## 05. 哈希表 - -### [哈希表题目](../../Contents/05.Hash-Table/02.Hash-Table-List.md) - -###### 0001. 两数之和、0015. 三数之和、0041. 缺失的第一个正数、0128. 最长连续序列、0136. 只出现一次的数字、0242. 有效的字母异位词、0442. 数组中重复的数据、剑指 Offer 61. 扑克牌中的顺子、0268. 丢失的数字、剑指 Offer 03. 数组中重复的数字 - -## 06. 字符串 - -### [字符串基础题目](../../Contents/06.String/01.String-Basic/02.String-Basic-List.md) - -###### 0125. 验证回文串、0005. 最长回文子串、0003. 无重复字符的最长子串、0344. 反转字符串、0557. 反转字符串中的单词 III、0415. 字符串相加、0151. 反转字符串中的单词、0043. 字符串相乘、0014. 最长公共前缀 - -### [单模式串匹配题目](../../Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) - -###### 0459. 重复的子字符串 - -### [字典树题目](../../Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) - -###### 0208. 实现 Trie (前缀树)、0440. 字典序的第K小数字 - -## 07. 树 - -### [二叉树的遍历题目](../../Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - -###### 0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0145. 二叉树的后序遍历、0102. 二叉树的层序遍历、0103. 二叉树的锯齿形层序遍历、0104. 二叉树的最大深度、0111. 二叉树的最小深度、0124. 二叉树中的最大路径和、0101. 对称二叉树、0112. 路径总和、0113. 路径总和 II、0236. 二叉树的最近公共祖先、0199. 二叉树的右视图、0226. 翻转二叉树、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0297. 二叉树的序列化与反序列化、0114. 二叉树展开为链表 - -### [二叉树的还原题目](../../Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) - -###### 0105. 从前序与中序遍历序列构造二叉树、0106. 从中序与后序遍历序列构造二叉树 - -### [二叉搜索树题目](../../Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) - -###### 0098. 验证二叉搜索树、0450. 删除二叉搜索树中的节点、剑指 Offer 54. 二叉搜索树的第k大节点、0230. 二叉搜索树中第K小的元素、0426. 将二叉搜索树转化为排序的双向链表、0110. 平衡二叉树 - -### [并查集题目](../../Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) - -###### 0128. 最长连续序列 - -## 08. 图论 - -### [图的深度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - -###### 0200. 岛屿数量、0695. 岛屿的最大面积、0144. 二叉树的前序遍历、0094. 二叉树的中序遍历、0145. 二叉树的后序遍历、0129. 求根节点到叶节点数字之和、0124. 二叉树中的最大路径和、0199. 二叉树的右视图、0543. 二叉树的直径、0662. 二叉树最大宽度、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0111. 二叉树的最小深度 - -### [图的广度优先搜索题目](../../Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - -###### 0200. 岛屿数量、0322. 零钱兑换、0207. 课程表、0199. 二叉树的右视图、0662. 二叉树最大宽度、0958. 二叉树的完全性检验、0572. 另一棵树的子树、0100. 相同的树、0111. 二叉树的最小深度、剑指 Offer 32 - III. 从上到下打印二叉树 III - -### [图的拓扑排序题目](../../Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) - -###### 0210. 课程表 II - -## 09. 基础算法 - -### [枚举算法题目](../../Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) - -###### 0001. 两数之和、0078. 子集、0221. 最大正方形、0560. 和为 K 的子数组 - -### [递归算法题目](../../Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) - -###### 0024. 两两交换链表中的节点、0206. 反转链表、0092. 反转链表 II、0021. 合并两个有序链表、0509. 斐波那契数、0070. 爬楼梯、0104. 二叉树的最大深度、0124. 二叉树中的最大路径和、0226. 翻转二叉树、剑指 Offer 62. 圆圈中最后剩下的数字 - -### [分治算法题目](../../Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) - -###### 0004. 寻找两个正序数组的中位数、0023. 合并 K 个升序链表、0053. 最大子数组和、0169. 多数元素、0014. 最长公共前缀、剑指 Offer 33. 二叉搜索树的后序遍历序列 - -### [回溯算法题目](../../Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) - -###### 0046. 全排列、0047. 全排列 II、0037. 解数独、0022. 括号生成、0078. 子集、0039. 组合总和、0040. 组合总和 II、0093. 复原 IP 地址、0079. 单词搜索、0679. 24 点游戏 - -### [贪心算法题目](../../Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) - -###### 0053. 最大子数组和、0056. 合并区间、0122. 买卖股票的最佳时机 II、0055. 跳跃游戏、0402. 移掉 K 位数字、0135. 分发糖果、0134. 加油站、0670. 最大交换 - -### [位运算题目](../../Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) - -###### 0136. 只出现一次的数字、0191. 位1的个数、0268. 丢失的数字 - -## 10. 动态规划 - -### [动态规划题目](../../Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) - -###### 0070. 爬楼梯、0509. 斐波那契数、0121. 买卖股票的最佳时机、0322. 零钱兑换、0518. 零钱兑换 II、0300. 最长递增子序列、1143. 最长公共子序列、0718. 最长重复子数组、0064. 最小路径和、0072. 编辑距离、0032. 最长有效括号、0221. 最大正方形、0062. 不同路径、0063. 不同路径 II、0152. 乘积最大子数组、0198. 打家劫舍、0213. 打家劫舍 II、0091. 解码方法、0010. 正则表达式匹配、0678. 有效的括号字符串、0045. 跳跃游戏 II、0673. 最长递增子序列的个数、0139. 单词拆分、0044. 通配符匹配、0120. 三角形最小路径和、0096. 不同的二叉搜索树、0887. 鸡蛋掉落、0097. 交错字符串、0516. 最长回文子序列 - -### 记忆化搜索题目 - -###### 0329. 矩阵中的最长递增路径 - -## 11. 补充题目 - -#### 设计数据结构题目 - -###### 0146. LRU 缓存、0460. LFU 缓存 - -#### 数学题目 - -###### 0007. 整数反转、0009. 回文数、剑指 Offer 62. 圆圈中最后剩下的数字、0168. Excel表列名称、0400. 第 N 位数字 - -#### 模拟题目 - -###### 0008. 字符串转换整数 (atoi)、0165. 比较版本号、0468. 验证IP地址、0086. 分隔链表 - -#### 前缀和 - -###### 0560. 和为 K 的子数组 - -#### 思维锻炼题目 - -###### 0031. 下一个排列、0556. 下一个更大元素 III、0470. 用 Rand7() 实现 Rand10() \ No newline at end of file diff --git a/Assets/Origins/README-Catalogue-List.md b/Assets/Origins/README-Catalogue-List.md deleted file mode 100644 index 47a2bd20..00000000 --- a/Assets/Origins/README-Catalogue-List.md +++ /dev/null @@ -1,216 +0,0 @@ -# 内容章节 - -## 00. 绪论 - -- [算法与数据结构](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/01.Data-Structures-Algorithms.md) -- [算法复杂度](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/02.Algorithm-Complexity.md) -- [LeetCode 入门与攻略](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/03.LeetCode-Guide.md) -- [LeetCode 题解(字典序排序,700+ 道题解)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/04.Solutions-List.md) -- [LeetCode 题解(按分类排序,推荐刷题列表 ★★★)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/05.Categories-List.md) -- [LeetCode 面试最常考 100 题(按分类排序)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/06.Interview-100-List.md) -- [LeetCode 面试最常考 200 题(按分类排序)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/07.Interview-200-List.md) - -## 01. 数组 - -- 数组基础知识 - - [数组基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/01.Array-Basic.md) - - [数组基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) -- 数组排序算法 - - [冒泡排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md) - - [选择排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md) - - [插入排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md) - - [希尔排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md) - - [归并排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md) - - [快速排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md) - - [堆排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md) - - [计数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md) - - [桶排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md) - - [基数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md) - - [数组排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) -- 二分查找 - - [二分查找知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md) - - [二分查找知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md) - - [二分查找题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) -- 数组双指针 - - [数组双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md) - - [数组双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) -- 数组滑动窗口 - - [数组滑动窗口知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md) - - [数组滑动窗口题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -## 02. 链表 - -- 链表基础知识 - - [链表基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md) - - [链表经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) -- 链表排序 - - [链表排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md) - - [链表排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) -- 链表双指针 - - [链表双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md) - - [链表双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - -## 03. 堆栈 - -- 堆栈基础知识 - - [堆栈基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md) - - [堆栈基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) -- 单调栈 - - [单调栈知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md) - - [单调栈题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -## 04. 队列 - -- 队列基础知识 - - [队列基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md) - - [队列基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) -- 优先队列 - - [优先队列知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md) - - [优先队列题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -## 05. 哈希表 - -- [哈希表知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/05.Hash-Table/01.Hash-Table.md) -- [哈希表题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/05.Hash-Table/02.Hash-Table-List.md) - -## 06. 字符串 - -- 字符串基础知识 - - [字符串基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/01.String-Basic.md) - - [字符串经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/02.String-Basic-List.md) -- 单模式串匹配 - - [Brute Force 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md) - - [Rabin Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md) - - [KMP 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md) - - [Boyer Moore 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md) - - [Horspool 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md) - - [Sunday 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md) - - [单模式串匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) -- 多模式串匹配 - - [字典树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md) - - [字典树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) - - [AC 自动机知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md) - - [AC 自动机题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md) - - [后缀数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md) - - [后缀数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md) - - -## 07. 树 - -- 二叉树 - - [树与二叉树基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md) - - [二叉树的遍历知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md) - - [二叉树的遍历题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - - [二叉树的还原知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md) - - [二叉树的还原题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) -- 二叉搜索树 - - [二叉搜索树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md) - - [二叉搜索树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) -- 线段树 - - [线段树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md) - - [线段树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) -- 树状数组 - - [树状数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md) - - [树状数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) -- 并查集 - - [并查集知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/01.Union-Find.md) - - [并查集题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) - -## 08. 图论 - -- 图的基础知识 - - [图的定义和分类](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md) - - [图的存储结构和问题应用](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md) -- 图的遍历 - - [图的深度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md) - - [图的深度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - - [图的广度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md) - - [图的广度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - - [图的拓扑排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md) - - [图的拓扑排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) -- 图的生成树 - - [图的最小生成树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md) - - [图的最小生成树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) -- 最短路径 - - [单源最短路径知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md) - - [单源最短路径知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md) - - [单源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) - - [多源最短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md) - - [多源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) - - [次短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md) - - [次短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) - - [差分约束系统知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md) - - [差分约束系统题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) -- 二分图 - - [二分图基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md) - - [二分图基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) - - [二分图最大匹配知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md) - - [匈牙利算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md) - - [Hopcroft-Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md) - - [二分图最大匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) - -## 09. 基础算法 - -- 枚举算法 - - [枚举算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md) - - [枚举算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) -- 递归算法 - - [递归算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md) - - [递归算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) -- 分治算法 - - [分治算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md) - - [分治算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) -- 回溯算法 - - [回溯算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md) - - [回溯算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) -- 贪心算法 - - [贪心算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md) - - [贪心算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) -- 位运算 - - [位运算知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md) - - [位运算题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) - -## 10. 动态规划 - -- 动态规划基础 - - [动态规划基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) - - [动态规划基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) -- 记忆化搜索 - - [记忆化搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md) - - [记忆化搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) -- 线性 DP - - [线性 DP 知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) - - [线性 DP 知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) - - [线性 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) -- 背包问题 - - [背包问题知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) - - [背包问题知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) - - [背包问题知识(三)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) - - [背包问题知识(四)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) - - [背包问题知识(五)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) - - [背包问题题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) -- 区间 DP - - [区间 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) - - [区间 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) -- 树形 DP - - [树形 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) - - [树形 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) -- 状态压缩 DP - - [状态压缩 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md) - - [状态压缩 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) -- 计数 DP - - [计数 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) - - [计数 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) -- 数位 DP - - [数位 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) - - [数位 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) -- 概率 DP - - [概率 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) - - [概率 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) -- 动态规划优化 - - [单调栈 / 优先队列优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md) - - [斜率优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md) - - [四边形不等式优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md) - - [动态规划优化题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) - -## 11. 附加内容 diff --git a/Assets/Origins/README-Head.md b/Assets/Origins/README-Head.md deleted file mode 100644 index d1b24470..00000000 --- a/Assets/Origins/README-Head.md +++ /dev/null @@ -1,41 +0,0 @@ -# 算法通关手册(LeetCode) - -## 项目简介 - -- **「算法与数据结构」** 基础知识的讲解教程,「LeetCode」800+ 道题目的详细解析。本项目易于理解,没有大跨度的思维跳跃,项目中使用部分图示、例子来帮助理解。 - -- 本教程先从基础的数据结构和算法开始讲解,再针对不同分类的数据结构和算法,进行具体题目的讲解分析。让读者可以通过「算法基础理论学习」和「编程实战学习」相结合的方式,彻底的掌握算法知识。 - -- 本教程采用 Python 作为编程语言,要求学习者已有基本 Python 程序设计的知识与经验。 - -## 项目地址 - -欢迎右上角 **「Star ⭐️ 」** 和 **「Fork」**,这是对我最大的鼓励和支持。 - -- GitHub 地址:[https://github.com/itcharge/LeetCode-Py](https://github.com/itcharge/LeetCode-Py) - -支持黑暗模式的在线电子书《算法通关手册》。 - -- 电子书地址:[https://algo.itcharge.cn](https://algo.itcharge.cn) - -![](./Assets/Images/algo-book-light.png) - -![](./Assets/Images/algo-book-dark.png) - -## 关于作者 - -我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 - -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 800+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 - -在公众号 **「程序员充电站」** 里回复 "**算法打卡**",拉你进 LeetCode 算法打卡计划群一起组队打卡。 - -- 进群暗号:**算法打卡** -- 进群要求:少闲聊、多分享、改备注。 - -![](./Assets/Images/itcharge-qr-code.png) - -## 版权说明 - -- 本教程采用 [知识署名—非商业性使用—禁止演绎(BY-NC-ND)4.0 协议国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。 -- 本教程题解中的所有题目版权均归 [LeetCode](https://leetcode.com/) 和 [力扣中国](https://leetcode.cn/) 所有。 diff --git a/Assets/Origins/Root-Index-Head.md b/Assets/Origins/Root-Index-Head.md deleted file mode 100644 index 78776f57..00000000 --- a/Assets/Origins/Root-Index-Head.md +++ /dev/null @@ -1,71 +0,0 @@ -# 算法通关手册(LeetCode) - -## 关于本书 - -本书不仅仅只是一本算法题解书,更是一本算法与数据结构基础知识的讲解书。本书易于理解,没有大跨度的思维跳跃,书中使用部分图示、例子来帮助理解。本书先从基础的数据结构和算法开始讲解,再针对不同分类的数据结构和算法,进行具体题目的讲解分析。让读者可以通过「算法基础理论学习」和「编程实战学习」相结合的方式,彻底的掌握算法知识。 - -本书采用 Python 作为编程语言,要求学习者已有基本 Python 程序设计的知识与经验。 - -## 本书起因 - -我想写一本通俗易懂的算法书已经很久了,久到大概有 6 年那么久。至今我还记着上大学时立下的 flag,我要把我所学的算法知识总结起来,整理成册,编辑成书。然后大大方方的在封面书上自己的昵称,再把它分享给想要学习算法的朋友们看。 - -结果是万万没想到,这一晃过去,毕业后参加工作都已经 5 年了,每天忙于开发需求、业务逻辑,写书这件事也跟其他大多数的待办事项和计划清单一样,被无限期地闲置一旁,再也不管不顾了。 - -不过,好在是今年我又重新拾起了算法,开始和朋友一起愉快的在 LeetCode 上刷题。于是往日的目标又浮现在了眼前,所以这次痛下决心,立志写一本浅显易懂、图文并茂的算法书,能够让没有算法基础的新手能够通过这本书学到一些「算法和数据结构」相关知识,并通过在 LeetCode 刷题的方式,锻炼自己的解决问题的能力和思维方式。 - -![](https://qcdn.itcharge.cn/images/20211027170432.png) - -## 源码地址 - -本书内容及代码都放在 [Github repo](https://github.com/itcharge/LeetCode-Py) 中,欢迎在下方项目中 **「Star ⭐️ 」** 和 **「Fork」**,这是对我最大的鼓励和支持。 - -- Github 地址:[https://github.com/itcharge/LeetCode-Py](https://github.com/itcharge/LeetCode-Py) - -## 本书前言 - -**「算法和数据结构」** 是计算机程序设计的重要理论技术基础,但很多程序员忽略了它的重要性。在日常开发工作中,最多的情况是使用成熟的开发框架,利用已经封装好的接口,进行 CRUD(增删改查)操作,似乎很少会需要自己实现相应的数据结构和算法。 - -况且工作中用到的编程语言、开发框架、开发平台,更新速度堪比摩尔定律。以前端为例,React 还没学明白呢,Vue 就火起来了。Vue 2.0 的文档还在研究呢,Vue 3.0 就发布了。很多时候,连新的技术还学不过来呢,哪还有时间去专门研究算法和数据结构呢。 - -诚然,语言、技术、框架固然重要,但背后的计算机算法和理论更为重要。因为语言、技术、框架的更新日新月异,但万变不离其宗的是背后的算法和理论,例如:**数据结构**、**算法**、**编译原理**、**计算机网络**、**计算机体系结构** 等等。任凭新技术如何变化,只要掌握了这些计算机科学的核心理论,就可以见招拆招,让自己立于不败之地。从此无论是看懂底层系统的设计原理、框架背后的设计思想,还是学习新技术、提升工作实战的效率,都可以做到得心应手。 - -**学习数据结构与算法的关键,在于掌握其中的思想和精髓,学会解决实际问题的方法。** - -本书采用算法与数据结构相结合的方法,把内容分为如下 6 部分: - -- 第一部分是序言(第 00 章):介绍数据结构与算法的基础知识、算法复杂度、LeetCode 的入门和攻略,为后面的学习打好基础。 -- 第二部分是数据结构篇(第 01 ~ 08 章):每一章对应一种数据结构,这个部分用来介绍最常见、最重要的数据结构,以及与该数据结构相关的算法知识。 -- 第三部分是基础算法篇(第 09 章):这一章用来介绍基本的算法思想。包括枚举、递归、贪心、分治、回溯以及位运算。 -- 第四部分是动态规划篇(第 10 章):这一章用来介绍动态规划的基础知识、题型和优化方法。 -- 第五部分是补充内容篇(第 11 章):这一章用来补充之前章节没有讲到的内容。 -- 第六部分是 LeetCode 题解篇(第 12 章):这一章用来讲解我在 LeetCode 上刷过的所有题目。可按照对应题号进行检索和学习。 - -在本书构思与写作阶段,很多朋友给我提出了有益的意见和建议。这些意见和建议令我受益匪浅。感谢在本书著作准备过程中,帮助过我的朋友,以及一起陪我刷题打卡的朋友,还有提供宝贵意见的读者。感谢为本书提供课程合作和宣传的 DataWhale 开源组织。谢谢诸位。 - -## 目标读者 - -- 拥有 Python 编程基础的编程爱好者 -- 对 LeetCode 刷题感兴趣的编程爱好者 -- 对算法感兴趣的计算机专业学生或程序员 - -## 使用说明 - -- 本电子书的左侧为所有章节目录导航,可直接点击对应章节跳转阅读。 -- 本电子书左上角有搜索栏,可以帮你迅速找到想看的章节和题解文章。 -- 本电子书每页都接入了 Utterances 评论系统,可在每页下方的评论框进行评论(需使用 GitHub 账号登录)。如果没有显示,请检查一下网络。 - -## 互助与勘误 - -限于本人的水平和经验,书中一定不乏纰漏和谬误之处。恳切希望读者给予批评指正。这将有利于我改进和提高,以帮助更多的读者。如果您对本书有任何评论和建议,或者遇到问题需要帮助,可在每页评论区留言,或者致信作者邮箱 [i@itcharge.cn](mailto:i@itcharge.cn),我将不胜感激。 - -## 关于作者 - -我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 - -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 800+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 - -## 版权说明 - -- 本书采用 [知识署名—非商业性使用—禁止演绎(BY-NC-ND)4.0 协议国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。 -- 本书题解中的所有题目版权均归 [LeetCode](https://leetcode.com/) 和 [力扣中国](https://leetcode.cn/) 所有。 diff --git a/Assets/Scripts/create_auto.sh b/Assets/Scripts/create_auto.sh deleted file mode 100644 index ecff0238..00000000 --- a/Assets/Scripts/create_auto.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh - -# 确保脚本抛出遇到的错误 -set -e - -cd ./ - -python3 create_readme.py \ No newline at end of file diff --git a/Assets/Scripts/create_readme.py b/Assets/Scripts/create_readme.py deleted file mode 100644 index 26d7b861..00000000 --- a/Assets/Scripts/create_readme.py +++ /dev/null @@ -1,33 +0,0 @@ -import create_solutions_list as gen - -# 生成分类题解列表 -solotions_path = '../../Solutions' -categories_origin_list_path = '../Origins/Categories-List.md' -categories_list_path = '../../Contents/00.Introduction/05.Categories-List.md' - -gen.gen_categories_list(solotions_path, categories_origin_list_path, categories_list_path) - -# 生成面试 Top 100 题解列表 -interview_100_origin_list_path = '../Origins/Interview-100-List.md' -interview_100_list_path = '../../Contents/00.Introduction/06.Interview-100-List.md' -gen.gen_interview_list(solotions_path, interview_100_origin_list_path, interview_100_list_path) - -# 生成面试 Top 200 题解列表 -interview_200_origin_list_path = '../Origins/Interview-200-List.md' -interview_200_list_path = '../../Contents/00.Introduction/07.Interview-200-List.md' -gen.gen_interview_list(solotions_path, interview_200_origin_list_path, interview_200_list_path) - -# 生成全部题解列表 -solotions_output_path = '../../Contents/00.Introduction/04.Solutions-List.md' - -solutions_count = gen.gen_solutions_list(solotions_path, solotions_output_path) - - -# 生成 README.md index.md 文件 -readme_head_path = '../Origins/README-Head.md' -readme_catalogue_list_path = '../Origins/README-Catalogue-List.md' -content_index_path = '../../Contents/index.md' -readme_path = '../../README.md' - -gen.merge_readme_file(solotions_output_path, readme_head_path, readme_catalogue_list_path, content_index_path, readme_path, solutions_count) - diff --git a/Assets/Scripts/create_solutions_list.py b/Assets/Scripts/create_solutions_list.py deleted file mode 100644 index ea822b91..00000000 --- a/Assets/Scripts/create_solutions_list.py +++ /dev/null @@ -1,322 +0,0 @@ -import os, re -from pathlib import Path -import pandas as pd -from urllib.parse import quote - -# 根据 frame 生成 Markdown 表格 -def gen_markdown_table(frame, need_sort): - - ELEMENT = " {} |" - - H = frame.shape[0] - W = frame.shape[1] - - LINE = "|" + ELEMENT * W - - head_name = ["题号", "标题", "题解", "标签", "难度"] - - lines = [] - - ## 表头部分 - lines += ["| {} | {} | {} | {} | {} |".format(head_name[0], head_name[1], head_name[2], head_name[3], head_name[4])] - - ## 分割线 - SPLIT = ":{}" - line = "|" - for i in range(W): - line = "{} {} |".format(line, SPLIT.format('-'*6)) - lines += [line] - - ## 数据部分 - if need_sort: - frame = frame.sort_values(by='题号') - frame = frame.reset_index(drop=True) - for i in range(H): - lines += ["| {} | {} | {} | {} | {} |".format(frame.at[i, '题号'], frame.at[i, '标题'], frame.at[i, '题解'], frame.at[i, '标签'], frame.at[i, '难度'])] - table = '\n'.join(lines) - return table - -# 根据题解目录 solutions_path 自动生成题解列表,并保存到 output_path 中 -def gen_solutions_list(solutions_path, solotions_output_path): - files = os.listdir(solutions_path) - frame = pd.DataFrame(columns=['题号', '标题', '题解', '标签', '难度']) - frame_cout = 0 - - df = pd.read_csv("leetcode-problems.csv") - - for file in files: - # 判断是否是文件夹 - if ".md" not in file: - continue - - # 获取题目所在行 - df_indexs = df[df['标题'] == Path(file).stem].index.tolist() - - if not df_indexs: - print('%s 没有出现在 leetcode-problems.csv 中' % (Path(file).stem)) - continue - row = df_indexs[0] - - problem_id = df.loc[row, "序号"] - problem_catalog = df.loc[row, "所在目录"] - problem_title = df.loc[row, "标题"] - problem_title_slug = df.loc[row, "标题末尾"] - problem_link = "[" + problem_title_slug + "](" + df.loc[row, "标题链接"] + ")" - problem_link_slug = df.loc[row, "标题链接末尾路径"] - problem_solution_path = os.path.join(solutions_path, problem_title + ".md") - if os.path.exists(problem_solution_path): - problem_solution_link = "[Python](" + df.loc[row, "github 题解链接"] + ")" - else: - problem_solution_link = "" - problem_label = df.loc[row, "标签"] - problem_difficulty = df.loc[row, "难度"] - res = [problem_id, problem_link, problem_solution_link, problem_label, problem_difficulty] - frame.loc[frame_cout] = res - frame_cout += 1 - - table = gen_markdown_table(frame, True) - with open(solotions_output_path, 'w') as f: - f.writelines("# LeetCode 题解(已完成 {} 道)\n\n".format(frame_cout)) - f.write(table) - f.close() - print("Create Solutions List Success") - return frame_cout - - -# 将 readme_head、list 合并到,自动生成 README.md 并保存到 readme_path 中 -def merge_readme_file(solotions_output_path, readme_head_path, readme_catalogue_list_path, content_index_path, readme_path, solutions_count): - - # 生成项目 README.md 文件 - readme_file = open(readme_path,'w') - - # 将 README 开头部分写入 README.md 中 - readme_head_file = open(readme_head_path) - readme_file.writelines(readme_head_file.readlines()) - readme_head_file.close() - - # 将章节目录写入 README.md 中 - readme_catelogue_list_file = open(readme_catalogue_list_path) - readme_catelogue_list_lines = readme_catelogue_list_file.readlines() - for readme_catelogue_list_line in readme_catelogue_list_lines: - readme_catelogue_list_line = readme_catelogue_list_line.replace('https://github.com/itcharge/LeetCode-Py/blob/main', '.') - readme_file.write(readme_catelogue_list_line) - readme_catelogue_list_file.close() - - # 将题解标题写入 readme 文件 - catalogue_list_file = open(solotions_output_path) - catalogue_list_lines = catalogue_list_file.readlines() - if len(catalogue_list_lines) > 0: - catalogue_list_title = catalogue_list_lines[0].strip('\n') - catalogue_list_title = '## [' + catalogue_list_title + '](./Contents/00.Introduction/04.Solutions-List.md)' - catalogue_list_title = catalogue_list_title.replace('# LeetCode 题解', '12. LeetCode 题解') - readme_file.writelines(catalogue_list_title) - catalogue_list_file.close() - - readme_file.close() - - - # 生成 Contents/index.md 文件 - content_index_file = open(content_index_path, 'w') - content_index_file.writelines("# 算法通关手册(LeetCode)\n\n") - - # 将章节目录写入 Contents/index.md 文件中 - readme_catelogue_list_file = open(readme_catalogue_list_path) - catalogue_list_lines = readme_catelogue_list_file.readlines() - for catalogue_list_line in catalogue_list_lines: - catalogue_list_line = catalogue_list_line.replace('https://github.com/itcharge/LeetCode-Py/blob/main/Contents', '.') - content_index_file.write(catalogue_list_line) - readme_catelogue_list_file.close() - content_index_file.close() - -# 根据题解目录, 题目分类原始列表目录,生成分类题解,并将整体保存到 categories_list_path -def gen_categories_list(solutions_path, categories_origin_list_path, categories_list_path): - - f = open(categories_origin_list_path) - lines = f.readlines() - category_h2 = None - category_h3 = None - category_h4 = None - category_h6 = None - category_h3_file_path = None - category_h3_file_content = "" - category_file_content = "" - - df = pd.read_csv("leetcode-problems.csv") - - for i in range(len(lines)): - pattern = re.compile(r'(#{2,6}) (.*)') - match = pattern.match(lines[i]) - if match: - title_size, title_content = match.group(1,2) - if title_size == "##": - category_h2 = title_content - category_file_content += "## " + category_h2 + "\n\n" - elif title_size == "###": - if category_h3 and category_h3_file_path and category_h3_file_content: - with open(category_h3_file_path, 'w') as fi: - fi.write(category_h3_file_content) - fi.close() - category_h3 = None - category_h3_file_path = None - category_h3_file_content = "" - pattern1 = re.compile(r'\[(.*)\]\((.*)\)') - match1 = pattern1.match(title_content) - if match1: - category_h3, category_h3_file_path = match1.group(1,2) - category_h3_file_content += "### " + category_h3 + "\n\n" - category_file_content += "### " + category_h3 + "\n\n" - else: - category_h3 = title_content - category_file_content += "### " + category_h3 + "\n\n" - elif title_size == "####": - category_h4 = title_content - category_h3_file_content += "#### " + category_h4 + "\n\n" - category_file_content += "#### " + category_h4 + "\n\n" - elif title_size == "######": - category_h6 = title_content - problem_titles = title_content.split('、') - if not problem_titles: - continue - - frame = pd.DataFrame(columns=['题号', '标题', '题解', '标签', '难度']) - frame_cout = 0 - for problem_title in problem_titles: - # 获取题目所在行 - df_indexs = df[df['标题'] == problem_title].index.tolist() - - if not df_indexs: - print('%s 没有出现在 leetcode-problems.csv 中' % (problem_title)) - continue - row = df_indexs[0] - - problem_id = df.loc[row, "序号"] - problem_catalog = df.loc[row, "所在目录"] - problem_title = df.loc[row, "标题"] - problem_title_slug = df.loc[row, "标题末尾"] - problem_link = "[" + problem_title_slug + "](" + df.loc[row, "标题链接"] + ")" - problem_link_slug = df.loc[row, "标题链接末尾路径"] - problem_solution_path = os.path.join(solutions_path, problem_title + ".md") - if os.path.exists(problem_solution_path): - problem_solution_link = "[Python](" + df.loc[row, "github 题解链接"] + ")" - else: - problem_solution_link = "" - problem_label = df.loc[row, "标签"] - problem_difficulty = df.loc[row, "难度"] - res = [problem_id, problem_link, problem_solution_link, problem_label, problem_difficulty] - frame.loc[frame_cout] = res - frame_cout += 1 - - table = gen_markdown_table(frame, False) - category_h3_file_content += table + "\n\n" - category_file_content += table + "\n\n" - - if category_h3 and category_h3_file_path and category_h3_file_content: - with open(category_h3_file_path, 'w') as fi: - fi.write(category_h3_file_content) - fi.close() - - if category_file_content: - with open(categories_list_path, 'w') as fi: - fi.write("# LeetCode 题解(按分类排序,推荐刷题列表 ★★★)\n\n") - fi.write(category_file_content) - fi.close() - - print("Create Categories List Success") - - -# 根据题解目录, 面试题目分类原始列表目录,生成面试题解,并将整体保存到 interview_list_path -def gen_interview_list(solutions_path, interview_origin_list_path, interview_list_path): - - f = open(interview_origin_list_path) - lines = f.readlines() - interview_h2 = None - interview_h3 = None - interview_h4 = None - interview_h6 = None - interview_h3_file_path = None - interview_h3_file_content = "" - interview_file_content = "" - - df = pd.read_csv("leetcode-problems.csv") - - problems_set = set() - for i in range(len(lines)): - pattern = re.compile(r'(#{2,6}) (.*)') - match = pattern.match(lines[i]) - if match: - title_size, title_content = match.group(1,2) - if title_size == "##": - interview_h2 = title_content - interview_file_content += "## " + interview_h2 + "\n\n" - elif title_size == "###": - if interview_h3 and interview_h3_file_path and interview_h3_file_content: - interview_h3 = None - interview_h3_file_path = None - interview_h3_file_content = "" - pattern1 = re.compile(r'\[(.*)\]\((.*)\)') - match1 = pattern1.match(title_content) - if match1: - interview_h3, interview_h3_file_path = match1.group(1,2) - interview_h3_file_content += "### " + interview_h3 + "\n\n" - interview_file_content += "### " + interview_h3 + "\n\n" - else: - interview_h3 = title_content - interview_file_content += "### " + interview_h3 + "\n\n" - elif title_size == "####": - interview_h4 = title_content - interview_h3_file_content += "#### " + interview_h4 + "\n\n" - interview_file_content += "#### " + interview_h4 + "\n\n" - elif title_size == "######": - interview_h6 = title_content - problem_titles = title_content.split('、') - if not problem_titles: - continue - - frame = pd.DataFrame(columns=['题号', '标题', '题解', '标签', '难度']) - frame_cout = 0 - for problem_title in problem_titles: - # 获取题目所在行 - df_indexs = df[df['标题'] == problem_title].index.tolist() - - if not df_indexs: - print('%s 没有出现在 leetcode-problems.csv 中' % (problem_title)) - continue - - problems_set.add(problem_title) - row = df_indexs[0] - - problem_id = df.loc[row, "序号"] - problem_catalog = df.loc[row, "所在目录"] - problem_title = df.loc[row, "标题"] - problem_title_slug = df.loc[row, "标题末尾"] - problem_link = "[" + problem_title_slug + "](" + df.loc[row, "标题链接"] + ")" - problem_link_slug = df.loc[row, "标题链接末尾路径"] - problem_solution_path = os.path.join(solutions_path, problem_title + ".md") - if os.path.exists(problem_solution_path): - problem_solution_link = "[Python](" + df.loc[row, "github 题解链接"] + ")" - else: - problem_solution_link = "" - problem_label = df.loc[row, "标签"] - problem_difficulty = df.loc[row, "难度"] - res = [problem_id, problem_link, problem_solution_link, problem_label, problem_difficulty] - frame.loc[frame_cout] = res - frame_cout += 1 - - table = gen_markdown_table(frame, False) - interview_h3_file_content += table + "\n\n" - interview_file_content += table + "\n\n" - - if interview_file_content: - with open(interview_list_path, 'w') as fi: - if "Interview-100-List.md" in interview_origin_list_path: - fi.write("# LeetCode 面试最常考 100 题(按分类排序)\n\n") - elif "Interview-200-List.md" in interview_origin_list_path: - fi.write("# LeetCode 面试最常考 200 题(按分类排序)\n\n") - fi.write(interview_file_content) - fi.write("\n## 参考资料\n") - fi.write("\n- 【清单】[CodeTop 企业题库](https://codetop.cc/home)\n") - fi.close() - - print("Total Problems Count: " + str(len(problems_set))) - print(sorted(list(problems_set))) - print("Create Interview List Success") \ No newline at end of file diff --git a/Assets/Scripts/leetcode-problems.csv b/Assets/Scripts/leetcode-problems.csv deleted file mode 100644 index 54c8f30f..00000000 --- a/Assets/Scripts/leetcode-problems.csv +++ /dev/null @@ -1,3141 +0,0 @@ -序号,所在目录,标题,标题末尾,标题链接,标题链接末尾路径,标签,网站题解链接,github 题解链接,通过率,难度,题解数目 -0001,0001-0099,0001. 两数之和,两数之和,https://leetcode.cn/problems/two-sum/,two-sum,数组、哈希表,https://algo.itcharge.cn/Solutions/0001-0099/two-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md,52.9%,简单,21940 -0002,0001-0099,0002. 两数相加,两数相加,https://leetcode.cn/problems/add-two-numbers/,add-two-numbers,递归、链表、数学,https://algo.itcharge.cn/Solutions/0001-0099/add-two-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md,42.4%,中等,12149 -0003,0001-0099,0003. 无重复字符的最长子串,无重复字符的最长子串,https://leetcode.cn/problems/longest-substring-without-repeating-characters/,longest-substring-without-repeating-characters,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0001-0099/longest-substring-without-repeating-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md,39.1%,中等,13753 -0004,0001-0099,0004. 寻找两个正序数组的中位数,寻找两个正序数组的中位数,https://leetcode.cn/problems/median-of-two-sorted-arrays/,median-of-two-sorted-arrays,数组、二分查找、分治,https://algo.itcharge.cn/Solutions/0001-0099/median-of-two-sorted-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md,41.5%,困难,6962 -0005,0001-0099,0005. 最长回文子串,最长回文子串,https://leetcode.cn/problems/longest-palindromic-substring/,longest-palindromic-substring,字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/longest-palindromic-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md,37.6%,中等,7674 -0006,0001-0099,0006. N 字形变换,N 字形变换,https://leetcode.cn/problems/zigzag-conversion/,zigzag-conversion,字符串,https://algo.itcharge.cn/Solutions/0001-0099/zigzag-conversion/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0006.%20N%20%E5%AD%97%E5%BD%A2%E5%8F%98%E6%8D%A2.md,52.0%,中等,4647 -0007,0001-0099,0007. 整数反转,整数反转,https://leetcode.cn/problems/reverse-integer/,reverse-integer,数学,https://algo.itcharge.cn/Solutions/0001-0099/reverse-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0007.%20%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC.md,35.4%,中等,7244 -0008,0001-0099,0008. 字符串转换整数 (atoi),字符串转换整数 (atoi),https://leetcode.cn/problems/string-to-integer-atoi/,string-to-integer-atoi,字符串,https://algo.itcharge.cn/Solutions/0001-0099/string-to-integer-atoi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0008.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0%20%28atoi%29.md,21.3%,中等,4293 -0009,0001-0099,0009. 回文数,回文数,https://leetcode.cn/problems/palindrome-number/,palindrome-number,数学,https://algo.itcharge.cn/Solutions/0001-0099/palindrome-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0009.%20%E5%9B%9E%E6%96%87%E6%95%B0.md,56.0%,简单,8400 -0010,0001-0099,0010. 正则表达式匹配,正则表达式匹配,https://leetcode.cn/problems/regular-expression-matching/,regular-expression-matching,递归、字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/regular-expression-matching/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0010.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md,30.8%,困难,2200 -0011,0001-0099,0011. 盛最多水的容器,盛最多水的容器,https://leetcode.cn/problems/container-with-most-water/,container-with-most-water,贪心、数组、双指针,https://algo.itcharge.cn/Solutions/0001-0099/container-with-most-water/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0011.%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8.md,60.3%,中等,5531 -0012,0001-0099,0012. 整数转罗马数字,整数转罗马数字,https://leetcode.cn/problems/integer-to-roman/,integer-to-roman,哈希表、数学、字符串,https://algo.itcharge.cn/Solutions/0001-0099/integer-to-roman/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0012.%20%E6%95%B4%E6%95%B0%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97.md,66.1%,中等,3016 -0013,0001-0099,0013. 罗马数字转整数,罗马数字转整数,https://leetcode.cn/problems/roman-to-integer/,roman-to-integer,哈希表、数学、字符串,https://algo.itcharge.cn/Solutions/0001-0099/roman-to-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0013.%20%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97%E8%BD%AC%E6%95%B4%E6%95%B0.md,62.0%,简单,6679 -0014,0001-0099,0014. 最长公共前缀,最长公共前缀,https://leetcode.cn/problems/longest-common-prefix/,longest-common-prefix,字典树、字符串,https://algo.itcharge.cn/Solutions/0001-0099/longest-common-prefix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md,43.4%,简单,6736 -0015,0001-0099,0015. 三数之和,三数之和,https://leetcode.cn/problems/3sum/,3sum,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0001-0099/3sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md,37.0%,中等,5927 -0016,0001-0099,0016. 最接近的三数之和,最接近的三数之和,https://leetcode.cn/problems/3sum-closest/,3sum-closest,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0001-0099/3sum-closest/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0016.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md,44.8%,中等,2456 -0017,0001-0099,0017. 电话号码的字母组合,电话号码的字母组合,https://leetcode.cn/problems/letter-combinations-of-a-phone-number/,letter-combinations-of-a-phone-number,哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/0001-0099/letter-combinations-of-a-phone-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0017.%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.md,58.1%,中等,5547 -0018,0001-0099,0018. 四数之和,四数之和,https://leetcode.cn/problems/4sum/,4sum,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0001-0099/4sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md,36.8%,中等,2880 -0019,0001-0099,0019. 删除链表的倒数第 N 个结点,删除链表的倒数第 N 个结点,https://leetcode.cn/problems/remove-nth-node-from-end-of-list/,remove-nth-node-from-end-of-list,链表、双指针,https://algo.itcharge.cn/Solutions/0001-0099/remove-nth-node-from-end-of-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md,45.6%,中等,7944 -0020,0001-0099,0020. 有效的括号,有效的括号,https://leetcode.cn/problems/valid-parentheses/,valid-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/0001-0099/valid-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md,44.0%,简单,9710 -0021,0001-0099,0021. 合并两个有序链表,合并两个有序链表,https://leetcode.cn/problems/merge-two-sorted-lists/,merge-two-sorted-lists,递归、链表,https://algo.itcharge.cn/Solutions/0001-0099/merge-two-sorted-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md,66.2%,简单,7227 -0022,0001-0099,0022. 括号生成,括号生成,https://leetcode.cn/problems/generate-parentheses/,generate-parentheses,字符串、动态规划、回溯,https://algo.itcharge.cn/Solutions/0001-0099/generate-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md,77.5%,中等,4751 -0023,0001-0099,0023. 合并 K 个升序链表,合并 K 个升序链表,https://leetcode.cn/problems/merge-k-sorted-lists/,merge-k-sorted-lists,链表、分治、堆(优先队列)、归并排序,https://algo.itcharge.cn/Solutions/0001-0099/merge-k-sorted-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md,57.8%,困难,4267 -0024,0001-0099,0024. 两两交换链表中的节点,两两交换链表中的节点,https://leetcode.cn/problems/swap-nodes-in-pairs/,swap-nodes-in-pairs,递归、链表,https://algo.itcharge.cn/Solutions/0001-0099/swap-nodes-in-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md,71.3%,中等,4583 -0025,0001-0099,0025. K 个一组翻转链表,K 个一组翻转链表,https://leetcode.cn/problems/reverse-nodes-in-k-group/,reverse-nodes-in-k-group,递归、链表,https://algo.itcharge.cn/Solutions/0001-0099/reverse-nodes-in-k-group/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md,67.7%,困难,3911 -0026,0001-0099,0026. 删除有序数组中的重复项,删除有序数组中的重复项,https://leetcode.cn/problems/remove-duplicates-from-sorted-array/,remove-duplicates-from-sorted-array,数组、双指针,https://algo.itcharge.cn/Solutions/0001-0099/remove-duplicates-from-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0026.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9.md,54.8%,简单,8460 -0027,0001-0099,0027. 移除元素,移除元素,https://leetcode.cn/problems/remove-element/,remove-element,数组、双指针,https://algo.itcharge.cn/Solutions/0001-0099/remove-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0027.%20%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md,59.2%,简单,7895 -0028,0001-0099,0028. 找出字符串中第一个匹配项的下标,找出字符串中第一个匹配项的下标,https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/,find-the-index-of-the-first-occurrence-in-a-string,双指针、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0028.%20%E6%89%BE%E5%87%BA%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8C%B9%E9%85%8D%E9%A1%B9%E7%9A%84%E4%B8%8B%E6%A0%87.md,42.5%,中等,5825 -0029,0001-0099,0029. 两数相除,两数相除,https://leetcode.cn/problems/divide-two-integers/,divide-two-integers,位运算、数学,https://algo.itcharge.cn/Solutions/0001-0099/divide-two-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0029.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E9%99%A4.md,22.2%,中等,1575 -0030,0001-0099,0030. 串联所有单词的子串,串联所有单词的子串,https://leetcode.cn/problems/substring-with-concatenation-of-all-words/,substring-with-concatenation-of-all-words,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0001-0099/substring-with-concatenation-of-all-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0030.%20%E4%B8%B2%E8%81%94%E6%89%80%E6%9C%89%E5%8D%95%E8%AF%8D%E7%9A%84%E5%AD%90%E4%B8%B2.md,39.6%,困难,1300 -0031,0001-0099,0031. 下一个排列,下一个排列,https://leetcode.cn/problems/next-permutation/,next-permutation,数组、双指针,https://algo.itcharge.cn/Solutions/0001-0099/next-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0031.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%8E%92%E5%88%97.md,38.3%,中等,3214 -0032,0001-0099,0032. 最长有效括号,最长有效括号,https://leetcode.cn/problems/longest-valid-parentheses/,longest-valid-parentheses,栈、字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/longest-valid-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md,37.2%,困难,2312 -0033,0001-0099,0033. 搜索旋转排序数组,搜索旋转排序数组,https://leetcode.cn/problems/search-in-rotated-sorted-array/,search-in-rotated-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/0001-0099/search-in-rotated-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md,43.8%,中等,4631 -0034,0001-0099,0034. 在排序数组中查找元素的第一个和最后一个位置,在排序数组中查找元素的第一个和最后一个位置,https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/,find-first-and-last-position-of-element-in-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md,42.4%,中等,7442 -0035,0001-0099,0035. 搜索插入位置,搜索插入位置,https://leetcode.cn/problems/search-insert-position/,search-insert-position,数组、二分查找,https://algo.itcharge.cn/Solutions/0001-0099/search-insert-position/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0035.%20%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md,45.0%,简单,7586 -0036,0001-0099,0036. 有效的数独,有效的数独,https://leetcode.cn/problems/valid-sudoku/,valid-sudoku,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/valid-sudoku/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0036.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%95%B0%E7%8B%AC.md,63.0%,中等,2646 -0037,0001-0099,0037. 解数独,解数独,https://leetcode.cn/problems/sudoku-solver/,sudoku-solver,数组、哈希表、回溯、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/sudoku-solver/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0037.%20%E8%A7%A3%E6%95%B0%E7%8B%AC.md,67.6%,困难,1595 -0038,0001-0099,0038. 外观数列,外观数列,https://leetcode.cn/problems/count-and-say/,count-and-say,字符串,https://algo.itcharge.cn/Solutions/0001-0099/count-and-say/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0038.%20%E5%A4%96%E8%A7%82%E6%95%B0%E5%88%97.md,60.4%,中等,3274 -0039,0001-0099,0039. 组合总和,组合总和,https://leetcode.cn/problems/combination-sum/,combination-sum,数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/combination-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md,72.4%,中等,3907 -0040,0001-0099,0040. 组合总和 II,组合总和 II,https://leetcode.cn/problems/combination-sum-ii/,combination-sum-ii,数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/combination-sum-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0040.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20II.md,59.7%,中等,2734 -0041,0001-0099,0041. 缺失的第一个正数,缺失的第一个正数,https://leetcode.cn/problems/first-missing-positive/,first-missing-positive,数组、哈希表,https://algo.itcharge.cn/Solutions/0001-0099/first-missing-positive/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md,43.1%,困难,2202 -0042,0001-0099,0042. 接雨水,接雨水,https://leetcode.cn/problems/trapping-rain-water/,trapping-rain-water,栈、数组、双指针、动态规划、单调栈,https://algo.itcharge.cn/Solutions/0001-0099/trapping-rain-water/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md,62.8%,困难,4396 -0043,0001-0099,0043. 字符串相乘,字符串相乘,https://leetcode.cn/problems/multiply-strings/,multiply-strings,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0001-0099/multiply-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md,44.4%,中等,2056 -0044,0001-0099,0044. 通配符匹配,通配符匹配,https://leetcode.cn/problems/wildcard-matching/,wildcard-matching,贪心、递归、字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/wildcard-matching/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0044.%20%E9%80%9A%E9%85%8D%E7%AC%A6%E5%8C%B9%E9%85%8D.md,33.8%,困难,905 -0045,0001-0099,0045. 跳跃游戏 II,跳跃游戏 II,https://leetcode.cn/problems/jump-game-ii/,jump-game-ii,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/jump-game-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md,45.1%,中等,3378 -0046,0001-0099,0046. 全排列,全排列,https://leetcode.cn/problems/permutations/,permutations,数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/permutations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md,78.9%,中等,5792 -0047,0001-0099,0047. 全排列 II,全排列 II,https://leetcode.cn/problems/permutations-ii/,permutations-ii,数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/permutations-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0047.%20%E5%85%A8%E6%8E%92%E5%88%97%20II.md,65.5%,中等,2867 -0048,0001-0099,0048. 旋转图像,旋转图像,https://leetcode.cn/problems/rotate-image/,rotate-image,数组、数学、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/rotate-image/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md,74.7%,中等,4452 -0049,0001-0099,0049. 字母异位词分组,字母异位词分组,https://leetcode.cn/problems/group-anagrams/,group-anagrams,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0001-0099/group-anagrams/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md,67.8%,中等,2594 -0050,0001-0099,"0050. Pow(x, n)","Pow(x, n)",https://leetcode.cn/problems/powx-n/,powx-n,递归、数学,https://algo.itcharge.cn/Solutions/0001-0099/powx-n/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md,38.0%,中等,2123 -0051,0001-0099,0051. N 皇后,N 皇后,https://leetcode.cn/problems/n-queens/,n-queens,数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/n-queens/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0051.%20N%20%E7%9A%87%E5%90%8E.md,74.1%,困难,3011 -0052,0001-0099,0052. N 皇后 II,N 皇后 II,https://leetcode.cn/problems/n-queens-ii/,n-queens-ii,回溯,https://algo.itcharge.cn/Solutions/0001-0099/n-queens-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0052.%20N%20%E7%9A%87%E5%90%8E%20II.md,82.4%,困难,933 -0053,0001-0099,0053. 最大子数组和,最大子数组和,https://leetcode.cn/problems/maximum-subarray/,maximum-subarray,数组、分治、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/maximum-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md,54.8%,中等,6888 -0054,0001-0099,0054. 螺旋矩阵,螺旋矩阵,https://leetcode.cn/problems/spiral-matrix/,spiral-matrix,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0001-0099/spiral-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md,49.3%,中等,3875 -0055,0001-0099,0055. 跳跃游戏,跳跃游戏,https://leetcode.cn/problems/jump-game/,jump-game,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/jump-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md,43.6%,中等,4932 -0056,0001-0099,0056. 合并区间,合并区间,https://leetcode.cn/problems/merge-intervals/,merge-intervals,数组、排序,https://algo.itcharge.cn/Solutions/0001-0099/merge-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md,49.4%,中等,4215 -0057,0001-0099,0057. 插入区间,插入区间,https://leetcode.cn/problems/insert-interval/,insert-interval,数组,https://algo.itcharge.cn/Solutions/0001-0099/insert-interval/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0057.%20%E6%8F%92%E5%85%A5%E5%8C%BA%E9%97%B4.md,42.0%,中等,1414 -0058,0001-0099,0058. 最后一个单词的长度,最后一个单词的长度,https://leetcode.cn/problems/length-of-last-word/,length-of-last-word,字符串,https://algo.itcharge.cn/Solutions/0001-0099/length-of-last-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0058.%20%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E5%8D%95%E8%AF%8D%E7%9A%84%E9%95%BF%E5%BA%A6.md,42.8%,简单,4621 -0059,0001-0099,0059. 螺旋矩阵 II,螺旋矩阵 II,https://leetcode.cn/problems/spiral-matrix-ii/,spiral-matrix-ii,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0001-0099/spiral-matrix-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0059.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20II.md,72.8%,中等,3443 -0060,0001-0099,0060. 排列序列,排列序列,https://leetcode.cn/problems/permutation-sequence/,permutation-sequence,递归、数学,https://algo.itcharge.cn/Solutions/0001-0099/permutation-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0060.%20%E6%8E%92%E5%88%97%E5%BA%8F%E5%88%97.md,53.5%,困难,1431 -0061,0001-0099,0061. 旋转链表,旋转链表,https://leetcode.cn/problems/rotate-list/,rotate-list,链表、双指针,https://algo.itcharge.cn/Solutions/0001-0099/rotate-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0061.%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8.md,41.4%,中等,3060 -0062,0001-0099,0062. 不同路径,不同路径,https://leetcode.cn/problems/unique-paths/,unique-paths,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/0001-0099/unique-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md,67.8%,中等,4228 -0063,0001-0099,0063. 不同路径 II,不同路径 II,https://leetcode.cn/problems/unique-paths-ii/,unique-paths-ii,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/unique-paths-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0063.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II.md,41.1%,中等,3006 -0064,0001-0099,0064. 最小路径和,最小路径和,https://leetcode.cn/problems/minimum-path-sum/,minimum-path-sum,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/minimum-path-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md,69.5%,中等,3408 -0065,0001-0099,0065. 有效数字,有效数字,https://leetcode.cn/problems/valid-number/,valid-number,字符串,https://algo.itcharge.cn/Solutions/0001-0099/valid-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0065.%20%E6%9C%89%E6%95%88%E6%95%B0%E5%AD%97.md,27.6%,困难,924 -0066,0001-0099,0066. 加一,加一,https://leetcode.cn/problems/plus-one/,plus-one,数组、数学,https://algo.itcharge.cn/Solutions/0001-0099/plus-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0066.%20%E5%8A%A0%E4%B8%80.md,45.1%,简单,5659 -0067,0001-0099,0067. 二进制求和,二进制求和,https://leetcode.cn/problems/add-binary/,add-binary,位运算、数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0001-0099/add-binary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0067.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%B1%82%E5%92%8C.md,53.0%,简单,2929 -0068,0001-0099,0068. 文本左右对齐,文本左右对齐,https://leetcode.cn/problems/text-justification/,text-justification,数组、字符串、模拟,https://algo.itcharge.cn/Solutions/0001-0099/text-justification/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0068.%20%E6%96%87%E6%9C%AC%E5%B7%A6%E5%8F%B3%E5%AF%B9%E9%BD%90.md,52.4%,困难,802 -0069,0001-0099,0069. x 的平方根,x 的平方根,https://leetcode.cn/problems/sqrtx/,sqrtx,数学、二分查找,https://algo.itcharge.cn/Solutions/0001-0099/sqrtx/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md,38.4%,简单,3757 -0070,0001-0099,0070. 爬楼梯,爬楼梯,https://leetcode.cn/problems/climbing-stairs/,climbing-stairs,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/climbing-stairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md,54.1%,简单,6311 -0071,0001-0099,0071. 简化路径,简化路径,https://leetcode.cn/problems/simplify-path/,simplify-path,栈、字符串,https://algo.itcharge.cn/Solutions/0001-0099/simplify-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0071.%20%E7%AE%80%E5%8C%96%E8%B7%AF%E5%BE%84.md,44.2%,中等,1650 -0072,0001-0099,0072. 编辑距离,编辑距离,https://leetcode.cn/problems/edit-distance/,edit-distance,字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/edit-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md,62.8%,困难,3123 -0073,0001-0099,0073. 矩阵置零,矩阵置零,https://leetcode.cn/problems/set-matrix-zeroes/,set-matrix-zeroes,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/set-matrix-zeroes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0073.%20%E7%9F%A9%E9%98%B5%E7%BD%AE%E9%9B%B6.md,63.2%,中等,1831 -0074,0001-0099,0074. 搜索二维矩阵,搜索二维矩阵,https://leetcode.cn/problems/search-a-2d-matrix/,search-a-2d-matrix,数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/search-a-2d-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0074.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5.md,48.7%,中等,2963 -0075,0001-0099,0075. 颜色分类,颜色分类,https://leetcode.cn/problems/sort-colors/,sort-colors,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0001-0099/sort-colors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md,60.4%,中等,3600 -0076,0001-0099,0076. 最小覆盖子串,最小覆盖子串,https://leetcode.cn/problems/minimum-window-substring/,minimum-window-substring,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0001-0099/minimum-window-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md,45.2%,困难,3407 -0077,0001-0099,0077. 组合,组合,https://leetcode.cn/problems/combinations/,combinations,回溯,https://algo.itcharge.cn/Solutions/0001-0099/combinations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0077.%20%E7%BB%84%E5%90%88.md,77.1%,中等,2912 -0078,0001-0099,0078. 子集,子集,https://leetcode.cn/problems/subsets/,subsets,位运算、数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md,81.1%,中等,4313 -0079,0001-0099,0079. 单词搜索,单词搜索,https://leetcode.cn/problems/word-search/,word-search,数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/0001-0099/word-search/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0079.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2.md,46.3%,中等,2603 -0080,0001-0099,0080. 删除有序数组中的重复项 II,删除有序数组中的重复项 II,https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/,remove-duplicates-from-sorted-array-ii,数组、双指针,https://algo.itcharge.cn/Solutions/0001-0099/remove-duplicates-from-sorted-array-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0080.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9%20II.md,61.7%,中等,1974 -0081,0001-0099,0081. 搜索旋转排序数组 II,搜索旋转排序数组 II,https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/,search-in-rotated-sorted-array-ii,数组、二分查找,https://algo.itcharge.cn/Solutions/0001-0099/search-in-rotated-sorted-array-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0081.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%20II.md,41.0%,中等,1461 -0082,0001-0099,0082. 删除排序链表中的重复元素 II,删除排序链表中的重复元素 II,https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/,remove-duplicates-from-sorted-list-ii,链表、双指针,https://algo.itcharge.cn/Solutions/0001-0099/remove-duplicates-from-sorted-list-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md,53.5%,中等,2784 -0083,0001-0099,0083. 删除排序链表中的重复元素,删除排序链表中的重复元素,https://leetcode.cn/problems/remove-duplicates-from-sorted-list/,remove-duplicates-from-sorted-list,链表,https://algo.itcharge.cn/Solutions/0001-0099/remove-duplicates-from-sorted-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md,53.1%,简单,3606 -0084,0001-0099,0084. 柱状图中最大的矩形,柱状图中最大的矩形,https://leetcode.cn/problems/largest-rectangle-in-histogram/,largest-rectangle-in-histogram,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/0001-0099/largest-rectangle-in-histogram/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0084.%20%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.md,45.0%,困难,1984 -0085,0001-0099,0085. 最大矩形,最大矩形,https://leetcode.cn/problems/maximal-rectangle/,maximal-rectangle,栈、数组、动态规划、矩阵、单调栈,https://algo.itcharge.cn/Solutions/0001-0099/maximal-rectangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0085.%20%E6%9C%80%E5%A4%A7%E7%9F%A9%E5%BD%A2.md,54.7%,困难,1196 -0086,0001-0099,0086. 分隔链表,分隔链表,https://leetcode.cn/problems/partition-list/,partition-list,链表、双指针,https://algo.itcharge.cn/Solutions/0001-0099/partition-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0086.%20%E5%88%86%E9%9A%94%E9%93%BE%E8%A1%A8.md,64.2%,中等,2083 -0087,0001-0099,0087. 扰乱字符串,扰乱字符串,https://leetcode.cn/problems/scramble-string/,scramble-string,字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/scramble-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0087.%20%E6%89%B0%E4%B9%B1%E5%AD%97%E7%AC%A6%E4%B8%B2.md,47.3%,困难,440 -0088,0001-0099,0088. 合并两个有序数组,合并两个有序数组,https://leetcode.cn/problems/merge-sorted-array/,merge-sorted-array,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0001-0099/merge-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md,52.5%,简单,6055 -0089,0001-0099,0089. 格雷编码,格雷编码,https://leetcode.cn/problems/gray-code/,gray-code,位运算、数学、回溯,https://algo.itcharge.cn/Solutions/0001-0099/gray-code/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0089.%20%E6%A0%BC%E9%9B%B7%E7%BC%96%E7%A0%81.md,75.5%,中等,906 -0090,0001-0099,0090. 子集 II,子集 II,https://leetcode.cn/problems/subsets-ii/,subsets-ii,位运算、数组、回溯,https://algo.itcharge.cn/Solutions/0001-0099/subsets-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md,63.6%,中等,2267 -0091,0001-0099,0091. 解码方法,解码方法,https://leetcode.cn/problems/decode-ways/,decode-ways,字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/decode-ways/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md,33.2%,中等,2315 -0092,0001-0099,0092. 反转链表 II,反转链表 II,https://leetcode.cn/problems/reverse-linked-list-ii/,reverse-linked-list-ii,链表,https://algo.itcharge.cn/Solutions/0001-0099/reverse-linked-list-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md,55.7%,中等,3711 -0093,0001-0099,0093. 复原 IP 地址,复原 IP 地址,https://leetcode.cn/problems/restore-ip-addresses/,restore-ip-addresses,字符串、回溯,https://algo.itcharge.cn/Solutions/0001-0099/restore-ip-addresses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md,58.1%,中等,2476 -0094,0001-0099,0094. 二叉树的中序遍历,二叉树的中序遍历,https://leetcode.cn/problems/binary-tree-inorder-traversal/,binary-tree-inorder-traversal,栈、树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0001-0099/binary-tree-inorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md,76.2%,简单,4277 -0095,0001-0099,0095. 不同的二叉搜索树 II,不同的二叉搜索树 II,https://leetcode.cn/problems/unique-binary-search-trees-ii/,unique-binary-search-trees-ii,树、二叉搜索树、动态规划、回溯、二叉树,https://algo.itcharge.cn/Solutions/0001-0099/unique-binary-search-trees-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0095.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%20II.md,73.4%,中等,1054 -0096,0001-0099,0096. 不同的二叉搜索树,不同的二叉搜索树,https://leetcode.cn/problems/unique-binary-search-trees/,unique-binary-search-trees,树、二叉搜索树、数学、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0001-0099/unique-binary-search-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0096.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,70.9%,中等,2502 -0097,0001-0099,0097. 交错字符串,交错字符串,https://leetcode.cn/problems/interleaving-string/,interleaving-string,字符串、动态规划,https://algo.itcharge.cn/Solutions/0001-0099/interleaving-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0097.%20%E4%BA%A4%E9%94%99%E5%AD%97%E7%AC%A6%E4%B8%B2.md,44.7%,中等,888 -0098,0001-0099,0098. 验证二叉搜索树,验证二叉搜索树,https://leetcode.cn/problems/validate-binary-search-tree/,validate-binary-search-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0001-0099/validate-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,37.0%,中等,4035 -0099,0001-0099,0099. 恢复二叉搜索树,恢复二叉搜索树,https://leetcode.cn/problems/recover-binary-search-tree/,recover-binary-search-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0001-0099/recover-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0099.%20%E6%81%A2%E5%A4%8D%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,60.3%,中等,1091 -0100,0100-0199,0100. 相同的树,相同的树,https://leetcode.cn/problems/same-tree/,same-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/same-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md,60.0%,简单,3145 -0101,0100-0199,0101. 对称二叉树,对称二叉树,https://leetcode.cn/problems/symmetric-tree/,symmetric-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/symmetric-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md,58.8%,简单,4297 -0102,0100-0199,0102. 二叉树的层序遍历,二叉树的层序遍历,https://leetcode.cn/problems/binary-tree-level-order-traversal/,binary-tree-level-order-traversal,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-level-order-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md,65.6%,中等,4512 -0103,0100-0199,0103. 二叉树的锯齿形层序遍历,二叉树的锯齿形层序遍历,https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/,binary-tree-zigzag-level-order-traversal,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-zigzag-level-order-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md,57.5%,中等,2476 -0104,0100-0199,0104. 二叉树的最大深度,二叉树的最大深度,https://leetcode.cn/problems/maximum-depth-of-binary-tree/,maximum-depth-of-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/maximum-depth-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md,77.1%,简单,5159 -0105,0100-0199,0105. 从前序与中序遍历序列构造二叉树,从前序与中序遍历序列构造二叉树,https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/,construct-binary-tree-from-preorder-and-inorder-traversal,树、数组、哈希表、分治、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md,71.3%,中等,2626 -0106,0100-0199,0106. 从中序与后序遍历序列构造二叉树,从中序与后序遍历序列构造二叉树,https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/,construct-binary-tree-from-inorder-and-postorder-traversal,树、数组、哈希表、分治、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0106.%20%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md,72.1%,中等,1737 -0107,0100-0199,0107. 二叉树的层序遍历 II,二叉树的层序遍历 II,https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/,binary-tree-level-order-traversal-ii,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-level-order-traversal-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0107.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%20II.md,72.5%,中等,1973 -0108,0100-0199,0108. 将有序数组转换为二叉搜索树,将有序数组转换为二叉搜索树,https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/,convert-sorted-array-to-binary-search-tree,树、二叉搜索树、数组、分治、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/convert-sorted-array-to-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0108.%20%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,77.4%,简单,2082 -0109,0100-0199,0109. 有序链表转换二叉搜索树,有序链表转换二叉搜索树,https://leetcode.cn/problems/convert-sorted-list-to-binary-search-tree/,convert-sorted-list-to-binary-search-tree,树、二叉搜索树、链表、分治、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/convert-sorted-list-to-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0109.%20%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8%E8%BD%AC%E6%8D%A2%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,76.5%,中等,1116 -0110,0100-0199,0110. 平衡二叉树,平衡二叉树,https://leetcode.cn/problems/balanced-binary-tree/,balanced-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/balanced-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md,57.5%,简单,2586 -0111,0100-0199,0111. 二叉树的最小深度,二叉树的最小深度,https://leetcode.cn/problems/minimum-depth-of-binary-tree/,minimum-depth-of-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/minimum-depth-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md,52.2%,简单,3859 -0112,0100-0199,0112. 路径总和,路径总和,https://leetcode.cn/problems/path-sum/,path-sum,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/path-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md,53.5%,简单,3126 -0113,0100-0199,0113. 路径总和 II,路径总和 II,https://leetcode.cn/problems/path-sum-ii/,path-sum-ii,树、深度优先搜索、回溯、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/path-sum-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md,63.2%,中等,2216 -0114,0100-0199,0114. 二叉树展开为链表,二叉树展开为链表,https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/,flatten-binary-tree-to-linked-list,栈、树、深度优先搜索、链表、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/flatten-binary-tree-to-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0114.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%B1%95%E5%BC%80%E4%B8%BA%E9%93%BE%E8%A1%A8.md,73.0%,中等,2921 -0115,0100-0199,0115. 不同的子序列,不同的子序列,https://leetcode.cn/problems/distinct-subsequences/,distinct-subsequences,字符串、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/distinct-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md,52.2%,困难,1251 -0116,0100-0199,0116. 填充每个节点的下一个右侧节点指针,填充每个节点的下一个右侧节点指针,https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/,populating-next-right-pointers-in-each-node,树、深度优先搜索、广度优先搜索、链表、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/populating-next-right-pointers-in-each-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0116.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88.md,72.6%,中等,2653 -0117,0100-0199,0117. 填充每个节点的下一个右侧节点指针 II,填充每个节点的下一个右侧节点指针 II,https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/,populating-next-right-pointers-in-each-node-ii,树、深度优先搜索、广度优先搜索、链表、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/populating-next-right-pointers-in-each-node-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0117.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88%20II.md,65.8%,中等,1378 -0118,0100-0199,0118. 杨辉三角,杨辉三角,https://leetcode.cn/problems/pascals-triangle/,pascals-triangle,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/pascals-triangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md,75.5%,简单,3186 -0119,0100-0199,0119. 杨辉三角 II,杨辉三角 II,https://leetcode.cn/problems/pascals-triangle-ii/,pascals-triangle-ii,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/pascals-triangle-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md,68.9%,简单,2067 -0120,0100-0199,0120. 三角形最小路径和,三角形最小路径和,https://leetcode.cn/problems/triangle/,triangle,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/triangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0120.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md,68.7%,中等,2422 -0121,0100-0199,0121. 买卖股票的最佳时机,买卖股票的最佳时机,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/,best-time-to-buy-and-sell-stock,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/best-time-to-buy-and-sell-stock/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md,58.0%,简单,6175 -0122,0100-0199,0122. 买卖股票的最佳时机 II,买卖股票的最佳时机 II,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/,best-time-to-buy-and-sell-stock-ii,贪心、数组,https://algo.itcharge.cn/Solutions/0100-0199/best-time-to-buy-and-sell-stock-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md,72.0%,中等,4616 -0123,0100-0199,0123. 买卖股票的最佳时机 III,买卖股票的最佳时机 III,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/,best-time-to-buy-and-sell-stock-iii,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/best-time-to-buy-and-sell-stock-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0123.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20III.md,58.8%,困难,1475 -0124,0100-0199,0124. 二叉树中的最大路径和,二叉树中的最大路径和,https://leetcode.cn/problems/binary-tree-maximum-path-sum/,binary-tree-maximum-path-sum,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-maximum-path-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md,45.3%,困难,1948 -0125,0100-0199,0125. 验证回文串,验证回文串,https://leetcode.cn/problems/valid-palindrome/,valid-palindrome,双指针、字符串,https://algo.itcharge.cn/Solutions/0100-0199/valid-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md,46.4%,简单,2976 -0126,0100-0199,0126. 单词接龙 II,单词接龙 II,https://leetcode.cn/problems/word-ladder-ii/,word-ladder-ii,广度优先搜索、哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/0100-0199/word-ladder-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0126.%20%E5%8D%95%E8%AF%8D%E6%8E%A5%E9%BE%99%20II.md,37.9%,困难,493 -0127,0100-0199,0127. 单词接龙,单词接龙,https://leetcode.cn/problems/word-ladder/,word-ladder,广度优先搜索、哈希表、字符串,https://algo.itcharge.cn/Solutions/0100-0199/word-ladder/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0127.%20%E5%8D%95%E8%AF%8D%E6%8E%A5%E9%BE%99.md,48.3%,困难,1008 -0128,0100-0199,0128. 最长连续序列,最长连续序列,https://leetcode.cn/problems/longest-consecutive-sequence/,longest-consecutive-sequence,并查集、数组、哈希表,https://algo.itcharge.cn/Solutions/0100-0199/longest-consecutive-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md,54.7%,中等,2349 -0129,0100-0199,0129. 求根节点到叶节点数字之和,求根节点到叶节点数字之和,https://leetcode.cn/problems/sum-root-to-leaf-numbers/,sum-root-to-leaf-numbers,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/sum-root-to-leaf-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md,70.1%,中等,1772 -0130,0100-0199,0130. 被围绕的区域,被围绕的区域,https://leetcode.cn/problems/surrounded-regions/,surrounded-regions,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0100-0199/surrounded-regions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0130.%20%E8%A2%AB%E5%9B%B4%E7%BB%95%E7%9A%84%E5%8C%BA%E5%9F%9F.md,46.2%,中等,2210 -0131,0100-0199,0131. 分割回文串,分割回文串,https://leetcode.cn/problems/palindrome-partitioning/,palindrome-partitioning,字符串、动态规划、回溯,https://algo.itcharge.cn/Solutions/0100-0199/palindrome-partitioning/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0131.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.md,73.4%,中等,1739 -0132,0100-0199,0132. 分割回文串 II,分割回文串 II,https://leetcode.cn/problems/palindrome-partitioning-ii/,palindrome-partitioning-ii,字符串、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/palindrome-partitioning-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0132.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2%20II.md,49.9%,困难,621 -0133,0100-0199,0133. 克隆图,克隆图,https://leetcode.cn/problems/clone-graph/,clone-graph,深度优先搜索、广度优先搜索、图、哈希表,https://algo.itcharge.cn/Solutions/0100-0199/clone-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md,69.0%,中等,805 -0134,0100-0199,0134. 加油站,加油站,https://leetcode.cn/problems/gas-station/,gas-station,贪心、数组,https://algo.itcharge.cn/Solutions/0100-0199/gas-station/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0134.%20%E5%8A%A0%E6%B2%B9%E7%AB%99.md,51.3%,中等,1732 -0135,0100-0199,0135. 分发糖果,分发糖果,https://leetcode.cn/problems/candy/,candy,贪心、数组,https://algo.itcharge.cn/Solutions/0100-0199/candy/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0135.%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.md,50.5%,困难,1557 -0136,0100-0199,0136. 只出现一次的数字,只出现一次的数字,https://leetcode.cn/problems/single-number/,single-number,位运算、数组,https://algo.itcharge.cn/Solutions/0100-0199/single-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md,72.3%,简单,4067 -0137,0100-0199,0137. 只出现一次的数字 II,只出现一次的数字 II,https://leetcode.cn/problems/single-number-ii/,single-number-ii,位运算、数组,https://algo.itcharge.cn/Solutions/0100-0199/single-number-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0137.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20II.md,71.8%,中等,1089 -0138,0100-0199,0138. 复制带随机指针的链表,复制带随机指针的链表,https://leetcode.cn/problems/copy-list-with-random-pointer/,copy-list-with-random-pointer,哈希表、链表,https://algo.itcharge.cn/Solutions/0100-0199/copy-list-with-random-pointer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0138.%20%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8.md,66.0%,中等,1403 -0139,0100-0199,0139. 单词拆分,单词拆分,https://leetcode.cn/problems/word-break/,word-break,字典树、记忆化搜索、数组、哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/word-break/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md,54.2%,中等,2500 -0140,0100-0199,0140. 单词拆分 II,单词拆分 II,https://leetcode.cn/problems/word-break-ii/,word-break-ii,字典树、记忆化搜索、数组、哈希表、字符串、动态规划、回溯,https://algo.itcharge.cn/Solutions/0100-0199/word-break-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0140.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86%20II.md,57.2%,困难,996 -0141,0100-0199,0141. 环形链表,环形链表,https://leetcode.cn/problems/linked-list-cycle/,linked-list-cycle,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/0100-0199/linked-list-cycle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md,51.6%,简单,5009 -0142,0100-0199,0142. 环形链表 II,环形链表 II,https://leetcode.cn/problems/linked-list-cycle-ii/,linked-list-cycle-ii,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/0100-0199/linked-list-cycle-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md,57.1%,中等,3798 -0143,0100-0199,0143. 重排链表,重排链表,https://leetcode.cn/problems/reorder-list/,reorder-list,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/0100-0199/reorder-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md,64.9%,中等,1851 -0144,0100-0199,0144. 二叉树的前序遍历,二叉树的前序遍历,https://leetcode.cn/problems/binary-tree-preorder-traversal/,binary-tree-preorder-traversal,栈、树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-preorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md,71.3%,简单,3566 -0145,0100-0199,0145. 二叉树的后序遍历,二叉树的后序遍历,https://leetcode.cn/problems/binary-tree-postorder-traversal/,binary-tree-postorder-traversal,栈、树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-postorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md,76.3%,简单,2714 -0146,0100-0199,0146. LRU 缓存,LRU 缓存,https://leetcode.cn/problems/lru-cache/,lru-cache,设计、哈希表、链表、双向链表,https://algo.itcharge.cn/Solutions/0100-0199/lru-cache/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0146.%20LRU%20%E7%BC%93%E5%AD%98.md,53.5%,中等,3603 -0147,0100-0199,0147. 对链表进行插入排序,对链表进行插入排序,https://leetcode.cn/problems/insertion-sort-list/,insertion-sort-list,链表、排序,https://algo.itcharge.cn/Solutions/0100-0199/insertion-sort-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0147.%20%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md,69.5%,中等,1107 -0148,0100-0199,0148. 排序链表,排序链表,https://leetcode.cn/problems/sort-list/,sort-list,链表、双指针、分治、排序、归并排序,https://algo.itcharge.cn/Solutions/0100-0199/sort-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md,65.7%,中等,2196 -0149,0100-0199,0149. 直线上最多的点数,直线上最多的点数,https://leetcode.cn/problems/max-points-on-a-line/,max-points-on-a-line,几何、数组、哈希表、数学,https://algo.itcharge.cn/Solutions/0100-0199/max-points-on-a-line/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0149.%20%E7%9B%B4%E7%BA%BF%E4%B8%8A%E6%9C%80%E5%A4%9A%E7%9A%84%E7%82%B9%E6%95%B0.md,39.3%,困难,786 -0150,0100-0199,0150. 逆波兰表达式求值,逆波兰表达式求值,https://leetcode.cn/problems/evaluate-reverse-polish-notation/,evaluate-reverse-polish-notation,栈、数组、数学,https://algo.itcharge.cn/Solutions/0100-0199/evaluate-reverse-polish-notation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0150.%20%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.md,52.7%,中等,1962 -0151,0100-0199,0151. 反转字符串中的单词,反转字符串中的单词,https://leetcode.cn/problems/reverse-words-in-a-string/,reverse-words-in-a-string,双指针、字符串,https://algo.itcharge.cn/Solutions/0100-0199/reverse-words-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md,51.8%,中等,3043 -0152,0100-0199,0152. 乘积最大子数组,乘积最大子数组,https://leetcode.cn/problems/maximum-product-subarray/,maximum-product-subarray,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/maximum-product-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md,43.1%,中等,2142 -0153,0100-0199,0153. 寻找旋转排序数组中的最小值,寻找旋转排序数组中的最小值,https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/,find-minimum-in-rotated-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/0100-0199/find-minimum-in-rotated-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,57.0%,中等,2473 -0154,0100-0199,0154. 寻找旋转排序数组中的最小值 II,寻找旋转排序数组中的最小值 II,https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/,find-minimum-in-rotated-sorted-array-ii,数组、二分查找,https://algo.itcharge.cn/Solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0154.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%20II.md,52.5%,困难,1287 -0155,0100-0199,0155. 最小栈,最小栈,https://leetcode.cn/problems/min-stack/,min-stack,栈、设计,https://algo.itcharge.cn/Solutions/0100-0199/min-stack/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md,59.0%,中等,2550 -0156,0100-0199,0156. 上下翻转二叉树,上下翻转二叉树,https://leetcode.cn/problems/binary-tree-upside-down/,binary-tree-upside-down,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-upside-down/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0156.%20%E4%B8%8A%E4%B8%8B%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md,72.3%,中等,234 -0157,0100-0199,0157. 用 Read4 读取 N 个字符,用 Read4 读取 N 个字符,https://leetcode.cn/problems/read-n-characters-given-read4/,read-n-characters-given-read4,字符串、交互、模拟,https://algo.itcharge.cn/Solutions/0100-0199/read-n-characters-given-read4/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0157.%20%E7%94%A8%20Read4%20%E8%AF%BB%E5%8F%96%20N%20%E4%B8%AA%E5%AD%97%E7%AC%A6.md,54.4%,简单,135 -0158,0100-0199,0158. 用 Read4 读取 N 个字符 II,用 Read4 读取 N 个字符 II,https://leetcode.cn/problems/read-n-characters-given-read4-ii-call-multiple-times/,read-n-characters-given-read4-ii-call-multiple-times,字符串、交互、模拟,https://algo.itcharge.cn/Solutions/0100-0199/read-n-characters-given-read4-ii-call-multiple-times/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0158.%20%E7%94%A8%20Read4%20%E8%AF%BB%E5%8F%96%20N%20%E4%B8%AA%E5%AD%97%E7%AC%A6%20II.md,60.0%,困难,104 -0159,0100-0199,0159. 至多包含两个不同字符的最长子串,至多包含两个不同字符的最长子串,https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/,longest-substring-with-at-most-two-distinct-characters,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0159.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md,55.6%,中等,345 -0160,0100-0199,0160. 相交链表,相交链表,https://leetcode.cn/problems/intersection-of-two-linked-lists/,intersection-of-two-linked-lists,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/0100-0199/intersection-of-two-linked-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md,63.7%,简单,3730 -0161,0100-0199,0161. 相隔为 1 的编辑距离,相隔为 1 的编辑距离,https://leetcode.cn/problems/one-edit-distance/,one-edit-distance,双指针、字符串,https://algo.itcharge.cn/Solutions/0100-0199/one-edit-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0161.%20%E7%9B%B8%E9%9A%94%E4%B8%BA%201%20%E7%9A%84%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md,33.9%,中等,184 -0162,0100-0199,0162. 寻找峰值,寻找峰值,https://leetcode.cn/problems/find-peak-element/,find-peak-element,数组、二分查找,https://algo.itcharge.cn/Solutions/0100-0199/find-peak-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md,49.3%,中等,1956 -0163,0100-0199,0163. 缺失的区间,缺失的区间,https://leetcode.cn/problems/missing-ranges/,missing-ranges,数组,https://algo.itcharge.cn/Solutions/0100-0199/missing-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0163.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E5%8C%BA%E9%97%B4.md,35.6%,简单,258 -0164,0100-0199,0164. 最大间距,最大间距,https://leetcode.cn/problems/maximum-gap/,maximum-gap,数组、桶排序、基数排序、排序,https://algo.itcharge.cn/Solutions/0100-0199/maximum-gap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md,60.2%,困难,661 -0165,0100-0199,0165. 比较版本号,比较版本号,https://leetcode.cn/problems/compare-version-numbers/,compare-version-numbers,双指针、字符串,https://algo.itcharge.cn/Solutions/0100-0199/compare-version-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0165.%20%E6%AF%94%E8%BE%83%E7%89%88%E6%9C%AC%E5%8F%B7.md,51.8%,中等,1304 -0166,0100-0199,0166. 分数到小数,分数到小数,https://leetcode.cn/problems/fraction-to-recurring-decimal/,fraction-to-recurring-decimal,哈希表、数学、字符串,https://algo.itcharge.cn/Solutions/0100-0199/fraction-to-recurring-decimal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0166.%20%E5%88%86%E6%95%B0%E5%88%B0%E5%B0%8F%E6%95%B0.md,33.4%,中等,530 -0167,0100-0199,0167. 两数之和 II - 输入有序数组,两数之和 II - 输入有序数组,https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/,two-sum-ii-input-array-is-sorted,数组、双指针、二分查找,https://algo.itcharge.cn/Solutions/0100-0199/two-sum-ii-input-array-is-sorted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md,59.4%,中等,3672 -0168,0100-0199,0168. Excel表列名称,Excel表列名称,https://leetcode.cn/problems/excel-sheet-column-title/,excel-sheet-column-title,数学、字符串,https://algo.itcharge.cn/Solutions/0100-0199/excel-sheet-column-title/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0168.%20Excel%E8%A1%A8%E5%88%97%E5%90%8D%E7%A7%B0.md,43.8%,简单,1157 -0169,0100-0199,0169. 多数元素,多数元素,https://leetcode.cn/problems/majority-element/,majority-element,数组、哈希表、分治、计数、排序,https://algo.itcharge.cn/Solutions/0100-0199/majority-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md,66.8%,简单,3743 -0170,0100-0199,0170. 两数之和 III - 数据结构设计,两数之和 III - 数据结构设计,https://leetcode.cn/problems/two-sum-iii-data-structure-design/,two-sum-iii-data-structure-design,设计、数组、哈希表、双指针、数据流,https://algo.itcharge.cn/Solutions/0100-0199/two-sum-iii-data-structure-design/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0170.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20III%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md,42.7%,简单,156 -0171,0100-0199,0171. Excel 表列序号,Excel 表列序号,https://leetcode.cn/problems/excel-sheet-column-number/,excel-sheet-column-number,数学、字符串,https://algo.itcharge.cn/Solutions/0100-0199/excel-sheet-column-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0171.%20Excel%20%E8%A1%A8%E5%88%97%E5%BA%8F%E5%8F%B7.md,71.3%,简单,1343 -0172,0100-0199,0172. 阶乘后的零,阶乘后的零,https://leetcode.cn/problems/factorial-trailing-zeroes/,factorial-trailing-zeroes,数学,https://algo.itcharge.cn/Solutions/0100-0199/factorial-trailing-zeroes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0172.%20%E9%98%B6%E4%B9%98%E5%90%8E%E7%9A%84%E9%9B%B6.md,50.0%,中等,1123 -0173,0100-0199,0173. 二叉搜索树迭代器,二叉搜索树迭代器,https://leetcode.cn/problems/binary-search-tree-iterator/,binary-search-tree-iterator,栈、树、设计、二叉搜索树、二叉树、迭代器,https://algo.itcharge.cn/Solutions/0100-0199/binary-search-tree-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0173.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md,81.6%,中等,896 -0174,0100-0199,0174. 地下城游戏,地下城游戏,https://leetcode.cn/problems/dungeon-game/,dungeon-game,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0100-0199/dungeon-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0174.%20%E5%9C%B0%E4%B8%8B%E5%9F%8E%E6%B8%B8%E6%88%8F.md,48.7%,困难,1403 -0175,0100-0199,0175. 组合两个表,组合两个表,https://leetcode.cn/problems/combine-two-tables/,combine-two-tables,,https://algo.itcharge.cn/Solutions/0100-0199/combine-two-tables/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0175.%20%E7%BB%84%E5%90%88%E4%B8%A4%E4%B8%AA%E8%A1%A8.md,74.2%,简单,2159 -0176,0100-0199,0176. 第二高的薪水,第二高的薪水,https://leetcode.cn/problems/second-highest-salary/,second-highest-salary,数据库,https://algo.itcharge.cn/Solutions/0100-0199/second-highest-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0176.%20%E7%AC%AC%E4%BA%8C%E9%AB%98%E7%9A%84%E8%96%AA%E6%B0%B4.md,36.5%,中等,1677 -0177,0100-0199,0177. 第N高的薪水,第N高的薪水,https://leetcode.cn/problems/nth-highest-salary/,nth-highest-salary,数据库,https://algo.itcharge.cn/Solutions/0100-0199/nth-highest-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0177.%20%E7%AC%ACN%E9%AB%98%E7%9A%84%E8%96%AA%E6%B0%B4.md,46.7%,中等,815 -0178,0100-0199,0178. 分数排名,分数排名,https://leetcode.cn/problems/rank-scores/,rank-scores,数据库,https://algo.itcharge.cn/Solutions/0100-0199/rank-scores/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0178.%20%E5%88%86%E6%95%B0%E6%8E%92%E5%90%8D.md,61.1%,中等,1012 -0179,0100-0199,0179. 最大数,最大数,https://leetcode.cn/problems/largest-number/,largest-number,贪心、数组、字符串、排序,https://algo.itcharge.cn/Solutions/0100-0199/largest-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md,41.1%,中等,1451 -0180,0100-0199,0180. 连续出现的数字,连续出现的数字,https://leetcode.cn/problems/consecutive-numbers/,consecutive-numbers,数据库,https://algo.itcharge.cn/Solutions/0100-0199/consecutive-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0180.%20%E8%BF%9E%E7%BB%AD%E5%87%BA%E7%8E%B0%E7%9A%84%E6%95%B0%E5%AD%97.md,47.8%,中等,877 -0181,0100-0199,0181. 超过经理收入的员工,超过经理收入的员工,https://leetcode.cn/problems/employees-earning-more-than-their-managers/,employees-earning-more-than-their-managers,数据库,https://algo.itcharge.cn/Solutions/0100-0199/employees-earning-more-than-their-managers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0181.%20%E8%B6%85%E8%BF%87%E7%BB%8F%E7%90%86%E6%94%B6%E5%85%A5%E7%9A%84%E5%91%98%E5%B7%A5.md,68.7%,简单,1115 -0182,0100-0199,0182. 查找重复的电子邮箱,查找重复的电子邮箱,https://leetcode.cn/problems/duplicate-emails/,duplicate-emails,数据库,https://algo.itcharge.cn/Solutions/0100-0199/duplicate-emails/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0182.%20%E6%9F%A5%E6%89%BE%E9%87%8D%E5%A4%8D%E7%9A%84%E7%94%B5%E5%AD%90%E9%82%AE%E7%AE%B1.md,79.0%,简单,1015 -0183,0100-0199,0183. 从不订购的客户,从不订购的客户,https://leetcode.cn/problems/customers-who-never-order/,customers-who-never-order,数据库,https://algo.itcharge.cn/Solutions/0100-0199/customers-who-never-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0183.%20%E4%BB%8E%E4%B8%8D%E8%AE%A2%E8%B4%AD%E7%9A%84%E5%AE%A2%E6%88%B7.md,66.3%,简单,1117 -0184,0100-0199,0184. 部门工资最高的员工,部门工资最高的员工,https://leetcode.cn/problems/department-highest-salary/,department-highest-salary,数据库,https://algo.itcharge.cn/Solutions/0100-0199/department-highest-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0184.%20%E9%83%A8%E9%97%A8%E5%B7%A5%E8%B5%84%E6%9C%80%E9%AB%98%E7%9A%84%E5%91%98%E5%B7%A5.md,51.3%,中等,1123 -0185,0100-0199,0185. 部门工资前三高的所有员工,部门工资前三高的所有员工,https://leetcode.cn/problems/department-top-three-salaries/,department-top-three-salaries,数据库,https://algo.itcharge.cn/Solutions/0100-0199/department-top-three-salaries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0185.%20%E9%83%A8%E9%97%A8%E5%B7%A5%E8%B5%84%E5%89%8D%E4%B8%89%E9%AB%98%E7%9A%84%E6%89%80%E6%9C%89%E5%91%98%E5%B7%A5.md,53.0%,困难,1020 -0186,0100-0199,0186. 反转字符串中的单词 II,反转字符串中的单词 II,https://leetcode.cn/problems/reverse-words-in-a-string-ii/,reverse-words-in-a-string-ii,双指针、字符串,https://algo.itcharge.cn/Solutions/0100-0199/reverse-words-in-a-string-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0186.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20II.md,76.0%,中等,186 -0187,0100-0199,0187. 重复的DNA序列,重复的DNA序列,https://leetcode.cn/problems/repeated-dna-sequences/,repeated-dna-sequences,位运算、哈希表、字符串、滑动窗口、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/0100-0199/repeated-dna-sequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0187.%20%E9%87%8D%E5%A4%8D%E7%9A%84DNA%E5%BA%8F%E5%88%97.md,53.4%,中等,1007 -0188,0100-0199,0188. 买卖股票的最佳时机 IV,买卖股票的最佳时机 IV,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/,best-time-to-buy-and-sell-stock-iv,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/best-time-to-buy-and-sell-stock-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0188.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20IV.md,45.6%,困难,1353 -0189,0100-0199,0189. 轮转数组,轮转数组,https://leetcode.cn/problems/rotate-array/,rotate-array,数组、数学、双指针,https://algo.itcharge.cn/Solutions/0100-0199/rotate-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0189.%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84.md,44.2%,中等,3304 -0190,0100-0199,0190. 颠倒二进制位,颠倒二进制位,https://leetcode.cn/problems/reverse-bits/,reverse-bits,位运算、分治,https://algo.itcharge.cn/Solutions/0100-0199/reverse-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0190.%20%E9%A2%A0%E5%80%92%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BD%8D.md,71.8%,简单,1310 -0191,0100-0199,0191. 位1的个数,位1的个数,https://leetcode.cn/problems/number-of-1-bits/,number-of-1-bits,位运算、分治,https://algo.itcharge.cn/Solutions/0100-0199/number-of-1-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0191.%20%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0.md,76.5%,简单,2075 -0192,0100-0199,0192. 统计词频,统计词频,https://leetcode.cn/problems/word-frequency/,word-frequency,,https://algo.itcharge.cn/Solutions/0100-0199/word-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0192.%20%E7%BB%9F%E8%AE%A1%E8%AF%8D%E9%A2%91.md,35.7%,中等,231 -0193,0100-0199,0193. 有效电话号码,有效电话号码,https://leetcode.cn/problems/valid-phone-numbers/,valid-phone-numbers,,https://algo.itcharge.cn/Solutions/0100-0199/valid-phone-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0193.%20%E6%9C%89%E6%95%88%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81.md,33.4%,简单,196 -0194,0100-0199,0194. 转置文件,转置文件,https://leetcode.cn/problems/transpose-file/,transpose-file,,https://algo.itcharge.cn/Solutions/0100-0199/transpose-file/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0194.%20%E8%BD%AC%E7%BD%AE%E6%96%87%E4%BB%B6.md,34.3%,中等,103 -0195,0100-0199,0195. 第十行,第十行,https://leetcode.cn/problems/tenth-line/,tenth-line,,https://algo.itcharge.cn/Solutions/0100-0199/tenth-line/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0195.%20%E7%AC%AC%E5%8D%81%E8%A1%8C.md,43.9%,简单,204 -0196,0100-0199,0196. 删除重复的电子邮箱,删除重复的电子邮箱,https://leetcode.cn/problems/delete-duplicate-emails/,delete-duplicate-emails,数据库,https://algo.itcharge.cn/Solutions/0100-0199/delete-duplicate-emails/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0196.%20%E5%88%A0%E9%99%A4%E9%87%8D%E5%A4%8D%E7%9A%84%E7%94%B5%E5%AD%90%E9%82%AE%E7%AE%B1.md,68.7%,简单,800 -0197,0100-0199,0197. 上升的温度,上升的温度,https://leetcode.cn/problems/rising-temperature/,rising-temperature,数据库,https://algo.itcharge.cn/Solutions/0100-0199/rising-temperature/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0197.%20%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B8%A9%E5%BA%A6.md,54.2%,简单,1009 -0198,0100-0199,0198. 打家劫舍,打家劫舍,https://leetcode.cn/problems/house-robber/,house-robber,数组、动态规划,https://algo.itcharge.cn/Solutions/0100-0199/house-robber/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md,54.4%,中等,4722 -0199,0100-0199,0199. 二叉树的右视图,二叉树的右视图,https://leetcode.cn/problems/binary-tree-right-side-view/,binary-tree-right-side-view,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0100-0199/binary-tree-right-side-view/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md,65.9%,中等,2734 -0200,0200-0299,0200. 岛屿数量,岛屿数量,https://leetcode.cn/problems/number-of-islands/,number-of-islands,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0200-0299/number-of-islands/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md,59.3%,中等,4002 -0201,0200-0299,0201. 数字范围按位与,数字范围按位与,https://leetcode.cn/problems/bitwise-and-of-numbers-range/,bitwise-and-of-numbers-range,位运算,https://algo.itcharge.cn/Solutions/0200-0299/bitwise-and-of-numbers-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0201.%20%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4%E6%8C%89%E4%BD%8D%E4%B8%8E.md,54.0%,中等,518 -0202,0200-0299,0202. 快乐数,快乐数,https://leetcode.cn/problems/happy-number/,happy-number,哈希表、数学、双指针,https://algo.itcharge.cn/Solutions/0200-0299/happy-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0202.%20%E5%BF%AB%E4%B9%90%E6%95%B0.md,63.3%,简单,2817 -0203,0200-0299,0203. 移除链表元素,移除链表元素,https://leetcode.cn/problems/remove-linked-list-elements/,remove-linked-list-elements,递归、链表,https://algo.itcharge.cn/Solutions/0200-0299/remove-linked-list-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0203.%20%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.md,54.9%,简单,3261 -0204,0200-0299,0204. 计数质数,计数质数,https://leetcode.cn/problems/count-primes/,count-primes,数组、数学、枚举、数论,https://algo.itcharge.cn/Solutions/0200-0299/count-primes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0204.%20%E8%AE%A1%E6%95%B0%E8%B4%A8%E6%95%B0.md,37.3%,中等,1102 -0205,0200-0299,0205. 同构字符串,同构字符串,https://leetcode.cn/problems/isomorphic-strings/,isomorphic-strings,哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/isomorphic-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0205.%20%E5%90%8C%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,49.6%,简单,1538 -0206,0200-0299,0206. 反转链表,反转链表,https://leetcode.cn/problems/reverse-linked-list/,reverse-linked-list,递归、链表,https://algo.itcharge.cn/Solutions/0200-0299/reverse-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md,73.5%,简单,11518 -0207,0200-0299,0207. 课程表,课程表,https://leetcode.cn/problems/course-schedule/,course-schedule,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/0200-0299/course-schedule/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md,53.6%,中等,2176 -0208,0200-0299,0208. 实现 Trie (前缀树),实现 Trie (前缀树),https://leetcode.cn/problems/implement-trie-prefix-tree/,implement-trie-prefix-tree,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/implement-trie-prefix-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0208.%20%E5%AE%9E%E7%8E%B0%20Trie%20%28%E5%89%8D%E7%BC%80%E6%A0%91%29.md,71.9%,中等,1658 -0209,0200-0299,0209. 长度最小的子数组,长度最小的子数组,https://leetcode.cn/problems/minimum-size-subarray-sum/,minimum-size-subarray-sum,数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/0200-0299/minimum-size-subarray-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,46.8%,中等,3433 -0210,0200-0299,0210. 课程表 II,课程表 II,https://leetcode.cn/problems/course-schedule-ii/,course-schedule-ii,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/0200-0299/course-schedule-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md,56.6%,中等,1329 -0211,0200-0299,0211. 添加与搜索单词 - 数据结构设计,添加与搜索单词 - 数据结构设计,https://leetcode.cn/problems/design-add-and-search-words-data-structure/,design-add-and-search-words-data-structure,深度优先搜索、设计、字典树、字符串,https://algo.itcharge.cn/Solutions/0200-0299/design-add-and-search-words-data-structure/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0211.%20%E6%B7%BB%E5%8A%A0%E4%B8%8E%E6%90%9C%E7%B4%A2%E5%8D%95%E8%AF%8D%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md,49.6%,中等,750 -0212,0200-0299,0212. 单词搜索 II,单词搜索 II,https://leetcode.cn/problems/word-search-ii/,word-search-ii,字典树、数组、字符串、回溯、矩阵,https://algo.itcharge.cn/Solutions/0200-0299/word-search-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0212.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%20II.md,44.0%,困难,749 -0213,0200-0299,0213. 打家劫舍 II,打家劫舍 II,https://leetcode.cn/problems/house-robber-ii/,house-robber-ii,数组、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/house-robber-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0213.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20II.md,44.1%,中等,2720 -0214,0200-0299,0214. 最短回文串,最短回文串,https://leetcode.cn/problems/shortest-palindrome/,shortest-palindrome,字符串、字符串匹配、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/0200-0299/shortest-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0214.%20%E6%9C%80%E7%9F%AD%E5%9B%9E%E6%96%87%E4%B8%B2.md,40.0%,困难,463 -0215,0200-0299,0215. 数组中的第K个最大元素,数组中的第K个最大元素,https://leetcode.cn/problems/kth-largest-element-in-an-array/,kth-largest-element-in-an-array,数组、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/kth-largest-element-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md,63.7%,中等,4063 -0216,0200-0299,0216. 组合总和 III,组合总和 III,https://leetcode.cn/problems/combination-sum-iii/,combination-sum-iii,数组、回溯,https://algo.itcharge.cn/Solutions/0200-0299/combination-sum-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0216.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20III.md,71.5%,中等,1952 -0217,0200-0299,0217. 存在重复元素,存在重复元素,https://leetcode.cn/problems/contains-duplicate/,contains-duplicate,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/0200-0299/contains-duplicate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md,54.8%,简单,3744 -0218,0200-0299,0218. 天际线问题,天际线问题,https://leetcode.cn/problems/the-skyline-problem/,the-skyline-problem,树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/the-skyline-problem/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md,55.1%,困难,436 -0219,0200-0299,0219. 存在重复元素 II,存在重复元素 II,https://leetcode.cn/problems/contains-duplicate-ii/,contains-duplicate-ii,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/0200-0299/contains-duplicate-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0219.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md,44.3%,简单,1823 -0220,0200-0299,0220. 存在重复元素 III,存在重复元素 III,https://leetcode.cn/problems/contains-duplicate-iii/,contains-duplicate-iii,数组、桶排序、有序集合、排序、滑动窗口,https://algo.itcharge.cn/Solutions/0200-0299/contains-duplicate-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md,30.0%,困难,630 -0221,0200-0299,0221. 最大正方形,最大正方形,https://leetcode.cn/problems/maximal-square/,maximal-square,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0200-0299/maximal-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md,49.7%,中等,1694 -0222,0200-0299,0222. 完全二叉树的节点个数,完全二叉树的节点个数,https://leetcode.cn/problems/count-complete-tree-nodes/,count-complete-tree-nodes,树、深度优先搜索、二分查找、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/count-complete-tree-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0222.%20%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%8A%82%E7%82%B9%E4%B8%AA%E6%95%B0.md,81.0%,中等,1780 -0223,0200-0299,0223. 矩形面积,矩形面积,https://leetcode.cn/problems/rectangle-area/,rectangle-area,几何、数学,https://algo.itcharge.cn/Solutions/0200-0299/rectangle-area/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0223.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md,53.8%,中等,647 -0224,0200-0299,0224. 基本计算器,基本计算器,https://leetcode.cn/problems/basic-calculator/,basic-calculator,栈、递归、数学、字符串,https://algo.itcharge.cn/Solutions/0200-0299/basic-calculator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0224.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8.md,42.4%,困难,1012 -0225,0200-0299,0225. 用队列实现栈,用队列实现栈,https://leetcode.cn/problems/implement-stack-using-queues/,implement-stack-using-queues,栈、设计、队列,https://algo.itcharge.cn/Solutions/0200-0299/implement-stack-using-queues/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md,66.1%,简单,5563 -0226,0200-0299,0226. 翻转二叉树,翻转二叉树,https://leetcode.cn/problems/invert-binary-tree/,invert-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/invert-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md,79.5%,简单,4246 -0227,0200-0299,0227. 基本计算器 II,基本计算器 II,https://leetcode.cn/problems/basic-calculator-ii/,basic-calculator-ii,栈、数学、字符串,https://algo.itcharge.cn/Solutions/0200-0299/basic-calculator-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md,44.5%,中等,1102 -0228,0200-0299,0228. 汇总区间,汇总区间,https://leetcode.cn/problems/summary-ranges/,summary-ranges,数组,https://algo.itcharge.cn/Solutions/0200-0299/summary-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0228.%20%E6%B1%87%E6%80%BB%E5%8C%BA%E9%97%B4.md,54.6%,简单,1057 -0229,0200-0299,0229. 多数元素 II,多数元素 II,https://leetcode.cn/problems/majority-element-ii/,majority-element-ii,数组、哈希表、计数、排序,https://algo.itcharge.cn/Solutions/0200-0299/majority-element-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0229.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0%20II.md,54.0%,中等,851 -0230,0200-0299,0230. 二叉搜索树中第K小的元素,二叉搜索树中第K小的元素,https://leetcode.cn/problems/kth-smallest-element-in-a-bst/,kth-smallest-element-in-a-bst,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/kth-smallest-element-in-a-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0230.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%AC%ACK%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0.md,76.0%,中等,1820 -0231,0200-0299,0231. 2 的幂,2 的幂,https://leetcode.cn/problems/power-of-two/,power-of-two,位运算、递归、数学,https://algo.itcharge.cn/Solutions/0200-0299/power-of-two/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0231.%202%20%E7%9A%84%E5%B9%82.md,50.1%,简单,2104 -0232,0200-0299,0232. 用栈实现队列,用栈实现队列,https://leetcode.cn/problems/implement-queue-using-stacks/,implement-queue-using-stacks,栈、设计、队列,https://algo.itcharge.cn/Solutions/0200-0299/implement-queue-using-stacks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md,68.3%,简单,2370 -0233,0200-0299,0233. 数字 1 的个数,数字 1 的个数,https://leetcode.cn/problems/number-of-digit-one/,number-of-digit-one,递归、数学、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/number-of-digit-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0233.%20%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md,49.1%,困难,623 -0234,0200-0299,0234. 回文链表,回文链表,https://leetcode.cn/problems/palindrome-linked-list/,palindrome-linked-list,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/0200-0299/palindrome-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md,53.3%,简单,3532 -0235,0200-0299,0235. 二叉搜索树的最近公共祖先,二叉搜索树的最近公共祖先,https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/,lowest-common-ancestor-of-a-binary-search-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0235.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md,68.4%,中等,1870 -0236,0200-0299,0236. 二叉树的最近公共祖先,二叉树的最近公共祖先,https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/,lowest-common-ancestor-of-a-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md,69.7%,中等,2791 -0237,0200-0299,0237. 删除链表中的节点,删除链表中的节点,https://leetcode.cn/problems/delete-node-in-a-linked-list/,delete-node-in-a-linked-list,链表,https://algo.itcharge.cn/Solutions/0200-0299/delete-node-in-a-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0237.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md,86.1%,中等,1748 -0238,0200-0299,0238. 除自身以外数组的乘积,除自身以外数组的乘积,https://leetcode.cn/problems/product-of-array-except-self/,product-of-array-except-self,数组、前缀和,https://algo.itcharge.cn/Solutions/0200-0299/product-of-array-except-self/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0238.%20%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF.md,74.8%,中等,1711 -0239,0200-0299,0239. 滑动窗口最大值,滑动窗口最大值,https://leetcode.cn/problems/sliding-window-maximum/,sliding-window-maximum,队列、数组、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/sliding-window-maximum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md,49.7%,困难,3311 -0240,0200-0299,0240. 搜索二维矩阵 II,搜索二维矩阵 II,https://leetcode.cn/problems/search-a-2d-matrix-ii/,search-a-2d-matrix-ii,数组、二分查找、分治、矩阵,https://algo.itcharge.cn/Solutions/0200-0299/search-a-2d-matrix-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md,52.8%,中等,1754 -0241,0200-0299,0241. 为运算表达式设计优先级,为运算表达式设计优先级,https://leetcode.cn/problems/different-ways-to-add-parentheses/,different-ways-to-add-parentheses,递归、记忆化搜索、数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/different-ways-to-add-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0241.%20%E4%B8%BA%E8%BF%90%E7%AE%97%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%BE%E8%AE%A1%E4%BC%98%E5%85%88%E7%BA%A7.md,75.6%,中等,752 -0242,0200-0299,0242. 有效的字母异位词,有效的字母异位词,https://leetcode.cn/problems/valid-anagram/,valid-anagram,哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0200-0299/valid-anagram/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0242.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md,65.8%,简单,3439 -0243,0200-0299,0243. 最短单词距离,最短单词距离,https://leetcode.cn/problems/shortest-word-distance/,shortest-word-distance,数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/shortest-word-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0243.%20%E6%9C%80%E7%9F%AD%E5%8D%95%E8%AF%8D%E8%B7%9D%E7%A6%BB.md,66.8%,简单,184 -0244,0200-0299,0244. 最短单词距离 II,最短单词距离 II,https://leetcode.cn/problems/shortest-word-distance-ii/,shortest-word-distance-ii,设计、数组、哈希表、双指针、字符串,https://algo.itcharge.cn/Solutions/0200-0299/shortest-word-distance-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0244.%20%E6%9C%80%E7%9F%AD%E5%8D%95%E8%AF%8D%E8%B7%9D%E7%A6%BB%20II.md,59.7%,中等,107 -0245,0200-0299,0245. 最短单词距离 III,最短单词距离 III,https://leetcode.cn/problems/shortest-word-distance-iii/,shortest-word-distance-iii,数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/shortest-word-distance-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0245.%20%E6%9C%80%E7%9F%AD%E5%8D%95%E8%AF%8D%E8%B7%9D%E7%A6%BB%20III.md,57.2%,中等,89 -0246,0200-0299,0246. 中心对称数,中心对称数,https://leetcode.cn/problems/strobogrammatic-number/,strobogrammatic-number,哈希表、双指针、字符串,https://algo.itcharge.cn/Solutions/0200-0299/strobogrammatic-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0246.%20%E4%B8%AD%E5%BF%83%E5%AF%B9%E7%A7%B0%E6%95%B0.md,47.5%,简单,173 -0247,0200-0299,0247. 中心对称数 II,中心对称数 II,https://leetcode.cn/problems/strobogrammatic-number-ii/,strobogrammatic-number-ii,递归、数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/strobogrammatic-number-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0247.%20%E4%B8%AD%E5%BF%83%E5%AF%B9%E7%A7%B0%E6%95%B0%20II.md,54.4%,中等,133 -0248,0200-0299,0248. 中心对称数 III,中心对称数 III,https://leetcode.cn/problems/strobogrammatic-number-iii/,strobogrammatic-number-iii,递归、数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/strobogrammatic-number-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0248.%20%E4%B8%AD%E5%BF%83%E5%AF%B9%E7%A7%B0%E6%95%B0%20III.md,49.1%,困难,83 -0249,0200-0299,0249. 移位字符串分组,移位字符串分组,https://leetcode.cn/problems/group-shifted-strings/,group-shifted-strings,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/group-shifted-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0249.%20%E7%A7%BB%E4%BD%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%86%E7%BB%84.md,64.8%,中等,176 -0250,0200-0299,0250. 统计同值子树,统计同值子树,https://leetcode.cn/problems/count-univalue-subtrees/,count-univalue-subtrees,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/count-univalue-subtrees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0250.%20%E7%BB%9F%E8%AE%A1%E5%90%8C%E5%80%BC%E5%AD%90%E6%A0%91.md,63.5%,中等,157 -0251,0200-0299,0251. 展开二维向量,展开二维向量,https://leetcode.cn/problems/flatten-2d-vector/,flatten-2d-vector,设计、数组、双指针、迭代器,https://algo.itcharge.cn/Solutions/0200-0299/flatten-2d-vector/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0251.%20%E5%B1%95%E5%BC%80%E4%BA%8C%E7%BB%B4%E5%90%91%E9%87%8F.md,54.9%,中等,111 -0252,0200-0299,0252. 会议室,会议室,https://leetcode.cn/problems/meeting-rooms/,meeting-rooms,数组、排序,https://algo.itcharge.cn/Solutions/0200-0299/meeting-rooms/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0252.%20%E4%BC%9A%E8%AE%AE%E5%AE%A4.md,57.8%,简单,240 -0253,0200-0299,0253. 会议室 II,会议室 II,https://leetcode.cn/problems/meeting-rooms-ii/,meeting-rooms-ii,贪心、数组、双指针、前缀和、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/meeting-rooms-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0253.%20%E4%BC%9A%E8%AE%AE%E5%AE%A4%20II.md,52.2%,中等,588 -0254,0200-0299,0254. 因子的组合,因子的组合,https://leetcode.cn/problems/factor-combinations/,factor-combinations,数组、回溯,https://algo.itcharge.cn/Solutions/0200-0299/factor-combinations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0254.%20%E5%9B%A0%E5%AD%90%E7%9A%84%E7%BB%84%E5%90%88.md,57.0%,中等,140 -0255,0200-0299,0255. 验证前序遍历序列二叉搜索树,验证前序遍历序列二叉搜索树,https://leetcode.cn/problems/verify-preorder-sequence-in-binary-search-tree/,verify-preorder-sequence-in-binary-search-tree,栈、树、二叉搜索树、递归、二叉树、单调栈,https://algo.itcharge.cn/Solutions/0200-0299/verify-preorder-sequence-in-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0255.%20%E9%AA%8C%E8%AF%81%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,49.5%,中等,110 -0256,0200-0299,0256. 粉刷房子,粉刷房子,https://leetcode.cn/problems/paint-house/,paint-house,数组、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/paint-house/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0256.%20%E7%B2%89%E5%88%B7%E6%88%BF%E5%AD%90.md,69.8%,中等,311 -0257,0200-0299,0257. 二叉树的所有路径,二叉树的所有路径,https://leetcode.cn/problems/binary-tree-paths/,binary-tree-paths,树、深度优先搜索、字符串、回溯、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/binary-tree-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0257.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84.md,70.7%,简单,1983 -0258,0200-0299,0258. 各位相加,各位相加,https://leetcode.cn/problems/add-digits/,add-digits,数学、数论、模拟,https://algo.itcharge.cn/Solutions/0200-0299/add-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0258.%20%E5%90%84%E4%BD%8D%E7%9B%B8%E5%8A%A0.md,71.0%,简单,1393 -0259,0200-0299,0259. 较小的三数之和,较小的三数之和,https://leetcode.cn/problems/3sum-smaller/,3sum-smaller,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0200-0299/3sum-smaller/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md,55.1%,中等,141 -0260,0200-0299,0260. 只出现一次的数字 III,只出现一次的数字 III,https://leetcode.cn/problems/single-number-iii/,single-number-iii,位运算、数组,https://algo.itcharge.cn/Solutions/0200-0299/single-number-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0260.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20III.md,72.0%,中等,949 -0261,0200-0299,0261. 以图判树,以图判树,https://leetcode.cn/problems/graph-valid-tree/,graph-valid-tree,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0200-0299/graph-valid-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0261.%20%E4%BB%A5%E5%9B%BE%E5%88%A4%E6%A0%91.md,51.1%,中等,235 -0262,0200-0299,0262. 行程和用户,行程和用户,https://leetcode.cn/problems/trips-and-users/,trips-and-users,数据库,https://algo.itcharge.cn/Solutions/0200-0299/trips-and-users/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0262.%20%E8%A1%8C%E7%A8%8B%E5%92%8C%E7%94%A8%E6%88%B7.md,41.6%,困难,629 -0263,0200-0299,0263. 丑数,丑数,https://leetcode.cn/problems/ugly-number/,ugly-number,数学,https://algo.itcharge.cn/Solutions/0200-0299/ugly-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0263.%20%E4%B8%91%E6%95%B0.md,50.6%,简单,1150 -0264,0200-0299,0264. 丑数 II,丑数 II,https://leetcode.cn/problems/ugly-number-ii/,ugly-number-ii,哈希表、数学、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/ugly-number-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0264.%20%E4%B8%91%E6%95%B0%20II.md,58.6%,中等,954 -0265,0200-0299,0265. 粉刷房子 II,粉刷房子 II,https://leetcode.cn/problems/paint-house-ii/,paint-house-ii,数组、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/paint-house-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0265.%20%E7%B2%89%E5%88%B7%E6%88%BF%E5%AD%90%20II.md,63.5%,困难,266 -0266,0200-0299,0266. 回文排列,回文排列,https://leetcode.cn/problems/palindrome-permutation/,palindrome-permutation,位运算、哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/palindrome-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0266.%20%E5%9B%9E%E6%96%87%E6%8E%92%E5%88%97.md,70.1%,简单,180 -0267,0200-0299,0267. 回文排列 II,回文排列 II,https://leetcode.cn/problems/palindrome-permutation-ii/,palindrome-permutation-ii,哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/0200-0299/palindrome-permutation-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0267.%20%E5%9B%9E%E6%96%87%E6%8E%92%E5%88%97%20II.md,47.1%,中等,131 -0268,0200-0299,0268. 丢失的数字,丢失的数字,https://leetcode.cn/problems/missing-number/,missing-number,位运算、数组、哈希表、数学、二分查找、排序,https://algo.itcharge.cn/Solutions/0200-0299/missing-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md,66.3%,简单,2306 -0269,0200-0299,0269. 火星词典,火星词典,https://leetcode.cn/problems/alien-dictionary/,alien-dictionary,深度优先搜索、广度优先搜索、图、拓扑排序、数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/alien-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0269.%20%E7%81%AB%E6%98%9F%E8%AF%8D%E5%85%B8.md,36.6%,困难,180 -0270,0200-0299,0270. 最接近的二叉搜索树值,最接近的二叉搜索树值,https://leetcode.cn/problems/closest-binary-search-tree-value/,closest-binary-search-tree-value,树、深度优先搜索、二叉搜索树、二分查找、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/closest-binary-search-tree-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0270.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%80%BC.md,57.0%,简单,205 -0271,0200-0299,0271. 字符串的编码与解码,字符串的编码与解码,https://leetcode.cn/problems/encode-and-decode-strings/,encode-and-decode-strings,设计、数组、字符串,https://algo.itcharge.cn/Solutions/0200-0299/encode-and-decode-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0271.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E7%BC%96%E7%A0%81%E4%B8%8E%E8%A7%A3%E7%A0%81.md,57.7%,中等,73 -0272,0200-0299,0272. 最接近的二叉搜索树值 II,最接近的二叉搜索树值 II,https://leetcode.cn/problems/closest-binary-search-tree-value-ii/,closest-binary-search-tree-value-ii,栈、树、深度优先搜索、二叉搜索树、双指针、二叉树、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/closest-binary-search-tree-value-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0272.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%80%BC%20II.md,66.0%,困难,128 -0273,0200-0299,0273. 整数转换英文表示,整数转换英文表示,https://leetcode.cn/problems/integer-to-english-words/,integer-to-english-words,递归、数学、字符串,https://algo.itcharge.cn/Solutions/0200-0299/integer-to-english-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0273.%20%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2%E8%8B%B1%E6%96%87%E8%A1%A8%E7%A4%BA.md,36.5%,困难,441 -0274,0200-0299,0274. H 指数,H 指数,https://leetcode.cn/problems/h-index/,h-index,数组、计数排序、排序,https://algo.itcharge.cn/Solutions/0200-0299/h-index/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0274.%20H%20%E6%8C%87%E6%95%B0.md,44.4%,中等,792 -0275,0200-0299,0275. H 指数 II,H 指数 II,https://leetcode.cn/problems/h-index-ii/,h-index-ii,数组、二分查找,https://algo.itcharge.cn/Solutions/0200-0299/h-index-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0275.%20H%20%E6%8C%87%E6%95%B0%20II.md,45.2%,中等,553 -0276,0200-0299,0276. 栅栏涂色,栅栏涂色,https://leetcode.cn/problems/paint-fence/,paint-fence,动态规划,https://algo.itcharge.cn/Solutions/0200-0299/paint-fence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0276.%20%E6%A0%85%E6%A0%8F%E6%B6%82%E8%89%B2.md,53.5%,中等,190 -0277,0200-0299,0277. 搜寻名人,搜寻名人,https://leetcode.cn/problems/find-the-celebrity/,find-the-celebrity,贪心、图、双指针、交互,https://algo.itcharge.cn/Solutions/0200-0299/find-the-celebrity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0277.%20%E6%90%9C%E5%AF%BB%E5%90%8D%E4%BA%BA.md,57.9%,中等,140 -0278,0200-0299,0278. 第一个错误的版本,第一个错误的版本,https://leetcode.cn/problems/first-bad-version/,first-bad-version,二分查找、交互,https://algo.itcharge.cn/Solutions/0200-0299/first-bad-version/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0278.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E7%9A%84%E7%89%88%E6%9C%AC.md,45.3%,简单,2528 -0279,0200-0299,0279. 完全平方数,完全平方数,https://leetcode.cn/problems/perfect-squares/,perfect-squares,广度优先搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/0200-0299/perfect-squares/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md,66.2%,中等,2035 -0280,0200-0299,0280. 摆动排序,摆动排序,https://leetcode.cn/problems/wiggle-sort/,wiggle-sort,贪心、数组、排序,https://algo.itcharge.cn/Solutions/0200-0299/wiggle-sort/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0280.%20%E6%91%86%E5%8A%A8%E6%8E%92%E5%BA%8F.md,68.8%,中等,120 -0281,0200-0299,0281. 锯齿迭代器,锯齿迭代器,https://leetcode.cn/problems/zigzag-iterator/,zigzag-iterator,设计、队列、数组、迭代器,https://algo.itcharge.cn/Solutions/0200-0299/zigzag-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0281.%20%E9%94%AF%E9%BD%BF%E8%BF%AD%E4%BB%A3%E5%99%A8.md,76.8%,中等,128 -0282,0200-0299,0282. 给表达式添加运算符,给表达式添加运算符,https://leetcode.cn/problems/expression-add-operators/,expression-add-operators,数学、字符串、回溯,https://algo.itcharge.cn/Solutions/0200-0299/expression-add-operators/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0282.%20%E7%BB%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B7%BB%E5%8A%A0%E8%BF%90%E7%AE%97%E7%AC%A6.md,46.9%,困难,214 -0283,0200-0299,0283. 移动零,移动零,https://leetcode.cn/problems/move-zeroes/,move-zeroes,数组、双指针,https://algo.itcharge.cn/Solutions/0200-0299/move-zeroes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md,63.8%,简单,6468 -0284,0200-0299,0284. 顶端迭代器,顶端迭代器,https://leetcode.cn/problems/peeking-iterator/,peeking-iterator,设计、数组、迭代器,https://algo.itcharge.cn/Solutions/0200-0299/peeking-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0284.%20%E9%A1%B6%E7%AB%AF%E8%BF%AD%E4%BB%A3%E5%99%A8.md,76.5%,中等,256 -0285,0200-0299,0285. 二叉搜索树中的中序后继,二叉搜索树中的中序后继,https://leetcode.cn/problems/inorder-successor-in-bst/,inorder-successor-in-bst,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/inorder-successor-in-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0285.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%B8%AD%E5%BA%8F%E5%90%8E%E7%BB%A7.md,64.4%,中等,175 -0286,0200-0299,0286. 墙与门,墙与门,https://leetcode.cn/problems/walls-and-gates/,walls-and-gates,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0200-0299/walls-and-gates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0286.%20%E5%A2%99%E4%B8%8E%E9%97%A8.md,54.9%,中等,237 -0287,0200-0299,0287. 寻找重复数,寻找重复数,https://leetcode.cn/problems/find-the-duplicate-number/,find-the-duplicate-number,位运算、数组、双指针、二分查找,https://algo.itcharge.cn/Solutions/0200-0299/find-the-duplicate-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md,64.2%,中等,2002 -0288,0200-0299,0288. 单词的唯一缩写,单词的唯一缩写,https://leetcode.cn/problems/unique-word-abbreviation/,unique-word-abbreviation,设计、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/unique-word-abbreviation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0288.%20%E5%8D%95%E8%AF%8D%E7%9A%84%E5%94%AF%E4%B8%80%E7%BC%A9%E5%86%99.md,49.2%,中等,86 -0289,0200-0299,0289. 生命游戏,生命游戏,https://leetcode.cn/problems/game-of-life/,game-of-life,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0200-0299/game-of-life/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0289.%20%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F.md,75.7%,中等,1128 -0290,0200-0299,0290. 单词规律,单词规律,https://leetcode.cn/problems/word-pattern/,word-pattern,哈希表、字符串,https://algo.itcharge.cn/Solutions/0200-0299/word-pattern/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0290.%20%E5%8D%95%E8%AF%8D%E8%A7%84%E5%BE%8B.md,44.6%,简单,1696 -0291,0200-0299,0291. 单词规律 II,单词规律 II,https://leetcode.cn/problems/word-pattern-ii/,word-pattern-ii,哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/0200-0299/word-pattern-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0291.%20%E5%8D%95%E8%AF%8D%E8%A7%84%E5%BE%8B%20II.md,51.9%,中等,102 -0292,0200-0299,0292. Nim 游戏,Nim 游戏,https://leetcode.cn/problems/nim-game/,nim-game,脑筋急转弯、数学、博弈,https://algo.itcharge.cn/Solutions/0200-0299/nim-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0292.%20Nim%20%E6%B8%B8%E6%88%8F.md,70.4%,简单,1081 -0293,0200-0299,0293. 翻转游戏,翻转游戏,https://leetcode.cn/problems/flip-game/,flip-game,字符串,https://algo.itcharge.cn/Solutions/0200-0299/flip-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0293.%20%E7%BF%BB%E8%BD%AC%E6%B8%B8%E6%88%8F.md,72.7%,简单,125 -0294,0200-0299,0294. 翻转游戏 II,翻转游戏 II,https://leetcode.cn/problems/flip-game-ii/,flip-game-ii,记忆化搜索、数学、动态规划、回溯、博弈,https://algo.itcharge.cn/Solutions/0200-0299/flip-game-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0294.%20%E7%BF%BB%E8%BD%AC%E6%B8%B8%E6%88%8F%20II.md,59.6%,中等,88 -0295,0200-0299,0295. 数据流的中位数,数据流的中位数,https://leetcode.cn/problems/find-median-from-data-stream/,find-median-from-data-stream,设计、双指针、数据流、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0200-0299/find-median-from-data-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0295.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md,53.4%,困难,769 -0296,0200-0299,0296. 最佳的碰头地点,最佳的碰头地点,https://leetcode.cn/problems/best-meeting-point/,best-meeting-point,数组、数学、矩阵、排序,https://algo.itcharge.cn/Solutions/0200-0299/best-meeting-point/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0296.%20%E6%9C%80%E4%BD%B3%E7%9A%84%E7%A2%B0%E5%A4%B4%E5%9C%B0%E7%82%B9.md,61.1%,困难,68 -0297,0200-0299,0297. 二叉树的序列化与反序列化,二叉树的序列化与反序列化,https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/,serialize-and-deserialize-binary-tree,树、深度优先搜索、广度优先搜索、设计、字符串、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/serialize-and-deserialize-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0297.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md,58.9%,困难,1357 -0298,0200-0299,0298. 二叉树最长连续序列,二叉树最长连续序列,https://leetcode.cn/problems/binary-tree-longest-consecutive-sequence/,binary-tree-longest-consecutive-sequence,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0200-0299/binary-tree-longest-consecutive-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0298.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md,59.9%,中等,145 -0299,0200-0299,0299. 猜数字游戏,猜数字游戏,https://leetcode.cn/problems/bulls-and-cows/,bulls-and-cows,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/0200-0299/bulls-and-cows/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0299.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E6%B8%B8%E6%88%8F.md,57.4%,中等,896 -0300,0300-0399,0300. 最长递增子序列,最长递增子序列,https://leetcode.cn/problems/longest-increasing-subsequence/,longest-increasing-subsequence,数组、二分查找、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/longest-increasing-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md,54.8%,中等,4343 -0301,0300-0399,0301. 删除无效的括号,删除无效的括号,https://leetcode.cn/problems/remove-invalid-parentheses/,remove-invalid-parentheses,广度优先搜索、字符串、回溯,https://algo.itcharge.cn/Solutions/0300-0399/remove-invalid-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0301.%20%E5%88%A0%E9%99%A4%E6%97%A0%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md,55.2%,困难,591 -0302,0300-0399,0302. 包含全部黑色像素的最小矩形,包含全部黑色像素的最小矩形,https://leetcode.cn/problems/smallest-rectangle-enclosing-black-pixels/,smallest-rectangle-enclosing-black-pixels,深度优先搜索、广度优先搜索、数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/smallest-rectangle-enclosing-black-pixels/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0302.%20%E5%8C%85%E5%90%AB%E5%85%A8%E9%83%A8%E9%BB%91%E8%89%B2%E5%83%8F%E7%B4%A0%E7%9A%84%E6%9C%80%E5%B0%8F%E7%9F%A9%E5%BD%A2.md,67.5%,困难,65 -0303,0300-0399,0303. 区域和检索 - 数组不可变,区域和检索 - 数组不可变,https://leetcode.cn/problems/range-sum-query-immutable/,range-sum-query-immutable,设计、数组、前缀和,https://algo.itcharge.cn/Solutions/0300-0399/range-sum-query-immutable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md,76.8%,简单,2144 -0304,0300-0399,0304. 二维区域和检索 - 矩阵不可变,二维区域和检索 - 矩阵不可变,https://leetcode.cn/problems/range-sum-query-2d-immutable/,range-sum-query-2d-immutable,设计、数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/0300-0399/range-sum-query-2d-immutable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0304.%20%E4%BA%8C%E7%BB%B4%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E7%9F%A9%E9%98%B5%E4%B8%8D%E5%8F%AF%E5%8F%98.md,61.6%,中等,1342 -0305,0300-0399,0305. 岛屿数量 II,岛屿数量 II,https://leetcode.cn/problems/number-of-islands-ii/,number-of-islands-ii,并查集、数组,https://algo.itcharge.cn/Solutions/0300-0399/number-of-islands-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0305.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F%20II.md,42.2%,困难,127 -0306,0300-0399,0306. 累加数,累加数,https://leetcode.cn/problems/additive-number/,additive-number,字符串、回溯,https://algo.itcharge.cn/Solutions/0300-0399/additive-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0306.%20%E7%B4%AF%E5%8A%A0%E6%95%B0.md,37.8%,中等,687 -0307,0300-0399,0307. 区域和检索 - 数组可修改,区域和检索 - 数组可修改,https://leetcode.cn/problems/range-sum-query-mutable/,range-sum-query-mutable,设计、树状数组、线段树、数组,https://algo.itcharge.cn/Solutions/0300-0399/range-sum-query-mutable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md,52.1%,中等,602 -0308,0300-0399,0308. 二维区域和检索 - 可变,二维区域和检索 - 可变,https://leetcode.cn/problems/range-sum-query-2d-mutable/,range-sum-query-2d-mutable,设计、树状数组、线段树、数组、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/range-sum-query-2d-mutable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0308.%20%E4%BA%8C%E7%BB%B4%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E5%8F%AF%E5%8F%98.md,64.2%,困难,83 -0309,0300-0399,0309. 最佳买卖股票时机含冷冻期,最佳买卖股票时机含冷冻期,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/,best-time-to-buy-and-sell-stock-with-cooldown,数组、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0309.%20%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.md,64.1%,中等,1916 -0310,0300-0399,0310. 最小高度树,最小高度树,https://leetcode.cn/problems/minimum-height-trees/,minimum-height-trees,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/0300-0399/minimum-height-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0310.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md,42.6%,中等,478 -0311,0300-0399,0311. 稀疏矩阵的乘法,稀疏矩阵的乘法,https://leetcode.cn/problems/sparse-matrix-multiplication/,sparse-matrix-multiplication,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/sparse-matrix-multiplication/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0311.%20%E7%A8%80%E7%96%8F%E7%9F%A9%E9%98%B5%E7%9A%84%E4%B9%98%E6%B3%95.md,75.6%,中等,84 -0312,0300-0399,0312. 戳气球,戳气球,https://leetcode.cn/problems/burst-balloons/,burst-balloons,数组、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/burst-balloons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md,69.9%,困难,660 -0313,0300-0399,0313. 超级丑数,超级丑数,https://leetcode.cn/problems/super-ugly-number/,super-ugly-number,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/super-ugly-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0313.%20%E8%B6%85%E7%BA%A7%E4%B8%91%E6%95%B0.md,56.7%,中等,503 -0314,0300-0399,0314. 二叉树的垂直遍历,二叉树的垂直遍历,https://leetcode.cn/problems/binary-tree-vertical-order-traversal/,binary-tree-vertical-order-traversal,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/0300-0399/binary-tree-vertical-order-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0314.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%9E%82%E7%9B%B4%E9%81%8D%E5%8E%86.md,55.6%,中等,172 -0315,0300-0399,0315. 计算右侧小于当前元素的个数,计算右侧小于当前元素的个数,https://leetcode.cn/problems/count-of-smaller-numbers-after-self/,count-of-smaller-numbers-after-self,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/0300-0399/count-of-smaller-numbers-after-self/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md,43.4%,困难,807 -0316,0300-0399,0316. 去除重复字母,去除重复字母,https://leetcode.cn/problems/remove-duplicate-letters/,remove-duplicate-letters,栈、贪心、字符串、单调栈,https://algo.itcharge.cn/Solutions/0300-0399/remove-duplicate-letters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0316.%20%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D.md,48.3%,中等,887 -0317,0300-0399,0317. 离建筑物最近的距离,离建筑物最近的距离,https://leetcode.cn/problems/shortest-distance-from-all-buildings/,shortest-distance-from-all-buildings,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/shortest-distance-from-all-buildings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0317.%20%E7%A6%BB%E5%BB%BA%E7%AD%91%E7%89%A9%E6%9C%80%E8%BF%91%E7%9A%84%E8%B7%9D%E7%A6%BB.md,47.7%,困难,95 -0318,0300-0399,0318. 最大单词长度乘积,最大单词长度乘积,https://leetcode.cn/problems/maximum-product-of-word-lengths/,maximum-product-of-word-lengths,位运算、数组、字符串,https://algo.itcharge.cn/Solutions/0300-0399/maximum-product-of-word-lengths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0318.%20%E6%9C%80%E5%A4%A7%E5%8D%95%E8%AF%8D%E9%95%BF%E5%BA%A6%E4%B9%98%E7%A7%AF.md,72.7%,中等,618 -0319,0300-0399,0319. 灯泡开关,灯泡开关,https://leetcode.cn/problems/bulb-switcher/,bulb-switcher,脑筋急转弯、数学,https://algo.itcharge.cn/Solutions/0300-0399/bulb-switcher/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0319.%20%E7%81%AF%E6%B3%A1%E5%BC%80%E5%85%B3.md,57.7%,中等,536 -0320,0300-0399,0320. 列举单词的全部缩写,列举单词的全部缩写,https://leetcode.cn/problems/generalized-abbreviation/,generalized-abbreviation,位运算、字符串、回溯,https://algo.itcharge.cn/Solutions/0300-0399/generalized-abbreviation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0320.%20%E5%88%97%E4%B8%BE%E5%8D%95%E8%AF%8D%E7%9A%84%E5%85%A8%E9%83%A8%E7%BC%A9%E5%86%99.md,70.1%,中等,82 -0321,0300-0399,0321. 拼接最大数,拼接最大数,https://leetcode.cn/problems/create-maximum-number/,create-maximum-number,栈、贪心、单调栈,https://algo.itcharge.cn/Solutions/0300-0399/create-maximum-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0321.%20%E6%8B%BC%E6%8E%A5%E6%9C%80%E5%A4%A7%E6%95%B0.md,42.0%,困难,300 -0322,0300-0399,0322. 零钱兑换,零钱兑换,https://leetcode.cn/problems/coin-change/,coin-change,广度优先搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/coin-change/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md,46.4%,中等,3607 -0323,0300-0399,0323. 无向图中连通分量的数目,无向图中连通分量的数目,https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/,number-of-connected-components-in-an-undirected-graph,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0300-0399/number-of-connected-components-in-an-undirected-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md,65.6%,中等,338 -0324,0300-0399,0324. 摆动排序 II,摆动排序 II,https://leetcode.cn/problems/wiggle-sort-ii/,wiggle-sort-ii,数组、分治、快速选择、排序,https://algo.itcharge.cn/Solutions/0300-0399/wiggle-sort-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0324.%20%E6%91%86%E5%8A%A8%E6%8E%92%E5%BA%8F%20II.md,40.9%,中等,399 -0325,0300-0399,0325. 和等于 k 的最长子数组长度,和等于 k 的最长子数组长度,https://leetcode.cn/problems/maximum-size-subarray-sum-equals-k/,maximum-size-subarray-sum-equals-k,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/0300-0399/maximum-size-subarray-sum-equals-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0325.%20%E5%92%8C%E7%AD%89%E4%BA%8E%20k%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6.md,51.7%,中等,191 -0326,0300-0399,0326. 3 的幂,3 的幂,https://leetcode.cn/problems/power-of-three/,power-of-three,递归、数学,https://algo.itcharge.cn/Solutions/0300-0399/power-of-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0326.%203%20%E7%9A%84%E5%B9%82.md,50.9%,简单,920 -0327,0300-0399,0327. 区间和的个数,区间和的个数,https://leetcode.cn/problems/count-of-range-sum/,count-of-range-sum,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/0300-0399/count-of-range-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0327.%20%E5%8C%BA%E9%97%B4%E5%92%8C%E7%9A%84%E4%B8%AA%E6%95%B0.md,40.7%,困难,353 -0328,0300-0399,0328. 奇偶链表,奇偶链表,https://leetcode.cn/problems/odd-even-linked-list/,odd-even-linked-list,链表,https://algo.itcharge.cn/Solutions/0300-0399/odd-even-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0328.%20%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md,65.0%,中等,1598 -0329,0300-0399,0329. 矩阵中的最长递增路径,矩阵中的最长递增路径,https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/,longest-increasing-path-in-a-matrix,深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/longest-increasing-path-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md,51.7%,困难,842 -0330,0300-0399,0330. 按要求补齐数组,按要求补齐数组,https://leetcode.cn/problems/patching-array/,patching-array,贪心、数组,https://algo.itcharge.cn/Solutions/0300-0399/patching-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0330.%20%E6%8C%89%E8%A6%81%E6%B1%82%E8%A1%A5%E9%BD%90%E6%95%B0%E7%BB%84.md,52.9%,困难,192 -0331,0300-0399,0331. 验证二叉树的前序序列化,验证二叉树的前序序列化,https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/,verify-preorder-serialization-of-a-binary-tree,栈、树、字符串、二叉树,https://algo.itcharge.cn/Solutions/0300-0399/verify-preorder-serialization-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0331.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E5%BA%8F%E5%88%97%E5%8C%96.md,48.1%,中等,648 -0332,0300-0399,0332. 重新安排行程,重新安排行程,https://leetcode.cn/problems/reconstruct-itinerary/,reconstruct-itinerary,深度优先搜索、图、欧拉回路,https://algo.itcharge.cn/Solutions/0300-0399/reconstruct-itinerary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0332.%20%E9%87%8D%E6%96%B0%E5%AE%89%E6%8E%92%E8%A1%8C%E7%A8%8B.md,47.7%,困难,608 -0333,0300-0399,0333. 最大 BST 子树,最大 BST 子树,https://leetcode.cn/problems/largest-bst-subtree/,largest-bst-subtree,树、深度优先搜索、二叉搜索树、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0300-0399/largest-bst-subtree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0333.%20%E6%9C%80%E5%A4%A7%20BST%20%E5%AD%90%E6%A0%91.md,48.8%,中等,147 -0334,0300-0399,0334. 递增的三元子序列,递增的三元子序列,https://leetcode.cn/problems/increasing-triplet-subsequence/,increasing-triplet-subsequence,贪心、数组,https://algo.itcharge.cn/Solutions/0300-0399/increasing-triplet-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0334.%20%E9%80%92%E5%A2%9E%E7%9A%84%E4%B8%89%E5%85%83%E5%AD%90%E5%BA%8F%E5%88%97.md,43.1%,中等,822 -0335,0300-0399,0335. 路径交叉,路径交叉,https://leetcode.cn/problems/self-crossing/,self-crossing,几何、数组、数学,https://algo.itcharge.cn/Solutions/0300-0399/self-crossing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0335.%20%E8%B7%AF%E5%BE%84%E4%BA%A4%E5%8F%89.md,42.4%,困难,168 -0336,0300-0399,0336. 回文对,回文对,https://leetcode.cn/problems/palindrome-pairs/,palindrome-pairs,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0300-0399/palindrome-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0336.%20%E5%9B%9E%E6%96%87%E5%AF%B9.md,38.2%,困难,242 -0337,0300-0399,0337. 打家劫舍 III,打家劫舍 III,https://leetcode.cn/problems/house-robber-iii/,house-robber-iii,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0300-0399/house-robber-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0337.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20III.md,61.1%,中等,1748 -0338,0300-0399,0338. 比特位计数,比特位计数,https://leetcode.cn/problems/counting-bits/,counting-bits,位运算、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/counting-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md,78.6%,简单,2226 -0339,0300-0399,0339. 嵌套列表权重和,嵌套列表权重和,https://leetcode.cn/problems/nested-list-weight-sum/,nested-list-weight-sum,深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/0300-0399/nested-list-weight-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0339.%20%E5%B5%8C%E5%A5%97%E5%88%97%E8%A1%A8%E6%9D%83%E9%87%8D%E5%92%8C.md,83.2%,中等,82 -0340,0300-0399,0340. 至多包含 K 个不同字符的最长子串,至多包含 K 个不同字符的最长子串,https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/,longest-substring-with-at-most-k-distinct-characters,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0340.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md,51.1%,中等,307 -0341,0300-0399,0341. 扁平化嵌套列表迭代器,扁平化嵌套列表迭代器,https://leetcode.cn/problems/flatten-nested-list-iterator/,flatten-nested-list-iterator,栈、树、深度优先搜索、设计、队列、迭代器,https://algo.itcharge.cn/Solutions/0300-0399/flatten-nested-list-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0341.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%B5%8C%E5%A5%97%E5%88%97%E8%A1%A8%E8%BF%AD%E4%BB%A3%E5%99%A8.md,72.8%,中等,666 -0342,0300-0399,0342. 4的幂,4的幂,https://leetcode.cn/problems/power-of-four/,power-of-four,位运算、递归、数学,https://algo.itcharge.cn/Solutions/0300-0399/power-of-four/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0342.%204%E7%9A%84%E5%B9%82.md,52.9%,简单,1098 -0343,0300-0399,0343. 整数拆分,整数拆分,https://leetcode.cn/problems/integer-break/,integer-break,数学、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/integer-break/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md,62.2%,中等,1923 -0344,0300-0399,0344. 反转字符串,反转字符串,https://leetcode.cn/problems/reverse-string/,reverse-string,双指针、字符串,https://algo.itcharge.cn/Solutions/0300-0399/reverse-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md,79.7%,简单,3706 -0345,0300-0399,0345. 反转字符串中的元音字母,反转字符串中的元音字母,https://leetcode.cn/problems/reverse-vowels-of-a-string/,reverse-vowels-of-a-string,双指针、字符串,https://algo.itcharge.cn/Solutions/0300-0399/reverse-vowels-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0345.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D.md,54.5%,简单,1442 -0346,0300-0399,0346. 数据流中的移动平均值,数据流中的移动平均值,https://leetcode.cn/problems/moving-average-from-data-stream/,moving-average-from-data-stream,设计、队列、数组、数据流,https://algo.itcharge.cn/Solutions/0300-0399/moving-average-from-data-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0346.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%A7%BB%E5%8A%A8%E5%B9%B3%E5%9D%87%E5%80%BC.md,72.2%,简单,207 -0347,0300-0399,0347. 前 K 个高频元素,前 K 个高频元素,https://leetcode.cn/problems/top-k-frequent-elements/,top-k-frequent-elements,数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0300-0399/top-k-frequent-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0347.%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.md,63.5%,中等,2922 -0348,0300-0399,0348. 设计井字棋,设计井字棋,https://leetcode.cn/problems/design-tic-tac-toe/,design-tic-tac-toe,设计、数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/design-tic-tac-toe/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0348.%20%E8%AE%BE%E8%AE%A1%E4%BA%95%E5%AD%97%E6%A3%8B.md,59.3%,中等,133 -0349,0300-0399,0349. 两个数组的交集,两个数组的交集,https://leetcode.cn/problems/intersection-of-two-arrays/,intersection-of-two-arrays,数组、哈希表、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0300-0399/intersection-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md,74.3%,简单,2927 -0350,0300-0399,0350. 两个数组的交集 II,两个数组的交集 II,https://leetcode.cn/problems/intersection-of-two-arrays-ii/,intersection-of-two-arrays-ii,数组、哈希表、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0300-0399/intersection-of-two-arrays-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md,57.0%,简单,2816 -0351,0300-0399,0351. 安卓系统手势解锁,安卓系统手势解锁,https://leetcode.cn/problems/android-unlock-patterns/,android-unlock-patterns,动态规划、回溯,https://algo.itcharge.cn/Solutions/0300-0399/android-unlock-patterns/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md,61.4%,中等,134 -0352,0300-0399,0352. 将数据流变为多个不相交区间,将数据流变为多个不相交区间,https://leetcode.cn/problems/data-stream-as-disjoint-intervals/,data-stream-as-disjoint-intervals,设计、二分查找、有序集合,https://algo.itcharge.cn/Solutions/0300-0399/data-stream-as-disjoint-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0352.%20%E5%B0%86%E6%95%B0%E6%8D%AE%E6%B5%81%E5%8F%98%E4%B8%BA%E5%A4%9A%E4%B8%AA%E4%B8%8D%E7%9B%B8%E4%BA%A4%E5%8C%BA%E9%97%B4.md,67.3%,困难,411 -0353,0300-0399,0353. 贪吃蛇,贪吃蛇,https://leetcode.cn/problems/design-snake-game/,design-snake-game,设计、队列、数组、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/design-snake-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0353.%20%E8%B4%AA%E5%90%83%E8%9B%87.md,43.4%,中等,85 -0354,0300-0399,0354. 俄罗斯套娃信封问题,俄罗斯套娃信封问题,https://leetcode.cn/problems/russian-doll-envelopes/,russian-doll-envelopes,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/0300-0399/russian-doll-envelopes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md,37.6%,困难,832 -0355,0300-0399,0355. 设计推特,设计推特,https://leetcode.cn/problems/design-twitter/,design-twitter,设计、哈希表、链表、堆(优先队列),https://algo.itcharge.cn/Solutions/0300-0399/design-twitter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0355.%20%E8%AE%BE%E8%AE%A1%E6%8E%A8%E7%89%B9.md,41.1%,中等,700 -0356,0300-0399,0356. 直线镜像,直线镜像,https://leetcode.cn/problems/line-reflection/,line-reflection,数组、哈希表、数学,https://algo.itcharge.cn/Solutions/0300-0399/line-reflection/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0356.%20%E7%9B%B4%E7%BA%BF%E9%95%9C%E5%83%8F.md,36.8%,中等,61 -0357,0300-0399,0357. 统计各位数字都不同的数字个数,统计各位数字都不同的数字个数,https://leetcode.cn/problems/count-numbers-with-unique-digits/,count-numbers-with-unique-digits,数学、动态规划、回溯,https://algo.itcharge.cn/Solutions/0300-0399/count-numbers-with-unique-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0357.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E9%83%BD%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97%E4%B8%AA%E6%95%B0.md,60.5%,中等,936 -0358,0300-0399,0358. K 距离间隔重排字符串,K 距离间隔重排字符串,https://leetcode.cn/problems/rearrange-string-k-distance-apart/,rearrange-string-k-distance-apart,贪心、哈希表、字符串、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0300-0399/rearrange-string-k-distance-apart/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0358.%20K%20%E8%B7%9D%E7%A6%BB%E9%97%B4%E9%9A%94%E9%87%8D%E6%8E%92%E5%AD%97%E7%AC%A6%E4%B8%B2.md,36.6%,困难,91 -0359,0300-0399,0359. 日志速率限制器,日志速率限制器,https://leetcode.cn/problems/logger-rate-limiter/,logger-rate-limiter,设计、哈希表,https://algo.itcharge.cn/Solutions/0300-0399/logger-rate-limiter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0359.%20%E6%97%A5%E5%BF%97%E9%80%9F%E7%8E%87%E9%99%90%E5%88%B6%E5%99%A8.md,74.7%,简单,103 -0360,0300-0399,0360. 有序转化数组,有序转化数组,https://leetcode.cn/problems/sort-transformed-array/,sort-transformed-array,数组、数学、双指针、排序,https://algo.itcharge.cn/Solutions/0300-0399/sort-transformed-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0360.%20%E6%9C%89%E5%BA%8F%E8%BD%AC%E5%8C%96%E6%95%B0%E7%BB%84.md,62.3%,中等,90 -0361,0300-0399,0361. 轰炸敌人,轰炸敌人,https://leetcode.cn/problems/bomb-enemy/,bomb-enemy,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0300-0399/bomb-enemy/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0361.%20%E8%BD%B0%E7%82%B8%E6%95%8C%E4%BA%BA.md,59.6%,中等,106 -0362,0300-0399,0362. 敲击计数器,敲击计数器,https://leetcode.cn/problems/design-hit-counter/,design-hit-counter,设计、队列、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/0300-0399/design-hit-counter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0362.%20%E6%95%B2%E5%87%BB%E8%AE%A1%E6%95%B0%E5%99%A8.md,68.8%,中等,124 -0363,0300-0399,0363. 矩形区域不超过 K 的最大数值和,矩形区域不超过 K 的最大数值和,https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/,max-sum-of-rectangle-no-larger-than-k,数组、二分查找、矩阵、有序集合、前缀和,https://algo.itcharge.cn/Solutions/0300-0399/max-sum-of-rectangle-no-larger-than-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0363.%20%E7%9F%A9%E5%BD%A2%E5%8C%BA%E5%9F%9F%E4%B8%8D%E8%B6%85%E8%BF%87%20K%20%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%80%BC%E5%92%8C.md,48.1%,困难,265 -0364,0300-0399,0364. 加权嵌套序列和 II,加权嵌套序列和 II,https://leetcode.cn/problems/nested-list-weight-sum-ii/,nested-list-weight-sum-ii,栈、深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/0300-0399/nested-list-weight-sum-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0364.%20%E5%8A%A0%E6%9D%83%E5%B5%8C%E5%A5%97%E5%BA%8F%E5%88%97%E5%92%8C%20II.md,67.0%,中等,77 -0365,0300-0399,0365. 水壶问题,水壶问题,https://leetcode.cn/problems/water-and-jug-problem/,water-and-jug-problem,深度优先搜索、广度优先搜索、数学,https://algo.itcharge.cn/Solutions/0300-0399/water-and-jug-problem/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0365.%20%E6%B0%B4%E5%A3%B6%E9%97%AE%E9%A2%98.md,39.9%,中等,610 -0366,0300-0399,0366. 寻找二叉树的叶子节点,寻找二叉树的叶子节点,https://leetcode.cn/problems/find-leaves-of-binary-tree/,find-leaves-of-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0300-0399/find-leaves-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0366.%20%E5%AF%BB%E6%89%BE%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9.md,80.1%,中等,216 -0367,0300-0399,0367. 有效的完全平方数,有效的完全平方数,https://leetcode.cn/problems/valid-perfect-square/,valid-perfect-square,数学、二分查找,https://algo.itcharge.cn/Solutions/0300-0399/valid-perfect-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0367.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md,44.8%,简单,1681 -0368,0300-0399,0368. 最大整除子集,最大整除子集,https://leetcode.cn/problems/largest-divisible-subset/,largest-divisible-subset,数组、数学、动态规划、排序,https://algo.itcharge.cn/Solutions/0300-0399/largest-divisible-subset/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0368.%20%E6%9C%80%E5%A4%A7%E6%95%B4%E9%99%A4%E5%AD%90%E9%9B%86.md,46.0%,中等,591 -0369,0300-0399,0369. 给单链表加一,给单链表加一,https://leetcode.cn/problems/plus-one-linked-list/,plus-one-linked-list,链表、数学,https://algo.itcharge.cn/Solutions/0300-0399/plus-one-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0369.%20%E7%BB%99%E5%8D%95%E9%93%BE%E8%A1%A8%E5%8A%A0%E4%B8%80.md,62.9%,中等,230 -0370,0300-0399,0370. 区间加法,区间加法,https://leetcode.cn/problems/range-addition/,range-addition,数组、前缀和,https://algo.itcharge.cn/Solutions/0300-0399/range-addition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0370.%20%E5%8C%BA%E9%97%B4%E5%8A%A0%E6%B3%95.md,76.7%,中等,276 -0371,0300-0399,0371. 两整数之和,两整数之和,https://leetcode.cn/problems/sum-of-two-integers/,sum-of-two-integers,位运算、数学,https://algo.itcharge.cn/Solutions/0300-0399/sum-of-two-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0371.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E4%B9%8B%E5%92%8C.md,61.6%,中等,690 -0372,0300-0399,0372. 超级次方,超级次方,https://leetcode.cn/problems/super-pow/,super-pow,数学、分治,https://algo.itcharge.cn/Solutions/0300-0399/super-pow/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0372.%20%E8%B6%85%E7%BA%A7%E6%AC%A1%E6%96%B9.md,57.2%,中等,509 -0373,0300-0399,0373. 查找和最小的 K 对数字,查找和最小的 K 对数字,https://leetcode.cn/problems/find-k-pairs-with-smallest-sums/,find-k-pairs-with-smallest-sums,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/0300-0399/find-k-pairs-with-smallest-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0373.%20%E6%9F%A5%E6%89%BE%E5%92%8C%E6%9C%80%E5%B0%8F%E7%9A%84%20K%20%E5%AF%B9%E6%95%B0%E5%AD%97.md,40.8%,中等,540 -0374,0300-0399,0374. 猜数字大小,猜数字大小,https://leetcode.cn/problems/guess-number-higher-or-lower/,guess-number-higher-or-lower,二分查找、交互,https://algo.itcharge.cn/Solutions/0300-0399/guess-number-higher-or-lower/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0374.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F.md,52.2%,简单,1024 -0375,0300-0399,0375. 猜数字大小 II,猜数字大小 II,https://leetcode.cn/problems/guess-number-higher-or-lower-ii/,guess-number-higher-or-lower-ii,数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/0300-0399/guess-number-higher-or-lower-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md,62.5%,中等,350 -0376,0300-0399,0376. 摆动序列,摆动序列,https://leetcode.cn/problems/wiggle-subsequence/,wiggle-subsequence,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/wiggle-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0376.%20%E6%91%86%E5%8A%A8%E5%BA%8F%E5%88%97.md,46.9%,中等,1568 -0377,0300-0399,0377. 组合总和 Ⅳ,组合总和 Ⅳ,https://leetcode.cn/problems/combination-sum-iv/,combination-sum-iv,数组、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/combination-sum-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md,52.9%,中等,964 -0378,0300-0399,0378. 有序矩阵中第 K 小的元素,有序矩阵中第 K 小的元素,https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/,kth-smallest-element-in-a-sorted-matrix,数组、二分查找、矩阵、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0378.%20%E6%9C%89%E5%BA%8F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0.md,63.6%,中等,839 -0379,0300-0399,0379. 电话目录管理系统,电话目录管理系统,https://leetcode.cn/problems/design-phone-directory/,design-phone-directory,设计、队列、数组、哈希表、链表,https://algo.itcharge.cn/Solutions/0300-0399/design-phone-directory/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0379.%20%E7%94%B5%E8%AF%9D%E7%9B%AE%E5%BD%95%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.md,61.2%,中等,116 -0380,0300-0399,0380. O(1) 时间插入、删除和获取随机元素,O(1) 时间插入、删除和获取随机元素,https://leetcode.cn/problems/insert-delete-getrandom-o1/,insert-delete-getrandom-o1,设计、数组、哈希表、数学、随机化,https://algo.itcharge.cn/Solutions/0300-0399/insert-delete-getrandom-o1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0380.%20O%281%29%20%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0.md,52.6%,中等,1663 -0381,0300-0399,0381. O(1) 时间插入、删除和获取随机元素 - 允许重复,O(1) 时间插入、删除和获取随机元素 - 允许重复,https://leetcode.cn/problems/insert-delete-getrandom-o1-duplicates-allowed/,insert-delete-getrandom-o1-duplicates-allowed,设计、数组、哈希表、数学、随机化,https://algo.itcharge.cn/Solutions/0300-0399/insert-delete-getrandom-o1-duplicates-allowed/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0381.%20O%281%29%20%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%20-%20%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D.md,42.3%,困难,239 -0382,0300-0399,0382. 链表随机节点,链表随机节点,https://leetcode.cn/problems/linked-list-random-node/,linked-list-random-node,水塘抽样、链表、数学、随机化,https://algo.itcharge.cn/Solutions/0300-0399/linked-list-random-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0382.%20%E9%93%BE%E8%A1%A8%E9%9A%8F%E6%9C%BA%E8%8A%82%E7%82%B9.md,72.8%,中等,454 -0383,0300-0399,0383. 赎金信,赎金信,https://leetcode.cn/problems/ransom-note/,ransom-note,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/0300-0399/ransom-note/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0383.%20%E8%B5%8E%E9%87%91%E4%BF%A1.md,60.6%,简单,3003 -0384,0300-0399,0384. 打乱数组,打乱数组,https://leetcode.cn/problems/shuffle-an-array/,shuffle-an-array,数组、数学、随机化,https://algo.itcharge.cn/Solutions/0300-0399/shuffle-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0384.%20%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84.md,61.6%,中等,601 -0385,0300-0399,0385. 迷你语法分析器,迷你语法分析器,https://leetcode.cn/problems/mini-parser/,mini-parser,栈、深度优先搜索、字符串,https://algo.itcharge.cn/Solutions/0300-0399/mini-parser/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0385.%20%E8%BF%B7%E4%BD%A0%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E5%99%A8.md,54.8%,中等,302 -0386,0300-0399,0386. 字典序排数,字典序排数,https://leetcode.cn/problems/lexicographical-numbers/,lexicographical-numbers,深度优先搜索、字典树,https://algo.itcharge.cn/Solutions/0300-0399/lexicographical-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0386.%20%E5%AD%97%E5%85%B8%E5%BA%8F%E6%8E%92%E6%95%B0.md,74.8%,中等,672 -0387,0300-0399,0387. 字符串中的第一个唯一字符,字符串中的第一个唯一字符,https://leetcode.cn/problems/first-unique-character-in-a-string/,first-unique-character-in-a-string,队列、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/0300-0399/first-unique-character-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0387.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%94%AF%E4%B8%80%E5%AD%97%E7%AC%A6.md,55.9%,简单,2154 -0388,0300-0399,0388. 文件的最长绝对路径,文件的最长绝对路径,https://leetcode.cn/problems/longest-absolute-file-path/,longest-absolute-file-path,栈、深度优先搜索、字符串,https://algo.itcharge.cn/Solutions/0300-0399/longest-absolute-file-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0388.%20%E6%96%87%E4%BB%B6%E7%9A%84%E6%9C%80%E9%95%BF%E7%BB%9D%E5%AF%B9%E8%B7%AF%E5%BE%84.md,63.8%,中等,529 -0389,0300-0399,0389. 找不同,找不同,https://leetcode.cn/problems/find-the-difference/,find-the-difference,位运算、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0300-0399/find-the-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0389.%20%E6%89%BE%E4%B8%8D%E5%90%8C.md,66.7%,简单,1644 -0390,0300-0399,0390. 消除游戏,消除游戏,https://leetcode.cn/problems/elimination-game/,elimination-game,递归、数学,https://algo.itcharge.cn/Solutions/0300-0399/elimination-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0390.%20%E6%B6%88%E9%99%A4%E6%B8%B8%E6%88%8F.md,60.2%,中等,472 -0391,0300-0399,0391. 完美矩形,完美矩形,https://leetcode.cn/problems/perfect-rectangle/,perfect-rectangle,数组、扫描线,https://algo.itcharge.cn/Solutions/0300-0399/perfect-rectangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md,46.1%,困难,321 -0392,0300-0399,0392. 判断子序列,判断子序列,https://leetcode.cn/problems/is-subsequence/,is-subsequence,双指针、字符串、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/is-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0392.%20%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.md,52.4%,简单,2748 -0393,0300-0399,0393. UTF-8 编码验证,UTF-8 编码验证,https://leetcode.cn/problems/utf-8-validation/,utf-8-validation,位运算、数组,https://algo.itcharge.cn/Solutions/0300-0399/utf-8-validation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0393.%20UTF-8%20%E7%BC%96%E7%A0%81%E9%AA%8C%E8%AF%81.md,43.7%,中等,488 -0394,0300-0399,0394. 字符串解码,字符串解码,https://leetcode.cn/problems/decode-string/,decode-string,栈、递归、字符串,https://algo.itcharge.cn/Solutions/0300-0399/decode-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md,56.7%,中等,2240 -0395,0300-0399,0395. 至少有 K 个重复字符的最长子串,至少有 K 个重复字符的最长子串,https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/,longest-substring-with-at-least-k-repeating-characters,哈希表、字符串、分治、滑动窗口,https://algo.itcharge.cn/Solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0395.%20%E8%87%B3%E5%B0%91%E6%9C%89%20K%20%E4%B8%AA%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md,52.5%,中等,661 -0396,0300-0399,0396. 旋转函数,旋转函数,https://leetcode.cn/problems/rotate-function/,rotate-function,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/rotate-function/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0396.%20%E6%97%8B%E8%BD%AC%E5%87%BD%E6%95%B0.md,53.2%,中等,605 -0397,0300-0399,0397. 整数替换,整数替换,https://leetcode.cn/problems/integer-replacement/,integer-replacement,贪心、位运算、记忆化搜索、动态规划,https://algo.itcharge.cn/Solutions/0300-0399/integer-replacement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0397.%20%E6%95%B4%E6%95%B0%E6%9B%BF%E6%8D%A2.md,42.4%,中等,702 -0398,0300-0399,0398. 随机数索引,随机数索引,https://leetcode.cn/problems/random-pick-index/,random-pick-index,水塘抽样、哈希表、数学、随机化,https://algo.itcharge.cn/Solutions/0300-0399/random-pick-index/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0398.%20%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%B4%A2%E5%BC%95.md,69.7%,中等,476 -0399,0300-0399,0399. 除法求值,除法求值,https://leetcode.cn/problems/evaluate-division/,evaluate-division,深度优先搜索、广度优先搜索、并查集、图、数组、最短路,https://algo.itcharge.cn/Solutions/0300-0399/evaluate-division/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0399.%20%E9%99%A4%E6%B3%95%E6%B1%82%E5%80%BC.md,59.1%,中等,892 -0400,0400-0499,0400. 第 N 位数字,第 N 位数字,https://leetcode.cn/problems/nth-digit/,nth-digit,数学、二分查找,https://algo.itcharge.cn/Solutions/0400-0499/nth-digit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md,45.6%,中等,630 -0401,0400-0499,0401. 二进制手表,二进制手表,https://leetcode.cn/problems/binary-watch/,binary-watch,位运算、回溯,https://algo.itcharge.cn/Solutions/0400-0499/binary-watch/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0401.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%89%8B%E8%A1%A8.md,62.6%,简单,884 -0402,0400-0499,0402. 移掉 K 位数字,移掉 K 位数字,https://leetcode.cn/problems/remove-k-digits/,remove-k-digits,栈、贪心、字符串、单调栈,https://algo.itcharge.cn/Solutions/0400-0499/remove-k-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0402.%20%E7%A7%BB%E6%8E%89%20K%20%E4%BD%8D%E6%95%B0%E5%AD%97.md,31.7%,中等,952 -0403,0400-0499,0403. 青蛙过河,青蛙过河,https://leetcode.cn/problems/frog-jump/,frog-jump,数组、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/frog-jump/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md,46.0%,困难,575 -0404,0400-0499,0404. 左叶子之和,左叶子之和,https://leetcode.cn/problems/sum-of-left-leaves/,sum-of-left-leaves,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0400-0499/sum-of-left-leaves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0404.%20%E5%B7%A6%E5%8F%B6%E5%AD%90%E4%B9%8B%E5%92%8C.md,62.4%,简单,2080 -0405,0400-0499,0405. 数字转换为十六进制数,数字转换为十六进制数,https://leetcode.cn/problems/convert-a-number-to-hexadecimal/,convert-a-number-to-hexadecimal,位运算、数学,https://algo.itcharge.cn/Solutions/0400-0499/convert-a-number-to-hexadecimal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0405.%20%E6%95%B0%E5%AD%97%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E6%95%B0.md,54.7%,简单,635 -0406,0400-0499,0406. 根据身高重建队列,根据身高重建队列,https://leetcode.cn/problems/queue-reconstruction-by-height/,queue-reconstruction-by-height,贪心、树状数组、线段树、数组、排序,https://algo.itcharge.cn/Solutions/0400-0499/queue-reconstruction-by-height/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0406.%20%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.md,76.3%,中等,1441 -0407,0400-0499,0407. 接雨水 II,接雨水 II,https://leetcode.cn/problems/trapping-rain-water-ii/,trapping-rain-water-ii,广度优先搜索、数组、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/0400-0499/trapping-rain-water-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0407.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4%20II.md,57.6%,困难,307 -0408,0400-0499,0408. 有效单词缩写,有效单词缩写,https://leetcode.cn/problems/valid-word-abbreviation/,valid-word-abbreviation,双指针、字符串,https://algo.itcharge.cn/Solutions/0400-0499/valid-word-abbreviation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0408.%20%E6%9C%89%E6%95%88%E5%8D%95%E8%AF%8D%E7%BC%A9%E5%86%99.md,35.4%,简单,124 -0409,0400-0499,0409. 最长回文串,最长回文串,https://leetcode.cn/problems/longest-palindrome/,longest-palindrome,贪心、哈希表、字符串,https://algo.itcharge.cn/Solutions/0400-0499/longest-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0409.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E4%B8%B2.md,55.8%,简单,2058 -0410,0400-0499,0410. 分割数组的最大值,分割数组的最大值,https://leetcode.cn/problems/split-array-largest-sum/,split-array-largest-sum,贪心、数组、二分查找、动态规划、前缀和,https://algo.itcharge.cn/Solutions/0400-0499/split-array-largest-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,59.2%,困难,537 -0411,0400-0499,0411. 最短独占单词缩写,最短独占单词缩写,https://leetcode.cn/problems/minimum-unique-word-abbreviation/,minimum-unique-word-abbreviation,位运算、字符串、回溯,https://algo.itcharge.cn/Solutions/0400-0499/minimum-unique-word-abbreviation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0411.%20%E6%9C%80%E7%9F%AD%E7%8B%AC%E5%8D%A0%E5%8D%95%E8%AF%8D%E7%BC%A9%E5%86%99.md,49.6%,困难,35 -0412,0400-0499,0412. Fizz Buzz,Fizz Buzz,https://leetcode.cn/problems/fizz-buzz/,fizz-buzz,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0400-0499/fizz-buzz/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0412.%20Fizz%20Buzz.md,68.9%,简单,1255 -0413,0400-0499,0413. 等差数列划分,等差数列划分,https://leetcode.cn/problems/arithmetic-slices/,arithmetic-slices,数组、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/arithmetic-slices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0413.%20%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97%E5%88%92%E5%88%86.md,69.5%,中等,1476 -0414,0400-0499,0414. 第三大的数,第三大的数,https://leetcode.cn/problems/third-maximum-number/,third-maximum-number,数组、排序,https://algo.itcharge.cn/Solutions/0400-0499/third-maximum-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0414.%20%E7%AC%AC%E4%B8%89%E5%A4%A7%E7%9A%84%E6%95%B0.md,39.7%,简单,1688 -0415,0400-0499,0415. 字符串相加,字符串相加,https://leetcode.cn/problems/add-strings/,add-strings,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0400-0499/add-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md,54.6%,简单,1725 -0416,0400-0499,0416. 分割等和子集,分割等和子集,https://leetcode.cn/problems/partition-equal-subset-sum/,partition-equal-subset-sum,数组、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/partition-equal-subset-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0416.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md,52.2%,中等,2089 -0417,0400-0499,0417. 太平洋大西洋水流问题,太平洋大西洋水流问题,https://leetcode.cn/problems/pacific-atlantic-water-flow/,pacific-atlantic-water-flow,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0400-0499/pacific-atlantic-water-flow/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0417.%20%E5%A4%AA%E5%B9%B3%E6%B4%8B%E5%A4%A7%E8%A5%BF%E6%B4%8B%E6%B0%B4%E6%B5%81%E9%97%AE%E9%A2%98.md,56.2%,中等,845 -0418,0400-0499,0418. 屏幕可显示句子的数量,屏幕可显示句子的数量,https://leetcode.cn/problems/sentence-screen-fitting/,sentence-screen-fitting,字符串、动态规划、模拟,https://algo.itcharge.cn/Solutions/0400-0499/sentence-screen-fitting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0418.%20%E5%B1%8F%E5%B9%95%E5%8F%AF%E6%98%BE%E7%A4%BA%E5%8F%A5%E5%AD%90%E7%9A%84%E6%95%B0%E9%87%8F.md,39.1%,中等,59 -0419,0400-0499,0419. 甲板上的战舰,甲板上的战舰,https://leetcode.cn/problems/battleships-in-a-board/,battleships-in-a-board,深度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0400-0499/battleships-in-a-board/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0419.%20%E7%94%B2%E6%9D%BF%E4%B8%8A%E7%9A%84%E6%88%98%E8%88%B0.md,78.0%,中等,563 -0420,0400-0499,0420. 强密码检验器,强密码检验器,https://leetcode.cn/problems/strong-password-checker/,strong-password-checker,贪心、字符串、堆(优先队列),https://algo.itcharge.cn/Solutions/0400-0499/strong-password-checker/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0420.%20%E5%BC%BA%E5%AF%86%E7%A0%81%E6%A3%80%E9%AA%8C%E5%99%A8.md,39.1%,困难,163 -0421,0400-0499,0421. 数组中两个数的最大异或值,数组中两个数的最大异或值,https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/,maximum-xor-of-two-numbers-in-an-array,位运算、字典树、数组、哈希表,https://algo.itcharge.cn/Solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0421.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md,61.1%,中等,418 -0422,0400-0499,0422. 有效的单词方块,有效的单词方块,https://leetcode.cn/problems/valid-word-square/,valid-word-square,数组、矩阵,https://algo.itcharge.cn/Solutions/0400-0499/valid-word-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0422.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%8D%95%E8%AF%8D%E6%96%B9%E5%9D%97.md,43.4%,简单,96 -0423,0400-0499,0423. 从英文中重建数字,从英文中重建数字,https://leetcode.cn/problems/reconstruct-original-digits-from-english/,reconstruct-original-digits-from-english,哈希表、数学、字符串,https://algo.itcharge.cn/Solutions/0400-0499/reconstruct-original-digits-from-english/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0423.%20%E4%BB%8E%E8%8B%B1%E6%96%87%E4%B8%AD%E9%87%8D%E5%BB%BA%E6%95%B0%E5%AD%97.md,60.8%,中等,456 -0424,0400-0499,0424. 替换后的最长重复字符,替换后的最长重复字符,https://leetcode.cn/problems/longest-repeating-character-replacement/,longest-repeating-character-replacement,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0400-0499/longest-repeating-character-replacement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0424.%20%E6%9B%BF%E6%8D%A2%E5%90%8E%E7%9A%84%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6.md,54.6%,中等,746 -0425,0400-0499,0425. 单词方块,单词方块,https://leetcode.cn/problems/word-squares/,word-squares,字典树、数组、字符串、回溯,https://algo.itcharge.cn/Solutions/0400-0499/word-squares/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0425.%20%E5%8D%95%E8%AF%8D%E6%96%B9%E5%9D%97.md,62.8%,困难,48 -0426,0400-0499,0426. 将二叉搜索树转化为排序的双向链表,将二叉搜索树转化为排序的双向链表,https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/,convert-binary-search-tree-to-sorted-doubly-linked-list,栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表,https://algo.itcharge.cn/Solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0426.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%8E%92%E5%BA%8F%E7%9A%84%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md,68.1%,中等,192 -0427,0400-0499,0427. 建立四叉树,建立四叉树,https://leetcode.cn/problems/construct-quad-tree/,construct-quad-tree,树、数组、分治、矩阵,https://algo.itcharge.cn/Solutions/0400-0499/construct-quad-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0427.%20%E5%BB%BA%E7%AB%8B%E5%9B%9B%E5%8F%89%E6%A0%91.md,71.2%,中等,337 -0428,0400-0499,0428. 序列化和反序列化 N 叉树,序列化和反序列化 N 叉树,https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/,serialize-and-deserialize-n-ary-tree,树、深度优先搜索、广度优先搜索、字符串,https://algo.itcharge.cn/Solutions/0400-0499/serialize-and-deserialize-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0428.%20%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20N%20%E5%8F%89%E6%A0%91.md,68.9%,困难,83 -0429,0400-0499,0429. N 叉树的层序遍历,N 叉树的层序遍历,https://leetcode.cn/problems/n-ary-tree-level-order-traversal/,n-ary-tree-level-order-traversal,树、广度优先搜索,https://algo.itcharge.cn/Solutions/0400-0499/n-ary-tree-level-order-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0429.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md,72.9%,中等,1174 -0430,0400-0499,0430. 扁平化多级双向链表,扁平化多级双向链表,https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/,flatten-a-multilevel-doubly-linked-list,深度优先搜索、链表、双向链表,https://algo.itcharge.cn/Solutions/0400-0499/flatten-a-multilevel-doubly-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0430.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md,59.5%,中等,893 -0431,0400-0499,0431. 将 N 叉树编码为二叉树,将 N 叉树编码为二叉树,https://leetcode.cn/problems/encode-n-ary-tree-to-binary-tree/,encode-n-ary-tree-to-binary-tree,树、深度优先搜索、广度优先搜索、设计、二叉树,https://algo.itcharge.cn/Solutions/0400-0499/encode-n-ary-tree-to-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0431.%20%E5%B0%86%20N%20%E5%8F%89%E6%A0%91%E7%BC%96%E7%A0%81%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md,75.1%,困难,47 -0432,0400-0499,0432. 全 O(1) 的数据结构,全 O(1) 的数据结构,https://leetcode.cn/problems/all-oone-data-structure/,all-oone-data-structure,设计、哈希表、链表、双向链表,https://algo.itcharge.cn/Solutions/0400-0499/all-oone-data-structure/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0432.%20%E5%85%A8%20O%281%29%20%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md,46.9%,困难,308 -0433,0400-0499,0433. 最小基因变化,最小基因变化,https://leetcode.cn/problems/minimum-genetic-mutation/,minimum-genetic-mutation,广度优先搜索、哈希表、字符串,https://algo.itcharge.cn/Solutions/0400-0499/minimum-genetic-mutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0433.%20%E6%9C%80%E5%B0%8F%E5%9F%BA%E5%9B%A0%E5%8F%98%E5%8C%96.md,54.8%,中等,739 -0434,0400-0499,0434. 字符串中的单词数,字符串中的单词数,https://leetcode.cn/problems/number-of-segments-in-a-string/,number-of-segments-in-a-string,字符串,https://algo.itcharge.cn/Solutions/0400-0499/number-of-segments-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0434.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%E6%95%B0.md,38.6%,简单,1151 -0435,0400-0499,0435. 无重叠区间,无重叠区间,https://leetcode.cn/problems/non-overlapping-intervals/,non-overlapping-intervals,贪心、数组、动态规划、排序,https://algo.itcharge.cn/Solutions/0400-0499/non-overlapping-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0435.%20%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.md,51.2%,中等,1644 -0436,0400-0499,0436. 寻找右区间,寻找右区间,https://leetcode.cn/problems/find-right-interval/,find-right-interval,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/0400-0499/find-right-interval/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0436.%20%E5%AF%BB%E6%89%BE%E5%8F%B3%E5%8C%BA%E9%97%B4.md,56.8%,中等,574 -0437,0400-0499,0437. 路径总和 III,路径总和 III,https://leetcode.cn/problems/path-sum-iii/,path-sum-iii,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0400-0499/path-sum-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0437.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20III.md,50.2%,中等,1526 -0438,0400-0499,0438. 找到字符串中所有字母异位词,找到字符串中所有字母异位词,https://leetcode.cn/problems/find-all-anagrams-in-a-string/,find-all-anagrams-in-a-string,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0400-0499/find-all-anagrams-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md,54.7%,中等,2161 -0439,0400-0499,0439. 三元表达式解析器,三元表达式解析器,https://leetcode.cn/problems/ternary-expression-parser/,ternary-expression-parser,栈、递归、字符串,https://algo.itcharge.cn/Solutions/0400-0499/ternary-expression-parser/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0439.%20%E4%B8%89%E5%85%83%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%A7%A3%E6%9E%90%E5%99%A8.md,60.6%,中等,104 -0440,0400-0499,0440. 字典序的第K小数字,字典序的第K小数字,https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/,k-th-smallest-in-lexicographical-order,字典树,https://algo.itcharge.cn/Solutions/0400-0499/k-th-smallest-in-lexicographical-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0440.%20%E5%AD%97%E5%85%B8%E5%BA%8F%E7%9A%84%E7%AC%ACK%E5%B0%8F%E6%95%B0%E5%AD%97.md,42.6%,困难,308 -0441,0400-0499,0441. 排列硬币,排列硬币,https://leetcode.cn/problems/arranging-coins/,arranging-coins,数学、二分查找,https://algo.itcharge.cn/Solutions/0400-0499/arranging-coins/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0441.%20%E6%8E%92%E5%88%97%E7%A1%AC%E5%B8%81.md,45.2%,简单,1122 -0442,0400-0499,0442. 数组中重复的数据,数组中重复的数据,https://leetcode.cn/problems/find-all-duplicates-in-an-array/,find-all-duplicates-in-an-array,数组、哈希表,https://algo.itcharge.cn/Solutions/0400-0499/find-all-duplicates-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0442.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E6%8D%AE.md,75.1%,中等,1021 -0443,0400-0499,0443. 压缩字符串,压缩字符串,https://leetcode.cn/problems/string-compression/,string-compression,双指针、字符串,https://algo.itcharge.cn/Solutions/0400-0499/string-compression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0443.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md,47.9%,中等,898 -0444,0400-0499,0444. 序列重建,序列重建,https://leetcode.cn/problems/sequence-reconstruction/,sequence-reconstruction,图、拓扑排序、数组,https://algo.itcharge.cn/Solutions/0400-0499/sequence-reconstruction/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0444.%20%E5%BA%8F%E5%88%97%E9%87%8D%E5%BB%BA.md,31.4%,中等,86 -0445,0400-0499,0445. 两数相加 II,两数相加 II,https://leetcode.cn/problems/add-two-numbers-ii/,add-two-numbers-ii,栈、链表、数学,https://algo.itcharge.cn/Solutions/0400-0499/add-two-numbers-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0445.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md,60.2%,中等,1299 -0446,0400-0499,0446. 等差数列划分 II - 子序列,等差数列划分 II - 子序列,https://leetcode.cn/problems/arithmetic-slices-ii-subsequence/,arithmetic-slices-ii-subsequence,数组、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/arithmetic-slices-ii-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0446.%20%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97%E5%88%92%E5%88%86%20II%20-%20%E5%AD%90%E5%BA%8F%E5%88%97.md,54.7%,困难,206 -0447,0400-0499,0447. 回旋镖的数量,回旋镖的数量,https://leetcode.cn/problems/number-of-boomerangs/,number-of-boomerangs,数组、哈希表、数学,https://algo.itcharge.cn/Solutions/0400-0499/number-of-boomerangs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0447.%20%E5%9B%9E%E6%97%8B%E9%95%96%E7%9A%84%E6%95%B0%E9%87%8F.md,66.7%,中等,434 -0448,0400-0499,0448. 找到所有数组中消失的数字,找到所有数组中消失的数字,https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/,find-all-numbers-disappeared-in-an-array,数组、哈希表,https://algo.itcharge.cn/Solutions/0400-0499/find-all-numbers-disappeared-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0448.%20%E6%89%BE%E5%88%B0%E6%89%80%E6%9C%89%E6%95%B0%E7%BB%84%E4%B8%AD%E6%B6%88%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md,65.9%,简单,1940 -0449,0400-0499,0449. 序列化和反序列化二叉搜索树,序列化和反序列化二叉搜索树,https://leetcode.cn/problems/serialize-and-deserialize-bst/,serialize-and-deserialize-bst,树、深度优先搜索、广度优先搜索、设计、二叉搜索树、字符串、二叉树,https://algo.itcharge.cn/Solutions/0400-0499/serialize-and-deserialize-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0449.%20%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,60.5%,中等,433 -0450,0400-0499,0450. 删除二叉搜索树中的节点,删除二叉搜索树中的节点,https://leetcode.cn/problems/delete-node-in-a-bst/,delete-node-in-a-bst,树、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0400-0499/delete-node-in-a-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0450.%20%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md,52.4%,中等,1968 -0451,0400-0499,0451. 根据字符出现频率排序,根据字符出现频率排序,https://leetcode.cn/problems/sort-characters-by-frequency/,sort-characters-by-frequency,哈希表、字符串、桶排序、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0400-0499/sort-characters-by-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md,72.0%,中等,1339 -0452,0400-0499,0452. 用最少数量的箭引爆气球,用最少数量的箭引爆气球,https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/,minimum-number-of-arrows-to-burst-balloons,贪心、数组、排序,https://algo.itcharge.cn/Solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0452.%20%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88%86%E6%B0%94%E7%90%83.md,50.7%,中等,1523 -0453,0400-0499,0453. 最小操作次数使数组元素相等,最小操作次数使数组元素相等,https://leetcode.cn/problems/minimum-moves-to-equal-array-elements/,minimum-moves-to-equal-array-elements,数组、数学,https://algo.itcharge.cn/Solutions/0400-0499/minimum-moves-to-equal-array-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0453.%20%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0%E4%BD%BF%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89.md,61.4%,中等,865 -0454,0400-0499,0454. 四数相加 II,四数相加 II,https://leetcode.cn/problems/4sum-ii/,4sum-ii,数组、哈希表,https://algo.itcharge.cn/Solutions/0400-0499/4sum-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0454.%20%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md,64.2%,中等,1203 -0455,0400-0499,0455. 分发饼干,分发饼干,https://leetcode.cn/problems/assign-cookies/,assign-cookies,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/0400-0499/assign-cookies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0455.%20%E5%88%86%E5%8F%91%E9%A5%BC%E5%B9%B2.md,56.4%,简单,2305 -0456,0400-0499,0456. 132 模式,132 模式,https://leetcode.cn/problems/132-pattern/,132-pattern,栈、数组、二分查找、有序集合、单调栈,https://algo.itcharge.cn/Solutions/0400-0499/132-pattern/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0456.%20132%20%E6%A8%A1%E5%BC%8F.md,36.4%,中等,614 -0457,0400-0499,0457. 环形数组是否存在循环,环形数组是否存在循环,https://leetcode.cn/problems/circular-array-loop/,circular-array-loop,数组、哈希表、双指针,https://algo.itcharge.cn/Solutions/0400-0499/circular-array-loop/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0457.%20%E7%8E%AF%E5%BD%A2%E6%95%B0%E7%BB%84%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E5%BE%AA%E7%8E%AF.md,43.4%,中等,374 -0458,0400-0499,0458. 可怜的小猪,可怜的小猪,https://leetcode.cn/problems/poor-pigs/,poor-pigs,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/0400-0499/poor-pigs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0458.%20%E5%8F%AF%E6%80%9C%E7%9A%84%E5%B0%8F%E7%8C%AA.md,67.7%,困难,244 -0459,0400-0499,0459. 重复的子字符串,重复的子字符串,https://leetcode.cn/problems/repeated-substring-pattern/,repeated-substring-pattern,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0400-0499/repeated-substring-pattern/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0459.%20%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,51.2%,简单,1307 -0460,0400-0499,0460. LFU 缓存,LFU 缓存,https://leetcode.cn/problems/lfu-cache/,lfu-cache,设计、哈希表、链表、双向链表,https://algo.itcharge.cn/Solutions/0400-0499/lfu-cache/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0460.%20LFU%20%E7%BC%93%E5%AD%98.md,44.8%,困难,646 -0461,0400-0499,0461. 汉明距离,汉明距离,https://leetcode.cn/problems/hamming-distance/,hamming-distance,位运算,https://algo.itcharge.cn/Solutions/0400-0499/hamming-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0461.%20%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB.md,81.9%,简单,2077 -0462,0400-0499,0462. 最小操作次数使数组元素相等 II,最小操作次数使数组元素相等 II,https://leetcode.cn/problems/minimum-moves-to-equal-array-elements-ii/,minimum-moves-to-equal-array-elements-ii,数组、数学、排序,https://algo.itcharge.cn/Solutions/0400-0499/minimum-moves-to-equal-array-elements-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0462.%20%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0%E4%BD%BF%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%20II.md,62.4%,中等,526 -0463,0400-0499,0463. 岛屿的周长,岛屿的周长,https://leetcode.cn/problems/island-perimeter/,island-perimeter,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0400-0499/island-perimeter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0463.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E5%91%A8%E9%95%BF.md,70.0%,简单,1436 -0464,0400-0499,0464. 我能赢吗,我能赢吗,https://leetcode.cn/problems/can-i-win/,can-i-win,位运算、记忆化搜索、数学、动态规划、状态压缩、博弈,https://algo.itcharge.cn/Solutions/0400-0499/can-i-win/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0464.%20%E6%88%91%E8%83%BD%E8%B5%A2%E5%90%97.md,41.2%,中等,289 -0465,0400-0499,0465. 最优账单平衡,最优账单平衡,https://leetcode.cn/problems/optimal-account-balancing/,optimal-account-balancing,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0400-0499/optimal-account-balancing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0465.%20%E6%9C%80%E4%BC%98%E8%B4%A6%E5%8D%95%E5%B9%B3%E8%A1%A1.md,54.5%,困难,42 -0466,0400-0499,0466. 统计重复个数,统计重复个数,https://leetcode.cn/problems/count-the-repetitions/,count-the-repetitions,字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/count-the-repetitions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0466.%20%E7%BB%9F%E8%AE%A1%E9%87%8D%E5%A4%8D%E4%B8%AA%E6%95%B0.md,37.6%,困难,204 -0467,0400-0499,0467. 环绕字符串中唯一的子字符串,环绕字符串中唯一的子字符串,https://leetcode.cn/problems/unique-substrings-in-wraparound-string/,unique-substrings-in-wraparound-string,字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/unique-substrings-in-wraparound-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,51.7%,中等,371 -0468,0400-0499,0468. 验证IP地址,验证IP地址,https://leetcode.cn/problems/validate-ip-address/,validate-ip-address,字符串,https://algo.itcharge.cn/Solutions/0400-0499/validate-ip-address/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0468.%20%E9%AA%8C%E8%AF%81IP%E5%9C%B0%E5%9D%80.md,28.1%,中等,716 -0469,0400-0499,0469. 凸多边形,凸多边形,https://leetcode.cn/problems/convex-polygon/,convex-polygon,几何、数学,https://algo.itcharge.cn/Solutions/0400-0499/convex-polygon/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0469.%20%E5%87%B8%E5%A4%9A%E8%BE%B9%E5%BD%A2.md,42.9%,中等,36 -0470,0400-0499,0470. 用 Rand7() 实现 Rand10(),用 Rand7() 实现 Rand10(),https://leetcode.cn/problems/implement-rand10-using-rand7/,implement-rand10-using-rand7,数学、拒绝采样、概率与统计、随机化,https://algo.itcharge.cn/Solutions/0400-0499/implement-rand10-using-rand7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0470.%20%E7%94%A8%20Rand7%28%29%20%E5%AE%9E%E7%8E%B0%20Rand10%28%29.md,55.2%,中等,465 -0471,0400-0499,0471. 编码最短长度的字符串,编码最短长度的字符串,https://leetcode.cn/problems/encode-string-with-shortest-length/,encode-string-with-shortest-length,字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/encode-string-with-shortest-length/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0471.%20%E7%BC%96%E7%A0%81%E6%9C%80%E7%9F%AD%E9%95%BF%E5%BA%A6%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,59.6%,困难,43 -0472,0400-0499,0472. 连接词,连接词,https://leetcode.cn/problems/concatenated-words/,concatenated-words,深度优先搜索、字典树、数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/concatenated-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0472.%20%E8%BF%9E%E6%8E%A5%E8%AF%8D.md,52.0%,困难,266 -0473,0400-0499,0473. 火柴拼正方形,火柴拼正方形,https://leetcode.cn/problems/matchsticks-to-square/,matchsticks-to-square,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0400-0499/matchsticks-to-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0473.%20%E7%81%AB%E6%9F%B4%E6%8B%BC%E6%AD%A3%E6%96%B9%E5%BD%A2.md,46.7%,中等,531 -0474,0400-0499,0474. 一和零,一和零,https://leetcode.cn/problems/ones-and-zeroes/,ones-and-zeroes,数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/ones-and-zeroes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0474.%20%E4%B8%80%E5%92%8C%E9%9B%B6.md,65.2%,中等,1075 -0475,0400-0499,0475. 供暖器,供暖器,https://leetcode.cn/problems/heaters/,heaters,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0400-0499/heaters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0475.%20%E4%BE%9B%E6%9A%96%E5%99%A8.md,41.0%,中等,706 -0476,0400-0499,0476. 数字的补数,数字的补数,https://leetcode.cn/problems/number-complement/,number-complement,位运算,https://algo.itcharge.cn/Solutions/0400-0499/number-complement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0476.%20%E6%95%B0%E5%AD%97%E7%9A%84%E8%A1%A5%E6%95%B0.md,69.6%,简单,1094 -0477,0400-0499,0477. 汉明距离总和,汉明距离总和,https://leetcode.cn/problems/total-hamming-distance/,total-hamming-distance,位运算、数组、数学,https://algo.itcharge.cn/Solutions/0400-0499/total-hamming-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0477.%20%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB%E6%80%BB%E5%92%8C.md,60.6%,中等,388 -0478,0400-0499,0478. 在圆内随机生成点,在圆内随机生成点,https://leetcode.cn/problems/generate-random-point-in-a-circle/,generate-random-point-in-a-circle,几何、数学、拒绝采样、随机化,https://algo.itcharge.cn/Solutions/0400-0499/generate-random-point-in-a-circle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0478.%20%E5%9C%A8%E5%9C%86%E5%86%85%E9%9A%8F%E6%9C%BA%E7%94%9F%E6%88%90%E7%82%B9.md,48.3%,中等,188 -0479,0400-0499,0479. 最大回文数乘积,最大回文数乘积,https://leetcode.cn/problems/largest-palindrome-product/,largest-palindrome-product,数学,https://algo.itcharge.cn/Solutions/0400-0499/largest-palindrome-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0479.%20%E6%9C%80%E5%A4%A7%E5%9B%9E%E6%96%87%E6%95%B0%E4%B9%98%E7%A7%AF.md,62.5%,困难,174 -0480,0400-0499,0480. 滑动窗口中位数,滑动窗口中位数,https://leetcode.cn/problems/sliding-window-median/,sliding-window-median,数组、哈希表、滑动窗口、堆(优先队列),https://algo.itcharge.cn/Solutions/0400-0499/sliding-window-median/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md,44.1%,困难,520 -0481,0400-0499,0481. 神奇字符串,神奇字符串,https://leetcode.cn/problems/magical-string/,magical-string,双指针、字符串,https://algo.itcharge.cn/Solutions/0400-0499/magical-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0481.%20%E7%A5%9E%E5%A5%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md,64.2%,中等,470 -0482,0400-0499,0482. 密钥格式化,密钥格式化,https://leetcode.cn/problems/license-key-formatting/,license-key-formatting,字符串,https://algo.itcharge.cn/Solutions/0400-0499/license-key-formatting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0482.%20%E5%AF%86%E9%92%A5%E6%A0%BC%E5%BC%8F%E5%8C%96.md,46.7%,简单,596 -0483,0400-0499,0483. 最小好进制,最小好进制,https://leetcode.cn/problems/smallest-good-base/,smallest-good-base,数学、二分查找,https://algo.itcharge.cn/Solutions/0400-0499/smallest-good-base/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0483.%20%E6%9C%80%E5%B0%8F%E5%A5%BD%E8%BF%9B%E5%88%B6.md,59.1%,困难,147 -0484,0400-0499,0484. 寻找排列,寻找排列,https://leetcode.cn/problems/find-permutation/,find-permutation,栈、贪心、数组、字符串,https://algo.itcharge.cn/Solutions/0400-0499/find-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0484.%20%E5%AF%BB%E6%89%BE%E6%8E%92%E5%88%97.md,59.7%,中等,58 -0485,0400-0499,0485. 最大连续 1 的个数,最大连续 1 的个数,https://leetcode.cn/problems/max-consecutive-ones/,max-consecutive-ones,数组,https://algo.itcharge.cn/Solutions/0400-0499/max-consecutive-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md,61.1%,简单,1615 -0486,0400-0499,0486. 预测赢家,预测赢家,https://leetcode.cn/problems/predict-the-winner/,predict-the-winner,递归、数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/0400-0499/predict-the-winner/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0486.%20%E9%A2%84%E6%B5%8B%E8%B5%A2%E5%AE%B6.md,59.5%,中等,562 -0487,0400-0499,0487. 最大连续1的个数 II,最大连续1的个数 II,https://leetcode.cn/problems/max-consecutive-ones-ii/,max-consecutive-ones-ii,数组、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/0400-0499/max-consecutive-ones-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0487.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20II.md,57.6%,中等,231 -0488,0400-0499,0488. 祖玛游戏,祖玛游戏,https://leetcode.cn/problems/zuma-game/,zuma-game,栈、广度优先搜索、记忆化搜索、字符串、动态规划,https://algo.itcharge.cn/Solutions/0400-0499/zuma-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0488.%20%E7%A5%96%E7%8E%9B%E6%B8%B8%E6%88%8F.md,48.6%,困难,188 -0489,0400-0499,0489. 扫地机器人,扫地机器人,https://leetcode.cn/problems/robot-room-cleaner/,robot-room-cleaner,回溯、交互,https://algo.itcharge.cn/Solutions/0400-0499/robot-room-cleaner/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0489.%20%E6%89%AB%E5%9C%B0%E6%9C%BA%E5%99%A8%E4%BA%BA.md,74.8%,困难,37 -0490,0400-0499,0490. 迷宫,迷宫,https://leetcode.cn/problems/the-maze/,the-maze,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/0400-0499/the-maze/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0490.%20%E8%BF%B7%E5%AE%AB.md,50.2%,中等,139 -0491,0400-0499,0491. 递增子序列,递增子序列,https://leetcode.cn/problems/non-decreasing-subsequences/,non-decreasing-subsequences,位运算、数组、哈希表、回溯,https://algo.itcharge.cn/Solutions/0400-0499/non-decreasing-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0491.%20%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md,52.0%,中等,900 -0492,0400-0499,0492. 构造矩形,构造矩形,https://leetcode.cn/problems/construct-the-rectangle/,construct-the-rectangle,数学,https://algo.itcharge.cn/Solutions/0400-0499/construct-the-rectangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0492.%20%E6%9E%84%E9%80%A0%E7%9F%A9%E5%BD%A2.md,61.2%,简单,535 -0493,0400-0499,0493. 翻转对,翻转对,https://leetcode.cn/problems/reverse-pairs/,reverse-pairs,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/0400-0499/reverse-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0493.%20%E7%BF%BB%E8%BD%AC%E5%AF%B9.md,36.5%,困难,401 -0494,0400-0499,0494. 目标和,目标和,https://leetcode.cn/problems/target-sum/,target-sum,数组、动态规划、回溯,https://algo.itcharge.cn/Solutions/0400-0499/target-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md,48.7%,中等,1985 -0495,0400-0499,0495. 提莫攻击,提莫攻击,https://leetcode.cn/problems/teemo-attacking/,teemo-attacking,数组、模拟,https://algo.itcharge.cn/Solutions/0400-0499/teemo-attacking/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0495.%20%E6%8F%90%E8%8E%AB%E6%94%BB%E5%87%BB.md,59.4%,简单,1275 -0496,0400-0499,0496. 下一个更大元素 I,下一个更大元素 I,https://leetcode.cn/problems/next-greater-element-i/,next-greater-element-i,栈、数组、哈希表、单调栈,https://algo.itcharge.cn/Solutions/0400-0499/next-greater-element-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0496.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20I.md,71.8%,简单,2391 -0497,0400-0499,0497. 非重叠矩形中的随机点,非重叠矩形中的随机点,https://leetcode.cn/problems/random-point-in-non-overlapping-rectangles/,random-point-in-non-overlapping-rectangles,水塘抽样、数组、数学、二分查找、有序集合、前缀和、随机化,https://algo.itcharge.cn/Solutions/0400-0499/random-point-in-non-overlapping-rectangles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0497.%20%E9%9D%9E%E9%87%8D%E5%8F%A0%E7%9F%A9%E5%BD%A2%E4%B8%AD%E7%9A%84%E9%9A%8F%E6%9C%BA%E7%82%B9.md,41.9%,中等,214 -0498,0400-0499,0498. 对角线遍历,对角线遍历,https://leetcode.cn/problems/diagonal-traverse/,diagonal-traverse,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0400-0499/diagonal-traverse/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0498.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86.md,55.9%,中等,1121 -0499,0400-0499,0499. 迷宫 III,迷宫 III,https://leetcode.cn/problems/the-maze-iii/,the-maze-iii,深度优先搜索、广度优先搜索、图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/0400-0499/the-maze-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0499.%20%E8%BF%B7%E5%AE%AB%20III.md,43.0%,困难,89 -0500,0500-0599,0500. 键盘行,键盘行,https://leetcode.cn/problems/keyboard-row/,keyboard-row,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0500-0599/keyboard-row/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0500.%20%E9%94%AE%E7%9B%98%E8%A1%8C.md,74.0%,简单,1033 -0501,0500-0599,0501. 二叉搜索树中的众数,二叉搜索树中的众数,https://leetcode.cn/problems/find-mode-in-binary-search-tree/,find-mode-in-binary-search-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/find-mode-in-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0501.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%BC%97%E6%95%B0.md,54.7%,简单,1099 -0502,0500-0599,0502. IPO,IPO,https://leetcode.cn/problems/ipo/,ipo,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0500-0599/ipo/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0502.%20IPO.md,44.9%,困难,428 -0503,0500-0599,0503. 下一个更大元素 II,下一个更大元素 II,https://leetcode.cn/problems/next-greater-element-ii/,next-greater-element-ii,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/0500-0599/next-greater-element-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II.md,66.8%,中等,1613 -0504,0500-0599,0504. 七进制数,七进制数,https://leetcode.cn/problems/base-7/,base-7,数学,https://algo.itcharge.cn/Solutions/0500-0599/base-7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0504.%20%E4%B8%83%E8%BF%9B%E5%88%B6%E6%95%B0.md,51.8%,简单,841 -0505,0500-0599,0505. 迷宫 II,迷宫 II,https://leetcode.cn/problems/the-maze-ii/,the-maze-ii,深度优先搜索、广度优先搜索、图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/0500-0599/the-maze-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0505.%20%E8%BF%B7%E5%AE%AB%20II.md,51.6%,中等,115 -0506,0500-0599,0506. 相对名次,相对名次,https://leetcode.cn/problems/relative-ranks/,relative-ranks,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0500-0599/relative-ranks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0506.%20%E7%9B%B8%E5%AF%B9%E5%90%8D%E6%AC%A1.md,65.0%,简单,979 -0507,0500-0599,0507. 完美数,完美数,https://leetcode.cn/problems/perfect-number/,perfect-number,数学,https://algo.itcharge.cn/Solutions/0500-0599/perfect-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0507.%20%E5%AE%8C%E7%BE%8E%E6%95%B0.md,49.1%,简单,570 -0508,0500-0599,0508. 出现次数最多的子树元素和,出现次数最多的子树元素和,https://leetcode.cn/problems/most-frequent-subtree-sum/,most-frequent-subtree-sum,树、深度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/most-frequent-subtree-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0508.%20%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E5%AD%90%E6%A0%91%E5%85%83%E7%B4%A0%E5%92%8C.md,75.5%,中等,567 -0509,0500-0599,0509. 斐波那契数,斐波那契数,https://leetcode.cn/problems/fibonacci-number/,fibonacci-number,递归、记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/fibonacci-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md,66.2%,简单,3152 -0510,0500-0599,0510. 二叉搜索树中的中序后继 II,二叉搜索树中的中序后继 II,https://leetcode.cn/problems/inorder-successor-in-bst-ii/,inorder-successor-in-bst-ii,树、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/inorder-successor-in-bst-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0510.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%B8%AD%E5%BA%8F%E5%90%8E%E7%BB%A7%20II.md,60.4%,中等,74 -0511,0500-0599,0511. 游戏玩法分析 I,游戏玩法分析 I,https://leetcode.cn/problems/game-play-analysis-i/,game-play-analysis-i,数据库,https://algo.itcharge.cn/Solutions/0500-0599/game-play-analysis-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0511.%20%E6%B8%B8%E6%88%8F%E7%8E%A9%E6%B3%95%E5%88%86%E6%9E%90%20I.md,70.7%,简单,378 -0512,0500-0599,0512. 游戏玩法分析 II,游戏玩法分析 II,https://leetcode.cn/problems/game-play-analysis-ii/,game-play-analysis-ii,数据库,https://algo.itcharge.cn/Solutions/0500-0599/game-play-analysis-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0512.%20%E6%B8%B8%E6%88%8F%E7%8E%A9%E6%B3%95%E5%88%86%E6%9E%90%20II.md,54.2%,简单,229 -0513,0500-0599,0513. 找树左下角的值,找树左下角的值,https://leetcode.cn/problems/find-bottom-left-tree-value/,find-bottom-left-tree-value,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/find-bottom-left-tree-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0513.%20%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.md,73.5%,中等,1542 -0514,0500-0599,0514. 自由之路,自由之路,https://leetcode.cn/problems/freedom-trail/,freedom-trail,深度优先搜索、广度优先搜索、字符串、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/freedom-trail/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0514.%20%E8%87%AA%E7%94%B1%E4%B9%8B%E8%B7%AF.md,51.5%,困难,350 -0515,0500-0599,0515. 在每个树行中找最大值,在每个树行中找最大值,https://leetcode.cn/problems/find-largest-value-in-each-tree-row/,find-largest-value-in-each-tree-row,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/find-largest-value-in-each-tree-row/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0515.%20%E5%9C%A8%E6%AF%8F%E4%B8%AA%E6%A0%91%E8%A1%8C%E4%B8%AD%E6%89%BE%E6%9C%80%E5%A4%A7%E5%80%BC.md,66.4%,中等,1115 -0516,0500-0599,0516. 最长回文子序列,最长回文子序列,https://leetcode.cn/problems/longest-palindromic-subsequence/,longest-palindromic-subsequence,字符串、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/longest-palindromic-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md,67.2%,中等,1118 -0517,0500-0599,0517. 超级洗衣机,超级洗衣机,https://leetcode.cn/problems/super-washing-machines/,super-washing-machines,贪心、数组,https://algo.itcharge.cn/Solutions/0500-0599/super-washing-machines/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0517.%20%E8%B6%85%E7%BA%A7%E6%B4%97%E8%A1%A3%E6%9C%BA.md,51.1%,困难,187 -0518,0500-0599,0518. 零钱兑换 II,零钱兑换 II,https://leetcode.cn/problems/coin-change-ii/,coin-change-ii,数组、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/coin-change-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md,70.5%,中等,1279 -0519,0500-0599,0519. 随机翻转矩阵,随机翻转矩阵,https://leetcode.cn/problems/random-flip-matrix/,random-flip-matrix,水塘抽样、哈希表、数学、随机化,https://algo.itcharge.cn/Solutions/0500-0599/random-flip-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0519.%20%E9%9A%8F%E6%9C%BA%E7%BF%BB%E8%BD%AC%E7%9F%A9%E9%98%B5.md,46.3%,中等,213 -0520,0500-0599,0520. 检测大写字母,检测大写字母,https://leetcode.cn/problems/detect-capital/,detect-capital,字符串,https://algo.itcharge.cn/Solutions/0500-0599/detect-capital/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0520.%20%E6%A3%80%E6%B5%8B%E5%A4%A7%E5%86%99%E5%AD%97%E6%AF%8D.md,56.8%,简单,1332 -0521,0500-0599,0521. 最长特殊序列 Ⅰ,最长特殊序列 Ⅰ,https://leetcode.cn/problems/longest-uncommon-subsequence-i/,longest-uncommon-subsequence-i,字符串,https://algo.itcharge.cn/Solutions/0500-0599/longest-uncommon-subsequence-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0521.%20%E6%9C%80%E9%95%BF%E7%89%B9%E6%AE%8A%E5%BA%8F%E5%88%97%20%E2%85%A0.md,73.7%,简单,573 -0522,0500-0599,0522. 最长特殊序列 II,最长特殊序列 II,https://leetcode.cn/problems/longest-uncommon-subsequence-ii/,longest-uncommon-subsequence-ii,数组、哈希表、双指针、字符串、排序,https://algo.itcharge.cn/Solutions/0500-0599/longest-uncommon-subsequence-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0522.%20%E6%9C%80%E9%95%BF%E7%89%B9%E6%AE%8A%E5%BA%8F%E5%88%97%20II.md,48.8%,中等,362 -0523,0500-0599,0523. 连续的子数组和,连续的子数组和,https://leetcode.cn/problems/continuous-subarray-sum/,continuous-subarray-sum,数组、哈希表、数学、前缀和,https://algo.itcharge.cn/Solutions/0500-0599/continuous-subarray-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0523.%20%E8%BF%9E%E7%BB%AD%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md,28.5%,中等,782 -0524,0500-0599,0524. 通过删除字母匹配到字典里最长单词,通过删除字母匹配到字典里最长单词,https://leetcode.cn/problems/longest-word-in-dictionary-through-deleting/,longest-word-in-dictionary-through-deleting,数组、双指针、字符串、排序,https://algo.itcharge.cn/Solutions/0500-0599/longest-word-in-dictionary-through-deleting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0524.%20%E9%80%9A%E8%BF%87%E5%88%A0%E9%99%A4%E5%AD%97%E6%AF%8D%E5%8C%B9%E9%85%8D%E5%88%B0%E5%AD%97%E5%85%B8%E9%87%8C%E6%9C%80%E9%95%BF%E5%8D%95%E8%AF%8D.md,50.0%,中等,974 -0525,0500-0599,0525. 连续数组,连续数组,https://leetcode.cn/problems/contiguous-array/,contiguous-array,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/0500-0599/contiguous-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0525.%20%E8%BF%9E%E7%BB%AD%E6%95%B0%E7%BB%84.md,54.7%,中等,704 -0526,0500-0599,0526. 优美的排列,优美的排列,https://leetcode.cn/problems/beautiful-arrangement/,beautiful-arrangement,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0500-0599/beautiful-arrangement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0526.%20%E4%BC%98%E7%BE%8E%E7%9A%84%E6%8E%92%E5%88%97.md,73.2%,中等,515 -0527,0500-0599,0527. 单词缩写,单词缩写,https://leetcode.cn/problems/word-abbreviation/,word-abbreviation,贪心、字典树、数组、字符串、排序,https://algo.itcharge.cn/Solutions/0500-0599/word-abbreviation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0527.%20%E5%8D%95%E8%AF%8D%E7%BC%A9%E5%86%99.md,60.1%,困难,42 -0528,0500-0599,0528. 按权重随机选择,按权重随机选择,https://leetcode.cn/problems/random-pick-with-weight/,random-pick-with-weight,数组、数学、二分查找、前缀和、随机化,https://algo.itcharge.cn/Solutions/0500-0599/random-pick-with-weight/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0528.%20%E6%8C%89%E6%9D%83%E9%87%8D%E9%9A%8F%E6%9C%BA%E9%80%89%E6%8B%A9.md,48.4%,中等,490 -0529,0500-0599,0529. 扫雷游戏,扫雷游戏,https://leetcode.cn/problems/minesweeper/,minesweeper,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/minesweeper/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0529.%20%E6%89%AB%E9%9B%B7%E6%B8%B8%E6%88%8F.md,64.0%,中等,600 -0530,0500-0599,0530. 二叉搜索树的最小绝对差,二叉搜索树的最小绝对差,https://leetcode.cn/problems/minimum-absolute-difference-in-bst/,minimum-absolute-difference-in-bst,树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/minimum-absolute-difference-in-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0530.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF%B9%E5%B7%AE.md,63.4%,简单,1173 -0531,0500-0599,0531. 孤独像素 I,孤独像素 I,https://leetcode.cn/problems/lonely-pixel-i/,lonely-pixel-i,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/lonely-pixel-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0531.%20%E5%AD%A4%E7%8B%AC%E5%83%8F%E7%B4%A0%20I.md,64.2%,中等,119 -0532,0500-0599,0532. 数组中的 k-diff 数对,数组中的 k-diff 数对,https://leetcode.cn/problems/k-diff-pairs-in-an-array/,k-diff-pairs-in-an-array,数组、哈希表、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0500-0599/k-diff-pairs-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0532.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%20k-diff%20%E6%95%B0%E5%AF%B9.md,45.6%,中等,706 -0533,0500-0599,0533. 孤独像素 II,孤独像素 II,https://leetcode.cn/problems/lonely-pixel-ii/,lonely-pixel-ii,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/lonely-pixel-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0533.%20%E5%AD%A4%E7%8B%AC%E5%83%8F%E7%B4%A0%20II.md,52.8%,中等,47 -0534,0500-0599,0534. 游戏玩法分析 III,游戏玩法分析 III,https://leetcode.cn/problems/game-play-analysis-iii/,game-play-analysis-iii,数据库,https://algo.itcharge.cn/Solutions/0500-0599/game-play-analysis-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0534.%20%E6%B8%B8%E6%88%8F%E7%8E%A9%E6%B3%95%E5%88%86%E6%9E%90%20III.md,68.9%,中等,201 -0535,0500-0599,0535. TinyURL 的加密与解密,TinyURL 的加密与解密,https://leetcode.cn/problems/encode-and-decode-tinyurl/,encode-and-decode-tinyurl,设计、哈希表、字符串、哈希函数,https://algo.itcharge.cn/Solutions/0500-0599/encode-and-decode-tinyurl/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0535.%20TinyURL%20%E7%9A%84%E5%8A%A0%E5%AF%86%E4%B8%8E%E8%A7%A3%E5%AF%86.md,87.5%,中等,278 -0536,0500-0599,0536. 从字符串生成二叉树,从字符串生成二叉树,https://leetcode.cn/problems/construct-binary-tree-from-string/,construct-binary-tree-from-string,树、深度优先搜索、字符串、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/construct-binary-tree-from-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0536.%20%E4%BB%8E%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91.md,55.2%,中等,97 -0537,0500-0599,0537. 复数乘法,复数乘法,https://leetcode.cn/problems/complex-number-multiplication/,complex-number-multiplication,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0500-0599/complex-number-multiplication/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0537.%20%E5%A4%8D%E6%95%B0%E4%B9%98%E6%B3%95.md,74.6%,中等,666 -0538,0500-0599,0538. 把二叉搜索树转换为累加树,把二叉搜索树转换为累加树,https://leetcode.cn/problems/convert-bst-to-greater-tree/,convert-bst-to-greater-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/convert-bst-to-greater-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0538.%20%E6%8A%8A%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E6%8D%A2%E4%B8%BA%E7%B4%AF%E5%8A%A0%E6%A0%91.md,76.6%,中等,1553 -0539,0500-0599,0539. 最小时间差,最小时间差,https://leetcode.cn/problems/minimum-time-difference/,minimum-time-difference,数组、数学、字符串、排序,https://algo.itcharge.cn/Solutions/0500-0599/minimum-time-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0539.%20%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4%E5%B7%AE.md,65.9%,中等,672 -0540,0500-0599,0540. 有序数组中的单一元素,有序数组中的单一元素,https://leetcode.cn/problems/single-element-in-a-sorted-array/,single-element-in-a-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/0500-0599/single-element-in-a-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0540.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%8D%95%E4%B8%80%E5%85%83%E7%B4%A0.md,60.5%,中等,1076 -0541,0500-0599,0541. 反转字符串 II,反转字符串 II,https://leetcode.cn/problems/reverse-string-ii/,reverse-string-ii,双指针、字符串,https://algo.itcharge.cn/Solutions/0500-0599/reverse-string-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0541.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%20II.md,58.0%,简单,1560 -0542,0500-0599,0542. 01 矩阵,01 矩阵,https://leetcode.cn/problems/01-matrix/,01-matrix,广度优先搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/01-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0542.%2001%20%E7%9F%A9%E9%98%B5.md,46.6%,中等,1110 -0543,0500-0599,0543. 二叉树的直径,二叉树的直径,https://leetcode.cn/problems/diameter-of-binary-tree/,diameter-of-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/diameter-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md,58.6%,简单,2257 -0544,0500-0599,0544. 输出比赛匹配对,输出比赛匹配对,https://leetcode.cn/problems/output-contest-matches/,output-contest-matches,递归、字符串、模拟,https://algo.itcharge.cn/Solutions/0500-0599/output-contest-matches/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0544.%20%E8%BE%93%E5%87%BA%E6%AF%94%E8%B5%9B%E5%8C%B9%E9%85%8D%E5%AF%B9.md,72.3%,中等,72 -0545,0500-0599,0545. 二叉树的边界,二叉树的边界,https://leetcode.cn/problems/boundary-of-binary-tree/,boundary-of-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/boundary-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0545.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%BE%B9%E7%95%8C.md,44.7%,中等,101 -0546,0500-0599,0546. 移除盒子,移除盒子,https://leetcode.cn/problems/remove-boxes/,remove-boxes,记忆化搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/remove-boxes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0546.%20%E7%A7%BB%E9%99%A4%E7%9B%92%E5%AD%90.md,60.8%,困难,149 -0547,0500-0599,0547. 省份数量,省份数量,https://leetcode.cn/problems/number-of-provinces/,number-of-provinces,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0500-0599/number-of-provinces/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0547.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md,62.2%,中等,1857 -0548,0500-0599,0548. 将数组分割成和相等的子数组,将数组分割成和相等的子数组,https://leetcode.cn/problems/split-array-with-equal-sum/,split-array-with-equal-sum,数组、前缀和,https://algo.itcharge.cn/Solutions/0500-0599/split-array-with-equal-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0548.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%86%E5%89%B2%E6%88%90%E5%92%8C%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,39.0%,困难,54 -0549,0500-0599,0549. 二叉树中最长的连续序列,二叉树中最长的连续序列,https://leetcode.cn/problems/binary-tree-longest-consecutive-sequence-ii/,binary-tree-longest-consecutive-sequence-ii,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/binary-tree-longest-consecutive-sequence-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0549.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E6%9C%80%E9%95%BF%E7%9A%84%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md,50.3%,中等,83 -0550,0500-0599,0550. 游戏玩法分析 IV,游戏玩法分析 IV,https://leetcode.cn/problems/game-play-analysis-iv/,game-play-analysis-iv,数据库,https://algo.itcharge.cn/Solutions/0500-0599/game-play-analysis-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0550.%20%E6%B8%B8%E6%88%8F%E7%8E%A9%E6%B3%95%E5%88%86%E6%9E%90%20IV.md,43.8%,中等,337 -0551,0500-0599,0551. 学生出勤记录 I,学生出勤记录 I,https://leetcode.cn/problems/student-attendance-record-i/,student-attendance-record-i,字符串,https://algo.itcharge.cn/Solutions/0500-0599/student-attendance-record-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0551.%20%E5%AD%A6%E7%94%9F%E5%87%BA%E5%8B%A4%E8%AE%B0%E5%BD%95%20I.md,56.7%,简单,991 -0552,0500-0599,0552. 学生出勤记录 II,学生出勤记录 II,https://leetcode.cn/problems/student-attendance-record-ii/,student-attendance-record-ii,动态规划,https://algo.itcharge.cn/Solutions/0500-0599/student-attendance-record-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0552.%20%E5%AD%A6%E7%94%9F%E5%87%BA%E5%8B%A4%E8%AE%B0%E5%BD%95%20II.md,57.8%,困难,345 -0553,0500-0599,0553. 最优除法,最优除法,https://leetcode.cn/problems/optimal-division/,optimal-division,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/optimal-division/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0553.%20%E6%9C%80%E4%BC%98%E9%99%A4%E6%B3%95.md,64.8%,中等,382 -0554,0500-0599,0554. 砖墙,砖墙,https://leetcode.cn/problems/brick-wall/,brick-wall,数组、哈希表,https://algo.itcharge.cn/Solutions/0500-0599/brick-wall/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0554.%20%E7%A0%96%E5%A2%99.md,51.5%,中等,601 -0555,0500-0599,0555. 分割连接字符串,分割连接字符串,https://leetcode.cn/problems/split-concatenated-strings/,split-concatenated-strings,贪心、数组、字符串,https://algo.itcharge.cn/Solutions/0500-0599/split-concatenated-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0555.%20%E5%88%86%E5%89%B2%E8%BF%9E%E6%8E%A5%E5%AD%97%E7%AC%A6%E4%B8%B2.md,37.6%,中等,32 -0556,0500-0599,0556. 下一个更大元素 III,下一个更大元素 III,https://leetcode.cn/problems/next-greater-element-iii/,next-greater-element-iii,数学、双指针、字符串,https://algo.itcharge.cn/Solutions/0500-0599/next-greater-element-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0556.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20III.md,36.9%,中等,568 -0557,0500-0599,0557. 反转字符串中的单词 III,反转字符串中的单词 III,https://leetcode.cn/problems/reverse-words-in-a-string-iii/,reverse-words-in-a-string-iii,双指针、字符串,https://algo.itcharge.cn/Solutions/0500-0599/reverse-words-in-a-string-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0557.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20III.md,73.8%,简单,2558 -0558,0500-0599,0558. 四叉树交集,四叉树交集,https://leetcode.cn/problems/logical-or-of-two-binary-grids-represented-as-quad-trees/,logical-or-of-two-binary-grids-represented-as-quad-trees,树、分治,https://algo.itcharge.cn/Solutions/0500-0599/logical-or-of-two-binary-grids-represented-as-quad-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0558.%20%E5%9B%9B%E5%8F%89%E6%A0%91%E4%BA%A4%E9%9B%86.md,62.8%,中等,164 -0559,0500-0599,0559. N 叉树的最大深度,N 叉树的最大深度,https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/,maximum-depth-of-n-ary-tree,树、深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/0500-0599/maximum-depth-of-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0559.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md,75.0%,简单,1071 -0560,0500-0599,0560. 和为 K 的子数组,和为 K 的子数组,https://leetcode.cn/problems/subarray-sum-equals-k/,subarray-sum-equals-k,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/0500-0599/subarray-sum-equals-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,45.0%,中等,1867 -0561,0500-0599,0561. 数组拆分,数组拆分,https://leetcode.cn/problems/array-partition/,array-partition,贪心、数组、计数排序、排序,https://algo.itcharge.cn/Solutions/0500-0599/array-partition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md,78.5%,简单,1125 -0562,0500-0599,0562. 矩阵中最长的连续1线段,矩阵中最长的连续1线段,https://leetcode.cn/problems/longest-line-of-consecutive-one-in-matrix/,longest-line-of-consecutive-one-in-matrix,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/longest-line-of-consecutive-one-in-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0562.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%9C%80%E9%95%BF%E7%9A%84%E8%BF%9E%E7%BB%AD1%E7%BA%BF%E6%AE%B5.md,49.7%,中等,102 -0563,0500-0599,0563. 二叉树的坡度,二叉树的坡度,https://leetcode.cn/problems/binary-tree-tilt/,binary-tree-tilt,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0500-0599/binary-tree-tilt/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0563.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%9D%A1%E5%BA%A6.md,65.9%,简单,761 -0564,0500-0599,0564. 寻找最近的回文数,寻找最近的回文数,https://leetcode.cn/problems/find-the-closest-palindrome/,find-the-closest-palindrome,数学、字符串,https://algo.itcharge.cn/Solutions/0500-0599/find-the-closest-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0564.%20%E5%AF%BB%E6%89%BE%E6%9C%80%E8%BF%91%E7%9A%84%E5%9B%9E%E6%96%87%E6%95%B0.md,30.4%,困难,261 -0565,0500-0599,0565. 数组嵌套,数组嵌套,https://leetcode.cn/problems/array-nesting/,array-nesting,深度优先搜索、数组,https://algo.itcharge.cn/Solutions/0500-0599/array-nesting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0565.%20%E6%95%B0%E7%BB%84%E5%B5%8C%E5%A5%97.md,62.4%,中等,457 -0566,0500-0599,0566. 重塑矩阵,重塑矩阵,https://leetcode.cn/problems/reshape-the-matrix/,reshape-the-matrix,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0500-0599/reshape-the-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0566.%20%E9%87%8D%E5%A1%91%E7%9F%A9%E9%98%B5.md,64.8%,简单,1336 -0567,0500-0599,0567. 字符串的排列,字符串的排列,https://leetcode.cn/problems/permutation-in-string/,permutation-in-string,哈希表、双指针、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/0500-0599/permutation-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0567.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md,44.5%,中等,2322 -0568,0500-0599,0568. 最大休假天数,最大休假天数,https://leetcode.cn/problems/maximum-vacation-days/,maximum-vacation-days,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0500-0599/maximum-vacation-days/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0568.%20%E6%9C%80%E5%A4%A7%E4%BC%91%E5%81%87%E5%A4%A9%E6%95%B0.md,53.8%,困难,42 -0569,0500-0599,0569. 员工薪水中位数,员工薪水中位数,https://leetcode.cn/problems/median-employee-salary/,median-employee-salary,数据库,https://algo.itcharge.cn/Solutions/0500-0599/median-employee-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0569.%20%E5%91%98%E5%B7%A5%E8%96%AA%E6%B0%B4%E4%B8%AD%E4%BD%8D%E6%95%B0.md,57.4%,困难,266 -0570,0500-0599,0570. 至少有5名直接下属的经理,至少有5名直接下属的经理,https://leetcode.cn/problems/managers-with-at-least-5-direct-reports/,managers-with-at-least-5-direct-reports,数据库,https://algo.itcharge.cn/Solutions/0500-0599/managers-with-at-least-5-direct-reports/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0570.%20%E8%87%B3%E5%B0%91%E6%9C%895%E5%90%8D%E7%9B%B4%E6%8E%A5%E4%B8%8B%E5%B1%9E%E7%9A%84%E7%BB%8F%E7%90%86.md,64.9%,中等,227 -0571,0500-0599,0571. 给定数字的频率查询中位数,给定数字的频率查询中位数,https://leetcode.cn/problems/find-median-given-frequency-of-numbers/,find-median-given-frequency-of-numbers,数据库,https://algo.itcharge.cn/Solutions/0500-0599/find-median-given-frequency-of-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0571.%20%E7%BB%99%E5%AE%9A%E6%95%B0%E5%AD%97%E7%9A%84%E9%A2%91%E7%8E%87%E6%9F%A5%E8%AF%A2%E4%B8%AD%E4%BD%8D%E6%95%B0.md,47.0%,困难,148 -0572,0500-0599,0572. 另一棵树的子树,另一棵树的子树,https://leetcode.cn/problems/subtree-of-another-tree/,subtree-of-another-tree,树、深度优先搜索、二叉树、字符串匹配、哈希函数,https://algo.itcharge.cn/Solutions/0500-0599/subtree-of-another-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0572.%20%E5%8F%A6%E4%B8%80%E6%A3%B5%E6%A0%91%E7%9A%84%E5%AD%90%E6%A0%91.md,47.5%,简单,1318 -0573,0500-0599,0573. 松鼠模拟,松鼠模拟,https://leetcode.cn/problems/squirrel-simulation/,squirrel-simulation,数组、数学,https://algo.itcharge.cn/Solutions/0500-0599/squirrel-simulation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0573.%20%E6%9D%BE%E9%BC%A0%E6%A8%A1%E6%8B%9F.md,65.3%,中等,49 -0574,0500-0599,0574. 当选者,当选者,https://leetcode.cn/problems/winning-candidate/,winning-candidate,数据库,https://algo.itcharge.cn/Solutions/0500-0599/winning-candidate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0574.%20%E5%BD%93%E9%80%89%E8%80%85.md,66.4%,中等,162 -0575,0500-0599,0575. 分糖果,分糖果,https://leetcode.cn/problems/distribute-candies/,distribute-candies,数组、哈希表,https://algo.itcharge.cn/Solutions/0500-0599/distribute-candies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0575.%20%E5%88%86%E7%B3%96%E6%9E%9C.md,70.4%,简单,1070 -0576,0500-0599,0576. 出界的路径数,出界的路径数,https://leetcode.cn/problems/out-of-boundary-paths/,out-of-boundary-paths,动态规划,https://algo.itcharge.cn/Solutions/0500-0599/out-of-boundary-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md,47.0%,中等,382 -0577,0500-0599,0577. 员工奖金,员工奖金,https://leetcode.cn/problems/employee-bonus/,employee-bonus,数据库,https://algo.itcharge.cn/Solutions/0500-0599/employee-bonus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0577.%20%E5%91%98%E5%B7%A5%E5%A5%96%E9%87%91.md,69.2%,简单,165 -0578,0500-0599,0578. 查询回答率最高的问题,查询回答率最高的问题,https://leetcode.cn/problems/get-highest-answer-rate-question/,get-highest-answer-rate-question,数据库,https://algo.itcharge.cn/Solutions/0500-0599/get-highest-answer-rate-question/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0578.%20%E6%9F%A5%E8%AF%A2%E5%9B%9E%E7%AD%94%E7%8E%87%E6%9C%80%E9%AB%98%E7%9A%84%E9%97%AE%E9%A2%98.md,42.3%,中等,190 -0579,0500-0599,0579. 查询员工的累计薪水,查询员工的累计薪水,https://leetcode.cn/problems/find-cumulative-salary-of-an-employee/,find-cumulative-salary-of-an-employee,数据库,https://algo.itcharge.cn/Solutions/0500-0599/find-cumulative-salary-of-an-employee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0579.%20%E6%9F%A5%E8%AF%A2%E5%91%98%E5%B7%A5%E7%9A%84%E7%B4%AF%E8%AE%A1%E8%96%AA%E6%B0%B4.md,44.1%,困难,188 -0580,0500-0599,0580. 统计各专业学生人数,统计各专业学生人数,https://leetcode.cn/problems/count-student-number-in-departments/,count-student-number-in-departments,数据库,https://algo.itcharge.cn/Solutions/0500-0599/count-student-number-in-departments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0580.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%B8%93%E4%B8%9A%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md,53.6%,中等,156 -0581,0500-0599,0581. 最短无序连续子数组,最短无序连续子数组,https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/,shortest-unsorted-continuous-subarray,栈、贪心、数组、双指针、排序、单调栈,https://algo.itcharge.cn/Solutions/0500-0599/shortest-unsorted-continuous-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0581.%20%E6%9C%80%E7%9F%AD%E6%97%A0%E5%BA%8F%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md,41.8%,中等,1350 -0582,0500-0599,0582. 杀掉进程,杀掉进程,https://leetcode.cn/problems/kill-process/,kill-process,树、深度优先搜索、广度优先搜索、数组、哈希表,https://algo.itcharge.cn/Solutions/0500-0599/kill-process/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0582.%20%E6%9D%80%E6%8E%89%E8%BF%9B%E7%A8%8B.md,45.9%,中等,188 -0583,0500-0599,0583. 两个字符串的删除操作,两个字符串的删除操作,https://leetcode.cn/problems/delete-operation-for-two-strings/,delete-operation-for-two-strings,字符串、动态规划,https://algo.itcharge.cn/Solutions/0500-0599/delete-operation-for-two-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0583.%20%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.md,66.6%,中等,918 -0584,0500-0599,0584. 寻找用户推荐人,寻找用户推荐人,https://leetcode.cn/problems/find-customer-referee/,find-customer-referee,数据库,https://algo.itcharge.cn/Solutions/0500-0599/find-customer-referee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0584.%20%E5%AF%BB%E6%89%BE%E7%94%A8%E6%88%B7%E6%8E%A8%E8%8D%90%E4%BA%BA.md,65.4%,简单,436 -0585,0500-0599,0585. 2016年的投资,2016年的投资,https://leetcode.cn/problems/investments-in-2016/,investments-in-2016,数据库,https://algo.itcharge.cn/Solutions/0500-0599/investments-in-2016/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0585.%202016%E5%B9%B4%E7%9A%84%E6%8A%95%E8%B5%84.md,47.7%,中等,190 -0586,0500-0599,0586. 订单最多的客户,订单最多的客户,https://leetcode.cn/problems/customer-placing-the-largest-number-of-orders/,customer-placing-the-largest-number-of-orders,数据库,https://algo.itcharge.cn/Solutions/0500-0599/customer-placing-the-largest-number-of-orders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0586.%20%E8%AE%A2%E5%8D%95%E6%9C%80%E5%A4%9A%E7%9A%84%E5%AE%A2%E6%88%B7.md,69.5%,简单,357 -0587,0500-0599,0587. 安装栅栏,安装栅栏,https://leetcode.cn/problems/erect-the-fence/,erect-the-fence,几何、数组、数学,https://algo.itcharge.cn/Solutions/0500-0599/erect-the-fence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0587.%20%E5%AE%89%E8%A3%85%E6%A0%85%E6%A0%8F.md,60.9%,困难,139 -0588,0500-0599,0588. 设计内存文件系统,设计内存文件系统,https://leetcode.cn/problems/design-in-memory-file-system/,design-in-memory-file-system,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/0500-0599/design-in-memory-file-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0588.%20%E8%AE%BE%E8%AE%A1%E5%86%85%E5%AD%98%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.md,43.5%,困难,56 -0589,0500-0599,0589. N 叉树的前序遍历,N 叉树的前序遍历,https://leetcode.cn/problems/n-ary-tree-preorder-traversal/,n-ary-tree-preorder-traversal,栈、树、深度优先搜索,https://algo.itcharge.cn/Solutions/0500-0599/n-ary-tree-preorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0589.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md,76.5%,简单,1142 -0590,0500-0599,0590. N 叉树的后序遍历,N 叉树的后序遍历,https://leetcode.cn/problems/n-ary-tree-postorder-traversal/,n-ary-tree-postorder-traversal,栈、树、深度优先搜索,https://algo.itcharge.cn/Solutions/0500-0599/n-ary-tree-postorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0590.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md,78.7%,简单,918 -0591,0500-0599,0591. 标签验证器,标签验证器,https://leetcode.cn/problems/tag-validator/,tag-validator,栈、字符串,https://algo.itcharge.cn/Solutions/0500-0599/tag-validator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0591.%20%E6%A0%87%E7%AD%BE%E9%AA%8C%E8%AF%81%E5%99%A8.md,52.1%,困难,197 -0592,0500-0599,0592. 分数加减运算,分数加减运算,https://leetcode.cn/problems/fraction-addition-and-subtraction/,fraction-addition-and-subtraction,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0500-0599/fraction-addition-and-subtraction/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0592.%20%E5%88%86%E6%95%B0%E5%8A%A0%E5%87%8F%E8%BF%90%E7%AE%97.md,59.8%,中等,478 -0593,0500-0599,0593. 有效的正方形,有效的正方形,https://leetcode.cn/problems/valid-square/,valid-square,几何、数学,https://algo.itcharge.cn/Solutions/0500-0599/valid-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0593.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2.md,47.1%,中等,590 -0594,0500-0599,0594. 最长和谐子序列,最长和谐子序列,https://leetcode.cn/problems/longest-harmonious-subsequence/,longest-harmonious-subsequence,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/0500-0599/longest-harmonious-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0594.%20%E6%9C%80%E9%95%BF%E5%92%8C%E8%B0%90%E5%AD%90%E5%BA%8F%E5%88%97.md,56.1%,简单,705 -0595,0500-0599,0595. 大的国家,大的国家,https://leetcode.cn/problems/big-countries/,big-countries,数据库,https://algo.itcharge.cn/Solutions/0500-0599/big-countries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0595.%20%E5%A4%A7%E7%9A%84%E5%9B%BD%E5%AE%B6.md,66.8%,简单,594 -0596,0500-0599,0596. 超过5名学生的课,超过5名学生的课,https://leetcode.cn/problems/classes-more-than-5-students/,classes-more-than-5-students,数据库,https://algo.itcharge.cn/Solutions/0500-0599/classes-more-than-5-students/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0596.%20%E8%B6%85%E8%BF%875%E5%90%8D%E5%AD%A6%E7%94%9F%E7%9A%84%E8%AF%BE.md,50.7%,简单,477 -0597,0500-0599,0597. 好友申请 I:总体通过率,好友申请 I:总体通过率,https://leetcode.cn/problems/friend-requests-i-overall-acceptance-rate/,friend-requests-i-overall-acceptance-rate,数据库,https://algo.itcharge.cn/Solutions/0500-0599/friend-requests-i-overall-acceptance-rate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0597.%20%E5%A5%BD%E5%8F%8B%E7%94%B3%E8%AF%B7%20I%EF%BC%9A%E6%80%BB%E4%BD%93%E9%80%9A%E8%BF%87%E7%8E%87.md,43.2%,简单,126 -0598,0500-0599,0598. 范围求和 II,范围求和 II,https://leetcode.cn/problems/range-addition-ii/,range-addition-ii,数组、数学,https://algo.itcharge.cn/Solutions/0500-0599/range-addition-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0598.%20%E8%8C%83%E5%9B%B4%E6%B1%82%E5%92%8C%20II.md,57.8%,简单,625 -0599,0500-0599,0599. 两个列表的最小索引总和,两个列表的最小索引总和,https://leetcode.cn/problems/minimum-index-sum-of-two-lists/,minimum-index-sum-of-two-lists,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0500-0599/minimum-index-sum-of-two-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0599.%20%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B4%A2%E5%BC%95%E6%80%BB%E5%92%8C.md,56.7%,简单,740 -0600,0600-0699,0600. 不含连续1的非负整数,不含连续1的非负整数,https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/,non-negative-integers-without-consecutive-ones,动态规划,https://algo.itcharge.cn/Solutions/0600-0699/non-negative-integers-without-consecutive-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0600.%20%E4%B8%8D%E5%90%AB%E8%BF%9E%E7%BB%AD1%E7%9A%84%E9%9D%9E%E8%B4%9F%E6%95%B4%E6%95%B0.md,49.5%,困难,314 -0601,0600-0699,0601. 体育馆的人流量,体育馆的人流量,https://leetcode.cn/problems/human-traffic-of-stadium/,human-traffic-of-stadium,数据库,https://algo.itcharge.cn/Solutions/0600-0699/human-traffic-of-stadium/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0601.%20%E4%BD%93%E8%82%B2%E9%A6%86%E7%9A%84%E4%BA%BA%E6%B5%81%E9%87%8F.md,48.0%,困难,573 -0602,0600-0699,0602. 好友申请 II :谁有最多的好友,好友申请 II :谁有最多的好友,https://leetcode.cn/problems/friend-requests-ii-who-has-the-most-friends/,friend-requests-ii-who-has-the-most-friends,数据库,https://algo.itcharge.cn/Solutions/0600-0699/friend-requests-ii-who-has-the-most-friends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0602.%20%E5%A5%BD%E5%8F%8B%E7%94%B3%E8%AF%B7%20II%20%EF%BC%9A%E8%B0%81%E6%9C%89%E6%9C%80%E5%A4%9A%E7%9A%84%E5%A5%BD%E5%8F%8B.md,61.1%,中等,164 -0603,0600-0699,0603. 连续空余座位,连续空余座位,https://leetcode.cn/problems/consecutive-available-seats/,consecutive-available-seats,数据库,https://algo.itcharge.cn/Solutions/0600-0699/consecutive-available-seats/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0603.%20%E8%BF%9E%E7%BB%AD%E7%A9%BA%E4%BD%99%E5%BA%A7%E4%BD%8D.md,64.0%,简单,237 -0604,0600-0699,0604. 迭代压缩字符串,迭代压缩字符串,https://leetcode.cn/problems/design-compressed-string-iterator/,design-compressed-string-iterator,设计、数组、哈希表、字符串、迭代器,https://algo.itcharge.cn/Solutions/0600-0699/design-compressed-string-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0604.%20%E8%BF%AD%E4%BB%A3%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md,38.7%,简单,93 -0605,0600-0699,0605. 种花问题,种花问题,https://leetcode.cn/problems/can-place-flowers/,can-place-flowers,贪心、数组,https://algo.itcharge.cn/Solutions/0600-0699/can-place-flowers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0605.%20%E7%A7%8D%E8%8A%B1%E9%97%AE%E9%A2%98.md,32.3%,简单,1868 -0606,0600-0699,0606. 根据二叉树创建字符串,根据二叉树创建字符串,https://leetcode.cn/problems/construct-string-from-binary-tree/,construct-string-from-binary-tree,树、深度优先搜索、字符串、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/construct-string-from-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0606.%20%E6%A0%B9%E6%8D%AE%E4%BA%8C%E5%8F%89%E6%A0%91%E5%88%9B%E5%BB%BA%E5%AD%97%E7%AC%A6%E4%B8%B2.md,62.2%,简单,728 -0607,0600-0699,0607. 销售员,销售员,https://leetcode.cn/problems/sales-person/,sales-person,数据库,https://algo.itcharge.cn/Solutions/0600-0699/sales-person/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0607.%20%E9%94%80%E5%94%AE%E5%91%98.md,68.2%,简单,396 -0608,0600-0699,0608. 树节点,树节点,https://leetcode.cn/problems/tree-node/,tree-node,数据库,https://algo.itcharge.cn/Solutions/0600-0699/tree-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0608.%20%E6%A0%91%E8%8A%82%E7%82%B9.md,61.0%,中等,401 -0609,0600-0699,0609. 在系统中查找重复文件,在系统中查找重复文件,https://leetcode.cn/problems/find-duplicate-file-in-system/,find-duplicate-file-in-system,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0600-0699/find-duplicate-file-in-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0609.%20%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%9F%A5%E6%89%BE%E9%87%8D%E5%A4%8D%E6%96%87%E4%BB%B6.md,52.4%,中等,159 -0610,0600-0699,0610. 判断三角形,判断三角形,https://leetcode.cn/problems/triangle-judgement/,triangle-judgement,数据库,https://algo.itcharge.cn/Solutions/0600-0699/triangle-judgement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0610.%20%E5%88%A4%E6%96%AD%E4%B8%89%E8%A7%92%E5%BD%A2.md,64.4%,简单,126 -0611,0600-0699,0611. 有效三角形的个数,有效三角形的个数,https://leetcode.cn/problems/valid-triangle-number/,valid-triangle-number,贪心、数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0600-0699/valid-triangle-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0611.%20%E6%9C%89%E6%95%88%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E4%B8%AA%E6%95%B0.md,53.7%,中等,642 -0612,0600-0699,0612. 平面上的最近距离,平面上的最近距离,https://leetcode.cn/problems/shortest-distance-in-a-plane/,shortest-distance-in-a-plane,数据库,https://algo.itcharge.cn/Solutions/0600-0699/shortest-distance-in-a-plane/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0612.%20%E5%B9%B3%E9%9D%A2%E4%B8%8A%E7%9A%84%E6%9C%80%E8%BF%91%E8%B7%9D%E7%A6%BB.md,65.0%,中等,103 -0613,0600-0699,0613. 直线上的最近距离,直线上的最近距离,https://leetcode.cn/problems/shortest-distance-in-a-line/,shortest-distance-in-a-line,数据库,https://algo.itcharge.cn/Solutions/0600-0699/shortest-distance-in-a-line/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0613.%20%E7%9B%B4%E7%BA%BF%E4%B8%8A%E7%9A%84%E6%9C%80%E8%BF%91%E8%B7%9D%E7%A6%BB.md,79.6%,简单,121 -0614,0600-0699,0614. 二级关注者,二级关注者,https://leetcode.cn/problems/second-degree-follower/,second-degree-follower,数据库,https://algo.itcharge.cn/Solutions/0600-0699/second-degree-follower/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0614.%20%E4%BA%8C%E7%BA%A7%E5%85%B3%E6%B3%A8%E8%80%85.md,37.3%,中等,107 -0615,0600-0699,0615. 平均工资:部门与公司比较,平均工资:部门与公司比较,https://leetcode.cn/problems/average-salary-departments-vs-company/,average-salary-departments-vs-company,数据库,https://algo.itcharge.cn/Solutions/0600-0699/average-salary-departments-vs-company/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0615.%20%E5%B9%B3%E5%9D%87%E5%B7%A5%E8%B5%84%EF%BC%9A%E9%83%A8%E9%97%A8%E4%B8%8E%E5%85%AC%E5%8F%B8%E6%AF%94%E8%BE%83.md,42.3%,困难,150 -0616,0600-0699,0616. 给字符串添加加粗标签,给字符串添加加粗标签,https://leetcode.cn/problems/add-bold-tag-in-string/,add-bold-tag-in-string,字典树、数组、哈希表、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0600-0699/add-bold-tag-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0616.%20%E7%BB%99%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%B7%BB%E5%8A%A0%E5%8A%A0%E7%B2%97%E6%A0%87%E7%AD%BE.md,49.2%,中等,95 -0617,0600-0699,0617. 合并二叉树,合并二叉树,https://leetcode.cn/problems/merge-two-binary-trees/,merge-two-binary-trees,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/merge-two-binary-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0617.%20%E5%90%88%E5%B9%B6%E4%BA%8C%E5%8F%89%E6%A0%91.md,79.1%,简单,2362 -0618,0600-0699,0618. 学生地理信息报告,学生地理信息报告,https://leetcode.cn/problems/students-report-by-geography/,students-report-by-geography,数据库,https://algo.itcharge.cn/Solutions/0600-0699/students-report-by-geography/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0618.%20%E5%AD%A6%E7%94%9F%E5%9C%B0%E7%90%86%E4%BF%A1%E6%81%AF%E6%8A%A5%E5%91%8A.md,62.2%,困难,88 -0619,0600-0699,0619. 只出现一次的最大数字,只出现一次的最大数字,https://leetcode.cn/problems/biggest-single-number/,biggest-single-number,数据库,https://algo.itcharge.cn/Solutions/0600-0699/biggest-single-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0619.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md,48.3%,简单,184 -0620,0600-0699,0620. 有趣的电影,有趣的电影,https://leetcode.cn/problems/not-boring-movies/,not-boring-movies,数据库,https://algo.itcharge.cn/Solutions/0600-0699/not-boring-movies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0620.%20%E6%9C%89%E8%B6%A3%E7%9A%84%E7%94%B5%E5%BD%B1.md,77.1%,简单,458 -0621,0600-0699,0621. 任务调度器,任务调度器,https://leetcode.cn/problems/task-scheduler/,task-scheduler,贪心、数组、哈希表、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/task-scheduler/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0621.%20%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8.md,59.7%,中等,844 -0622,0600-0699,0622. 设计循环队列,设计循环队列,https://leetcode.cn/problems/design-circular-queue/,design-circular-queue,设计、队列、数组、链表,https://algo.itcharge.cn/Solutions/0600-0699/design-circular-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0622.%20%E8%AE%BE%E8%AE%A1%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97.md,47.1%,中等,1002 -0623,0600-0699,0623. 在二叉树中增加一行,在二叉树中增加一行,https://leetcode.cn/problems/add-one-row-to-tree/,add-one-row-to-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/add-one-row-to-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0623.%20%E5%9C%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%A2%9E%E5%8A%A0%E4%B8%80%E8%A1%8C.md,60.1%,中等,655 -0624,0600-0699,0624. 数组列表中的最大距离,数组列表中的最大距离,https://leetcode.cn/problems/maximum-distance-in-arrays/,maximum-distance-in-arrays,贪心、数组,https://algo.itcharge.cn/Solutions/0600-0699/maximum-distance-in-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0624.%20%E6%95%B0%E7%BB%84%E5%88%97%E8%A1%A8%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,40.8%,中等,96 -0625,0600-0699,0625. 最小因式分解,最小因式分解,https://leetcode.cn/problems/minimum-factorization/,minimum-factorization,贪心、数学,https://algo.itcharge.cn/Solutions/0600-0699/minimum-factorization/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0625.%20%E6%9C%80%E5%B0%8F%E5%9B%A0%E5%BC%8F%E5%88%86%E8%A7%A3.md,34.6%,中等,70 -0626,0600-0699,0626. 换座位,换座位,https://leetcode.cn/problems/exchange-seats/,exchange-seats,数据库,https://algo.itcharge.cn/Solutions/0600-0699/exchange-seats/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0626.%20%E6%8D%A2%E5%BA%A7%E4%BD%8D.md,67.9%,中等,601 -0627,0600-0699,0627. 变更性别,变更性别,https://leetcode.cn/problems/swap-salary/,swap-salary,数据库,https://algo.itcharge.cn/Solutions/0600-0699/swap-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0627.%20%E5%8F%98%E6%9B%B4%E6%80%A7%E5%88%AB.md,82.1%,简单,653 -0628,0600-0699,0628. 三个数的最大乘积,三个数的最大乘积,https://leetcode.cn/problems/maximum-product-of-three-numbers/,maximum-product-of-three-numbers,数组、数学、排序,https://algo.itcharge.cn/Solutions/0600-0699/maximum-product-of-three-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0628.%20%E4%B8%89%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,52.0%,简单,1090 -0629,0600-0699,0629. K 个逆序对数组,K 个逆序对数组,https://leetcode.cn/problems/k-inverse-pairs-array/,k-inverse-pairs-array,动态规划,https://algo.itcharge.cn/Solutions/0600-0699/k-inverse-pairs-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0629.%20K%20%E4%B8%AA%E9%80%86%E5%BA%8F%E5%AF%B9%E6%95%B0%E7%BB%84.md,51.1%,困难,212 -0630,0600-0699,0630. 课程表 III,课程表 III,https://leetcode.cn/problems/course-schedule-iii/,course-schedule-iii,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/course-schedule-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0630.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20III.md,46.2%,困难,267 -0631,0600-0699,0631. 设计 Excel 求和公式,设计 Excel 求和公式,https://leetcode.cn/problems/design-excel-sum-formula/,design-excel-sum-formula,图、设计、拓扑排序,https://algo.itcharge.cn/Solutions/0600-0699/design-excel-sum-formula/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0631.%20%E8%AE%BE%E8%AE%A1%20Excel%20%E6%B1%82%E5%92%8C%E5%85%AC%E5%BC%8F.md,33.4%,困难,48 -0632,0600-0699,0632. 最小区间,最小区间,https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists/,smallest-range-covering-elements-from-k-lists,贪心、数组、哈希表、排序、滑动窗口、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/smallest-range-covering-elements-from-k-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0632.%20%E6%9C%80%E5%B0%8F%E5%8C%BA%E9%97%B4.md,60.7%,困难,313 -0633,0600-0699,0633. 平方数之和,平方数之和,https://leetcode.cn/problems/sum-of-square-numbers/,sum-of-square-numbers,数学、双指针、二分查找,https://algo.itcharge.cn/Solutions/0600-0699/sum-of-square-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0633.%20%E5%B9%B3%E6%96%B9%E6%95%B0%E4%B9%8B%E5%92%8C.md,38.3%,中等,1032 -0634,0600-0699,0634. 寻找数组的错位排列,寻找数组的错位排列,https://leetcode.cn/problems/find-the-derangement-of-an-array/,find-the-derangement-of-an-array,数学、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/find-the-derangement-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0634.%20%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E9%94%99%E4%BD%8D%E6%8E%92%E5%88%97.md,47.4%,中等,35 -0635,0600-0699,0635. 设计日志存储系统,设计日志存储系统,https://leetcode.cn/problems/design-log-storage-system/,design-log-storage-system,设计、哈希表、字符串、有序集合,https://algo.itcharge.cn/Solutions/0600-0699/design-log-storage-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0635.%20%E8%AE%BE%E8%AE%A1%E6%97%A5%E5%BF%97%E5%AD%98%E5%82%A8%E7%B3%BB%E7%BB%9F.md,56.3%,中等,108 -0636,0600-0699,0636. 函数的独占时间,函数的独占时间,https://leetcode.cn/problems/exclusive-time-of-functions/,exclusive-time-of-functions,栈、数组,https://algo.itcharge.cn/Solutions/0600-0699/exclusive-time-of-functions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0636.%20%E5%87%BD%E6%95%B0%E7%9A%84%E7%8B%AC%E5%8D%A0%E6%97%B6%E9%97%B4.md,66.0%,中等,348 -0637,0600-0699,0637. 二叉树的层平均值,二叉树的层平均值,https://leetcode.cn/problems/average-of-levels-in-binary-tree/,average-of-levels-in-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/average-of-levels-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0637.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%B9%B3%E5%9D%87%E5%80%BC.md,69.8%,简单,1059 -0638,0600-0699,0638. 大礼包,大礼包,https://leetcode.cn/problems/shopping-offers/,shopping-offers,位运算、记忆化搜索、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0600-0699/shopping-offers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0638.%20%E5%A4%A7%E7%A4%BC%E5%8C%85.md,62.9%,中等,338 -0639,0600-0699,0639. 解码方法 II,解码方法 II,https://leetcode.cn/problems/decode-ways-ii/,decode-ways-ii,字符串、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/decode-ways-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0639.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95%20II.md,37.4%,困难,322 -0640,0600-0699,0640. 求解方程,求解方程,https://leetcode.cn/problems/solve-the-equation/,solve-the-equation,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/0600-0699/solve-the-equation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0640.%20%E6%B1%82%E8%A7%A3%E6%96%B9%E7%A8%8B.md,44.6%,中等,711 -0641,0600-0699,0641. 设计循环双端队列,设计循环双端队列,https://leetcode.cn/problems/design-circular-deque/,design-circular-deque,设计、队列、数组、链表,https://algo.itcharge.cn/Solutions/0600-0699/design-circular-deque/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0641.%20%E8%AE%BE%E8%AE%A1%E5%BE%AA%E7%8E%AF%E5%8F%8C%E7%AB%AF%E9%98%9F%E5%88%97.md,57.1%,中等,647 -0642,0600-0699,0642. 设计搜索自动补全系统,设计搜索自动补全系统,https://leetcode.cn/problems/design-search-autocomplete-system/,design-search-autocomplete-system,设计、字典树、字符串、数据流,https://algo.itcharge.cn/Solutions/0600-0699/design-search-autocomplete-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0642.%20%E8%AE%BE%E8%AE%A1%E6%90%9C%E7%B4%A2%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E7%B3%BB%E7%BB%9F.md,56.1%,困难,91 -0643,0600-0699,0643. 子数组最大平均数 I,子数组最大平均数 I,https://leetcode.cn/problems/maximum-average-subarray-i/,maximum-average-subarray-i,数组、滑动窗口,https://algo.itcharge.cn/Solutions/0600-0699/maximum-average-subarray-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0643.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E6%95%B0%20I.md,43.4%,简单,912 -0644,0600-0699,0644. 子数组最大平均数 II,子数组最大平均数 II,https://leetcode.cn/problems/maximum-average-subarray-ii/,maximum-average-subarray-ii,数组、二分查找、前缀和,https://algo.itcharge.cn/Solutions/0600-0699/maximum-average-subarray-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0644.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E6%95%B0%20II.md,44.4%,困难,41 -0645,0600-0699,0645. 错误的集合,错误的集合,https://leetcode.cn/problems/set-mismatch/,set-mismatch,位运算、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/0600-0699/set-mismatch/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0645.%20%E9%94%99%E8%AF%AF%E7%9A%84%E9%9B%86%E5%90%88.md,39.7%,简单,1049 -0646,0600-0699,0646. 最长数对链,最长数对链,https://leetcode.cn/problems/maximum-length-of-pair-chain/,maximum-length-of-pair-chain,贪心、数组、动态规划、排序,https://algo.itcharge.cn/Solutions/0600-0699/maximum-length-of-pair-chain/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0646.%20%E6%9C%80%E9%95%BF%E6%95%B0%E5%AF%B9%E9%93%BE.md,62.3%,中等,649 -0647,0600-0699,0647. 回文子串,回文子串,https://leetcode.cn/problems/palindromic-substrings/,palindromic-substrings,字符串、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/palindromic-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0647.%20%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md,66.9%,中等,1716 -0648,0600-0699,0648. 单词替换,单词替换,https://leetcode.cn/problems/replace-words/,replace-words,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0600-0699/replace-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0648.%20%E5%8D%95%E8%AF%8D%E6%9B%BF%E6%8D%A2.md,64.0%,中等,823 -0649,0600-0699,0649. Dota2 参议院,Dota2 参议院,https://leetcode.cn/problems/dota2-senate/,dota2-senate,贪心、队列、字符串,https://algo.itcharge.cn/Solutions/0600-0699/dota2-senate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0649.%20Dota2%20%E5%8F%82%E8%AE%AE%E9%99%A2.md,48.2%,中等,417 -0650,0600-0699,0650. 只有两个键的键盘,只有两个键的键盘,https://leetcode.cn/problems/2-keys-keyboard/,2-keys-keyboard,数学、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/2-keys-keyboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0650.%20%E5%8F%AA%E6%9C%89%E4%B8%A4%E4%B8%AA%E9%94%AE%E7%9A%84%E9%94%AE%E7%9B%98.md,57.7%,中等,788 -0651,0600-0699,0651. 4键键盘,4键键盘,https://leetcode.cn/problems/4-keys-keyboard/,4-keys-keyboard,数学、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/4-keys-keyboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0651.%204%E9%94%AE%E9%94%AE%E7%9B%98.md,59.7%,中等,98 -0652,0600-0699,0652. 寻找重复的子树,寻找重复的子树,https://leetcode.cn/problems/find-duplicate-subtrees/,find-duplicate-subtrees,树、深度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/find-duplicate-subtrees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0652.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E6%A0%91.md,61.3%,中等,645 -0653,0600-0699,0653. 两数之和 IV - 输入二叉搜索树,两数之和 IV - 输入二叉搜索树,https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/,two-sum-iv-input-is-a-bst,树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/two-sum-iv-input-is-a-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0653.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20IV%20-%20%E8%BE%93%E5%85%A5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,63.5%,简单,985 -0654,0600-0699,0654. 最大二叉树,最大二叉树,https://leetcode.cn/problems/maximum-binary-tree/,maximum-binary-tree,栈、树、数组、分治、二叉树、单调栈,https://algo.itcharge.cn/Solutions/0600-0699/maximum-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0654.%20%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md,82.6%,中等,1602 -0655,0600-0699,0655. 输出二叉树,输出二叉树,https://leetcode.cn/problems/print-binary-tree/,print-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/print-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0655.%20%E8%BE%93%E5%87%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md,69.7%,中等,510 -0656,0600-0699,0656. 金币路径,金币路径,https://leetcode.cn/problems/coin-path/,coin-path,数组、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/coin-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0656.%20%E9%87%91%E5%B8%81%E8%B7%AF%E5%BE%84.md,34.3%,困难,35 -0657,0600-0699,0657. 机器人能否返回原点,机器人能否返回原点,https://leetcode.cn/problems/robot-return-to-origin/,robot-return-to-origin,字符串、模拟,https://algo.itcharge.cn/Solutions/0600-0699/robot-return-to-origin/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0657.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%83%BD%E5%90%A6%E8%BF%94%E5%9B%9E%E5%8E%9F%E7%82%B9.md,79.0%,简单,835 -0658,0600-0699,0658. 找到 K 个最接近的元素,找到 K 个最接近的元素,https://leetcode.cn/problems/find-k-closest-elements/,find-k-closest-elements,数组、双指针、二分查找、排序、滑动窗口、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/find-k-closest-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md,47.9%,中等,827 -0659,0600-0699,0659. 分割数组为连续子序列,分割数组为连续子序列,https://leetcode.cn/problems/split-array-into-consecutive-subsequences/,split-array-into-consecutive-subsequences,贪心、数组、哈希表、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/split-array-into-consecutive-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0659.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E4%B8%BA%E8%BF%9E%E7%BB%AD%E5%AD%90%E5%BA%8F%E5%88%97.md,54.7%,中等,389 -0660,0600-0699,0660. 移除 9,移除 9,https://leetcode.cn/problems/remove-9/,remove-9,数学,https://algo.itcharge.cn/Solutions/0600-0699/remove-9/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0660.%20%E7%A7%BB%E9%99%A4%209.md,65.0%,困难,40 -0661,0600-0699,0661. 图片平滑器,图片平滑器,https://leetcode.cn/problems/image-smoother/,image-smoother,数组、矩阵,https://algo.itcharge.cn/Solutions/0600-0699/image-smoother/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0661.%20%E5%9B%BE%E7%89%87%E5%B9%B3%E6%BB%91%E5%99%A8.md,64.2%,简单,571 -0662,0600-0699,0662. 二叉树最大宽度,二叉树最大宽度,https://leetcode.cn/problems/maximum-width-of-binary-tree/,maximum-width-of-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/maximum-width-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md,43.6%,中等,830 -0663,0600-0699,0663. 均匀树划分,均匀树划分,https://leetcode.cn/problems/equal-tree-partition/,equal-tree-partition,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/equal-tree-partition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0663.%20%E5%9D%87%E5%8C%80%E6%A0%91%E5%88%92%E5%88%86.md,46.0%,中等,59 -0664,0600-0699,0664. 奇怪的打印机,奇怪的打印机,https://leetcode.cn/problems/strange-printer/,strange-printer,字符串、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/strange-printer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0664.%20%E5%A5%87%E6%80%AA%E7%9A%84%E6%89%93%E5%8D%B0%E6%9C%BA.md,65.3%,困难,236 -0665,0600-0699,0665. 非递减数列,非递减数列,https://leetcode.cn/problems/non-decreasing-array/,non-decreasing-array,数组,https://algo.itcharge.cn/Solutions/0600-0699/non-decreasing-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0665.%20%E9%9D%9E%E9%80%92%E5%87%8F%E6%95%B0%E5%88%97.md,27.7%,中等,1195 -0666,0600-0699,0666. 路径总和 IV,路径总和 IV,https://leetcode.cn/problems/path-sum-iv/,path-sum-iv,树、深度优先搜索、数组、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/path-sum-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0666.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20IV.md,62.7%,中等,110 -0667,0600-0699,0667. 优美的排列 II,优美的排列 II,https://leetcode.cn/problems/beautiful-arrangement-ii/,beautiful-arrangement-ii,数组、数学,https://algo.itcharge.cn/Solutions/0600-0699/beautiful-arrangement-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0667.%20%E4%BC%98%E7%BE%8E%E7%9A%84%E6%8E%92%E5%88%97%20II.md,66.7%,中等,519 -0668,0600-0699,0668. 乘法表中第k小的数,乘法表中第k小的数,https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/,kth-smallest-number-in-multiplication-table,数学、二分查找,https://algo.itcharge.cn/Solutions/0600-0699/kth-smallest-number-in-multiplication-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0668.%20%E4%B9%98%E6%B3%95%E8%A1%A8%E4%B8%AD%E7%AC%ACk%E5%B0%8F%E7%9A%84%E6%95%B0.md,58.6%,困难,278 -0669,0600-0699,0669. 修剪二叉搜索树,修剪二叉搜索树,https://leetcode.cn/problems/trim-a-binary-search-tree/,trim-a-binary-search-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/trim-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0669.%20%E4%BF%AE%E5%89%AA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,67.5%,中等,933 -0670,0600-0699,0670. 最大交换,最大交换,https://leetcode.cn/problems/maximum-swap/,maximum-swap,贪心、数学,https://algo.itcharge.cn/Solutions/0600-0699/maximum-swap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0670.%20%E6%9C%80%E5%A4%A7%E4%BA%A4%E6%8D%A2.md,47.9%,中等,936 -0671,0600-0699,0671. 二叉树中第二小的节点,二叉树中第二小的节点,https://leetcode.cn/problems/second-minimum-node-in-a-binary-tree/,second-minimum-node-in-a-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/second-minimum-node-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0671.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%AC%AC%E4%BA%8C%E5%B0%8F%E7%9A%84%E8%8A%82%E7%82%B9.md,48.0%,简单,862 -0672,0600-0699,0672. 灯泡开关 Ⅱ,灯泡开关 Ⅱ,https://leetcode.cn/problems/bulb-switcher-ii/,bulb-switcher-ii,位运算、深度优先搜索、广度优先搜索、数学,https://algo.itcharge.cn/Solutions/0600-0699/bulb-switcher-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0672.%20%E7%81%AF%E6%B3%A1%E5%BC%80%E5%85%B3%20%E2%85%A1.md,60.8%,中等,263 -0673,0600-0699,0673. 最长递增子序列的个数,最长递增子序列的个数,https://leetcode.cn/problems/number-of-longest-increasing-subsequence/,number-of-longest-increasing-subsequence,树状数组、线段树、数组、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/number-of-longest-increasing-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md,44.6%,中等,653 -0674,0600-0699,0674. 最长连续递增序列,最长连续递增序列,https://leetcode.cn/problems/longest-continuous-increasing-subsequence/,longest-continuous-increasing-subsequence,数组,https://algo.itcharge.cn/Solutions/0600-0699/longest-continuous-increasing-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0674.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.md,55.6%,简单,1298 -0675,0600-0699,0675. 为高尔夫比赛砍树,为高尔夫比赛砍树,https://leetcode.cn/problems/cut-off-trees-for-golf-event/,cut-off-trees-for-golf-event,广度优先搜索、数组、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/cut-off-trees-for-golf-event/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0675.%20%E4%B8%BA%E9%AB%98%E5%B0%94%E5%A4%AB%E6%AF%94%E8%B5%9B%E7%A0%8D%E6%A0%91.md,52.5%,困难,219 -0676,0600-0699,0676. 实现一个魔法字典,实现一个魔法字典,https://leetcode.cn/problems/implement-magic-dictionary/,implement-magic-dictionary,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/0600-0699/implement-magic-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0676.%20%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E9%AD%94%E6%B3%95%E5%AD%97%E5%85%B8.md,65.3%,中等,471 -0677,0600-0699,0677. 键值映射,键值映射,https://leetcode.cn/problems/map-sum-pairs/,map-sum-pairs,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/0600-0699/map-sum-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0677.%20%E9%94%AE%E5%80%BC%E6%98%A0%E5%B0%84.md,65.7%,中等,705 -0678,0600-0699,0678. 有效的括号字符串,有效的括号字符串,https://leetcode.cn/problems/valid-parenthesis-string/,valid-parenthesis-string,栈、贪心、字符串、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/valid-parenthesis-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md,39.3%,中等,671 -0679,0600-0699,0679. 24 点游戏,24 点游戏,https://leetcode.cn/problems/24-game/,24-game,数组、数学、回溯,https://algo.itcharge.cn/Solutions/0600-0699/24-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0679.%2024%20%E7%82%B9%E6%B8%B8%E6%88%8F.md,53.8%,困难,362 -0680,0600-0699,0680. 验证回文串 II,验证回文串 II,https://leetcode.cn/problems/valid-palindrome-ii/,valid-palindrome-ii,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/0600-0699/valid-palindrome-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0680.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2%20II.md,40.0%,简单,1190 -0681,0600-0699,0681. 最近时刻,最近时刻,https://leetcode.cn/problems/next-closest-time/,next-closest-time,字符串、枚举,https://algo.itcharge.cn/Solutions/0600-0699/next-closest-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0681.%20%E6%9C%80%E8%BF%91%E6%97%B6%E5%88%BB.md,49.9%,中等,92 -0682,0600-0699,0682. 棒球比赛,棒球比赛,https://leetcode.cn/problems/baseball-game/,baseball-game,栈、数组、模拟,https://algo.itcharge.cn/Solutions/0600-0699/baseball-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0682.%20%E6%A3%92%E7%90%83%E6%AF%94%E8%B5%9B.md,71.3%,简单,991 -0683,0600-0699,0683. K 个关闭的灯泡,K 个关闭的灯泡,https://leetcode.cn/problems/k-empty-slots/,k-empty-slots,树状数组、数组、有序集合、滑动窗口,https://algo.itcharge.cn/Solutions/0600-0699/k-empty-slots/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md,45.9%,困难,78 -0684,0600-0699,0684. 冗余连接,冗余连接,https://leetcode.cn/problems/redundant-connection/,redundant-connection,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0600-0699/redundant-connection/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md,67.4%,中等,939 -0685,0600-0699,0685. 冗余连接 II,冗余连接 II,https://leetcode.cn/problems/redundant-connection-ii/,redundant-connection-ii,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0600-0699/redundant-connection-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0685.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5%20II.md,42.2%,困难,356 -0686,0600-0699,0686. 重复叠加字符串匹配,重复叠加字符串匹配,https://leetcode.cn/problems/repeated-string-match/,repeated-string-match,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0600-0699/repeated-string-match/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0686.%20%E9%87%8D%E5%A4%8D%E5%8F%A0%E5%8A%A0%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md,39.8%,中等,642 -0687,0600-0699,0687. 最长同值路径,最长同值路径,https://leetcode.cn/problems/longest-univalue-path/,longest-univalue-path,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0600-0699/longest-univalue-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0687.%20%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84.md,47.7%,中等,671 -0688,0600-0699,0688. 骑士在棋盘上的概率,骑士在棋盘上的概率,https://leetcode.cn/problems/knight-probability-in-chessboard/,knight-probability-in-chessboard,动态规划,https://algo.itcharge.cn/Solutions/0600-0699/knight-probability-in-chessboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0688.%20%E9%AA%91%E5%A3%AB%E5%9C%A8%E6%A3%8B%E7%9B%98%E4%B8%8A%E7%9A%84%E6%A6%82%E7%8E%87.md,58.1%,中等,391 -0689,0600-0699,0689. 三个无重叠子数组的最大和,三个无重叠子数组的最大和,https://leetcode.cn/problems/maximum-sum-of-3-non-overlapping-subarrays/,maximum-sum-of-3-non-overlapping-subarrays,数组、动态规划,https://algo.itcharge.cn/Solutions/0600-0699/maximum-sum-of-3-non-overlapping-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0689.%20%E4%B8%89%E4%B8%AA%E6%97%A0%E9%87%8D%E5%8F%A0%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,56.2%,困难,293 -0690,0600-0699,0690. 员工的重要性,员工的重要性,https://leetcode.cn/problems/employee-importance/,employee-importance,深度优先搜索、广度优先搜索、哈希表,https://algo.itcharge.cn/Solutions/0600-0699/employee-importance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0690.%20%E5%91%98%E5%B7%A5%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7.md,65.7%,中等,668 -0691,0600-0699,0691. 贴纸拼词,贴纸拼词,https://leetcode.cn/problems/stickers-to-spell-word/,stickers-to-spell-word,位运算、数组、字符串、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0600-0699/stickers-to-spell-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0691.%20%E8%B4%B4%E7%BA%B8%E6%8B%BC%E8%AF%8D.md,58.9%,困难,207 -0692,0600-0699,0692. 前K个高频单词,前K个高频单词,https://leetcode.cn/problems/top-k-frequent-words/,top-k-frequent-words,字典树、哈希表、字符串、桶排序、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0600-0699/top-k-frequent-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0692.%20%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%8D%95%E8%AF%8D.md,56.2%,中等,1066 -0693,0600-0699,0693. 交替位二进制数,交替位二进制数,https://leetcode.cn/problems/binary-number-with-alternating-bits/,binary-number-with-alternating-bits,位运算,https://algo.itcharge.cn/Solutions/0600-0699/binary-number-with-alternating-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0693.%20%E4%BA%A4%E6%9B%BF%E4%BD%8D%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0.md,65.1%,简单,840 -0694,0600-0699,0694. 不同岛屿的数量,不同岛屿的数量,https://leetcode.cn/problems/number-of-distinct-islands/,number-of-distinct-islands,深度优先搜索、广度优先搜索、并查集、哈希表、哈希函数,https://algo.itcharge.cn/Solutions/0600-0699/number-of-distinct-islands/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0694.%20%E4%B8%8D%E5%90%8C%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E9%87%8F.md,57.6%,中等,173 -0695,0600-0699,0695. 岛屿的最大面积,岛屿的最大面积,https://leetcode.cn/problems/max-area-of-island/,max-area-of-island,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0600-0699/max-area-of-island/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md,68.0%,中等,2524 -0696,0600-0699,0696. 计数二进制子串,计数二进制子串,https://leetcode.cn/problems/count-binary-substrings/,count-binary-substrings,双指针、字符串,https://algo.itcharge.cn/Solutions/0600-0699/count-binary-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0696.%20%E8%AE%A1%E6%95%B0%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%90%E4%B8%B2.md,63.7%,简单,630 -0697,0600-0699,0697. 数组的度,数组的度,https://leetcode.cn/problems/degree-of-an-array/,degree-of-an-array,数组、哈希表,https://algo.itcharge.cn/Solutions/0600-0699/degree-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0697.%20%E6%95%B0%E7%BB%84%E7%9A%84%E5%BA%A6.md,59.3%,简单,1136 -0698,0600-0699,0698. 划分为k个相等的子集,划分为k个相等的子集,https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/,partition-to-k-equal-sum-subsets,位运算、记忆化搜索、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0600-0699/partition-to-k-equal-sum-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0698.%20%E5%88%92%E5%88%86%E4%B8%BAk%E4%B8%AA%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E9%9B%86.md,42.1%,中等,1398 -0699,0600-0699,0699. 掉落的方块,掉落的方块,https://leetcode.cn/problems/falling-squares/,falling-squares,线段树、数组、有序集合,https://algo.itcharge.cn/Solutions/0600-0699/falling-squares/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0699.%20%E6%8E%89%E8%90%BD%E7%9A%84%E6%96%B9%E5%9D%97.md,54.8%,困难,240 -0700,0700-0799,0700. 二叉搜索树中的搜索,二叉搜索树中的搜索,https://leetcode.cn/problems/search-in-a-binary-search-tree/,search-in-a-binary-search-tree,树、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0700-0799/search-in-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0700.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%90%9C%E7%B4%A2.md,77.6%,简单,1861 -0701,0700-0799,0701. 二叉搜索树中的插入操作,二叉搜索树中的插入操作,https://leetcode.cn/problems/insert-into-a-binary-search-tree/,insert-into-a-binary-search-tree,树、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0700-0799/insert-into-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0701.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%8F%92%E5%85%A5%E6%93%8D%E4%BD%9C.md,70.8%,中等,1364 -0702,0700-0799,0702. 搜索长度未知的有序数组,搜索长度未知的有序数组,https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/,search-in-a-sorted-array-of-unknown-size,数组、二分查找、交互,https://algo.itcharge.cn/Solutions/0700-0799/search-in-a-sorted-array-of-unknown-size/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0702.%20%E6%90%9C%E7%B4%A2%E9%95%BF%E5%BA%A6%E6%9C%AA%E7%9F%A5%E7%9A%84%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md,74.8%,中等,121 -0703,0700-0799,0703. 数据流中的第 K 大元素,数据流中的第 K 大元素,https://leetcode.cn/problems/kth-largest-element-in-a-stream/,kth-largest-element-in-a-stream,树、设计、二叉搜索树、二叉树、数据流、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/kth-largest-element-in-a-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md,52.5%,简单,805 -0704,0700-0799,0704. 二分查找,二分查找,https://leetcode.cn/problems/binary-search/,binary-search,数组、二分查找,https://algo.itcharge.cn/Solutions/0700-0799/binary-search/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md,54.5%,简单,4941 -0705,0700-0799,0705. 设计哈希集合,设计哈希集合,https://leetcode.cn/problems/design-hashset/,design-hashset,设计、数组、哈希表、链表、哈希函数,https://algo.itcharge.cn/Solutions/0700-0799/design-hashset/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0705.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E9%9B%86%E5%90%88.md,63.3%,简单,620 -0706,0700-0799,0706. 设计哈希映射,设计哈希映射,https://leetcode.cn/problems/design-hashmap/,design-hashmap,设计、数组、哈希表、链表、哈希函数,https://algo.itcharge.cn/Solutions/0700-0799/design-hashmap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0706.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E6%98%A0%E5%B0%84.md,63.6%,简单,600 -0707,0700-0799,0707. 设计链表,设计链表,https://leetcode.cn/problems/design-linked-list/,design-linked-list,设计、链表,https://algo.itcharge.cn/Solutions/0700-0799/design-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0707.%20%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.md,34.7%,中等,1405 -0708,0700-0799,0708. 循环有序列表的插入,循环有序列表的插入,https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/,insert-into-a-sorted-circular-linked-list,链表,https://algo.itcharge.cn/Solutions/0700-0799/insert-into-a-sorted-circular-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0708.%20%E5%BE%AA%E7%8E%AF%E6%9C%89%E5%BA%8F%E5%88%97%E8%A1%A8%E7%9A%84%E6%8F%92%E5%85%A5.md,37.7%,中等,98 -0709,0700-0799,0709. 转换成小写字母,转换成小写字母,https://leetcode.cn/problems/to-lower-case/,to-lower-case,字符串,https://algo.itcharge.cn/Solutions/0700-0799/to-lower-case/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0709.%20%E8%BD%AC%E6%8D%A2%E6%88%90%E5%B0%8F%E5%86%99%E5%AD%97%E6%AF%8D.md,76.8%,简单,916 -0710,0700-0799,0710. 黑名单中的随机数,黑名单中的随机数,https://leetcode.cn/problems/random-pick-with-blacklist/,random-pick-with-blacklist,数组、哈希表、数学、二分查找、排序、随机化,https://algo.itcharge.cn/Solutions/0700-0799/random-pick-with-blacklist/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0710.%20%E9%BB%91%E5%90%8D%E5%8D%95%E4%B8%AD%E7%9A%84%E9%9A%8F%E6%9C%BA%E6%95%B0.md,43.7%,困难,436 -0711,0700-0799,0711. 不同岛屿的数量 II,不同岛屿的数量 II,https://leetcode.cn/problems/number-of-distinct-islands-ii/,number-of-distinct-islands-ii,深度优先搜索、广度优先搜索、并查集、哈希表、哈希函数,https://algo.itcharge.cn/Solutions/0700-0799/number-of-distinct-islands-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0711.%20%E4%B8%8D%E5%90%8C%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E9%87%8F%20II.md,56.0%,困难,47 -0712,0700-0799,0712. 两个字符串的最小ASCII删除和,两个字符串的最小ASCII删除和,https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/,minimum-ascii-delete-sum-for-two-strings,字符串、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/minimum-ascii-delete-sum-for-two-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0712.%20%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%8FASCII%E5%88%A0%E9%99%A4%E5%92%8C.md,68.9%,中等,334 -0713,0700-0799,0713. 乘积小于 K 的子数组,乘积小于 K 的子数组,https://leetcode.cn/problems/subarray-product-less-than-k/,subarray-product-less-than-k,数组、滑动窗口,https://algo.itcharge.cn/Solutions/0700-0799/subarray-product-less-than-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0713.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,49.6%,中等,899 -0714,0700-0799,0714. 买卖股票的最佳时机含手续费,买卖股票的最佳时机含手续费,https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/,best-time-to-buy-and-sell-stock-with-transaction-fee,贪心、数组,https://algo.itcharge.cn/Solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0714.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%E5%90%AB%E6%89%8B%E7%BB%AD%E8%B4%B9.md,75.2%,中等,1269 -0715,0700-0799,0715. Range 模块,Range 模块,https://leetcode.cn/problems/range-module/,range-module,设计、线段树、有序集合,https://algo.itcharge.cn/Solutions/0700-0799/range-module/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0715.%20Range%20%E6%A8%A1%E5%9D%97.md,52.9%,困难,207 -0716,0700-0799,0716. 最大栈,最大栈,https://leetcode.cn/problems/max-stack/,max-stack,栈、设计、链表、双向链表、有序集合,https://algo.itcharge.cn/Solutions/0700-0799/max-stack/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0716.%20%E6%9C%80%E5%A4%A7%E6%A0%88.md,44.3%,困难,109 -0717,0700-0799,0717. 1 比特与 2 比特字符,1 比特与 2 比特字符,https://leetcode.cn/problems/1-bit-and-2-bit-characters/,1-bit-and-2-bit-characters,数组,https://algo.itcharge.cn/Solutions/0700-0799/1-bit-and-2-bit-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0717.%201%20%E6%AF%94%E7%89%B9%E4%B8%8E%202%20%E6%AF%94%E7%89%B9%E5%AD%97%E7%AC%A6.md,55.3%,简单,827 -0718,0700-0799,0718. 最长重复子数组,最长重复子数组,https://leetcode.cn/problems/maximum-length-of-repeated-subarray/,maximum-length-of-repeated-subarray,数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/0700-0799/maximum-length-of-repeated-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md,56.9%,中等,1059 -0719,0700-0799,0719. 找出第 K 小的数对距离,找出第 K 小的数对距离,https://leetcode.cn/problems/find-k-th-smallest-pair-distance/,find-k-th-smallest-pair-distance,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0700-0799/find-k-th-smallest-pair-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md,46.7%,困难,273 -0720,0700-0799,0720. 词典中最长的单词,词典中最长的单词,https://leetcode.cn/problems/longest-word-in-dictionary/,longest-word-in-dictionary,字典树、数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0700-0799/longest-word-in-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0720.%20%E8%AF%8D%E5%85%B8%E4%B8%AD%E6%9C%80%E9%95%BF%E7%9A%84%E5%8D%95%E8%AF%8D.md,51.9%,中等,711 -0721,0700-0799,0721. 账户合并,账户合并,https://leetcode.cn/problems/accounts-merge/,accounts-merge,深度优先搜索、广度优先搜索、并查集、数组、字符串,https://algo.itcharge.cn/Solutions/0700-0799/accounts-merge/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0721.%20%E8%B4%A6%E6%88%B7%E5%90%88%E5%B9%B6.md,48.2%,中等,441 -0722,0700-0799,0722. 删除注释,删除注释,https://leetcode.cn/problems/remove-comments/,remove-comments,数组、字符串,https://algo.itcharge.cn/Solutions/0700-0799/remove-comments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0722.%20%E5%88%A0%E9%99%A4%E6%B3%A8%E9%87%8A.md,34.2%,中等,104 -0723,0700-0799,0723. 粉碎糖果,粉碎糖果,https://leetcode.cn/problems/candy-crush/,candy-crush,数组、双指针、矩阵、模拟,https://algo.itcharge.cn/Solutions/0700-0799/candy-crush/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0723.%20%E7%B2%89%E7%A2%8E%E7%B3%96%E6%9E%9C.md,73.9%,中等,61 -0724,0700-0799,0724. 寻找数组的中心下标,寻找数组的中心下标,https://leetcode.cn/problems/find-pivot-index/,find-pivot-index,数组、前缀和,https://algo.itcharge.cn/Solutions/0700-0799/find-pivot-index/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0724.%20%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E5%BF%83%E4%B8%8B%E6%A0%87.md,51.5%,简单,1549 -0725,0700-0799,0725. 分隔链表,分隔链表,https://leetcode.cn/problems/split-linked-list-in-parts/,split-linked-list-in-parts,链表,https://algo.itcharge.cn/Solutions/0700-0799/split-linked-list-in-parts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0725.%20%E5%88%86%E9%9A%94%E9%93%BE%E8%A1%A8.md,60.5%,中等,748 -0726,0700-0799,0726. 原子的数量,原子的数量,https://leetcode.cn/problems/number-of-atoms/,number-of-atoms,栈、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0700-0799/number-of-atoms/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0726.%20%E5%8E%9F%E5%AD%90%E7%9A%84%E6%95%B0%E9%87%8F.md,55.2%,困难,476 -0727,0700-0799,0727. 最小窗口子序列,最小窗口子序列,https://leetcode.cn/problems/minimum-window-subsequence/,minimum-window-subsequence,字符串、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/0700-0799/minimum-window-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0727.%20%E6%9C%80%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%AD%90%E5%BA%8F%E5%88%97.md,42.4%,困难,81 -0728,0700-0799,0728. 自除数,自除数,https://leetcode.cn/problems/self-dividing-numbers/,self-dividing-numbers,数学,https://algo.itcharge.cn/Solutions/0700-0799/self-dividing-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0728.%20%E8%87%AA%E9%99%A4%E6%95%B0.md,77.2%,简单,753 -0729,0700-0799,0729. 我的日程安排表 I,我的日程安排表 I,https://leetcode.cn/problems/my-calendar-i/,my-calendar-i,设计、线段树、二分查找、有序集合,https://algo.itcharge.cn/Solutions/0700-0799/my-calendar-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0729.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20I.md,58.4%,中等,442 -0730,0700-0799,0730. 统计不同回文子序列,统计不同回文子序列,https://leetcode.cn/problems/count-different-palindromic-subsequences/,count-different-palindromic-subsequences,字符串、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/count-different-palindromic-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0730.%20%E7%BB%9F%E8%AE%A1%E4%B8%8D%E5%90%8C%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md,64.2%,困难,127 -0731,0700-0799,0731. 我的日程安排表 II,我的日程安排表 II,https://leetcode.cn/problems/my-calendar-ii/,my-calendar-ii,设计、线段树、二分查找、有序集合,https://algo.itcharge.cn/Solutions/0700-0799/my-calendar-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0731.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20II.md,62.7%,中等,239 -0732,0700-0799,0732. 我的日程安排表 III,我的日程安排表 III,https://leetcode.cn/problems/my-calendar-iii/,my-calendar-iii,设计、线段树、二分查找、有序集合,https://algo.itcharge.cn/Solutions/0700-0799/my-calendar-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0732.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20III.md,71.5%,困难,241 -0733,0700-0799,0733. 图像渲染,图像渲染,https://leetcode.cn/problems/flood-fill/,flood-fill,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/flood-fill/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0733.%20%E5%9B%BE%E5%83%8F%E6%B8%B2%E6%9F%93.md,58.7%,简单,1298 -0734,0700-0799,0734. 句子相似性,句子相似性,https://leetcode.cn/problems/sentence-similarity/,sentence-similarity,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/sentence-similarity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0734.%20%E5%8F%A5%E5%AD%90%E7%9B%B8%E4%BC%BC%E6%80%A7.md,46.7%,简单,76 -0735,0700-0799,0735. 行星碰撞,行星碰撞,https://leetcode.cn/problems/asteroid-collision/,asteroid-collision,栈、数组、模拟,https://algo.itcharge.cn/Solutions/0700-0799/asteroid-collision/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0735.%20%E8%A1%8C%E6%98%9F%E7%A2%B0%E6%92%9E.md,42.9%,中等,827 -0736,0700-0799,0736. Lisp 语法解析,Lisp 语法解析,https://leetcode.cn/problems/parse-lisp-expression/,parse-lisp-expression,栈、递归、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/parse-lisp-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0736.%20Lisp%20%E8%AF%AD%E6%B3%95%E8%A7%A3%E6%9E%90.md,66.4%,困难,156 -0737,0700-0799,0737. 句子相似性 II,句子相似性 II,https://leetcode.cn/problems/sentence-similarity-ii/,sentence-similarity-ii,深度优先搜索、广度优先搜索、并查集、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/sentence-similarity-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0737.%20%E5%8F%A5%E5%AD%90%E7%9B%B8%E4%BC%BC%E6%80%A7%20II.md,48.4%,中等,110 -0738,0700-0799,0738. 单调递增的数字,单调递增的数字,https://leetcode.cn/problems/monotone-increasing-digits/,monotone-increasing-digits,贪心、数学,https://algo.itcharge.cn/Solutions/0700-0799/monotone-increasing-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0738.%20%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E%E7%9A%84%E6%95%B0%E5%AD%97.md,50.3%,中等,957 -0739,0700-0799,0739. 每日温度,每日温度,https://leetcode.cn/problems/daily-temperatures/,daily-temperatures,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/0700-0799/daily-temperatures/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md,68.9%,中等,2682 -0740,0700-0799,0740. 删除并获得点数,删除并获得点数,https://leetcode.cn/problems/delete-and-earn/,delete-and-earn,数组、哈希表、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/delete-and-earn/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0740.%20%E5%88%A0%E9%99%A4%E5%B9%B6%E8%8E%B7%E5%BE%97%E7%82%B9%E6%95%B0.md,62.0%,中等,976 -0741,0700-0799,0741. 摘樱桃,摘樱桃,https://leetcode.cn/problems/cherry-pickup/,cherry-pickup,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/cherry-pickup/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0741.%20%E6%91%98%E6%A8%B1%E6%A1%83.md,50.5%,困难,144 -0742,0700-0799,0742. 二叉树最近的叶节点,二叉树最近的叶节点,https://leetcode.cn/problems/closest-leaf-in-a-binary-tree/,closest-leaf-in-a-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0700-0799/closest-leaf-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0742.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E8%BF%91%E7%9A%84%E5%8F%B6%E8%8A%82%E7%82%B9.md,48.9%,中等,81 -0743,0700-0799,0743. 网络延迟时间,网络延迟时间,https://leetcode.cn/problems/network-delay-time/,network-delay-time,深度优先搜索、广度优先搜索、图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/network-delay-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0743.%20%E7%BD%91%E7%BB%9C%E5%BB%B6%E8%BF%9F%E6%97%B6%E9%97%B4.md,55.6%,中等,874 -0744,0700-0799,0744. 寻找比目标字母大的最小字母,寻找比目标字母大的最小字母,https://leetcode.cn/problems/find-smallest-letter-greater-than-target/,find-smallest-letter-greater-than-target,数组、二分查找,https://algo.itcharge.cn/Solutions/0700-0799/find-smallest-letter-greater-than-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0744.%20%E5%AF%BB%E6%89%BE%E6%AF%94%E7%9B%AE%E6%A0%87%E5%AD%97%E6%AF%8D%E5%A4%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E6%AF%8D.md,48.3%,简单,990 -0745,0700-0799,0745. 前缀和后缀搜索,前缀和后缀搜索,https://leetcode.cn/problems/prefix-and-suffix-search/,prefix-and-suffix-search,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/prefix-and-suffix-search/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0745.%20%E5%89%8D%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E6%90%9C%E7%B4%A2.md,43.9%,困难,256 -0746,0700-0799,0746. 使用最小花费爬楼梯,使用最小花费爬楼梯,https://leetcode.cn/problems/min-cost-climbing-stairs/,min-cost-climbing-stairs,数组、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/min-cost-climbing-stairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0746.%20%E4%BD%BF%E7%94%A8%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9%E7%88%AC%E6%A5%BC%E6%A2%AF.md,64.7%,简单,2409 -0747,0700-0799,0747. 至少是其他数字两倍的最大数,至少是其他数字两倍的最大数,https://leetcode.cn/problems/largest-number-at-least-twice-of-others/,largest-number-at-least-twice-of-others,数组、排序,https://algo.itcharge.cn/Solutions/0700-0799/largest-number-at-least-twice-of-others/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0747.%20%E8%87%B3%E5%B0%91%E6%98%AF%E5%85%B6%E4%BB%96%E6%95%B0%E5%AD%97%E4%B8%A4%E5%80%8D%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0.md,46.3%,简单,936 -0748,0700-0799,0748. 最短补全词,最短补全词,https://leetcode.cn/problems/shortest-completing-word/,shortest-completing-word,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/shortest-completing-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0748.%20%E6%9C%80%E7%9F%AD%E8%A1%A5%E5%85%A8%E8%AF%8D.md,66.7%,简单,572 -0749,0700-0799,0749. 隔离病毒,隔离病毒,https://leetcode.cn/problems/contain-virus/,contain-virus,深度优先搜索、广度优先搜索、数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0700-0799/contain-virus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0749.%20%E9%9A%94%E7%A6%BB%E7%97%85%E6%AF%92.md,69.1%,困难,140 -0750,0700-0799,0750. 角矩形的数量,角矩形的数量,https://leetcode.cn/problems/number-of-corner-rectangles/,number-of-corner-rectangles,数组、数学、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/number-of-corner-rectangles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0750.%20%E8%A7%92%E7%9F%A9%E5%BD%A2%E7%9A%84%E6%95%B0%E9%87%8F.md,73.1%,中等,61 -0751,0700-0799,0751. IP 到 CIDR,IP 到 CIDR,https://leetcode.cn/problems/ip-to-cidr/,ip-to-cidr,位运算、字符串,https://algo.itcharge.cn/Solutions/0700-0799/ip-to-cidr/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0751.%20IP%20%E5%88%B0%20CIDR.md,48.3%,中等,38 -0752,0700-0799,0752. 打开转盘锁,打开转盘锁,https://leetcode.cn/problems/open-the-lock/,open-the-lock,广度优先搜索、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/open-the-lock/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0752.%20%E6%89%93%E5%BC%80%E8%BD%AC%E7%9B%98%E9%94%81.md,52.7%,中等,1214 -0753,0700-0799,0753. 破解保险箱,破解保险箱,https://leetcode.cn/problems/cracking-the-safe/,cracking-the-safe,深度优先搜索、图、欧拉回路,https://algo.itcharge.cn/Solutions/0700-0799/cracking-the-safe/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0753.%20%E7%A0%B4%E8%A7%A3%E4%BF%9D%E9%99%A9%E7%AE%B1.md,74.9%,困难,152 -0754,0700-0799,0754. 到达终点数字,到达终点数字,https://leetcode.cn/problems/reach-a-number/,reach-a-number,数学、二分查找,https://algo.itcharge.cn/Solutions/0700-0799/reach-a-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0754.%20%E5%88%B0%E8%BE%BE%E7%BB%88%E7%82%B9%E6%95%B0%E5%AD%97.md,51.4%,中等,404 -0755,0700-0799,0755. 倒水,倒水,https://leetcode.cn/problems/pour-water/,pour-water,数组、模拟,https://algo.itcharge.cn/Solutions/0700-0799/pour-water/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0755.%20%E5%80%92%E6%B0%B4.md,48.2%,中等,43 -0756,0700-0799,0756. 金字塔转换矩阵,金字塔转换矩阵,https://leetcode.cn/problems/pyramid-transition-matrix/,pyramid-transition-matrix,位运算、深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/0700-0799/pyramid-transition-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0756.%20%E9%87%91%E5%AD%97%E5%A1%94%E8%BD%AC%E6%8D%A2%E7%9F%A9%E9%98%B5.md,52.2%,中等,104 -0757,0700-0799,0757. 设置交集大小至少为2,设置交集大小至少为2,https://leetcode.cn/problems/set-intersection-size-at-least-two/,set-intersection-size-at-least-two,贪心、数组、排序,https://algo.itcharge.cn/Solutions/0700-0799/set-intersection-size-at-least-two/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0757.%20%E8%AE%BE%E7%BD%AE%E4%BA%A4%E9%9B%86%E5%A4%A7%E5%B0%8F%E8%87%B3%E5%B0%91%E4%B8%BA2.md,56.1%,困难,196 -0758,0700-0799,0758. 字符串中的加粗单词,字符串中的加粗单词,https://leetcode.cn/problems/bold-words-in-string/,bold-words-in-string,字典树、数组、哈希表、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0700-0799/bold-words-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0758.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8A%A0%E7%B2%97%E5%8D%95%E8%AF%8D.md,48.3%,中等,85 -0759,0700-0799,0759. 员工空闲时间,员工空闲时间,https://leetcode.cn/problems/employee-free-time/,employee-free-time,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/employee-free-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0759.%20%E5%91%98%E5%B7%A5%E7%A9%BA%E9%97%B2%E6%97%B6%E9%97%B4.md,70.2%,困难,77 -0760,0700-0799,0760. 找出变位映射,找出变位映射,https://leetcode.cn/problems/find-anagram-mappings/,find-anagram-mappings,数组、哈希表,https://algo.itcharge.cn/Solutions/0700-0799/find-anagram-mappings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0760.%20%E6%89%BE%E5%87%BA%E5%8F%98%E4%BD%8D%E6%98%A0%E5%B0%84.md,84.4%,简单,131 -0761,0700-0799,0761. 特殊的二进制序列,特殊的二进制序列,https://leetcode.cn/problems/special-binary-string/,special-binary-string,递归、字符串,https://algo.itcharge.cn/Solutions/0700-0799/special-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0761.%20%E7%89%B9%E6%AE%8A%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%BA%8F%E5%88%97.md,75.2%,困难,150 -0762,0700-0799,0762. 二进制表示中质数个计算置位,二进制表示中质数个计算置位,https://leetcode.cn/problems/prime-number-of-set-bits-in-binary-representation/,prime-number-of-set-bits-in-binary-representation,位运算、数学,https://algo.itcharge.cn/Solutions/0700-0799/prime-number-of-set-bits-in-binary-representation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0762.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA%E4%B8%AD%E8%B4%A8%E6%95%B0%E4%B8%AA%E8%AE%A1%E7%AE%97%E7%BD%AE%E4%BD%8D.md,75.3%,简单,497 -0763,0700-0799,0763. 划分字母区间,划分字母区间,https://leetcode.cn/problems/partition-labels/,partition-labels,贪心、哈希表、双指针、字符串,https://algo.itcharge.cn/Solutions/0700-0799/partition-labels/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0763.%20%E5%88%92%E5%88%86%E5%AD%97%E6%AF%8D%E5%8C%BA%E9%97%B4.md,76.8%,中等,1613 -0764,0700-0799,0764. 最大加号标志,最大加号标志,https://leetcode.cn/problems/largest-plus-sign/,largest-plus-sign,数组、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/largest-plus-sign/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0764.%20%E6%9C%80%E5%A4%A7%E5%8A%A0%E5%8F%B7%E6%A0%87%E5%BF%97.md,54.2%,中等,346 -0765,0700-0799,0765. 情侣牵手,情侣牵手,https://leetcode.cn/problems/couples-holding-hands/,couples-holding-hands,贪心、深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0700-0799/couples-holding-hands/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0765.%20%E6%83%85%E4%BE%A3%E7%89%B5%E6%89%8B.md,65.9%,困难,549 -0766,0700-0799,0766. 托普利茨矩阵,托普利茨矩阵,https://leetcode.cn/problems/toeplitz-matrix/,toeplitz-matrix,数组、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/toeplitz-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0766.%20%E6%89%98%E6%99%AE%E5%88%A9%E8%8C%A8%E7%9F%A9%E9%98%B5.md,70.3%,简单,772 -0767,0700-0799,0767. 重构字符串,重构字符串,https://leetcode.cn/problems/reorganize-string/,reorganize-string,贪心、哈希表、字符串、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/reorganize-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0767.%20%E9%87%8D%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,48.4%,中等,620 -0768,0700-0799,0768. 最多能完成排序的块 II,最多能完成排序的块 II,https://leetcode.cn/problems/max-chunks-to-make-sorted-ii/,max-chunks-to-make-sorted-ii,栈、贪心、数组、排序、单调栈,https://algo.itcharge.cn/Solutions/0700-0799/max-chunks-to-make-sorted-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0768.%20%E6%9C%80%E5%A4%9A%E8%83%BD%E5%AE%8C%E6%88%90%E6%8E%92%E5%BA%8F%E7%9A%84%E5%9D%97%20II.md,58.7%,困难,386 -0769,0700-0799,0769. 最多能完成排序的块,最多能完成排序的块,https://leetcode.cn/problems/max-chunks-to-make-sorted/,max-chunks-to-make-sorted,栈、贪心、数组、排序、单调栈,https://algo.itcharge.cn/Solutions/0700-0799/max-chunks-to-make-sorted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0769.%20%E6%9C%80%E5%A4%9A%E8%83%BD%E5%AE%8C%E6%88%90%E6%8E%92%E5%BA%8F%E7%9A%84%E5%9D%97.md,59.2%,中等,752 -0770,0700-0799,0770. 基本计算器 IV,基本计算器 IV,https://leetcode.cn/problems/basic-calculator-iv/,basic-calculator-iv,栈、递归、哈希表、数学、字符串,https://algo.itcharge.cn/Solutions/0700-0799/basic-calculator-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0770.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20IV.md,56.0%,困难,40 -0771,0700-0799,0771. 宝石与石头,宝石与石头,https://leetcode.cn/problems/jewels-and-stones/,jewels-and-stones,哈希表、字符串,https://algo.itcharge.cn/Solutions/0700-0799/jewels-and-stones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0771.%20%E5%AE%9D%E7%9F%B3%E4%B8%8E%E7%9F%B3%E5%A4%B4.md,85.1%,简单,1437 -0772,0700-0799,0772. 基本计算器 III,基本计算器 III,https://leetcode.cn/problems/basic-calculator-iii/,basic-calculator-iii,栈、递归、数学、字符串,https://algo.itcharge.cn/Solutions/0700-0799/basic-calculator-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0772.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20III.md,53.0%,困难,203 -0773,0700-0799,0773. 滑动谜题,滑动谜题,https://leetcode.cn/problems/sliding-puzzle/,sliding-puzzle,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/sliding-puzzle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0773.%20%E6%BB%91%E5%8A%A8%E8%B0%9C%E9%A2%98.md,70.2%,困难,501 -0774,0700-0799,0774. 最小化去加油站的最大距离,最小化去加油站的最大距离,https://leetcode.cn/problems/minimize-max-distance-to-gas-station/,minimize-max-distance-to-gas-station,数组、二分查找,https://algo.itcharge.cn/Solutions/0700-0799/minimize-max-distance-to-gas-station/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0774.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E5%8E%BB%E5%8A%A0%E6%B2%B9%E7%AB%99%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,63.9%,困难,39 -0775,0700-0799,0775. 全局倒置与局部倒置,全局倒置与局部倒置,https://leetcode.cn/problems/global-and-local-inversions/,global-and-local-inversions,数组、数学,https://algo.itcharge.cn/Solutions/0700-0799/global-and-local-inversions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0775.%20%E5%85%A8%E5%B1%80%E5%80%92%E7%BD%AE%E4%B8%8E%E5%B1%80%E9%83%A8%E5%80%92%E7%BD%AE.md,49.4%,中等,418 -0776,0700-0799,0776. 拆分二叉搜索树,拆分二叉搜索树,https://leetcode.cn/problems/split-bst/,split-bst,树、二叉搜索树、递归、二叉树,https://algo.itcharge.cn/Solutions/0700-0799/split-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0776.%20%E6%8B%86%E5%88%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,59.8%,中等,53 -0777,0700-0799,0777. 在LR字符串中交换相邻字符,在LR字符串中交换相邻字符,https://leetcode.cn/problems/swap-adjacent-in-lr-string/,swap-adjacent-in-lr-string,双指针、字符串,https://algo.itcharge.cn/Solutions/0700-0799/swap-adjacent-in-lr-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0777.%20%E5%9C%A8LR%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E4%BA%A4%E6%8D%A2%E7%9B%B8%E9%82%BB%E5%AD%97%E7%AC%A6.md,38.4%,中等,305 -0778,0700-0799,0778. 水位上升的泳池中游泳,水位上升的泳池中游泳,https://leetcode.cn/problems/swim-in-rising-water/,swim-in-rising-water,深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/swim-in-rising-water/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md,59.0%,困难,436 -0779,0700-0799,0779. 第K个语法符号,第K个语法符号,https://leetcode.cn/problems/k-th-symbol-in-grammar/,k-th-symbol-in-grammar,位运算、递归、数学,https://algo.itcharge.cn/Solutions/0700-0799/k-th-symbol-in-grammar/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0779.%20%E7%AC%ACK%E4%B8%AA%E8%AF%AD%E6%B3%95%E7%AC%A6%E5%8F%B7.md,49.7%,中等,751 -0780,0700-0799,0780. 到达终点,到达终点,https://leetcode.cn/problems/reaching-points/,reaching-points,数学,https://algo.itcharge.cn/Solutions/0700-0799/reaching-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0780.%20%E5%88%B0%E8%BE%BE%E7%BB%88%E7%82%B9.md,37.6%,困难,266 -0781,0700-0799,0781. 森林中的兔子,森林中的兔子,https://leetcode.cn/problems/rabbits-in-forest/,rabbits-in-forest,贪心、数组、哈希表、数学,https://algo.itcharge.cn/Solutions/0700-0799/rabbits-in-forest/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0781.%20%E6%A3%AE%E6%9E%97%E4%B8%AD%E7%9A%84%E5%85%94%E5%AD%90.md,58.9%,中等,795 -0782,0700-0799,0782. 变为棋盘,变为棋盘,https://leetcode.cn/problems/transform-to-chessboard/,transform-to-chessboard,位运算、数组、数学、矩阵,https://algo.itcharge.cn/Solutions/0700-0799/transform-to-chessboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0782.%20%E5%8F%98%E4%B8%BA%E6%A3%8B%E7%9B%98.md,59.7%,困难,149 -0783,0700-0799,0783. 二叉搜索树节点最小距离,二叉搜索树节点最小距离,https://leetcode.cn/problems/minimum-distance-between-bst-nodes/,minimum-distance-between-bst-nodes,树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0700-0799/minimum-distance-between-bst-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0783.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%8A%82%E7%82%B9%E6%9C%80%E5%B0%8F%E8%B7%9D%E7%A6%BB.md,60.1%,简单,830 -0784,0700-0799,0784. 字母大小写全排列,字母大小写全排列,https://leetcode.cn/problems/letter-case-permutation/,letter-case-permutation,位运算、字符串、回溯,https://algo.itcharge.cn/Solutions/0700-0799/letter-case-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0784.%20%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%B0%8F%E5%86%99%E5%85%A8%E6%8E%92%E5%88%97.md,72.6%,中等,1186 -0785,0700-0799,0785. 判断二分图,判断二分图,https://leetcode.cn/problems/is-graph-bipartite/,is-graph-bipartite,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0700-0799/is-graph-bipartite/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md,54.6%,中等,1468 -0786,0700-0799,0786. 第 K 个最小的素数分数,第 K 个最小的素数分数,https://leetcode.cn/problems/k-th-smallest-prime-fraction/,k-th-smallest-prime-fraction,数组、二分查找、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/k-th-smallest-prime-fraction/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0786.%20%E7%AC%AC%20K%20%E4%B8%AA%E6%9C%80%E5%B0%8F%E7%9A%84%E7%B4%A0%E6%95%B0%E5%88%86%E6%95%B0.md,67.6%,中等,318 -0787,0700-0799,0787. K 站中转内最便宜的航班,K 站中转内最便宜的航班,https://leetcode.cn/problems/cheapest-flights-within-k-stops/,cheapest-flights-within-k-stops,深度优先搜索、广度优先搜索、图、动态规划、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/0700-0799/cheapest-flights-within-k-stops/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0787.%20K%20%E7%AB%99%E4%B8%AD%E8%BD%AC%E5%86%85%E6%9C%80%E4%BE%BF%E5%AE%9C%E7%9A%84%E8%88%AA%E7%8F%AD.md,39.7%,中等,667 -0788,0700-0799,0788. 旋转数字,旋转数字,https://leetcode.cn/problems/rotated-digits/,rotated-digits,数学、动态规划,https://algo.itcharge.cn/Solutions/0700-0799/rotated-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0788.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E5%AD%97.md,66.2%,中等,517 -0789,0700-0799,0789. 逃脱阻碍者,逃脱阻碍者,https://leetcode.cn/problems/escape-the-ghosts/,escape-the-ghosts,数组、数学,https://algo.itcharge.cn/Solutions/0700-0799/escape-the-ghosts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0789.%20%E9%80%83%E8%84%B1%E9%98%BB%E7%A2%8D%E8%80%85.md,68.5%,中等,326 -0790,0700-0799,0790. 多米诺和托米诺平铺,多米诺和托米诺平铺,https://leetcode.cn/problems/domino-and-tromino-tiling/,domino-and-tromino-tiling,动态规划,https://algo.itcharge.cn/Solutions/0700-0799/domino-and-tromino-tiling/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0790.%20%E5%A4%9A%E7%B1%B3%E8%AF%BA%E5%92%8C%E6%89%98%E7%B1%B3%E8%AF%BA%E5%B9%B3%E9%93%BA.md,55.8%,中等,284 -0791,0700-0799,0791. 自定义字符串排序,自定义字符串排序,https://leetcode.cn/problems/custom-sort-string/,custom-sort-string,哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0700-0799/custom-sort-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0791.%20%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8E%92%E5%BA%8F.md,74.2%,中等,599 -0792,0700-0799,0792. 匹配子序列的单词数,匹配子序列的单词数,https://leetcode.cn/problems/number-of-matching-subsequences/,number-of-matching-subsequences,字典树、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/0700-0799/number-of-matching-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0792.%20%E5%8C%B9%E9%85%8D%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E5%8D%95%E8%AF%8D%E6%95%B0.md,51.0%,中等,447 -0793,0700-0799,0793. 阶乘函数后 K 个零,阶乘函数后 K 个零,https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/,preimage-size-of-factorial-zeroes-function,数学、二分查找,https://algo.itcharge.cn/Solutions/0700-0799/preimage-size-of-factorial-zeroes-function/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0793.%20%E9%98%B6%E4%B9%98%E5%87%BD%E6%95%B0%E5%90%8E%20K%20%E4%B8%AA%E9%9B%B6.md,48.7%,困难,289 -0794,0700-0799,0794. 有效的井字游戏,有效的井字游戏,https://leetcode.cn/problems/valid-tic-tac-toe-state/,valid-tic-tac-toe-state,数组、字符串,https://algo.itcharge.cn/Solutions/0700-0799/valid-tic-tac-toe-state/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0794.%20%E6%9C%89%E6%95%88%E7%9A%84%E4%BA%95%E5%AD%97%E6%B8%B8%E6%88%8F.md,38.6%,中等,497 -0795,0700-0799,0795. 区间子数组个数,区间子数组个数,https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/,number-of-subarrays-with-bounded-maximum,数组、双指针,https://algo.itcharge.cn/Solutions/0700-0799/number-of-subarrays-with-bounded-maximum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0795.%20%E5%8C%BA%E9%97%B4%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md,57.8%,中等,351 -0796,0700-0799,0796. 旋转字符串,旋转字符串,https://leetcode.cn/problems/rotate-string/,rotate-string,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/0700-0799/rotate-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0796.%20%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md,63.3%,简单,899 -0797,0700-0799,0797. 所有可能的路径,所有可能的路径,https://leetcode.cn/problems/all-paths-from-source-to-target/,all-paths-from-source-to-target,深度优先搜索、广度优先搜索、图、回溯,https://algo.itcharge.cn/Solutions/0700-0799/all-paths-from-source-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md,78.9%,中等,1854 -0798,0700-0799,0798. 得分最高的最小轮调,得分最高的最小轮调,https://leetcode.cn/problems/smallest-rotation-with-highest-score/,smallest-rotation-with-highest-score,数组、前缀和,https://algo.itcharge.cn/Solutions/0700-0799/smallest-rotation-with-highest-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0798.%20%E5%BE%97%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E6%9C%80%E5%B0%8F%E8%BD%AE%E8%B0%83.md,61.6%,困难,226 -0799,0700-0799,0799. 香槟塔,香槟塔,https://leetcode.cn/problems/champagne-tower/,champagne-tower,动态规划,https://algo.itcharge.cn/Solutions/0700-0799/champagne-tower/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0799.%20%E9%A6%99%E6%A7%9F%E5%A1%94.md,53.0%,中等,311 -0800,0800-0899,0800. 相似 RGB 颜色,相似 RGB 颜色,https://leetcode.cn/problems/similar-rgb-color/,similar-rgb-color,数学、字符串、枚举,https://algo.itcharge.cn/Solutions/0800-0899/similar-rgb-color/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0800.%20%E7%9B%B8%E4%BC%BC%20RGB%20%E9%A2%9C%E8%89%B2.md,70.2%,简单,56 -0801,0800-0899,0801. 使序列递增的最小交换次数,使序列递增的最小交换次数,https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/,minimum-swaps-to-make-sequences-increasing,数组、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/minimum-swaps-to-make-sequences-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0801.%20%E4%BD%BF%E5%BA%8F%E5%88%97%E9%80%92%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,50.6%,困难,316 -0802,0800-0899,0802. 找到最终的安全状态,找到最终的安全状态,https://leetcode.cn/problems/find-eventual-safe-states/,find-eventual-safe-states,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/0800-0899/find-eventual-safe-states/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md,59.2%,中等,465 -0803,0800-0899,0803. 打砖块,打砖块,https://leetcode.cn/problems/bricks-falling-when-hit/,bricks-falling-when-hit,并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/bricks-falling-when-hit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0803.%20%E6%89%93%E7%A0%96%E5%9D%97.md,47.0%,困难,153 -0804,0800-0899,0804. 唯一摩尔斯密码词,唯一摩尔斯密码词,https://leetcode.cn/problems/unique-morse-code-words/,unique-morse-code-words,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/unique-morse-code-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0804.%20%E5%94%AF%E4%B8%80%E6%91%A9%E5%B0%94%E6%96%AF%E5%AF%86%E7%A0%81%E8%AF%8D.md,82.2%,简单,842 -0805,0800-0899,0805. 数组的均值分割,数组的均值分割,https://leetcode.cn/problems/split-array-with-same-average/,split-array-with-same-average,位运算、数组、数学、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/0800-0899/split-array-with-same-average/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0805.%20%E6%95%B0%E7%BB%84%E7%9A%84%E5%9D%87%E5%80%BC%E5%88%86%E5%89%B2.md,43.0%,困难,190 -0806,0800-0899,0806. 写字符串需要的行数,写字符串需要的行数,https://leetcode.cn/problems/number-of-lines-to-write-string/,number-of-lines-to-write-string,数组、字符串,https://algo.itcharge.cn/Solutions/0800-0899/number-of-lines-to-write-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0806.%20%E5%86%99%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%9C%80%E8%A6%81%E7%9A%84%E8%A1%8C%E6%95%B0.md,68.4%,简单,642 -0807,0800-0899,0807. 保持城市天际线,保持城市天际线,https://leetcode.cn/problems/max-increase-to-keep-city-skyline/,max-increase-to-keep-city-skyline,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/max-increase-to-keep-city-skyline/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0807.%20%E4%BF%9D%E6%8C%81%E5%9F%8E%E5%B8%82%E5%A4%A9%E9%99%85%E7%BA%BF.md,88.1%,中等,759 -0808,0800-0899,0808. 分汤,分汤,https://leetcode.cn/problems/soup-servings/,soup-servings,数学、动态规划、概率与统计,https://algo.itcharge.cn/Solutions/0800-0899/soup-servings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0808.%20%E5%88%86%E6%B1%A4.md,58.8%,中等,166 -0809,0800-0899,0809. 情感丰富的文字,情感丰富的文字,https://leetcode.cn/problems/expressive-words/,expressive-words,数组、双指针、字符串,https://algo.itcharge.cn/Solutions/0800-0899/expressive-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0809.%20%E6%83%85%E6%84%9F%E4%B8%B0%E5%AF%8C%E7%9A%84%E6%96%87%E5%AD%97.md,48.9%,中等,370 -0810,0800-0899,0810. 黑板异或游戏,黑板异或游戏,https://leetcode.cn/problems/chalkboard-xor-game/,chalkboard-xor-game,位运算、脑筋急转弯、数组、数学、博弈,https://algo.itcharge.cn/Solutions/0800-0899/chalkboard-xor-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0810.%20%E9%BB%91%E6%9D%BF%E5%BC%82%E6%88%96%E6%B8%B8%E6%88%8F.md,72.5%,困难,154 -0811,0800-0899,0811. 子域名访问计数,子域名访问计数,https://leetcode.cn/problems/subdomain-visit-count/,subdomain-visit-count,数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/0800-0899/subdomain-visit-count/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0811.%20%E5%AD%90%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E8%AE%A1%E6%95%B0.md,76.7%,中等,565 -0812,0800-0899,0812. 最大三角形面积,最大三角形面积,https://leetcode.cn/problems/largest-triangle-area/,largest-triangle-area,几何、数组、数学,https://algo.itcharge.cn/Solutions/0800-0899/largest-triangle-area/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0812.%20%E6%9C%80%E5%A4%A7%E4%B8%89%E8%A7%92%E5%BD%A2%E9%9D%A2%E7%A7%AF.md,68.2%,简单,325 -0813,0800-0899,0813. 最大平均值和的分组,最大平均值和的分组,https://leetcode.cn/problems/largest-sum-of-averages/,largest-sum-of-averages,数组、动态规划、前缀和,https://algo.itcharge.cn/Solutions/0800-0899/largest-sum-of-averages/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0813.%20%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E5%80%BC%E5%92%8C%E7%9A%84%E5%88%86%E7%BB%84.md,61.7%,中等,270 -0814,0800-0899,0814. 二叉树剪枝,二叉树剪枝,https://leetcode.cn/problems/binary-tree-pruning/,binary-tree-pruning,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/binary-tree-pruning/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0814.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D.md,72.6%,中等,796 -0815,0800-0899,0815. 公交路线,公交路线,https://leetcode.cn/problems/bus-routes/,bus-routes,广度优先搜索、数组、哈希表,https://algo.itcharge.cn/Solutions/0800-0899/bus-routes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0815.%20%E5%85%AC%E4%BA%A4%E8%B7%AF%E7%BA%BF.md,44.4%,困难,412 -0816,0800-0899,0816. 模糊坐标,模糊坐标,https://leetcode.cn/problems/ambiguous-coordinates/,ambiguous-coordinates,字符串、回溯,https://algo.itcharge.cn/Solutions/0800-0899/ambiguous-coordinates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0816.%20%E6%A8%A1%E7%B3%8A%E5%9D%90%E6%A0%87.md,62.6%,中等,391 -0817,0800-0899,0817. 链表组件,链表组件,https://leetcode.cn/problems/linked-list-components/,linked-list-components,数组、哈希表、链表,https://algo.itcharge.cn/Solutions/0800-0899/linked-list-components/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0817.%20%E9%93%BE%E8%A1%A8%E7%BB%84%E4%BB%B6.md,61.4%,中等,586 -0818,0800-0899,0818. 赛车,赛车,https://leetcode.cn/problems/race-car/,race-car,动态规划,https://algo.itcharge.cn/Solutions/0800-0899/race-car/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0818.%20%E8%B5%9B%E8%BD%A6.md,44.9%,困难,69 -0819,0800-0899,0819. 最常见的单词,最常见的单词,https://leetcode.cn/problems/most-common-word/,most-common-word,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/0800-0899/most-common-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0819.%20%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84%E5%8D%95%E8%AF%8D.md,45.6%,简单,728 -0820,0800-0899,0820. 单词的压缩编码,单词的压缩编码,https://leetcode.cn/problems/short-encoding-of-words/,short-encoding-of-words,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/short-encoding-of-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0820.%20%E5%8D%95%E8%AF%8D%E7%9A%84%E5%8E%8B%E7%BC%A9%E7%BC%96%E7%A0%81.md,52.1%,中等,736 -0821,0800-0899,0821. 字符的最短距离,字符的最短距离,https://leetcode.cn/problems/shortest-distance-to-a-character/,shortest-distance-to-a-character,数组、双指针、字符串,https://algo.itcharge.cn/Solutions/0800-0899/shortest-distance-to-a-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0821.%20%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%9D%E7%A6%BB.md,72.9%,简单,997 -0822,0800-0899,0822. 翻转卡片游戏,翻转卡片游戏,https://leetcode.cn/problems/card-flipping-game/,card-flipping-game,数组、哈希表,https://algo.itcharge.cn/Solutions/0800-0899/card-flipping-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0822.%20%E7%BF%BB%E8%BD%AC%E5%8D%A1%E7%89%87%E6%B8%B8%E6%88%8F.md,51.6%,中等,68 -0823,0800-0899,0823. 带因子的二叉树,带因子的二叉树,https://leetcode.cn/problems/binary-trees-with-factors/,binary-trees-with-factors,数组、哈希表、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/binary-trees-with-factors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0823.%20%E5%B8%A6%E5%9B%A0%E5%AD%90%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md,43.5%,中等,82 -0824,0800-0899,0824. 山羊拉丁文,山羊拉丁文,https://leetcode.cn/problems/goat-latin/,goat-latin,字符串,https://algo.itcharge.cn/Solutions/0800-0899/goat-latin/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0824.%20%E5%B1%B1%E7%BE%8A%E6%8B%89%E4%B8%81%E6%96%87.md,65.1%,简单,747 -0825,0800-0899,0825. 适龄的朋友,适龄的朋友,https://leetcode.cn/problems/friends-of-appropriate-ages/,friends-of-appropriate-ages,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0800-0899/friends-of-appropriate-ages/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0825.%20%E9%80%82%E9%BE%84%E7%9A%84%E6%9C%8B%E5%8F%8B.md,45.4%,中等,361 -0826,0800-0899,0826. 安排工作以达到最大收益,安排工作以达到最大收益,https://leetcode.cn/problems/most-profit-assigning-work/,most-profit-assigning-work,贪心、数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/0800-0899/most-profit-assigning-work/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0826.%20%E5%AE%89%E6%8E%92%E5%B7%A5%E4%BD%9C%E4%BB%A5%E8%BE%BE%E5%88%B0%E6%9C%80%E5%A4%A7%E6%94%B6%E7%9B%8A.md,42.6%,中等,228 -0827,0800-0899,0827. 最大人工岛,最大人工岛,https://leetcode.cn/problems/making-a-large-island/,making-a-large-island,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/making-a-large-island/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0827.%20%E6%9C%80%E5%A4%A7%E4%BA%BA%E5%B7%A5%E5%B2%9B.md,47.1%,困难,538 -0828,0800-0899,0828. 统计子串中的唯一字符,统计子串中的唯一字符,https://leetcode.cn/problems/count-unique-characters-of-all-substrings-of-a-given-string/,count-unique-characters-of-all-substrings-of-a-given-string,哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/count-unique-characters-of-all-substrings-of-a-given-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0828.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%94%AF%E4%B8%80%E5%AD%97%E7%AC%A6.md,65.6%,困难,310 -0829,0800-0899,0829. 连续整数求和,连续整数求和,https://leetcode.cn/problems/consecutive-numbers-sum/,consecutive-numbers-sum,数学、枚举,https://algo.itcharge.cn/Solutions/0800-0899/consecutive-numbers-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0829.%20%E8%BF%9E%E7%BB%AD%E6%95%B4%E6%95%B0%E6%B1%82%E5%92%8C.md,46.3%,困难,377 -0830,0800-0899,0830. 较大分组的位置,较大分组的位置,https://leetcode.cn/problems/positions-of-large-groups/,positions-of-large-groups,字符串,https://algo.itcharge.cn/Solutions/0800-0899/positions-of-large-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0830.%20%E8%BE%83%E5%A4%A7%E5%88%86%E7%BB%84%E7%9A%84%E4%BD%8D%E7%BD%AE.md,54.1%,简单,699 -0831,0800-0899,0831. 隐藏个人信息,隐藏个人信息,https://leetcode.cn/problems/masking-personal-information/,masking-personal-information,字符串,https://algo.itcharge.cn/Solutions/0800-0899/masking-personal-information/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0831.%20%E9%9A%90%E8%97%8F%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF.md,53.8%,中等,276 -0832,0800-0899,0832. 翻转图像,翻转图像,https://leetcode.cn/problems/flipping-an-image/,flipping-an-image,数组、双指针、矩阵、模拟,https://algo.itcharge.cn/Solutions/0800-0899/flipping-an-image/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0832.%20%E7%BF%BB%E8%BD%AC%E5%9B%BE%E5%83%8F.md,79.5%,简单,1123 -0833,0800-0899,0833. 字符串中的查找与替换,字符串中的查找与替换,https://leetcode.cn/problems/find-and-replace-in-string/,find-and-replace-in-string,数组、字符串、排序,https://algo.itcharge.cn/Solutions/0800-0899/find-and-replace-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0833.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE%E4%B8%8E%E6%9B%BF%E6%8D%A2.md,44.0%,中等,135 -0834,0800-0899,0834. 树中距离之和,树中距离之和,https://leetcode.cn/problems/sum-of-distances-in-tree/,sum-of-distances-in-tree,树、深度优先搜索、图、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/sum-of-distances-in-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0834.%20%E6%A0%91%E4%B8%AD%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C.md,54.4%,困难,179 -0835,0800-0899,0835. 图像重叠,图像重叠,https://leetcode.cn/problems/image-overlap/,image-overlap,数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/image-overlap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0835.%20%E5%9B%BE%E5%83%8F%E9%87%8D%E5%8F%A0.md,58.2%,中等,73 -0836,0800-0899,0836. 矩形重叠,矩形重叠,https://leetcode.cn/problems/rectangle-overlap/,rectangle-overlap,几何、数学,https://algo.itcharge.cn/Solutions/0800-0899/rectangle-overlap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0836.%20%E7%9F%A9%E5%BD%A2%E9%87%8D%E5%8F%A0.md,49.0%,简单,776 -0837,0800-0899,0837. 新 21 点,新 21 点,https://leetcode.cn/problems/new-21-game/,new-21-game,数学、动态规划、滑动窗口、概率与统计,https://algo.itcharge.cn/Solutions/0800-0899/new-21-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0837.%20%E6%96%B0%2021%20%E7%82%B9.md,39.8%,中等,273 -0838,0800-0899,0838. 推多米诺,推多米诺,https://leetcode.cn/problems/push-dominoes/,push-dominoes,双指针、字符串、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/push-dominoes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0838.%20%E6%8E%A8%E5%A4%9A%E7%B1%B3%E8%AF%BA.md,55.6%,中等,559 -0839,0800-0899,0839. 相似字符串组,相似字符串组,https://leetcode.cn/problems/similar-string-groups/,similar-string-groups,深度优先搜索、广度优先搜索、并查集、数组、字符串,https://algo.itcharge.cn/Solutions/0800-0899/similar-string-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0839.%20%E7%9B%B8%E4%BC%BC%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%BB%84.md,58.8%,困难,332 -0840,0800-0899,0840. 矩阵中的幻方,矩阵中的幻方,https://leetcode.cn/problems/magic-squares-in-grid/,magic-squares-in-grid,数组、数学、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/magic-squares-in-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0840.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E5%B9%BB%E6%96%B9.md,36.6%,中等,148 -0841,0800-0899,0841. 钥匙和房间,钥匙和房间,https://leetcode.cn/problems/keys-and-rooms/,keys-and-rooms,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/0800-0899/keys-and-rooms/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0841.%20%E9%92%A5%E5%8C%99%E5%92%8C%E6%88%BF%E9%97%B4.md,67.9%,中等,812 -0842,0800-0899,0842. 将数组拆分成斐波那契序列,将数组拆分成斐波那契序列,https://leetcode.cn/problems/split-array-into-fibonacci-sequence/,split-array-into-fibonacci-sequence,字符串、回溯,https://algo.itcharge.cn/Solutions/0800-0899/split-array-into-fibonacci-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0842.%20%E5%B0%86%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86%E6%88%90%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E5%BA%8F%E5%88%97.md,48.3%,中等,381 -0843,0800-0899,0843. 猜猜这个单词,猜猜这个单词,https://leetcode.cn/problems/guess-the-word/,guess-the-word,数组、数学、字符串、博弈、交互,https://algo.itcharge.cn/Solutions/0800-0899/guess-the-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0843.%20%E7%8C%9C%E7%8C%9C%E8%BF%99%E4%B8%AA%E5%8D%95%E8%AF%8D.md,36.7%,困难,65 -0844,0800-0899,0844. 比较含退格的字符串,比较含退格的字符串,https://leetcode.cn/problems/backspace-string-compare/,backspace-string-compare,栈、双指针、字符串、模拟,https://algo.itcharge.cn/Solutions/0800-0899/backspace-string-compare/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0844.%20%E6%AF%94%E8%BE%83%E5%90%AB%E9%80%80%E6%A0%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,47.9%,简单,1897 -0845,0800-0899,0845. 数组中的最长山脉,数组中的最长山脉,https://leetcode.cn/problems/longest-mountain-in-array/,longest-mountain-in-array,数组、双指针、动态规划、枚举,https://algo.itcharge.cn/Solutions/0800-0899/longest-mountain-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0845.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E5%B1%B1%E8%84%89.md,42.3%,中等,712 -0846,0800-0899,0846. 一手顺子,一手顺子,https://leetcode.cn/problems/hand-of-straights/,hand-of-straights,贪心、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/0800-0899/hand-of-straights/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0846.%20%E4%B8%80%E6%89%8B%E9%A1%BA%E5%AD%90.md,57.2%,中等,537 -0847,0800-0899,0847. 访问所有节点的最短路径,访问所有节点的最短路径,https://leetcode.cn/problems/shortest-path-visiting-all-nodes/,shortest-path-visiting-all-nodes,位运算、广度优先搜索、图、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/0800-0899/shortest-path-visiting-all-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0847.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,67.6%,困难,231 -0848,0800-0899,0848. 字母移位,字母移位,https://leetcode.cn/problems/shifting-letters/,shifting-letters,数组、字符串,https://algo.itcharge.cn/Solutions/0800-0899/shifting-letters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0848.%20%E5%AD%97%E6%AF%8D%E7%A7%BB%E4%BD%8D.md,46.1%,中等,151 -0849,0800-0899,0849. 到最近的人的最大距离,到最近的人的最大距离,https://leetcode.cn/problems/maximize-distance-to-closest-person/,maximize-distance-to-closest-person,数组,https://algo.itcharge.cn/Solutions/0800-0899/maximize-distance-to-closest-person/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0849.%20%E5%88%B0%E6%9C%80%E8%BF%91%E7%9A%84%E4%BA%BA%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,44.2%,中等,267 -0850,0800-0899,0850. 矩形面积 II,矩形面积 II,https://leetcode.cn/problems/rectangle-area-ii/,rectangle-area-ii,线段树、数组、有序集合、扫描线,https://algo.itcharge.cn/Solutions/0800-0899/rectangle-area-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md,62.9%,困难,179 -0851,0800-0899,0851. 喧闹和富有,喧闹和富有,https://leetcode.cn/problems/loud-and-rich/,loud-and-rich,深度优先搜索、图、拓扑排序、数组,https://algo.itcharge.cn/Solutions/0800-0899/loud-and-rich/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md,63.1%,中等,331 -0852,0800-0899,0852. 山脉数组的峰顶索引,山脉数组的峰顶索引,https://leetcode.cn/problems/peak-index-in-a-mountain-array/,peak-index-in-a-mountain-array,数组、二分查找,https://algo.itcharge.cn/Solutions/0800-0899/peak-index-in-a-mountain-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0852.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E7%9A%84%E5%B3%B0%E9%A1%B6%E7%B4%A2%E5%BC%95.md,68.7%,中等,1144 -0853,0800-0899,0853. 车队,车队,https://leetcode.cn/problems/car-fleet/,car-fleet,栈、数组、排序、单调栈,https://algo.itcharge.cn/Solutions/0800-0899/car-fleet/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0853.%20%E8%BD%A6%E9%98%9F.md,41.3%,中等,205 -0854,0800-0899,0854. 相似度为 K 的字符串,相似度为 K 的字符串,https://leetcode.cn/problems/k-similar-strings/,k-similar-strings,广度优先搜索、字符串,https://algo.itcharge.cn/Solutions/0800-0899/k-similar-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0854.%20%E7%9B%B8%E4%BC%BC%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,46.8%,困难,248 -0855,0800-0899,0855. 考场就座,考场就座,https://leetcode.cn/problems/exam-room/,exam-room,设计、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/0800-0899/exam-room/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0855.%20%E8%80%83%E5%9C%BA%E5%B0%B1%E5%BA%A7.md,47.7%,中等,214 -0856,0800-0899,0856. 括号的分数,括号的分数,https://leetcode.cn/problems/score-of-parentheses/,score-of-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/0800-0899/score-of-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0856.%20%E6%8B%AC%E5%8F%B7%E7%9A%84%E5%88%86%E6%95%B0.md,68.4%,中等,708 -0857,0800-0899,0857. 雇佣 K 名工人的最低成本,雇佣 K 名工人的最低成本,https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/,minimum-cost-to-hire-k-workers,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0800-0899/minimum-cost-to-hire-k-workers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0857.%20%E9%9B%87%E4%BD%A3%20K%20%E5%90%8D%E5%B7%A5%E4%BA%BA%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md,63.6%,困难,216 -0858,0800-0899,0858. 镜面反射,镜面反射,https://leetcode.cn/problems/mirror-reflection/,mirror-reflection,几何、数学、数论,https://algo.itcharge.cn/Solutions/0800-0899/mirror-reflection/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0858.%20%E9%95%9C%E9%9D%A2%E5%8F%8D%E5%B0%84.md,57.5%,中等,80 -0859,0800-0899,0859. 亲密字符串,亲密字符串,https://leetcode.cn/problems/buddy-strings/,buddy-strings,哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/buddy-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0859.%20%E4%BA%B2%E5%AF%86%E5%AD%97%E7%AC%A6%E4%B8%B2.md,34.5%,简单,889 -0860,0800-0899,0860. 柠檬水找零,柠檬水找零,https://leetcode.cn/problems/lemonade-change/,lemonade-change,贪心、数组,https://algo.itcharge.cn/Solutions/0800-0899/lemonade-change/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0860.%20%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.md,58.6%,简单,1307 -0861,0800-0899,0861. 翻转矩阵后的得分,翻转矩阵后的得分,https://leetcode.cn/problems/score-after-flipping-matrix/,score-after-flipping-matrix,贪心、位运算、数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/score-after-flipping-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0861.%20%E7%BF%BB%E8%BD%AC%E7%9F%A9%E9%98%B5%E5%90%8E%E7%9A%84%E5%BE%97%E5%88%86.md,80.8%,中等,520 -0862,0800-0899,0862. 和至少为 K 的最短子数组,和至少为 K 的最短子数组,https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/,shortest-subarray-with-sum-at-least-k,队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/0800-0899/shortest-subarray-with-sum-at-least-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0862.%20%E5%92%8C%E8%87%B3%E5%B0%91%E4%B8%BA%20K%20%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%90%E6%95%B0%E7%BB%84.md,26.5%,困难,383 -0863,0800-0899,0863. 二叉树中所有距离为 K 的结点,二叉树中所有距离为 K 的结点,https://leetcode.cn/problems/all-nodes-distance-k-in-binary-tree/,all-nodes-distance-k-in-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/all-nodes-distance-k-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0863.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E6%89%80%E6%9C%89%E8%B7%9D%E7%A6%BB%E4%B8%BA%20K%20%E7%9A%84%E7%BB%93%E7%82%B9.md,61.4%,中等,756 -0864,0800-0899,0864. 获取所有钥匙的最短路径,获取所有钥匙的最短路径,https://leetcode.cn/problems/shortest-path-to-get-all-keys/,shortest-path-to-get-all-keys,位运算、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/shortest-path-to-get-all-keys/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0864.%20%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%92%A5%E5%8C%99%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,59.5%,困难,245 -0865,0800-0899,0865. 具有所有最深节点的最小子树,具有所有最深节点的最小子树,https://leetcode.cn/problems/smallest-subtree-with-all-the-deepest-nodes/,smallest-subtree-with-all-the-deepest-nodes,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/smallest-subtree-with-all-the-deepest-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0865.%20%E5%85%B7%E6%9C%89%E6%89%80%E6%9C%89%E6%9C%80%E6%B7%B1%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E6%A0%91.md,68.9%,中等,225 -0866,0800-0899,0866. 回文素数,回文素数,https://leetcode.cn/problems/prime-palindrome/,prime-palindrome,数学,https://algo.itcharge.cn/Solutions/0800-0899/prime-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0866.%20%E5%9B%9E%E6%96%87%E7%B4%A0%E6%95%B0.md,23.8%,中等,128 -0867,0800-0899,0867. 转置矩阵,转置矩阵,https://leetcode.cn/problems/transpose-matrix/,transpose-matrix,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0800-0899/transpose-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0867.%20%E8%BD%AC%E7%BD%AE%E7%9F%A9%E9%98%B5.md,66.8%,简单,871 -0868,0800-0899,0868. 二进制间距,二进制间距,https://leetcode.cn/problems/binary-gap/,binary-gap,位运算,https://algo.itcharge.cn/Solutions/0800-0899/binary-gap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0868.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%97%B4%E8%B7%9D.md,69.4%,简单,693 -0869,0800-0899,0869. 重新排序得到 2 的幂,重新排序得到 2 的幂,https://leetcode.cn/problems/reordered-power-of-2/,reordered-power-of-2,数学、计数、枚举、排序,https://algo.itcharge.cn/Solutions/0800-0899/reordered-power-of-2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0869.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%BA%8F%E5%BE%97%E5%88%B0%202%20%E7%9A%84%E5%B9%82.md,63.6%,中等,532 -0870,0800-0899,0870. 优势洗牌,优势洗牌,https://leetcode.cn/problems/advantage-shuffle/,advantage-shuffle,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/0800-0899/advantage-shuffle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0870.%20%E4%BC%98%E5%8A%BF%E6%B4%97%E7%89%8C.md,50.4%,中等,756 -0871,0800-0899,0871. 最低加油次数,最低加油次数,https://leetcode.cn/problems/minimum-number-of-refueling-stops/,minimum-number-of-refueling-stops,贪心、数组、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/0800-0899/minimum-number-of-refueling-stops/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0871.%20%E6%9C%80%E4%BD%8E%E5%8A%A0%E6%B2%B9%E6%AC%A1%E6%95%B0.md,43.2%,困难,367 -0872,0800-0899,0872. 叶子相似的树,叶子相似的树,https://leetcode.cn/problems/leaf-similar-trees/,leaf-similar-trees,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/leaf-similar-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0872.%20%E5%8F%B6%E5%AD%90%E7%9B%B8%E4%BC%BC%E7%9A%84%E6%A0%91.md,65.0%,简单,883 -0873,0800-0899,0873. 最长的斐波那契子序列的长度,最长的斐波那契子序列的长度,https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/,length-of-longest-fibonacci-subsequence,数组、哈希表、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/length-of-longest-fibonacci-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0873.%20%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6.md,56.3%,中等,381 -0874,0800-0899,0874. 模拟行走机器人,模拟行走机器人,https://leetcode.cn/problems/walking-robot-simulation/,walking-robot-simulation,数组、模拟,https://algo.itcharge.cn/Solutions/0800-0899/walking-robot-simulation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0874.%20%E6%A8%A1%E6%8B%9F%E8%A1%8C%E8%B5%B0%E6%9C%BA%E5%99%A8%E4%BA%BA.md,43.1%,中等,341 -0875,0800-0899,0875. 爱吃香蕉的珂珂,爱吃香蕉的珂珂,https://leetcode.cn/problems/koko-eating-bananas/,koko-eating-bananas,数组、二分查找,https://algo.itcharge.cn/Solutions/0800-0899/koko-eating-bananas/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0875.%20%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82.md,49.5%,中等,971 -0876,0800-0899,0876. 链表的中间结点,链表的中间结点,https://leetcode.cn/problems/middle-of-the-linked-list/,middle-of-the-linked-list,链表、双指针,https://algo.itcharge.cn/Solutions/0800-0899/middle-of-the-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0876.%20%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E7%BB%93%E7%82%B9.md,70.2%,简单,3542 -0877,0800-0899,0877. 石子游戏,石子游戏,https://leetcode.cn/problems/stone-game/,stone-game,数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/0800-0899/stone-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md,76.5%,中等,664 -0878,0800-0899,0878. 第 N 个神奇数字,第 N 个神奇数字,https://leetcode.cn/problems/nth-magical-number/,nth-magical-number,数学、二分查找,https://algo.itcharge.cn/Solutions/0800-0899/nth-magical-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0878.%20%E7%AC%AC%20N%20%E4%B8%AA%E7%A5%9E%E5%A5%87%E6%95%B0%E5%AD%97.md,40.0%,困难,226 -0879,0800-0899,0879. 盈利计划,盈利计划,https://leetcode.cn/problems/profitable-schemes/,profitable-schemes,数组、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/profitable-schemes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0879.%20%E7%9B%88%E5%88%A9%E8%AE%A1%E5%88%92.md,54.7%,困难,233 -0880,0800-0899,0880. 索引处的解码字符串,索引处的解码字符串,https://leetcode.cn/problems/decoded-string-at-index/,decoded-string-at-index,栈、字符串,https://algo.itcharge.cn/Solutions/0800-0899/decoded-string-at-index/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0880.%20%E7%B4%A2%E5%BC%95%E5%A4%84%E7%9A%84%E8%A7%A3%E7%A0%81%E5%AD%97%E7%AC%A6%E4%B8%B2.md,26.9%,中等,113 -0881,0800-0899,0881. 救生艇,救生艇,https://leetcode.cn/problems/boats-to-save-people/,boats-to-save-people,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/0800-0899/boats-to-save-people/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0881.%20%E6%95%91%E7%94%9F%E8%89%87.md,53.7%,中等,754 -0882,0800-0899,0882. 细分图中的可到达节点,细分图中的可到达节点,https://leetcode.cn/problems/reachable-nodes-in-subdivided-graph/,reachable-nodes-in-subdivided-graph,图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/0800-0899/reachable-nodes-in-subdivided-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0882.%20%E7%BB%86%E5%88%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%8F%AF%E5%88%B0%E8%BE%BE%E8%8A%82%E7%82%B9.md,64.1%,困难,141 -0883,0800-0899,0883. 三维形体投影面积,三维形体投影面积,https://leetcode.cn/problems/projection-area-of-3d-shapes/,projection-area-of-3d-shapes,几何、数组、数学、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/projection-area-of-3d-shapes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0883.%20%E4%B8%89%E7%BB%B4%E5%BD%A2%E4%BD%93%E6%8A%95%E5%BD%B1%E9%9D%A2%E7%A7%AF.md,76.4%,简单,566 -0884,0800-0899,0884. 两句话中的不常见单词,两句话中的不常见单词,https://leetcode.cn/problems/uncommon-words-from-two-sentences/,uncommon-words-from-two-sentences,哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/uncommon-words-from-two-sentences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0884.%20%E4%B8%A4%E5%8F%A5%E8%AF%9D%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%B8%B8%E8%A7%81%E5%8D%95%E8%AF%8D.md,71.2%,简单,640 -0885,0800-0899,0885. 螺旋矩阵 III,螺旋矩阵 III,https://leetcode.cn/problems/spiral-matrix-iii/,spiral-matrix-iii,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0800-0899/spiral-matrix-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0885.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20III.md,71.2%,中等,132 -0886,0800-0899,0886. 可能的二分法,可能的二分法,https://leetcode.cn/problems/possible-bipartition/,possible-bipartition,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0800-0899/possible-bipartition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0886.%20%E5%8F%AF%E8%83%BD%E7%9A%84%E4%BA%8C%E5%88%86%E6%B3%95.md,52.1%,中等,614 -0887,0800-0899,0887. 鸡蛋掉落,鸡蛋掉落,https://leetcode.cn/problems/super-egg-drop/,super-egg-drop,数学、二分查找、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/super-egg-drop/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0887.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD.md,30.6%,困难,468 -0888,0800-0899,0888. 公平的糖果交换,公平的糖果交换,https://leetcode.cn/problems/fair-candy-swap/,fair-candy-swap,数组、哈希表、二分查找、排序,https://algo.itcharge.cn/Solutions/0800-0899/fair-candy-swap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0888.%20%E5%85%AC%E5%B9%B3%E7%9A%84%E7%B3%96%E6%9E%9C%E4%BA%A4%E6%8D%A2.md,63.6%,简单,598 -0889,0800-0899,0889. 根据前序和后序遍历构造二叉树,根据前序和后序遍历构造二叉树,https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/,construct-binary-tree-from-preorder-and-postorder-traversal,树、数组、哈希表、分治、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0889.%20%E6%A0%B9%E6%8D%AE%E5%89%8D%E5%BA%8F%E5%92%8C%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md,68.0%,中等,362 -0890,0800-0899,0890. 查找和替换模式,查找和替换模式,https://leetcode.cn/problems/find-and-replace-pattern/,find-and-replace-pattern,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/find-and-replace-pattern/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0890.%20%E6%9F%A5%E6%89%BE%E5%92%8C%E6%9B%BF%E6%8D%A2%E6%A8%A1%E5%BC%8F.md,78.6%,中等,474 -0891,0800-0899,0891. 子序列宽度之和,子序列宽度之和,https://leetcode.cn/problems/sum-of-subsequence-widths/,sum-of-subsequence-widths,数组、数学、排序,https://algo.itcharge.cn/Solutions/0800-0899/sum-of-subsequence-widths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0891.%20%E5%AD%90%E5%BA%8F%E5%88%97%E5%AE%BD%E5%BA%A6%E4%B9%8B%E5%92%8C.md,46.5%,困难,200 -0892,0800-0899,0892. 三维形体的表面积,三维形体的表面积,https://leetcode.cn/problems/surface-area-of-3d-shapes/,surface-area-of-3d-shapes,几何、数组、数学、矩阵,https://algo.itcharge.cn/Solutions/0800-0899/surface-area-of-3d-shapes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0892.%20%E4%B8%89%E7%BB%B4%E5%BD%A2%E4%BD%93%E7%9A%84%E8%A1%A8%E9%9D%A2%E7%A7%AF.md,64.4%,简单,776 -0893,0800-0899,0893. 特殊等价字符串组,特殊等价字符串组,https://leetcode.cn/problems/groups-of-special-equivalent-strings/,groups-of-special-equivalent-strings,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0800-0899/groups-of-special-equivalent-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0893.%20%E7%89%B9%E6%AE%8A%E7%AD%89%E4%BB%B7%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%BB%84.md,73.0%,中等,142 -0894,0800-0899,0894. 所有可能的真二叉树,所有可能的真二叉树,https://leetcode.cn/problems/all-possible-full-binary-trees/,all-possible-full-binary-trees,树、递归、记忆化搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/all-possible-full-binary-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0894.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E7%9C%9F%E4%BA%8C%E5%8F%89%E6%A0%91.md,77.6%,中等,197 -0895,0800-0899,0895. 最大频率栈,最大频率栈,https://leetcode.cn/problems/maximum-frequency-stack/,maximum-frequency-stack,栈、设计、哈希表、有序集合,https://algo.itcharge.cn/Solutions/0800-0899/maximum-frequency-stack/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0895.%20%E6%9C%80%E5%A4%A7%E9%A2%91%E7%8E%87%E6%A0%88.md,64.4%,困难,322 -0896,0800-0899,0896. 单调数列,单调数列,https://leetcode.cn/problems/monotonic-array/,monotonic-array,数组,https://algo.itcharge.cn/Solutions/0800-0899/monotonic-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0896.%20%E5%8D%95%E8%B0%83%E6%95%B0%E5%88%97.md,57.0%,简单,979 -0897,0800-0899,0897. 递增顺序搜索树,递增顺序搜索树,https://leetcode.cn/problems/increasing-order-search-tree/,increasing-order-search-tree,栈、树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0800-0899/increasing-order-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0897.%20%E9%80%92%E5%A2%9E%E9%A1%BA%E5%BA%8F%E6%90%9C%E7%B4%A2%E6%A0%91.md,74.1%,简单,895 -0898,0800-0899,0898. 子数组按位或操作,子数组按位或操作,https://leetcode.cn/problems/bitwise-ors-of-subarrays/,bitwise-ors-of-subarrays,位运算、数组、动态规划,https://algo.itcharge.cn/Solutions/0800-0899/bitwise-ors-of-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0898.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%8C%89%E4%BD%8D%E6%88%96%E6%93%8D%E4%BD%9C.md,39.1%,中等,62 -0899,0800-0899,0899. 有序队列,有序队列,https://leetcode.cn/problems/orderly-queue/,orderly-queue,数学、字符串、排序,https://algo.itcharge.cn/Solutions/0800-0899/orderly-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0899.%20%E6%9C%89%E5%BA%8F%E9%98%9F%E5%88%97.md,64.2%,困难,278 -0900,0900-0999,0900. RLE 迭代器,RLE 迭代器,https://leetcode.cn/problems/rle-iterator/,rle-iterator,设计、数组、计数、迭代器,https://algo.itcharge.cn/Solutions/0900-0999/rle-iterator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0900.%20RLE%20%E8%BF%AD%E4%BB%A3%E5%99%A8.md,51.3%,中等,87 -0901,0900-0999,0901. 股票价格跨度,股票价格跨度,https://leetcode.cn/problems/online-stock-span/,online-stock-span,栈、设计、数据流、单调栈,https://algo.itcharge.cn/Solutions/0900-0999/online-stock-span/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0901.%20%E8%82%A1%E7%A5%A8%E4%BB%B7%E6%A0%BC%E8%B7%A8%E5%BA%A6.md,62.3%,中等,662 -0902,0900-0999,0902. 最大为 N 的数字组合,最大为 N 的数字组合,https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/,numbers-at-most-n-given-digit-set,数组、数学、字符串、二分查找、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/numbers-at-most-n-given-digit-set/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0902.%20%E6%9C%80%E5%A4%A7%E4%B8%BA%20N%20%E7%9A%84%E6%95%B0%E5%AD%97%E7%BB%84%E5%90%88.md,46.2%,困难,397 -0903,0900-0999,0903. DI 序列的有效排列,DI 序列的有效排列,https://leetcode.cn/problems/valid-permutations-for-di-sequence/,valid-permutations-for-di-sequence,字符串、动态规划、前缀和,https://algo.itcharge.cn/Solutions/0900-0999/valid-permutations-for-di-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0903.%20DI%20%E5%BA%8F%E5%88%97%E7%9A%84%E6%9C%89%E6%95%88%E6%8E%92%E5%88%97.md,56.4%,困难,64 -0904,0900-0999,0904. 水果成篮,水果成篮,https://leetcode.cn/problems/fruit-into-baskets/,fruit-into-baskets,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/0900-0999/fruit-into-baskets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md,44.8%,中等,1444 -0905,0900-0999,0905. 按奇偶排序数组,按奇偶排序数组,https://leetcode.cn/problems/sort-array-by-parity/,sort-array-by-parity,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0900-0999/sort-array-by-parity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0905.%20%E6%8C%89%E5%A5%87%E5%81%B6%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md,71.0%,简单,1444 -0906,0900-0999,0906. 超级回文数,超级回文数,https://leetcode.cn/problems/super-palindromes/,super-palindromes,数学、枚举,https://algo.itcharge.cn/Solutions/0900-0999/super-palindromes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0906.%20%E8%B6%85%E7%BA%A7%E5%9B%9E%E6%96%87%E6%95%B0.md,31.8%,困难,79 -0907,0900-0999,0907. 子数组的最小值之和,子数组的最小值之和,https://leetcode.cn/problems/sum-of-subarray-minimums/,sum-of-subarray-minimums,栈、数组、动态规划、单调栈,https://algo.itcharge.cn/Solutions/0900-0999/sum-of-subarray-minimums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0907.%20%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%E4%B9%8B%E5%92%8C.md,38.3%,中等,438 -0908,0900-0999,0908. 最小差值 I,最小差值 I,https://leetcode.cn/problems/smallest-range-i/,smallest-range-i,数组、数学,https://algo.itcharge.cn/Solutions/0900-0999/smallest-range-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.md,74.4%,简单,581 -0909,0900-0999,0909. 蛇梯棋,蛇梯棋,https://leetcode.cn/problems/snakes-and-ladders/,snakes-and-ladders,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/snakes-and-ladders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0909.%20%E8%9B%87%E6%A2%AF%E6%A3%8B.md,45.8%,中等,222 -0910,0900-0999,0910. 最小差值 II,最小差值 II,https://leetcode.cn/problems/smallest-range-ii/,smallest-range-ii,贪心、数组、数学、排序,https://algo.itcharge.cn/Solutions/0900-0999/smallest-range-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0910.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20II.md,35.6%,中等,123 -0911,0900-0999,0911. 在线选举,在线选举,https://leetcode.cn/problems/online-election/,online-election,设计、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/0900-0999/online-election/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0911.%20%E5%9C%A8%E7%BA%BF%E9%80%89%E4%B8%BE.md,53.8%,中等,367 -0912,0900-0999,0912. 排序数组,排序数组,https://leetcode.cn/problems/sort-an-array/,sort-an-array,数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序,https://algo.itcharge.cn/Solutions/0900-0999/sort-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md,51.8%,中等,2119 -0913,0900-0999,0913. 猫和老鼠,猫和老鼠,https://leetcode.cn/problems/cat-and-mouse/,cat-and-mouse,图、拓扑排序、记忆化搜索、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/0900-0999/cat-and-mouse/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0913.%20%E7%8C%AB%E5%92%8C%E8%80%81%E9%BC%A0.md,54.0%,困难,140 -0914,0900-0999,0914. 卡牌分组,卡牌分组,https://leetcode.cn/problems/x-of-a-kind-in-a-deck-of-cards/,x-of-a-kind-in-a-deck-of-cards,数组、哈希表、数学、计数、数论,https://algo.itcharge.cn/Solutions/0900-0999/x-of-a-kind-in-a-deck-of-cards/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0914.%20%E5%8D%A1%E7%89%8C%E5%88%86%E7%BB%84.md,37.5%,简单,745 -0915,0900-0999,0915. 分割数组,分割数组,https://leetcode.cn/problems/partition-array-into-disjoint-intervals/,partition-array-into-disjoint-intervals,数组,https://algo.itcharge.cn/Solutions/0900-0999/partition-array-into-disjoint-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0915.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84.md,50.1%,中等,560 -0916,0900-0999,0916. 单词子集,单词子集,https://leetcode.cn/problems/word-subsets/,word-subsets,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0900-0999/word-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0916.%20%E5%8D%95%E8%AF%8D%E5%AD%90%E9%9B%86.md,46.2%,中等,119 -0917,0900-0999,0917. 仅仅反转字母,仅仅反转字母,https://leetcode.cn/problems/reverse-only-letters/,reverse-only-letters,双指针、字符串,https://algo.itcharge.cn/Solutions/0900-0999/reverse-only-letters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0917.%20%E4%BB%85%E4%BB%85%E5%8F%8D%E8%BD%AC%E5%AD%97%E6%AF%8D.md,59.4%,简单,1067 -0918,0900-0999,0918. 环形子数组的最大和,环形子数组的最大和,https://leetcode.cn/problems/maximum-sum-circular-subarray/,maximum-sum-circular-subarray,队列、数组、分治、动态规划、单调队列,https://algo.itcharge.cn/Solutions/0900-0999/maximum-sum-circular-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0918.%20%E7%8E%AF%E5%BD%A2%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,37.8%,中等,509 -0919,0900-0999,0919. 完全二叉树插入器,完全二叉树插入器,https://leetcode.cn/problems/complete-binary-tree-inserter/,complete-binary-tree-inserter,树、广度优先搜索、设计、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/complete-binary-tree-inserter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0919.%20%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E6%8F%92%E5%85%A5%E5%99%A8.md,66.9%,中等,414 -0920,0900-0999,0920. 播放列表的数量,播放列表的数量,https://leetcode.cn/problems/number-of-music-playlists/,number-of-music-playlists,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/0900-0999/number-of-music-playlists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0920.%20%E6%92%AD%E6%94%BE%E5%88%97%E8%A1%A8%E7%9A%84%E6%95%B0%E9%87%8F.md,52.2%,困难,41 -0921,0900-0999,0921. 使括号有效的最少添加,使括号有效的最少添加,https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/,minimum-add-to-make-parentheses-valid,栈、贪心、字符串,https://algo.itcharge.cn/Solutions/0900-0999/minimum-add-to-make-parentheses-valid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0921.%20%E4%BD%BF%E6%8B%AC%E5%8F%B7%E6%9C%89%E6%95%88%E7%9A%84%E6%9C%80%E5%B0%91%E6%B7%BB%E5%8A%A0.md,73.0%,中等,916 -0922,0900-0999,0922. 按奇偶排序数组 II,按奇偶排序数组 II,https://leetcode.cn/problems/sort-array-by-parity-ii/,sort-array-by-parity-ii,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0900-0999/sort-array-by-parity-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0922.%20%E6%8C%89%E5%A5%87%E5%81%B6%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%20II.md,71.3%,简单,1069 -0923,0900-0999,0923. 三数之和的多种可能,三数之和的多种可能,https://leetcode.cn/problems/3sum-with-multiplicity/,3sum-with-multiplicity,数组、哈希表、双指针、计数、排序,https://algo.itcharge.cn/Solutions/0900-0999/3sum-with-multiplicity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0923.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C%E7%9A%84%E5%A4%9A%E7%A7%8D%E5%8F%AF%E8%83%BD.md,36.9%,中等,120 -0924,0900-0999,0924. 尽量减少恶意软件的传播,尽量减少恶意软件的传播,https://leetcode.cn/problems/minimize-malware-spread/,minimize-malware-spread,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/minimize-malware-spread/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0924.%20%E5%B0%BD%E9%87%8F%E5%87%8F%E5%B0%91%E6%81%B6%E6%84%8F%E8%BD%AF%E4%BB%B6%E7%9A%84%E4%BC%A0%E6%92%AD.md,35.8%,困难,148 -0925,0900-0999,0925. 长按键入,长按键入,https://leetcode.cn/problems/long-pressed-name/,long-pressed-name,双指针、字符串,https://algo.itcharge.cn/Solutions/0900-0999/long-pressed-name/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0925.%20%E9%95%BF%E6%8C%89%E9%94%AE%E5%85%A5.md,37.5%,简单,828 -0926,0900-0999,0926. 将字符串翻转到单调递增,将字符串翻转到单调递增,https://leetcode.cn/problems/flip-string-to-monotone-increasing/,flip-string-to-monotone-increasing,字符串、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/flip-string-to-monotone-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0926.%20%E5%B0%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%BF%BB%E8%BD%AC%E5%88%B0%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E.md,63.5%,中等,518 -0927,0900-0999,0927. 三等分,三等分,https://leetcode.cn/problems/three-equal-parts/,three-equal-parts,数组、数学,https://algo.itcharge.cn/Solutions/0900-0999/three-equal-parts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0927.%20%E4%B8%89%E7%AD%89%E5%88%86.md,43.9%,困难,365 -0928,0900-0999,0928. 尽量减少恶意软件的传播 II,尽量减少恶意软件的传播 II,https://leetcode.cn/problems/minimize-malware-spread-ii/,minimize-malware-spread-ii,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/minimize-malware-spread-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0928.%20%E5%B0%BD%E9%87%8F%E5%87%8F%E5%B0%91%E6%81%B6%E6%84%8F%E8%BD%AF%E4%BB%B6%E7%9A%84%E4%BC%A0%E6%92%AD%20II.md,43.7%,困难,74 -0929,0900-0999,0929. 独特的电子邮件地址,独特的电子邮件地址,https://leetcode.cn/problems/unique-email-addresses/,unique-email-addresses,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0900-0999/unique-email-addresses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0929.%20%E7%8B%AC%E7%89%B9%E7%9A%84%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6%E5%9C%B0%E5%9D%80.md,68.6%,简单,539 -0930,0900-0999,0930. 和相同的二元子数组,和相同的二元子数组,https://leetcode.cn/problems/binary-subarrays-with-sum/,binary-subarrays-with-sum,数组、哈希表、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/0900-0999/binary-subarrays-with-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0930.%20%E5%92%8C%E7%9B%B8%E5%90%8C%E7%9A%84%E4%BA%8C%E5%85%83%E5%AD%90%E6%95%B0%E7%BB%84.md,55.1%,中等,438 -0931,0900-0999,0931. 下降路径最小和,下降路径最小和,https://leetcode.cn/problems/minimum-falling-path-sum/,minimum-falling-path-sum,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/minimum-falling-path-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0931.%20%E4%B8%8B%E9%99%8D%E8%B7%AF%E5%BE%84%E6%9C%80%E5%B0%8F%E5%92%8C.md,67.1%,中等,1052 -0932,0900-0999,0932. 漂亮数组,漂亮数组,https://leetcode.cn/problems/beautiful-array/,beautiful-array,数组、数学、分治,https://algo.itcharge.cn/Solutions/0900-0999/beautiful-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0932.%20%E6%BC%82%E4%BA%AE%E6%95%B0%E7%BB%84.md,65.6%,中等,130 -0933,0900-0999,0933. 最近的请求次数,最近的请求次数,https://leetcode.cn/problems/number-of-recent-calls/,number-of-recent-calls,设计、队列、数据流,https://algo.itcharge.cn/Solutions/0900-0999/number-of-recent-calls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0933.%20%E6%9C%80%E8%BF%91%E7%9A%84%E8%AF%B7%E6%B1%82%E6%AC%A1%E6%95%B0.md,76.8%,简单,835 -0934,0900-0999,0934. 最短的桥,最短的桥,https://leetcode.cn/problems/shortest-bridge/,shortest-bridge,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/shortest-bridge/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0934.%20%E6%9C%80%E7%9F%AD%E7%9A%84%E6%A1%A5.md,52.4%,中等,730 -0935,0900-0999,0935. 骑士拨号器,骑士拨号器,https://leetcode.cn/problems/knight-dialer/,knight-dialer,动态规划,https://algo.itcharge.cn/Solutions/0900-0999/knight-dialer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0935.%20%E9%AA%91%E5%A3%AB%E6%8B%A8%E5%8F%B7%E5%99%A8.md,51.5%,中等,130 -0936,0900-0999,0936. 戳印序列,戳印序列,https://leetcode.cn/problems/stamping-the-sequence/,stamping-the-sequence,栈、贪心、队列、字符串,https://algo.itcharge.cn/Solutions/0900-0999/stamping-the-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0936.%20%E6%88%B3%E5%8D%B0%E5%BA%8F%E5%88%97.md,42.3%,困难,39 -0937,0900-0999,0937. 重新排列日志文件,重新排列日志文件,https://leetcode.cn/problems/reorder-data-in-log-files/,reorder-data-in-log-files,数组、字符串、排序,https://algo.itcharge.cn/Solutions/0900-0999/reorder-data-in-log-files/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0937.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6.md,63.5%,中等,504 -0938,0900-0999,0938. 二叉搜索树的范围和,二叉搜索树的范围和,https://leetcode.cn/problems/range-sum-of-bst/,range-sum-of-bst,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/range-sum-of-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0938.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E8%8C%83%E5%9B%B4%E5%92%8C.md,82.1%,简单,999 -0939,0900-0999,0939. 最小面积矩形,最小面积矩形,https://leetcode.cn/problems/minimum-area-rectangle/,minimum-area-rectangle,几何、数组、哈希表、数学、排序,https://algo.itcharge.cn/Solutions/0900-0999/minimum-area-rectangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0939.%20%E6%9C%80%E5%B0%8F%E9%9D%A2%E7%A7%AF%E7%9F%A9%E5%BD%A2.md,48.4%,中等,104 -0940,0900-0999,0940. 不同的子序列 II,不同的子序列 II,https://leetcode.cn/problems/distinct-subsequences-ii/,distinct-subsequences-ii,字符串、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/distinct-subsequences-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0940.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%20II.md,53.5%,困难,283 -0941,0900-0999,0941. 有效的山脉数组,有效的山脉数组,https://leetcode.cn/problems/valid-mountain-array/,valid-mountain-array,数组,https://algo.itcharge.cn/Solutions/0900-0999/valid-mountain-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0941.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84.md,39.5%,简单,805 -0942,0900-0999,0942. 增减字符串匹配,增减字符串匹配,https://leetcode.cn/problems/di-string-match/,di-string-match,贪心、数组、双指针、字符串,https://algo.itcharge.cn/Solutions/0900-0999/di-string-match/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0942.%20%E5%A2%9E%E5%87%8F%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md,77.2%,简单,794 -0943,0900-0999,0943. 最短超级串,最短超级串,https://leetcode.cn/problems/find-the-shortest-superstring/,find-the-shortest-superstring,位运算、数组、字符串、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/0900-0999/find-the-shortest-superstring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0943.%20%E6%9C%80%E7%9F%AD%E8%B6%85%E7%BA%A7%E4%B8%B2.md,47.2%,困难,76 -0944,0900-0999,0944. 删列造序,删列造序,https://leetcode.cn/problems/delete-columns-to-make-sorted/,delete-columns-to-make-sorted,数组、字符串,https://algo.itcharge.cn/Solutions/0900-0999/delete-columns-to-make-sorted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0944.%20%E5%88%A0%E5%88%97%E9%80%A0%E5%BA%8F.md,69.0%,简单,504 -0945,0900-0999,0945. 使数组唯一的最小增量,使数组唯一的最小增量,https://leetcode.cn/problems/minimum-increment-to-make-array-unique/,minimum-increment-to-make-array-unique,贪心、数组、计数、排序,https://algo.itcharge.cn/Solutions/0900-0999/minimum-increment-to-make-array-unique/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0945.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%94%AF%E4%B8%80%E7%9A%84%E6%9C%80%E5%B0%8F%E5%A2%9E%E9%87%8F.md,47.9%,中等,619 -0946,0900-0999,0946. 验证栈序列,验证栈序列,https://leetcode.cn/problems/validate-stack-sequences/,validate-stack-sequences,栈、数组、模拟,https://algo.itcharge.cn/Solutions/0900-0999/validate-stack-sequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0946.%20%E9%AA%8C%E8%AF%81%E6%A0%88%E5%BA%8F%E5%88%97.md,66.6%,中等,851 -0947,0900-0999,0947. 移除最多的同行或同列石头,移除最多的同行或同列石头,https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/,most-stones-removed-with-same-row-or-column,深度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0900-0999/most-stones-removed-with-same-row-or-column/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0947.%20%E7%A7%BB%E9%99%A4%E6%9C%80%E5%A4%9A%E7%9A%84%E5%90%8C%E8%A1%8C%E6%88%96%E5%90%8C%E5%88%97%E7%9F%B3%E5%A4%B4.md,61.6%,中等,426 -0948,0900-0999,0948. 令牌放置,令牌放置,https://leetcode.cn/problems/bag-of-tokens/,bag-of-tokens,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/0900-0999/bag-of-tokens/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0948.%20%E4%BB%A4%E7%89%8C%E6%94%BE%E7%BD%AE.md,40.5%,中等,157 -0949,0900-0999,0949. 给定数字能组成的最大时间,给定数字能组成的最大时间,https://leetcode.cn/problems/largest-time-for-given-digits/,largest-time-for-given-digits,字符串、枚举,https://algo.itcharge.cn/Solutions/0900-0999/largest-time-for-given-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0949.%20%E7%BB%99%E5%AE%9A%E6%95%B0%E5%AD%97%E8%83%BD%E7%BB%84%E6%88%90%E7%9A%84%E6%9C%80%E5%A4%A7%E6%97%B6%E9%97%B4.md,37.9%,中等,184 -0950,0900-0999,0950. 按递增顺序显示卡牌,按递增顺序显示卡牌,https://leetcode.cn/problems/reveal-cards-in-increasing-order/,reveal-cards-in-increasing-order,队列、数组、排序、模拟,https://algo.itcharge.cn/Solutions/0900-0999/reveal-cards-in-increasing-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0950.%20%E6%8C%89%E9%80%92%E5%A2%9E%E9%A1%BA%E5%BA%8F%E6%98%BE%E7%A4%BA%E5%8D%A1%E7%89%8C.md,78.6%,中等,233 -0951,0900-0999,0951. 翻转等价二叉树,翻转等价二叉树,https://leetcode.cn/problems/flip-equivalent-binary-trees/,flip-equivalent-binary-trees,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/flip-equivalent-binary-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0951.%20%E7%BF%BB%E8%BD%AC%E7%AD%89%E4%BB%B7%E4%BA%8C%E5%8F%89%E6%A0%91.md,66.8%,中等,253 -0952,0900-0999,0952. 按公因数计算最大组件大小,按公因数计算最大组件大小,https://leetcode.cn/problems/largest-component-size-by-common-factor/,largest-component-size-by-common-factor,并查集、数组、数学、数论,https://algo.itcharge.cn/Solutions/0900-0999/largest-component-size-by-common-factor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0952.%20%E6%8C%89%E5%85%AC%E5%9B%A0%E6%95%B0%E8%AE%A1%E7%AE%97%E6%9C%80%E5%A4%A7%E7%BB%84%E4%BB%B6%E5%A4%A7%E5%B0%8F.md,51.1%,困难,184 -0953,0900-0999,0953. 验证外星语词典,验证外星语词典,https://leetcode.cn/problems/verifying-an-alien-dictionary/,verifying-an-alien-dictionary,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0900-0999/verifying-an-alien-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0953.%20%E9%AA%8C%E8%AF%81%E5%A4%96%E6%98%9F%E8%AF%AD%E8%AF%8D%E5%85%B8.md,57.7%,简单,699 -0954,0900-0999,0954. 二倍数对数组,二倍数对数组,https://leetcode.cn/problems/array-of-doubled-pairs/,array-of-doubled-pairs,贪心、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/0900-0999/array-of-doubled-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0954.%20%E4%BA%8C%E5%80%8D%E6%95%B0%E5%AF%B9%E6%95%B0%E7%BB%84.md,39.1%,中等,478 -0955,0900-0999,0955. 删列造序 II,删列造序 II,https://leetcode.cn/problems/delete-columns-to-make-sorted-ii/,delete-columns-to-make-sorted-ii,贪心、数组、字符串,https://algo.itcharge.cn/Solutions/0900-0999/delete-columns-to-make-sorted-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0955.%20%E5%88%A0%E5%88%97%E9%80%A0%E5%BA%8F%20II.md,35.5%,中等,90 -0956,0900-0999,0956. 最高的广告牌,最高的广告牌,https://leetcode.cn/problems/tallest-billboard/,tallest-billboard,数组、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/tallest-billboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0956.%20%E6%9C%80%E9%AB%98%E7%9A%84%E5%B9%BF%E5%91%8A%E7%89%8C.md,46.4%,困难,78 -0957,0900-0999,0957. N 天后的牢房,N 天后的牢房,https://leetcode.cn/problems/prison-cells-after-n-days/,prison-cells-after-n-days,位运算、数组、哈希表、数学,https://algo.itcharge.cn/Solutions/0900-0999/prison-cells-after-n-days/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0957.%20N%20%E5%A4%A9%E5%90%8E%E7%9A%84%E7%89%A2%E6%88%BF.md,37.0%,中等,189 -0958,0900-0999,0958. 二叉树的完全性检验,二叉树的完全性检验,https://leetcode.cn/problems/check-completeness-of-a-binary-tree/,check-completeness-of-a-binary-tree,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/check-completeness-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md,54.5%,中等,459 -0959,0900-0999,0959. 由斜杠划分区域,由斜杠划分区域,https://leetcode.cn/problems/regions-cut-by-slashes/,regions-cut-by-slashes,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/0900-0999/regions-cut-by-slashes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0959.%20%E7%94%B1%E6%96%9C%E6%9D%A0%E5%88%92%E5%88%86%E5%8C%BA%E5%9F%9F.md,74.3%,中等,354 -0960,0900-0999,0960. 删列造序 III,删列造序 III,https://leetcode.cn/problems/delete-columns-to-make-sorted-iii/,delete-columns-to-make-sorted-iii,数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/delete-columns-to-make-sorted-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0960.%20%E5%88%A0%E5%88%97%E9%80%A0%E5%BA%8F%20III.md,59.0%,困难,61 -0961,0900-0999,0961. 在长度 2N 的数组中找出重复 N 次的元素,在长度 2N 的数组中找出重复 N 次的元素,https://leetcode.cn/problems/n-repeated-element-in-size-2n-array/,n-repeated-element-in-size-2n-array,数组、哈希表,https://algo.itcharge.cn/Solutions/0900-0999/n-repeated-element-in-size-2n-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0961.%20%E5%9C%A8%E9%95%BF%E5%BA%A6%202N%20%E7%9A%84%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%BE%E5%87%BA%E9%87%8D%E5%A4%8D%20N%20%E6%AC%A1%E7%9A%84%E5%85%83%E7%B4%A0.md,70.2%,简单,726 -0962,0900-0999,0962. 最大宽度坡,最大宽度坡,https://leetcode.cn/problems/maximum-width-ramp/,maximum-width-ramp,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/0900-0999/maximum-width-ramp/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0962.%20%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6%E5%9D%A1.md,47.4%,中等,227 -0963,0900-0999,0963. 最小面积矩形 II,最小面积矩形 II,https://leetcode.cn/problems/minimum-area-rectangle-ii/,minimum-area-rectangle-ii,几何、数组、数学,https://algo.itcharge.cn/Solutions/0900-0999/minimum-area-rectangle-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0963.%20%E6%9C%80%E5%B0%8F%E9%9D%A2%E7%A7%AF%E7%9F%A9%E5%BD%A2%20II.md,51.1%,中等,52 -0964,0900-0999,0964. 表示数字的最少运算符,表示数字的最少运算符,https://leetcode.cn/problems/least-operators-to-express-number/,least-operators-to-express-number,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/least-operators-to-express-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0964.%20%E8%A1%A8%E7%A4%BA%E6%95%B0%E5%AD%97%E7%9A%84%E6%9C%80%E5%B0%91%E8%BF%90%E7%AE%97%E7%AC%A6.md,46.6%,困难,40 -0965,0900-0999,0965. 单值二叉树,单值二叉树,https://leetcode.cn/problems/univalued-binary-tree/,univalued-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/univalued-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0965.%20%E5%8D%95%E5%80%BC%E4%BA%8C%E5%8F%89%E6%A0%91.md,70.7%,简单,881 -0966,0900-0999,0966. 元音拼写检查器,元音拼写检查器,https://leetcode.cn/problems/vowel-spellchecker/,vowel-spellchecker,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/0900-0999/vowel-spellchecker/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0966.%20%E5%85%83%E9%9F%B3%E6%8B%BC%E5%86%99%E6%A3%80%E6%9F%A5%E5%99%A8.md,43.0%,中等,69 -0967,0900-0999,0967. 连续差相同的数字,连续差相同的数字,https://leetcode.cn/problems/numbers-with-same-consecutive-differences/,numbers-with-same-consecutive-differences,广度优先搜索、回溯,https://algo.itcharge.cn/Solutions/0900-0999/numbers-with-same-consecutive-differences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0967.%20%E8%BF%9E%E7%BB%AD%E5%B7%AE%E7%9B%B8%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97.md,50.5%,中等,237 -0968,0900-0999,0968. 监控二叉树,监控二叉树,https://leetcode.cn/problems/binary-tree-cameras/,binary-tree-cameras,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/binary-tree-cameras/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0968.%20%E7%9B%91%E6%8E%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md,52.3%,困难,618 -0969,0900-0999,0969. 煎饼排序,煎饼排序,https://leetcode.cn/problems/pancake-sorting/,pancake-sorting,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/0900-0999/pancake-sorting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0969.%20%E7%85%8E%E9%A5%BC%E6%8E%92%E5%BA%8F.md,67.5%,中等,684 -0970,0900-0999,0970. 强整数,强整数,https://leetcode.cn/problems/powerful-integers/,powerful-integers,哈希表、数学,https://algo.itcharge.cn/Solutions/0900-0999/powerful-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0970.%20%E5%BC%BA%E6%95%B4%E6%95%B0.md,47.0%,中等,307 -0971,0900-0999,0971. 翻转二叉树以匹配先序遍历,翻转二叉树以匹配先序遍历,https://leetcode.cn/problems/flip-binary-tree-to-match-preorder-traversal/,flip-binary-tree-to-match-preorder-traversal,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/flip-binary-tree-to-match-preorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0971.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91%E4%BB%A5%E5%8C%B9%E9%85%8D%E5%85%88%E5%BA%8F%E9%81%8D%E5%8E%86.md,45.5%,中等,164 -0972,0900-0999,0972. 相等的有理数,相等的有理数,https://leetcode.cn/problems/equal-rational-numbers/,equal-rational-numbers,数学、字符串,https://algo.itcharge.cn/Solutions/0900-0999/equal-rational-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0972.%20%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%89%E7%90%86%E6%95%B0.md,41.5%,困难,53 -0973,0900-0999,0973. 最接近原点的 K 个点,最接近原点的 K 个点,https://leetcode.cn/problems/k-closest-points-to-origin/,k-closest-points-to-origin,几何、数组、数学、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/0900-0999/k-closest-points-to-origin/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0973.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E5%8E%9F%E7%82%B9%E7%9A%84%20K%20%E4%B8%AA%E7%82%B9.md,65.2%,中等,823 -0974,0900-0999,0974. 和可被 K 整除的子数组,和可被 K 整除的子数组,https://leetcode.cn/problems/subarray-sums-divisible-by-k/,subarray-sums-divisible-by-k,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/0900-0999/subarray-sums-divisible-by-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0974.%20%E5%92%8C%E5%8F%AF%E8%A2%AB%20K%20%E6%95%B4%E9%99%A4%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,47.9%,中等,540 -0975,0900-0999,0975. 奇偶跳,奇偶跳,https://leetcode.cn/problems/odd-even-jump/,odd-even-jump,栈、数组、动态规划、有序集合、单调栈,https://algo.itcharge.cn/Solutions/0900-0999/odd-even-jump/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0975.%20%E5%A5%87%E5%81%B6%E8%B7%B3.md,47.8%,困难,90 -0976,0900-0999,0976. 三角形的最大周长,三角形的最大周长,https://leetcode.cn/problems/largest-perimeter-triangle/,largest-perimeter-triangle,贪心、数组、数学、排序,https://algo.itcharge.cn/Solutions/0900-0999/largest-perimeter-triangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0976.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%91%A8%E9%95%BF.md,57.5%,简单,842 -0977,0900-0999,0977. 有序数组的平方,有序数组的平方,https://leetcode.cn/problems/squares-of-a-sorted-array/,squares-of-a-sorted-array,数组、双指针、排序,https://algo.itcharge.cn/Solutions/0900-0999/squares-of-a-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0977.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.md,68.0%,简单,3385 -0978,0900-0999,0978. 最长湍流子数组,最长湍流子数组,https://leetcode.cn/problems/longest-turbulent-subarray/,longest-turbulent-subarray,数组、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/0900-0999/longest-turbulent-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0978.%20%E6%9C%80%E9%95%BF%E6%B9%8D%E6%B5%81%E5%AD%90%E6%95%B0%E7%BB%84.md,47.5%,中等,690 -0979,0900-0999,0979. 在二叉树中分配硬币,在二叉树中分配硬币,https://leetcode.cn/problems/distribute-coins-in-binary-tree/,distribute-coins-in-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/distribute-coins-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0979.%20%E5%9C%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%88%86%E9%85%8D%E7%A1%AC%E5%B8%81.md,72.4%,中等,204 -0980,0900-0999,0980. 不同路径 III,不同路径 III,https://leetcode.cn/problems/unique-paths-iii/,unique-paths-iii,位运算、数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/unique-paths-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0980.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20III.md,74.1%,困难,307 -0981,0900-0999,0981. 基于时间的键值存储,基于时间的键值存储,https://leetcode.cn/problems/time-based-key-value-store/,time-based-key-value-store,设计、哈希表、字符串、二分查找,https://algo.itcharge.cn/Solutions/0900-0999/time-based-key-value-store/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0981.%20%E5%9F%BA%E4%BA%8E%E6%97%B6%E9%97%B4%E7%9A%84%E9%94%AE%E5%80%BC%E5%AD%98%E5%82%A8.md,52.9%,中等,388 -0982,0900-0999,0982. 按位与为零的三元组,按位与为零的三元组,https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/,triples-with-bitwise-and-equal-to-zero,位运算、数组、哈希表,https://algo.itcharge.cn/Solutions/0900-0999/triples-with-bitwise-and-equal-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0982.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E4%B8%BA%E9%9B%B6%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84.md,67.3%,困难,151 -0983,0900-0999,0983. 最低票价,最低票价,https://leetcode.cn/problems/minimum-cost-for-tickets/,minimum-cost-for-tickets,数组、动态规划,https://algo.itcharge.cn/Solutions/0900-0999/minimum-cost-for-tickets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0983.%20%E6%9C%80%E4%BD%8E%E7%A5%A8%E4%BB%B7.md,63.5%,中等,545 -0984,0900-0999,0984. 不含 AAA 或 BBB 的字符串,不含 AAA 或 BBB 的字符串,https://leetcode.cn/problems/string-without-aaa-or-bbb/,string-without-aaa-or-bbb,贪心、字符串,https://algo.itcharge.cn/Solutions/0900-0999/string-without-aaa-or-bbb/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0984.%20%E4%B8%8D%E5%90%AB%20AAA%20%E6%88%96%20BBB%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,43.4%,中等,217 -0985,0900-0999,0985. 查询后的偶数和,查询后的偶数和,https://leetcode.cn/problems/sum-of-even-numbers-after-queries/,sum-of-even-numbers-after-queries,数组、模拟,https://algo.itcharge.cn/Solutions/0900-0999/sum-of-even-numbers-after-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0985.%20%E6%9F%A5%E8%AF%A2%E5%90%8E%E7%9A%84%E5%81%B6%E6%95%B0%E5%92%8C.md,61.1%,中等,225 -0986,0900-0999,0986. 区间列表的交集,区间列表的交集,https://leetcode.cn/problems/interval-list-intersections/,interval-list-intersections,数组、双指针,https://algo.itcharge.cn/Solutions/0900-0999/interval-list-intersections/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0986.%20%E5%8C%BA%E9%97%B4%E5%88%97%E8%A1%A8%E7%9A%84%E4%BA%A4%E9%9B%86.md,68.5%,中等,590 -0987,0900-0999,0987. 二叉树的垂序遍历,二叉树的垂序遍历,https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/,vertical-order-traversal-of-a-binary-tree,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/vertical-order-traversal-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0987.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%9E%82%E5%BA%8F%E9%81%8D%E5%8E%86.md,53.8%,困难,513 -0988,0900-0999,0988. 从叶结点开始的最小字符串,从叶结点开始的最小字符串,https://leetcode.cn/problems/smallest-string-starting-from-leaf/,smallest-string-starting-from-leaf,树、深度优先搜索、字符串、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/smallest-string-starting-from-leaf/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0988.%20%E4%BB%8E%E5%8F%B6%E7%BB%93%E7%82%B9%E5%BC%80%E5%A7%8B%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E7%AC%A6%E4%B8%B2.md,51.1%,中等,193 -0989,0900-0999,0989. 数组形式的整数加法,数组形式的整数加法,https://leetcode.cn/problems/add-to-array-form-of-integer/,add-to-array-form-of-integer,数组、数学,https://algo.itcharge.cn/Solutions/0900-0999/add-to-array-form-of-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0989.%20%E6%95%B0%E7%BB%84%E5%BD%A2%E5%BC%8F%E7%9A%84%E6%95%B4%E6%95%B0%E5%8A%A0%E6%B3%95.md,46.0%,简单,787 -0990,0900-0999,0990. 等式方程的可满足性,等式方程的可满足性,https://leetcode.cn/problems/satisfiability-of-equality-equations/,satisfiability-of-equality-equations,并查集、图、数组、字符串,https://algo.itcharge.cn/Solutions/0900-0999/satisfiability-of-equality-equations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0990.%20%E7%AD%89%E5%BC%8F%E6%96%B9%E7%A8%8B%E7%9A%84%E5%8F%AF%E6%BB%A1%E8%B6%B3%E6%80%A7.md,53.4%,中等,898 -0991,0900-0999,0991. 坏了的计算器,坏了的计算器,https://leetcode.cn/problems/broken-calculator/,broken-calculator,贪心、数学,https://algo.itcharge.cn/Solutions/0900-0999/broken-calculator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0991.%20%E5%9D%8F%E4%BA%86%E7%9A%84%E8%AE%A1%E7%AE%97%E5%99%A8.md,52.3%,中等,145 -0992,0900-0999,0992. K 个不同整数的子数组,K 个不同整数的子数组,https://leetcode.cn/problems/subarrays-with-k-different-integers/,subarrays-with-k-different-integers,数组、哈希表、计数、滑动窗口,https://algo.itcharge.cn/Solutions/0900-0999/subarrays-with-k-different-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0992.%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,47.5%,困难,336 -0993,0900-0999,0993. 二叉树的堂兄弟节点,二叉树的堂兄弟节点,https://leetcode.cn/problems/cousins-in-binary-tree/,cousins-in-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/cousins-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0993.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%A0%82%E5%85%84%E5%BC%9F%E8%8A%82%E7%82%B9.md,55.8%,简单,1010 -0994,0900-0999,0994. 腐烂的橘子,腐烂的橘子,https://leetcode.cn/problems/rotting-oranges/,rotting-oranges,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/0900-0999/rotting-oranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0994.%20%E8%85%90%E7%83%82%E7%9A%84%E6%A9%98%E5%AD%90.md,51.0%,中等,2107 -0995,0900-0999,0995. K 连续位的最小翻转次数,K 连续位的最小翻转次数,https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/,minimum-number-of-k-consecutive-bit-flips,位运算、队列、数组、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md,53.9%,困难,287 -0996,0900-0999,0996. 正方形数组的数目,正方形数组的数目,https://leetcode.cn/problems/number-of-squareful-arrays/,number-of-squareful-arrays,位运算、数组、数学、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/0900-0999/number-of-squareful-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0996.%20%E6%AD%A3%E6%96%B9%E5%BD%A2%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,50.4%,困难,138 -0997,0900-0999,0997. 找到小镇的法官,找到小镇的法官,https://leetcode.cn/problems/find-the-town-judge/,find-the-town-judge,图、数组、哈希表,https://algo.itcharge.cn/Solutions/0900-0999/find-the-town-judge/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0997.%20%E6%89%BE%E5%88%B0%E5%B0%8F%E9%95%87%E7%9A%84%E6%B3%95%E5%AE%98.md,51.9%,简单,863 -0998,0900-0999,0998. 最大二叉树 II,最大二叉树 II,https://leetcode.cn/problems/maximum-binary-tree-ii/,maximum-binary-tree-ii,树、二叉树,https://algo.itcharge.cn/Solutions/0900-0999/maximum-binary-tree-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0998.%20%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91%20II.md,68.7%,中等,437 -0999,0900-0999,0999. 可以被一步捕获的棋子数,可以被一步捕获的棋子数,https://leetcode.cn/problems/available-captures-for-rook/,available-captures-for-rook,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/0900-0999/available-captures-for-rook/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0999.%20%E5%8F%AF%E4%BB%A5%E8%A2%AB%E4%B8%80%E6%AD%A5%E6%8D%95%E8%8E%B7%E7%9A%84%E6%A3%8B%E5%AD%90%E6%95%B0.md,69.3%,简单,639 -1000,1000-1099,1000. 合并石头的最低成本,合并石头的最低成本,https://leetcode.cn/problems/minimum-cost-to-merge-stones/,minimum-cost-to-merge-stones,数组、动态规划、前缀和,https://algo.itcharge.cn/Solutions/1000-1099/minimum-cost-to-merge-stones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1000.%20%E5%90%88%E5%B9%B6%E7%9F%B3%E5%A4%B4%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md,53.6%,困难,110 -1001,1000-1099,1001. 网格照明,网格照明,https://leetcode.cn/problems/grid-illumination/,grid-illumination,数组、哈希表,https://algo.itcharge.cn/Solutions/1000-1099/grid-illumination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1001.%20%E7%BD%91%E6%A0%BC%E7%85%A7%E6%98%8E.md,44.9%,困难,283 -1002,1000-1099,1002. 查找共用字符,查找共用字符,https://leetcode.cn/problems/find-common-characters/,find-common-characters,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1000-1099/find-common-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1002.%20%E6%9F%A5%E6%89%BE%E5%85%B1%E7%94%A8%E5%AD%97%E7%AC%A6.md,70.6%,简单,842 -1003,1000-1099,1003. 检查替换后的词是否有效,检查替换后的词是否有效,https://leetcode.cn/problems/check-if-word-is-valid-after-substitutions/,check-if-word-is-valid-after-substitutions,栈、字符串,https://algo.itcharge.cn/Solutions/1000-1099/check-if-word-is-valid-after-substitutions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1003.%20%E6%A3%80%E6%9F%A5%E6%9B%BF%E6%8D%A2%E5%90%8E%E7%9A%84%E8%AF%8D%E6%98%AF%E5%90%A6%E6%9C%89%E6%95%88.md,63.6%,中等,442 -1004,1000-1099,1004. 最大连续1的个数 III,最大连续1的个数 III,https://leetcode.cn/problems/max-consecutive-ones-iii/,max-consecutive-ones-iii,数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/1000-1099/max-consecutive-ones-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1004.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III.md,59.5%,中等,1101 -1005,1000-1099,1005. K 次取反后最大化的数组和,K 次取反后最大化的数组和,https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/,maximize-sum-of-array-after-k-negations,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1000-1099/maximize-sum-of-array-after-k-negations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1005.%20K%20%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md,50.8%,简单,1493 -1006,1000-1099,1006. 笨阶乘,笨阶乘,https://leetcode.cn/problems/clumsy-factorial/,clumsy-factorial,栈、数学、模拟,https://algo.itcharge.cn/Solutions/1000-1099/clumsy-factorial/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1006.%20%E7%AC%A8%E9%98%B6%E4%B9%98.md,62.7%,中等,667 -1007,1000-1099,1007. 行相等的最少多米诺旋转,行相等的最少多米诺旋转,https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row/,minimum-domino-rotations-for-equal-row,贪心、数组,https://algo.itcharge.cn/Solutions/1000-1099/minimum-domino-rotations-for-equal-row/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1007.%20%E8%A1%8C%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%9A%E7%B1%B3%E8%AF%BA%E6%97%8B%E8%BD%AC.md,47.8%,中等,125 -1008,1000-1099,1008. 前序遍历构造二叉搜索树,前序遍历构造二叉搜索树,https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/,construct-binary-search-tree-from-preorder-traversal,栈、树、二叉搜索树、数组、二叉树、单调栈,https://algo.itcharge.cn/Solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1008.%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,71.8%,中等,392 -1009,1000-1099,1009. 十进制整数的反码,十进制整数的反码,https://leetcode.cn/problems/complement-of-base-10-integer/,complement-of-base-10-integer,位运算,https://algo.itcharge.cn/Solutions/1000-1099/complement-of-base-10-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1009.%20%E5%8D%81%E8%BF%9B%E5%88%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%8F%8D%E7%A0%81.md,58.6%,简单,340 -1010,1000-1099,1010. 总持续时间可被 60 整除的歌曲,总持续时间可被 60 整除的歌曲,https://leetcode.cn/problems/pairs-of-songs-with-total-durations-divisible-by-60/,pairs-of-songs-with-total-durations-divisible-by-60,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1000-1099/pairs-of-songs-with-total-durations-divisible-by-60/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1010.%20%E6%80%BB%E6%8C%81%E7%BB%AD%E6%97%B6%E9%97%B4%E5%8F%AF%E8%A2%AB%2060%20%E6%95%B4%E9%99%A4%E7%9A%84%E6%AD%8C%E6%9B%B2.md,49.6%,中等,487 -1011,1000-1099,1011. 在 D 天内送达包裹的能力,在 D 天内送达包裹的能力,https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/,capacity-to-ship-packages-within-d-days,数组、二分查找,https://algo.itcharge.cn/Solutions/1000-1099/capacity-to-ship-packages-within-d-days/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1011.%20%E5%9C%A8%20D%20%E5%A4%A9%E5%86%85%E9%80%81%E8%BE%BE%E5%8C%85%E8%A3%B9%E7%9A%84%E8%83%BD%E5%8A%9B.md,62.0%,中等,857 -1012,1000-1099,1012. 至少有 1 位重复的数字,至少有 1 位重复的数字,https://leetcode.cn/problems/numbers-with-repeated-digits/,numbers-with-repeated-digits,数学、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/numbers-with-repeated-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1012.%20%E8%87%B3%E5%B0%91%E6%9C%89%201%20%E4%BD%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md,52.4%,困难,189 -1013,1000-1099,1013. 将数组分成和相等的三个部分,将数组分成和相等的三个部分,https://leetcode.cn/problems/partition-array-into-three-parts-with-equal-sum/,partition-array-into-three-parts-with-equal-sum,贪心、数组,https://algo.itcharge.cn/Solutions/1000-1099/partition-array-into-three-parts-with-equal-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1013.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%86%E6%88%90%E5%92%8C%E7%9B%B8%E7%AD%89%E7%9A%84%E4%B8%89%E4%B8%AA%E9%83%A8%E5%88%86.md,38.5%,简单,1048 -1014,1000-1099,1014. 最佳观光组合,最佳观光组合,https://leetcode.cn/problems/best-sightseeing-pair/,best-sightseeing-pair,数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/best-sightseeing-pair/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1014.%20%E6%9C%80%E4%BD%B3%E8%A7%82%E5%85%89%E7%BB%84%E5%90%88.md,57.1%,中等,719 -1015,1000-1099,1015. 可被 K 整除的最小整数,可被 K 整除的最小整数,https://leetcode.cn/problems/smallest-integer-divisible-by-k/,smallest-integer-divisible-by-k,哈希表、数学,https://algo.itcharge.cn/Solutions/1000-1099/smallest-integer-divisible-by-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1015.%20%E5%8F%AF%E8%A2%AB%20K%20%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B4%E6%95%B0.md,46.6%,中等,170 -1016,1000-1099,1016. 子串能表示从 1 到 N 数字的二进制串,子串能表示从 1 到 N 数字的二进制串,https://leetcode.cn/problems/binary-string-with-substrings-representing-1-to-n/,binary-string-with-substrings-representing-1-to-n,字符串,https://algo.itcharge.cn/Solutions/1000-1099/binary-string-with-substrings-representing-1-to-n/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1016.%20%E5%AD%90%E4%B8%B2%E8%83%BD%E8%A1%A8%E7%A4%BA%E4%BB%8E%201%20%E5%88%B0%20N%20%E6%95%B0%E5%AD%97%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%B2.md,63.3%,中等,268 -1017,1000-1099,1017. 负二进制转换,负二进制转换,https://leetcode.cn/problems/convert-to-base-2/,convert-to-base-2,数学,https://algo.itcharge.cn/Solutions/1000-1099/convert-to-base-2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1017.%20%E8%B4%9F%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.md,65.0%,中等,255 -1018,1000-1099,1018. 可被 5 整除的二进制前缀,可被 5 整除的二进制前缀,https://leetcode.cn/problems/binary-prefix-divisible-by-5/,binary-prefix-divisible-by-5,数组,https://algo.itcharge.cn/Solutions/1000-1099/binary-prefix-divisible-by-5/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1018.%20%E5%8F%AF%E8%A2%AB%205%20%E6%95%B4%E9%99%A4%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%89%8D%E7%BC%80.md,50.6%,简单,450 -1019,1000-1099,1019. 链表中的下一个更大节点,链表中的下一个更大节点,https://leetcode.cn/problems/next-greater-node-in-linked-list/,next-greater-node-in-linked-list,栈、数组、链表、单调栈,https://algo.itcharge.cn/Solutions/1000-1099/next-greater-node-in-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1019.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E8%8A%82%E7%82%B9.md,64.2%,中等,654 -1020,1000-1099,1020. 飞地的数量,飞地的数量,https://leetcode.cn/problems/number-of-enclaves/,number-of-enclaves,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/1000-1099/number-of-enclaves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1020.%20%E9%A3%9E%E5%9C%B0%E7%9A%84%E6%95%B0%E9%87%8F.md,62.3%,中等,836 -1021,1000-1099,1021. 删除最外层的括号,删除最外层的括号,https://leetcode.cn/problems/remove-outermost-parentheses/,remove-outermost-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/1000-1099/remove-outermost-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1021.%20%E5%88%A0%E9%99%A4%E6%9C%80%E5%A4%96%E5%B1%82%E7%9A%84%E6%8B%AC%E5%8F%B7.md,81.4%,简单,1078 -1022,1000-1099,1022. 从根到叶的二进制数之和,从根到叶的二进制数之和,https://leetcode.cn/problems/sum-of-root-to-leaf-binary-numbers/,sum-of-root-to-leaf-binary-numbers,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1000-1099/sum-of-root-to-leaf-binary-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1022.%20%E4%BB%8E%E6%A0%B9%E5%88%B0%E5%8F%B6%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E4%B9%8B%E5%92%8C.md,74.8%,简单,654 -1023,1000-1099,1023. 驼峰式匹配,驼峰式匹配,https://leetcode.cn/problems/camelcase-matching/,camelcase-matching,字典树、双指针、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/1000-1099/camelcase-matching/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1023.%20%E9%A9%BC%E5%B3%B0%E5%BC%8F%E5%8C%B9%E9%85%8D.md,64.6%,中等,475 -1024,1000-1099,1024. 视频拼接,视频拼接,https://leetcode.cn/problems/video-stitching/,video-stitching,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/video-stitching/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1024.%20%E8%A7%86%E9%A2%91%E6%8B%BC%E6%8E%A5.md,53.1%,中等,547 -1025,1000-1099,1025. 除数博弈,除数博弈,https://leetcode.cn/problems/divisor-game/,divisor-game,脑筋急转弯、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1000-1099/divisor-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1025.%20%E9%99%A4%E6%95%B0%E5%8D%9A%E5%BC%88.md,70.7%,简单,796 -1026,1000-1099,1026. 节点与其祖先之间的最大差值,节点与其祖先之间的最大差值,https://leetcode.cn/problems/maximum-difference-between-node-and-ancestor/,maximum-difference-between-node-and-ancestor,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1000-1099/maximum-difference-between-node-and-ancestor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1026.%20%E8%8A%82%E7%82%B9%E4%B8%8E%E5%85%B6%E7%A5%96%E5%85%88%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B7%AE%E5%80%BC.md,75.2%,中等,478 -1027,1000-1099,1027. 最长等差数列,最长等差数列,https://leetcode.cn/problems/longest-arithmetic-subsequence/,longest-arithmetic-subsequence,数组、哈希表、二分查找、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/longest-arithmetic-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1027.%20%E6%9C%80%E9%95%BF%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97.md,49.4%,中等,317 -1028,1000-1099,1028. 从先序遍历还原二叉树,从先序遍历还原二叉树,https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/,recover-a-tree-from-preorder-traversal,树、深度优先搜索、字符串、二叉树,https://algo.itcharge.cn/Solutions/1000-1099/recover-a-tree-from-preorder-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1028.%20%E4%BB%8E%E5%85%88%E5%BA%8F%E9%81%8D%E5%8E%86%E8%BF%98%E5%8E%9F%E4%BA%8C%E5%8F%89%E6%A0%91.md,72.9%,困难,535 -1029,1000-1099,1029. 两地调度,两地调度,https://leetcode.cn/problems/two-city-scheduling/,two-city-scheduling,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1000-1099/two-city-scheduling/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1029.%20%E4%B8%A4%E5%9C%B0%E8%B0%83%E5%BA%A6.md,68.8%,中等,279 -1030,1000-1099,1030. 距离顺序排列矩阵单元格,距离顺序排列矩阵单元格,https://leetcode.cn/problems/matrix-cells-in-distance-order/,matrix-cells-in-distance-order,几何、数组、数学、矩阵、排序,https://algo.itcharge.cn/Solutions/1000-1099/matrix-cells-in-distance-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1030.%20%E8%B7%9D%E7%A6%BB%E9%A1%BA%E5%BA%8F%E6%8E%92%E5%88%97%E7%9F%A9%E9%98%B5%E5%8D%95%E5%85%83%E6%A0%BC.md,70.6%,简单,527 -1031,1000-1099,1031. 两个非重叠子数组的最大和,两个非重叠子数组的最大和,https://leetcode.cn/problems/maximum-sum-of-two-non-overlapping-subarrays/,maximum-sum-of-two-non-overlapping-subarrays,数组、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/1000-1099/maximum-sum-of-two-non-overlapping-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1031.%20%E4%B8%A4%E4%B8%AA%E9%9D%9E%E9%87%8D%E5%8F%A0%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,65.6%,中等,303 -1032,1000-1099,1032. 字符流,字符流,https://leetcode.cn/problems/stream-of-characters/,stream-of-characters,设计、字典树、数组、字符串、数据流,https://algo.itcharge.cn/Solutions/1000-1099/stream-of-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1032.%20%E5%AD%97%E7%AC%A6%E6%B5%81.md,56.6%,困难,259 -1033,1000-1099,1033. 移动石子直到连续,移动石子直到连续,https://leetcode.cn/problems/moving-stones-until-consecutive/,moving-stones-until-consecutive,脑筋急转弯、数学,https://algo.itcharge.cn/Solutions/1000-1099/moving-stones-until-consecutive/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1033.%20%E7%A7%BB%E5%8A%A8%E7%9F%B3%E5%AD%90%E7%9B%B4%E5%88%B0%E8%BF%9E%E7%BB%AD.md,49.3%,中等,301 -1034,1000-1099,1034. 边界着色,边界着色,https://leetcode.cn/problems/coloring-a-border/,coloring-a-border,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1000-1099/coloring-a-border/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1034.%20%E8%BE%B9%E7%95%8C%E7%9D%80%E8%89%B2.md,55.1%,中等,534 -1035,1000-1099,1035. 不相交的线,不相交的线,https://leetcode.cn/problems/uncrossed-lines/,uncrossed-lines,数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/uncrossed-lines/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1035.%20%E4%B8%8D%E7%9B%B8%E4%BA%A4%E7%9A%84%E7%BA%BF.md,70.3%,中等,611 -1036,1000-1099,1036. 逃离大迷宫,逃离大迷宫,https://leetcode.cn/problems/escape-a-large-maze/,escape-a-large-maze,深度优先搜索、广度优先搜索、数组、哈希表,https://algo.itcharge.cn/Solutions/1000-1099/escape-a-large-maze/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1036.%20%E9%80%83%E7%A6%BB%E5%A4%A7%E8%BF%B7%E5%AE%AB.md,46.8%,困难,218 -1037,1000-1099,1037. 有效的回旋镖,有效的回旋镖,https://leetcode.cn/problems/valid-boomerang/,valid-boomerang,几何、数组、数学,https://algo.itcharge.cn/Solutions/1000-1099/valid-boomerang/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1037.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%9B%9E%E6%97%8B%E9%95%96.md,48.5%,简单,424 -1038,1000-1099,1038. 从二叉搜索树到更大和树,从二叉搜索树到更大和树,https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/,binary-search-tree-to-greater-sum-tree,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/1000-1099/binary-search-tree-to-greater-sum-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1038.%20%E4%BB%8E%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%88%B0%E6%9B%B4%E5%A4%A7%E5%92%8C%E6%A0%91.md,81.3%,中等,530 -1039,1000-1099,1039. 多边形三角剖分的最低得分,多边形三角剖分的最低得分,https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/,minimum-score-triangulation-of-polygon,数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/minimum-score-triangulation-of-polygon/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1039.%20%E5%A4%9A%E8%BE%B9%E5%BD%A2%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%9A%84%E6%9C%80%E4%BD%8E%E5%BE%97%E5%88%86.md,64.5%,中等,149 -1040,1000-1099,1040. 移动石子直到连续 II,移动石子直到连续 II,https://leetcode.cn/problems/moving-stones-until-consecutive-ii/,moving-stones-until-consecutive-ii,数组、数学、双指针、排序,https://algo.itcharge.cn/Solutions/1000-1099/moving-stones-until-consecutive-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1040.%20%E7%A7%BB%E5%8A%A8%E7%9F%B3%E5%AD%90%E7%9B%B4%E5%88%B0%E8%BF%9E%E7%BB%AD%20II.md,66.2%,中等,94 -1041,1000-1099,1041. 困于环中的机器人,困于环中的机器人,https://leetcode.cn/problems/robot-bounded-in-circle/,robot-bounded-in-circle,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/1000-1099/robot-bounded-in-circle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1041.%20%E5%9B%B0%E4%BA%8E%E7%8E%AF%E4%B8%AD%E7%9A%84%E6%9C%BA%E5%99%A8%E4%BA%BA.md,57.0%,中等,431 -1042,1000-1099,1042. 不邻接植花,不邻接植花,https://leetcode.cn/problems/flower-planting-with-no-adjacent/,flower-planting-with-no-adjacent,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/1000-1099/flower-planting-with-no-adjacent/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1042.%20%E4%B8%8D%E9%82%BB%E6%8E%A5%E6%A4%8D%E8%8A%B1.md,61.1%,中等,342 -1043,1000-1099,1043. 分隔数组以得到最大和,分隔数组以得到最大和,https://leetcode.cn/problems/partition-array-for-maximum-sum/,partition-array-for-maximum-sum,数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/partition-array-for-maximum-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1043.%20%E5%88%86%E9%9A%94%E6%95%B0%E7%BB%84%E4%BB%A5%E5%BE%97%E5%88%B0%E6%9C%80%E5%A4%A7%E5%92%8C.md,75.5%,中等,297 -1044,1000-1099,1044. 最长重复子串,最长重复子串,https://leetcode.cn/problems/longest-duplicate-substring/,longest-duplicate-substring,字符串、二分查找、后缀数组、滑动窗口、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1000-1099/longest-duplicate-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1044.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E4%B8%B2.md,35.4%,困难,239 -1045,1000-1099,1045. 买下所有产品的客户,买下所有产品的客户,https://leetcode.cn/problems/customers-who-bought-all-products/,customers-who-bought-all-products,数据库,https://algo.itcharge.cn/Solutions/1000-1099/customers-who-bought-all-products/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1045.%20%E4%B9%B0%E4%B8%8B%E6%89%80%E6%9C%89%E4%BA%A7%E5%93%81%E7%9A%84%E5%AE%A2%E6%88%B7.md,61.6%,中等,202 -1046,1000-1099,1046. 最后一块石头的重量,最后一块石头的重量,https://leetcode.cn/problems/last-stone-weight/,last-stone-weight,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1000-1099/last-stone-weight/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1046.%20%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F.md,65.6%,简单,1214 -1047,1000-1099,1047. 删除字符串中的所有相邻重复项,删除字符串中的所有相邻重复项,https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/,remove-all-adjacent-duplicates-in-string,栈、字符串,https://algo.itcharge.cn/Solutions/1000-1099/remove-all-adjacent-duplicates-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1047.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md,72.4%,简单,1615 -1048,1000-1099,1048. 最长字符串链,最长字符串链,https://leetcode.cn/problems/longest-string-chain/,longest-string-chain,数组、哈希表、双指针、字符串、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/longest-string-chain/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1048.%20%E6%9C%80%E9%95%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%93%BE.md,55.8%,中等,399 -1049,1000-1099,1049. 最后一块石头的重量 II,最后一块石头的重量 II,https://leetcode.cn/problems/last-stone-weight-ii/,last-stone-weight-ii,数组、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/last-stone-weight-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1049.%20%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F%20II.md,69.2%,中等,840 -1050,1000-1099,1050. 合作过至少三次的演员和导演,合作过至少三次的演员和导演,https://leetcode.cn/problems/actors-and-directors-who-cooperated-at-least-three-times/,actors-and-directors-who-cooperated-at-least-three-times,数据库,https://algo.itcharge.cn/Solutions/1000-1099/actors-and-directors-who-cooperated-at-least-three-times/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1050.%20%E5%90%88%E4%BD%9C%E8%BF%87%E8%87%B3%E5%B0%91%E4%B8%89%E6%AC%A1%E7%9A%84%E6%BC%94%E5%91%98%E5%92%8C%E5%AF%BC%E6%BC%94.md,76.8%,简单,207 -1051,1000-1099,1051. 高度检查器,高度检查器,https://leetcode.cn/problems/height-checker/,height-checker,数组、计数排序、排序,https://algo.itcharge.cn/Solutions/1000-1099/height-checker/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1051.%20%E9%AB%98%E5%BA%A6%E6%A3%80%E6%9F%A5%E5%99%A8.md,80.1%,简单,685 -1052,1000-1099,1052. 爱生气的书店老板,爱生气的书店老板,https://leetcode.cn/problems/grumpy-bookstore-owner/,grumpy-bookstore-owner,数组、滑动窗口,https://algo.itcharge.cn/Solutions/1000-1099/grumpy-bookstore-owner/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1052.%20%E7%88%B1%E7%94%9F%E6%B0%94%E7%9A%84%E4%B9%A6%E5%BA%97%E8%80%81%E6%9D%BF.md,58.0%,中等,909 -1053,1000-1099,1053. 交换一次的先前排列,交换一次的先前排列,https://leetcode.cn/problems/previous-permutation-with-one-swap/,previous-permutation-with-one-swap,贪心、数组,https://algo.itcharge.cn/Solutions/1000-1099/previous-permutation-with-one-swap/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1053.%20%E4%BA%A4%E6%8D%A2%E4%B8%80%E6%AC%A1%E7%9A%84%E5%85%88%E5%89%8D%E6%8E%92%E5%88%97.md,48.3%,中等,302 -1054,1000-1099,1054. 距离相等的条形码,距离相等的条形码,https://leetcode.cn/problems/distant-barcodes/,distant-barcodes,贪心、数组、哈希表、计数、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1000-1099/distant-barcodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1054.%20%E8%B7%9D%E7%A6%BB%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9D%A1%E5%BD%A2%E7%A0%81.md,44.8%,中等,312 -1055,1000-1099,1055. 形成字符串的最短路径,形成字符串的最短路径,https://leetcode.cn/problems/shortest-way-to-form-string/,shortest-way-to-form-string,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/1000-1099/shortest-way-to-form-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1055.%20%E5%BD%A2%E6%88%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,60.5%,中等,150 -1056,1000-1099,1056. 易混淆数,易混淆数,https://leetcode.cn/problems/confusing-number/,confusing-number,数学,https://algo.itcharge.cn/Solutions/1000-1099/confusing-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1056.%20%E6%98%93%E6%B7%B7%E6%B7%86%E6%95%B0.md,43.6%,简单,120 -1057,1000-1099,1057. 校园自行车分配,校园自行车分配,https://leetcode.cn/problems/campus-bikes/,campus-bikes,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1000-1099/campus-bikes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1057.%20%E6%A0%A1%E5%9B%AD%E8%87%AA%E8%A1%8C%E8%BD%A6%E5%88%86%E9%85%8D.md,51.0%,中等,59 -1058,1000-1099,1058. 最小化舍入误差以满足目标,最小化舍入误差以满足目标,https://leetcode.cn/problems/minimize-rounding-error-to-meet-target/,minimize-rounding-error-to-meet-target,贪心、数组、数学、字符串,https://algo.itcharge.cn/Solutions/1000-1099/minimize-rounding-error-to-meet-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1058.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E8%88%8D%E5%85%A5%E8%AF%AF%E5%B7%AE%E4%BB%A5%E6%BB%A1%E8%B6%B3%E7%9B%AE%E6%A0%87.md,37.4%,中等,50 -1059,1000-1099,1059. 从始点到终点的所有路径,从始点到终点的所有路径,https://leetcode.cn/problems/all-paths-from-source-lead-to-destination/,all-paths-from-source-lead-to-destination,深度优先搜索、图,https://algo.itcharge.cn/Solutions/1000-1099/all-paths-from-source-lead-to-destination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1059.%20%E4%BB%8E%E5%A7%8B%E7%82%B9%E5%88%B0%E7%BB%88%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84.md,35.8%,中等,57 -1060,1000-1099,1060. 有序数组中的缺失元素,有序数组中的缺失元素,https://leetcode.cn/problems/missing-element-in-sorted-array/,missing-element-in-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/1000-1099/missing-element-in-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1060.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%BC%BA%E5%A4%B1%E5%85%83%E7%B4%A0.md,54.7%,中等,158 -1061,1000-1099,1061. 按字典序排列最小的等效字符串,按字典序排列最小的等效字符串,https://leetcode.cn/problems/lexicographically-smallest-equivalent-string/,lexicographically-smallest-equivalent-string,并查集、字符串,https://algo.itcharge.cn/Solutions/1000-1099/lexicographically-smallest-equivalent-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1061.%20%E6%8C%89%E5%AD%97%E5%85%B8%E5%BA%8F%E6%8E%92%E5%88%97%E6%9C%80%E5%B0%8F%E7%9A%84%E7%AD%89%E6%95%88%E5%AD%97%E7%AC%A6%E4%B8%B2.md,64.7%,中等,119 -1062,1000-1099,1062. 最长重复子串,最长重复子串,https://leetcode.cn/problems/longest-repeating-substring/,longest-repeating-substring,字符串、二分查找、动态规划、后缀数组、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1000-1099/longest-repeating-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1062.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E4%B8%B2.md,56.9%,中等,87 -1063,1000-1099,1063. 有效子数组的数目,有效子数组的数目,https://leetcode.cn/problems/number-of-valid-subarrays/,number-of-valid-subarrays,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/1000-1099/number-of-valid-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1063.%20%E6%9C%89%E6%95%88%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,73.8%,困难,61 -1064,1000-1099,1064. 不动点,不动点,https://leetcode.cn/problems/fixed-point/,fixed-point,数组、二分查找,https://algo.itcharge.cn/Solutions/1000-1099/fixed-point/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1064.%20%E4%B8%8D%E5%8A%A8%E7%82%B9.md,64.8%,简单,105 -1065,1000-1099,1065. 字符串的索引对,字符串的索引对,https://leetcode.cn/problems/index-pairs-of-a-string/,index-pairs-of-a-string,字典树、数组、字符串、排序,https://algo.itcharge.cn/Solutions/1000-1099/index-pairs-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1065.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E7%B4%A2%E5%BC%95%E5%AF%B9.md,56.5%,简单,110 -1066,1000-1099,1066. 校园自行车分配 II,校园自行车分配 II,https://leetcode.cn/problems/campus-bikes-ii/,campus-bikes-ii,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1000-1099/campus-bikes-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1066.%20%E6%A0%A1%E5%9B%AD%E8%87%AA%E8%A1%8C%E8%BD%A6%E5%88%86%E9%85%8D%20II.md,51.3%,中等,71 -1067,1000-1099,1067. 范围内的数字计数,范围内的数字计数,https://leetcode.cn/problems/digit-count-in-range/,digit-count-in-range,数学、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/digit-count-in-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1067.%20%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E6%95%B0%E5%AD%97%E8%AE%A1%E6%95%B0.md,48.3%,困难,51 -1068,1000-1099,1068. 产品销售分析 I,产品销售分析 I,https://leetcode.cn/problems/product-sales-analysis-i/,product-sales-analysis-i,数据库,https://algo.itcharge.cn/Solutions/1000-1099/product-sales-analysis-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1068.%20%E4%BA%A7%E5%93%81%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20I.md,86.7%,简单,112 -1069,1000-1099,1069. 产品销售分析 II,产品销售分析 II,https://leetcode.cn/problems/product-sales-analysis-ii/,product-sales-analysis-ii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/product-sales-analysis-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1069.%20%E4%BA%A7%E5%93%81%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20II.md,81.8%,简单,85 -1070,1000-1099,1070. 产品销售分析 III,产品销售分析 III,https://leetcode.cn/problems/product-sales-analysis-iii/,product-sales-analysis-iii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/product-sales-analysis-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1070.%20%E4%BA%A7%E5%93%81%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20III.md,48.0%,中等,106 -1071,1000-1099,1071. 字符串的最大公因子,字符串的最大公因子,https://leetcode.cn/problems/greatest-common-divisor-of-strings/,greatest-common-divisor-of-strings,数学、字符串,https://algo.itcharge.cn/Solutions/1000-1099/greatest-common-divisor-of-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1071.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%AC%E5%9B%A0%E5%AD%90.md,57.9%,简单,818 -1072,1000-1099,1072. 按列翻转得到最大值等行数,按列翻转得到最大值等行数,https://leetcode.cn/problems/flip-columns-for-maximum-number-of-equal-rows/,flip-columns-for-maximum-number-of-equal-rows,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/1000-1099/flip-columns-for-maximum-number-of-equal-rows/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1072.%20%E6%8C%89%E5%88%97%E7%BF%BB%E8%BD%AC%E5%BE%97%E5%88%B0%E6%9C%80%E5%A4%A7%E5%80%BC%E7%AD%89%E8%A1%8C%E6%95%B0.md,71.4%,中等,206 -1073,1000-1099,1073. 负二进制数相加,负二进制数相加,https://leetcode.cn/problems/adding-two-negabinary-numbers/,adding-two-negabinary-numbers,数组、数学,https://algo.itcharge.cn/Solutions/1000-1099/adding-two-negabinary-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1073.%20%E8%B4%9F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E7%9B%B8%E5%8A%A0.md,41.6%,中等,193 -1074,1000-1099,1074. 元素和为目标值的子矩阵数量,元素和为目标值的子矩阵数量,https://leetcode.cn/problems/number-of-submatrices-that-sum-to-target/,number-of-submatrices-that-sum-to-target,数组、哈希表、矩阵、前缀和,https://algo.itcharge.cn/Solutions/1000-1099/number-of-submatrices-that-sum-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1074.%20%E5%85%83%E7%B4%A0%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E5%AD%90%E7%9F%A9%E9%98%B5%E6%95%B0%E9%87%8F.md,67.4%,困难,223 -1075,1000-1099,1075. 项目员工 I,项目员工 I,https://leetcode.cn/problems/project-employees-i/,project-employees-i,数据库,https://algo.itcharge.cn/Solutions/1000-1099/project-employees-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1075.%20%E9%A1%B9%E7%9B%AE%E5%91%98%E5%B7%A5%20I.md,70.2%,简单,132 -1076,1000-1099,1076. 项目员工II,项目员工II,https://leetcode.cn/problems/project-employees-ii/,project-employees-ii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/project-employees-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1076.%20%E9%A1%B9%E7%9B%AE%E5%91%98%E5%B7%A5II.md,49.4%,简单,132 -1077,1000-1099,1077. 项目员工 III,项目员工 III,https://leetcode.cn/problems/project-employees-iii/,project-employees-iii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/project-employees-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1077.%20%E9%A1%B9%E7%9B%AE%E5%91%98%E5%B7%A5%20III.md,72.3%,中等,147 -1078,1000-1099,1078. Bigram 分词,Bigram 分词,https://leetcode.cn/problems/occurrences-after-bigram/,occurrences-after-bigram,字符串,https://algo.itcharge.cn/Solutions/1000-1099/occurrences-after-bigram/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1078.%20Bigram%20%E5%88%86%E8%AF%8D.md,65.2%,简单,521 -1079,1000-1099,1079. 活字印刷,活字印刷,https://leetcode.cn/problems/letter-tile-possibilities/,letter-tile-possibilities,哈希表、字符串、回溯、计数,https://algo.itcharge.cn/Solutions/1000-1099/letter-tile-possibilities/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1079.%20%E6%B4%BB%E5%AD%97%E5%8D%B0%E5%88%B7.md,79.1%,中等,405 -1080,1000-1099,1080. 根到叶路径上的不足节点,根到叶路径上的不足节点,https://leetcode.cn/problems/insufficient-nodes-in-root-to-leaf-paths/,insufficient-nodes-in-root-to-leaf-paths,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1000-1099/insufficient-nodes-in-root-to-leaf-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1080.%20%E6%A0%B9%E5%88%B0%E5%8F%B6%E8%B7%AF%E5%BE%84%E4%B8%8A%E7%9A%84%E4%B8%8D%E8%B6%B3%E8%8A%82%E7%82%B9.md,61.4%,中等,271 -1081,1000-1099,1081. 不同字符的最小子序列,不同字符的最小子序列,https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/,smallest-subsequence-of-distinct-characters,栈、贪心、字符串、单调栈,https://algo.itcharge.cn/Solutions/1000-1099/smallest-subsequence-of-distinct-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1081.%20%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E5%BA%8F%E5%88%97.md,58.4%,中等,241 -1082,1000-1099,1082. 销售分析 I,销售分析 I,https://leetcode.cn/problems/sales-analysis-i/,sales-analysis-i,数据库,https://algo.itcharge.cn/Solutions/1000-1099/sales-analysis-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1082.%20%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20I.md,76.6%,简单,139 -1083,1000-1099,1083. 销售分析 II,销售分析 II,https://leetcode.cn/problems/sales-analysis-ii/,sales-analysis-ii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/sales-analysis-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1083.%20%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20II.md,53.0%,简单,156 -1084,1000-1099,1084. 销售分析III,销售分析III,https://leetcode.cn/problems/sales-analysis-iii/,sales-analysis-iii,数据库,https://algo.itcharge.cn/Solutions/1000-1099/sales-analysis-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1084.%20%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90III.md,52.2%,简单,351 -1085,1000-1099,1085. 最小元素各数位之和,最小元素各数位之和,https://leetcode.cn/problems/sum-of-digits-in-the-minimum-number/,sum-of-digits-in-the-minimum-number,数组、数学,https://algo.itcharge.cn/Solutions/1000-1099/sum-of-digits-in-the-minimum-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1085.%20%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E5%90%84%E6%95%B0%E4%BD%8D%E4%B9%8B%E5%92%8C.md,77.9%,简单,110 -1086,1000-1099,1086. 前五科的均分,前五科的均分,https://leetcode.cn/problems/high-five/,high-five,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1000-1099/high-five/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1086.%20%E5%89%8D%E4%BA%94%E7%A7%91%E7%9A%84%E5%9D%87%E5%88%86.md,67.7%,简单,120 -1087,1000-1099,1087. 花括号展开,花括号展开,https://leetcode.cn/problems/brace-expansion/,brace-expansion,广度优先搜索、字符串、回溯,https://algo.itcharge.cn/Solutions/1000-1099/brace-expansion/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1087.%20%E8%8A%B1%E6%8B%AC%E5%8F%B7%E5%B1%95%E5%BC%80.md,56.8%,中等,89 -1088,1000-1099,1088. 易混淆数 II,易混淆数 II,https://leetcode.cn/problems/confusing-number-ii/,confusing-number-ii,数学、回溯,https://algo.itcharge.cn/Solutions/1000-1099/confusing-number-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1088.%20%E6%98%93%E6%B7%B7%E6%B7%86%E6%95%B0%20II.md,49.9%,困难,50 -1089,1000-1099,1089. 复写零,复写零,https://leetcode.cn/problems/duplicate-zeros/,duplicate-zeros,数组、双指针,https://algo.itcharge.cn/Solutions/1000-1099/duplicate-zeros/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1089.%20%E5%A4%8D%E5%86%99%E9%9B%B6.md,60.1%,简单,762 -1090,1000-1099,1090. 受标签影响的最大值,受标签影响的最大值,https://leetcode.cn/problems/largest-values-from-labels/,largest-values-from-labels,贪心、数组、哈希表、计数、排序,https://algo.itcharge.cn/Solutions/1000-1099/largest-values-from-labels/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1090.%20%E5%8F%97%E6%A0%87%E7%AD%BE%E5%BD%B1%E5%93%8D%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,66.9%,中等,313 -1091,1000-1099,1091. 二进制矩阵中的最短路径,二进制矩阵中的最短路径,https://leetcode.cn/problems/shortest-path-in-binary-matrix/,shortest-path-in-binary-matrix,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1000-1099/shortest-path-in-binary-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1091.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,40.4%,中等,736 -1092,1000-1099,1092. 最短公共超序列,最短公共超序列,https://leetcode.cn/problems/shortest-common-supersequence/,shortest-common-supersequence,字符串、动态规划,https://algo.itcharge.cn/Solutions/1000-1099/shortest-common-supersequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1092.%20%E6%9C%80%E7%9F%AD%E5%85%AC%E5%85%B1%E8%B6%85%E5%BA%8F%E5%88%97.md,58.5%,困难,172 -1093,1000-1099,1093. 大样本统计,大样本统计,https://leetcode.cn/problems/statistics-from-a-large-sample/,statistics-from-a-large-sample,数组、数学、概率与统计,https://algo.itcharge.cn/Solutions/1000-1099/statistics-from-a-large-sample/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1093.%20%E5%A4%A7%E6%A0%B7%E6%9C%AC%E7%BB%9F%E8%AE%A1.md,41.9%,中等,206 -1094,1000-1099,1094. 拼车,拼车,https://leetcode.cn/problems/car-pooling/,car-pooling,数组、前缀和、排序、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/1000-1099/car-pooling/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1094.%20%E6%8B%BC%E8%BD%A6.md,51.8%,中等,1350 -1095,1000-1099,1095. 山脉数组中查找目标值,山脉数组中查找目标值,https://leetcode.cn/problems/find-in-mountain-array/,find-in-mountain-array,数组、二分查找、交互,https://algo.itcharge.cn/Solutions/1000-1099/find-in-mountain-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1095.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%80%BC.md,37.7%,困难,470 -1096,1000-1099,1096. 花括号展开 II,花括号展开 II,https://leetcode.cn/problems/brace-expansion-ii/,brace-expansion-ii,栈、广度优先搜索、字符串、回溯,https://algo.itcharge.cn/Solutions/1000-1099/brace-expansion-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1096.%20%E8%8A%B1%E6%8B%AC%E5%8F%B7%E5%B1%95%E5%BC%80%20II.md,73.5%,困难,155 -1097,1000-1099,1097. 游戏玩法分析 V,游戏玩法分析 V,https://leetcode.cn/problems/game-play-analysis-v/,game-play-analysis-v,数据库,https://algo.itcharge.cn/Solutions/1000-1099/game-play-analysis-v/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1097.%20%E6%B8%B8%E6%88%8F%E7%8E%A9%E6%B3%95%E5%88%86%E6%9E%90%20V.md,52.4%,困难,169 -1098,1000-1099,1098. 小众书籍,小众书籍,https://leetcode.cn/problems/unpopular-books/,unpopular-books,数据库,https://algo.itcharge.cn/Solutions/1000-1099/unpopular-books/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1098.%20%E5%B0%8F%E4%BC%97%E4%B9%A6%E7%B1%8D.md,47.1%,中等,135 -1099,1000-1099,1099. 小于 K 的两数之和,小于 K 的两数之和,https://leetcode.cn/problems/two-sum-less-than-k/,two-sum-less-than-k,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/1000-1099/two-sum-less-than-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1099.%20%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md,59.8%,简单,145 -1100,1100-1199,1100. 长度为 K 的无重复字符子串,长度为 K 的无重复字符子串,https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/,find-k-length-substrings-with-no-repeated-characters,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1100.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E5%AD%90%E4%B8%B2.md,69.8%,中等,180 -1101,1100-1199,1101. 彼此熟识的最早时间,彼此熟识的最早时间,https://leetcode.cn/problems/the-earliest-moment-when-everyone-become-friends/,the-earliest-moment-when-everyone-become-friends,并查集、数组,https://algo.itcharge.cn/Solutions/1100-1199/the-earliest-moment-when-everyone-become-friends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1101.%20%E5%BD%BC%E6%AD%A4%E7%86%9F%E8%AF%86%E7%9A%84%E6%9C%80%E6%97%A9%E6%97%B6%E9%97%B4.md,68.8%,中等,108 -1102,1100-1199,1102. 得分最高的路径,得分最高的路径,https://leetcode.cn/problems/path-with-maximum-minimum-value/,path-with-maximum-minimum-value,深度优先搜索、广度优先搜索、并查集、数组、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/path-with-maximum-minimum-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1102.%20%E5%BE%97%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E8%B7%AF%E5%BE%84.md,40.2%,中等,112 -1103,1100-1199,1103. 分糖果 II,分糖果 II,https://leetcode.cn/problems/distribute-candies-to-people/,distribute-candies-to-people,数学、模拟,https://algo.itcharge.cn/Solutions/1100-1199/distribute-candies-to-people/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1103.%20%E5%88%86%E7%B3%96%E6%9E%9C%20II.md,63.7%,简单,1236 -1104,1100-1199,1104. 二叉树寻路,二叉树寻路,https://leetcode.cn/problems/path-in-zigzag-labelled-binary-tree/,path-in-zigzag-labelled-binary-tree,树、数学、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/path-in-zigzag-labelled-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%AF%BB%E8%B7%AF.md,75.9%,中等,777 -1105,1100-1199,1105. 填充书架,填充书架,https://leetcode.cn/problems/filling-bookcase-shelves/,filling-bookcase-shelves,数组、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/filling-bookcase-shelves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1105.%20%E5%A1%AB%E5%85%85%E4%B9%A6%E6%9E%B6.md,67.2%,中等,207 -1106,1100-1199,1106. 解析布尔表达式,解析布尔表达式,https://leetcode.cn/problems/parsing-a-boolean-expression/,parsing-a-boolean-expression,栈、递归、字符串,https://algo.itcharge.cn/Solutions/1100-1199/parsing-a-boolean-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1106.%20%E8%A7%A3%E6%9E%90%E5%B8%83%E5%B0%94%E8%A1%A8%E8%BE%BE%E5%BC%8F.md,68.5%,困难,572 -1107,1100-1199,1107. 每日新用户统计,每日新用户统计,https://leetcode.cn/problems/new-users-daily-count/,new-users-daily-count,数据库,https://algo.itcharge.cn/Solutions/1100-1199/new-users-daily-count/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1107.%20%E6%AF%8F%E6%97%A5%E6%96%B0%E7%94%A8%E6%88%B7%E7%BB%9F%E8%AE%A1.md,41.2%,中等,135 -1108,1100-1199,1108. IP 地址无效化,IP 地址无效化,https://leetcode.cn/problems/defanging-an-ip-address/,defanging-an-ip-address,字符串,https://algo.itcharge.cn/Solutions/1100-1199/defanging-an-ip-address/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1108.%20IP%20%E5%9C%B0%E5%9D%80%E6%97%A0%E6%95%88%E5%8C%96.md,85.4%,简单,1011 -1109,1100-1199,1109. 航班预订统计,航班预订统计,https://leetcode.cn/problems/corporate-flight-bookings/,corporate-flight-bookings,数组、前缀和,https://algo.itcharge.cn/Solutions/1100-1199/corporate-flight-bookings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md,63.6%,中等,1625 -1110,1100-1199,1110. 删点成林,删点成林,https://leetcode.cn/problems/delete-nodes-and-return-forest/,delete-nodes-and-return-forest,树、深度优先搜索、数组、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/delete-nodes-and-return-forest/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1110.%20%E5%88%A0%E7%82%B9%E6%88%90%E6%9E%97.md,69.4%,中等,511 -1111,1100-1199,1111. 有效括号的嵌套深度,有效括号的嵌套深度,https://leetcode.cn/problems/maximum-nesting-depth-of-two-valid-parentheses-strings/,maximum-nesting-depth-of-two-valid-parentheses-strings,栈、字符串,https://algo.itcharge.cn/Solutions/1100-1199/maximum-nesting-depth-of-two-valid-parentheses-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1111.%20%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7%E7%9A%84%E5%B5%8C%E5%A5%97%E6%B7%B1%E5%BA%A6.md,76.8%,中等,446 -1112,1100-1199,1112. 每位学生的最高成绩,每位学生的最高成绩,https://leetcode.cn/problems/highest-grade-for-each-student/,highest-grade-for-each-student,数据库,https://algo.itcharge.cn/Solutions/1100-1199/highest-grade-for-each-student/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1112.%20%E6%AF%8F%E4%BD%8D%E5%AD%A6%E7%94%9F%E7%9A%84%E6%9C%80%E9%AB%98%E6%88%90%E7%BB%A9.md,65.9%,中等,168 -1113,1100-1199,1113. 报告的记录,报告的记录,https://leetcode.cn/problems/reported-posts/,reported-posts,数据库,https://algo.itcharge.cn/Solutions/1100-1199/reported-posts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1113.%20%E6%8A%A5%E5%91%8A%E7%9A%84%E8%AE%B0%E5%BD%95.md,53.4%,简单,75 -1114,1100-1199,1114. 按序打印,按序打印,https://leetcode.cn/problems/print-in-order/,print-in-order,多线程,https://algo.itcharge.cn/Solutions/1100-1199/print-in-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1114.%20%E6%8C%89%E5%BA%8F%E6%89%93%E5%8D%B0.md,65.2%,简单,744 -1115,1100-1199,1115. 交替打印 FooBar,交替打印 FooBar,https://leetcode.cn/problems/print-foobar-alternately/,print-foobar-alternately,多线程,https://algo.itcharge.cn/Solutions/1100-1199/print-foobar-alternately/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1115.%20%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0%20FooBar.md,57.0%,中等,607 -1116,1100-1199,1116. 打印零与奇偶数,打印零与奇偶数,https://leetcode.cn/problems/print-zero-even-odd/,print-zero-even-odd,多线程,https://algo.itcharge.cn/Solutions/1100-1199/print-zero-even-odd/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1116.%20%E6%89%93%E5%8D%B0%E9%9B%B6%E4%B8%8E%E5%A5%87%E5%81%B6%E6%95%B0.md,54.3%,中等,460 -1117,1100-1199,1117. H2O 生成,H2O 生成,https://leetcode.cn/problems/building-h2o/,building-h2o,多线程,https://algo.itcharge.cn/Solutions/1100-1199/building-h2o/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1117.%20H2O%20%E7%94%9F%E6%88%90.md,53.9%,中等,334 -1118,1100-1199,1118. 一月有多少天,一月有多少天,https://leetcode.cn/problems/number-of-days-in-a-month/,number-of-days-in-a-month,数学,https://algo.itcharge.cn/Solutions/1100-1199/number-of-days-in-a-month/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1118.%20%E4%B8%80%E6%9C%88%E6%9C%89%E5%A4%9A%E5%B0%91%E5%A4%A9.md,64.9%,简单,68 -1119,1100-1199,1119. 删去字符串中的元音,删去字符串中的元音,https://leetcode.cn/problems/remove-vowels-from-a-string/,remove-vowels-from-a-string,字符串,https://algo.itcharge.cn/Solutions/1100-1199/remove-vowels-from-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1119.%20%E5%88%A0%E5%8E%BB%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3.md,87.2%,简单,184 -1120,1100-1199,1120. 子树的最大平均值,子树的最大平均值,https://leetcode.cn/problems/maximum-average-subtree/,maximum-average-subtree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/maximum-average-subtree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1120.%20%E5%AD%90%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E5%80%BC.md,62.9%,中等,82 -1121,1100-1199,1121. 将数组分成几个递增序列,将数组分成几个递增序列,https://leetcode.cn/problems/divide-array-into-increasing-sequences/,divide-array-into-increasing-sequences,贪心、数组,https://algo.itcharge.cn/Solutions/1100-1199/divide-array-into-increasing-sequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1121.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%86%E6%88%90%E5%87%A0%E4%B8%AA%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.md,60.6%,困难,27 -1122,1100-1199,1122. 数组的相对排序,数组的相对排序,https://leetcode.cn/problems/relative-sort-array/,relative-sort-array,数组、哈希表、计数排序、排序,https://algo.itcharge.cn/Solutions/1100-1199/relative-sort-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1122.%20%E6%95%B0%E7%BB%84%E7%9A%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md,70.5%,简单,1118 -1123,1100-1199,1123. 最深叶节点的最近公共祖先,最深叶节点的最近公共祖先,https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/,lowest-common-ancestor-of-deepest-leaves,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/lowest-common-ancestor-of-deepest-leaves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1123.%20%E6%9C%80%E6%B7%B1%E5%8F%B6%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md,71.8%,中等,171 -1124,1100-1199,1124. 表现良好的最长时间段,表现良好的最长时间段,https://leetcode.cn/problems/longest-well-performing-interval/,longest-well-performing-interval,栈、数组、哈希表、前缀和、单调栈,https://algo.itcharge.cn/Solutions/1100-1199/longest-well-performing-interval/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1124.%20%E8%A1%A8%E7%8E%B0%E8%89%AF%E5%A5%BD%E7%9A%84%E6%9C%80%E9%95%BF%E6%97%B6%E9%97%B4%E6%AE%B5.md,39.2%,中等,371 -1125,1100-1199,1125. 最小的必要团队,最小的必要团队,https://leetcode.cn/problems/smallest-sufficient-team/,smallest-sufficient-team,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1100-1199/smallest-sufficient-team/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1125.%20%E6%9C%80%E5%B0%8F%E7%9A%84%E5%BF%85%E8%A6%81%E5%9B%A2%E9%98%9F.md,61.1%,困难,151 -1126,1100-1199,1126. 查询活跃业务,查询活跃业务,https://leetcode.cn/problems/active-businesses/,active-businesses,数据库,https://algo.itcharge.cn/Solutions/1100-1199/active-businesses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1126.%20%E6%9F%A5%E8%AF%A2%E6%B4%BB%E8%B7%83%E4%B8%9A%E5%8A%A1.md,68.1%,中等,150 -1127,1100-1199,1127. 用户购买平台,用户购买平台,https://leetcode.cn/problems/user-purchase-platform/,user-purchase-platform,数据库,https://algo.itcharge.cn/Solutions/1100-1199/user-purchase-platform/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1127.%20%E7%94%A8%E6%88%B7%E8%B4%AD%E4%B9%B0%E5%B9%B3%E5%8F%B0.md,43.0%,困难,108 -1128,1100-1199,1128. 等价多米诺骨牌对的数量,等价多米诺骨牌对的数量,https://leetcode.cn/problems/number-of-equivalent-domino-pairs/,number-of-equivalent-domino-pairs,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1100-1199/number-of-equivalent-domino-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1128.%20%E7%AD%89%E4%BB%B7%E5%A4%9A%E7%B1%B3%E8%AF%BA%E9%AA%A8%E7%89%8C%E5%AF%B9%E7%9A%84%E6%95%B0%E9%87%8F.md,54.1%,简单,547 -1129,1100-1199,1129. 颜色交替的最短路径,颜色交替的最短路径,https://leetcode.cn/problems/shortest-path-with-alternating-colors/,shortest-path-with-alternating-colors,广度优先搜索、图,https://algo.itcharge.cn/Solutions/1100-1199/shortest-path-with-alternating-colors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1129.%20%E9%A2%9C%E8%89%B2%E4%BA%A4%E6%9B%BF%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,48.7%,中等,400 -1130,1100-1199,1130. 叶值的最小代价生成树,叶值的最小代价生成树,https://leetcode.cn/problems/minimum-cost-tree-from-leaf-values/,minimum-cost-tree-from-leaf-values,栈、贪心、数组、动态规划、单调栈,https://algo.itcharge.cn/Solutions/1100-1199/minimum-cost-tree-from-leaf-values/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1130.%20%E5%8F%B6%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%E7%94%9F%E6%88%90%E6%A0%91.md,70.8%,中等,253 -1131,1100-1199,1131. 绝对值表达式的最大值,绝对值表达式的最大值,https://leetcode.cn/problems/maximum-of-absolute-value-expression/,maximum-of-absolute-value-expression,数组、数学,https://algo.itcharge.cn/Solutions/1100-1199/maximum-of-absolute-value-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1131.%20%E7%BB%9D%E5%AF%B9%E5%80%BC%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,47.5%,中等,69 -1132,1100-1199,1132. 报告的记录 II,报告的记录 II,https://leetcode.cn/problems/reported-posts-ii/,reported-posts-ii,数据库,https://algo.itcharge.cn/Solutions/1100-1199/reported-posts-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1132.%20%E6%8A%A5%E5%91%8A%E7%9A%84%E8%AE%B0%E5%BD%95%20II.md,38.6%,中等,79 -1133,1100-1199,1133. 最大唯一数,最大唯一数,https://leetcode.cn/problems/largest-unique-number/,largest-unique-number,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1100-1199/largest-unique-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1133.%20%E6%9C%80%E5%A4%A7%E5%94%AF%E4%B8%80%E6%95%B0.md,65.6%,简单,104 -1134,1100-1199,1134. 阿姆斯特朗数,阿姆斯特朗数,https://leetcode.cn/problems/armstrong-number/,armstrong-number,数学,https://algo.itcharge.cn/Solutions/1100-1199/armstrong-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1134.%20%E9%98%BF%E5%A7%86%E6%96%AF%E7%89%B9%E6%9C%97%E6%95%B0.md,77.4%,简单,78 -1135,1100-1199,1135. 最低成本联通所有城市,最低成本联通所有城市,https://leetcode.cn/problems/connecting-cities-with-minimum-cost/,connecting-cities-with-minimum-cost,并查集、图、最小生成树、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/connecting-cities-with-minimum-cost/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1135.%20%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC%E8%81%94%E9%80%9A%E6%89%80%E6%9C%89%E5%9F%8E%E5%B8%82.md,57.9%,中等,152 -1136,1100-1199,1136. 并行课程,并行课程,https://leetcode.cn/problems/parallel-courses/,parallel-courses,图、拓扑排序,https://algo.itcharge.cn/Solutions/1100-1199/parallel-courses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1136.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B.md,60.3%,中等,102 -1137,1100-1199,1137. 第 N 个泰波那契数,第 N 个泰波那契数,https://leetcode.cn/problems/n-th-tribonacci-number/,n-th-tribonacci-number,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/n-th-tribonacci-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md,61.0%,简单,1439 -1138,1100-1199,1138. 字母板上的路径,字母板上的路径,https://leetcode.cn/problems/alphabet-board-path/,alphabet-board-path,哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/alphabet-board-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1138.%20%E5%AD%97%E6%AF%8D%E6%9D%BF%E4%B8%8A%E7%9A%84%E8%B7%AF%E5%BE%84.md,51.8%,中等,440 -1139,1100-1199,1139. 最大的以 1 为边界的正方形,最大的以 1 为边界的正方形,https://leetcode.cn/problems/largest-1-bordered-square/,largest-1-bordered-square,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1100-1199/largest-1-bordered-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1139.%20%E6%9C%80%E5%A4%A7%E7%9A%84%E4%BB%A5%201%20%E4%B8%BA%E8%BE%B9%E7%95%8C%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2.md,56.1%,中等,372 -1140,1100-1199,1140. 石子游戏 II,石子游戏 II,https://leetcode.cn/problems/stone-game-ii/,stone-game-ii,数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1100-1199/stone-game-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1140.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20II.md,70.4%,中等,221 -1141,1100-1199,1141. 查询近30天活跃用户数,查询近30天活跃用户数,https://leetcode.cn/problems/user-activity-for-the-past-30-days-i/,user-activity-for-the-past-30-days-i,数据库,https://algo.itcharge.cn/Solutions/1100-1199/user-activity-for-the-past-30-days-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1141.%20%E6%9F%A5%E8%AF%A2%E8%BF%9130%E5%A4%A9%E6%B4%BB%E8%B7%83%E7%94%A8%E6%88%B7%E6%95%B0.md,44.9%,简单,321 -1142,1100-1199,1142. 过去30天的用户活动 II,过去30天的用户活动 II,https://leetcode.cn/problems/user-activity-for-the-past-30-days-ii/,user-activity-for-the-past-30-days-ii,数据库,https://algo.itcharge.cn/Solutions/1100-1199/user-activity-for-the-past-30-days-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1142.%20%E8%BF%87%E5%8E%BB30%E5%A4%A9%E7%9A%84%E7%94%A8%E6%88%B7%E6%B4%BB%E5%8A%A8%20II.md,37.2%,简单,72 -1143,1100-1199,1143. 最长公共子序列,最长公共子序列,https://leetcode.cn/problems/longest-common-subsequence/,longest-common-subsequence,字符串、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/longest-common-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md,64.9%,中等,1882 -1144,1100-1199,1144. 递减元素使数组呈锯齿状,递减元素使数组呈锯齿状,https://leetcode.cn/problems/decrease-elements-to-make-array-zigzag/,decrease-elements-to-make-array-zigzag,贪心、数组,https://algo.itcharge.cn/Solutions/1100-1199/decrease-elements-to-make-array-zigzag/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1144.%20%E9%80%92%E5%87%8F%E5%85%83%E7%B4%A0%E4%BD%BF%E6%95%B0%E7%BB%84%E5%91%88%E9%94%AF%E9%BD%BF%E7%8A%B6.md,50.5%,中等,339 -1145,1100-1199,1145. 二叉树着色游戏,二叉树着色游戏,https://leetcode.cn/problems/binary-tree-coloring-game/,binary-tree-coloring-game,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/binary-tree-coloring-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9D%80%E8%89%B2%E6%B8%B8%E6%88%8F.md,55.4%,中等,382 -1146,1100-1199,1146. 快照数组,快照数组,https://leetcode.cn/problems/snapshot-array/,snapshot-array,设计、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/1100-1199/snapshot-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1146.%20%E5%BF%AB%E7%85%A7%E6%95%B0%E7%BB%84.md,33.5%,中等,118 -1147,1100-1199,1147. 段式回文,段式回文,https://leetcode.cn/problems/longest-chunked-palindrome-decomposition/,longest-chunked-palindrome-decomposition,贪心、双指针、字符串、动态规划、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1100-1199/longest-chunked-palindrome-decomposition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1147.%20%E6%AE%B5%E5%BC%8F%E5%9B%9E%E6%96%87.md,59.0%,困难,361 -1148,1100-1199,1148. 文章浏览 I,文章浏览 I,https://leetcode.cn/problems/article-views-i/,article-views-i,数据库,https://algo.itcharge.cn/Solutions/1100-1199/article-views-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1148.%20%E6%96%87%E7%AB%A0%E6%B5%8F%E8%A7%88%20I.md,71.4%,简单,189 -1149,1100-1199,1149. 文章浏览 II,文章浏览 II,https://leetcode.cn/problems/article-views-ii/,article-views-ii,数据库,https://algo.itcharge.cn/Solutions/1100-1199/article-views-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1149.%20%E6%96%87%E7%AB%A0%E6%B5%8F%E8%A7%88%20II.md,44.5%,中等,76 -1150,1100-1199,1150. 检查一个数是否在数组中占绝大多数,检查一个数是否在数组中占绝大多数,https://leetcode.cn/problems/check-if-a-number-is-majority-element-in-a-sorted-array/,check-if-a-number-is-majority-element-in-a-sorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/1100-1199/check-if-a-number-is-majority-element-in-a-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1150.%20%E6%A3%80%E6%9F%A5%E4%B8%80%E4%B8%AA%E6%95%B0%E6%98%AF%E5%90%A6%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E5%8D%A0%E7%BB%9D%E5%A4%A7%E5%A4%9A%E6%95%B0.md,59.5%,简单,128 -1151,1100-1199,1151. 最少交换次数来组合所有的 1,最少交换次数来组合所有的 1,https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/,minimum-swaps-to-group-all-1s-together,数组、滑动窗口,https://algo.itcharge.cn/Solutions/1100-1199/minimum-swaps-to-group-all-1s-together/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1151.%20%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0%E6%9D%A5%E7%BB%84%E5%90%88%E6%89%80%E6%9C%89%E7%9A%84%201.md,52.8%,中等,139 -1152,1100-1199,1152. 用户网站访问行为分析,用户网站访问行为分析,https://leetcode.cn/problems/analyze-user-website-visit-pattern/,analyze-user-website-visit-pattern,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1100-1199/analyze-user-website-visit-pattern/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1152.%20%E7%94%A8%E6%88%B7%E7%BD%91%E7%AB%99%E8%AE%BF%E9%97%AE%E8%A1%8C%E4%B8%BA%E5%88%86%E6%9E%90.md,41.5%,中等,58 -1153,1100-1199,1153. 字符串转化,字符串转化,https://leetcode.cn/problems/string-transforms-into-another-string/,string-transforms-into-another-string,哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/string-transforms-into-another-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1153.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E5%8C%96.md,37.8%,困难,31 -1154,1100-1199,1154. 一年中的第几天,一年中的第几天,https://leetcode.cn/problems/day-of-the-year/,day-of-the-year,数学、字符串,https://algo.itcharge.cn/Solutions/1100-1199/day-of-the-year/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1154.%20%E4%B8%80%E5%B9%B4%E4%B8%AD%E7%9A%84%E7%AC%AC%E5%87%A0%E5%A4%A9.md,62.5%,简单,643 -1155,1100-1199,1155. 掷骰子等于目标和的方法数,掷骰子等于目标和的方法数,https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/,number-of-dice-rolls-with-target-sum,动态规划,https://algo.itcharge.cn/Solutions/1100-1199/number-of-dice-rolls-with-target-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1155.%20%E6%8E%B7%E9%AA%B0%E5%AD%90%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%92%8C%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,50.9%,中等,204 -1156,1100-1199,1156. 单字符重复子串的最大长度,单字符重复子串的最大长度,https://leetcode.cn/problems/swap-for-longest-repeated-character-substring/,swap-for-longest-repeated-character-substring,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1100-1199/swap-for-longest-repeated-character-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1156.%20%E5%8D%95%E5%AD%97%E7%AC%A6%E9%87%8D%E5%A4%8D%E5%AD%90%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E9%95%BF%E5%BA%A6.md,51.3%,中等,297 -1157,1100-1199,1157. 子数组中占绝大多数的元素,子数组中占绝大多数的元素,https://leetcode.cn/problems/online-majority-element-in-subarray/,online-majority-element-in-subarray,设计、树状数组、线段树、数组、二分查找,https://algo.itcharge.cn/Solutions/1100-1199/online-majority-element-in-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1157.%20%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AD%E5%8D%A0%E7%BB%9D%E5%A4%A7%E5%A4%9A%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0.md,46.2%,困难,126 -1158,1100-1199,1158. 市场分析 I,市场分析 I,https://leetcode.cn/problems/market-analysis-i/,market-analysis-i,数据库,https://algo.itcharge.cn/Solutions/1100-1199/market-analysis-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1158.%20%E5%B8%82%E5%9C%BA%E5%88%86%E6%9E%90%20I.md,54.5%,中等,271 -1159,1100-1199,1159. 市场分析 II,市场分析 II,https://leetcode.cn/problems/market-analysis-ii/,market-analysis-ii,数据库,https://algo.itcharge.cn/Solutions/1100-1199/market-analysis-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1159.%20%E5%B8%82%E5%9C%BA%E5%88%86%E6%9E%90%20II.md,50.6%,困难,123 -1160,1100-1199,1160. 拼写单词,拼写单词,https://leetcode.cn/problems/find-words-that-can-be-formed-by-characters/,find-words-that-can-be-formed-by-characters,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/find-words-that-can-be-formed-by-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1160.%20%E6%8B%BC%E5%86%99%E5%8D%95%E8%AF%8D.md,68.2%,简单,1065 -1161,1100-1199,1161. 最大层内元素和,最大层内元素和,https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/,maximum-level-sum-of-a-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1100-1199/maximum-level-sum-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1161.%20%E6%9C%80%E5%A4%A7%E5%B1%82%E5%86%85%E5%85%83%E7%B4%A0%E5%92%8C.md,66.1%,中等,508 -1162,1100-1199,1162. 地图分析,地图分析,https://leetcode.cn/problems/as-far-from-land-as-possible/,as-far-from-land-as-possible,广度优先搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1100-1199/as-far-from-land-as-possible/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1162.%20%E5%9C%B0%E5%9B%BE%E5%88%86%E6%9E%90.md,46.7%,中等,669 -1163,1100-1199,1163. 按字典序排在最后的子串,按字典序排在最后的子串,https://leetcode.cn/problems/last-substring-in-lexicographical-order/,last-substring-in-lexicographical-order,双指针、字符串,https://algo.itcharge.cn/Solutions/1100-1199/last-substring-in-lexicographical-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1163.%20%E6%8C%89%E5%AD%97%E5%85%B8%E5%BA%8F%E6%8E%92%E5%9C%A8%E6%9C%80%E5%90%8E%E7%9A%84%E5%AD%90%E4%B8%B2.md,35.2%,困难,238 -1164,1100-1199,1164. 指定日期的产品价格,指定日期的产品价格,https://leetcode.cn/problems/product-price-at-a-given-date/,product-price-at-a-given-date,数据库,https://algo.itcharge.cn/Solutions/1100-1199/product-price-at-a-given-date/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1164.%20%E6%8C%87%E5%AE%9A%E6%97%A5%E6%9C%9F%E7%9A%84%E4%BA%A7%E5%93%81%E4%BB%B7%E6%A0%BC.md,58.4%,中等,188 -1165,1100-1199,1165. 单行键盘,单行键盘,https://leetcode.cn/problems/single-row-keyboard/,single-row-keyboard,哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/single-row-keyboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1165.%20%E5%8D%95%E8%A1%8C%E9%94%AE%E7%9B%98.md,84.0%,简单,156 -1166,1100-1199,1166. 设计文件系统,设计文件系统,https://leetcode.cn/problems/design-file-system/,design-file-system,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/design-file-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1166.%20%E8%AE%BE%E8%AE%A1%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.md,49.7%,中等,87 -1167,1100-1199,1167. 连接棒材的最低费用,连接棒材的最低费用,https://leetcode.cn/problems/minimum-cost-to-connect-sticks/,minimum-cost-to-connect-sticks,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/minimum-cost-to-connect-sticks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1167.%20%E8%BF%9E%E6%8E%A5%E6%A3%92%E6%9D%90%E7%9A%84%E6%9C%80%E4%BD%8E%E8%B4%B9%E7%94%A8.md,51.2%,中等,54 -1168,1100-1199,1168. 水资源分配优化,水资源分配优化,https://leetcode.cn/problems/optimize-water-distribution-in-a-village/,optimize-water-distribution-in-a-village,并查集、图、最小生成树,https://algo.itcharge.cn/Solutions/1100-1199/optimize-water-distribution-in-a-village/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1168.%20%E6%B0%B4%E8%B5%84%E6%BA%90%E5%88%86%E9%85%8D%E4%BC%98%E5%8C%96.md,62.5%,困难,55 -1169,1100-1199,1169. 查询无效交易,查询无效交易,https://leetcode.cn/problems/invalid-transactions/,invalid-transactions,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1100-1199/invalid-transactions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1169.%20%E6%9F%A5%E8%AF%A2%E6%97%A0%E6%95%88%E4%BA%A4%E6%98%93.md,33.0%,中等,112 -1170,1100-1199,1170. 比较字符串最小字母出现频次,比较字符串最小字母出现频次,https://leetcode.cn/problems/compare-strings-by-frequency-of-the-smallest-character/,compare-strings-by-frequency-of-the-smallest-character,数组、哈希表、字符串、二分查找、排序,https://algo.itcharge.cn/Solutions/1100-1199/compare-strings-by-frequency-of-the-smallest-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1170.%20%E6%AF%94%E8%BE%83%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9C%80%E5%B0%8F%E5%AD%97%E6%AF%8D%E5%87%BA%E7%8E%B0%E9%A2%91%E6%AC%A1.md,66.3%,中等,432 -1171,1100-1199,1171. 从链表中删去总和值为零的连续节点,从链表中删去总和值为零的连续节点,https://leetcode.cn/problems/remove-zero-sum-consecutive-nodes-from-linked-list/,remove-zero-sum-consecutive-nodes-from-linked-list,哈希表、链表,https://algo.itcharge.cn/Solutions/1100-1199/remove-zero-sum-consecutive-nodes-from-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1171.%20%E4%BB%8E%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%88%A0%E5%8E%BB%E6%80%BB%E5%92%8C%E5%80%BC%E4%B8%BA%E9%9B%B6%E7%9A%84%E8%BF%9E%E7%BB%AD%E8%8A%82%E7%82%B9.md,53.1%,中等,332 -1172,1100-1199,1172. 餐盘栈,餐盘栈,https://leetcode.cn/problems/dinner-plate-stacks/,dinner-plate-stacks,栈、设计、哈希表、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/dinner-plate-stacks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1172.%20%E9%A4%90%E7%9B%98%E6%A0%88.md,40.0%,困难,158 -1173,1100-1199,1173. 即时食物配送 I,即时食物配送 I,https://leetcode.cn/problems/immediate-food-delivery-i/,immediate-food-delivery-i,数据库,https://algo.itcharge.cn/Solutions/1100-1199/immediate-food-delivery-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1173.%20%E5%8D%B3%E6%97%B6%E9%A3%9F%E7%89%A9%E9%85%8D%E9%80%81%20I.md,76.5%,简单,140 -1174,1100-1199,1174. 即时食物配送 II,即时食物配送 II,https://leetcode.cn/problems/immediate-food-delivery-ii/,immediate-food-delivery-ii,数据库,https://algo.itcharge.cn/Solutions/1100-1199/immediate-food-delivery-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1174.%20%E5%8D%B3%E6%97%B6%E9%A3%9F%E7%89%A9%E9%85%8D%E9%80%81%20II.md,61.9%,中等,118 -1175,1100-1199,1175. 质数排列,质数排列,https://leetcode.cn/problems/prime-arrangements/,prime-arrangements,数学,https://algo.itcharge.cn/Solutions/1100-1199/prime-arrangements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1175.%20%E8%B4%A8%E6%95%B0%E6%8E%92%E5%88%97.md,56.3%,简单,381 -1176,1100-1199,1176. 健身计划评估,健身计划评估,https://leetcode.cn/problems/diet-plan-performance/,diet-plan-performance,数组、滑动窗口,https://algo.itcharge.cn/Solutions/1100-1199/diet-plan-performance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1176.%20%E5%81%A5%E8%BA%AB%E8%AE%A1%E5%88%92%E8%AF%84%E4%BC%B0.md,48.5%,简单,96 -1177,1100-1199,1177. 构建回文串检测,构建回文串检测,https://leetcode.cn/problems/can-make-palindrome-from-substring/,can-make-palindrome-from-substring,位运算、数组、哈希表、字符串、前缀和,https://algo.itcharge.cn/Solutions/1100-1199/can-make-palindrome-from-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1177.%20%E6%9E%84%E5%BB%BA%E5%9B%9E%E6%96%87%E4%B8%B2%E6%A3%80%E6%B5%8B.md,41.9%,中等,242 -1178,1100-1199,1178. 猜字谜,猜字谜,https://leetcode.cn/problems/number-of-valid-words-for-each-puzzle/,number-of-valid-words-for-each-puzzle,位运算、字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1100-1199/number-of-valid-words-for-each-puzzle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1178.%20%E7%8C%9C%E5%AD%97%E8%B0%9C.md,46.7%,困难,201 -1179,1100-1199,1179. 重新格式化部门表,重新格式化部门表,https://leetcode.cn/problems/reformat-department-table/,reformat-department-table,数据库,https://algo.itcharge.cn/Solutions/1100-1199/reformat-department-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1179.%20%E9%87%8D%E6%96%B0%E6%A0%BC%E5%BC%8F%E5%8C%96%E9%83%A8%E9%97%A8%E8%A1%A8.md,64.8%,简单,272 -1180,1100-1199,1180. 统计只含单一字母的子串,统计只含单一字母的子串,https://leetcode.cn/problems/count-substrings-with-only-one-distinct-letter/,count-substrings-with-only-one-distinct-letter,数学、字符串,https://algo.itcharge.cn/Solutions/1100-1199/count-substrings-with-only-one-distinct-letter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1180.%20%E7%BB%9F%E8%AE%A1%E5%8F%AA%E5%90%AB%E5%8D%95%E4%B8%80%E5%AD%97%E6%AF%8D%E7%9A%84%E5%AD%90%E4%B8%B2.md,78.1%,简单,107 -1181,1100-1199,1181. 前后拼接,前后拼接,https://leetcode.cn/problems/before-and-after-puzzle/,before-and-after-puzzle,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1100-1199/before-and-after-puzzle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1181.%20%E5%89%8D%E5%90%8E%E6%8B%BC%E6%8E%A5.md,39.8%,中等,37 -1182,1100-1199,1182. 与目标颜色间的最短距离,与目标颜色间的最短距离,https://leetcode.cn/problems/shortest-distance-to-target-color/,shortest-distance-to-target-color,数组、二分查找、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/shortest-distance-to-target-color/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1182.%20%E4%B8%8E%E7%9B%AE%E6%A0%87%E9%A2%9C%E8%89%B2%E9%97%B4%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%9D%E7%A6%BB.md,47.7%,中等,99 -1183,1100-1199,1183. 矩阵中 1 的最大数量,矩阵中 1 的最大数量,https://leetcode.cn/problems/maximum-number-of-ones/,maximum-number-of-ones,贪心、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/maximum-number-of-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1183.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%201%20%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,62.5%,困难,28 -1184,1100-1199,1184. 公交站间的距离,公交站间的距离,https://leetcode.cn/problems/distance-between-bus-stops/,distance-between-bus-stops,数组,https://algo.itcharge.cn/Solutions/1100-1199/distance-between-bus-stops/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1184.%20%E5%85%AC%E4%BA%A4%E7%AB%99%E9%97%B4%E7%9A%84%E8%B7%9D%E7%A6%BB.md,61.8%,简单,622 -1185,1100-1199,1185. 一周中的第几天,一周中的第几天,https://leetcode.cn/problems/day-of-the-week/,day-of-the-week,数学,https://algo.itcharge.cn/Solutions/1100-1199/day-of-the-week/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1185.%20%E4%B8%80%E5%91%A8%E4%B8%AD%E7%9A%84%E7%AC%AC%E5%87%A0%E5%A4%A9.md,62.2%,简单,532 -1186,1100-1199,1186. 删除一次得到子数组最大和,删除一次得到子数组最大和,https://leetcode.cn/problems/maximum-subarray-sum-with-one-deletion/,maximum-subarray-sum-with-one-deletion,数组、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/maximum-subarray-sum-with-one-deletion/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1186.%20%E5%88%A0%E9%99%A4%E4%B8%80%E6%AC%A1%E5%BE%97%E5%88%B0%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,48.1%,中等,262 -1187,1100-1199,1187. 使数组严格递增,使数组严格递增,https://leetcode.cn/problems/make-array-strictly-increasing/,make-array-strictly-increasing,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/1100-1199/make-array-strictly-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1187.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%A5%E6%A0%BC%E9%80%92%E5%A2%9E.md,59.8%,困难,113 -1188,1100-1199,1188. 设计有限阻塞队列,设计有限阻塞队列,https://leetcode.cn/problems/design-bounded-blocking-queue/,design-bounded-blocking-queue,多线程,https://algo.itcharge.cn/Solutions/1100-1199/design-bounded-blocking-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1188.%20%E8%AE%BE%E8%AE%A1%E6%9C%89%E9%99%90%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97.md,70.2%,中等,89 -1189,1100-1199,1189. “气球” 的最大数量,“气球” 的最大数量,https://leetcode.cn/problems/maximum-number-of-balloons/,maximum-number-of-balloons,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1100-1199/maximum-number-of-balloons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1189.%20%E2%80%9C%E6%B0%94%E7%90%83%E2%80%9D%20%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,68.3%,简单,815 -1190,1100-1199,1190. 反转每对括号间的子串,反转每对括号间的子串,https://leetcode.cn/problems/reverse-substrings-between-each-pair-of-parentheses/,reverse-substrings-between-each-pair-of-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/1100-1199/reverse-substrings-between-each-pair-of-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1190.%20%E5%8F%8D%E8%BD%AC%E6%AF%8F%E5%AF%B9%E6%8B%AC%E5%8F%B7%E9%97%B4%E7%9A%84%E5%AD%90%E4%B8%B2.md,64.8%,中等,752 -1191,1100-1199,1191. K 次串联后最大子数组之和,K 次串联后最大子数组之和,https://leetcode.cn/problems/k-concatenation-maximum-sum/,k-concatenation-maximum-sum,数组、动态规划,https://algo.itcharge.cn/Solutions/1100-1199/k-concatenation-maximum-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1191.%20K%20%E6%AC%A1%E4%B8%B2%E8%81%94%E5%90%8E%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E4%B9%8B%E5%92%8C.md,27.0%,中等,135 -1192,1100-1199,1192. 查找集群内的关键连接,查找集群内的关键连接,https://leetcode.cn/problems/critical-connections-in-a-network/,critical-connections-in-a-network,深度优先搜索、图、双连通分量,https://algo.itcharge.cn/Solutions/1100-1199/critical-connections-in-a-network/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1192.%20%E6%9F%A5%E6%89%BE%E9%9B%86%E7%BE%A4%E5%86%85%E7%9A%84%E5%85%B3%E9%94%AE%E8%BF%9E%E6%8E%A5.md,54.3%,困难,112 -1193,1100-1199,1193. 每月交易 I,每月交易 I,https://leetcode.cn/problems/monthly-transactions-i/,monthly-transactions-i,数据库,https://algo.itcharge.cn/Solutions/1100-1199/monthly-transactions-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1193.%20%E6%AF%8F%E6%9C%88%E4%BA%A4%E6%98%93%20I.md,60.9%,中等,155 -1194,1100-1199,1194. 锦标赛优胜者,锦标赛优胜者,https://leetcode.cn/problems/tournament-winners/,tournament-winners,数据库,https://algo.itcharge.cn/Solutions/1100-1199/tournament-winners/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1194.%20%E9%94%A6%E6%A0%87%E8%B5%9B%E4%BC%98%E8%83%9C%E8%80%85.md,51.0%,困难,99 -1195,1100-1199,1195. 交替打印字符串,交替打印字符串,https://leetcode.cn/problems/fizz-buzz-multithreaded/,fizz-buzz-multithreaded,多线程,https://algo.itcharge.cn/Solutions/1100-1199/fizz-buzz-multithreaded/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1195.%20%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0%E5%AD%97%E7%AC%A6%E4%B8%B2.md,65.0%,中等,359 -1196,1100-1199,1196. 最多可以买到的苹果数量,最多可以买到的苹果数量,https://leetcode.cn/problems/how-many-apples-can-you-put-into-the-basket/,how-many-apples-can-you-put-into-the-basket,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1100-1199/how-many-apples-can-you-put-into-the-basket/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1196.%20%E6%9C%80%E5%A4%9A%E5%8F%AF%E4%BB%A5%E4%B9%B0%E5%88%B0%E7%9A%84%E8%8B%B9%E6%9E%9C%E6%95%B0%E9%87%8F.md,68.2%,简单,82 -1197,1100-1199,1197. 进击的骑士,进击的骑士,https://leetcode.cn/problems/minimum-knight-moves/,minimum-knight-moves,广度优先搜索,https://algo.itcharge.cn/Solutions/1100-1199/minimum-knight-moves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1197.%20%E8%BF%9B%E5%87%BB%E7%9A%84%E9%AA%91%E5%A3%AB.md,39.2%,中等,88 -1198,1100-1199,1198. 找出所有行中最小公共元素,找出所有行中最小公共元素,https://leetcode.cn/problems/find-smallest-common-element-in-all-rows/,find-smallest-common-element-in-all-rows,数组、哈希表、二分查找、计数、矩阵,https://algo.itcharge.cn/Solutions/1100-1199/find-smallest-common-element-in-all-rows/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1198.%20%E6%89%BE%E5%87%BA%E6%89%80%E6%9C%89%E8%A1%8C%E4%B8%AD%E6%9C%80%E5%B0%8F%E5%85%AC%E5%85%B1%E5%85%83%E7%B4%A0.md,73.9%,中等,94 -1199,1100-1199,1199. 建造街区的最短时间,建造街区的最短时间,https://leetcode.cn/problems/minimum-time-to-build-blocks/,minimum-time-to-build-blocks,贪心、数学、堆(优先队列),https://algo.itcharge.cn/Solutions/1100-1199/minimum-time-to-build-blocks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1199.%20%E5%BB%BA%E9%80%A0%E8%A1%97%E5%8C%BA%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,47.3%,困难,31 -1200,1200-1299,1200. 最小绝对差,最小绝对差,https://leetcode.cn/problems/minimum-absolute-difference/,minimum-absolute-difference,数组、排序,https://algo.itcharge.cn/Solutions/1200-1299/minimum-absolute-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1200.%20%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF%B9%E5%B7%AE.md,72.5%,简单,629 -1201,1200-1299,1201. 丑数 III,丑数 III,https://leetcode.cn/problems/ugly-number-iii/,ugly-number-iii,数学、二分查找、数论,https://algo.itcharge.cn/Solutions/1200-1299/ugly-number-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1201.%20%E4%B8%91%E6%95%B0%20III.md,28.5%,中等,156 -1202,1200-1299,1202. 交换字符串中的元素,交换字符串中的元素,https://leetcode.cn/problems/smallest-string-with-swaps/,smallest-string-with-swaps,深度优先搜索、广度优先搜索、并查集、哈希表、字符串,https://algo.itcharge.cn/Solutions/1200-1299/smallest-string-with-swaps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1202.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md,51.3%,中等,371 -1203,1200-1299,1203. 项目管理,项目管理,https://leetcode.cn/problems/sort-items-by-groups-respecting-dependencies/,sort-items-by-groups-respecting-dependencies,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/1200-1299/sort-items-by-groups-respecting-dependencies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1203.%20%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86.md,60.8%,困难,175 -1204,1200-1299,1204. 最后一个能进入电梯的人,最后一个能进入电梯的人,https://leetcode.cn/problems/last-person-to-fit-in-the-bus/,last-person-to-fit-in-the-bus,数据库,https://algo.itcharge.cn/Solutions/1200-1299/last-person-to-fit-in-the-bus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1204.%20%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E8%83%BD%E8%BF%9B%E5%85%A5%E7%94%B5%E6%A2%AF%E7%9A%84%E4%BA%BA.md,74.0%,中等,132 -1205,1200-1299,1205. 每月交易II,每月交易II,https://leetcode.cn/problems/monthly-transactions-ii/,monthly-transactions-ii,数据库,https://algo.itcharge.cn/Solutions/1200-1299/monthly-transactions-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1205.%20%E6%AF%8F%E6%9C%88%E4%BA%A4%E6%98%93II.md,45.7%,中等,109 -1206,1200-1299,1206. 设计跳表,设计跳表,https://leetcode.cn/problems/design-skiplist/,design-skiplist,设计、链表,https://algo.itcharge.cn/Solutions/1200-1299/design-skiplist/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1206.%20%E8%AE%BE%E8%AE%A1%E8%B7%B3%E8%A1%A8.md,68.3%,困难,251 -1207,1200-1299,1207. 独一无二的出现次数,独一无二的出现次数,https://leetcode.cn/problems/unique-number-of-occurrences/,unique-number-of-occurrences,数组、哈希表,https://algo.itcharge.cn/Solutions/1200-1299/unique-number-of-occurrences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1207.%20%E7%8B%AC%E4%B8%80%E6%97%A0%E4%BA%8C%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0.md,72.8%,简单,1078 -1208,1200-1299,1208. 尽可能使字符串相等,尽可能使字符串相等,https://leetcode.cn/problems/get-equal-substrings-within-budget/,get-equal-substrings-within-budget,字符串、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/1200-1299/get-equal-substrings-within-budget/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1208.%20%E5%B0%BD%E5%8F%AF%E8%83%BD%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md,50.0%,中等,773 -1209,1200-1299,1209. 删除字符串中的所有相邻重复项 II,删除字符串中的所有相邻重复项 II,https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string-ii/,remove-all-adjacent-duplicates-in-string-ii,栈、字符串,https://algo.itcharge.cn/Solutions/1200-1299/remove-all-adjacent-duplicates-in-string-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1209.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9%20II.md,50.8%,中等,231 -1210,1200-1299,1210. 穿过迷宫的最少移动次数,穿过迷宫的最少移动次数,https://leetcode.cn/problems/minimum-moves-to-reach-target-with-rotations/,minimum-moves-to-reach-target-with-rotations,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/minimum-moves-to-reach-target-with-rotations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1210.%20%E7%A9%BF%E8%BF%87%E8%BF%B7%E5%AE%AB%E7%9A%84%E6%9C%80%E5%B0%91%E7%A7%BB%E5%8A%A8%E6%AC%A1%E6%95%B0.md,64.3%,困难,181 -1211,1200-1299,1211. 查询结果的质量和占比,查询结果的质量和占比,https://leetcode.cn/problems/queries-quality-and-percentage/,queries-quality-and-percentage,数据库,https://algo.itcharge.cn/Solutions/1200-1299/queries-quality-and-percentage/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1211.%20%E6%9F%A5%E8%AF%A2%E7%BB%93%E6%9E%9C%E7%9A%84%E8%B4%A8%E9%87%8F%E5%92%8C%E5%8D%A0%E6%AF%94.md,67.3%,简单,146 -1212,1200-1299,1212. 查询球队积分,查询球队积分,https://leetcode.cn/problems/team-scores-in-football-tournament/,team-scores-in-football-tournament,数据库,https://algo.itcharge.cn/Solutions/1200-1299/team-scores-in-football-tournament/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1212.%20%E6%9F%A5%E8%AF%A2%E7%90%83%E9%98%9F%E7%A7%AF%E5%88%86.md,51.4%,中等,165 -1213,1200-1299,1213. 三个有序数组的交集,三个有序数组的交集,https://leetcode.cn/problems/intersection-of-three-sorted-arrays/,intersection-of-three-sorted-arrays,数组、哈希表、二分查找、计数,https://algo.itcharge.cn/Solutions/1200-1299/intersection-of-three-sorted-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1213.%20%E4%B8%89%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md,78.3%,简单,184 -1214,1200-1299,1214. 查找两棵二叉搜索树之和,查找两棵二叉搜索树之和,https://leetcode.cn/problems/two-sum-bsts/,two-sum-bsts,栈、树、深度优先搜索、二叉搜索树、双指针、二分查找、二叉树,https://algo.itcharge.cn/Solutions/1200-1299/two-sum-bsts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1214.%20%E6%9F%A5%E6%89%BE%E4%B8%A4%E6%A3%B5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B9%8B%E5%92%8C.md,65.9%,中等,100 -1215,1200-1299,1215. 步进数,步进数,https://leetcode.cn/problems/stepping-numbers/,stepping-numbers,广度优先搜索、回溯,https://algo.itcharge.cn/Solutions/1200-1299/stepping-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1215.%20%E6%AD%A5%E8%BF%9B%E6%95%B0.md,43.3%,中等,54 -1216,1200-1299,1216. 验证回文字符串 III,验证回文字符串 III,https://leetcode.cn/problems/valid-palindrome-iii/,valid-palindrome-iii,字符串、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/valid-palindrome-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1216.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2%20III.md,57.8%,困难,57 -1217,1200-1299,1217. 玩筹码,玩筹码,https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/,minimum-cost-to-move-chips-to-the-same-position,贪心、数组、数学,https://algo.itcharge.cn/Solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1217.%20%E7%8E%A9%E7%AD%B9%E7%A0%81.md,74.6%,简单,750 -1218,1200-1299,1218. 最长定差子序列,最长定差子序列,https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/,longest-arithmetic-subsequence-of-given-difference,数组、哈希表、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/longest-arithmetic-subsequence-of-given-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1218.%20%E6%9C%80%E9%95%BF%E5%AE%9A%E5%B7%AE%E5%AD%90%E5%BA%8F%E5%88%97.md,51.6%,中等,421 -1219,1200-1299,1219. 黄金矿工,黄金矿工,https://leetcode.cn/problems/path-with-maximum-gold/,path-with-maximum-gold,数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/path-with-maximum-gold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1219.%20%E9%BB%84%E9%87%91%E7%9F%BF%E5%B7%A5.md,69.1%,中等,525 -1220,1200-1299,1220. 统计元音字母序列的数目,统计元音字母序列的数目,https://leetcode.cn/problems/count-vowels-permutation/,count-vowels-permutation,动态规划,https://algo.itcharge.cn/Solutions/1200-1299/count-vowels-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1220.%20%E7%BB%9F%E8%AE%A1%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,60.7%,困难,380 -1221,1200-1299,1221. 分割平衡字符串,分割平衡字符串,https://leetcode.cn/problems/split-a-string-in-balanced-strings/,split-a-string-in-balanced-strings,贪心、字符串、计数,https://algo.itcharge.cn/Solutions/1200-1299/split-a-string-in-balanced-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1221.%20%E5%88%86%E5%89%B2%E5%B9%B3%E8%A1%A1%E5%AD%97%E7%AC%A6%E4%B8%B2.md,85.0%,简单,1363 -1222,1200-1299,1222. 可以攻击国王的皇后,可以攻击国王的皇后,https://leetcode.cn/problems/queens-that-can-attack-the-king/,queens-that-can-attack-the-king,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/1200-1299/queens-that-can-attack-the-king/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1222.%20%E5%8F%AF%E4%BB%A5%E6%94%BB%E5%87%BB%E5%9B%BD%E7%8E%8B%E7%9A%84%E7%9A%87%E5%90%8E.md,69.4%,中等,200 -1223,1200-1299,1223. 掷骰子模拟,掷骰子模拟,https://leetcode.cn/problems/dice-roll-simulation/,dice-roll-simulation,数组、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/dice-roll-simulation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1223.%20%E6%8E%B7%E9%AA%B0%E5%AD%90%E6%A8%A1%E6%8B%9F.md,61.9%,困难,186 -1224,1200-1299,1224. 最大相等频率,最大相等频率,https://leetcode.cn/problems/maximum-equal-frequency/,maximum-equal-frequency,数组、哈希表,https://algo.itcharge.cn/Solutions/1200-1299/maximum-equal-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1224.%20%E6%9C%80%E5%A4%A7%E7%9B%B8%E7%AD%89%E9%A2%91%E7%8E%87.md,43.6%,困难,297 -1225,1200-1299,1225. 报告系统状态的连续日期,报告系统状态的连续日期,https://leetcode.cn/problems/report-contiguous-dates/,report-contiguous-dates,数据库,https://algo.itcharge.cn/Solutions/1200-1299/report-contiguous-dates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1225.%20%E6%8A%A5%E5%91%8A%E7%B3%BB%E7%BB%9F%E7%8A%B6%E6%80%81%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%97%A5%E6%9C%9F.md,53.5%,困难,136 -1226,1200-1299,1226. 哲学家进餐,哲学家进餐,https://leetcode.cn/problems/the-dining-philosophers/,the-dining-philosophers,多线程,https://algo.itcharge.cn/Solutions/1200-1299/the-dining-philosophers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1226.%20%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90.md,59.4%,中等,183 -1227,1200-1299,1227. 飞机座位分配概率,飞机座位分配概率,https://leetcode.cn/problems/airplane-seat-assignment-probability/,airplane-seat-assignment-probability,脑筋急转弯、数学、动态规划、概率与统计,https://algo.itcharge.cn/Solutions/1200-1299/airplane-seat-assignment-probability/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1227.%20%E9%A3%9E%E6%9C%BA%E5%BA%A7%E4%BD%8D%E5%88%86%E9%85%8D%E6%A6%82%E7%8E%87.md,67.0%,中等,191 -1228,1200-1299,1228. 等差数列中缺失的数字,等差数列中缺失的数字,https://leetcode.cn/problems/missing-number-in-arithmetic-progression/,missing-number-in-arithmetic-progression,数组、数学,https://algo.itcharge.cn/Solutions/1200-1299/missing-number-in-arithmetic-progression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1228.%20%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md,53.9%,简单,82 -1229,1200-1299,1229. 安排会议日程,安排会议日程,https://leetcode.cn/problems/meeting-scheduler/,meeting-scheduler,数组、双指针、排序,https://algo.itcharge.cn/Solutions/1200-1299/meeting-scheduler/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1229.%20%E5%AE%89%E6%8E%92%E4%BC%9A%E8%AE%AE%E6%97%A5%E7%A8%8B.md,46.5%,中等,123 -1230,1200-1299,1230. 抛掷硬币,抛掷硬币,https://leetcode.cn/problems/toss-strange-coins/,toss-strange-coins,数学、动态规划、概率与统计,https://algo.itcharge.cn/Solutions/1200-1299/toss-strange-coins/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1230.%20%E6%8A%9B%E6%8E%B7%E7%A1%AC%E5%B8%81.md,54.3%,中等,115 -1231,1200-1299,1231. 分享巧克力,分享巧克力,https://leetcode.cn/problems/divide-chocolate/,divide-chocolate,数组、二分查找,https://algo.itcharge.cn/Solutions/1200-1299/divide-chocolate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1231.%20%E5%88%86%E4%BA%AB%E5%B7%A7%E5%85%8B%E5%8A%9B.md,58.9%,困难,103 -1232,1200-1299,1232. 缀点成线,缀点成线,https://leetcode.cn/problems/check-if-it-is-a-straight-line/,check-if-it-is-a-straight-line,几何、数组、数学,https://algo.itcharge.cn/Solutions/1200-1299/check-if-it-is-a-straight-line/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1232.%20%E7%BC%80%E7%82%B9%E6%88%90%E7%BA%BF.md,45.5%,简单,602 -1233,1200-1299,1233. 删除子文件夹,删除子文件夹,https://leetcode.cn/problems/remove-sub-folders-from-the-filesystem/,remove-sub-folders-from-the-filesystem,字典树、数组、字符串,https://algo.itcharge.cn/Solutions/1200-1299/remove-sub-folders-from-the-filesystem/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1233.%20%E5%88%A0%E9%99%A4%E5%AD%90%E6%96%87%E4%BB%B6%E5%A4%B9.md,61.5%,中等,451 -1234,1200-1299,1234. 替换子串得到平衡字符串,替换子串得到平衡字符串,https://leetcode.cn/problems/replace-the-substring-for-balanced-string/,replace-the-substring-for-balanced-string,字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1200-1299/replace-the-substring-for-balanced-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1234.%20%E6%9B%BF%E6%8D%A2%E5%AD%90%E4%B8%B2%E5%BE%97%E5%88%B0%E5%B9%B3%E8%A1%A1%E5%AD%97%E7%AC%A6%E4%B8%B2.md,44.9%,中等,323 -1235,1200-1299,1235. 规划兼职工作,规划兼职工作,https://leetcode.cn/problems/maximum-profit-in-job-scheduling/,maximum-profit-in-job-scheduling,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/1200-1299/maximum-profit-in-job-scheduling/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1235.%20%E8%A7%84%E5%88%92%E5%85%BC%E8%81%8C%E5%B7%A5%E4%BD%9C.md,57.8%,困难,380 -1236,1200-1299,1236. 网络爬虫,网络爬虫,https://leetcode.cn/problems/web-crawler/,web-crawler,深度优先搜索、广度优先搜索、字符串、交互,https://algo.itcharge.cn/Solutions/1200-1299/web-crawler/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1236.%20%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB.md,57.3%,中等,42 -1237,1200-1299,1237. 找出给定方程的正整数解,找出给定方程的正整数解,https://leetcode.cn/problems/find-positive-integer-solution-for-a-given-equation/,find-positive-integer-solution-for-a-given-equation,数学、双指针、二分查找、交互,https://algo.itcharge.cn/Solutions/1200-1299/find-positive-integer-solution-for-a-given-equation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1237.%20%E6%89%BE%E5%87%BA%E7%BB%99%E5%AE%9A%E6%96%B9%E7%A8%8B%E7%9A%84%E6%AD%A3%E6%95%B4%E6%95%B0%E8%A7%A3.md,78.2%,中等,316 -1238,1200-1299,1238. 循环码排列,循环码排列,https://leetcode.cn/problems/circular-permutation-in-binary-representation/,circular-permutation-in-binary-representation,位运算、数学、回溯,https://algo.itcharge.cn/Solutions/1200-1299/circular-permutation-in-binary-representation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1238.%20%E5%BE%AA%E7%8E%AF%E7%A0%81%E6%8E%92%E5%88%97.md,77.5%,中等,226 -1239,1200-1299,1239. 串联字符串的最大长度,串联字符串的最大长度,https://leetcode.cn/problems/maximum-length-of-a-concatenated-string-with-unique-characters/,maximum-length-of-a-concatenated-string-with-unique-characters,位运算、数组、字符串、回溯,https://algo.itcharge.cn/Solutions/1200-1299/maximum-length-of-a-concatenated-string-with-unique-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1239.%20%E4%B8%B2%E8%81%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E9%95%BF%E5%BA%A6.md,49.1%,中等,451 -1240,1200-1299,1240. 铺瓷砖,铺瓷砖,https://leetcode.cn/problems/tiling-a-rectangle-with-the-fewest-squares/,tiling-a-rectangle-with-the-fewest-squares,回溯,https://algo.itcharge.cn/Solutions/1200-1299/tiling-a-rectangle-with-the-fewest-squares/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1240.%20%E9%93%BA%E7%93%B7%E7%A0%96.md,65.7%,困难,112 -1241,1200-1299,1241. 每个帖子的评论数,每个帖子的评论数,https://leetcode.cn/problems/number-of-comments-per-post/,number-of-comments-per-post,数据库,https://algo.itcharge.cn/Solutions/1200-1299/number-of-comments-per-post/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1241.%20%E6%AF%8F%E4%B8%AA%E5%B8%96%E5%AD%90%E7%9A%84%E8%AF%84%E8%AE%BA%E6%95%B0.md,59.7%,简单,94 -1242,1200-1299,1242. 多线程网页爬虫,多线程网页爬虫,https://leetcode.cn/problems/web-crawler-multithreaded/,web-crawler-multithreaded,深度优先搜索、广度优先搜索、多线程,https://algo.itcharge.cn/Solutions/1200-1299/web-crawler-multithreaded/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1242.%20%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BD%91%E9%A1%B5%E7%88%AC%E8%99%AB.md,48.2%,中等,28 -1243,1200-1299,1243. 数组变换,数组变换,https://leetcode.cn/problems/array-transformation/,array-transformation,数组、模拟,https://algo.itcharge.cn/Solutions/1200-1299/array-transformation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1243.%20%E6%95%B0%E7%BB%84%E5%8F%98%E6%8D%A2.md,53.8%,简单,86 -1244,1200-1299,1244. 力扣排行榜,力扣排行榜,https://leetcode.cn/problems/design-a-leaderboard/,design-a-leaderboard,设计、哈希表、排序,https://algo.itcharge.cn/Solutions/1200-1299/design-a-leaderboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1244.%20%E5%8A%9B%E6%89%A3%E6%8E%92%E8%A1%8C%E6%A6%9C.md,63.5%,中等,69 -1245,1200-1299,1245. 树的直径,树的直径,https://leetcode.cn/problems/tree-diameter/,tree-diameter,树、深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/1200-1299/tree-diameter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1245.%20%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md,54.4%,中等,116 -1246,1200-1299,1246. 删除回文子数组,删除回文子数组,https://leetcode.cn/problems/palindrome-removal/,palindrome-removal,数组、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/palindrome-removal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1246.%20%E5%88%A0%E9%99%A4%E5%9B%9E%E6%96%87%E5%AD%90%E6%95%B0%E7%BB%84.md,50.3%,困难,61 -1247,1200-1299,1247. 交换字符使得字符串相同,交换字符使得字符串相同,https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/,minimum-swaps-to-make-strings-equal,贪心、数学、字符串,https://algo.itcharge.cn/Solutions/1200-1299/minimum-swaps-to-make-strings-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1247.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%BD%BF%E5%BE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%90%8C.md,69.1%,中等,441 -1248,1200-1299,1248. 统计「优美子数组」,统计「优美子数组」,https://leetcode.cn/problems/count-number-of-nice-subarrays/,count-number-of-nice-subarrays,数组、哈希表、数学、滑动窗口,https://algo.itcharge.cn/Solutions/1200-1299/count-number-of-nice-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1248.%20%E7%BB%9F%E8%AE%A1%E3%80%8C%E4%BC%98%E7%BE%8E%E5%AD%90%E6%95%B0%E7%BB%84%E3%80%8D.md,57.9%,中等,637 -1249,1200-1299,1249. 移除无效的括号,移除无效的括号,https://leetcode.cn/problems/minimum-remove-to-make-valid-parentheses/,minimum-remove-to-make-valid-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/1200-1299/minimum-remove-to-make-valid-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1249.%20%E7%A7%BB%E9%99%A4%E6%97%A0%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md,59.4%,中等,561 -1250,1200-1299,1250. 检查「好数组」,检查「好数组」,https://leetcode.cn/problems/check-if-it-is-a-good-array/,check-if-it-is-a-good-array,数组、数学、数论,https://algo.itcharge.cn/Solutions/1200-1299/check-if-it-is-a-good-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1250.%20%E6%A3%80%E6%9F%A5%E3%80%8C%E5%A5%BD%E6%95%B0%E7%BB%84%E3%80%8D.md,71.0%,困难,153 -1251,1200-1299,1251. 平均售价,平均售价,https://leetcode.cn/problems/average-selling-price/,average-selling-price,数据库,https://algo.itcharge.cn/Solutions/1200-1299/average-selling-price/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1251.%20%E5%B9%B3%E5%9D%87%E5%94%AE%E4%BB%B7.md,75.3%,简单,203 -1252,1200-1299,1252. 奇数值单元格的数目,奇数值单元格的数目,https://leetcode.cn/problems/cells-with-odd-values-in-a-matrix/,cells-with-odd-values-in-a-matrix,数组、数学、模拟,https://algo.itcharge.cn/Solutions/1200-1299/cells-with-odd-values-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1252.%20%E5%A5%87%E6%95%B0%E5%80%BC%E5%8D%95%E5%85%83%E6%A0%BC%E7%9A%84%E6%95%B0%E7%9B%AE.md,79.6%,简单,657 -1253,1200-1299,1253. 重构 2 行二进制矩阵,重构 2 行二进制矩阵,https://leetcode.cn/problems/reconstruct-a-2-row-binary-matrix/,reconstruct-a-2-row-binary-matrix,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/reconstruct-a-2-row-binary-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1253.%20%E9%87%8D%E6%9E%84%202%20%E8%A1%8C%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%9F%A9%E9%98%B5.md,40.8%,中等,124 -1254,1200-1299,1254. 统计封闭岛屿的数目,统计封闭岛屿的数目,https://leetcode.cn/problems/number-of-closed-islands/,number-of-closed-islands,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/number-of-closed-islands/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1254.%20%E7%BB%9F%E8%AE%A1%E5%B0%81%E9%97%AD%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E7%9B%AE.md,64.2%,中等,810 -1255,1200-1299,1255. 得分最高的单词集合,得分最高的单词集合,https://leetcode.cn/problems/maximum-score-words-formed-by-letters/,maximum-score-words-formed-by-letters,位运算、数组、字符串、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1200-1299/maximum-score-words-formed-by-letters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1255.%20%E5%BE%97%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E5%8D%95%E8%AF%8D%E9%9B%86%E5%90%88.md,79.9%,困难,246 -1256,1200-1299,1256. 加密数字,加密数字,https://leetcode.cn/problems/encode-number/,encode-number,位运算、数学、字符串,https://algo.itcharge.cn/Solutions/1200-1299/encode-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1256.%20%E5%8A%A0%E5%AF%86%E6%95%B0%E5%AD%97.md,68.7%,中等,55 -1257,1200-1299,1257. 最小公共区域,最小公共区域,https://leetcode.cn/problems/smallest-common-region/,smallest-common-region,树、深度优先搜索、广度优先搜索、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1200-1299/smallest-common-region/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1257.%20%E6%9C%80%E5%B0%8F%E5%85%AC%E5%85%B1%E5%8C%BA%E5%9F%9F.md,59.1%,中等,68 -1258,1200-1299,1258. 近义词句子,近义词句子,https://leetcode.cn/problems/synonymous-sentences/,synonymous-sentences,并查集、数组、哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/1200-1299/synonymous-sentences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1258.%20%E8%BF%91%E4%B9%89%E8%AF%8D%E5%8F%A5%E5%AD%90.md,59.2%,中等,78 -1259,1200-1299,1259. 不相交的握手,不相交的握手,https://leetcode.cn/problems/handshakes-that-dont-cross/,handshakes-that-dont-cross,数学、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/handshakes-that-dont-cross/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1259.%20%E4%B8%8D%E7%9B%B8%E4%BA%A4%E7%9A%84%E6%8F%A1%E6%89%8B.md,53.2%,困难,68 -1260,1200-1299,1260. 二维网格迁移,二维网格迁移,https://leetcode.cn/problems/shift-2d-grid/,shift-2d-grid,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/1200-1299/shift-2d-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1260.%20%E4%BA%8C%E7%BB%B4%E7%BD%91%E6%A0%BC%E8%BF%81%E7%A7%BB.md,65.4%,简单,667 -1261,1200-1299,1261. 在受污染的二叉树中查找元素,在受污染的二叉树中查找元素,https://leetcode.cn/problems/find-elements-in-a-contaminated-binary-tree/,find-elements-in-a-contaminated-binary-tree,树、深度优先搜索、广度优先搜索、设计、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1200-1299/find-elements-in-a-contaminated-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1261.%20%E5%9C%A8%E5%8F%97%E6%B1%A1%E6%9F%93%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0.md,73.5%,中等,168 -1262,1200-1299,1262. 可被三整除的最大和,可被三整除的最大和,https://leetcode.cn/problems/greatest-sum-divisible-by-three/,greatest-sum-divisible-by-three,贪心、数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1200-1299/greatest-sum-divisible-by-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1262.%20%E5%8F%AF%E8%A2%AB%E4%B8%89%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,56.3%,中等,416 -1263,1200-1299,1263. 推箱子,推箱子,https://leetcode.cn/problems/minimum-moves-to-move-a-box-to-their-target-location/,minimum-moves-to-move-a-box-to-their-target-location,广度优先搜索、数组、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/1200-1299/minimum-moves-to-move-a-box-to-their-target-location/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1263.%20%E6%8E%A8%E7%AE%B1%E5%AD%90.md,54.3%,困难,156 -1264,1200-1299,1264. 页面推荐,页面推荐,https://leetcode.cn/problems/page-recommendations/,page-recommendations,数据库,https://algo.itcharge.cn/Solutions/1200-1299/page-recommendations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1264.%20%E9%A1%B5%E9%9D%A2%E6%8E%A8%E8%8D%90.md,57.0%,中等,163 -1265,1200-1299,1265. 逆序打印不可变链表,逆序打印不可变链表,https://leetcode.cn/problems/print-immutable-linked-list-in-reverse/,print-immutable-linked-list-in-reverse,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/1200-1299/print-immutable-linked-list-in-reverse/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1265.%20%E9%80%86%E5%BA%8F%E6%89%93%E5%8D%B0%E4%B8%8D%E5%8F%AF%E5%8F%98%E9%93%BE%E8%A1%A8.md,90.7%,中等,101 -1266,1200-1299,1266. 访问所有点的最小时间,访问所有点的最小时间,https://leetcode.cn/problems/minimum-time-visiting-all-points/,minimum-time-visiting-all-points,几何、数组、数学,https://algo.itcharge.cn/Solutions/1200-1299/minimum-time-visiting-all-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1266.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4.md,82.8%,简单,533 -1267,1200-1299,1267. 统计参与通信的服务器,统计参与通信的服务器,https://leetcode.cn/problems/count-servers-that-communicate/,count-servers-that-communicate,深度优先搜索、广度优先搜索、并查集、数组、计数、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/count-servers-that-communicate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1267.%20%E7%BB%9F%E8%AE%A1%E5%8F%82%E4%B8%8E%E9%80%9A%E4%BF%A1%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8.md,61.9%,中等,206 -1268,1200-1299,1268. 搜索推荐系统,搜索推荐系统,https://leetcode.cn/problems/search-suggestions-system/,search-suggestions-system,字典树、数组、字符串,https://algo.itcharge.cn/Solutions/1200-1299/search-suggestions-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1268.%20%E6%90%9C%E7%B4%A2%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F.md,59.5%,中等,244 -1269,1200-1299,1269. 停在原地的方案数,停在原地的方案数,https://leetcode.cn/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/,number-of-ways-to-stay-in-the-same-place-after-some-steps,动态规划,https://algo.itcharge.cn/Solutions/1200-1299/number-of-ways-to-stay-in-the-same-place-after-some-steps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1269.%20%E5%81%9C%E5%9C%A8%E5%8E%9F%E5%9C%B0%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,49.0%,困难,350 -1270,1200-1299,1270. 向公司CEO汇报工作的所有人,向公司CEO汇报工作的所有人,https://leetcode.cn/problems/all-people-report-to-the-given-manager/,all-people-report-to-the-given-manager,数据库,https://algo.itcharge.cn/Solutions/1200-1299/all-people-report-to-the-given-manager/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1270.%20%E5%90%91%E5%85%AC%E5%8F%B8CEO%E6%B1%87%E6%8A%A5%E5%B7%A5%E4%BD%9C%E7%9A%84%E6%89%80%E6%9C%89%E4%BA%BA.md,79.7%,中等,135 -1271,1200-1299,1271. 十六进制魔术数字,十六进制魔术数字,https://leetcode.cn/problems/hexspeak/,hexspeak,数学、字符串,https://algo.itcharge.cn/Solutions/1200-1299/hexspeak/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1271.%20%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E9%AD%94%E6%9C%AF%E6%95%B0%E5%AD%97.md,52.4%,简单,67 -1272,1200-1299,1272. 删除区间,删除区间,https://leetcode.cn/problems/remove-interval/,remove-interval,数组,https://algo.itcharge.cn/Solutions/1200-1299/remove-interval/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1272.%20%E5%88%A0%E9%99%A4%E5%8C%BA%E9%97%B4.md,56.8%,中等,51 -1273,1200-1299,1273. 删除树节点,删除树节点,https://leetcode.cn/problems/delete-tree-nodes/,delete-tree-nodes,树、深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/1200-1299/delete-tree-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1273.%20%E5%88%A0%E9%99%A4%E6%A0%91%E8%8A%82%E7%82%B9.md,57.1%,中等,70 -1274,1200-1299,1274. 矩形内船只的数目,矩形内船只的数目,https://leetcode.cn/problems/number-of-ships-in-a-rectangle/,number-of-ships-in-a-rectangle,数组、分治、交互,https://algo.itcharge.cn/Solutions/1200-1299/number-of-ships-in-a-rectangle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1274.%20%E7%9F%A9%E5%BD%A2%E5%86%85%E8%88%B9%E5%8F%AA%E7%9A%84%E6%95%B0%E7%9B%AE.md,64.9%,困难,36 -1275,1200-1299,1275. 找出井字棋的获胜者,找出井字棋的获胜者,https://leetcode.cn/problems/find-winner-on-a-tic-tac-toe-game/,find-winner-on-a-tic-tac-toe-game,数组、哈希表、矩阵、模拟,https://algo.itcharge.cn/Solutions/1200-1299/find-winner-on-a-tic-tac-toe-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1275.%20%E6%89%BE%E5%87%BA%E4%BA%95%E5%AD%97%E6%A3%8B%E7%9A%84%E8%8E%B7%E8%83%9C%E8%80%85.md,55.1%,简单,241 -1276,1200-1299,1276. 不浪费原料的汉堡制作方案,不浪费原料的汉堡制作方案,https://leetcode.cn/problems/number-of-burgers-with-no-waste-of-ingredients/,number-of-burgers-with-no-waste-of-ingredients,数学,https://algo.itcharge.cn/Solutions/1200-1299/number-of-burgers-with-no-waste-of-ingredients/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1276.%20%E4%B8%8D%E6%B5%AA%E8%B4%B9%E5%8E%9F%E6%96%99%E7%9A%84%E6%B1%89%E5%A0%A1%E5%88%B6%E4%BD%9C%E6%96%B9%E6%A1%88.md,50.3%,中等,132 -1277,1200-1299,1277. 统计全为 1 的正方形子矩阵,统计全为 1 的正方形子矩阵,https://leetcode.cn/problems/count-square-submatrices-with-all-ones/,count-square-submatrices-with-all-ones,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/count-square-submatrices-with-all-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1277.%20%E7%BB%9F%E8%AE%A1%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2%E5%AD%90%E7%9F%A9%E9%98%B5.md,73.5%,中等,345 -1278,1200-1299,1278. 分割回文串 III,分割回文串 III,https://leetcode.cn/problems/palindrome-partitioning-iii/,palindrome-partitioning-iii,字符串、动态规划,https://algo.itcharge.cn/Solutions/1200-1299/palindrome-partitioning-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1278.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2%20III.md,62.2%,困难,97 -1279,1200-1299,1279. 红绿灯路口,红绿灯路口,https://leetcode.cn/problems/traffic-light-controlled-intersection/,traffic-light-controlled-intersection,多线程,https://algo.itcharge.cn/Solutions/1200-1299/traffic-light-controlled-intersection/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1279.%20%E7%BA%A2%E7%BB%BF%E7%81%AF%E8%B7%AF%E5%8F%A3.md,57.6%,简单,28 -1280,1200-1299,1280. 学生们参加各科测试的次数,学生们参加各科测试的次数,https://leetcode.cn/problems/students-and-examinations/,students-and-examinations,数据库,https://algo.itcharge.cn/Solutions/1200-1299/students-and-examinations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1280.%20%E5%AD%A6%E7%94%9F%E4%BB%AC%E5%8F%82%E5%8A%A0%E5%90%84%E7%A7%91%E6%B5%8B%E8%AF%95%E7%9A%84%E6%AC%A1%E6%95%B0.md,49.6%,简单,157 -1281,1200-1299,1281. 整数的各位积和之差,整数的各位积和之差,https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/,subtract-the-product-and-sum-of-digits-of-an-integer,数学,https://algo.itcharge.cn/Solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1281.%20%E6%95%B4%E6%95%B0%E7%9A%84%E5%90%84%E4%BD%8D%E7%A7%AF%E5%92%8C%E4%B9%8B%E5%B7%AE.md,83.2%,简单,929 -1282,1200-1299,1282. 用户分组,用户分组,https://leetcode.cn/problems/group-the-people-given-the-group-size-they-belong-to/,group-the-people-given-the-group-size-they-belong-to,数组、哈希表,https://algo.itcharge.cn/Solutions/1200-1299/group-the-people-given-the-group-size-they-belong-to/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1282.%20%E7%94%A8%E6%88%B7%E5%88%86%E7%BB%84.md,86.5%,中等,803 -1283,1200-1299,1283. 使结果不超过阈值的最小除数,使结果不超过阈值的最小除数,https://leetcode.cn/problems/find-the-smallest-divisor-given-a-threshold/,find-the-smallest-divisor-given-a-threshold,数组、二分查找,https://algo.itcharge.cn/Solutions/1200-1299/find-the-smallest-divisor-given-a-threshold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1283.%20%E4%BD%BF%E7%BB%93%E6%9E%9C%E4%B8%8D%E8%B6%85%E8%BF%87%E9%98%88%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E9%99%A4%E6%95%B0.md,49.1%,中等,190 -1284,1200-1299,1284. 转化为全零矩阵的最少反转次数,转化为全零矩阵的最少反转次数,https://leetcode.cn/problems/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix/,minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix,位运算、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1284.%20%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%85%A8%E9%9B%B6%E7%9F%A9%E9%98%B5%E7%9A%84%E6%9C%80%E5%B0%91%E5%8F%8D%E8%BD%AC%E6%AC%A1%E6%95%B0.md,68.2%,困难,98 -1285,1200-1299,1285. 找到连续区间的开始和结束数字,找到连续区间的开始和结束数字,https://leetcode.cn/problems/find-the-start-and-end-number-of-continuous-ranges/,find-the-start-and-end-number-of-continuous-ranges,数据库,https://algo.itcharge.cn/Solutions/1200-1299/find-the-start-and-end-number-of-continuous-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1285.%20%E6%89%BE%E5%88%B0%E8%BF%9E%E7%BB%AD%E5%8C%BA%E9%97%B4%E7%9A%84%E5%BC%80%E5%A7%8B%E5%92%8C%E7%BB%93%E6%9D%9F%E6%95%B0%E5%AD%97.md,80.9%,中等,113 -1286,1200-1299,1286. 字母组合迭代器,字母组合迭代器,https://leetcode.cn/problems/iterator-for-combination/,iterator-for-combination,设计、字符串、回溯、迭代器,https://algo.itcharge.cn/Solutions/1200-1299/iterator-for-combination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1286.%20%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88%E8%BF%AD%E4%BB%A3%E5%99%A8.md,65.1%,中等,144 -1287,1200-1299,1287. 有序数组中出现次数超过25%的元素,有序数组中出现次数超过25%的元素,https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array/,element-appearing-more-than-25-in-sorted-array,数组,https://algo.itcharge.cn/Solutions/1200-1299/element-appearing-more-than-25-in-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1287.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%8725%25%E7%9A%84%E5%85%83%E7%B4%A0.md,58.8%,简单,344 -1288,1200-1299,1288. 删除被覆盖区间,删除被覆盖区间,https://leetcode.cn/problems/remove-covered-intervals/,remove-covered-intervals,数组、排序,https://algo.itcharge.cn/Solutions/1200-1299/remove-covered-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1288.%20%E5%88%A0%E9%99%A4%E8%A2%AB%E8%A6%86%E7%9B%96%E5%8C%BA%E9%97%B4.md,56.0%,中等,291 -1289,1200-1299,1289. 下降路径最小和 II,下降路径最小和 II,https://leetcode.cn/problems/minimum-falling-path-sum-ii/,minimum-falling-path-sum-ii,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/minimum-falling-path-sum-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1289.%20%E4%B8%8B%E9%99%8D%E8%B7%AF%E5%BE%84%E6%9C%80%E5%B0%8F%E5%92%8C%20%20II.md,58.4%,困难,181 -1290,1200-1299,1290. 二进制链表转整数,二进制链表转整数,https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/,convert-binary-number-in-a-linked-list-to-integer,链表、数学,https://algo.itcharge.cn/Solutions/1200-1299/convert-binary-number-in-a-linked-list-to-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1290.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%93%BE%E8%A1%A8%E8%BD%AC%E6%95%B4%E6%95%B0.md,80.5%,简单,986 -1291,1200-1299,1291. 顺次数,顺次数,https://leetcode.cn/problems/sequential-digits/,sequential-digits,枚举,https://algo.itcharge.cn/Solutions/1200-1299/sequential-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1291.%20%E9%A1%BA%E6%AC%A1%E6%95%B0.md,53.5%,中等,219 -1292,1200-1299,1292. 元素和小于等于阈值的正方形的最大边长,元素和小于等于阈值的正方形的最大边长,https://leetcode.cn/problems/maximum-side-length-of-a-square-with-sum-less-than-or-equal-to-threshold/,maximum-side-length-of-a-square-with-sum-less-than-or-equal-to-threshold,数组、二分查找、矩阵、前缀和,https://algo.itcharge.cn/Solutions/1200-1299/maximum-side-length-of-a-square-with-sum-less-than-or-equal-to-threshold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1292.%20%E5%85%83%E7%B4%A0%E5%92%8C%E5%B0%8F%E4%BA%8E%E7%AD%89%E4%BA%8E%E9%98%88%E5%80%BC%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2%E7%9A%84%E6%9C%80%E5%A4%A7%E8%BE%B9%E9%95%BF.md,50.7%,中等,161 -1293,1200-1299,1293. 网格中的最短路径,网格中的最短路径,https://leetcode.cn/problems/shortest-path-in-a-grid-with-obstacles-elimination/,shortest-path-in-a-grid-with-obstacles-elimination,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1200-1299/shortest-path-in-a-grid-with-obstacles-elimination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1293.%20%E7%BD%91%E6%A0%BC%E4%B8%AD%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,38.3%,困难,198 -1294,1200-1299,1294. 不同国家的天气类型,不同国家的天气类型,https://leetcode.cn/problems/weather-type-in-each-country/,weather-type-in-each-country,数据库,https://algo.itcharge.cn/Solutions/1200-1299/weather-type-in-each-country/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1294.%20%E4%B8%8D%E5%90%8C%E5%9B%BD%E5%AE%B6%E7%9A%84%E5%A4%A9%E6%B0%94%E7%B1%BB%E5%9E%8B.md,65.5%,简单,132 -1295,1200-1299,1295. 统计位数为偶数的数字,统计位数为偶数的数字,https://leetcode.cn/problems/find-numbers-with-even-number-of-digits/,find-numbers-with-even-number-of-digits,数组,https://algo.itcharge.cn/Solutions/1200-1299/find-numbers-with-even-number-of-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1295.%20%E7%BB%9F%E8%AE%A1%E4%BD%8D%E6%95%B0%E4%B8%BA%E5%81%B6%E6%95%B0%E7%9A%84%E6%95%B0%E5%AD%97.md,80.1%,简单,770 -1296,1200-1299,1296. 划分数组为连续数字的集合,划分数组为连续数字的集合,https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/,divide-array-in-sets-of-k-consecutive-numbers,贪心、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1296.%20%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84%E4%B8%BA%E8%BF%9E%E7%BB%AD%E6%95%B0%E5%AD%97%E7%9A%84%E9%9B%86%E5%90%88.md,49.1%,中等,169 -1297,1200-1299,1297. 子串的最大出现次数,子串的最大出现次数,https://leetcode.cn/problems/maximum-number-of-occurrences-of-a-substring/,maximum-number-of-occurrences-of-a-substring,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1200-1299/maximum-number-of-occurrences-of-a-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1297.%20%E5%AD%90%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0.md,48.3%,中等,127 -1298,1200-1299,1298. 你能从盒子里获得的最大糖果数,你能从盒子里获得的最大糖果数,https://leetcode.cn/problems/maximum-candies-you-can-get-from-boxes/,maximum-candies-you-can-get-from-boxes,广度优先搜索、图、数组,https://algo.itcharge.cn/Solutions/1200-1299/maximum-candies-you-can-get-from-boxes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1298.%20%E4%BD%A0%E8%83%BD%E4%BB%8E%E7%9B%92%E5%AD%90%E9%87%8C%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%B3%96%E6%9E%9C%E6%95%B0.md,59.9%,困难,119 -1299,1200-1299,1299. 将每个元素替换为右侧最大元素,将每个元素替换为右侧最大元素,https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side/,replace-elements-with-greatest-element-on-right-side,数组,https://algo.itcharge.cn/Solutions/1200-1299/replace-elements-with-greatest-element-on-right-side/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1299.%20%E5%B0%86%E6%AF%8F%E4%B8%AA%E5%85%83%E7%B4%A0%E6%9B%BF%E6%8D%A2%E4%B8%BA%E5%8F%B3%E4%BE%A7%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md,77.0%,简单,562 -1300,1300-1399,1300. 转变数组后最接近目标值的数组和,转变数组后最接近目标值的数组和,https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/,sum-of-mutated-array-closest-to-target,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/1300-1399/sum-of-mutated-array-closest-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1300.%20%E8%BD%AC%E5%8F%98%E6%95%B0%E7%BB%84%E5%90%8E%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md,46.8%,中等,452 -1301,1300-1399,1301. 最大得分的路径数目,最大得分的路径数目,https://leetcode.cn/problems/number-of-paths-with-max-score/,number-of-paths-with-max-score,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1300-1399/number-of-paths-with-max-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1301.%20%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0%E7%9B%AE.md,37.7%,困难,148 -1302,1300-1399,1302. 层数最深叶子节点的和,层数最深叶子节点的和,https://leetcode.cn/problems/deepest-leaves-sum/,deepest-leaves-sum,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/deepest-leaves-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1302.%20%E5%B1%82%E6%95%B0%E6%9C%80%E6%B7%B1%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E5%92%8C.md,85.5%,中等,774 -1303,1300-1399,1303. 求团队人数,求团队人数,https://leetcode.cn/problems/find-the-team-size/,find-the-team-size,数据库,https://algo.itcharge.cn/Solutions/1300-1399/find-the-team-size/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1303.%20%E6%B1%82%E5%9B%A2%E9%98%9F%E4%BA%BA%E6%95%B0.md,82.5%,简单,167 -1304,1300-1399,1304. 和为零的 N 个不同整数,和为零的 N 个不同整数,https://leetcode.cn/problems/find-n-unique-integers-sum-up-to-zero/,find-n-unique-integers-sum-up-to-zero,数组、数学,https://algo.itcharge.cn/Solutions/1300-1399/find-n-unique-integers-sum-up-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1304.%20%E5%92%8C%E4%B8%BA%E9%9B%B6%E7%9A%84%20N%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0.md,70.3%,简单,488 -1305,1300-1399,1305. 两棵二叉搜索树中的所有元素,两棵二叉搜索树中的所有元素,https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/,all-elements-in-two-binary-search-trees,树、深度优先搜索、二叉搜索树、二叉树、排序,https://algo.itcharge.cn/Solutions/1300-1399/all-elements-in-two-binary-search-trees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1305.%20%E4%B8%A4%E6%A3%B5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0.md,78.0%,中等,693 -1306,1300-1399,1306. 跳跃游戏 III,跳跃游戏 III,https://leetcode.cn/problems/jump-game-iii/,jump-game-iii,深度优先搜索、广度优先搜索、数组,https://algo.itcharge.cn/Solutions/1300-1399/jump-game-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1306.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20III.md,58.2%,中等,450 -1307,1300-1399,1307. 口算难题,口算难题,https://leetcode.cn/problems/verbal-arithmetic-puzzle/,verbal-arithmetic-puzzle,数组、数学、字符串、回溯,https://algo.itcharge.cn/Solutions/1300-1399/verbal-arithmetic-puzzle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1307.%20%E5%8F%A3%E7%AE%97%E9%9A%BE%E9%A2%98.md,34.7%,困难,73 -1308,1300-1399,1308. 不同性别每日分数总计,不同性别每日分数总计,https://leetcode.cn/problems/running-total-for-different-genders/,running-total-for-different-genders,数据库,https://algo.itcharge.cn/Solutions/1300-1399/running-total-for-different-genders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1308.%20%E4%B8%8D%E5%90%8C%E6%80%A7%E5%88%AB%E6%AF%8F%E6%97%A5%E5%88%86%E6%95%B0%E6%80%BB%E8%AE%A1.md,73.6%,中等,102 -1309,1300-1399,1309. 解码字母到整数映射,解码字母到整数映射,https://leetcode.cn/problems/decrypt-string-from-alphabet-to-integer-mapping/,decrypt-string-from-alphabet-to-integer-mapping,字符串,https://algo.itcharge.cn/Solutions/1300-1399/decrypt-string-from-alphabet-to-integer-mapping/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1309.%20%E8%A7%A3%E7%A0%81%E5%AD%97%E6%AF%8D%E5%88%B0%E6%95%B4%E6%95%B0%E6%98%A0%E5%B0%84.md,76.8%,简单,485 -1310,1300-1399,1310. 子数组异或查询,子数组异或查询,https://leetcode.cn/problems/xor-queries-of-a-subarray/,xor-queries-of-a-subarray,位运算、数组、前缀和,https://algo.itcharge.cn/Solutions/1300-1399/xor-queries-of-a-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md,72.0%,中等,558 -1311,1300-1399,1311. 获取你好友已观看的视频,获取你好友已观看的视频,https://leetcode.cn/problems/get-watched-videos-by-your-friends/,get-watched-videos-by-your-friends,广度优先搜索、图、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1300-1399/get-watched-videos-by-your-friends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1311.%20%E8%8E%B7%E5%8F%96%E4%BD%A0%E5%A5%BD%E5%8F%8B%E5%B7%B2%E8%A7%82%E7%9C%8B%E7%9A%84%E8%A7%86%E9%A2%91.md,40.0%,中等,118 -1312,1300-1399,1312. 让字符串成为回文串的最少插入次数,让字符串成为回文串的最少插入次数,https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/,minimum-insertion-steps-to-make-a-string-palindrome,字符串、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/minimum-insertion-steps-to-make-a-string-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1312.%20%E8%AE%A9%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%88%90%E4%B8%BA%E5%9B%9E%E6%96%87%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%8F%92%E5%85%A5%E6%AC%A1%E6%95%B0.md,69.1%,困难,254 -1313,1300-1399,1313. 解压缩编码列表,解压缩编码列表,https://leetcode.cn/problems/decompress-run-length-encoded-list/,decompress-run-length-encoded-list,数组,https://algo.itcharge.cn/Solutions/1300-1399/decompress-run-length-encoded-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1313.%20%E8%A7%A3%E5%8E%8B%E7%BC%A9%E7%BC%96%E7%A0%81%E5%88%97%E8%A1%A8.md,83.2%,简单,647 -1314,1300-1399,1314. 矩阵区域和,矩阵区域和,https://leetcode.cn/problems/matrix-block-sum/,matrix-block-sum,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/1300-1399/matrix-block-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1314.%20%E7%9F%A9%E9%98%B5%E5%8C%BA%E5%9F%9F%E5%92%8C.md,75.5%,中等,375 -1315,1300-1399,1315. 祖父节点值为偶数的节点和,祖父节点值为偶数的节点和,https://leetcode.cn/problems/sum-of-nodes-with-even-valued-grandparent/,sum-of-nodes-with-even-valued-grandparent,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/sum-of-nodes-with-even-valued-grandparent/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1315.%20%E7%A5%96%E7%88%B6%E8%8A%82%E7%82%B9%E5%80%BC%E4%B8%BA%E5%81%B6%E6%95%B0%E7%9A%84%E8%8A%82%E7%82%B9%E5%92%8C.md,81.6%,中等,377 -1316,1300-1399,1316. 不同的循环子字符串,不同的循环子字符串,https://leetcode.cn/problems/distinct-echo-substrings/,distinct-echo-substrings,字典树、字符串、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1300-1399/distinct-echo-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1316.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%BE%AA%E7%8E%AF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,48.3%,困难,88 -1317,1300-1399,1317. 将整数转换为两个无零整数的和,将整数转换为两个无零整数的和,https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/,convert-integer-to-the-sum-of-two-no-zero-integers,数学,https://algo.itcharge.cn/Solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1317.%20%E5%B0%86%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%B8%A4%E4%B8%AA%E6%97%A0%E9%9B%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%92%8C.md,61.9%,简单,258 -1318,1300-1399,1318. 或运算的最小翻转次数,或运算的最小翻转次数,https://leetcode.cn/problems/minimum-flips-to-make-a-or-b-equal-to-c/,minimum-flips-to-make-a-or-b-equal-to-c,位运算,https://algo.itcharge.cn/Solutions/1300-1399/minimum-flips-to-make-a-or-b-equal-to-c/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1318.%20%E6%88%96%E8%BF%90%E7%AE%97%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md,66.8%,中等,177 -1319,1300-1399,1319. 连通网络的操作次数,连通网络的操作次数,https://leetcode.cn/problems/number-of-operations-to-make-network-connected/,number-of-operations-to-make-network-connected,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/1300-1399/number-of-operations-to-make-network-connected/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1319.%20%E8%BF%9E%E9%80%9A%E7%BD%91%E7%BB%9C%E7%9A%84%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,62.4%,中等,687 -1320,1300-1399,1320. 二指输入的的最小距离,二指输入的的最小距离,https://leetcode.cn/problems/minimum-distance-to-type-a-word-using-two-fingers/,minimum-distance-to-type-a-word-using-two-fingers,字符串、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/minimum-distance-to-type-a-word-using-two-fingers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1320.%20%E4%BA%8C%E6%8C%87%E8%BE%93%E5%85%A5%E7%9A%84%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%9D%E7%A6%BB.md,60.6%,困难,79 -1321,1300-1399,1321. 餐馆营业额变化增长,餐馆营业额变化增长,https://leetcode.cn/problems/restaurant-growth/,restaurant-growth,数据库,https://algo.itcharge.cn/Solutions/1300-1399/restaurant-growth/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1321.%20%E9%A4%90%E9%A6%86%E8%90%A5%E4%B8%9A%E9%A2%9D%E5%8F%98%E5%8C%96%E5%A2%9E%E9%95%BF.md,61.9%,中等,172 -1322,1300-1399,1322. 广告效果,广告效果,https://leetcode.cn/problems/ads-performance/,ads-performance,数据库,https://algo.itcharge.cn/Solutions/1300-1399/ads-performance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1322.%20%E5%B9%BF%E5%91%8A%E6%95%88%E6%9E%9C.md,59.6%,简单,86 -1323,1300-1399,1323. 6 和 9 组成的最大数字,6 和 9 组成的最大数字,https://leetcode.cn/problems/maximum-69-number/,maximum-69-number,贪心、数学,https://algo.itcharge.cn/Solutions/1300-1399/maximum-69-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1323.%206%20%E5%92%8C%209%20%E7%BB%84%E6%88%90%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md,75.0%,简单,639 -1324,1300-1399,1324. 竖直打印单词,竖直打印单词,https://leetcode.cn/problems/print-words-vertically/,print-words-vertically,数组、字符串、模拟,https://algo.itcharge.cn/Solutions/1300-1399/print-words-vertically/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1324.%20%E7%AB%96%E7%9B%B4%E6%89%93%E5%8D%B0%E5%8D%95%E8%AF%8D.md,59.2%,中等,179 -1325,1300-1399,1325. 删除给定值的叶子节点,删除给定值的叶子节点,https://leetcode.cn/problems/delete-leaves-with-a-given-value/,delete-leaves-with-a-given-value,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/delete-leaves-with-a-given-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1325.%20%E5%88%A0%E9%99%A4%E7%BB%99%E5%AE%9A%E5%80%BC%E7%9A%84%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9.md,73.3%,中等,265 -1326,1300-1399,1326. 灌溉花园的最少水龙头数目,灌溉花园的最少水龙头数目,https://leetcode.cn/problems/minimum-number-of-taps-to-open-to-water-a-garden/,minimum-number-of-taps-to-open-to-water-a-garden,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/minimum-number-of-taps-to-open-to-water-a-garden/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1326.%20%E7%81%8C%E6%BA%89%E8%8A%B1%E5%9B%AD%E7%9A%84%E6%9C%80%E5%B0%91%E6%B0%B4%E9%BE%99%E5%A4%B4%E6%95%B0%E7%9B%AE.md,54.2%,困难,262 -1327,1300-1399,1327. 列出指定时间段内所有的下单产品,列出指定时间段内所有的下单产品,https://leetcode.cn/problems/list-the-products-ordered-in-a-period/,list-the-products-ordered-in-a-period,数据库,https://algo.itcharge.cn/Solutions/1300-1399/list-the-products-ordered-in-a-period/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1327.%20%E5%88%97%E5%87%BA%E6%8C%87%E5%AE%9A%E6%97%B6%E9%97%B4%E6%AE%B5%E5%86%85%E6%89%80%E6%9C%89%E7%9A%84%E4%B8%8B%E5%8D%95%E4%BA%A7%E5%93%81.md,69.5%,简单,92 -1328,1300-1399,1328. 破坏回文串,破坏回文串,https://leetcode.cn/problems/break-a-palindrome/,break-a-palindrome,贪心、字符串,https://algo.itcharge.cn/Solutions/1300-1399/break-a-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1328.%20%E7%A0%B4%E5%9D%8F%E5%9B%9E%E6%96%87%E4%B8%B2.md,47.0%,中等,170 -1329,1300-1399,1329. 将矩阵按对角线排序,将矩阵按对角线排序,https://leetcode.cn/problems/sort-the-matrix-diagonally/,sort-the-matrix-diagonally,数组、矩阵、排序,https://algo.itcharge.cn/Solutions/1300-1399/sort-the-matrix-diagonally/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1329.%20%E5%B0%86%E7%9F%A9%E9%98%B5%E6%8C%89%E5%AF%B9%E8%A7%92%E7%BA%BF%E6%8E%92%E5%BA%8F.md,77.8%,中等,240 -1330,1300-1399,1330. 翻转子数组得到最大的数组值,翻转子数组得到最大的数组值,https://leetcode.cn/problems/reverse-subarray-to-maximize-array-value/,reverse-subarray-to-maximize-array-value,贪心、数组、数学,https://algo.itcharge.cn/Solutions/1300-1399/reverse-subarray-to-maximize-array-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1330.%20%E7%BF%BB%E8%BD%AC%E5%AD%90%E6%95%B0%E7%BB%84%E5%BE%97%E5%88%B0%E6%9C%80%E5%A4%A7%E7%9A%84%E6%95%B0%E7%BB%84%E5%80%BC.md,57.7%,困难,81 -1331,1300-1399,1331. 数组序号转换,数组序号转换,https://leetcode.cn/problems/rank-transform-of-an-array/,rank-transform-of-an-array,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1300-1399/rank-transform-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1331.%20%E6%95%B0%E7%BB%84%E5%BA%8F%E5%8F%B7%E8%BD%AC%E6%8D%A2.md,60.4%,简单,529 -1332,1300-1399,1332. 删除回文子序列,删除回文子序列,https://leetcode.cn/problems/remove-palindromic-subsequences/,remove-palindromic-subsequences,双指针、字符串,https://algo.itcharge.cn/Solutions/1300-1399/remove-palindromic-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1332.%20%E5%88%A0%E9%99%A4%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md,77.7%,简单,379 -1333,1300-1399,1333. 餐厅过滤器,餐厅过滤器,https://leetcode.cn/problems/filter-restaurants-by-vegan-friendly-price-and-distance/,filter-restaurants-by-vegan-friendly-price-and-distance,数组、排序,https://algo.itcharge.cn/Solutions/1300-1399/filter-restaurants-by-vegan-friendly-price-and-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1333.%20%E9%A4%90%E5%8E%85%E8%BF%87%E6%BB%A4%E5%99%A8.md,56.7%,中等,166 -1334,1300-1399,1334. 阈值距离内邻居最少的城市,阈值距离内邻居最少的城市,https://leetcode.cn/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/,find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance,图、动态规划、最短路,https://algo.itcharge.cn/Solutions/1300-1399/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1334.%20%E9%98%88%E5%80%BC%E8%B7%9D%E7%A6%BB%E5%86%85%E9%82%BB%E5%B1%85%E6%9C%80%E5%B0%91%E7%9A%84%E5%9F%8E%E5%B8%82.md,52.7%,中等,168 -1335,1300-1399,1335. 工作计划的最低难度,工作计划的最低难度,https://leetcode.cn/problems/minimum-difficulty-of-a-job-schedule/,minimum-difficulty-of-a-job-schedule,数组、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/minimum-difficulty-of-a-job-schedule/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1335.%20%E5%B7%A5%E4%BD%9C%E8%AE%A1%E5%88%92%E7%9A%84%E6%9C%80%E4%BD%8E%E9%9A%BE%E5%BA%A6.md,66.7%,困难,175 -1336,1300-1399,1336. 每次访问的交易次数,每次访问的交易次数,https://leetcode.cn/problems/number-of-transactions-per-visit/,number-of-transactions-per-visit,数据库,https://algo.itcharge.cn/Solutions/1300-1399/number-of-transactions-per-visit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1336.%20%E6%AF%8F%E6%AC%A1%E8%AE%BF%E9%97%AE%E7%9A%84%E4%BA%A4%E6%98%93%E6%AC%A1%E6%95%B0.md,45.5%,困难,81 -1337,1300-1399,1337. 矩阵中战斗力最弱的 K 行,矩阵中战斗力最弱的 K 行,https://leetcode.cn/problems/the-k-weakest-rows-in-a-matrix/,the-k-weakest-rows-in-a-matrix,数组、二分查找、矩阵、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/the-k-weakest-rows-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1337.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%88%98%E6%96%97%E5%8A%9B%E6%9C%80%E5%BC%B1%E7%9A%84%20K%20%E8%A1%8C.md,68.6%,简单,909 -1338,1300-1399,1338. 数组大小减半,数组大小减半,https://leetcode.cn/problems/reduce-array-size-to-the-half/,reduce-array-size-to-the-half,贪心、数组、哈希表、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/reduce-array-size-to-the-half/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1338.%20%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F%E5%87%8F%E5%8D%8A.md,64.9%,中等,186 -1339,1300-1399,1339. 分裂二叉树的最大乘积,分裂二叉树的最大乘积,https://leetcode.cn/problems/maximum-product-of-splitted-binary-tree/,maximum-product-of-splitted-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/maximum-product-of-splitted-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1339.%20%E5%88%86%E8%A3%82%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,41.5%,中等,189 -1340,1300-1399,1340. 跳跃游戏 V,跳跃游戏 V,https://leetcode.cn/problems/jump-game-v/,jump-game-v,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1300-1399/jump-game-v/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1340.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20V.md,59.3%,困难,136 -1341,1300-1399,1341. 电影评分,电影评分,https://leetcode.cn/problems/movie-rating/,movie-rating,数据库,https://algo.itcharge.cn/Solutions/1300-1399/movie-rating/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1341.%20%E7%94%B5%E5%BD%B1%E8%AF%84%E5%88%86.md,40.5%,中等,121 -1342,1300-1399,1342. 将数字变成 0 的操作次数,将数字变成 0 的操作次数,https://leetcode.cn/problems/number-of-steps-to-reduce-a-number-to-zero/,number-of-steps-to-reduce-a-number-to-zero,位运算、数学,https://algo.itcharge.cn/Solutions/1300-1399/number-of-steps-to-reduce-a-number-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1342.%20%E5%B0%86%E6%95%B0%E5%AD%97%E5%8F%98%E6%88%90%200%20%E7%9A%84%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,76.0%,简单,1576 -1343,1300-1399,1343. 大小为 K 且平均值大于等于阈值的子数组数目,大小为 K 且平均值大于等于阈值的子数组数目,https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/,number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold,数组、滑动窗口,https://algo.itcharge.cn/Solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1343.%20%E5%A4%A7%E5%B0%8F%E4%B8%BA%20K%20%E4%B8%94%E5%B9%B3%E5%9D%87%E5%80%BC%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E9%98%88%E5%80%BC%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,57.4%,中等,303 -1344,1300-1399,1344. 时钟指针的夹角,时钟指针的夹角,https://leetcode.cn/problems/angle-between-hands-of-a-clock/,angle-between-hands-of-a-clock,数学,https://algo.itcharge.cn/Solutions/1300-1399/angle-between-hands-of-a-clock/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1344.%20%E6%97%B6%E9%92%9F%E6%8C%87%E9%92%88%E7%9A%84%E5%A4%B9%E8%A7%92.md,60.2%,中等,179 -1345,1300-1399,1345. 跳跃游戏 IV,跳跃游戏 IV,https://leetcode.cn/problems/jump-game-iv/,jump-game-iv,广度优先搜索、数组、哈希表,https://algo.itcharge.cn/Solutions/1300-1399/jump-game-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1345.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20IV.md,45.7%,困难,312 -1346,1300-1399,1346. 检查整数及其两倍数是否存在,检查整数及其两倍数是否存在,https://leetcode.cn/problems/check-if-n-and-its-double-exist/,check-if-n-and-its-double-exist,数组、哈希表、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/1300-1399/check-if-n-and-its-double-exist/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1346.%20%E6%A3%80%E6%9F%A5%E6%95%B4%E6%95%B0%E5%8F%8A%E5%85%B6%E4%B8%A4%E5%80%8D%E6%95%B0%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8.md,42.0%,简单,545 -1347,1300-1399,1347. 制造字母异位词的最小步骤数,制造字母异位词的最小步骤数,https://leetcode.cn/problems/minimum-number-of-steps-to-make-two-strings-anagram/,minimum-number-of-steps-to-make-two-strings-anagram,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1347.%20%E5%88%B6%E9%80%A0%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E6%AD%A5%E9%AA%A4%E6%95%B0.md,76.1%,中等,175 -1348,1300-1399,1348. 推文计数,推文计数,https://leetcode.cn/problems/tweet-counts-per-frequency/,tweet-counts-per-frequency,设计、哈希表、二分查找、有序集合、排序,https://algo.itcharge.cn/Solutions/1300-1399/tweet-counts-per-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1348.%20%E6%8E%A8%E6%96%87%E8%AE%A1%E6%95%B0.md,35.0%,中等,80 -1349,1300-1399,1349. 参加考试的最大学生数,参加考试的最大学生数,https://leetcode.cn/problems/maximum-students-taking-exam/,maximum-students-taking-exam,位运算、数组、动态规划、状态压缩、矩阵,https://algo.itcharge.cn/Solutions/1300-1399/maximum-students-taking-exam/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md,54.3%,困难,138 -1350,1300-1399,1350. 院系无效的学生,院系无效的学生,https://leetcode.cn/problems/students-with-invalid-departments/,students-with-invalid-departments,数据库,https://algo.itcharge.cn/Solutions/1300-1399/students-with-invalid-departments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1350.%20%E9%99%A2%E7%B3%BB%E6%97%A0%E6%95%88%E7%9A%84%E5%AD%A6%E7%94%9F.md,85.0%,简单,106 -1351,1300-1399,1351. 统计有序矩阵中的负数,统计有序矩阵中的负数,https://leetcode.cn/problems/count-negative-numbers-in-a-sorted-matrix/,count-negative-numbers-in-a-sorted-matrix,数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/1300-1399/count-negative-numbers-in-a-sorted-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1351.%20%E7%BB%9F%E8%AE%A1%E6%9C%89%E5%BA%8F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B4%9F%E6%95%B0.md,74.2%,简单,797 -1352,1300-1399,1352. 最后 K 个数的乘积,最后 K 个数的乘积,https://leetcode.cn/problems/product-of-the-last-k-numbers/,product-of-the-last-k-numbers,设计、队列、数组、数学、数据流,https://algo.itcharge.cn/Solutions/1300-1399/product-of-the-last-k-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1352.%20%E6%9C%80%E5%90%8E%20K%20%E4%B8%AA%E6%95%B0%E7%9A%84%E4%B9%98%E7%A7%AF.md,48.4%,中等,130 -1353,1300-1399,1353. 最多可以参加的会议数目,最多可以参加的会议数目,https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended/,maximum-number-of-events-that-can-be-attended,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/maximum-number-of-events-that-can-be-attended/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1353.%20%E6%9C%80%E5%A4%9A%E5%8F%AF%E4%BB%A5%E5%8F%82%E5%8A%A0%E7%9A%84%E4%BC%9A%E8%AE%AE%E6%95%B0%E7%9B%AE.md,29.5%,中等,213 -1354,1300-1399,1354. 多次求和构造目标数组,多次求和构造目标数组,https://leetcode.cn/problems/construct-target-array-with-multiple-sums/,construct-target-array-with-multiple-sums,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/construct-target-array-with-multiple-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1354.%20%E5%A4%9A%E6%AC%A1%E6%B1%82%E5%92%8C%E6%9E%84%E9%80%A0%E7%9B%AE%E6%A0%87%E6%95%B0%E7%BB%84.md,29.0%,困难,93 -1355,1300-1399,1355. 活动参与者,活动参与者,https://leetcode.cn/problems/activity-participants/,activity-participants,数据库,https://algo.itcharge.cn/Solutions/1300-1399/activity-participants/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1355.%20%E6%B4%BB%E5%8A%A8%E5%8F%82%E4%B8%8E%E8%80%85.md,66.8%,中等,86 -1356,1300-1399,1356. 根据数字二进制下 1 的数目排序,根据数字二进制下 1 的数目排序,https://leetcode.cn/problems/sort-integers-by-the-number-of-1-bits/,sort-integers-by-the-number-of-1-bits,位运算、数组、计数、排序,https://algo.itcharge.cn/Solutions/1300-1399/sort-integers-by-the-number-of-1-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1356.%20%E6%A0%B9%E6%8D%AE%E6%95%B0%E5%AD%97%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%8B%201%20%E7%9A%84%E6%95%B0%E7%9B%AE%E6%8E%92%E5%BA%8F.md,73.5%,简单,686 -1357,1300-1399,1357. 每隔 n 个顾客打折,每隔 n 个顾客打折,https://leetcode.cn/problems/apply-discount-every-n-orders/,apply-discount-every-n-orders,设计、数组、哈希表,https://algo.itcharge.cn/Solutions/1300-1399/apply-discount-every-n-orders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1357.%20%E6%AF%8F%E9%9A%94%20n%20%E4%B8%AA%E9%A1%BE%E5%AE%A2%E6%89%93%E6%8A%98.md,54.5%,中等,71 -1358,1300-1399,1358. 包含所有三种字符的子字符串数目,包含所有三种字符的子字符串数目,https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/,number-of-substrings-containing-all-three-characters,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1300-1399/number-of-substrings-containing-all-three-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md,52.9%,中等,184 -1359,1300-1399,1359. 有效的快递序列数目,有效的快递序列数目,https://leetcode.cn/problems/count-all-valid-pickup-and-delivery-options/,count-all-valid-pickup-and-delivery-options,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/1300-1399/count-all-valid-pickup-and-delivery-options/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1359.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%BF%AB%E9%80%92%E5%BA%8F%E5%88%97%E6%95%B0%E7%9B%AE.md,58.4%,困难,122 -1360,1300-1399,1360. 日期之间隔几天,日期之间隔几天,https://leetcode.cn/problems/number-of-days-between-two-dates/,number-of-days-between-two-dates,数学、字符串,https://algo.itcharge.cn/Solutions/1300-1399/number-of-days-between-two-dates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1360.%20%E6%97%A5%E6%9C%9F%E4%B9%8B%E9%97%B4%E9%9A%94%E5%87%A0%E5%A4%A9.md,51.1%,简单,239 -1361,1300-1399,1361. 验证二叉树,验证二叉树,https://leetcode.cn/problems/validate-binary-tree-nodes/,validate-binary-tree-nodes,树、深度优先搜索、广度优先搜索、并查集、图、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/validate-binary-tree-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1361.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%A0%91.md,39.9%,中等,302 -1362,1300-1399,1362. 最接近的因数,最接近的因数,https://leetcode.cn/problems/closest-divisors/,closest-divisors,数学,https://algo.itcharge.cn/Solutions/1300-1399/closest-divisors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1362.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%9B%A0%E6%95%B0.md,54.7%,中等,97 -1363,1300-1399,1363. 形成三的最大倍数,形成三的最大倍数,https://leetcode.cn/problems/largest-multiple-of-three/,largest-multiple-of-three,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/largest-multiple-of-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1363.%20%E5%BD%A2%E6%88%90%E4%B8%89%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%8D%E6%95%B0.md,35.9%,困难,134 -1364,1300-1399,1364. 顾客的可信联系人数量,顾客的可信联系人数量,https://leetcode.cn/problems/number-of-trusted-contacts-of-a-customer/,number-of-trusted-contacts-of-a-customer,数据库,https://algo.itcharge.cn/Solutions/1300-1399/number-of-trusted-contacts-of-a-customer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1364.%20%E9%A1%BE%E5%AE%A2%E7%9A%84%E5%8F%AF%E4%BF%A1%E8%81%94%E7%B3%BB%E4%BA%BA%E6%95%B0%E9%87%8F.md,68.3%,中等,90 -1365,1300-1399,1365. 有多少小于当前数字的数字,有多少小于当前数字的数字,https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/,how-many-numbers-are-smaller-than-the-current-number,数组、哈希表、计数、排序,https://algo.itcharge.cn/Solutions/1300-1399/how-many-numbers-are-smaller-than-the-current-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1365.%20%E6%9C%89%E5%A4%9A%E5%B0%91%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E6%95%B0%E5%AD%97%E7%9A%84%E6%95%B0%E5%AD%97.md,82.4%,简单,1313 -1366,1300-1399,1366. 通过投票对团队排名,通过投票对团队排名,https://leetcode.cn/problems/rank-teams-by-votes/,rank-teams-by-votes,数组、哈希表、字符串、计数、排序,https://algo.itcharge.cn/Solutions/1300-1399/rank-teams-by-votes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1366.%20%E9%80%9A%E8%BF%87%E6%8A%95%E7%A5%A8%E5%AF%B9%E5%9B%A2%E9%98%9F%E6%8E%92%E5%90%8D.md,51.1%,中等,213 -1367,1300-1399,1367. 二叉树中的链表,二叉树中的链表,https://leetcode.cn/problems/linked-list-in-binary-tree/,linked-list-in-binary-tree,树、深度优先搜索、广度优先搜索、链表、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/linked-list-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1367.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E9%93%BE%E8%A1%A8.md,43.7%,中等,354 -1368,1300-1399,1368. 使网格图至少有一条有效路径的最小代价,使网格图至少有一条有效路径的最小代价,https://leetcode.cn/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/,minimum-cost-to-make-at-least-one-valid-path-in-a-grid,广度优先搜索、图、数组、矩阵、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1368.%20%E4%BD%BF%E7%BD%91%E6%A0%BC%E5%9B%BE%E8%87%B3%E5%B0%91%E6%9C%89%E4%B8%80%E6%9D%A1%E6%9C%89%E6%95%88%E8%B7%AF%E5%BE%84%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7.md,59.6%,困难,153 -1369,1300-1399,1369. 获取最近第二次的活动,获取最近第二次的活动,https://leetcode.cn/problems/get-the-second-most-recent-activity/,get-the-second-most-recent-activity,数据库,https://algo.itcharge.cn/Solutions/1300-1399/get-the-second-most-recent-activity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1369.%20%E8%8E%B7%E5%8F%96%E6%9C%80%E8%BF%91%E7%AC%AC%E4%BA%8C%E6%AC%A1%E7%9A%84%E6%B4%BB%E5%8A%A8.md,61.6%,困难,84 -1370,1300-1399,1370. 上升下降字符串,上升下降字符串,https://leetcode.cn/problems/increasing-decreasing-string/,increasing-decreasing-string,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1300-1399/increasing-decreasing-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1370.%20%E4%B8%8A%E5%8D%87%E4%B8%8B%E9%99%8D%E5%AD%97%E7%AC%A6%E4%B8%B2.md,79.0%,简单,670 -1371,1300-1399,1371. 每个元音包含偶数次的最长子字符串,每个元音包含偶数次的最长子字符串,https://leetcode.cn/problems/find-the-longest-substring-containing-vowels-in-even-counts/,find-the-longest-substring-containing-vowels-in-even-counts,位运算、哈希表、字符串、前缀和,https://algo.itcharge.cn/Solutions/1300-1399/find-the-longest-substring-containing-vowels-in-even-counts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1371.%20%E6%AF%8F%E4%B8%AA%E5%85%83%E9%9F%B3%E5%8C%85%E5%90%AB%E5%81%B6%E6%95%B0%E6%AC%A1%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,59.1%,中等,325 -1372,1300-1399,1372. 二叉树中的最长交错路径,二叉树中的最长交错路径,https://leetcode.cn/problems/longest-zigzag-path-in-a-binary-tree/,longest-zigzag-path-in-a-binary-tree,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/longest-zigzag-path-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1372.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E4%BA%A4%E9%94%99%E8%B7%AF%E5%BE%84.md,54.7%,中等,264 -1373,1300-1399,1373. 二叉搜索子树的最大键值和,二叉搜索子树的最大键值和,https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/,maximum-sum-bst-in-binary-tree,树、深度优先搜索、二叉搜索树、动态规划、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/maximum-sum-bst-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1373.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E5%AD%90%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E9%94%AE%E5%80%BC%E5%92%8C.md,47.8%,困难,410 -1374,1300-1399,1374. 生成每种字符都是奇数个的字符串,生成每种字符都是奇数个的字符串,https://leetcode.cn/problems/generate-a-string-with-characters-that-have-odd-counts/,generate-a-string-with-characters-that-have-odd-counts,字符串,https://algo.itcharge.cn/Solutions/1300-1399/generate-a-string-with-characters-that-have-odd-counts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1374.%20%E7%94%9F%E6%88%90%E6%AF%8F%E7%A7%8D%E5%AD%97%E7%AC%A6%E9%83%BD%E6%98%AF%E5%A5%87%E6%95%B0%E4%B8%AA%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,77.7%,简单,551 -1375,1300-1399,1375. 二进制字符串前缀一致的次数,二进制字符串前缀一致的次数,https://leetcode.cn/problems/number-of-times-binary-string-is-prefix-aligned/,number-of-times-binary-string-is-prefix-aligned,数组,https://algo.itcharge.cn/Solutions/1300-1399/number-of-times-binary-string-is-prefix-aligned/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1375.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%89%8D%E7%BC%80%E4%B8%80%E8%87%B4%E7%9A%84%E6%AC%A1%E6%95%B0.md,68.1%,中等,423 -1376,1300-1399,1376. 通知所有员工所需的时间,通知所有员工所需的时间,https://leetcode.cn/problems/time-needed-to-inform-all-employees/,time-needed-to-inform-all-employees,树、深度优先搜索、广度优先搜索,https://algo.itcharge.cn/Solutions/1300-1399/time-needed-to-inform-all-employees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1376.%20%E9%80%9A%E7%9F%A5%E6%89%80%E6%9C%89%E5%91%98%E5%B7%A5%E6%89%80%E9%9C%80%E7%9A%84%E6%97%B6%E9%97%B4.md,60.2%,中等,431 -1377,1300-1399,1377. T 秒后青蛙的位置,T 秒后青蛙的位置,https://leetcode.cn/problems/frog-position-after-t-seconds/,frog-position-after-t-seconds,树、深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/1300-1399/frog-position-after-t-seconds/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1377.%20T%20%E7%A7%92%E5%90%8E%E9%9D%92%E8%9B%99%E7%9A%84%E4%BD%8D%E7%BD%AE.md,42.7%,困难,273 -1378,1300-1399,1378. 使用唯一标识码替换员工ID,使用唯一标识码替换员工ID,https://leetcode.cn/problems/replace-employee-id-with-the-unique-identifier/,replace-employee-id-with-the-unique-identifier,数据库,https://algo.itcharge.cn/Solutions/1300-1399/replace-employee-id-with-the-unique-identifier/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1378.%20%E4%BD%BF%E7%94%A8%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86%E7%A0%81%E6%9B%BF%E6%8D%A2%E5%91%98%E5%B7%A5ID.md,85.7%,简单,105 -1379,1300-1399,1379. 找出克隆二叉树中的相同节点,找出克隆二叉树中的相同节点,https://leetcode.cn/problems/find-a-corresponding-node-of-a-binary-tree-in-a-clone-of-that-tree/,find-a-corresponding-node-of-a-binary-tree-in-a-clone-of-that-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/find-a-corresponding-node-of-a-binary-tree-in-a-clone-of-that-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1379.%20%E6%89%BE%E5%87%BA%E5%85%8B%E9%9A%86%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E7%9B%B8%E5%90%8C%E8%8A%82%E7%82%B9.md,83.3%,简单,248 -1380,1300-1399,1380. 矩阵中的幸运数,矩阵中的幸运数,https://leetcode.cn/problems/lucky-numbers-in-a-matrix/,lucky-numbers-in-a-matrix,数组、矩阵,https://algo.itcharge.cn/Solutions/1300-1399/lucky-numbers-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1380.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E5%B9%B8%E8%BF%90%E6%95%B0.md,76.4%,简单,758 -1381,1300-1399,1381. 设计一个支持增量操作的栈,设计一个支持增量操作的栈,https://leetcode.cn/problems/design-a-stack-with-increment-operation/,design-a-stack-with-increment-operation,栈、设计、数组,https://algo.itcharge.cn/Solutions/1300-1399/design-a-stack-with-increment-operation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1381.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%94%AF%E6%8C%81%E5%A2%9E%E9%87%8F%E6%93%8D%E4%BD%9C%E7%9A%84%E6%A0%88.md,72.4%,中等,230 -1382,1300-1399,1382. 将二叉搜索树变平衡,将二叉搜索树变平衡,https://leetcode.cn/problems/balance-a-binary-search-tree/,balance-a-binary-search-tree,贪心、树、深度优先搜索、二叉搜索树、分治、二叉树,https://algo.itcharge.cn/Solutions/1300-1399/balance-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1382.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%8F%98%E5%B9%B3%E8%A1%A1.md,73.6%,中等,300 -1383,1300-1399,1383. 最大的团队表现值,最大的团队表现值,https://leetcode.cn/problems/maximum-performance-of-a-team/,maximum-performance-of-a-team,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/maximum-performance-of-a-team/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1383.%20%E6%9C%80%E5%A4%A7%E7%9A%84%E5%9B%A2%E9%98%9F%E8%A1%A8%E7%8E%B0%E5%80%BC.md,35.3%,困难,128 -1384,1300-1399,1384. 按年度列出销售总额,按年度列出销售总额,https://leetcode.cn/problems/total-sales-amount-by-year/,total-sales-amount-by-year,数据库,https://algo.itcharge.cn/Solutions/1300-1399/total-sales-amount-by-year/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1384.%20%E6%8C%89%E5%B9%B4%E5%BA%A6%E5%88%97%E5%87%BA%E9%94%80%E5%94%AE%E6%80%BB%E9%A2%9D.md,57.7%,困难,101 -1385,1300-1399,1385. 两个数组间的距离值,两个数组间的距离值,https://leetcode.cn/problems/find-the-distance-value-between-two-arrays/,find-the-distance-value-between-two-arrays,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/1300-1399/find-the-distance-value-between-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1385.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E9%97%B4%E7%9A%84%E8%B7%9D%E7%A6%BB%E5%80%BC.md,64.1%,简单,511 -1386,1300-1399,1386. 安排电影院座位,安排电影院座位,https://leetcode.cn/problems/cinema-seat-allocation/,cinema-seat-allocation,贪心、位运算、数组、哈希表,https://algo.itcharge.cn/Solutions/1300-1399/cinema-seat-allocation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1386.%20%E5%AE%89%E6%8E%92%E7%94%B5%E5%BD%B1%E9%99%A2%E5%BA%A7%E4%BD%8D.md,35.5%,中等,175 -1387,1300-1399,1387. 将整数按权重排序,将整数按权重排序,https://leetcode.cn/problems/sort-integers-by-the-power-value/,sort-integers-by-the-power-value,记忆化搜索、动态规划、排序,https://algo.itcharge.cn/Solutions/1300-1399/sort-integers-by-the-power-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1387.%20%E5%B0%86%E6%95%B4%E6%95%B0%E6%8C%89%E6%9D%83%E9%87%8D%E6%8E%92%E5%BA%8F.md,69.5%,中等,268 -1388,1300-1399,1388. 3n 块披萨,3n 块披萨,https://leetcode.cn/problems/pizza-with-3n-slices/,pizza-with-3n-slices,贪心、数组、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/1300-1399/pizza-with-3n-slices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1388.%203n%20%E5%9D%97%E6%8A%AB%E8%90%A8.md,56.5%,困难,76 -1389,1300-1399,1389. 按既定顺序创建目标数组,按既定顺序创建目标数组,https://leetcode.cn/problems/create-target-array-in-the-given-order/,create-target-array-in-the-given-order,数组、模拟,https://algo.itcharge.cn/Solutions/1300-1399/create-target-array-in-the-given-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1389.%20%E6%8C%89%E6%97%A2%E5%AE%9A%E9%A1%BA%E5%BA%8F%E5%88%9B%E5%BB%BA%E7%9B%AE%E6%A0%87%E6%95%B0%E7%BB%84.md,83.0%,简单,582 -1390,1300-1399,1390. 四因数,四因数,https://leetcode.cn/problems/four-divisors/,four-divisors,数组、数学,https://algo.itcharge.cn/Solutions/1300-1399/four-divisors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1390.%20%E5%9B%9B%E5%9B%A0%E6%95%B0.md,38.9%,中等,146 -1391,1300-1399,1391. 检查网格中是否存在有效路径,检查网格中是否存在有效路径,https://leetcode.cn/problems/check-if-there-is-a-valid-path-in-a-grid/,check-if-there-is-a-valid-path-in-a-grid,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/1300-1399/check-if-there-is-a-valid-path-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1391.%20%E6%A3%80%E6%9F%A5%E7%BD%91%E6%A0%BC%E4%B8%AD%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E6%9C%89%E6%95%88%E8%B7%AF%E5%BE%84.md,42.0%,中等,266 -1392,1300-1399,1392. 最长快乐前缀,最长快乐前缀,https://leetcode.cn/problems/longest-happy-prefix/,longest-happy-prefix,字符串、字符串匹配、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1300-1399/longest-happy-prefix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1392.%20%E6%9C%80%E9%95%BF%E5%BF%AB%E4%B9%90%E5%89%8D%E7%BC%80.md,44.6%,困难,232 -1393,1300-1399,1393. 股票的资本损益,股票的资本损益,https://leetcode.cn/problems/capital-gainloss/,capital-gainloss,数据库,https://algo.itcharge.cn/Solutions/1300-1399/capital-gainloss/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1393.%20%E8%82%A1%E7%A5%A8%E7%9A%84%E8%B5%84%E6%9C%AC%E6%8D%9F%E7%9B%8A.md,83.8%,中等,260 -1394,1300-1399,1394. 找出数组中的幸运数,找出数组中的幸运数,https://leetcode.cn/problems/find-lucky-integer-in-an-array/,find-lucky-integer-in-an-array,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1300-1399/find-lucky-integer-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1394.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%B9%B8%E8%BF%90%E6%95%B0.md,65.6%,简单,430 -1395,1300-1399,1395. 统计作战单位数,统计作战单位数,https://leetcode.cn/problems/count-number-of-teams/,count-number-of-teams,树状数组、数组、动态规划,https://algo.itcharge.cn/Solutions/1300-1399/count-number-of-teams/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1395.%20%E7%BB%9F%E8%AE%A1%E4%BD%9C%E6%88%98%E5%8D%95%E4%BD%8D%E6%95%B0.md,71.3%,中等,294 -1396,1300-1399,1396. 设计地铁系统,设计地铁系统,https://leetcode.cn/problems/design-underground-system/,design-underground-system,设计、哈希表、字符串,https://algo.itcharge.cn/Solutions/1300-1399/design-underground-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1396.%20%E8%AE%BE%E8%AE%A1%E5%9C%B0%E9%93%81%E7%B3%BB%E7%BB%9F.md,41.9%,中等,266 -1397,1300-1399,1397. 找到所有好字符串,找到所有好字符串,https://leetcode.cn/problems/find-all-good-strings/,find-all-good-strings,字符串、动态规划、字符串匹配,https://algo.itcharge.cn/Solutions/1300-1399/find-all-good-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1397.%20%E6%89%BE%E5%88%B0%E6%89%80%E6%9C%89%E5%A5%BD%E5%AD%97%E7%AC%A6%E4%B8%B2.md,43.8%,困难,67 -1398,1300-1399,1398. 购买了产品 A 和产品 B 却没有购买产品 C 的顾客,购买了产品 A 和产品 B 却没有购买产品 C 的顾客,https://leetcode.cn/problems/customers-who-bought-products-a-and-b-but-not-c/,customers-who-bought-products-a-and-b-but-not-c,数据库,https://algo.itcharge.cn/Solutions/1300-1399/customers-who-bought-products-a-and-b-but-not-c/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1398.%20%E8%B4%AD%E4%B9%B0%E4%BA%86%E4%BA%A7%E5%93%81%20A%20%E5%92%8C%E4%BA%A7%E5%93%81%20B%20%E5%8D%B4%E6%B2%A1%E6%9C%89%E8%B4%AD%E4%B9%B0%E4%BA%A7%E5%93%81%20C%20%E7%9A%84%E9%A1%BE%E5%AE%A2.md,72.2%,中等,145 -1399,1300-1399,1399. 统计最大组的数目,统计最大组的数目,https://leetcode.cn/problems/count-largest-group/,count-largest-group,哈希表、数学,https://algo.itcharge.cn/Solutions/1300-1399/count-largest-group/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1399.%20%E7%BB%9F%E8%AE%A1%E6%9C%80%E5%A4%A7%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,67.0%,简单,211 -1400,1400-1499,1400. 构造 K 个回文字符串,构造 K 个回文字符串,https://leetcode.cn/problems/construct-k-palindrome-strings/,construct-k-palindrome-strings,贪心、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1400-1499/construct-k-palindrome-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1400.%20%E6%9E%84%E9%80%A0%20K%20%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md,61.3%,中等,151 -1401,1400-1499,1401. 圆和矩形是否有重叠,圆和矩形是否有重叠,https://leetcode.cn/problems/circle-and-rectangle-overlapping/,circle-and-rectangle-overlapping,几何、数学,https://algo.itcharge.cn/Solutions/1400-1499/circle-and-rectangle-overlapping/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1401.%20%E5%9C%86%E5%92%8C%E7%9F%A9%E5%BD%A2%E6%98%AF%E5%90%A6%E6%9C%89%E9%87%8D%E5%8F%A0.md,51.7%,中等,251 -1402,1400-1499,1402. 做菜顺序,做菜顺序,https://leetcode.cn/problems/reducing-dishes/,reducing-dishes,贪心、数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1400-1499/reducing-dishes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1402.%20%E5%81%9A%E8%8F%9C%E9%A1%BA%E5%BA%8F.md,76.1%,困难,244 -1403,1400-1499,1403. 非递增顺序的最小子序列,非递增顺序的最小子序列,https://leetcode.cn/problems/minimum-subsequence-in-non-increasing-order/,minimum-subsequence-in-non-increasing-order,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1400-1499/minimum-subsequence-in-non-increasing-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1403.%20%E9%9D%9E%E9%80%92%E5%A2%9E%E9%A1%BA%E5%BA%8F%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E5%BA%8F%E5%88%97.md,73.3%,简单,756 -1404,1400-1499,1404. 将二进制表示减到 1 的步骤数,将二进制表示减到 1 的步骤数,https://leetcode.cn/problems/number-of-steps-to-reduce-a-number-in-binary-representation-to-one/,number-of-steps-to-reduce-a-number-in-binary-representation-to-one,位运算、字符串,https://algo.itcharge.cn/Solutions/1400-1499/number-of-steps-to-reduce-a-number-in-binary-representation-to-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1404.%20%E5%B0%86%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA%E5%87%8F%E5%88%B0%201%20%E7%9A%84%E6%AD%A5%E9%AA%A4%E6%95%B0.md,50.9%,中等,198 -1405,1400-1499,1405. 最长快乐字符串,最长快乐字符串,https://leetcode.cn/problems/longest-happy-string/,longest-happy-string,贪心、字符串、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/longest-happy-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1405.%20%E6%9C%80%E9%95%BF%E5%BF%AB%E4%B9%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,63.6%,中等,521 -1406,1400-1499,1406. 石子游戏 III,石子游戏 III,https://leetcode.cn/problems/stone-game-iii/,stone-game-iii,数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1400-1499/stone-game-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1406.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20III.md,59.2%,困难,124 -1407,1400-1499,1407. 排名靠前的旅行者,排名靠前的旅行者,https://leetcode.cn/problems/top-travellers/,top-travellers,数据库,https://algo.itcharge.cn/Solutions/1400-1499/top-travellers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1407.%20%E6%8E%92%E5%90%8D%E9%9D%A0%E5%89%8D%E7%9A%84%E6%97%85%E8%A1%8C%E8%80%85.md,56.8%,简单,265 -1408,1400-1499,1408. 数组中的字符串匹配,数组中的字符串匹配,https://leetcode.cn/problems/string-matching-in-an-array/,string-matching-in-an-array,数组、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/1400-1499/string-matching-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1408.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md,64.4%,简单,558 -1409,1400-1499,1409. 查询带键的排列,查询带键的排列,https://leetcode.cn/problems/queries-on-a-permutation-with-key/,queries-on-a-permutation-with-key,树状数组、数组、模拟,https://algo.itcharge.cn/Solutions/1400-1499/queries-on-a-permutation-with-key/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1409.%20%E6%9F%A5%E8%AF%A2%E5%B8%A6%E9%94%AE%E7%9A%84%E6%8E%92%E5%88%97.md,81.4%,中等,197 -1410,1400-1499,1410. HTML 实体解析器,HTML 实体解析器,https://leetcode.cn/problems/html-entity-parser/,html-entity-parser,哈希表、字符串,https://algo.itcharge.cn/Solutions/1400-1499/html-entity-parser/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1410.%20HTML%20%E5%AE%9E%E4%BD%93%E8%A7%A3%E6%9E%90%E5%99%A8.md,47.0%,中等,143 -1411,1400-1499,1411. 给 N x 3 网格图涂色的方案数,给 N x 3 网格图涂色的方案数,https://leetcode.cn/problems/number-of-ways-to-paint-n-3-grid/,number-of-ways-to-paint-n-3-grid,动态规划,https://algo.itcharge.cn/Solutions/1400-1499/number-of-ways-to-paint-n-3-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1411.%20%E7%BB%99%20N%20x%203%20%E7%BD%91%E6%A0%BC%E5%9B%BE%E6%B6%82%E8%89%B2%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,57.2%,困难,168 -1412,1400-1499,1412. 查找成绩处于中游的学生,查找成绩处于中游的学生,https://leetcode.cn/problems/find-the-quiet-students-in-all-exams/,find-the-quiet-students-in-all-exams,数据库,https://algo.itcharge.cn/Solutions/1400-1499/find-the-quiet-students-in-all-exams/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1412.%20%E6%9F%A5%E6%89%BE%E6%88%90%E7%BB%A9%E5%A4%84%E4%BA%8E%E4%B8%AD%E6%B8%B8%E7%9A%84%E5%AD%A6%E7%94%9F.md,54.4%,困难,121 -1413,1400-1499,1413. 逐步求和得到正数的最小值,逐步求和得到正数的最小值,https://leetcode.cn/problems/minimum-value-to-get-positive-step-by-step-sum/,minimum-value-to-get-positive-step-by-step-sum,数组、前缀和,https://algo.itcharge.cn/Solutions/1400-1499/minimum-value-to-get-positive-step-by-step-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1413.%20%E9%80%90%E6%AD%A5%E6%B1%82%E5%92%8C%E5%BE%97%E5%88%B0%E6%AD%A3%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,73.0%,简单,701 -1414,1400-1499,1414. 和为 K 的最少斐波那契数字数目,和为 K 的最少斐波那契数字数目,https://leetcode.cn/problems/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k/,find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k,贪心、数学,https://algo.itcharge.cn/Solutions/1400-1499/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1414.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E6%9C%80%E5%B0%91%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%AD%97%E6%95%B0%E7%9B%AE.md,70.4%,中等,456 -1415,1400-1499,1415. 长度为 n 的开心字符串中字典序第 k 小的字符串,长度为 n 的开心字符串中字典序第 k 小的字符串,https://leetcode.cn/problems/the-k-th-lexicographical-string-of-all-happy-strings-of-length-n/,the-k-th-lexicographical-string-of-all-happy-strings-of-length-n,字符串、回溯,https://algo.itcharge.cn/Solutions/1400-1499/the-k-th-lexicographical-string-of-all-happy-strings-of-length-n/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1415.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%BC%80%E5%BF%83%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%AD%97%E5%85%B8%E5%BA%8F%E7%AC%AC%20k%20%E5%B0%8F%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,68.6%,中等,238 -1416,1400-1499,1416. 恢复数组,恢复数组,https://leetcode.cn/problems/restore-the-array/,restore-the-array,字符串、动态规划,https://algo.itcharge.cn/Solutions/1400-1499/restore-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1416.%20%E6%81%A2%E5%A4%8D%E6%95%B0%E7%BB%84.md,42.7%,困难,81 -1417,1400-1499,1417. 重新格式化字符串,重新格式化字符串,https://leetcode.cn/problems/reformat-the-string/,reformat-the-string,字符串,https://algo.itcharge.cn/Solutions/1400-1499/reformat-the-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1417.%20%E9%87%8D%E6%96%B0%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2.md,55.2%,简单,623 -1418,1400-1499,1418. 点菜展示表,点菜展示表,https://leetcode.cn/problems/display-table-of-food-orders-in-a-restaurant/,display-table-of-food-orders-in-a-restaurant,数组、哈希表、字符串、有序集合、排序,https://algo.itcharge.cn/Solutions/1400-1499/display-table-of-food-orders-in-a-restaurant/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1418.%20%E7%82%B9%E8%8F%9C%E5%B1%95%E7%A4%BA%E8%A1%A8.md,73.1%,中等,454 -1419,1400-1499,1419. 数青蛙,数青蛙,https://leetcode.cn/problems/minimum-number-of-frogs-croaking/,minimum-number-of-frogs-croaking,字符串、计数,https://algo.itcharge.cn/Solutions/1400-1499/minimum-number-of-frogs-croaking/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1419.%20%E6%95%B0%E9%9D%92%E8%9B%99.md,50.2%,中等,438 -1420,1400-1499,1420. 生成数组,生成数组,https://leetcode.cn/problems/build-array-where-you-can-find-the-maximum-exactly-k-comparisons/,build-array-where-you-can-find-the-maximum-exactly-k-comparisons,动态规划、前缀和,https://algo.itcharge.cn/Solutions/1400-1499/build-array-where-you-can-find-the-maximum-exactly-k-comparisons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1420.%20%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84.md,63.6%,困难,68 -1421,1400-1499,1421. 净现值查询,净现值查询,https://leetcode.cn/problems/npv-queries/,npv-queries,数据库,https://algo.itcharge.cn/Solutions/1400-1499/npv-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1421.%20%E5%87%80%E7%8E%B0%E5%80%BC%E6%9F%A5%E8%AF%A2.md,70.9%,简单,49 -1422,1400-1499,1422. 分割字符串的最大得分,分割字符串的最大得分,https://leetcode.cn/problems/maximum-score-after-splitting-a-string/,maximum-score-after-splitting-a-string,字符串,https://algo.itcharge.cn/Solutions/1400-1499/maximum-score-after-splitting-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1422.%20%E5%88%86%E5%89%B2%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,56.8%,简单,687 -1423,1400-1499,1423. 可获得的最大点数,可获得的最大点数,https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/,maximum-points-you-can-obtain-from-cards,数组、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/1400-1499/maximum-points-you-can-obtain-from-cards/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1423.%20%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E6%95%B0.md,55.0%,中等,650 -1424,1400-1499,1424. 对角线遍历 II,对角线遍历 II,https://leetcode.cn/problems/diagonal-traverse-ii/,diagonal-traverse-ii,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/diagonal-traverse-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1424.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86%20II.md,42.2%,中等,211 -1425,1400-1499,1425. 带限制的子序列和,带限制的子序列和,https://leetcode.cn/problems/constrained-subsequence-sum/,constrained-subsequence-sum,队列、数组、动态规划、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/constrained-subsequence-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1425.%20%E5%B8%A6%E9%99%90%E5%88%B6%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%E5%92%8C.md,47.5%,困难,116 -1426,1400-1499,1426. 数元素,数元素,https://leetcode.cn/problems/counting-elements/,counting-elements,数组、哈希表,https://algo.itcharge.cn/Solutions/1400-1499/counting-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1426.%20%E6%95%B0%E5%85%83%E7%B4%A0.md,69.7%,简单,83 -1427,1400-1499,1427. 字符串的左右移,字符串的左右移,https://leetcode.cn/problems/perform-string-shifts/,perform-string-shifts,数组、数学、字符串,https://algo.itcharge.cn/Solutions/1400-1499/perform-string-shifts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1427.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%B7%A6%E5%8F%B3%E7%A7%BB.md,58.7%,简单,131 -1428,1400-1499,1428. 至少有一个 1 的最左端列,至少有一个 1 的最左端列,https://leetcode.cn/problems/leftmost-column-with-at-least-a-one/,leftmost-column-with-at-least-a-one,数组、二分查找、交互、矩阵,https://algo.itcharge.cn/Solutions/1400-1499/leftmost-column-with-at-least-a-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1428.%20%E8%87%B3%E5%B0%91%E6%9C%89%E4%B8%80%E4%B8%AA%201%20%E7%9A%84%E6%9C%80%E5%B7%A6%E7%AB%AF%E5%88%97.md,61.4%,中等,38 -1429,1400-1499,1429. 第一个唯一数字,第一个唯一数字,https://leetcode.cn/problems/first-unique-number/,first-unique-number,设计、队列、数组、哈希表、数据流,https://algo.itcharge.cn/Solutions/1400-1499/first-unique-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1429.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%94%AF%E4%B8%80%E6%95%B0%E5%AD%97.md,53.3%,中等,85 -1430,1400-1499,1430. 判断给定的序列是否是二叉树从根到叶的路径,判断给定的序列是否是二叉树从根到叶的路径,https://leetcode.cn/problems/check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree/,check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1400-1499/check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1430.%20%E5%88%A4%E6%96%AD%E7%BB%99%E5%AE%9A%E7%9A%84%E5%BA%8F%E5%88%97%E6%98%AF%E5%90%A6%E6%98%AF%E4%BA%8C%E5%8F%89%E6%A0%91%E4%BB%8E%E6%A0%B9%E5%88%B0%E5%8F%B6%E7%9A%84%E8%B7%AF%E5%BE%84.md,54.6%,中等,54 -1431,1400-1499,1431. 拥有最多糖果的孩子,拥有最多糖果的孩子,https://leetcode.cn/problems/kids-with-the-greatest-number-of-candies/,kids-with-the-greatest-number-of-candies,数组,https://algo.itcharge.cn/Solutions/1400-1499/kids-with-the-greatest-number-of-candies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1431.%20%E6%8B%A5%E6%9C%89%E6%9C%80%E5%A4%9A%E7%B3%96%E6%9E%9C%E7%9A%84%E5%AD%A9%E5%AD%90.md,84.6%,简单,1215 -1432,1400-1499,1432. 改变一个整数能得到的最大差值,改变一个整数能得到的最大差值,https://leetcode.cn/problems/max-difference-you-can-get-from-changing-an-integer/,max-difference-you-can-get-from-changing-an-integer,贪心、数学,https://algo.itcharge.cn/Solutions/1400-1499/max-difference-you-can-get-from-changing-an-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1432.%20%E6%94%B9%E5%8F%98%E4%B8%80%E4%B8%AA%E6%95%B4%E6%95%B0%E8%83%BD%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B7%AE%E5%80%BC.md,39.9%,中等,137 -1433,1400-1499,1433. 检查一个字符串是否可以打破另一个字符串,检查一个字符串是否可以打破另一个字符串,https://leetcode.cn/problems/check-if-a-string-can-break-another-string/,check-if-a-string-can-break-another-string,贪心、字符串、排序,https://algo.itcharge.cn/Solutions/1400-1499/check-if-a-string-can-break-another-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1433.%20%E6%A3%80%E6%9F%A5%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E6%89%93%E7%A0%B4%E5%8F%A6%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2.md,64.7%,中等,148 -1434,1400-1499,1434. 每个人戴不同帽子的方案数,每个人戴不同帽子的方案数,https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other/,number-of-ways-to-wear-different-hats-to-each-other,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1400-1499/number-of-ways-to-wear-different-hats-to-each-other/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1434.%20%E6%AF%8F%E4%B8%AA%E4%BA%BA%E6%88%B4%E4%B8%8D%E5%90%8C%E5%B8%BD%E5%AD%90%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,51.7%,困难,72 -1435,1400-1499,1435. 制作会话柱状图,制作会话柱状图,https://leetcode.cn/problems/create-a-session-bar-chart/,create-a-session-bar-chart,数据库,https://algo.itcharge.cn/Solutions/1400-1499/create-a-session-bar-chart/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1435.%20%E5%88%B6%E4%BD%9C%E4%BC%9A%E8%AF%9D%E6%9F%B1%E7%8A%B6%E5%9B%BE.md,63.7%,简单,62 -1436,1400-1499,1436. 旅行终点站,旅行终点站,https://leetcode.cn/problems/destination-city/,destination-city,哈希表、字符串,https://algo.itcharge.cn/Solutions/1400-1499/destination-city/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1436.%20%E6%97%85%E8%A1%8C%E7%BB%88%E7%82%B9%E7%AB%99.md,81.8%,简单,766 -1437,1400-1499,1437. 是否所有 1 都至少相隔 k 个元素,是否所有 1 都至少相隔 k 个元素,https://leetcode.cn/problems/check-if-all-1s-are-at-least-length-k-places-away/,check-if-all-1s-are-at-least-length-k-places-away,数组,https://algo.itcharge.cn/Solutions/1400-1499/check-if-all-1s-are-at-least-length-k-places-away/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1437.%20%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%201%20%E9%83%BD%E8%87%B3%E5%B0%91%E7%9B%B8%E9%9A%94%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0.md,55.6%,简单,260 -1438,1400-1499,1438. 绝对差不超过限制的最长连续子数组,绝对差不超过限制的最长连续子数组,https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/,longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit,队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md,49.6%,中等,586 -1439,1400-1499,1439. 有序矩阵中的第 k 个最小数组和,有序矩阵中的第 k 个最小数组和,https://leetcode.cn/problems/find-the-kth-smallest-sum-of-a-matrix-with-sorted-rows/,find-the-kth-smallest-sum-of-a-matrix-with-sorted-rows,数组、二分查找、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/find-the-kth-smallest-sum-of-a-matrix-with-sorted-rows/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1439.%20%E6%9C%89%E5%BA%8F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E7%AC%AC%20k%20%E4%B8%AA%E6%9C%80%E5%B0%8F%E6%95%B0%E7%BB%84%E5%92%8C.md,66.6%,困难,136 -1440,1400-1499,1440. 计算布尔表达式的值,计算布尔表达式的值,https://leetcode.cn/problems/evaluate-boolean-expression/,evaluate-boolean-expression,数据库,https://algo.itcharge.cn/Solutions/1400-1499/evaluate-boolean-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1440.%20%E8%AE%A1%E7%AE%97%E5%B8%83%E5%B0%94%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%80%BC.md,69.7%,中等,87 -1441,1400-1499,1441. 用栈操作构建数组,用栈操作构建数组,https://leetcode.cn/problems/build-an-array-with-stack-operations/,build-an-array-with-stack-operations,栈、数组、模拟,https://algo.itcharge.cn/Solutions/1400-1499/build-an-array-with-stack-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1441.%20%E7%94%A8%E6%A0%88%E6%93%8D%E4%BD%9C%E6%9E%84%E5%BB%BA%E6%95%B0%E7%BB%84.md,71.5%,中等,899 -1442,1400-1499,1442. 形成两个异或相等数组的三元组数目,形成两个异或相等数组的三元组数目,https://leetcode.cn/problems/count-triplets-that-can-form-two-arrays-of-equal-xor/,count-triplets-that-can-form-two-arrays-of-equal-xor,位运算、数组、哈希表、数学、前缀和,https://algo.itcharge.cn/Solutions/1400-1499/count-triplets-that-can-form-two-arrays-of-equal-xor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1442.%20%E5%BD%A2%E6%88%90%E4%B8%A4%E4%B8%AA%E5%BC%82%E6%88%96%E7%9B%B8%E7%AD%89%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84%E6%95%B0%E7%9B%AE.md,79.3%,中等,415 -1443,1400-1499,1443. 收集树上所有苹果的最少时间,收集树上所有苹果的最少时间,https://leetcode.cn/problems/minimum-time-to-collect-all-apples-in-a-tree/,minimum-time-to-collect-all-apples-in-a-tree,树、深度优先搜索、广度优先搜索、哈希表,https://algo.itcharge.cn/Solutions/1400-1499/minimum-time-to-collect-all-apples-in-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1443.%20%E6%94%B6%E9%9B%86%E6%A0%91%E4%B8%8A%E6%89%80%E6%9C%89%E8%8B%B9%E6%9E%9C%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,43.1%,中等,190 -1444,1400-1499,1444. 切披萨的方案数,切披萨的方案数,https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/,number-of-ways-of-cutting-a-pizza,记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1400-1499/number-of-ways-of-cutting-a-pizza/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1444.%20%E5%88%87%E6%8A%AB%E8%90%A8%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,54.3%,困难,99 -1445,1400-1499,1445. 苹果和桔子,苹果和桔子,https://leetcode.cn/problems/apples-oranges/,apples-oranges,数据库,https://algo.itcharge.cn/Solutions/1400-1499/apples-oranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1445.%20%E8%8B%B9%E6%9E%9C%E5%92%8C%E6%A1%94%E5%AD%90.md,84.1%,中等,130 -1446,1400-1499,1446. 连续字符,连续字符,https://leetcode.cn/problems/consecutive-characters/,consecutive-characters,字符串,https://algo.itcharge.cn/Solutions/1400-1499/consecutive-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1446.%20%E8%BF%9E%E7%BB%AD%E5%AD%97%E7%AC%A6.md,60.3%,简单,795 -1447,1400-1499,1447. 最简分数,最简分数,https://leetcode.cn/problems/simplified-fractions/,simplified-fractions,数学、字符串、数论,https://algo.itcharge.cn/Solutions/1400-1499/simplified-fractions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1447.%20%E6%9C%80%E7%AE%80%E5%88%86%E6%95%B0.md,67.5%,中等,529 -1448,1400-1499,1448. 统计二叉树中好节点的数目,统计二叉树中好节点的数目,https://leetcode.cn/problems/count-good-nodes-in-binary-tree/,count-good-nodes-in-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1400-1499/count-good-nodes-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1448.%20%E7%BB%9F%E8%AE%A1%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%A5%BD%E8%8A%82%E7%82%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,71.6%,中等,316 -1449,1400-1499,1449. 数位成本和为目标值的最大数字,数位成本和为目标值的最大数字,https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/,form-largest-integer-with-digits-that-add-up-to-target,数组、动态规划,https://algo.itcharge.cn/Solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md,62.3%,困难,232 -1450,1400-1499,1450. 在既定时间做作业的学生人数,在既定时间做作业的学生人数,https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/,number-of-students-doing-homework-at-a-given-time,数组,https://algo.itcharge.cn/Solutions/1400-1499/number-of-students-doing-homework-at-a-given-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md,82.4%,简单,687 -1451,1400-1499,1451. 重新排列句子中的单词,重新排列句子中的单词,https://leetcode.cn/problems/rearrange-words-in-a-sentence/,rearrange-words-in-a-sentence,字符串、排序,https://algo.itcharge.cn/Solutions/1400-1499/rearrange-words-in-a-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1451.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E5%8F%A5%E5%AD%90%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md,54.0%,中等,213 -1452,1400-1499,1452. 收藏清单,收藏清单,https://leetcode.cn/problems/people-whose-list-of-favorite-companies-is-not-a-subset-of-another-list/,people-whose-list-of-favorite-companies-is-not-a-subset-of-another-list,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1400-1499/people-whose-list-of-favorite-companies-is-not-a-subset-of-another-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1452.%20%E6%94%B6%E8%97%8F%E6%B8%85%E5%8D%95.md,51.1%,中等,121 -1453,1400-1499,1453. 圆形靶内的最大飞镖数量,圆形靶内的最大飞镖数量,https://leetcode.cn/problems/maximum-number-of-darts-inside-of-a-circular-dartboard/,maximum-number-of-darts-inside-of-a-circular-dartboard,几何、数组、数学,https://algo.itcharge.cn/Solutions/1400-1499/maximum-number-of-darts-inside-of-a-circular-dartboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1453.%20%E5%9C%86%E5%BD%A2%E9%9D%B6%E5%86%85%E7%9A%84%E6%9C%80%E5%A4%A7%E9%A3%9E%E9%95%96%E6%95%B0%E9%87%8F.md,37.8%,困难,43 -1454,1400-1499,1454. 活跃用户,活跃用户,https://leetcode.cn/problems/active-users/,active-users,数据库,https://algo.itcharge.cn/Solutions/1400-1499/active-users/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1454.%20%E6%B4%BB%E8%B7%83%E7%94%A8%E6%88%B7.md,38.3%,中等,127 -1455,1400-1499,1455. 检查单词是否为句中其他单词的前缀,检查单词是否为句中其他单词的前缀,https://leetcode.cn/problems/check-if-a-word-occurs-as-a-prefix-of-any-word-in-a-sentence/,check-if-a-word-occurs-as-a-prefix-of-any-word-in-a-sentence,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/1400-1499/check-if-a-word-occurs-as-a-prefix-of-any-word-in-a-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1455.%20%E6%A3%80%E6%9F%A5%E5%8D%95%E8%AF%8D%E6%98%AF%E5%90%A6%E4%B8%BA%E5%8F%A5%E4%B8%AD%E5%85%B6%E4%BB%96%E5%8D%95%E8%AF%8D%E7%9A%84%E5%89%8D%E7%BC%80.md,64.8%,简单,701 -1456,1400-1499,1456. 定长子串中元音的最大数目,定长子串中元音的最大数目,https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/,maximum-number-of-vowels-in-a-substring-of-given-length,字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1456.%20%E5%AE%9A%E9%95%BF%E5%AD%90%E4%B8%B2%E4%B8%AD%E5%85%83%E9%9F%B3%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,54.5%,中等,370 -1457,1400-1499,1457. 二叉树中的伪回文路径,二叉树中的伪回文路径,https://leetcode.cn/problems/pseudo-palindromic-paths-in-a-binary-tree/,pseudo-palindromic-paths-in-a-binary-tree,位运算、树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1400-1499/pseudo-palindromic-paths-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1457.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E4%BC%AA%E5%9B%9E%E6%96%87%E8%B7%AF%E5%BE%84.md,62.1%,中等,187 -1458,1400-1499,1458. 两个子序列的最大点积,两个子序列的最大点积,https://leetcode.cn/problems/max-dot-product-of-two-subsequences/,max-dot-product-of-two-subsequences,数组、动态规划,https://algo.itcharge.cn/Solutions/1400-1499/max-dot-product-of-two-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1458.%20%E4%B8%A4%E4%B8%AA%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E7%A7%AF.md,46.8%,困难,125 -1459,1400-1499,1459. 矩形面积,矩形面积,https://leetcode.cn/problems/rectangles-area/,rectangles-area,数据库,https://algo.itcharge.cn/Solutions/1400-1499/rectangles-area/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1459.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md,62.7%,中等,98 -1460,1400-1499,1460. 通过翻转子数组使两个数组相等,通过翻转子数组使两个数组相等,https://leetcode.cn/problems/make-two-arrays-equal-by-reversing-subarrays/,make-two-arrays-equal-by-reversing-subarrays,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1400-1499/make-two-arrays-equal-by-reversing-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1460.%20%E9%80%9A%E8%BF%87%E7%BF%BB%E8%BD%AC%E5%AD%90%E6%95%B0%E7%BB%84%E4%BD%BF%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9B%B8%E7%AD%89.md,77.2%,简单,755 -1461,1400-1499,1461. 检查一个字符串是否包含所有长度为 K 的二进制子串,检查一个字符串是否包含所有长度为 K 的二进制子串,https://leetcode.cn/problems/check-if-a-string-contains-all-binary-codes-of-size-k/,check-if-a-string-contains-all-binary-codes-of-size-k,位运算、哈希表、字符串、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1400-1499/check-if-a-string-contains-all-binary-codes-of-size-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1461.%20%E6%A3%80%E6%9F%A5%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%90%E4%B8%B2.md,52.6%,中等,132 -1462,1400-1499,1462. 课程表 IV,课程表 IV,https://leetcode.cn/problems/course-schedule-iv/,course-schedule-iv,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/1400-1499/course-schedule-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1462.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20IV.md,45.8%,中等,220 -1463,1400-1499,1463. 摘樱桃 II,摘樱桃 II,https://leetcode.cn/problems/cherry-pickup-ii/,cherry-pickup-ii,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1400-1499/cherry-pickup-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1463.%20%E6%91%98%E6%A8%B1%E6%A1%83%20II.md,62.1%,困难,104 -1464,1400-1499,1464. 数组中两元素的最大乘积,数组中两元素的最大乘积,https://leetcode.cn/problems/maximum-product-of-two-elements-in-an-array/,maximum-product-of-two-elements-in-an-array,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/maximum-product-of-two-elements-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1464.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,78.6%,简单,859 -1465,1400-1499,1465. 切割后面积最大的蛋糕,切割后面积最大的蛋糕,https://leetcode.cn/problems/maximum-area-of-a-piece-of-cake-after-horizontal-and-vertical-cuts/,maximum-area-of-a-piece-of-cake-after-horizontal-and-vertical-cuts,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1400-1499/maximum-area-of-a-piece-of-cake-after-horizontal-and-vertical-cuts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1465.%20%E5%88%87%E5%89%B2%E5%90%8E%E9%9D%A2%E7%A7%AF%E6%9C%80%E5%A4%A7%E7%9A%84%E8%9B%8B%E7%B3%95.md,32.8%,中等,106 -1466,1400-1499,1466. 重新规划路线,重新规划路线,https://leetcode.cn/problems/reorder-routes-to-make-all-paths-lead-to-the-city-zero/,reorder-routes-to-make-all-paths-lead-to-the-city-zero,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/1400-1499/reorder-routes-to-make-all-paths-lead-to-the-city-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1466.%20%E9%87%8D%E6%96%B0%E8%A7%84%E5%88%92%E8%B7%AF%E7%BA%BF.md,51.1%,中等,231 -1467,1400-1499,1467. 两个盒子中球的颜色数相同的概率,两个盒子中球的颜色数相同的概率,https://leetcode.cn/problems/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls/,probability-of-a-two-boxes-having-the-same-number-of-distinct-balls,数组、数学、动态规划、回溯、组合数学、概率与统计,https://algo.itcharge.cn/Solutions/1400-1499/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1467.%20%E4%B8%A4%E4%B8%AA%E7%9B%92%E5%AD%90%E4%B8%AD%E7%90%83%E7%9A%84%E9%A2%9C%E8%89%B2%E6%95%B0%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A6%82%E7%8E%87.md,63.1%,困难,68 -1468,1400-1499,1468. 计算税后工资,计算税后工资,https://leetcode.cn/problems/calculate-salaries/,calculate-salaries,数据库,https://algo.itcharge.cn/Solutions/1400-1499/calculate-salaries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1468.%20%E8%AE%A1%E7%AE%97%E7%A8%8E%E5%90%8E%E5%B7%A5%E8%B5%84.md,70.0%,中等,65 -1469,1400-1499,1469. 寻找所有的独生节点,寻找所有的独生节点,https://leetcode.cn/problems/find-all-the-lonely-nodes/,find-all-the-lonely-nodes,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1400-1499/find-all-the-lonely-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1469.%20%E5%AF%BB%E6%89%BE%E6%89%80%E6%9C%89%E7%9A%84%E7%8B%AC%E7%94%9F%E8%8A%82%E7%82%B9.md,82.2%,简单,138 -1470,1400-1499,1470. 重新排列数组,重新排列数组,https://leetcode.cn/problems/shuffle-the-array/,shuffle-the-array,数组,https://algo.itcharge.cn/Solutions/1400-1499/shuffle-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1470.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E6%95%B0%E7%BB%84.md,84.9%,简单,1150 -1471,1400-1499,1471. 数组中的 k 个最强值,数组中的 k 个最强值,https://leetcode.cn/problems/the-k-strongest-values-in-an-array/,the-k-strongest-values-in-an-array,数组、双指针、排序,https://algo.itcharge.cn/Solutions/1400-1499/the-k-strongest-values-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1471.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%20k%20%E4%B8%AA%E6%9C%80%E5%BC%BA%E5%80%BC.md,55.5%,中等,144 -1472,1400-1499,1472. 设计浏览器历史记录,设计浏览器历史记录,https://leetcode.cn/problems/design-browser-history/,design-browser-history,栈、设计、数组、链表、数据流、双向链表,https://algo.itcharge.cn/Solutions/1400-1499/design-browser-history/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1472.%20%E8%AE%BE%E8%AE%A1%E6%B5%8F%E8%A7%88%E5%99%A8%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95.md,61.5%,中等,258 -1473,1400-1499,1473. 粉刷房子 III,粉刷房子 III,https://leetcode.cn/problems/paint-house-iii/,paint-house-iii,数组、动态规划,https://algo.itcharge.cn/Solutions/1400-1499/paint-house-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1473.%20%E7%B2%89%E5%88%B7%E6%88%BF%E5%AD%90%20III.md,66.3%,困难,192 -1474,1400-1499,1474. 删除链表 M 个节点之后的 N 个节点,删除链表 M 个节点之后的 N 个节点,https://leetcode.cn/problems/delete-n-nodes-after-m-nodes-of-a-linked-list/,delete-n-nodes-after-m-nodes-of-a-linked-list,链表,https://algo.itcharge.cn/Solutions/1400-1499/delete-n-nodes-after-m-nodes-of-a-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1474.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%20M%20%E4%B8%AA%E8%8A%82%E7%82%B9%E4%B9%8B%E5%90%8E%E7%9A%84%20N%20%E4%B8%AA%E8%8A%82%E7%82%B9.md,69.0%,简单,105 -1475,1400-1499,1475. 商品折扣后的最终价格,商品折扣后的最终价格,https://leetcode.cn/problems/final-prices-with-a-special-discount-in-a-shop/,final-prices-with-a-special-discount-in-a-shop,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/1400-1499/final-prices-with-a-special-discount-in-a-shop/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1475.%20%E5%95%86%E5%93%81%E6%8A%98%E6%89%A3%E5%90%8E%E7%9A%84%E6%9C%80%E7%BB%88%E4%BB%B7%E6%A0%BC.md,73.3%,简单,858 -1476,1400-1499,1476. 子矩形查询,子矩形查询,https://leetcode.cn/problems/subrectangle-queries/,subrectangle-queries,设计、数组、矩阵,https://algo.itcharge.cn/Solutions/1400-1499/subrectangle-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1476.%20%E5%AD%90%E7%9F%A9%E5%BD%A2%E6%9F%A5%E8%AF%A2.md,86.6%,中等,147 -1477,1400-1499,1477. 找两个和为目标值且不重叠的子数组,找两个和为目标值且不重叠的子数组,https://leetcode.cn/problems/find-two-non-overlapping-sub-arrays-each-with-target-sum/,find-two-non-overlapping-sub-arrays-each-with-target-sum,数组、哈希表、二分查找、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/1400-1499/find-two-non-overlapping-sub-arrays-each-with-target-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1477.%20%E6%89%BE%E4%B8%A4%E4%B8%AA%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E4%B8%94%E4%B8%8D%E9%87%8D%E5%8F%A0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,31.2%,中等,146 -1478,1400-1499,1478. 安排邮筒,安排邮筒,https://leetcode.cn/problems/allocate-mailboxes/,allocate-mailboxes,数组、数学、动态规划、排序,https://algo.itcharge.cn/Solutions/1400-1499/allocate-mailboxes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1478.%20%E5%AE%89%E6%8E%92%E9%82%AE%E7%AD%92.md,62.7%,困难,97 -1479,1400-1499,1479. 周内每天的销售情况,周内每天的销售情况,https://leetcode.cn/problems/sales-by-day-of-the-week/,sales-by-day-of-the-week,数据库,https://algo.itcharge.cn/Solutions/1400-1499/sales-by-day-of-the-week/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1479.%20%E5%91%A8%E5%86%85%E6%AF%8F%E5%A4%A9%E7%9A%84%E9%94%80%E5%94%AE%E6%83%85%E5%86%B5.md,55.7%,困难,64 -1480,1400-1499,1480. 一维数组的动态和,一维数组的动态和,https://leetcode.cn/problems/running-sum-of-1d-array/,running-sum-of-1d-array,数组、前缀和,https://algo.itcharge.cn/Solutions/1400-1499/running-sum-of-1d-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1480.%20%E4%B8%80%E7%BB%B4%E6%95%B0%E7%BB%84%E7%9A%84%E5%8A%A8%E6%80%81%E5%92%8C.md,76.5%,简单,2514 -1481,1400-1499,1481. 不同整数的最少数目,不同整数的最少数目,https://leetcode.cn/problems/least-number-of-unique-integers-after-k-removals/,least-number-of-unique-integers-after-k-removals,贪心、数组、哈希表、计数、排序,https://algo.itcharge.cn/Solutions/1400-1499/least-number-of-unique-integers-after-k-removals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1481.%20%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE.md,45.2%,中等,253 -1482,1400-1499,1482. 制作 m 束花所需的最少天数,制作 m 束花所需的最少天数,https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/,minimum-number-of-days-to-make-m-bouquets,数组、二分查找,https://algo.itcharge.cn/Solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1482.%20%E5%88%B6%E4%BD%9C%20m%20%E6%9D%9F%E8%8A%B1%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md,56.8%,中等,507 -1483,1400-1499,1483. 树节点的第 K 个祖先,树节点的第 K 个祖先,https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/,kth-ancestor-of-a-tree-node,树、深度优先搜索、广度优先搜索、设计、二分查找,https://algo.itcharge.cn/Solutions/1400-1499/kth-ancestor-of-a-tree-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1483.%20%E6%A0%91%E8%8A%82%E7%82%B9%E7%9A%84%E7%AC%AC%20K%20%E4%B8%AA%E7%A5%96%E5%85%88.md,44.9%,困难,157 -1484,1400-1499,1484. 按日期分组销售产品,按日期分组销售产品,https://leetcode.cn/problems/group-sold-products-by-the-date/,group-sold-products-by-the-date,数据库,https://algo.itcharge.cn/Solutions/1400-1499/group-sold-products-by-the-date/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1484.%20%E6%8C%89%E6%97%A5%E6%9C%9F%E5%88%86%E7%BB%84%E9%94%80%E5%94%AE%E4%BA%A7%E5%93%81.md,67.7%,简单,300 -1485,1400-1499,1485. 克隆含随机指针的二叉树,克隆含随机指针的二叉树,https://leetcode.cn/problems/clone-binary-tree-with-random-pointer/,clone-binary-tree-with-random-pointer,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1400-1499/clone-binary-tree-with-random-pointer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1485.%20%E5%85%8B%E9%9A%86%E5%90%AB%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md,78.5%,中等,39 -1486,1400-1499,1486. 数组异或操作,数组异或操作,https://leetcode.cn/problems/xor-operation-in-an-array/,xor-operation-in-an-array,位运算、数学,https://algo.itcharge.cn/Solutions/1400-1499/xor-operation-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1486.%20%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%93%8D%E4%BD%9C.md,85.5%,简单,796 -1487,1400-1499,1487. 保证文件名唯一,保证文件名唯一,https://leetcode.cn/problems/making-file-names-unique/,making-file-names-unique,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1400-1499/making-file-names-unique/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1487.%20%E4%BF%9D%E8%AF%81%E6%96%87%E4%BB%B6%E5%90%8D%E5%94%AF%E4%B8%80.md,41.5%,中等,336 -1488,1400-1499,1488. 避免洪水泛滥,避免洪水泛滥,https://leetcode.cn/problems/avoid-flood-in-the-city/,avoid-flood-in-the-city,贪心、数组、哈希表、二分查找、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/avoid-flood-in-the-city/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1488.%20%E9%81%BF%E5%85%8D%E6%B4%AA%E6%B0%B4%E6%B3%9B%E6%BB%A5.md,26.3%,中等,172 -1489,1400-1499,1489. 找到最小生成树里的关键边和伪关键边,找到最小生成树里的关键边和伪关键边,https://leetcode.cn/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/,find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree,并查集、图、最小生成树、排序、强连通分量,https://algo.itcharge.cn/Solutions/1400-1499/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1489.%20%E6%89%BE%E5%88%B0%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91%E9%87%8C%E7%9A%84%E5%85%B3%E9%94%AE%E8%BE%B9%E5%92%8C%E4%BC%AA%E5%85%B3%E9%94%AE%E8%BE%B9.md,66.5%,困难,150 -1490,1400-1499,1490. 克隆 N 叉树,克隆 N 叉树,https://leetcode.cn/problems/clone-n-ary-tree/,clone-n-ary-tree,树、深度优先搜索、广度优先搜索、哈希表,https://algo.itcharge.cn/Solutions/1400-1499/clone-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1490.%20%E5%85%8B%E9%9A%86%20N%20%E5%8F%89%E6%A0%91.md,83.6%,中等,51 -1491,1400-1499,1491. 去掉最低工资和最高工资后的工资平均值,去掉最低工资和最高工资后的工资平均值,https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/,average-salary-excluding-the-minimum-and-maximum-salary,数组、排序,https://algo.itcharge.cn/Solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1491.%20%E5%8E%BB%E6%8E%89%E6%9C%80%E4%BD%8E%E5%B7%A5%E8%B5%84%E5%92%8C%E6%9C%80%E9%AB%98%E5%B7%A5%E8%B5%84%E5%90%8E%E7%9A%84%E5%B7%A5%E8%B5%84%E5%B9%B3%E5%9D%87%E5%80%BC.md,62.7%,简单,700 -1492,1400-1499,1492. n 的第 k 个因子,n 的第 k 个因子,https://leetcode.cn/problems/the-kth-factor-of-n/,the-kth-factor-of-n,数学、数论,https://algo.itcharge.cn/Solutions/1400-1499/the-kth-factor-of-n/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1492.%20n%20%E7%9A%84%E7%AC%AC%20k%20%E4%B8%AA%E5%9B%A0%E5%AD%90.md,60.8%,中等,158 -1493,1400-1499,1493. 删掉一个元素以后全为 1 的最长子数组,删掉一个元素以后全为 1 的最长子数组,https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/,longest-subarray-of-1s-after-deleting-one-element,数组、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1493.%20%E5%88%A0%E6%8E%89%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BB%A5%E5%90%8E%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md,59.5%,中等,318 -1494,1400-1499,1494. 并行课程 II,并行课程 II,https://leetcode.cn/problems/parallel-courses-ii/,parallel-courses-ii,位运算、图、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1400-1499/parallel-courses-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1494.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B%20II.md,51.8%,困难,99 -1495,1400-1499,1495. 上月播放的儿童适宜电影,上月播放的儿童适宜电影,https://leetcode.cn/problems/friendly-movies-streamed-last-month/,friendly-movies-streamed-last-month,数据库,https://algo.itcharge.cn/Solutions/1400-1499/friendly-movies-streamed-last-month/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1495.%20%E4%B8%8A%E6%9C%88%E6%92%AD%E6%94%BE%E7%9A%84%E5%84%BF%E7%AB%A5%E9%80%82%E5%AE%9C%E7%94%B5%E5%BD%B1.md,53.6%,简单,50 -1496,1400-1499,1496. 判断路径是否相交,判断路径是否相交,https://leetcode.cn/problems/path-crossing/,path-crossing,哈希表、字符串,https://algo.itcharge.cn/Solutions/1400-1499/path-crossing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1496.%20%E5%88%A4%E6%96%AD%E8%B7%AF%E5%BE%84%E6%98%AF%E5%90%A6%E7%9B%B8%E4%BA%A4.md,53.5%,简单,249 -1497,1400-1499,1497. 检查数组对是否可以被 k 整除,检查数组对是否可以被 k 整除,https://leetcode.cn/problems/check-if-array-pairs-are-divisible-by-k/,check-if-array-pairs-are-divisible-by-k,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1400-1499/check-if-array-pairs-are-divisible-by-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1497.%20%E6%A3%80%E6%9F%A5%E6%95%B0%E7%BB%84%E5%AF%B9%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E8%A2%AB%20k%20%E6%95%B4%E9%99%A4.md,40.1%,中等,151 -1498,1400-1499,1498. 满足条件的子序列数目,满足条件的子序列数目,https://leetcode.cn/problems/number-of-subsequences-that-satisfy-the-given-sum-condition/,number-of-subsequences-that-satisfy-the-given-sum-condition,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/1400-1499/number-of-subsequences-that-satisfy-the-given-sum-condition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1498.%20%E6%BB%A1%E8%B6%B3%E6%9D%A1%E4%BB%B6%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%E6%95%B0%E7%9B%AE.md,38.2%,中等,132 -1499,1400-1499,1499. 满足不等式的最大值,满足不等式的最大值,https://leetcode.cn/problems/max-value-of-equation/,max-value-of-equation,队列、数组、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/1400-1499/max-value-of-equation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1499.%20%E6%BB%A1%E8%B6%B3%E4%B8%8D%E7%AD%89%E5%BC%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,40.9%,困难,93 -1500,1500-1599,1500. 设计文件分享系统,设计文件分享系统,https://leetcode.cn/problems/design-a-file-sharing-system/,design-a-file-sharing-system,设计、哈希表、数据流、堆(优先队列),https://algo.itcharge.cn/Solutions/1500-1599/design-a-file-sharing-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1500.%20%E8%AE%BE%E8%AE%A1%E6%96%87%E4%BB%B6%E5%88%86%E4%BA%AB%E7%B3%BB%E7%BB%9F.md,29.8%,中等,51 -1501,1500-1599,1501. 可以放心投资的国家,可以放心投资的国家,https://leetcode.cn/problems/countries-you-can-safely-invest-in/,countries-you-can-safely-invest-in,数据库,https://algo.itcharge.cn/Solutions/1500-1599/countries-you-can-safely-invest-in/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1501.%20%E5%8F%AF%E4%BB%A5%E6%94%BE%E5%BF%83%E6%8A%95%E8%B5%84%E7%9A%84%E5%9B%BD%E5%AE%B6.md,58.5%,中等,146 -1502,1500-1599,1502. 判断能否形成等差数列,判断能否形成等差数列,https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/,can-make-arithmetic-progression-from-sequence,数组、排序,https://algo.itcharge.cn/Solutions/1500-1599/can-make-arithmetic-progression-from-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1502.%20%E5%88%A4%E6%96%AD%E8%83%BD%E5%90%A6%E5%BD%A2%E6%88%90%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97.md,69.4%,简单,593 -1503,1500-1599,1503. 所有蚂蚁掉下来前的最后一刻,所有蚂蚁掉下来前的最后一刻,https://leetcode.cn/problems/last-moment-before-all-ants-fall-out-of-a-plank/,last-moment-before-all-ants-fall-out-of-a-plank,脑筋急转弯、数组、模拟,https://algo.itcharge.cn/Solutions/1500-1599/last-moment-before-all-ants-fall-out-of-a-plank/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1503.%20%E6%89%80%E6%9C%89%E8%9A%82%E8%9A%81%E6%8E%89%E4%B8%8B%E6%9D%A5%E5%89%8D%E7%9A%84%E6%9C%80%E5%90%8E%E4%B8%80%E5%88%BB.md,53.7%,中等,119 -1504,1500-1599,1504. 统计全 1 子矩形,统计全 1 子矩形,https://leetcode.cn/problems/count-submatrices-with-all-ones/,count-submatrices-with-all-ones,栈、数组、动态规划、矩阵、单调栈,https://algo.itcharge.cn/Solutions/1500-1599/count-submatrices-with-all-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1504.%20%E7%BB%9F%E8%AE%A1%E5%85%A8%201%20%E5%AD%90%E7%9F%A9%E5%BD%A2.md,62.4%,中等,173 -1505,1500-1599,1505. 最多 K 次交换相邻数位后得到的最小整数,最多 K 次交换相邻数位后得到的最小整数,https://leetcode.cn/problems/minimum-possible-integer-after-at-most-k-adjacent-swaps-on-digits/,minimum-possible-integer-after-at-most-k-adjacent-swaps-on-digits,贪心、树状数组、线段树、字符串,https://algo.itcharge.cn/Solutions/1500-1599/minimum-possible-integer-after-at-most-k-adjacent-swaps-on-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1505.%20%E6%9C%80%E5%A4%9A%20K%20%E6%AC%A1%E4%BA%A4%E6%8D%A2%E7%9B%B8%E9%82%BB%E6%95%B0%E4%BD%8D%E5%90%8E%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B4%E6%95%B0.md,39.8%,困难,95 -1506,1500-1599,1506. 找到 N 叉树的根节点,找到 N 叉树的根节点,https://leetcode.cn/problems/find-root-of-n-ary-tree/,find-root-of-n-ary-tree,位运算、树、深度优先搜索、哈希表,https://algo.itcharge.cn/Solutions/1500-1599/find-root-of-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1506.%20%E6%89%BE%E5%88%B0%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E6%A0%B9%E8%8A%82%E7%82%B9.md,81.4%,中等,32 -1507,1500-1599,1507. 转变日期格式,转变日期格式,https://leetcode.cn/problems/reformat-date/,reformat-date,字符串,https://algo.itcharge.cn/Solutions/1500-1599/reformat-date/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1507.%20%E8%BD%AC%E5%8F%98%E6%97%A5%E6%9C%9F%E6%A0%BC%E5%BC%8F.md,58.9%,简单,211 -1508,1500-1599,1508. 子数组和排序后的区间和,子数组和排序后的区间和,https://leetcode.cn/problems/range-sum-of-sorted-subarray-sums/,range-sum-of-sorted-subarray-sums,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/1500-1599/range-sum-of-sorted-subarray-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1508.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E6%8E%92%E5%BA%8F%E5%90%8E%E7%9A%84%E5%8C%BA%E9%97%B4%E5%92%8C.md,57.7%,中等,114 -1509,1500-1599,1509. 三次操作后最大值与最小值的最小差,三次操作后最大值与最小值的最小差,https://leetcode.cn/problems/minimum-difference-between-largest-and-smallest-value-in-three-moves/,minimum-difference-between-largest-and-smallest-value-in-three-moves,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1500-1599/minimum-difference-between-largest-and-smallest-value-in-three-moves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1509.%20%E4%B8%89%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%90%8E%E6%9C%80%E5%A4%A7%E5%80%BC%E4%B8%8E%E6%9C%80%E5%B0%8F%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E5%B7%AE.md,55.5%,中等,139 -1510,1500-1599,1510. 石子游戏 IV,石子游戏 IV,https://leetcode.cn/problems/stone-game-iv/,stone-game-iv,数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1500-1599/stone-game-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1510.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20IV.md,60.2%,困难,105 -1511,1500-1599,1511. 消费者下单频率,消费者下单频率,https://leetcode.cn/problems/customer-order-frequency/,customer-order-frequency,数据库,https://algo.itcharge.cn/Solutions/1500-1599/customer-order-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1511.%20%E6%B6%88%E8%B4%B9%E8%80%85%E4%B8%8B%E5%8D%95%E9%A2%91%E7%8E%87.md,68.9%,简单,99 -1512,1500-1599,1512. 好数对的数目,好数对的数目,https://leetcode.cn/problems/number-of-good-pairs/,number-of-good-pairs,数组、哈希表、数学、计数,https://algo.itcharge.cn/Solutions/1500-1599/number-of-good-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1512.%20%E5%A5%BD%E6%95%B0%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,84.4%,简单,967 -1513,1500-1599,1513. 仅含 1 的子串数,仅含 1 的子串数,https://leetcode.cn/problems/number-of-substrings-with-only-1s/,number-of-substrings-with-only-1s,数学、字符串,https://algo.itcharge.cn/Solutions/1500-1599/number-of-substrings-with-only-1s/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1513.%20%E4%BB%85%E5%90%AB%201%20%E7%9A%84%E5%AD%90%E4%B8%B2%E6%95%B0.md,39.5%,中等,201 -1514,1500-1599,1514. 概率最大的路径,概率最大的路径,https://leetcode.cn/problems/path-with-maximum-probability/,path-with-maximum-probability,图、数组、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/1500-1599/path-with-maximum-probability/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1514.%20%E6%A6%82%E7%8E%87%E6%9C%80%E5%A4%A7%E7%9A%84%E8%B7%AF%E5%BE%84.md,40.7%,中等,265 -1515,1500-1599,1515. 服务中心的最佳位置,服务中心的最佳位置,https://leetcode.cn/problems/best-position-for-a-service-centre/,best-position-for-a-service-centre,几何、数学、随机化,https://algo.itcharge.cn/Solutions/1500-1599/best-position-for-a-service-centre/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1515.%20%E6%9C%8D%E5%8A%A1%E4%B8%AD%E5%BF%83%E7%9A%84%E6%9C%80%E4%BD%B3%E4%BD%8D%E7%BD%AE.md,35.2%,困难,72 -1516,1500-1599,1516. 移动 N 叉树的子树,移动 N 叉树的子树,https://leetcode.cn/problems/move-sub-tree-of-n-ary-tree/,move-sub-tree-of-n-ary-tree,树、深度优先搜索,https://algo.itcharge.cn/Solutions/1500-1599/move-sub-tree-of-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1516.%20%E7%A7%BB%E5%8A%A8%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%AD%90%E6%A0%91.md,55.9%,困难,18 -1517,1500-1599,1517. 查找拥有有效邮箱的用户,查找拥有有效邮箱的用户,https://leetcode.cn/problems/find-users-with-valid-e-mails/,find-users-with-valid-e-mails,数据库,https://algo.itcharge.cn/Solutions/1500-1599/find-users-with-valid-e-mails/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1517.%20%E6%9F%A5%E6%89%BE%E6%8B%A5%E6%9C%89%E6%9C%89%E6%95%88%E9%82%AE%E7%AE%B1%E7%9A%84%E7%94%A8%E6%88%B7.md,50.4%,简单,66 -1518,1500-1599,1518. 换水问题,换水问题,https://leetcode.cn/problems/water-bottles/,water-bottles,数学、模拟,https://algo.itcharge.cn/Solutions/1500-1599/water-bottles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1518.%20%E6%8D%A2%E6%B0%B4%E9%97%AE%E9%A2%98.md,69.6%,简单,913 -1519,1500-1599,1519. 子树中标签相同的节点数,子树中标签相同的节点数,https://leetcode.cn/problems/number-of-nodes-in-the-sub-tree-with-the-same-label/,number-of-nodes-in-the-sub-tree-with-the-same-label,树、深度优先搜索、广度优先搜索、哈希表、计数,https://algo.itcharge.cn/Solutions/1500-1599/number-of-nodes-in-the-sub-tree-with-the-same-label/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1519.%20%E5%AD%90%E6%A0%91%E4%B8%AD%E6%A0%87%E7%AD%BE%E7%9B%B8%E5%90%8C%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0.md,33.2%,中等,150 -1520,1500-1599,1520. 最多的不重叠子字符串,最多的不重叠子字符串,https://leetcode.cn/problems/maximum-number-of-non-overlapping-substrings/,maximum-number-of-non-overlapping-substrings,贪心、字符串,https://algo.itcharge.cn/Solutions/1500-1599/maximum-number-of-non-overlapping-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1520.%20%E6%9C%80%E5%A4%9A%E7%9A%84%E4%B8%8D%E9%87%8D%E5%8F%A0%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,35.6%,困难,87 -1521,1500-1599,1521. 找到最接近目标值的函数值,找到最接近目标值的函数值,https://leetcode.cn/problems/find-a-value-of-a-mysterious-function-closest-to-target/,find-a-value-of-a-mysterious-function-closest-to-target,位运算、线段树、数组、二分查找,https://algo.itcharge.cn/Solutions/1500-1599/find-a-value-of-a-mysterious-function-closest-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1521.%20%E6%89%BE%E5%88%B0%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E5%87%BD%E6%95%B0%E5%80%BC.md,43.6%,困难,78 -1522,1500-1599,1522. N 叉树的直径,N 叉树的直径,https://leetcode.cn/problems/diameter-of-n-ary-tree/,diameter-of-n-ary-tree,树、深度优先搜索,https://algo.itcharge.cn/Solutions/1500-1599/diameter-of-n-ary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1522.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md,71.5%,中等,63 -1523,1500-1599,1523. 在区间范围内统计奇数数目,在区间范围内统计奇数数目,https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/,count-odd-numbers-in-an-interval-range,数学,https://algo.itcharge.cn/Solutions/1500-1599/count-odd-numbers-in-an-interval-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1523.%20%E5%9C%A8%E5%8C%BA%E9%97%B4%E8%8C%83%E5%9B%B4%E5%86%85%E7%BB%9F%E8%AE%A1%E5%A5%87%E6%95%B0%E6%95%B0%E7%9B%AE.md,49.2%,简单,705 -1524,1500-1599,1524. 和为奇数的子数组数目,和为奇数的子数组数目,https://leetcode.cn/problems/number-of-sub-arrays-with-odd-sum/,number-of-sub-arrays-with-odd-sum,数组、数学、动态规划、前缀和,https://algo.itcharge.cn/Solutions/1500-1599/number-of-sub-arrays-with-odd-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1524.%20%E5%92%8C%E4%B8%BA%E5%A5%87%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,45.7%,中等,167 -1525,1500-1599,1525. 字符串的好分割数目,字符串的好分割数目,https://leetcode.cn/problems/number-of-good-ways-to-split-a-string/,number-of-good-ways-to-split-a-string,位运算、字符串、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/number-of-good-ways-to-split-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1525.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%A5%BD%E5%88%86%E5%89%B2%E6%95%B0%E7%9B%AE.md,66.1%,中等,159 -1526,1500-1599,1526. 形成目标数组的子数组最少增加次数,形成目标数组的子数组最少增加次数,https://leetcode.cn/problems/minimum-number-of-increments-on-subarrays-to-form-a-target-array/,minimum-number-of-increments-on-subarrays-to-form-a-target-array,栈、贪心、数组、动态规划、单调栈,https://algo.itcharge.cn/Solutions/1500-1599/minimum-number-of-increments-on-subarrays-to-form-a-target-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1526.%20%E5%BD%A2%E6%88%90%E7%9B%AE%E6%A0%87%E6%95%B0%E7%BB%84%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%91%E5%A2%9E%E5%8A%A0%E6%AC%A1%E6%95%B0.md,65.0%,困难,94 -1527,1500-1599,1527. 患某种疾病的患者,患某种疾病的患者,https://leetcode.cn/problems/patients-with-a-condition/,patients-with-a-condition,数据库,https://algo.itcharge.cn/Solutions/1500-1599/patients-with-a-condition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1527.%20%E6%82%A3%E6%9F%90%E7%A7%8D%E7%96%BE%E7%97%85%E7%9A%84%E6%82%A3%E8%80%85.md,46.7%,简单,264 -1528,1500-1599,1528. 重新排列字符串,重新排列字符串,https://leetcode.cn/problems/shuffle-string/,shuffle-string,数组、字符串,https://algo.itcharge.cn/Solutions/1500-1599/shuffle-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1528.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E5%AD%97%E7%AC%A6%E4%B8%B2.md,78.4%,简单,486 -1529,1500-1599,1529. 最少的后缀翻转次数,最少的后缀翻转次数,https://leetcode.cn/problems/minimum-suffix-flips/,minimum-suffix-flips,贪心、字符串,https://algo.itcharge.cn/Solutions/1500-1599/minimum-suffix-flips/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1529.%20%E6%9C%80%E5%B0%91%E7%9A%84%E5%90%8E%E7%BC%80%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md,70.3%,中等,182 -1530,1500-1599,1530. 好叶子节点对的数量,好叶子节点对的数量,https://leetcode.cn/problems/number-of-good-leaf-nodes-pairs/,number-of-good-leaf-nodes-pairs,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1500-1599/number-of-good-leaf-nodes-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1530.%20%E5%A5%BD%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9%E5%AF%B9%E7%9A%84%E6%95%B0%E9%87%8F.md,58.5%,中等,204 -1531,1500-1599,1531. 压缩字符串 II,压缩字符串 II,https://leetcode.cn/problems/string-compression-ii/,string-compression-ii,字符串、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/string-compression-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1531.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2%20II.md,38.0%,困难,49 -1532,1500-1599,1532. 最近的三笔订单,最近的三笔订单,https://leetcode.cn/problems/the-most-recent-three-orders/,the-most-recent-three-orders,数据库,https://algo.itcharge.cn/Solutions/1500-1599/the-most-recent-three-orders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1532.%20%E6%9C%80%E8%BF%91%E7%9A%84%E4%B8%89%E7%AC%94%E8%AE%A2%E5%8D%95.md,63.0%,中等,97 -1533,1500-1599,1533. 找到最大整数的索引,找到最大整数的索引,https://leetcode.cn/problems/find-the-index-of-the-large-integer/,find-the-index-of-the-large-integer,数组、二分查找、交互,https://algo.itcharge.cn/Solutions/1500-1599/find-the-index-of-the-large-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1533.%20%E6%89%BE%E5%88%B0%E6%9C%80%E5%A4%A7%E6%95%B4%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95.md,58.8%,中等,43 -1534,1500-1599,1534. 统计好三元组,统计好三元组,https://leetcode.cn/problems/count-good-triplets/,count-good-triplets,数组、枚举,https://algo.itcharge.cn/Solutions/1500-1599/count-good-triplets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1534.%20%E7%BB%9F%E8%AE%A1%E5%A5%BD%E4%B8%89%E5%85%83%E7%BB%84.md,77.3%,简单,248 -1535,1500-1599,1535. 找出数组游戏的赢家,找出数组游戏的赢家,https://leetcode.cn/problems/find-the-winner-of-an-array-game/,find-the-winner-of-an-array-game,数组、模拟,https://algo.itcharge.cn/Solutions/1500-1599/find-the-winner-of-an-array-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1535.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E6%B8%B8%E6%88%8F%E7%9A%84%E8%B5%A2%E5%AE%B6.md,46.2%,中等,225 -1536,1500-1599,1536. 排布二进制网格的最少交换次数,排布二进制网格的最少交换次数,https://leetcode.cn/problems/minimum-swaps-to-arrange-a-binary-grid/,minimum-swaps-to-arrange-a-binary-grid,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/minimum-swaps-to-arrange-a-binary-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1536.%20%E6%8E%92%E5%B8%83%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%BD%91%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,46.2%,中等,96 -1537,1500-1599,1537. 最大得分,最大得分,https://leetcode.cn/problems/get-the-maximum-score/,get-the-maximum-score,贪心、数组、双指针、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/get-the-maximum-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1537.%20%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,40.1%,困难,131 -1538,1500-1599,1538. 找出隐藏数组中出现次数最多的元素,找出隐藏数组中出现次数最多的元素,https://leetcode.cn/problems/guess-the-majority-in-a-hidden-array/,guess-the-majority-in-a-hidden-array,数组、数学、交互,https://algo.itcharge.cn/Solutions/1500-1599/guess-the-majority-in-a-hidden-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1538.%20%E6%89%BE%E5%87%BA%E9%9A%90%E8%97%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E5%85%83%E7%B4%A0.md,56.8%,中等,14 -1539,1500-1599,1539. 第 k 个缺失的正整数,第 k 个缺失的正整数,https://leetcode.cn/problems/kth-missing-positive-number/,kth-missing-positive-number,数组、二分查找,https://algo.itcharge.cn/Solutions/1500-1599/kth-missing-positive-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1539.%20%E7%AC%AC%20k%20%E4%B8%AA%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%AD%A3%E6%95%B4%E6%95%B0.md,54.0%,简单,566 -1540,1500-1599,1540. K 次操作转变字符串,K 次操作转变字符串,https://leetcode.cn/problems/can-convert-string-in-k-moves/,can-convert-string-in-k-moves,哈希表、字符串,https://algo.itcharge.cn/Solutions/1500-1599/can-convert-string-in-k-moves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1540.%20K%20%E6%AC%A1%E6%93%8D%E4%BD%9C%E8%BD%AC%E5%8F%98%E5%AD%97%E7%AC%A6%E4%B8%B2.md,33.4%,中等,112 -1541,1500-1599,1541. 平衡括号字符串的最少插入次数,平衡括号字符串的最少插入次数,https://leetcode.cn/problems/minimum-insertions-to-balance-a-parentheses-string/,minimum-insertions-to-balance-a-parentheses-string,栈、贪心、字符串,https://algo.itcharge.cn/Solutions/1500-1599/minimum-insertions-to-balance-a-parentheses-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1541.%20%E5%B9%B3%E8%A1%A1%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%8F%92%E5%85%A5%E6%AC%A1%E6%95%B0.md,48.8%,中等,223 -1542,1500-1599,1542. 找出最长的超赞子字符串,找出最长的超赞子字符串,https://leetcode.cn/problems/find-longest-awesome-substring/,find-longest-awesome-substring,位运算、哈希表、字符串,https://algo.itcharge.cn/Solutions/1500-1599/find-longest-awesome-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1542.%20%E6%89%BE%E5%87%BA%E6%9C%80%E9%95%BF%E7%9A%84%E8%B6%85%E8%B5%9E%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,43.9%,困难,76 -1543,1500-1599,1543. 产品名称格式修复,产品名称格式修复,https://leetcode.cn/problems/fix-product-name-format/,fix-product-name-format,数据库,https://algo.itcharge.cn/Solutions/1500-1599/fix-product-name-format/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1543.%20%E4%BA%A7%E5%93%81%E5%90%8D%E7%A7%B0%E6%A0%BC%E5%BC%8F%E4%BF%AE%E5%A4%8D.md,55.4%,简单,57 -1544,1500-1599,1544. 整理字符串,整理字符串,https://leetcode.cn/problems/make-the-string-great/,make-the-string-great,栈、字符串,https://algo.itcharge.cn/Solutions/1500-1599/make-the-string-great/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1544.%20%E6%95%B4%E7%90%86%E5%AD%97%E7%AC%A6%E4%B8%B2.md,55.9%,简单,452 -1545,1500-1599,1545. 找出第 N 个二进制字符串中的第 K 位,找出第 N 个二进制字符串中的第 K 位,https://leetcode.cn/problems/find-kth-bit-in-nth-binary-string/,find-kth-bit-in-nth-binary-string,递归、字符串,https://algo.itcharge.cn/Solutions/1500-1599/find-kth-bit-in-nth-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1545.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20N%20%E4%B8%AA%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E4%BD%8D.md,59.0%,中等,189 -1546,1500-1599,1546. 和为目标值且不重叠的非空子数组的最大数目,和为目标值且不重叠的非空子数组的最大数目,https://leetcode.cn/problems/maximum-number-of-non-overlapping-subarrays-with-sum-equals-target/,maximum-number-of-non-overlapping-subarrays-with-sum-equals-target,贪心、数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/1500-1599/maximum-number-of-non-overlapping-subarrays-with-sum-equals-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1546.%20%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E4%B8%94%E4%B8%8D%E9%87%8D%E5%8F%A0%E7%9A%84%E9%9D%9E%E7%A9%BA%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,44.8%,中等,110 -1547,1500-1599,1547. 切棍子的最小成本,切棍子的最小成本,https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/,minimum-cost-to-cut-a-stick,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1500-1599/minimum-cost-to-cut-a-stick/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md,56.6%,困难,107 -1548,1500-1599,1548. 图中最相似的路径,图中最相似的路径,https://leetcode.cn/problems/the-most-similar-path-in-a-graph/,the-most-similar-path-in-a-graph,图、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/the-most-similar-path-in-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1548.%20%E5%9B%BE%E4%B8%AD%E6%9C%80%E7%9B%B8%E4%BC%BC%E7%9A%84%E8%B7%AF%E5%BE%84.md,68.0%,困难,24 -1549,1500-1599,1549. 每件商品的最新订单,每件商品的最新订单,https://leetcode.cn/problems/the-most-recent-orders-for-each-product/,the-most-recent-orders-for-each-product,数据库,https://algo.itcharge.cn/Solutions/1500-1599/the-most-recent-orders-for-each-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1549.%20%E6%AF%8F%E4%BB%B6%E5%95%86%E5%93%81%E7%9A%84%E6%9C%80%E6%96%B0%E8%AE%A2%E5%8D%95.md,67.7%,中等,121 -1550,1500-1599,1550. 存在连续三个奇数的数组,存在连续三个奇数的数组,https://leetcode.cn/problems/three-consecutive-odds/,three-consecutive-odds,数组,https://algo.itcharge.cn/Solutions/1500-1599/three-consecutive-odds/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1550.%20%E5%AD%98%E5%9C%A8%E8%BF%9E%E7%BB%AD%E4%B8%89%E4%B8%AA%E5%A5%87%E6%95%B0%E7%9A%84%E6%95%B0%E7%BB%84.md,65.6%,简单,399 -1551,1500-1599,1551. 使数组中所有元素相等的最小操作数,使数组中所有元素相等的最小操作数,https://leetcode.cn/problems/minimum-operations-to-make-array-equal/,minimum-operations-to-make-array-equal,数学,https://algo.itcharge.cn/Solutions/1500-1599/minimum-operations-to-make-array-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1551.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md,82.1%,中等,223 -1552,1500-1599,1552. 两球之间的磁力,两球之间的磁力,https://leetcode.cn/problems/magnetic-force-between-two-balls/,magnetic-force-between-two-balls,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/1500-1599/magnetic-force-between-two-balls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1552.%20%E4%B8%A4%E7%90%83%E4%B9%8B%E9%97%B4%E7%9A%84%E7%A3%81%E5%8A%9B.md,56.8%,中等,226 -1553,1500-1599,1553. 吃掉 N 个橘子的最少天数,吃掉 N 个橘子的最少天数,https://leetcode.cn/problems/minimum-number-of-days-to-eat-n-oranges/,minimum-number-of-days-to-eat-n-oranges,记忆化搜索、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/minimum-number-of-days-to-eat-n-oranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1553.%20%E5%90%83%E6%8E%89%20N%20%E4%B8%AA%E6%A9%98%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md,32.4%,困难,150 -1554,1500-1599,1554. 只有一个不同字符的字符串,只有一个不同字符的字符串,https://leetcode.cn/problems/strings-differ-by-one-character/,strings-differ-by-one-character,哈希表、字符串、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1500-1599/strings-differ-by-one-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1554.%20%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,50.0%,中等,33 -1555,1500-1599,1555. 银行账户概要,银行账户概要,https://leetcode.cn/problems/bank-account-summary/,bank-account-summary,数据库,https://algo.itcharge.cn/Solutions/1500-1599/bank-account-summary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1555.%20%E9%93%B6%E8%A1%8C%E8%B4%A6%E6%88%B7%E6%A6%82%E8%A6%81.md,44.9%,中等,76 -1556,1500-1599,1556. 千位分隔数,千位分隔数,https://leetcode.cn/problems/thousand-separator/,thousand-separator,字符串,https://algo.itcharge.cn/Solutions/1500-1599/thousand-separator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1556.%20%E5%8D%83%E4%BD%8D%E5%88%86%E9%9A%94%E6%95%B0.md,56.2%,简单,301 -1557,1500-1599,1557. 可以到达所有点的最少点数目,可以到达所有点的最少点数目,https://leetcode.cn/problems/minimum-number-of-vertices-to-reach-all-nodes/,minimum-number-of-vertices-to-reach-all-nodes,图,https://algo.itcharge.cn/Solutions/1500-1599/minimum-number-of-vertices-to-reach-all-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1557.%20%E5%8F%AF%E4%BB%A5%E5%88%B0%E8%BE%BE%E6%89%80%E6%9C%89%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%91%E7%82%B9%E6%95%B0%E7%9B%AE.md,81.5%,中等,236 -1558,1500-1599,1558. 得到目标数组的最少函数调用次数,得到目标数组的最少函数调用次数,https://leetcode.cn/problems/minimum-numbers-of-function-calls-to-make-target-array/,minimum-numbers-of-function-calls-to-make-target-array,贪心、位运算、数组,https://algo.itcharge.cn/Solutions/1500-1599/minimum-numbers-of-function-calls-to-make-target-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1558.%20%E5%BE%97%E5%88%B0%E7%9B%AE%E6%A0%87%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%91%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%AC%A1%E6%95%B0.md,63.0%,中等,97 -1559,1500-1599,1559. 二维网格图中探测环,二维网格图中探测环,https://leetcode.cn/problems/detect-cycles-in-2d-grid/,detect-cycles-in-2d-grid,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/detect-cycles-in-2d-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1559.%20%E4%BA%8C%E7%BB%B4%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%8E%A2%E6%B5%8B%E7%8E%AF.md,40.6%,中等,115 -1560,1500-1599,1560. 圆形赛道上经过次数最多的扇区,圆形赛道上经过次数最多的扇区,https://leetcode.cn/problems/most-visited-sector-in-a-circular-track/,most-visited-sector-in-a-circular-track,数组、模拟,https://algo.itcharge.cn/Solutions/1500-1599/most-visited-sector-in-a-circular-track/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1560.%20%E5%9C%86%E5%BD%A2%E8%B5%9B%E9%81%93%E4%B8%8A%E7%BB%8F%E8%BF%87%E6%AC%A1%E6%95%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E6%89%87%E5%8C%BA.md,57.4%,简单,131 -1561,1500-1599,1561. 你可以获得的最大硬币数目,你可以获得的最大硬币数目,https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/,maximum-number-of-coins-you-can-get,贪心、数组、数学、博弈、排序,https://algo.itcharge.cn/Solutions/1500-1599/maximum-number-of-coins-you-can-get/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1561.%20%E4%BD%A0%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%A1%AC%E5%B8%81%E6%95%B0%E7%9B%AE.md,77.8%,中等,250 -1562,1500-1599,1562. 查找大小为 M 的最新分组,查找大小为 M 的最新分组,https://leetcode.cn/problems/find-latest-group-of-size-m/,find-latest-group-of-size-m,数组、二分查找、模拟,https://algo.itcharge.cn/Solutions/1500-1599/find-latest-group-of-size-m/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1562.%20%E6%9F%A5%E6%89%BE%E5%A4%A7%E5%B0%8F%E4%B8%BA%20M%20%E7%9A%84%E6%9C%80%E6%96%B0%E5%88%86%E7%BB%84.md,36.5%,中等,131 -1563,1500-1599,1563. 石子游戏 V,石子游戏 V,https://leetcode.cn/problems/stone-game-v/,stone-game-v,数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1500-1599/stone-game-v/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1563.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20V.md,39.6%,困难,79 -1564,1500-1599,1564. 把箱子放进仓库里 I,把箱子放进仓库里 I,https://leetcode.cn/problems/put-boxes-into-the-warehouse-i/,put-boxes-into-the-warehouse-i,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1500-1599/put-boxes-into-the-warehouse-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1564.%20%E6%8A%8A%E7%AE%B1%E5%AD%90%E6%94%BE%E8%BF%9B%E4%BB%93%E5%BA%93%E9%87%8C%20I.md,57.8%,中等,41 -1565,1500-1599,1565. 按月统计订单数与顾客数,按月统计订单数与顾客数,https://leetcode.cn/problems/unique-orders-and-customers-per-month/,unique-orders-and-customers-per-month,数据库,https://algo.itcharge.cn/Solutions/1500-1599/unique-orders-and-customers-per-month/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1565.%20%E6%8C%89%E6%9C%88%E7%BB%9F%E8%AE%A1%E8%AE%A2%E5%8D%95%E6%95%B0%E4%B8%8E%E9%A1%BE%E5%AE%A2%E6%95%B0.md,73.9%,简单,55 -1566,1500-1599,1566. 重复至少 K 次且长度为 M 的模式,重复至少 K 次且长度为 M 的模式,https://leetcode.cn/problems/detect-pattern-of-length-m-repeated-k-or-more-times/,detect-pattern-of-length-m-repeated-k-or-more-times,数组、枚举,https://algo.itcharge.cn/Solutions/1500-1599/detect-pattern-of-length-m-repeated-k-or-more-times/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1566.%20%E9%87%8D%E5%A4%8D%E8%87%B3%E5%B0%91%20K%20%E6%AC%A1%E4%B8%94%E9%95%BF%E5%BA%A6%E4%B8%BA%20M%20%E7%9A%84%E6%A8%A1%E5%BC%8F.md,44.2%,简单,182 -1567,1500-1599,1567. 乘积为正数的最长子数组长度,乘积为正数的最长子数组长度,https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/,maximum-length-of-subarray-with-positive-product,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/maximum-length-of-subarray-with-positive-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1567.%20%E4%B9%98%E7%A7%AF%E4%B8%BA%E6%AD%A3%E6%95%B0%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6.md,42.7%,中等,462 -1568,1500-1599,1568. 使陆地分离的最少天数,使陆地分离的最少天数,https://leetcode.cn/problems/minimum-number-of-days-to-disconnect-island/,minimum-number-of-days-to-disconnect-island,深度优先搜索、广度优先搜索、数组、矩阵、强连通分量,https://algo.itcharge.cn/Solutions/1500-1599/minimum-number-of-days-to-disconnect-island/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1568.%20%E4%BD%BF%E9%99%86%E5%9C%B0%E5%88%86%E7%A6%BB%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md,45.0%,困难,82 -1569,1500-1599,1569. 将子数组重新排序得到同一个二叉搜索树的方案数,将子数组重新排序得到同一个二叉搜索树的方案数,https://leetcode.cn/problems/number-of-ways-to-reorder-array-to-get-same-bst/,number-of-ways-to-reorder-array-to-get-same-bst,树、并查集、二叉搜索树、记忆化搜索、数组、数学、分治、动态规划、二叉树、组合数学,https://algo.itcharge.cn/Solutions/1500-1599/number-of-ways-to-reorder-array-to-get-same-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1569.%20%E5%B0%86%E5%AD%90%E6%95%B0%E7%BB%84%E9%87%8D%E6%96%B0%E6%8E%92%E5%BA%8F%E5%BE%97%E5%88%B0%E5%90%8C%E4%B8%80%E4%B8%AA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,49.1%,困难,64 -1570,1500-1599,1570. 两个稀疏向量的点积,两个稀疏向量的点积,https://leetcode.cn/problems/dot-product-of-two-sparse-vectors/,dot-product-of-two-sparse-vectors,设计、数组、哈希表、双指针,https://algo.itcharge.cn/Solutions/1500-1599/dot-product-of-two-sparse-vectors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1570.%20%E4%B8%A4%E4%B8%AA%E7%A8%80%E7%96%8F%E5%90%91%E9%87%8F%E7%9A%84%E7%82%B9%E7%A7%AF.md,88.8%,中等,59 -1571,1500-1599,1571. 仓库经理,仓库经理,https://leetcode.cn/problems/warehouse-manager/,warehouse-manager,数据库,https://algo.itcharge.cn/Solutions/1500-1599/warehouse-manager/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1571.%20%E4%BB%93%E5%BA%93%E7%BB%8F%E7%90%86.md,77.6%,简单,96 -1572,1500-1599,1572. 矩阵对角线元素的和,矩阵对角线元素的和,https://leetcode.cn/problems/matrix-diagonal-sum/,matrix-diagonal-sum,数组、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/matrix-diagonal-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1572.%20%E7%9F%A9%E9%98%B5%E5%AF%B9%E8%A7%92%E7%BA%BF%E5%85%83%E7%B4%A0%E7%9A%84%E5%92%8C.md,80.7%,简单,577 -1573,1500-1599,1573. 分割字符串的方案数,分割字符串的方案数,https://leetcode.cn/problems/number-of-ways-to-split-a-string/,number-of-ways-to-split-a-string,数学、字符串,https://algo.itcharge.cn/Solutions/1500-1599/number-of-ways-to-split-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1573.%20%E5%88%86%E5%89%B2%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,31.2%,中等,92 -1574,1500-1599,1574. 删除最短的子数组使剩余数组有序,删除最短的子数组使剩余数组有序,https://leetcode.cn/problems/shortest-subarray-to-be-removed-to-make-array-sorted/,shortest-subarray-to-be-removed-to-make-array-sorted,栈、数组、双指针、二分查找、单调栈,https://algo.itcharge.cn/Solutions/1500-1599/shortest-subarray-to-be-removed-to-make-array-sorted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1574.%20%E5%88%A0%E9%99%A4%E6%9C%80%E7%9F%AD%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%BD%BF%E5%89%A9%E4%BD%99%E6%95%B0%E7%BB%84%E6%9C%89%E5%BA%8F.md,43.2%,中等,288 -1575,1500-1599,1575. 统计所有可行路径,统计所有可行路径,https://leetcode.cn/problems/count-all-possible-routes/,count-all-possible-routes,记忆化搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/count-all-possible-routes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1575.%20%E7%BB%9F%E8%AE%A1%E6%89%80%E6%9C%89%E5%8F%AF%E8%A1%8C%E8%B7%AF%E5%BE%84.md,57.5%,困难,123 -1576,1500-1599,1576. 替换所有的问号,替换所有的问号,https://leetcode.cn/problems/replace-all-s-to-avoid-consecutive-repeating-characters/,replace-all-s-to-avoid-consecutive-repeating-characters,字符串,https://algo.itcharge.cn/Solutions/1500-1599/replace-all-s-to-avoid-consecutive-repeating-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1576.%20%E6%9B%BF%E6%8D%A2%E6%89%80%E6%9C%89%E7%9A%84%E9%97%AE%E5%8F%B7.md,51.0%,简单,649 -1577,1500-1599,1577. 数的平方等于两数乘积的方法数,数的平方等于两数乘积的方法数,https://leetcode.cn/problems/number-of-ways-where-square-of-number-is-equal-to-product-of-two-numbers/,number-of-ways-where-square-of-number-is-equal-to-product-of-two-numbers,数组、哈希表、数学、双指针,https://algo.itcharge.cn/Solutions/1500-1599/number-of-ways-where-square-of-number-is-equal-to-product-of-two-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1577.%20%E6%95%B0%E7%9A%84%E5%B9%B3%E6%96%B9%E7%AD%89%E4%BA%8E%E4%B8%A4%E6%95%B0%E4%B9%98%E7%A7%AF%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,34.6%,中等,108 -1578,1500-1599,1578. 使绳子变成彩色的最短时间,使绳子变成彩色的最短时间,https://leetcode.cn/problems/minimum-time-to-make-rope-colorful/,minimum-time-to-make-rope-colorful,贪心、数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/1500-1599/minimum-time-to-make-rope-colorful/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1578.%20%E4%BD%BF%E7%BB%B3%E5%AD%90%E5%8F%98%E6%88%90%E5%BD%A9%E8%89%B2%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,59.7%,中等,265 -1579,1500-1599,1579. 保证图可完全遍历,保证图可完全遍历,https://leetcode.cn/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/,remove-max-number-of-edges-to-keep-graph-fully-traversable,并查集、图,https://algo.itcharge.cn/Solutions/1500-1599/remove-max-number-of-edges-to-keep-graph-fully-traversable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1579.%20%E4%BF%9D%E8%AF%81%E5%9B%BE%E5%8F%AF%E5%AE%8C%E5%85%A8%E9%81%8D%E5%8E%86.md,62.1%,困难,329 -1580,1500-1599,1580. 把箱子放进仓库里 II,把箱子放进仓库里 II,https://leetcode.cn/problems/put-boxes-into-the-warehouse-ii/,put-boxes-into-the-warehouse-ii,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1500-1599/put-boxes-into-the-warehouse-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1580.%20%E6%8A%8A%E7%AE%B1%E5%AD%90%E6%94%BE%E8%BF%9B%E4%BB%93%E5%BA%93%E9%87%8C%20II.md,59.5%,中等,31 -1581,1500-1599,1581. 进店却未进行过交易的顾客,进店却未进行过交易的顾客,https://leetcode.cn/problems/customer-who-visited-but-did-not-make-any-transactions/,customer-who-visited-but-did-not-make-any-transactions,数据库,https://algo.itcharge.cn/Solutions/1500-1599/customer-who-visited-but-did-not-make-any-transactions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1581.%20%E8%BF%9B%E5%BA%97%E5%8D%B4%E6%9C%AA%E8%BF%9B%E8%A1%8C%E8%BF%87%E4%BA%A4%E6%98%93%E7%9A%84%E9%A1%BE%E5%AE%A2.md,79.5%,简单,288 -1582,1500-1599,1582. 二进制矩阵中的特殊位置,二进制矩阵中的特殊位置,https://leetcode.cn/problems/special-positions-in-a-binary-matrix/,special-positions-in-a-binary-matrix,数组、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/special-positions-in-a-binary-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1582.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E7%89%B9%E6%AE%8A%E4%BD%8D%E7%BD%AE.md,69.5%,简单,469 -1583,1500-1599,1583. 统计不开心的朋友,统计不开心的朋友,https://leetcode.cn/problems/count-unhappy-friends/,count-unhappy-friends,数组、模拟,https://algo.itcharge.cn/Solutions/1500-1599/count-unhappy-friends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1583.%20%E7%BB%9F%E8%AE%A1%E4%B8%8D%E5%BC%80%E5%BF%83%E7%9A%84%E6%9C%8B%E5%8F%8B.md,67.9%,中等,256 -1584,1500-1599,1584. 连接所有点的最小费用,连接所有点的最小费用,https://leetcode.cn/problems/min-cost-to-connect-all-points/,min-cost-to-connect-all-points,并查集、图、数组、最小生成树,https://algo.itcharge.cn/Solutions/1500-1599/min-cost-to-connect-all-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1584.%20%E8%BF%9E%E6%8E%A5%E6%89%80%E6%9C%89%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8.md,65.7%,中等,641 -1585,1500-1599,1585. 检查字符串是否可以通过排序子字符串得到另一个字符串,检查字符串是否可以通过排序子字符串得到另一个字符串,https://leetcode.cn/problems/check-if-string-is-transformable-with-substring-sort-operations/,check-if-string-is-transformable-with-substring-sort-operations,贪心、字符串、排序,https://algo.itcharge.cn/Solutions/1500-1599/check-if-string-is-transformable-with-substring-sort-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1585.%20%E6%A3%80%E6%9F%A5%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%E6%8E%92%E5%BA%8F%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%BE%97%E5%88%B0%E5%8F%A6%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2.md,43.5%,困难,53 -1586,1500-1599,1586. 二叉搜索树迭代器 II,二叉搜索树迭代器 II,https://leetcode.cn/problems/binary-search-tree-iterator-ii/,binary-search-tree-iterator-ii,栈、树、设计、二叉搜索树、二叉树、迭代器,https://algo.itcharge.cn/Solutions/1500-1599/binary-search-tree-iterator-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1586.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8%20II.md,63.0%,中等,40 -1587,1500-1599,1587. 银行账户概要 II,银行账户概要 II,https://leetcode.cn/problems/bank-account-summary-ii/,bank-account-summary-ii,数据库,https://algo.itcharge.cn/Solutions/1500-1599/bank-account-summary-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1587.%20%E9%93%B6%E8%A1%8C%E8%B4%A6%E6%88%B7%E6%A6%82%E8%A6%81%20II.md,80.2%,简单,157 -1588,1500-1599,1588. 所有奇数长度子数组的和,所有奇数长度子数组的和,https://leetcode.cn/problems/sum-of-all-odd-length-subarrays/,sum-of-all-odd-length-subarrays,数组、数学、前缀和,https://algo.itcharge.cn/Solutions/1500-1599/sum-of-all-odd-length-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1588.%20%E6%89%80%E6%9C%89%E5%A5%87%E6%95%B0%E9%95%BF%E5%BA%A6%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%92%8C.md,83.2%,简单,985 -1589,1500-1599,1589. 所有排列中的最大和,所有排列中的最大和,https://leetcode.cn/problems/maximum-sum-obtained-of-any-permutation/,maximum-sum-obtained-of-any-permutation,贪心、数组、前缀和、排序,https://algo.itcharge.cn/Solutions/1500-1599/maximum-sum-obtained-of-any-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1589.%20%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,32.4%,中等,96 -1590,1500-1599,1590. 使数组和能被 P 整除,使数组和能被 P 整除,https://leetcode.cn/problems/make-sum-divisible-by-p/,make-sum-divisible-by-p,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/1500-1599/make-sum-divisible-by-p/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1590.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%92%8C%E8%83%BD%E8%A2%AB%20P%20%E6%95%B4%E9%99%A4.md,36.4%,中等,198 -1591,1500-1599,1591. 奇怪的打印机 II,奇怪的打印机 II,https://leetcode.cn/problems/strange-printer-ii/,strange-printer-ii,图、拓扑排序、数组、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/strange-printer-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1591.%20%E5%A5%87%E6%80%AA%E7%9A%84%E6%89%93%E5%8D%B0%E6%9C%BA%20II.md,63.7%,困难,67 -1592,1500-1599,1592. 重新排列单词间的空格,重新排列单词间的空格,https://leetcode.cn/problems/rearrange-spaces-between-words/,rearrange-spaces-between-words,字符串,https://algo.itcharge.cn/Solutions/1500-1599/rearrange-spaces-between-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1592.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E5%8D%95%E8%AF%8D%E9%97%B4%E7%9A%84%E7%A9%BA%E6%A0%BC.md,46.7%,简单,578 -1593,1500-1599,1593. 拆分字符串使唯一子字符串的数目最大,拆分字符串使唯一子字符串的数目最大,https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/,split-a-string-into-the-max-number-of-unique-substrings,哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1593.%20%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E5%94%AF%E4%B8%80%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE%E6%9C%80%E5%A4%A7.md,57.4%,中等,141 -1594,1500-1599,1594. 矩阵的最大非负积,矩阵的最大非负积,https://leetcode.cn/problems/maximum-non-negative-product-in-a-matrix/,maximum-non-negative-product-in-a-matrix,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/maximum-non-negative-product-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1594.%20%E7%9F%A9%E9%98%B5%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%9E%E8%B4%9F%E7%A7%AF.md,33.7%,中等,132 -1595,1500-1599,1595. 连通两组点的最小成本,连通两组点的最小成本,https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/,minimum-cost-to-connect-two-groups-of-points,位运算、数组、动态规划、状态压缩、矩阵,https://algo.itcharge.cn/Solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md,65.8%,困难,88 -1596,1500-1599,1596. 每位顾客最经常订购的商品,每位顾客最经常订购的商品,https://leetcode.cn/problems/the-most-frequently-ordered-products-for-each-customer/,the-most-frequently-ordered-products-for-each-customer,数据库,https://algo.itcharge.cn/Solutions/1500-1599/the-most-frequently-ordered-products-for-each-customer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1596.%20%E6%AF%8F%E4%BD%8D%E9%A1%BE%E5%AE%A2%E6%9C%80%E7%BB%8F%E5%B8%B8%E8%AE%A2%E8%B4%AD%E7%9A%84%E5%95%86%E5%93%81.md,73.3%,中等,92 -1597,1500-1599,1597. 根据中缀表达式构造二叉表达式树,根据中缀表达式构造二叉表达式树,https://leetcode.cn/problems/build-binary-expression-tree-from-infix-expression/,build-binary-expression-tree-from-infix-expression,栈、树、字符串、二叉树,https://algo.itcharge.cn/Solutions/1500-1599/build-binary-expression-tree-from-infix-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1597.%20%E6%A0%B9%E6%8D%AE%E4%B8%AD%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%A0%91.md,68.4%,困难,33 -1598,1500-1599,1598. 文件夹操作日志搜集器,文件夹操作日志搜集器,https://leetcode.cn/problems/crawler-log-folder/,crawler-log-folder,栈、数组、字符串,https://algo.itcharge.cn/Solutions/1500-1599/crawler-log-folder/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1598.%20%E6%96%87%E4%BB%B6%E5%A4%B9%E6%93%8D%E4%BD%9C%E6%97%A5%E5%BF%97%E6%90%9C%E9%9B%86%E5%99%A8.md,69.5%,简单,560 -1599,1500-1599,1599. 经营摩天轮的最大利润,经营摩天轮的最大利润,https://leetcode.cn/problems/maximum-profit-of-operating-a-centennial-wheel/,maximum-profit-of-operating-a-centennial-wheel,数组、模拟,https://algo.itcharge.cn/Solutions/1500-1599/maximum-profit-of-operating-a-centennial-wheel/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1599.%20%E7%BB%8F%E8%90%A5%E6%91%A9%E5%A4%A9%E8%BD%AE%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A9%E6%B6%A6.md,51.5%,中等,269 -1600,1600-1699,1600. 王位继承顺序,王位继承顺序,https://leetcode.cn/problems/throne-inheritance/,throne-inheritance,树、深度优先搜索、设计、哈希表,https://algo.itcharge.cn/Solutions/1600-1699/throne-inheritance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1600.%20%E7%8E%8B%E4%BD%8D%E7%BB%A7%E6%89%BF%E9%A1%BA%E5%BA%8F.md,65.6%,中等,235 -1601,1600-1699,1601. 最多可达成的换楼请求数目,最多可达成的换楼请求数目,https://leetcode.cn/problems/maximum-number-of-achievable-transfer-requests/,maximum-number-of-achievable-transfer-requests,位运算、数组、回溯、枚举,https://algo.itcharge.cn/Solutions/1600-1699/maximum-number-of-achievable-transfer-requests/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1601.%20%E6%9C%80%E5%A4%9A%E5%8F%AF%E8%BE%BE%E6%88%90%E7%9A%84%E6%8D%A2%E6%A5%BC%E8%AF%B7%E6%B1%82%E6%95%B0%E7%9B%AE.md,61.8%,困难,243 -1602,1600-1699,1602. 找到二叉树中最近的右侧节点,找到二叉树中最近的右侧节点,https://leetcode.cn/problems/find-nearest-right-node-in-binary-tree/,find-nearest-right-node-in-binary-tree,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/find-nearest-right-node-in-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1602.%20%E6%89%BE%E5%88%B0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E6%9C%80%E8%BF%91%E7%9A%84%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9.md,74.6%,中等,60 -1603,1600-1699,1603. 设计停车系统,设计停车系统,https://leetcode.cn/problems/design-parking-system/,design-parking-system,设计、计数、模拟,https://algo.itcharge.cn/Solutions/1600-1699/design-parking-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1603.%20%E8%AE%BE%E8%AE%A1%E5%81%9C%E8%BD%A6%E7%B3%BB%E7%BB%9F.md,83.8%,简单,586 -1604,1600-1699,1604. 警告一小时内使用相同员工卡大于等于三次的人,警告一小时内使用相同员工卡大于等于三次的人,https://leetcode.cn/problems/alert-using-same-key-card-three-or-more-times-in-a-one-hour-period/,alert-using-same-key-card-three-or-more-times-in-a-one-hour-period,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1600-1699/alert-using-same-key-card-three-or-more-times-in-a-one-hour-period/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1604.%20%E8%AD%A6%E5%91%8A%E4%B8%80%E5%B0%8F%E6%97%B6%E5%86%85%E4%BD%BF%E7%94%A8%E7%9B%B8%E5%90%8C%E5%91%98%E5%B7%A5%E5%8D%A1%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E4%B8%89%E6%AC%A1%E7%9A%84%E4%BA%BA.md,50.4%,中等,346 -1605,1600-1699,1605. 给定行和列的和求可行矩阵,给定行和列的和求可行矩阵,https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/,find-valid-matrix-given-row-and-column-sums,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/1600-1699/find-valid-matrix-given-row-and-column-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1605.%20%E7%BB%99%E5%AE%9A%E8%A1%8C%E5%92%8C%E5%88%97%E7%9A%84%E5%92%8C%E6%B1%82%E5%8F%AF%E8%A1%8C%E7%9F%A9%E9%98%B5.md,80.9%,中等,273 -1606,1600-1699,1606. 找到处理最多请求的服务器,找到处理最多请求的服务器,https://leetcode.cn/problems/find-servers-that-handled-most-number-of-requests/,find-servers-that-handled-most-number-of-requests,贪心、数组、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/find-servers-that-handled-most-number-of-requests/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1606.%20%E6%89%BE%E5%88%B0%E5%A4%84%E7%90%86%E6%9C%80%E5%A4%9A%E8%AF%B7%E6%B1%82%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8.md,48.4%,困难,204 -1607,1600-1699,1607. 没有卖出的卖家,没有卖出的卖家,https://leetcode.cn/problems/sellers-with-no-sales/,sellers-with-no-sales,数据库,https://algo.itcharge.cn/Solutions/1600-1699/sellers-with-no-sales/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1607.%20%E6%B2%A1%E6%9C%89%E5%8D%96%E5%87%BA%E7%9A%84%E5%8D%96%E5%AE%B6.md,54.4%,简单,91 -1608,1600-1699,1608. 特殊数组的特征值,特殊数组的特征值,https://leetcode.cn/problems/special-array-with-x-elements-greater-than-or-equal-x/,special-array-with-x-elements-greater-than-or-equal-x,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/1600-1699/special-array-with-x-elements-greater-than-or-equal-x/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1608.%20%E7%89%B9%E6%AE%8A%E6%95%B0%E7%BB%84%E7%9A%84%E7%89%B9%E5%BE%81%E5%80%BC.md,61.4%,简单,808 -1609,1600-1699,1609. 奇偶树,奇偶树,https://leetcode.cn/problems/even-odd-tree/,even-odd-tree,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/even-odd-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1609.%20%E5%A5%87%E5%81%B6%E6%A0%91.md,58.1%,中等,502 -1610,1600-1699,1610. 可见点的最大数目,可见点的最大数目,https://leetcode.cn/problems/maximum-number-of-visible-points/,maximum-number-of-visible-points,几何、数组、数学、排序、滑动窗口,https://algo.itcharge.cn/Solutions/1600-1699/maximum-number-of-visible-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1610.%20%E5%8F%AF%E8%A7%81%E7%82%B9%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,44.4%,困难,161 -1611,1600-1699,1611. 使整数变为 0 的最少操作次数,使整数变为 0 的最少操作次数,https://leetcode.cn/problems/minimum-one-bit-operations-to-make-integers-zero/,minimum-one-bit-operations-to-make-integers-zero,位运算、记忆化搜索、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/minimum-one-bit-operations-to-make-integers-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1611.%20%E4%BD%BF%E6%95%B4%E6%95%B0%E5%8F%98%E4%B8%BA%200%20%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,61.1%,困难,73 -1612,1600-1699,1612. 检查两棵二叉表达式树是否等价,检查两棵二叉表达式树是否等价,https://leetcode.cn/problems/check-if-two-expression-trees-are-equivalent/,check-if-two-expression-trees-are-equivalent,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/check-if-two-expression-trees-are-equivalent/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1612.%20%E6%A3%80%E6%9F%A5%E4%B8%A4%E6%A3%B5%E4%BA%8C%E5%8F%89%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%A0%91%E6%98%AF%E5%90%A6%E7%AD%89%E4%BB%B7.md,74.3%,中等,35 -1613,1600-1699,1613. 找到遗失的ID,找到遗失的ID,https://leetcode.cn/problems/find-the-missing-ids/,find-the-missing-ids,数据库,https://algo.itcharge.cn/Solutions/1600-1699/find-the-missing-ids/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1613.%20%E6%89%BE%E5%88%B0%E9%81%97%E5%A4%B1%E7%9A%84ID.md,72.8%,中等,67 -1614,1600-1699,1614. 括号的最大嵌套深度,括号的最大嵌套深度,https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/,maximum-nesting-depth-of-the-parentheses,栈、字符串,https://algo.itcharge.cn/Solutions/1600-1699/maximum-nesting-depth-of-the-parentheses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1614.%20%E6%8B%AC%E5%8F%B7%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B5%8C%E5%A5%97%E6%B7%B1%E5%BA%A6.md,82.4%,简单,835 -1615,1600-1699,1615. 最大网络秩,最大网络秩,https://leetcode.cn/problems/maximal-network-rank/,maximal-network-rank,图,https://algo.itcharge.cn/Solutions/1600-1699/maximal-network-rank/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1615.%20%E6%9C%80%E5%A4%A7%E7%BD%91%E7%BB%9C%E7%A7%A9.md,60.1%,中等,293 -1616,1600-1699,1616. 分割两个字符串得到回文串,分割两个字符串得到回文串,https://leetcode.cn/problems/split-two-strings-to-make-palindrome/,split-two-strings-to-make-palindrome,双指针、字符串,https://algo.itcharge.cn/Solutions/1600-1699/split-two-strings-to-make-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1616.%20%E5%88%86%E5%89%B2%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%BE%97%E5%88%B0%E5%9B%9E%E6%96%87%E4%B8%B2.md,37.6%,中等,261 -1617,1600-1699,1617. 统计子树中城市之间最大距离,统计子树中城市之间最大距离,https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/,count-subtrees-with-max-distance-between-cities,位运算、树、动态规划、状态压缩、枚举,https://algo.itcharge.cn/Solutions/1600-1699/count-subtrees-with-max-distance-between-cities/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1617.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E6%A0%91%E4%B8%AD%E5%9F%8E%E5%B8%82%E4%B9%8B%E9%97%B4%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,79.2%,困难,100 -1618,1600-1699,1618. 找出适应屏幕的最大字号,找出适应屏幕的最大字号,https://leetcode.cn/problems/maximum-font-to-fit-a-sentence-in-a-screen/,maximum-font-to-fit-a-sentence-in-a-screen,数组、字符串、二分查找、交互,https://algo.itcharge.cn/Solutions/1600-1699/maximum-font-to-fit-a-sentence-in-a-screen/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1618.%20%E6%89%BE%E5%87%BA%E9%80%82%E5%BA%94%E5%B1%8F%E5%B9%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%97%E5%8F%B7.md,62.5%,中等,27 -1619,1600-1699,1619. 删除某些元素后的数组均值,删除某些元素后的数组均值,https://leetcode.cn/problems/mean-of-array-after-removing-some-elements/,mean-of-array-after-removing-some-elements,数组、排序,https://algo.itcharge.cn/Solutions/1600-1699/mean-of-array-after-removing-some-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1619.%20%E5%88%A0%E9%99%A4%E6%9F%90%E4%BA%9B%E5%85%83%E7%B4%A0%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84%E5%9D%87%E5%80%BC.md,71.8%,简单,450 -1620,1600-1699,1620. 网络信号最好的坐标,网络信号最好的坐标,https://leetcode.cn/problems/coordinate-with-maximum-network-quality/,coordinate-with-maximum-network-quality,数组、枚举,https://algo.itcharge.cn/Solutions/1600-1699/coordinate-with-maximum-network-quality/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1620.%20%E7%BD%91%E7%BB%9C%E4%BF%A1%E5%8F%B7%E6%9C%80%E5%A5%BD%E7%9A%84%E5%9D%90%E6%A0%87.md,46.1%,中等,282 -1621,1600-1699,1621. 大小为 K 的不重叠线段的数目,大小为 K 的不重叠线段的数目,https://leetcode.cn/problems/number-of-sets-of-k-non-overlapping-line-segments/,number-of-sets-of-k-non-overlapping-line-segments,数学、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/number-of-sets-of-k-non-overlapping-line-segments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1621.%20%E5%A4%A7%E5%B0%8F%E4%B8%BA%20K%20%E7%9A%84%E4%B8%8D%E9%87%8D%E5%8F%A0%E7%BA%BF%E6%AE%B5%E7%9A%84%E6%95%B0%E7%9B%AE.md,48.0%,中等,55 -1622,1600-1699,1622. 奇妙序列,奇妙序列,https://leetcode.cn/problems/fancy-sequence/,fancy-sequence,设计、线段树、数学,https://algo.itcharge.cn/Solutions/1600-1699/fancy-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1622.%20%E5%A5%87%E5%A6%99%E5%BA%8F%E5%88%97.md,17.2%,困难,106 -1623,1600-1699,1623. 三人国家代表队,三人国家代表队,https://leetcode.cn/problems/all-valid-triplets-that-can-represent-a-country/,all-valid-triplets-that-can-represent-a-country,数据库,https://algo.itcharge.cn/Solutions/1600-1699/all-valid-triplets-that-can-represent-a-country/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1623.%20%E4%B8%89%E4%BA%BA%E5%9B%BD%E5%AE%B6%E4%BB%A3%E8%A1%A8%E9%98%9F.md,78.5%,简单,61 -1624,1600-1699,1624. 两个相同字符之间的最长子字符串,两个相同字符之间的最长子字符串,https://leetcode.cn/problems/largest-substring-between-two-equal-characters/,largest-substring-between-two-equal-characters,哈希表、字符串,https://algo.itcharge.cn/Solutions/1600-1699/largest-substring-between-two-equal-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1624.%20%E4%B8%A4%E4%B8%AA%E7%9B%B8%E5%90%8C%E5%AD%97%E7%AC%A6%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,64.4%,简单,693 -1625,1600-1699,1625. 执行操作后字典序最小的字符串,执行操作后字典序最小的字符串,https://leetcode.cn/problems/lexicographically-smallest-string-after-applying-operations/,lexicographically-smallest-string-after-applying-operations,广度优先搜索、字符串,https://algo.itcharge.cn/Solutions/1600-1699/lexicographically-smallest-string-after-applying-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1625.%20%E6%89%A7%E8%A1%8C%E6%93%8D%E4%BD%9C%E5%90%8E%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,66.7%,中等,150 -1626,1600-1699,1626. 无矛盾的最佳球队,无矛盾的最佳球队,https://leetcode.cn/problems/best-team-with-no-conflicts/,best-team-with-no-conflicts,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1600-1699/best-team-with-no-conflicts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1626.%20%E6%97%A0%E7%9F%9B%E7%9B%BE%E7%9A%84%E6%9C%80%E4%BD%B3%E7%90%83%E9%98%9F.md,53.7%,中等,248 -1627,1600-1699,1627. 带阈值的图连通性,带阈值的图连通性,https://leetcode.cn/problems/graph-connectivity-with-threshold/,graph-connectivity-with-threshold,并查集、数组、数学,https://algo.itcharge.cn/Solutions/1600-1699/graph-connectivity-with-threshold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1627.%20%E5%B8%A6%E9%98%88%E5%80%BC%E7%9A%84%E5%9B%BE%E8%BF%9E%E9%80%9A%E6%80%A7.md,42.2%,困难,65 -1628,1600-1699,1628. 设计带解析函数的表达式树,设计带解析函数的表达式树,https://leetcode.cn/problems/design-an-expression-tree-with-evaluate-function/,design-an-expression-tree-with-evaluate-function,栈、树、设计、数学、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/design-an-expression-tree-with-evaluate-function/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1628.%20%E8%AE%BE%E8%AE%A1%E5%B8%A6%E8%A7%A3%E6%9E%90%E5%87%BD%E6%95%B0%E7%9A%84%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%A0%91.md,82.4%,中等,34 -1629,1600-1699,1629. 按键持续时间最长的键,按键持续时间最长的键,https://leetcode.cn/problems/slowest-key/,slowest-key,数组、字符串,https://algo.itcharge.cn/Solutions/1600-1699/slowest-key/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1629.%20%E6%8C%89%E9%94%AE%E6%8C%81%E7%BB%AD%E6%97%B6%E9%97%B4%E6%9C%80%E9%95%BF%E7%9A%84%E9%94%AE.md,55.8%,简单,446 -1630,1600-1699,1630. 等差子数组,等差子数组,https://leetcode.cn/problems/arithmetic-subarrays/,arithmetic-subarrays,数组、排序,https://algo.itcharge.cn/Solutions/1600-1699/arithmetic-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1630.%20%E7%AD%89%E5%B7%AE%E5%AD%90%E6%95%B0%E7%BB%84.md,76.1%,中等,447 -1631,1600-1699,1631. 最小体力消耗路径,最小体力消耗路径,https://leetcode.cn/problems/path-with-minimum-effort/,path-with-minimum-effort,深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/path-with-minimum-effort/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md,50.9%,中等,443 -1632,1600-1699,1632. 矩阵转换后的秩,矩阵转换后的秩,https://leetcode.cn/problems/rank-transform-of-a-matrix/,rank-transform-of-a-matrix,贪心、并查集、图、拓扑排序、数组、矩阵,https://algo.itcharge.cn/Solutions/1600-1699/rank-transform-of-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1632.%20%E7%9F%A9%E9%98%B5%E8%BD%AC%E6%8D%A2%E5%90%8E%E7%9A%84%E7%A7%A9.md,57.5%,困难,97 -1633,1600-1699,1633. 各赛事的用户注册率,各赛事的用户注册率,https://leetcode.cn/problems/percentage-of-users-attended-a-contest/,percentage-of-users-attended-a-contest,数据库,https://algo.itcharge.cn/Solutions/1600-1699/percentage-of-users-attended-a-contest/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1633.%20%E5%90%84%E8%B5%9B%E4%BA%8B%E7%9A%84%E7%94%A8%E6%88%B7%E6%B3%A8%E5%86%8C%E7%8E%87.md,62.7%,简单,102 -1634,1600-1699,1634. 求两个多项式链表的和,求两个多项式链表的和,https://leetcode.cn/problems/add-two-polynomials-represented-as-linked-lists/,add-two-polynomials-represented-as-linked-lists,链表、数学、双指针,https://algo.itcharge.cn/Solutions/1600-1699/add-two-polynomials-represented-as-linked-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1634.%20%E6%B1%82%E4%B8%A4%E4%B8%AA%E5%A4%9A%E9%A1%B9%E5%BC%8F%E9%93%BE%E8%A1%A8%E7%9A%84%E5%92%8C.md,60.1%,中等,56 -1635,1600-1699,1635. Hopper 公司查询 I,Hopper 公司查询 I,https://leetcode.cn/problems/hopper-company-queries-i/,hopper-company-queries-i,数据库,https://algo.itcharge.cn/Solutions/1600-1699/hopper-company-queries-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1635.%20Hopper%20%E5%85%AC%E5%8F%B8%E6%9F%A5%E8%AF%A2%20I.md,49.1%,困难,54 -1636,1600-1699,1636. 按照频率将数组升序排序,按照频率将数组升序排序,https://leetcode.cn/problems/sort-array-by-increasing-frequency/,sort-array-by-increasing-frequency,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/1600-1699/sort-array-by-increasing-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1636.%20%E6%8C%89%E7%85%A7%E9%A2%91%E7%8E%87%E5%B0%86%E6%95%B0%E7%BB%84%E5%8D%87%E5%BA%8F%E6%8E%92%E5%BA%8F.md,74.8%,简单,609 -1637,1600-1699,1637. 两点之间不包含任何点的最宽垂直区域,两点之间不包含任何点的最宽垂直区域,https://leetcode.cn/problems/widest-vertical-area-between-two-points-containing-no-points/,widest-vertical-area-between-two-points-containing-no-points,数组、排序,https://algo.itcharge.cn/Solutions/1600-1699/widest-vertical-area-between-two-points-containing-no-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1637.%20%E4%B8%A4%E7%82%B9%E4%B9%8B%E9%97%B4%E4%B8%8D%E5%8C%85%E5%90%AB%E4%BB%BB%E4%BD%95%E7%82%B9%E7%9A%84%E6%9C%80%E5%AE%BD%E5%9E%82%E7%9B%B4%E5%8C%BA%E5%9F%9F.md,84.3%,中等,274 -1638,1600-1699,1638. 统计只差一个字符的子串数目,统计只差一个字符的子串数目,https://leetcode.cn/problems/count-substrings-that-differ-by-one-character/,count-substrings-that-differ-by-one-character,哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/count-substrings-that-differ-by-one-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1638.%20%E7%BB%9F%E8%AE%A1%E5%8F%AA%E5%B7%AE%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E4%B8%B2%E6%95%B0%E7%9B%AE.md,77.6%,中等,194 -1639,1600-1699,1639. 通过给定词典构造目标字符串的方案数,通过给定词典构造目标字符串的方案数,https://leetcode.cn/problems/number-of-ways-to-form-a-target-string-given-a-dictionary/,number-of-ways-to-form-a-target-string-given-a-dictionary,数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/number-of-ways-to-form-a-target-string-given-a-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1639.%20%E9%80%9A%E8%BF%87%E7%BB%99%E5%AE%9A%E8%AF%8D%E5%85%B8%E6%9E%84%E9%80%A0%E7%9B%AE%E6%A0%87%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,46.9%,困难,55 -1640,1600-1699,1640. 能否连接形成数组,能否连接形成数组,https://leetcode.cn/problems/check-array-formation-through-concatenation/,check-array-formation-through-concatenation,数组、哈希表,https://algo.itcharge.cn/Solutions/1600-1699/check-array-formation-through-concatenation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1640.%20%E8%83%BD%E5%90%A6%E8%BF%9E%E6%8E%A5%E5%BD%A2%E6%88%90%E6%95%B0%E7%BB%84.md,60.9%,简单,660 -1641,1600-1699,1641. 统计字典序元音字符串的数目,统计字典序元音字符串的数目,https://leetcode.cn/problems/count-sorted-vowel-strings/,count-sorted-vowel-strings,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/1600-1699/count-sorted-vowel-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1641.%20%E7%BB%9F%E8%AE%A1%E5%AD%97%E5%85%B8%E5%BA%8F%E5%85%83%E9%9F%B3%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE.md,82.4%,中等,598 -1642,1600-1699,1642. 可以到达的最远建筑,可以到达的最远建筑,https://leetcode.cn/problems/furthest-building-you-can-reach/,furthest-building-you-can-reach,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/furthest-building-you-can-reach/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1642.%20%E5%8F%AF%E4%BB%A5%E5%88%B0%E8%BE%BE%E7%9A%84%E6%9C%80%E8%BF%9C%E5%BB%BA%E7%AD%91.md,45.3%,中等,171 -1643,1600-1699,1643. 第 K 条最小指令,第 K 条最小指令,https://leetcode.cn/problems/kth-smallest-instructions/,kth-smallest-instructions,数组、数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/1600-1699/kth-smallest-instructions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1643.%20%E7%AC%AC%20K%20%E6%9D%A1%E6%9C%80%E5%B0%8F%E6%8C%87%E4%BB%A4.md,48.0%,困难,78 -1644,1600-1699,1644. 二叉树的最近公共祖先 II,二叉树的最近公共祖先 II,https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree-ii/,lowest-common-ancestor-of-a-binary-tree-ii,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/lowest-common-ancestor-of-a-binary-tree-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1644.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%20II.md,56.0%,中等,56 -1645,1600-1699,1645. 1645.Hopper 公司查询 II,1645.Hopper 公司查询 II,https://leetcode.cn/problems/hopper-company-queries-ii/,hopper-company-queries-ii,数据库,https://algo.itcharge.cn/Solutions/1600-1699/hopper-company-queries-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1645.%201645.Hopper%20%E5%85%AC%E5%8F%B8%E6%9F%A5%E8%AF%A2%20II.md,41.8%,困难,37 -1646,1600-1699,1646. 获取生成数组中的最大值,获取生成数组中的最大值,https://leetcode.cn/problems/get-maximum-in-generated-array/,get-maximum-in-generated-array,数组、动态规划、模拟,https://algo.itcharge.cn/Solutions/1600-1699/get-maximum-in-generated-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1646.%20%E8%8E%B7%E5%8F%96%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,51.9%,简单,503 -1647,1600-1699,1647. 字符频次唯一的最小删除次数,字符频次唯一的最小删除次数,https://leetcode.cn/problems/minimum-deletions-to-make-character-frequencies-unique/,minimum-deletions-to-make-character-frequencies-unique,贪心、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1647.%20%E5%AD%97%E7%AC%A6%E9%A2%91%E6%AC%A1%E5%94%AF%E4%B8%80%E7%9A%84%E6%9C%80%E5%B0%8F%E5%88%A0%E9%99%A4%E6%AC%A1%E6%95%B0.md,54.5%,中等,186 -1648,1600-1699,1648. 销售价值减少的颜色球,销售价值减少的颜色球,https://leetcode.cn/problems/sell-diminishing-valued-colored-balls/,sell-diminishing-valued-colored-balls,贪心、数组、数学、二分查找、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/sell-diminishing-valued-colored-balls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1648.%20%E9%94%80%E5%94%AE%E4%BB%B7%E5%80%BC%E5%87%8F%E5%B0%91%E7%9A%84%E9%A2%9C%E8%89%B2%E7%90%83.md,31.6%,中等,147 -1649,1600-1699,1649. 通过指令创建有序数组,通过指令创建有序数组,https://leetcode.cn/problems/create-sorted-array-through-instructions/,create-sorted-array-through-instructions,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/1600-1699/create-sorted-array-through-instructions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1649.%20%E9%80%9A%E8%BF%87%E6%8C%87%E4%BB%A4%E5%88%9B%E5%BB%BA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md,49.3%,困难,98 -1650,1600-1699,1650. 二叉树的最近公共祖先 III,二叉树的最近公共祖先 III,https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree-iii/,lowest-common-ancestor-of-a-binary-tree-iii,树、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/lowest-common-ancestor-of-a-binary-tree-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1650.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%20III.md,80.0%,中等,66 -1651,1600-1699,1651. Hopper 公司查询 III,Hopper 公司查询 III,https://leetcode.cn/problems/hopper-company-queries-iii/,hopper-company-queries-iii,数据库,https://algo.itcharge.cn/Solutions/1600-1699/hopper-company-queries-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1651.%20Hopper%20%E5%85%AC%E5%8F%B8%E6%9F%A5%E8%AF%A2%20III.md,64.0%,困难,37 -1652,1600-1699,1652. 拆炸弹,拆炸弹,https://leetcode.cn/problems/defuse-the-bomb/,defuse-the-bomb,数组,https://algo.itcharge.cn/Solutions/1600-1699/defuse-the-bomb/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1652.%20%E6%8B%86%E7%82%B8%E5%BC%B9.md,66.4%,简单,608 -1653,1600-1699,1653. 使字符串平衡的最少删除次数,使字符串平衡的最少删除次数,https://leetcode.cn/problems/minimum-deletions-to-make-string-balanced/,minimum-deletions-to-make-string-balanced,栈、字符串、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/minimum-deletions-to-make-string-balanced/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1653.%20%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B9%B3%E8%A1%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%A0%E9%99%A4%E6%AC%A1%E6%95%B0.md,61.0%,中等,355 -1654,1600-1699,1654. 到家的最少跳跃次数,到家的最少跳跃次数,https://leetcode.cn/problems/minimum-jumps-to-reach-home/,minimum-jumps-to-reach-home,广度优先搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/minimum-jumps-to-reach-home/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1654.%20%E5%88%B0%E5%AE%B6%E7%9A%84%E6%9C%80%E5%B0%91%E8%B7%B3%E8%B7%83%E6%AC%A1%E6%95%B0.md,31.2%,中等,116 -1655,1600-1699,1655. 分配重复整数,分配重复整数,https://leetcode.cn/problems/distribute-repeating-integers/,distribute-repeating-integers,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1600-1699/distribute-repeating-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1655.%20%E5%88%86%E9%85%8D%E9%87%8D%E5%A4%8D%E6%95%B4%E6%95%B0.md,39.5%,困难,62 -1656,1600-1699,1656. 设计有序流,设计有序流,https://leetcode.cn/problems/design-an-ordered-stream/,design-an-ordered-stream,设计、数组、哈希表、数据流,https://algo.itcharge.cn/Solutions/1600-1699/design-an-ordered-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1656.%20%E8%AE%BE%E8%AE%A1%E6%9C%89%E5%BA%8F%E6%B5%81.md,84.1%,简单,368 -1657,1600-1699,1657. 确定两个字符串是否接近,确定两个字符串是否接近,https://leetcode.cn/problems/determine-if-two-strings-are-close/,determine-if-two-strings-are-close,哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1600-1699/determine-if-two-strings-are-close/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1657.%20%E7%A1%AE%E5%AE%9A%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E6%8E%A5%E8%BF%91.md,47.1%,中等,165 -1658,1600-1699,1658. 将 x 减到 0 的最小操作数,将 x 减到 0 的最小操作数,https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/,minimum-operations-to-reduce-x-to-zero,数组、哈希表、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/1600-1699/minimum-operations-to-reduce-x-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1658.%20%E5%B0%86%20x%20%E5%87%8F%E5%88%B0%200%20%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md,39.2%,中等,440 -1659,1600-1699,1659. 最大化网格幸福感,最大化网格幸福感,https://leetcode.cn/problems/maximize-grid-happiness/,maximize-grid-happiness,位运算、记忆化搜索、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1600-1699/maximize-grid-happiness/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1659.%20%E6%9C%80%E5%A4%A7%E5%8C%96%E7%BD%91%E6%A0%BC%E5%B9%B8%E7%A6%8F%E6%84%9F.md,64.7%,困难,67 -1660,1600-1699,1660. 纠正二叉树,纠正二叉树,https://leetcode.cn/problems/correct-a-binary-tree/,correct-a-binary-tree,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/correct-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1660.%20%E7%BA%A0%E6%AD%A3%E4%BA%8C%E5%8F%89%E6%A0%91.md,76.2%,中等,36 -1661,1600-1699,1661. 每台机器的进程平均运行时间,每台机器的进程平均运行时间,https://leetcode.cn/problems/average-time-of-process-per-machine/,average-time-of-process-per-machine,数据库,https://algo.itcharge.cn/Solutions/1600-1699/average-time-of-process-per-machine/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1661.%20%E6%AF%8F%E5%8F%B0%E6%9C%BA%E5%99%A8%E7%9A%84%E8%BF%9B%E7%A8%8B%E5%B9%B3%E5%9D%87%E8%BF%90%E8%A1%8C%E6%97%B6%E9%97%B4.md,72.6%,简单,131 -1662,1600-1699,1662. 检查两个字符串数组是否相等,检查两个字符串数组是否相等,https://leetcode.cn/problems/check-if-two-string-arrays-are-equivalent/,check-if-two-string-arrays-are-equivalent,数组、字符串,https://algo.itcharge.cn/Solutions/1600-1699/check-if-two-string-arrays-are-equivalent/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1662.%20%E6%A3%80%E6%9F%A5%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%BB%84%E6%98%AF%E5%90%A6%E7%9B%B8%E7%AD%89.md,81.0%,简单,710 -1663,1600-1699,1663. 具有给定数值的最小字符串,具有给定数值的最小字符串,https://leetcode.cn/problems/smallest-string-with-a-given-numeric-value/,smallest-string-with-a-given-numeric-value,贪心、字符串,https://algo.itcharge.cn/Solutions/1600-1699/smallest-string-with-a-given-numeric-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1663.%20%E5%85%B7%E6%9C%89%E7%BB%99%E5%AE%9A%E6%95%B0%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E7%AC%A6%E4%B8%B2.md,65.1%,中等,349 -1664,1600-1699,1664. 生成平衡数组的方案数,生成平衡数组的方案数,https://leetcode.cn/problems/ways-to-make-a-fair-array/,ways-to-make-a-fair-array,数组、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/ways-to-make-a-fair-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1664.%20%E7%94%9F%E6%88%90%E5%B9%B3%E8%A1%A1%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,66.5%,中等,399 -1665,1600-1699,1665. 完成所有任务的最少初始能量,完成所有任务的最少初始能量,https://leetcode.cn/problems/minimum-initial-energy-to-finish-tasks/,minimum-initial-energy-to-finish-tasks,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1600-1699/minimum-initial-energy-to-finish-tasks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1665.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%9D%E5%A7%8B%E8%83%BD%E9%87%8F.md,65.4%,困难,124 -1666,1600-1699,1666. 改变二叉树的根节点,改变二叉树的根节点,https://leetcode.cn/problems/change-the-root-of-a-binary-tree/,change-the-root-of-a-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/change-the-root-of-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1666.%20%E6%94%B9%E5%8F%98%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%A0%B9%E8%8A%82%E7%82%B9.md,66.8%,中等,24 -1667,1600-1699,1667. 修复表中的名字,修复表中的名字,https://leetcode.cn/problems/fix-names-in-a-table/,fix-names-in-a-table,数据库,https://algo.itcharge.cn/Solutions/1600-1699/fix-names-in-a-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1667.%20%E4%BF%AE%E5%A4%8D%E8%A1%A8%E4%B8%AD%E7%9A%84%E5%90%8D%E5%AD%97.md,63.6%,简单,282 -1668,1600-1699,1668. 最大重复子字符串,最大重复子字符串,https://leetcode.cn/problems/maximum-repeating-substring/,maximum-repeating-substring,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/1600-1699/maximum-repeating-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1668.%20%E6%9C%80%E5%A4%A7%E9%87%8D%E5%A4%8D%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,47.0%,简单,588 -1669,1600-1699,1669. 合并两个链表,合并两个链表,https://leetcode.cn/problems/merge-in-between-linked-lists/,merge-in-between-linked-lists,链表,https://algo.itcharge.cn/Solutions/1600-1699/merge-in-between-linked-lists/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1669.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8.md,77.3%,中等,626 -1670,1600-1699,1670. 设计前中后队列,设计前中后队列,https://leetcode.cn/problems/design-front-middle-back-queue/,design-front-middle-back-queue,设计、队列、数组、链表、数据流,https://algo.itcharge.cn/Solutions/1600-1699/design-front-middle-back-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1670.%20%E8%AE%BE%E8%AE%A1%E5%89%8D%E4%B8%AD%E5%90%8E%E9%98%9F%E5%88%97.md,52.0%,中等,145 -1671,1600-1699,1671. 得到山形数组的最少删除次数,得到山形数组的最少删除次数,https://leetcode.cn/problems/minimum-number-of-removals-to-make-mountain-array/,minimum-number-of-removals-to-make-mountain-array,贪心、数组、二分查找、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/minimum-number-of-removals-to-make-mountain-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1671.%20%E5%BE%97%E5%88%B0%E5%B1%B1%E5%BD%A2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%A0%E9%99%A4%E6%AC%A1%E6%95%B0.md,46.7%,困难,75 -1672,1600-1699,1672. 最富有客户的资产总量,最富有客户的资产总量,https://leetcode.cn/problems/richest-customer-wealth/,richest-customer-wealth,数组、矩阵,https://algo.itcharge.cn/Solutions/1600-1699/richest-customer-wealth/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1672.%20%E6%9C%80%E5%AF%8C%E6%9C%89%E5%AE%A2%E6%88%B7%E7%9A%84%E8%B5%84%E4%BA%A7%E6%80%BB%E9%87%8F.md,83.7%,简单,1370 -1673,1600-1699,1673. 找出最具竞争力的子序列,找出最具竞争力的子序列,https://leetcode.cn/problems/find-the-most-competitive-subsequence/,find-the-most-competitive-subsequence,栈、贪心、数组、单调栈,https://algo.itcharge.cn/Solutions/1600-1699/find-the-most-competitive-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1673.%20%E6%89%BE%E5%87%BA%E6%9C%80%E5%85%B7%E7%AB%9E%E4%BA%89%E5%8A%9B%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md,39.5%,中等,226 -1674,1600-1699,1674. 使数组互补的最少操作次数,使数组互补的最少操作次数,https://leetcode.cn/problems/minimum-moves-to-make-array-complementary/,minimum-moves-to-make-array-complementary,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/1600-1699/minimum-moves-to-make-array-complementary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1674.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%BA%92%E8%A1%A5%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,41.9%,中等,81 -1675,1600-1699,1675. 数组的最小偏移量,数组的最小偏移量,https://leetcode.cn/problems/minimize-deviation-in-array/,minimize-deviation-in-array,贪心、数组、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/minimize-deviation-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1675.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%81%8F%E7%A7%BB%E9%87%8F.md,45.6%,困难,71 -1676,1600-1699,1676. 二叉树的最近公共祖先 IV,二叉树的最近公共祖先 IV,https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree-iv/,lowest-common-ancestor-of-a-binary-tree-iv,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/1600-1699/lowest-common-ancestor-of-a-binary-tree-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1676.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%20IV.md,79.9%,中等,53 -1677,1600-1699,1677. 发票中的产品金额,发票中的产品金额,https://leetcode.cn/problems/products-worth-over-invoices/,products-worth-over-invoices,数据库,https://algo.itcharge.cn/Solutions/1600-1699/products-worth-over-invoices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1677.%20%E5%8F%91%E7%A5%A8%E4%B8%AD%E7%9A%84%E4%BA%A7%E5%93%81%E9%87%91%E9%A2%9D.md,35.3%,简单,54 -1678,1600-1699,1678. 设计 Goal 解析器,设计 Goal 解析器,https://leetcode.cn/problems/goal-parser-interpretation/,goal-parser-interpretation,字符串,https://algo.itcharge.cn/Solutions/1600-1699/goal-parser-interpretation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1678.%20%E8%AE%BE%E8%AE%A1%20Goal%20%E8%A7%A3%E6%9E%90%E5%99%A8.md,86.1%,简单,766 -1679,1600-1699,1679. K 和数对的最大数目,K 和数对的最大数目,https://leetcode.cn/problems/max-number-of-k-sum-pairs/,max-number-of-k-sum-pairs,数组、哈希表、双指针、排序,https://algo.itcharge.cn/Solutions/1600-1699/max-number-of-k-sum-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1679.%20K%20%E5%92%8C%E6%95%B0%E5%AF%B9%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,54.0%,中等,220 -1680,1600-1699,1680. 连接连续二进制数字,连接连续二进制数字,https://leetcode.cn/problems/concatenation-of-consecutive-binary-numbers/,concatenation-of-consecutive-binary-numbers,位运算、数学、模拟,https://algo.itcharge.cn/Solutions/1600-1699/concatenation-of-consecutive-binary-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1680.%20%E8%BF%9E%E6%8E%A5%E8%BF%9E%E7%BB%AD%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E5%AD%97.md,49.9%,中等,92 -1681,1600-1699,1681. 最小不兼容性,最小不兼容性,https://leetcode.cn/problems/minimum-incompatibility/,minimum-incompatibility,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1600-1699/minimum-incompatibility/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1681.%20%E6%9C%80%E5%B0%8F%E4%B8%8D%E5%85%BC%E5%AE%B9%E6%80%A7.md,54.9%,困难,99 -1682,1600-1699,1682. 最长回文子序列 II,最长回文子序列 II,https://leetcode.cn/problems/longest-palindromic-subsequence-ii/,longest-palindromic-subsequence-ii,字符串、动态规划,https://algo.itcharge.cn/Solutions/1600-1699/longest-palindromic-subsequence-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1682.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97%20II.md,57.2%,中等,42 -1683,1600-1699,1683. 无效的推文,无效的推文,https://leetcode.cn/problems/invalid-tweets/,invalid-tweets,数据库,https://algo.itcharge.cn/Solutions/1600-1699/invalid-tweets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1683.%20%E6%97%A0%E6%95%88%E7%9A%84%E6%8E%A8%E6%96%87.md,87.4%,简单,72 -1684,1600-1699,1684. 统计一致字符串的数目,统计一致字符串的数目,https://leetcode.cn/problems/count-the-number-of-consistent-strings/,count-the-number-of-consistent-strings,位运算、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1600-1699/count-the-number-of-consistent-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1684.%20%E7%BB%9F%E8%AE%A1%E4%B8%80%E8%87%B4%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE.md,85.2%,简单,840 -1685,1600-1699,1685. 有序数组中差绝对值之和,有序数组中差绝对值之和,https://leetcode.cn/problems/sum-of-absolute-differences-in-a-sorted-array/,sum-of-absolute-differences-in-a-sorted-array,数组、数学、前缀和,https://algo.itcharge.cn/Solutions/1600-1699/sum-of-absolute-differences-in-a-sorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1685.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E5%B7%AE%E7%BB%9D%E5%AF%B9%E5%80%BC%E4%B9%8B%E5%92%8C.md,64.9%,中等,171 -1686,1600-1699,1686. 石子游戏 VI,石子游戏 VI,https://leetcode.cn/problems/stone-game-vi/,stone-game-vi,贪心、数组、数学、博弈、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/stone-game-vi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1686.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20VI.md,50.1%,中等,79 -1687,1600-1699,1687. 从仓库到码头运输箱子,从仓库到码头运输箱子,https://leetcode.cn/problems/delivering-boxes-from-storage-to-ports/,delivering-boxes-from-storage-to-ports,线段树、队列、数组、动态规划、前缀和、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/delivering-boxes-from-storage-to-ports/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1687.%20%E4%BB%8E%E4%BB%93%E5%BA%93%E5%88%B0%E7%A0%81%E5%A4%B4%E8%BF%90%E8%BE%93%E7%AE%B1%E5%AD%90.md,58.8%,困难,99 -1688,1600-1699,1688. 比赛中的配对次数,比赛中的配对次数,https://leetcode.cn/problems/count-of-matches-in-tournament/,count-of-matches-in-tournament,数学、模拟,https://algo.itcharge.cn/Solutions/1600-1699/count-of-matches-in-tournament/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1688.%20%E6%AF%94%E8%B5%9B%E4%B8%AD%E7%9A%84%E9%85%8D%E5%AF%B9%E6%AC%A1%E6%95%B0.md,83.8%,简单,643 -1689,1600-1699,1689. 十-二进制数的最少数目,十-二进制数的最少数目,https://leetcode.cn/problems/partitioning-into-minimum-number-of-deci-binary-numbers/,partitioning-into-minimum-number-of-deci-binary-numbers,贪心、字符串,https://algo.itcharge.cn/Solutions/1600-1699/partitioning-into-minimum-number-of-deci-binary-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1689.%20%E5%8D%81-%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE.md,86.6%,中等,298 -1690,1600-1699,1690. 石子游戏 VII,石子游戏 VII,https://leetcode.cn/problems/stone-game-vii/,stone-game-vii,数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1600-1699/stone-game-vii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1690.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20VII.md,55.7%,中等,135 -1691,1600-1699,1691. 堆叠长方体的最大高度,堆叠长方体的最大高度,https://leetcode.cn/problems/maximum-height-by-stacking-cuboids/,maximum-height-by-stacking-cuboids,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/1600-1699/maximum-height-by-stacking-cuboids/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1691.%20%E5%A0%86%E5%8F%A0%E9%95%BF%E6%96%B9%E4%BD%93%E7%9A%84%E6%9C%80%E5%A4%A7%E9%AB%98%E5%BA%A6.md,65.0%,困难,176 -1692,1600-1699,1692. 计算分配糖果的不同方式,计算分配糖果的不同方式,https://leetcode.cn/problems/count-ways-to-distribute-candies/,count-ways-to-distribute-candies,动态规划,https://algo.itcharge.cn/Solutions/1600-1699/count-ways-to-distribute-candies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1692.%20%E8%AE%A1%E7%AE%97%E5%88%86%E9%85%8D%E7%B3%96%E6%9E%9C%E7%9A%84%E4%B8%8D%E5%90%8C%E6%96%B9%E5%BC%8F.md,66.1%,困难,25 -1693,1600-1699,1693. 每天的领导和合伙人,每天的领导和合伙人,https://leetcode.cn/problems/daily-leads-and-partners/,daily-leads-and-partners,数据库,https://algo.itcharge.cn/Solutions/1600-1699/daily-leads-and-partners/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1693.%20%E6%AF%8F%E5%A4%A9%E7%9A%84%E9%A2%86%E5%AF%BC%E5%92%8C%E5%90%88%E4%BC%99%E4%BA%BA.md,82.0%,简单,168 -1694,1600-1699,1694. 重新格式化电话号码,重新格式化电话号码,https://leetcode.cn/problems/reformat-phone-number/,reformat-phone-number,字符串,https://algo.itcharge.cn/Solutions/1600-1699/reformat-phone-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1694.%20%E9%87%8D%E6%96%B0%E6%A0%BC%E5%BC%8F%E5%8C%96%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81.md,66.1%,简单,566 -1695,1600-1699,1695. 删除子数组的最大得分,删除子数组的最大得分,https://leetcode.cn/problems/maximum-erasure-value/,maximum-erasure-value,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/1600-1699/maximum-erasure-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1695.%20%E5%88%A0%E9%99%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,51.9%,中等,228 -1696,1600-1699,1696. 跳跃游戏 VI,跳跃游戏 VI,https://leetcode.cn/problems/jump-game-vi/,jump-game-vi,队列、数组、动态规划、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/1600-1699/jump-game-vi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1696.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20VI.md,40.2%,中等,193 -1697,1600-1699,1697. 检查边长度限制的路径是否存在,检查边长度限制的路径是否存在,https://leetcode.cn/problems/checking-existence-of-edge-length-limited-paths/,checking-existence-of-edge-length-limited-paths,并查集、图、数组、排序,https://algo.itcharge.cn/Solutions/1600-1699/checking-existence-of-edge-length-limited-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1697.%20%E6%A3%80%E6%9F%A5%E8%BE%B9%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E7%9A%84%E8%B7%AF%E5%BE%84%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8.md,64.9%,困难,163 -1698,1600-1699,1698. 字符串的不同子字符串个数,字符串的不同子字符串个数,https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/,number-of-distinct-substrings-in-a-string,字典树、字符串、后缀数组、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1600-1699/number-of-distinct-substrings-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1698.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%8D%E5%90%8C%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AA%E6%95%B0.md,55.2%,中等,28 -1699,1600-1699,1699. 两人之间的通话次数,两人之间的通话次数,https://leetcode.cn/problems/number-of-calls-between-two-persons/,number-of-calls-between-two-persons,数据库,https://algo.itcharge.cn/Solutions/1600-1699/number-of-calls-between-two-persons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1699.%20%E4%B8%A4%E4%BA%BA%E4%B9%8B%E9%97%B4%E7%9A%84%E9%80%9A%E8%AF%9D%E6%AC%A1%E6%95%B0.md,76.4%,中等,124 -1700,1700-1799,1700. 无法吃午餐的学生数量,无法吃午餐的学生数量,https://leetcode.cn/problems/number-of-students-unable-to-eat-lunch/,number-of-students-unable-to-eat-lunch,栈、队列、数组、模拟,https://algo.itcharge.cn/Solutions/1700-1799/number-of-students-unable-to-eat-lunch/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1700.%20%E6%97%A0%E6%B3%95%E5%90%83%E5%8D%88%E9%A4%90%E7%9A%84%E5%AD%A6%E7%94%9F%E6%95%B0%E9%87%8F.md,73.2%,简单,764 -1701,1700-1799,1701. 平均等待时间,平均等待时间,https://leetcode.cn/problems/average-waiting-time/,average-waiting-time,数组、模拟,https://algo.itcharge.cn/Solutions/1700-1799/average-waiting-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1701.%20%E5%B9%B3%E5%9D%87%E7%AD%89%E5%BE%85%E6%97%B6%E9%97%B4.md,60.5%,中等,145 -1702,1700-1799,1702. 修改后的最大二进制字符串,修改后的最大二进制字符串,https://leetcode.cn/problems/maximum-binary-string-after-change/,maximum-binary-string-after-change,贪心、字符串,https://algo.itcharge.cn/Solutions/1700-1799/maximum-binary-string-after-change/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1702.%20%E4%BF%AE%E6%94%B9%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.md,48.3%,中等,99 -1703,1700-1799,1703. 得到连续 K 个 1 的最少相邻交换次数,得到连续 K 个 1 的最少相邻交换次数,https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/,minimum-adjacent-swaps-for-k-consecutive-ones,贪心、数组、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/1700-1799/minimum-adjacent-swaps-for-k-consecutive-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1703.%20%E5%BE%97%E5%88%B0%E8%BF%9E%E7%BB%AD%20K%20%E4%B8%AA%201%20%E7%9A%84%E6%9C%80%E5%B0%91%E7%9B%B8%E9%82%BB%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,56.6%,困难,87 -1704,1700-1799,1704. 判断字符串的两半是否相似,判断字符串的两半是否相似,https://leetcode.cn/problems/determine-if-string-halves-are-alike/,determine-if-string-halves-are-alike,字符串、计数,https://algo.itcharge.cn/Solutions/1700-1799/determine-if-string-halves-are-alike/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1704.%20%E5%88%A4%E6%96%AD%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%A4%E5%8D%8A%E6%98%AF%E5%90%A6%E7%9B%B8%E4%BC%BC.md,78.4%,简单,569 -1705,1700-1799,1705. 吃苹果的最大数目,吃苹果的最大数目,https://leetcode.cn/problems/maximum-number-of-eaten-apples/,maximum-number-of-eaten-apples,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/maximum-number-of-eaten-apples/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1705.%20%E5%90%83%E8%8B%B9%E6%9E%9C%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,45.4%,中等,394 -1706,1700-1799,1706. 球会落何处,球会落何处,https://leetcode.cn/problems/where-will-the-ball-fall/,where-will-the-ball-fall,深度优先搜索、数组、动态规划、矩阵、模拟,https://algo.itcharge.cn/Solutions/1700-1799/where-will-the-ball-fall/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1706.%20%E7%90%83%E4%BC%9A%E8%90%BD%E4%BD%95%E5%A4%84.md,69.1%,中等,692 -1707,1700-1799,1707. 与数组中元素的最大异或值,与数组中元素的最大异或值,https://leetcode.cn/problems/maximum-xor-with-an-element-from-array/,maximum-xor-with-an-element-from-array,位运算、字典树、数组,https://algo.itcharge.cn/Solutions/1700-1799/maximum-xor-with-an-element-from-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1707.%20%E4%B8%8E%E6%95%B0%E7%BB%84%E4%B8%AD%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md,50.9%,困难,176 -1708,1700-1799,1708. 长度为 K 的最大子数组,长度为 K 的最大子数组,https://leetcode.cn/problems/largest-subarray-length-k/,largest-subarray-length-k,贪心、数组,https://algo.itcharge.cn/Solutions/1700-1799/largest-subarray-length-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1708.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md,66.9%,简单,52 -1709,1700-1799,1709. 访问日期之间最大的空档期,访问日期之间最大的空档期,https://leetcode.cn/problems/biggest-window-between-visits/,biggest-window-between-visits,数据库,https://algo.itcharge.cn/Solutions/1700-1799/biggest-window-between-visits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1709.%20%E8%AE%BF%E9%97%AE%E6%97%A5%E6%9C%9F%E4%B9%8B%E9%97%B4%E6%9C%80%E5%A4%A7%E7%9A%84%E7%A9%BA%E6%A1%A3%E6%9C%9F.md,69.0%,中等,82 -1710,1700-1799,1710. 卡车上的最大单元数,卡车上的最大单元数,https://leetcode.cn/problems/maximum-units-on-a-truck/,maximum-units-on-a-truck,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1700-1799/maximum-units-on-a-truck/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1710.%20%E5%8D%A1%E8%BD%A6%E4%B8%8A%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%95%E5%85%83%E6%95%B0.md,73.4%,简单,583 -1711,1700-1799,1711. 大餐计数,大餐计数,https://leetcode.cn/problems/count-good-meals/,count-good-meals,数组、哈希表,https://algo.itcharge.cn/Solutions/1700-1799/count-good-meals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1711.%20%E5%A4%A7%E9%A4%90%E8%AE%A1%E6%95%B0.md,36.0%,中等,389 -1712,1700-1799,1712. 将数组分成三个子数组的方案数,将数组分成三个子数组的方案数,https://leetcode.cn/problems/ways-to-split-array-into-three-subarrays/,ways-to-split-array-into-three-subarrays,数组、双指针、二分查找、前缀和,https://algo.itcharge.cn/Solutions/1700-1799/ways-to-split-array-into-three-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1712.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%86%E6%88%90%E4%B8%89%E4%B8%AA%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,28.5%,中等,175 -1713,1700-1799,1713. 得到子序列的最少操作次数,得到子序列的最少操作次数,https://leetcode.cn/problems/minimum-operations-to-make-a-subsequence/,minimum-operations-to-make-a-subsequence,贪心、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/1700-1799/minimum-operations-to-make-a-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1713.%20%E5%BE%97%E5%88%B0%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,49.9%,困难,198 -1714,1700-1799,1714. 数组中特殊等间距元素的和,数组中特殊等间距元素的和,https://leetcode.cn/problems/sum-of-special-evenly-spaced-elements-in-array/,sum-of-special-evenly-spaced-elements-in-array,数组、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/sum-of-special-evenly-spaced-elements-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1714.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%89%B9%E6%AE%8A%E7%AD%89%E9%97%B4%E8%B7%9D%E5%85%83%E7%B4%A0%E7%9A%84%E5%92%8C.md,59.6%,困难,14 -1715,1700-1799,1715. 苹果和橘子的个数,苹果和橘子的个数,https://leetcode.cn/problems/count-apples-and-oranges/,count-apples-and-oranges,数据库,https://algo.itcharge.cn/Solutions/1700-1799/count-apples-and-oranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1715.%20%E8%8B%B9%E6%9E%9C%E5%92%8C%E6%A9%98%E5%AD%90%E7%9A%84%E4%B8%AA%E6%95%B0.md,71.7%,中等,57 -1716,1700-1799,1716. 计算力扣银行的钱,计算力扣银行的钱,https://leetcode.cn/problems/calculate-money-in-leetcode-bank/,calculate-money-in-leetcode-bank,数学,https://algo.itcharge.cn/Solutions/1700-1799/calculate-money-in-leetcode-bank/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1716.%20%E8%AE%A1%E7%AE%97%E5%8A%9B%E6%89%A3%E9%93%B6%E8%A1%8C%E7%9A%84%E9%92%B1.md,69.1%,简单,748 -1717,1700-1799,1717. 删除子字符串的最大得分,删除子字符串的最大得分,https://leetcode.cn/problems/maximum-score-from-removing-substrings/,maximum-score-from-removing-substrings,栈、贪心、字符串,https://algo.itcharge.cn/Solutions/1700-1799/maximum-score-from-removing-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1717.%20%E5%88%A0%E9%99%A4%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,46.1%,中等,85 -1718,1700-1799,1718. 构建字典序最大的可行序列,构建字典序最大的可行序列,https://leetcode.cn/problems/construct-the-lexicographically-largest-valid-sequence/,construct-the-lexicographically-largest-valid-sequence,数组、回溯,https://algo.itcharge.cn/Solutions/1700-1799/construct-the-lexicographically-largest-valid-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1718.%20%E6%9E%84%E5%BB%BA%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%A4%A7%E7%9A%84%E5%8F%AF%E8%A1%8C%E5%BA%8F%E5%88%97.md,50.9%,中等,79 -1719,1700-1799,1719. 重构一棵树的方案数,重构一棵树的方案数,https://leetcode.cn/problems/number-of-ways-to-reconstruct-a-tree/,number-of-ways-to-reconstruct-a-tree,树、图,https://algo.itcharge.cn/Solutions/1700-1799/number-of-ways-to-reconstruct-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1719.%20%E9%87%8D%E6%9E%84%E4%B8%80%E6%A3%B5%E6%A0%91%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,69.0%,困难,93 -1720,1700-1799,1720. 解码异或后的数组,解码异或后的数组,https://leetcode.cn/problems/decode-xored-array/,decode-xored-array,位运算、数组,https://algo.itcharge.cn/Solutions/1700-1799/decode-xored-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1720.%20%E8%A7%A3%E7%A0%81%E5%BC%82%E6%88%96%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84.md,86.0%,简单,598 -1721,1700-1799,1721. 交换链表中的节点,交换链表中的节点,https://leetcode.cn/problems/swapping-nodes-in-a-linked-list/,swapping-nodes-in-a-linked-list,链表、双指针,https://algo.itcharge.cn/Solutions/1700-1799/swapping-nodes-in-a-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1721.%20%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md,63.8%,中等,301 -1722,1700-1799,1722. 执行交换操作后的最小汉明距离,执行交换操作后的最小汉明距离,https://leetcode.cn/problems/minimize-hamming-distance-after-swap-operations/,minimize-hamming-distance-after-swap-operations,深度优先搜索、并查集、数组,https://algo.itcharge.cn/Solutions/1700-1799/minimize-hamming-distance-after-swap-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1722.%20%E6%89%A7%E8%A1%8C%E4%BA%A4%E6%8D%A2%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB.md,51.0%,中等,149 -1723,1700-1799,1723. 完成所有工作的最短时间,完成所有工作的最短时间,https://leetcode.cn/problems/find-minimum-time-to-finish-all-jobs/,find-minimum-time-to-finish-all-jobs,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1700-1799/find-minimum-time-to-finish-all-jobs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1723.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E5%B7%A5%E4%BD%9C%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,50.9%,困难,223 -1724,1700-1799,1724. 检查边长度限制的路径是否存在 II,检查边长度限制的路径是否存在 II,https://leetcode.cn/problems/checking-existence-of-edge-length-limited-paths-ii/,checking-existence-of-edge-length-limited-paths-ii,并查集、图、最小生成树,https://algo.itcharge.cn/Solutions/1700-1799/checking-existence-of-edge-length-limited-paths-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1724.%20%E6%A3%80%E6%9F%A5%E8%BE%B9%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E7%9A%84%E8%B7%AF%E5%BE%84%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%20II.md,58.5%,困难,22 -1725,1700-1799,1725. 可以形成最大正方形的矩形数目,可以形成最大正方形的矩形数目,https://leetcode.cn/problems/number-of-rectangles-that-can-form-the-largest-square/,number-of-rectangles-that-can-form-the-largest-square,数组,https://algo.itcharge.cn/Solutions/1700-1799/number-of-rectangles-that-can-form-the-largest-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1725.%20%E5%8F%AF%E4%BB%A5%E5%BD%A2%E6%88%90%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2%E7%9A%84%E7%9F%A9%E5%BD%A2%E6%95%B0%E7%9B%AE.md,82.8%,简单,485 -1726,1700-1799,1726. 同积元组,同积元组,https://leetcode.cn/problems/tuple-with-same-product/,tuple-with-same-product,数组、哈希表,https://algo.itcharge.cn/Solutions/1700-1799/tuple-with-same-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1726.%20%E5%90%8C%E7%A7%AF%E5%85%83%E7%BB%84.md,52.2%,中等,143 -1727,1700-1799,1727. 重新排列后的最大子矩阵,重新排列后的最大子矩阵,https://leetcode.cn/problems/largest-submatrix-with-rearrangements/,largest-submatrix-with-rearrangements,贪心、数组、矩阵、排序,https://algo.itcharge.cn/Solutions/1700-1799/largest-submatrix-with-rearrangements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1727.%20%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%90%E7%9F%A9%E9%98%B5.md,59.2%,中等,94 -1728,1700-1799,1728. 猫和老鼠 II,猫和老鼠 II,https://leetcode.cn/problems/cat-and-mouse-ii/,cat-and-mouse-ii,图、拓扑排序、记忆化搜索、数组、数学、动态规划、博弈、矩阵,https://algo.itcharge.cn/Solutions/1700-1799/cat-and-mouse-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1728.%20%E7%8C%AB%E5%92%8C%E8%80%81%E9%BC%A0%20II.md,64.1%,困难,83 -1729,1700-1799,1729. 求关注者的数量,求关注者的数量,https://leetcode.cn/problems/find-followers-count/,find-followers-count,数据库,https://algo.itcharge.cn/Solutions/1700-1799/find-followers-count/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1729.%20%E6%B1%82%E5%85%B3%E6%B3%A8%E8%80%85%E7%9A%84%E6%95%B0%E9%87%8F.md,61.6%,简单,138 -1730,1700-1799,1730. 获取食物的最短路径,获取食物的最短路径,https://leetcode.cn/problems/shortest-path-to-get-food/,shortest-path-to-get-food,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1700-1799/shortest-path-to-get-food/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1730.%20%E8%8E%B7%E5%8F%96%E9%A3%9F%E7%89%A9%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,52.7%,中等,41 -1731,1700-1799,1731. 每位经理的下属员工数量,每位经理的下属员工数量,https://leetcode.cn/problems/the-number-of-employees-which-report-to-each-employee/,the-number-of-employees-which-report-to-each-employee,数据库,https://algo.itcharge.cn/Solutions/1700-1799/the-number-of-employees-which-report-to-each-employee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1731.%20%E6%AF%8F%E4%BD%8D%E7%BB%8F%E7%90%86%E7%9A%84%E4%B8%8B%E5%B1%9E%E5%91%98%E5%B7%A5%E6%95%B0%E9%87%8F.md,47.0%,简单,103 -1732,1700-1799,1732. 找到最高海拔,找到最高海拔,https://leetcode.cn/problems/find-the-highest-altitude/,find-the-highest-altitude,数组、前缀和,https://algo.itcharge.cn/Solutions/1700-1799/find-the-highest-altitude/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1732.%20%E6%89%BE%E5%88%B0%E6%9C%80%E9%AB%98%E6%B5%B7%E6%8B%94.md,81.3%,简单,556 -1733,1700-1799,1733. 需要教语言的最少人数,需要教语言的最少人数,https://leetcode.cn/problems/minimum-number-of-people-to-teach/,minimum-number-of-people-to-teach,贪心、数组,https://algo.itcharge.cn/Solutions/1700-1799/minimum-number-of-people-to-teach/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1733.%20%E9%9C%80%E8%A6%81%E6%95%99%E8%AF%AD%E8%A8%80%E7%9A%84%E6%9C%80%E5%B0%91%E4%BA%BA%E6%95%B0.md,48.5%,中等,76 -1734,1700-1799,1734. 解码异或后的排列,解码异或后的排列,https://leetcode.cn/problems/decode-xored-permutation/,decode-xored-permutation,位运算、数组,https://algo.itcharge.cn/Solutions/1700-1799/decode-xored-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1734.%20%E8%A7%A3%E7%A0%81%E5%BC%82%E6%88%96%E5%90%8E%E7%9A%84%E6%8E%92%E5%88%97.md,72.4%,中等,385 -1735,1700-1799,1735. 生成乘积数组的方案数,生成乘积数组的方案数,https://leetcode.cn/problems/count-ways-to-make-array-with-product/,count-ways-to-make-array-with-product,数组、数学、动态规划、组合数学、数论,https://algo.itcharge.cn/Solutions/1700-1799/count-ways-to-make-array-with-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1735.%20%E7%94%9F%E6%88%90%E4%B9%98%E7%A7%AF%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,52.2%,困难,50 -1736,1700-1799,1736. 替换隐藏数字得到的最晚时间,替换隐藏数字得到的最晚时间,https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/,latest-time-by-replacing-hidden-digits,贪心、字符串,https://algo.itcharge.cn/Solutions/1700-1799/latest-time-by-replacing-hidden-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1736.%20%E6%9B%BF%E6%8D%A2%E9%9A%90%E8%97%8F%E6%95%B0%E5%AD%97%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E6%99%9A%E6%97%B6%E9%97%B4.md,44.5%,简单,421 -1737,1700-1799,1737. 满足三条件之一需改变的最少字符数,满足三条件之一需改变的最少字符数,https://leetcode.cn/problems/change-minimum-characters-to-satisfy-one-of-three-conditions/,change-minimum-characters-to-satisfy-one-of-three-conditions,哈希表、字符串、计数、前缀和,https://algo.itcharge.cn/Solutions/1700-1799/change-minimum-characters-to-satisfy-one-of-three-conditions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1737.%20%E6%BB%A1%E8%B6%B3%E4%B8%89%E6%9D%A1%E4%BB%B6%E4%B9%8B%E4%B8%80%E9%9C%80%E6%94%B9%E5%8F%98%E7%9A%84%E6%9C%80%E5%B0%91%E5%AD%97%E7%AC%A6%E6%95%B0.md,35.8%,中等,130 -1738,1700-1799,1738. 找出第 K 大的异或坐标值,找出第 K 大的异或坐标值,https://leetcode.cn/problems/find-kth-largest-xor-coordinate-value/,find-kth-largest-xor-coordinate-value,位运算、数组、分治、矩阵、前缀和、快速选择、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/find-kth-largest-xor-coordinate-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1738.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%A4%A7%E7%9A%84%E5%BC%82%E6%88%96%E5%9D%90%E6%A0%87%E5%80%BC.md,65.1%,中等,431 -1739,1700-1799,1739. 放置盒子,放置盒子,https://leetcode.cn/problems/building-boxes/,building-boxes,贪心、数学、二分查找,https://algo.itcharge.cn/Solutions/1700-1799/building-boxes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1739.%20%E6%94%BE%E7%BD%AE%E7%9B%92%E5%AD%90.md,64.4%,困难,133 -1740,1700-1799,1740. 找到二叉树中的距离,找到二叉树中的距离,https://leetcode.cn/problems/find-distance-in-a-binary-tree/,find-distance-in-a-binary-tree,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/1700-1799/find-distance-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1740.%20%E6%89%BE%E5%88%B0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E8%B7%9D%E7%A6%BB.md,67.1%,中等,55 -1741,1700-1799,1741. 查找每个员工花费的总时间,查找每个员工花费的总时间,https://leetcode.cn/problems/find-total-time-spent-by-each-employee/,find-total-time-spent-by-each-employee,数据库,https://algo.itcharge.cn/Solutions/1700-1799/find-total-time-spent-by-each-employee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1741.%20%E6%9F%A5%E6%89%BE%E6%AF%8F%E4%B8%AA%E5%91%98%E5%B7%A5%E8%8A%B1%E8%B4%B9%E7%9A%84%E6%80%BB%E6%97%B6%E9%97%B4.md,84.6%,简单,177 -1742,1700-1799,1742. 盒子中小球的最大数量,盒子中小球的最大数量,https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/,maximum-number-of-balls-in-a-box,哈希表、数学、计数,https://algo.itcharge.cn/Solutions/1700-1799/maximum-number-of-balls-in-a-box/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1742.%20%E7%9B%92%E5%AD%90%E4%B8%AD%E5%B0%8F%E7%90%83%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,75.8%,简单,390 -1743,1700-1799,1743. 从相邻元素对还原数组,从相邻元素对还原数组,https://leetcode.cn/problems/restore-the-array-from-adjacent-pairs/,restore-the-array-from-adjacent-pairs,数组、哈希表,https://algo.itcharge.cn/Solutions/1700-1799/restore-the-array-from-adjacent-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1743.%20%E4%BB%8E%E7%9B%B8%E9%82%BB%E5%85%83%E7%B4%A0%E5%AF%B9%E8%BF%98%E5%8E%9F%E6%95%B0%E7%BB%84.md,69.6%,中等,357 -1744,1700-1799,1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?,你能在你最喜欢的那天吃到你最喜欢的糖果吗?,https://leetcode.cn/problems/can-you-eat-your-favorite-candy-on-your-favorite-day/,can-you-eat-your-favorite-candy-on-your-favorite-day,数组、前缀和,https://algo.itcharge.cn/Solutions/1700-1799/can-you-eat-your-favorite-candy-on-your-favorite-day/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1744.%20%E4%BD%A0%E8%83%BD%E5%9C%A8%E4%BD%A0%E6%9C%80%E5%96%9C%E6%AC%A2%E7%9A%84%E9%82%A3%E5%A4%A9%E5%90%83%E5%88%B0%E4%BD%A0%E6%9C%80%E5%96%9C%E6%AC%A2%E7%9A%84%E7%B3%96%E6%9E%9C%E5%90%97%EF%BC%9F.md,36.4%,中等,392 -1745,1700-1799,1745. 回文串分割 IV,回文串分割 IV,https://leetcode.cn/problems/palindrome-partitioning-iv/,palindrome-partitioning-iv,字符串、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/palindrome-partitioning-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1745.%20%E5%9B%9E%E6%96%87%E4%B8%B2%E5%88%86%E5%89%B2%20IV.md,50.6%,困难,108 -1746,1700-1799,1746. 经过一次操作后的最大子数组和,经过一次操作后的最大子数组和,https://leetcode.cn/problems/maximum-subarray-sum-after-one-operation/,maximum-subarray-sum-after-one-operation,数组、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/maximum-subarray-sum-after-one-operation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1746.%20%E7%BB%8F%E8%BF%87%E4%B8%80%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md,60.6%,中等,77 -1747,1700-1799,1747. 应该被禁止的 Leetflex 账户,应该被禁止的 Leetflex 账户,https://leetcode.cn/problems/leetflex-banned-accounts/,leetflex-banned-accounts,数据库,https://algo.itcharge.cn/Solutions/1700-1799/leetflex-banned-accounts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1747.%20%E5%BA%94%E8%AF%A5%E8%A2%AB%E7%A6%81%E6%AD%A2%E7%9A%84%20Leetflex%20%E8%B4%A6%E6%88%B7.md,65.5%,中等,87 -1748,1700-1799,1748. 唯一元素的和,唯一元素的和,https://leetcode.cn/problems/sum-of-unique-elements/,sum-of-unique-elements,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1700-1799/sum-of-unique-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1748.%20%E5%94%AF%E4%B8%80%E5%85%83%E7%B4%A0%E7%9A%84%E5%92%8C.md,76.8%,简单,645 -1749,1700-1799,1749. 任意子数组和的绝对值的最大值,任意子数组和的绝对值的最大值,https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/,maximum-absolute-sum-of-any-subarray,数组、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/maximum-absolute-sum-of-any-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1749.%20%E4%BB%BB%E6%84%8F%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E7%9A%84%E7%BB%9D%E5%AF%B9%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,54.4%,中等,147 -1750,1700-1799,1750. 删除字符串两端相同字符后的最短长度,删除字符串两端相同字符后的最短长度,https://leetcode.cn/problems/minimum-length-of-string-after-deleting-similar-ends/,minimum-length-of-string-after-deleting-similar-ends,双指针、字符串,https://algo.itcharge.cn/Solutions/1700-1799/minimum-length-of-string-after-deleting-similar-ends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1750.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%A4%E7%AB%AF%E7%9B%B8%E5%90%8C%E5%AD%97%E7%AC%A6%E5%90%8E%E7%9A%84%E6%9C%80%E7%9F%AD%E9%95%BF%E5%BA%A6.md,50.6%,中等,294 -1751,1700-1799,1751. 最多可以参加的会议数目 II,最多可以参加的会议数目 II,https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/,maximum-number-of-events-that-can-be-attended-ii,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/1700-1799/maximum-number-of-events-that-can-be-attended-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1751.%20%E6%9C%80%E5%A4%9A%E5%8F%AF%E4%BB%A5%E5%8F%82%E5%8A%A0%E7%9A%84%E4%BC%9A%E8%AE%AE%E6%95%B0%E7%9B%AE%20II.md,54.2%,困难,90 -1752,1700-1799,1752. 检查数组是否经排序和轮转得到,检查数组是否经排序和轮转得到,https://leetcode.cn/problems/check-if-array-is-sorted-and-rotated/,check-if-array-is-sorted-and-rotated,数组,https://algo.itcharge.cn/Solutions/1700-1799/check-if-array-is-sorted-and-rotated/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1752.%20%E6%A3%80%E6%9F%A5%E6%95%B0%E7%BB%84%E6%98%AF%E5%90%A6%E7%BB%8F%E6%8E%92%E5%BA%8F%E5%92%8C%E8%BD%AE%E8%BD%AC%E5%BE%97%E5%88%B0.md,58.1%,简单,409 -1753,1700-1799,1753. 移除石子的最大得分,移除石子的最大得分,https://leetcode.cn/problems/maximum-score-from-removing-stones/,maximum-score-from-removing-stones,贪心、数学、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/maximum-score-from-removing-stones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1753.%20%E7%A7%BB%E9%99%A4%E7%9F%B3%E5%AD%90%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,70.7%,中等,336 -1754,1700-1799,1754. 构造字典序最大的合并字符串,构造字典序最大的合并字符串,https://leetcode.cn/problems/largest-merge-of-two-strings/,largest-merge-of-two-strings,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/1700-1799/largest-merge-of-two-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1754.%20%E6%9E%84%E9%80%A0%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%A4%A7%E7%9A%84%E5%90%88%E5%B9%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.md,56.3%,中等,209 -1755,1700-1799,1755. 最接近目标值的子序列和,最接近目标值的子序列和,https://leetcode.cn/problems/closest-subsequence-sum/,closest-subsequence-sum,位运算、数组、双指针、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1700-1799/closest-subsequence-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1755.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%E5%92%8C.md,45.0%,困难,91 -1756,1700-1799,1756. 设计最近使用(MRU)队列,设计最近使用(MRU)队列,https://leetcode.cn/problems/design-most-recently-used-queue/,design-most-recently-used-queue,栈、设计、树状数组、数组、哈希表、有序集合,https://algo.itcharge.cn/Solutions/1700-1799/design-most-recently-used-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1756.%20%E8%AE%BE%E8%AE%A1%E6%9C%80%E8%BF%91%E4%BD%BF%E7%94%A8%EF%BC%88MRU%EF%BC%89%E9%98%9F%E5%88%97.md,82.0%,中等,34 -1757,1700-1799,1757. 可回收且低脂的产品,可回收且低脂的产品,https://leetcode.cn/problems/recyclable-and-low-fat-products/,recyclable-and-low-fat-products,数据库,https://algo.itcharge.cn/Solutions/1700-1799/recyclable-and-low-fat-products/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1757.%20%E5%8F%AF%E5%9B%9E%E6%94%B6%E4%B8%94%E4%BD%8E%E8%84%82%E7%9A%84%E4%BA%A7%E5%93%81.md,87.7%,简单,279 -1758,1700-1799,1758. 生成交替二进制字符串的最少操作数,生成交替二进制字符串的最少操作数,https://leetcode.cn/problems/minimum-changes-to-make-alternating-binary-string/,minimum-changes-to-make-alternating-binary-string,字符串,https://algo.itcharge.cn/Solutions/1700-1799/minimum-changes-to-make-alternating-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1758.%20%E7%94%9F%E6%88%90%E4%BA%A4%E6%9B%BF%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,69.7%,简单,451 -1759,1700-1799,1759. 统计同质子字符串的数目,统计同质子字符串的数目,https://leetcode.cn/problems/count-number-of-homogenous-substrings/,count-number-of-homogenous-substrings,数学、字符串,https://algo.itcharge.cn/Solutions/1700-1799/count-number-of-homogenous-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1759.%20%E7%BB%9F%E8%AE%A1%E5%90%8C%E8%B4%A8%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE.md,51.1%,中等,272 -1760,1700-1799,1760. 袋子里最少数目的球,袋子里最少数目的球,https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag/,minimum-limit-of-balls-in-a-bag,数组、二分查找,https://algo.itcharge.cn/Solutions/1700-1799/minimum-limit-of-balls-in-a-bag/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1760.%20%E8%A2%8B%E5%AD%90%E9%87%8C%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE%E7%9A%84%E7%90%83.md,64.1%,中等,280 -1761,1700-1799,1761. 一个图中连通三元组的最小度数,一个图中连通三元组的最小度数,https://leetcode.cn/problems/minimum-degree-of-a-connected-trio-in-a-graph/,minimum-degree-of-a-connected-trio-in-a-graph,图,https://algo.itcharge.cn/Solutions/1700-1799/minimum-degree-of-a-connected-trio-in-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1761.%20%E4%B8%80%E4%B8%AA%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%BA%A6%E6%95%B0.md,46.1%,困难,52 -1762,1700-1799,1762. 能看到海景的建筑物,能看到海景的建筑物,https://leetcode.cn/problems/buildings-with-an-ocean-view/,buildings-with-an-ocean-view,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/1700-1799/buildings-with-an-ocean-view/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1762.%20%E8%83%BD%E7%9C%8B%E5%88%B0%E6%B5%B7%E6%99%AF%E7%9A%84%E5%BB%BA%E7%AD%91%E7%89%A9.md,71.4%,中等,47 -1763,1700-1799,1763. 最长的美好子字符串,最长的美好子字符串,https://leetcode.cn/problems/longest-nice-substring/,longest-nice-substring,位运算、哈希表、字符串、分治、滑动窗口,https://algo.itcharge.cn/Solutions/1700-1799/longest-nice-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1763.%20%E6%9C%80%E9%95%BF%E7%9A%84%E7%BE%8E%E5%A5%BD%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,67.2%,简单,307 -1764,1700-1799,1764. 通过连接另一个数组的子数组得到一个数组,通过连接另一个数组的子数组得到一个数组,https://leetcode.cn/problems/form-array-by-concatenating-subarrays-of-another-array/,form-array-by-concatenating-subarrays-of-another-array,贪心、数组、字符串匹配,https://algo.itcharge.cn/Solutions/1700-1799/form-array-by-concatenating-subarrays-of-another-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1764.%20%E9%80%9A%E8%BF%87%E8%BF%9E%E6%8E%A5%E5%8F%A6%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E5%BE%97%E5%88%B0%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84.md,55.7%,中等,258 -1765,1700-1799,1765. 地图中的最高点,地图中的最高点,https://leetcode.cn/problems/map-of-highest-peak/,map-of-highest-peak,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1700-1799/map-of-highest-peak/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1765.%20%E5%9C%B0%E5%9B%BE%E4%B8%AD%E7%9A%84%E6%9C%80%E9%AB%98%E7%82%B9.md,66.4%,中等,328 -1766,1700-1799,1766. 互质树,互质树,https://leetcode.cn/problems/tree-of-coprimes/,tree-of-coprimes,树、深度优先搜索、广度优先搜索、数学,https://algo.itcharge.cn/Solutions/1700-1799/tree-of-coprimes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1766.%20%E4%BA%92%E8%B4%A8%E6%A0%91.md,40.2%,困难,47 -1767,1700-1799,1767. 寻找没有被执行的任务对,寻找没有被执行的任务对,https://leetcode.cn/problems/find-the-subtasks-that-did-not-execute/,find-the-subtasks-that-did-not-execute,数据库,https://algo.itcharge.cn/Solutions/1700-1799/find-the-subtasks-that-did-not-execute/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1767.%20%E5%AF%BB%E6%89%BE%E6%B2%A1%E6%9C%89%E8%A2%AB%E6%89%A7%E8%A1%8C%E7%9A%84%E4%BB%BB%E5%8A%A1%E5%AF%B9.md,80.2%,困难,61 -1768,1700-1799,1768. 交替合并字符串,交替合并字符串,https://leetcode.cn/problems/merge-strings-alternately/,merge-strings-alternately,双指针、字符串,https://algo.itcharge.cn/Solutions/1700-1799/merge-strings-alternately/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1768.%20%E4%BA%A4%E6%9B%BF%E5%90%88%E5%B9%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.md,76.6%,简单,824 -1769,1700-1799,1769. 移动所有球到每个盒子所需的最小操作数,移动所有球到每个盒子所需的最小操作数,https://leetcode.cn/problems/minimum-number-of-operations-to-move-all-balls-to-each-box/,minimum-number-of-operations-to-move-all-balls-to-each-box,数组、字符串,https://algo.itcharge.cn/Solutions/1700-1799/minimum-number-of-operations-to-move-all-balls-to-each-box/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1769.%20%E7%A7%BB%E5%8A%A8%E6%89%80%E6%9C%89%E7%90%83%E5%88%B0%E6%AF%8F%E4%B8%AA%E7%9B%92%E5%AD%90%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md,87.9%,中等,562 -1770,1700-1799,1770. 执行乘法运算的最大分数,执行乘法运算的最大分数,https://leetcode.cn/problems/maximum-score-from-performing-multiplication-operations/,maximum-score-from-performing-multiplication-operations,数组、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/maximum-score-from-performing-multiplication-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1770.%20%E6%89%A7%E8%A1%8C%E4%B9%98%E6%B3%95%E8%BF%90%E7%AE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0.md,39.6%,困难,131 -1771,1700-1799,1771. 由子序列构造的最长回文串的长度,由子序列构造的最长回文串的长度,https://leetcode.cn/problems/maximize-palindrome-length-from-subsequences/,maximize-palindrome-length-from-subsequences,字符串、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/maximize-palindrome-length-from-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1771.%20%E7%94%B1%E5%AD%90%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E7%9A%84%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E4%B8%B2%E7%9A%84%E9%95%BF%E5%BA%A6.md,38.4%,困难,64 -1772,1700-1799,1772. 按受欢迎程度排列功能,按受欢迎程度排列功能,https://leetcode.cn/problems/sort-features-by-popularity/,sort-features-by-popularity,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/1700-1799/sort-features-by-popularity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1772.%20%E6%8C%89%E5%8F%97%E6%AC%A2%E8%BF%8E%E7%A8%8B%E5%BA%A6%E6%8E%92%E5%88%97%E5%8A%9F%E8%83%BD.md,47.2%,中等,21 -1773,1700-1799,1773. 统计匹配检索规则的物品数量,统计匹配检索规则的物品数量,https://leetcode.cn/problems/count-items-matching-a-rule/,count-items-matching-a-rule,数组、字符串,https://algo.itcharge.cn/Solutions/1700-1799/count-items-matching-a-rule/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1773.%20%E7%BB%9F%E8%AE%A1%E5%8C%B9%E9%85%8D%E6%A3%80%E7%B4%A2%E8%A7%84%E5%88%99%E7%9A%84%E7%89%A9%E5%93%81%E6%95%B0%E9%87%8F.md,86.5%,简单,507 -1774,1700-1799,1774. 最接近目标价格的甜点成本,最接近目标价格的甜点成本,https://leetcode.cn/problems/closest-dessert-cost/,closest-dessert-cost,数组、动态规划、回溯,https://algo.itcharge.cn/Solutions/1700-1799/closest-dessert-cost/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1774.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E4%BB%B7%E6%A0%BC%E7%9A%84%E7%94%9C%E7%82%B9%E6%88%90%E6%9C%AC.md,57.5%,中等,304 -1775,1700-1799,1775. 通过最少操作次数使数组的和相等,通过最少操作次数使数组的和相等,https://leetcode.cn/problems/equal-sum-arrays-with-minimum-number-of-operations/,equal-sum-arrays-with-minimum-number-of-operations,贪心、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1700-1799/equal-sum-arrays-with-minimum-number-of-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1775.%20%E9%80%9A%E8%BF%87%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0%E4%BD%BF%E6%95%B0%E7%BB%84%E7%9A%84%E5%92%8C%E7%9B%B8%E7%AD%89.md,56.4%,中等,361 -1776,1700-1799,1776. 车队 II,车队 II,https://leetcode.cn/problems/car-fleet-ii/,car-fleet-ii,栈、数组、数学、单调栈、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/car-fleet-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1776.%20%E8%BD%A6%E9%98%9F%20II.md,48.5%,困难,65 -1777,1700-1799,1777. 每家商店的产品价格,每家商店的产品价格,https://leetcode.cn/problems/products-price-for-each-store/,products-price-for-each-store,数据库,https://algo.itcharge.cn/Solutions/1700-1799/products-price-for-each-store/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1777.%20%E6%AF%8F%E5%AE%B6%E5%95%86%E5%BA%97%E7%9A%84%E4%BA%A7%E5%93%81%E4%BB%B7%E6%A0%BC.md,78.3%,简单,85 -1778,1700-1799,1778. 未知网格中的最短路径,未知网格中的最短路径,https://leetcode.cn/problems/shortest-path-in-a-hidden-grid/,shortest-path-in-a-hidden-grid,深度优先搜索、广度优先搜索、图、交互,https://algo.itcharge.cn/Solutions/1700-1799/shortest-path-in-a-hidden-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1778.%20%E6%9C%AA%E7%9F%A5%E7%BD%91%E6%A0%BC%E4%B8%AD%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md,48.1%,中等,16 -1779,1700-1799,1779. 找到最近的有相同 X 或 Y 坐标的点,找到最近的有相同 X 或 Y 坐标的点,https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/,find-nearest-point-that-has-the-same-x-or-y-coordinate,数组,https://algo.itcharge.cn/Solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1779.%20%E6%89%BE%E5%88%B0%E6%9C%80%E8%BF%91%E7%9A%84%E6%9C%89%E7%9B%B8%E5%90%8C%20X%20%E6%88%96%20Y%20%E5%9D%90%E6%A0%87%E7%9A%84%E7%82%B9.md,69.0%,简单,547 -1780,1700-1799,1780. 判断一个数字是否可以表示成三的幂的和,判断一个数字是否可以表示成三的幂的和,https://leetcode.cn/problems/check-if-number-is-a-sum-of-powers-of-three/,check-if-number-is-a-sum-of-powers-of-three,数学,https://algo.itcharge.cn/Solutions/1700-1799/check-if-number-is-a-sum-of-powers-of-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1780.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E8%A1%A8%E7%A4%BA%E6%88%90%E4%B8%89%E7%9A%84%E5%B9%82%E7%9A%84%E5%92%8C.md,74.8%,中等,503 -1781,1700-1799,1781. 所有子字符串美丽值之和,所有子字符串美丽值之和,https://leetcode.cn/problems/sum-of-beauty-of-all-substrings/,sum-of-beauty-of-all-substrings,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1700-1799/sum-of-beauty-of-all-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1781.%20%E6%89%80%E6%9C%89%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%BE%8E%E4%B8%BD%E5%80%BC%E4%B9%8B%E5%92%8C.md,66.5%,中等,226 -1782,1700-1799,1782. 统计点对的数目,统计点对的数目,https://leetcode.cn/problems/count-pairs-of-nodes/,count-pairs-of-nodes,图、双指针、二分查找,https://algo.itcharge.cn/Solutions/1700-1799/count-pairs-of-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1782.%20%E7%BB%9F%E8%AE%A1%E7%82%B9%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,35.7%,困难,52 -1783,1700-1799,1783. 大满贯数量,大满贯数量,https://leetcode.cn/problems/grand-slam-titles/,grand-slam-titles,数据库,https://algo.itcharge.cn/Solutions/1700-1799/grand-slam-titles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1783.%20%E5%A4%A7%E6%BB%A1%E8%B4%AF%E6%95%B0%E9%87%8F.md,80.5%,中等,86 -1784,1700-1799,1784. 检查二进制字符串字段,检查二进制字符串字段,https://leetcode.cn/problems/check-if-binary-string-has-at-most-one-segment-of-ones/,check-if-binary-string-has-at-most-one-segment-of-ones,字符串,https://algo.itcharge.cn/Solutions/1700-1799/check-if-binary-string-has-at-most-one-segment-of-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1784.%20%E6%A3%80%E6%9F%A5%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AD%97%E6%AE%B5.md,59.5%,简单,541 -1785,1700-1799,1785. 构成特定和需要添加的最少元素,构成特定和需要添加的最少元素,https://leetcode.cn/problems/minimum-elements-to-add-to-form-a-given-sum/,minimum-elements-to-add-to-form-a-given-sum,贪心、数组,https://algo.itcharge.cn/Solutions/1700-1799/minimum-elements-to-add-to-form-a-given-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1785.%20%E6%9E%84%E6%88%90%E7%89%B9%E5%AE%9A%E5%92%8C%E9%9C%80%E8%A6%81%E6%B7%BB%E5%8A%A0%E7%9A%84%E6%9C%80%E5%B0%91%E5%85%83%E7%B4%A0.md,43.5%,中等,292 -1786,1700-1799,1786. 从第一个节点出发到最后一个节点的受限路径数,从第一个节点出发到最后一个节点的受限路径数,https://leetcode.cn/problems/number-of-restricted-paths-from-first-to-last-node/,number-of-restricted-paths-from-first-to-last-node,图、拓扑排序、动态规划、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/number-of-restricted-paths-from-first-to-last-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1786.%20%E4%BB%8E%E7%AC%AC%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%E5%87%BA%E5%8F%91%E5%88%B0%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E5%8F%97%E9%99%90%E8%B7%AF%E5%BE%84%E6%95%B0.md,36.4%,中等,133 -1787,1700-1799,1787. 使所有区间的异或结果为零,使所有区间的异或结果为零,https://leetcode.cn/problems/make-the-xor-of-all-segments-equal-to-zero/,make-the-xor-of-all-segments-equal-to-zero,位运算、数组、动态规划,https://algo.itcharge.cn/Solutions/1700-1799/make-the-xor-of-all-segments-equal-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1787.%20%E4%BD%BF%E6%89%80%E6%9C%89%E5%8C%BA%E9%97%B4%E7%9A%84%E5%BC%82%E6%88%96%E7%BB%93%E6%9E%9C%E4%B8%BA%E9%9B%B6.md,64.3%,困难,66 -1788,1700-1799,1788. 最大化花园的美观度,最大化花园的美观度,https://leetcode.cn/problems/maximize-the-beauty-of-the-garden/,maximize-the-beauty-of-the-garden,贪心、数组、前缀和,https://algo.itcharge.cn/Solutions/1700-1799/maximize-the-beauty-of-the-garden/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1788.%20%E6%9C%80%E5%A4%A7%E5%8C%96%E8%8A%B1%E5%9B%AD%E7%9A%84%E7%BE%8E%E8%A7%82%E5%BA%A6.md,65.5%,困难,32 -1789,1700-1799,1789. 员工的直属部门,员工的直属部门,https://leetcode.cn/problems/primary-department-for-each-employee/,primary-department-for-each-employee,数据库,https://algo.itcharge.cn/Solutions/1700-1799/primary-department-for-each-employee/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1789.%20%E5%91%98%E5%B7%A5%E7%9A%84%E7%9B%B4%E5%B1%9E%E9%83%A8%E9%97%A8.md,71.7%,简单,81 -1790,1700-1799,1790. 仅执行一次字符串交换能否使两个字符串相等,仅执行一次字符串交换能否使两个字符串相等,https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/,check-if-one-string-swap-can-make-strings-equal,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1790.%20%E4%BB%85%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%A4%E6%8D%A2%E8%83%BD%E5%90%A6%E4%BD%BF%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md,52.5%,简单,868 -1791,1700-1799,1791. 找出星型图的中心节点,找出星型图的中心节点,https://leetcode.cn/problems/find-center-of-star-graph/,find-center-of-star-graph,图,https://algo.itcharge.cn/Solutions/1700-1799/find-center-of-star-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1791.%20%E6%89%BE%E5%87%BA%E6%98%9F%E5%9E%8B%E5%9B%BE%E7%9A%84%E4%B8%AD%E5%BF%83%E8%8A%82%E7%82%B9.md,83.1%,简单,507 -1792,1700-1799,1792. 最大平均通过率,最大平均通过率,https://leetcode.cn/problems/maximum-average-pass-ratio/,maximum-average-pass-ratio,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1700-1799/maximum-average-pass-ratio/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1792.%20%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E9%80%9A%E8%BF%87%E7%8E%87.md,58.8%,中等,255 -1793,1700-1799,1793. 好子数组的最大分数,好子数组的最大分数,https://leetcode.cn/problems/maximum-score-of-a-good-subarray/,maximum-score-of-a-good-subarray,栈、数组、双指针、二分查找、单调栈,https://algo.itcharge.cn/Solutions/1700-1799/maximum-score-of-a-good-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1793.%20%E5%A5%BD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0.md,45.5%,困难,129 -1794,1700-1799,1794. 统计距离最小的子串对个数,统计距离最小的子串对个数,https://leetcode.cn/problems/count-pairs-of-equal-substrings-with-minimum-difference/,count-pairs-of-equal-substrings-with-minimum-difference,贪心、哈希表、字符串,https://algo.itcharge.cn/Solutions/1700-1799/count-pairs-of-equal-substrings-with-minimum-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1794.%20%E7%BB%9F%E8%AE%A1%E8%B7%9D%E7%A6%BB%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E4%B8%B2%E5%AF%B9%E4%B8%AA%E6%95%B0.md,55.3%,中等,13 -1795,1700-1799,1795. 每个产品在不同商店的价格,每个产品在不同商店的价格,https://leetcode.cn/problems/rearrange-products-table/,rearrange-products-table,数据库,https://algo.itcharge.cn/Solutions/1700-1799/rearrange-products-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1795.%20%E6%AF%8F%E4%B8%AA%E4%BA%A7%E5%93%81%E5%9C%A8%E4%B8%8D%E5%90%8C%E5%95%86%E5%BA%97%E7%9A%84%E4%BB%B7%E6%A0%BC.md,78.2%,简单,178 -1796,1700-1799,1796. 字符串中第二大的数字,字符串中第二大的数字,https://leetcode.cn/problems/second-largest-digit-in-a-string/,second-largest-digit-in-a-string,哈希表、字符串,https://algo.itcharge.cn/Solutions/1700-1799/second-largest-digit-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1796.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%AC%AC%E4%BA%8C%E5%A4%A7%E7%9A%84%E6%95%B0%E5%AD%97.md,54.9%,简单,456 -1797,1700-1799,1797. 设计一个验证系统,设计一个验证系统,https://leetcode.cn/problems/design-authentication-manager/,design-authentication-manager,设计、哈希表,https://algo.itcharge.cn/Solutions/1700-1799/design-authentication-manager/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1797.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E9%AA%8C%E8%AF%81%E7%B3%BB%E7%BB%9F.md,65.0%,中等,347 -1798,1700-1799,1798. 你能构造出连续值的最大数目,你能构造出连续值的最大数目,https://leetcode.cn/problems/maximum-number-of-consecutive-values-you-can-make/,maximum-number-of-consecutive-values-you-can-make,贪心、数组,https://algo.itcharge.cn/Solutions/1700-1799/maximum-number-of-consecutive-values-you-can-make/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1798.%20%E4%BD%A0%E8%83%BD%E6%9E%84%E9%80%A0%E5%87%BA%E8%BF%9E%E7%BB%AD%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,71.0%,中等,242 -1799,1700-1799,1799. N 次操作后的最大分数和,N 次操作后的最大分数和,https://leetcode.cn/problems/maximize-score-after-n-operations/,maximize-score-after-n-operations,位运算、数组、数学、动态规划、回溯、状态压缩、数论,https://algo.itcharge.cn/Solutions/1700-1799/maximize-score-after-n-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1799.%20N%20%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0%E5%92%8C.md,65.7%,困难,164 -1800,1800-1899,1800. 最大升序子数组和,最大升序子数组和,https://leetcode.cn/problems/maximum-ascending-subarray-sum/,maximum-ascending-subarray-sum,数组,https://algo.itcharge.cn/Solutions/1800-1899/maximum-ascending-subarray-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1800.%20%E6%9C%80%E5%A4%A7%E5%8D%87%E5%BA%8F%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md,68.7%,简单,640 -1801,1800-1899,1801. 积压订单中的订单总数,积压订单中的订单总数,https://leetcode.cn/problems/number-of-orders-in-the-backlog/,number-of-orders-in-the-backlog,数组、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/number-of-orders-in-the-backlog/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1801.%20%E7%A7%AF%E5%8E%8B%E8%AE%A2%E5%8D%95%E4%B8%AD%E7%9A%84%E8%AE%A2%E5%8D%95%E6%80%BB%E6%95%B0.md,53.2%,中等,299 -1802,1800-1899,1802. 有界数组中指定下标处的最大值,有界数组中指定下标处的最大值,https://leetcode.cn/problems/maximum-value-at-a-given-index-in-a-bounded-array/,maximum-value-at-a-given-index-in-a-bounded-array,贪心、二分查找,https://algo.itcharge.cn/Solutions/1800-1899/maximum-value-at-a-given-index-in-a-bounded-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1802.%20%E6%9C%89%E7%95%8C%E6%95%B0%E7%BB%84%E4%B8%AD%E6%8C%87%E5%AE%9A%E4%B8%8B%E6%A0%87%E5%A4%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,38.1%,中等,382 -1803,1800-1899,1803. 统计异或值在范围内的数对有多少,统计异或值在范围内的数对有多少,https://leetcode.cn/problems/count-pairs-with-xor-in-a-range/,count-pairs-with-xor-in-a-range,位运算、字典树、数组,https://algo.itcharge.cn/Solutions/1800-1899/count-pairs-with-xor-in-a-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1803.%20%E7%BB%9F%E8%AE%A1%E5%BC%82%E6%88%96%E5%80%BC%E5%9C%A8%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E6%95%B0%E5%AF%B9%E6%9C%89%E5%A4%9A%E5%B0%91.md,56.3%,困难,156 -1804,1800-1899,1804. 实现 Trie (前缀树) II,实现 Trie (前缀树) II,https://leetcode.cn/problems/implement-trie-ii-prefix-tree/,implement-trie-ii-prefix-tree,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/1800-1899/implement-trie-ii-prefix-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1804.%20%E5%AE%9E%E7%8E%B0%20Trie%20%EF%BC%88%E5%89%8D%E7%BC%80%E6%A0%91%EF%BC%89%20II.md,57.1%,中等,60 -1805,1800-1899,1805. 字符串中不同整数的数目,字符串中不同整数的数目,https://leetcode.cn/problems/number-of-different-integers-in-a-string/,number-of-different-integers-in-a-string,哈希表、字符串,https://algo.itcharge.cn/Solutions/1800-1899/number-of-different-integers-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1805.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E6%95%B0%E7%9B%AE.md,43.2%,简单,552 -1806,1800-1899,1806. 还原排列的最少操作步数,还原排列的最少操作步数,https://leetcode.cn/problems/minimum-number-of-operations-to-reinitialize-a-permutation/,minimum-number-of-operations-to-reinitialize-a-permutation,数组、数学、模拟,https://algo.itcharge.cn/Solutions/1800-1899/minimum-number-of-operations-to-reinitialize-a-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1806.%20%E8%BF%98%E5%8E%9F%E6%8E%92%E5%88%97%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AD%A5%E6%95%B0.md,76.7%,中等,304 -1807,1800-1899,1807. 替换字符串中的括号内容,替换字符串中的括号内容,https://leetcode.cn/problems/evaluate-the-bracket-pairs-of-a-string/,evaluate-the-bracket-pairs-of-a-string,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/1800-1899/evaluate-the-bracket-pairs-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1807.%20%E6%9B%BF%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%86%85%E5%AE%B9.md,67.5%,中等,435 -1808,1800-1899,1808. 好因子的最大数目,好因子的最大数目,https://leetcode.cn/problems/maximize-number-of-nice-divisors/,maximize-number-of-nice-divisors,递归、数学,https://algo.itcharge.cn/Solutions/1800-1899/maximize-number-of-nice-divisors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1808.%20%E5%A5%BD%E5%9B%A0%E5%AD%90%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,28.6%,困难,61 -1809,1800-1899,1809. 没有广告的剧集,没有广告的剧集,https://leetcode.cn/problems/ad-free-sessions/,ad-free-sessions,数据库,https://algo.itcharge.cn/Solutions/1800-1899/ad-free-sessions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1809.%20%E6%B2%A1%E6%9C%89%E5%B9%BF%E5%91%8A%E7%9A%84%E5%89%A7%E9%9B%86.md,61.0%,简单,51 -1810,1800-1899,1810. 隐藏网格下的最小消耗路径,隐藏网格下的最小消耗路径,https://leetcode.cn/problems/minimum-path-cost-in-a-hidden-grid/,minimum-path-cost-in-a-hidden-grid,深度优先搜索、广度优先搜索、图、交互、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/minimum-path-cost-in-a-hidden-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1810.%20%E9%9A%90%E8%97%8F%E7%BD%91%E6%A0%BC%E4%B8%8B%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md,62.7%,中等,10 -1811,1800-1899,1811. 寻找面试候选人,寻找面试候选人,https://leetcode.cn/problems/find-interview-candidates/,find-interview-candidates,数据库,https://algo.itcharge.cn/Solutions/1800-1899/find-interview-candidates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1811.%20%E5%AF%BB%E6%89%BE%E9%9D%A2%E8%AF%95%E5%80%99%E9%80%89%E4%BA%BA.md,63.7%,中等,54 -1812,1800-1899,1812. 判断国际象棋棋盘中一个格子的颜色,判断国际象棋棋盘中一个格子的颜色,https://leetcode.cn/problems/determine-color-of-a-chessboard-square/,determine-color-of-a-chessboard-square,数学、字符串,https://algo.itcharge.cn/Solutions/1800-1899/determine-color-of-a-chessboard-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1812.%20%E5%88%A4%E6%96%AD%E5%9B%BD%E9%99%85%E8%B1%A1%E6%A3%8B%E6%A3%8B%E7%9B%98%E4%B8%AD%E4%B8%80%E4%B8%AA%E6%A0%BC%E5%AD%90%E7%9A%84%E9%A2%9C%E8%89%B2.md,81.8%,简单,700 -1813,1800-1899,1813. 句子相似性 III,句子相似性 III,https://leetcode.cn/problems/sentence-similarity-iii/,sentence-similarity-iii,数组、双指针、字符串,https://algo.itcharge.cn/Solutions/1800-1899/sentence-similarity-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1813.%20%E5%8F%A5%E5%AD%90%E7%9B%B8%E4%BC%BC%E6%80%A7%20III.md,41.6%,中等,333 -1814,1800-1899,1814. 统计一个数组中好对子的数目,统计一个数组中好对子的数目,https://leetcode.cn/problems/count-nice-pairs-in-an-array/,count-nice-pairs-in-an-array,数组、哈希表、数学、计数,https://algo.itcharge.cn/Solutions/1800-1899/count-nice-pairs-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1814.%20%E7%BB%9F%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%E4%B8%AD%E5%A5%BD%E5%AF%B9%E5%AD%90%E7%9A%84%E6%95%B0%E7%9B%AE.md,47.1%,中等,314 -1815,1800-1899,1815. 得到新鲜甜甜圈的最多组数,得到新鲜甜甜圈的最多组数,https://leetcode.cn/problems/maximum-number-of-groups-getting-fresh-donuts/,maximum-number-of-groups-getting-fresh-donuts,位运算、记忆化搜索、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1800-1899/maximum-number-of-groups-getting-fresh-donuts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1815.%20%E5%BE%97%E5%88%B0%E6%96%B0%E9%B2%9C%E7%94%9C%E7%94%9C%E5%9C%88%E7%9A%84%E6%9C%80%E5%A4%9A%E7%BB%84%E6%95%B0.md,53.4%,困难,93 -1816,1800-1899,1816. 截断句子,截断句子,https://leetcode.cn/problems/truncate-sentence/,truncate-sentence,数组、字符串,https://algo.itcharge.cn/Solutions/1800-1899/truncate-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1816.%20%E6%88%AA%E6%96%AD%E5%8F%A5%E5%AD%90.md,72.4%,简单,738 -1817,1800-1899,1817. 查找用户活跃分钟数,查找用户活跃分钟数,https://leetcode.cn/problems/finding-the-users-active-minutes/,finding-the-users-active-minutes,数组、哈希表,https://algo.itcharge.cn/Solutions/1800-1899/finding-the-users-active-minutes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1817.%20%E6%9F%A5%E6%89%BE%E7%94%A8%E6%88%B7%E6%B4%BB%E8%B7%83%E5%88%86%E9%92%9F%E6%95%B0.md,79.4%,中等,315 -1818,1800-1899,1818. 绝对差值和,绝对差值和,https://leetcode.cn/problems/minimum-absolute-sum-difference/,minimum-absolute-sum-difference,数组、二分查找、有序集合、排序,https://algo.itcharge.cn/Solutions/1800-1899/minimum-absolute-sum-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1818.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E5%80%BC%E5%92%8C.md,37.6%,中等,393 -1819,1800-1899,1819. 序列中不同最大公约数的数目,序列中不同最大公约数的数目,https://leetcode.cn/problems/number-of-different-subsequences-gcds/,number-of-different-subsequences-gcds,数组、数学、计数、数论,https://algo.itcharge.cn/Solutions/1800-1899/number-of-different-subsequences-gcds/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1819.%20%E5%BA%8F%E5%88%97%E4%B8%AD%E4%B8%8D%E5%90%8C%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0%E7%9A%84%E6%95%B0%E7%9B%AE.md,63.3%,困难,108 -1820,1800-1899,1820. 最多邀请的个数,最多邀请的个数,https://leetcode.cn/problems/maximum-number-of-accepted-invitations/,maximum-number-of-accepted-invitations,数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/1800-1899/maximum-number-of-accepted-invitations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1820.%20%E6%9C%80%E5%A4%9A%E9%82%80%E8%AF%B7%E7%9A%84%E4%B8%AA%E6%95%B0.md,47.7%,中等,17 -1821,1800-1899,1821. 寻找今年具有正收入的客户,寻找今年具有正收入的客户,https://leetcode.cn/problems/find-customers-with-positive-revenue-this-year/,find-customers-with-positive-revenue-this-year,数据库,https://algo.itcharge.cn/Solutions/1800-1899/find-customers-with-positive-revenue-this-year/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1821.%20%E5%AF%BB%E6%89%BE%E4%BB%8A%E5%B9%B4%E5%85%B7%E6%9C%89%E6%AD%A3%E6%94%B6%E5%85%A5%E7%9A%84%E5%AE%A2%E6%88%B7.md,88.5%,简单,45 -1822,1800-1899,1822. 数组元素积的符号,数组元素积的符号,https://leetcode.cn/problems/sign-of-the-product-of-an-array/,sign-of-the-product-of-an-array,数组、数学,https://algo.itcharge.cn/Solutions/1800-1899/sign-of-the-product-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1822.%20%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%A7%AF%E7%9A%84%E7%AC%A6%E5%8F%B7.md,71.7%,简单,732 -1823,1800-1899,1823. 找出游戏的获胜者,找出游戏的获胜者,https://leetcode.cn/problems/find-the-winner-of-the-circular-game/,find-the-winner-of-the-circular-game,递归、队列、数组、数学、模拟,https://algo.itcharge.cn/Solutions/1800-1899/find-the-winner-of-the-circular-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1823.%20%E6%89%BE%E5%87%BA%E6%B8%B8%E6%88%8F%E7%9A%84%E8%8E%B7%E8%83%9C%E8%80%85.md,78.5%,中等,578 -1824,1800-1899,1824. 最少侧跳次数,最少侧跳次数,https://leetcode.cn/problems/minimum-sideway-jumps/,minimum-sideway-jumps,贪心、数组、动态规划,https://algo.itcharge.cn/Solutions/1800-1899/minimum-sideway-jumps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1824.%20%E6%9C%80%E5%B0%91%E4%BE%A7%E8%B7%B3%E6%AC%A1%E6%95%B0.md,68.4%,中等,332 -1825,1800-1899,1825. 求出 MK 平均值,求出 MK 平均值,https://leetcode.cn/problems/finding-mk-average/,finding-mk-average,设计、队列、数据流、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/finding-mk-average/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1825.%20%E6%B1%82%E5%87%BA%20MK%20%E5%B9%B3%E5%9D%87%E5%80%BC.md,43.4%,困难,190 -1826,1800-1899,1826. 有缺陷的传感器,有缺陷的传感器,https://leetcode.cn/problems/faulty-sensor/,faulty-sensor,数组、双指针,https://algo.itcharge.cn/Solutions/1800-1899/faulty-sensor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1826.%20%E6%9C%89%E7%BC%BA%E9%99%B7%E7%9A%84%E4%BC%A0%E6%84%9F%E5%99%A8.md,41.7%,简单,40 -1827,1800-1899,1827. 最少操作使数组递增,最少操作使数组递增,https://leetcode.cn/problems/minimum-operations-to-make-the-array-increasing/,minimum-operations-to-make-the-array-increasing,贪心、数组,https://algo.itcharge.cn/Solutions/1800-1899/minimum-operations-to-make-the-array-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1827.%20%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E4%BD%BF%E6%95%B0%E7%BB%84%E9%80%92%E5%A2%9E.md,81.4%,简单,418 -1828,1800-1899,1828. 统计一个圆中点的数目,统计一个圆中点的数目,https://leetcode.cn/problems/queries-on-number-of-points-inside-a-circle/,queries-on-number-of-points-inside-a-circle,几何、数组、数学,https://algo.itcharge.cn/Solutions/1800-1899/queries-on-number-of-points-inside-a-circle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1828.%20%E7%BB%9F%E8%AE%A1%E4%B8%80%E4%B8%AA%E5%9C%86%E4%B8%AD%E7%82%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,88.5%,中等,323 -1829,1800-1899,1829. 每个查询的最大异或值,每个查询的最大异或值,https://leetcode.cn/problems/maximum-xor-for-each-query/,maximum-xor-for-each-query,位运算、数组、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/maximum-xor-for-each-query/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1829.%20%E6%AF%8F%E4%B8%AA%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md,72.1%,中等,118 -1830,1800-1899,1830. 使字符串有序的最少操作次数,使字符串有序的最少操作次数,https://leetcode.cn/problems/minimum-number-of-operations-to-make-string-sorted/,minimum-number-of-operations-to-make-string-sorted,数学、字符串、组合数学,https://algo.itcharge.cn/Solutions/1800-1899/minimum-number-of-operations-to-make-string-sorted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1830.%20%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9C%89%E5%BA%8F%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,53.1%,困难,31 -1831,1800-1899,1831. 每天的最大交易,每天的最大交易,https://leetcode.cn/problems/maximum-transaction-each-day/,maximum-transaction-each-day,数据库,https://algo.itcharge.cn/Solutions/1800-1899/maximum-transaction-each-day/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1831.%20%E6%AF%8F%E5%A4%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BA%A4%E6%98%93.md,78.1%,中等,67 -1832,1800-1899,1832. 判断句子是否为全字母句,判断句子是否为全字母句,https://leetcode.cn/problems/check-if-the-sentence-is-pangram/,check-if-the-sentence-is-pangram,哈希表、字符串,https://algo.itcharge.cn/Solutions/1800-1899/check-if-the-sentence-is-pangram/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1832.%20%E5%88%A4%E6%96%AD%E5%8F%A5%E5%AD%90%E6%98%AF%E5%90%A6%E4%B8%BA%E5%85%A8%E5%AD%97%E6%AF%8D%E5%8F%A5.md,84.8%,简单,612 -1833,1800-1899,1833. 雪糕的最大数量,雪糕的最大数量,https://leetcode.cn/problems/maximum-ice-cream-bars/,maximum-ice-cream-bars,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1800-1899/maximum-ice-cream-bars/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1833.%20%E9%9B%AA%E7%B3%95%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,68.1%,中等,466 -1834,1800-1899,1834. 单线程 CPU,单线程 CPU,https://leetcode.cn/problems/single-threaded-cpu/,single-threaded-cpu,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/single-threaded-cpu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1834.%20%E5%8D%95%E7%BA%BF%E7%A8%8B%20CPU.md,37.9%,中等,179 -1835,1800-1899,1835. 所有数对按位与结果的异或和,所有数对按位与结果的异或和,https://leetcode.cn/problems/find-xor-sum-of-all-pairs-bitwise-and/,find-xor-sum-of-all-pairs-bitwise-and,位运算、数组、数学,https://algo.itcharge.cn/Solutions/1800-1899/find-xor-sum-of-all-pairs-bitwise-and/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1835.%20%E6%89%80%E6%9C%89%E6%95%B0%E5%AF%B9%E6%8C%89%E4%BD%8D%E4%B8%8E%E7%BB%93%E6%9E%9C%E7%9A%84%E5%BC%82%E6%88%96%E5%92%8C.md,56.0%,困难,77 -1836,1800-1899,1836. 从未排序的链表中移除重复元素,从未排序的链表中移除重复元素,https://leetcode.cn/problems/remove-duplicates-from-an-unsorted-linked-list/,remove-duplicates-from-an-unsorted-linked-list,哈希表、链表,https://algo.itcharge.cn/Solutions/1800-1899/remove-duplicates-from-an-unsorted-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1836.%20%E4%BB%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%A7%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md,73.0%,中等,60 -1837,1800-1899,1837. K 进制表示下的各位数字总和,K 进制表示下的各位数字总和,https://leetcode.cn/problems/sum-of-digits-in-base-k/,sum-of-digits-in-base-k,数学,https://algo.itcharge.cn/Solutions/1800-1899/sum-of-digits-in-base-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1837.%20K%20%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E6%80%BB%E5%92%8C.md,79.3%,简单,173 -1838,1800-1899,1838. 最高频元素的频数,最高频元素的频数,https://leetcode.cn/problems/frequency-of-the-most-frequent-element/,frequency-of-the-most-frequent-element,贪心、数组、二分查找、前缀和、排序、滑动窗口,https://algo.itcharge.cn/Solutions/1800-1899/frequency-of-the-most-frequent-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1838.%20%E6%9C%80%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0%E7%9A%84%E9%A2%91%E6%95%B0.md,43.1%,中等,381 -1839,1800-1899,1839. 所有元音按顺序排布的最长子字符串,所有元音按顺序排布的最长子字符串,https://leetcode.cn/problems/longest-substring-of-all-vowels-in-order/,longest-substring-of-all-vowels-in-order,字符串、滑动窗口,https://algo.itcharge.cn/Solutions/1800-1899/longest-substring-of-all-vowels-in-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1839.%20%E6%89%80%E6%9C%89%E5%85%83%E9%9F%B3%E6%8C%89%E9%A1%BA%E5%BA%8F%E6%8E%92%E5%B8%83%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,49.1%,中等,208 -1840,1800-1899,1840. 最高建筑高度,最高建筑高度,https://leetcode.cn/problems/maximum-building-height/,maximum-building-height,数组、数学,https://algo.itcharge.cn/Solutions/1800-1899/maximum-building-height/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1840.%20%E6%9C%80%E9%AB%98%E5%BB%BA%E7%AD%91%E9%AB%98%E5%BA%A6.md,39.4%,困难,63 -1841,1800-1899,1841. 联赛信息统计,联赛信息统计,https://leetcode.cn/problems/league-statistics/,league-statistics,数据库,https://algo.itcharge.cn/Solutions/1800-1899/league-statistics/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1841.%20%E8%81%94%E8%B5%9B%E4%BF%A1%E6%81%AF%E7%BB%9F%E8%AE%A1.md,52.6%,中等,64 -1842,1800-1899,1842. 下个由相同数字构成的回文串,下个由相同数字构成的回文串,https://leetcode.cn/problems/next-palindrome-using-same-digits/,next-palindrome-using-same-digits,双指针、字符串,https://algo.itcharge.cn/Solutions/1800-1899/next-palindrome-using-same-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1842.%20%E4%B8%8B%E4%B8%AA%E7%94%B1%E7%9B%B8%E5%90%8C%E6%95%B0%E5%AD%97%E6%9E%84%E6%88%90%E7%9A%84%E5%9B%9E%E6%96%87%E4%B8%B2.md,55.7%,困难,19 -1843,1800-1899,1843. 可疑银行账户,可疑银行账户,https://leetcode.cn/problems/suspicious-bank-accounts/,suspicious-bank-accounts,数据库,https://algo.itcharge.cn/Solutions/1800-1899/suspicious-bank-accounts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1843.%20%E5%8F%AF%E7%96%91%E9%93%B6%E8%A1%8C%E8%B4%A6%E6%88%B7.md,43.8%,中等,46 -1844,1800-1899,1844. 将所有数字用字符替换,将所有数字用字符替换,https://leetcode.cn/problems/replace-all-digits-with-characters/,replace-all-digits-with-characters,字符串,https://algo.itcharge.cn/Solutions/1800-1899/replace-all-digits-with-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1844.%20%E5%B0%86%E6%89%80%E6%9C%89%E6%95%B0%E5%AD%97%E7%94%A8%E5%AD%97%E7%AC%A6%E6%9B%BF%E6%8D%A2.md,78.6%,简单,214 -1845,1800-1899,1845. 座位预约管理系统,座位预约管理系统,https://leetcode.cn/problems/seat-reservation-manager/,seat-reservation-manager,设计、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/seat-reservation-manager/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1845.%20%E5%BA%A7%E4%BD%8D%E9%A2%84%E7%BA%A6%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.md,48.1%,中等,166 -1846,1800-1899,1846. 减小和重新排列数组后的最大元素,减小和重新排列数组后的最大元素,https://leetcode.cn/problems/maximum-element-after-decreasing-and-rearranging/,maximum-element-after-decreasing-and-rearranging,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1800-1899/maximum-element-after-decreasing-and-rearranging/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1846.%20%E5%87%8F%E5%B0%8F%E5%92%8C%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E6%95%B0%E7%BB%84%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md,63.1%,中等,415 -1847,1800-1899,1847. 最近的房间,最近的房间,https://leetcode.cn/problems/closest-room/,closest-room,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/1800-1899/closest-room/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1847.%20%E6%9C%80%E8%BF%91%E7%9A%84%E6%88%BF%E9%97%B4.md,39.9%,困难,75 -1848,1800-1899,1848. 到目标元素的最小距离,到目标元素的最小距离,https://leetcode.cn/problems/minimum-distance-to-the-target-element/,minimum-distance-to-the-target-element,数组,https://algo.itcharge.cn/Solutions/1800-1899/minimum-distance-to-the-target-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1848.%20%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%9D%E7%A6%BB.md,66.2%,简单,183 -1849,1800-1899,1849. 将字符串拆分为递减的连续值,将字符串拆分为递减的连续值,https://leetcode.cn/problems/splitting-a-string-into-descending-consecutive-values/,splitting-a-string-into-descending-consecutive-values,字符串、回溯,https://algo.itcharge.cn/Solutions/1800-1899/splitting-a-string-into-descending-consecutive-values/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1849.%20%E5%B0%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%86%E5%88%86%E4%B8%BA%E9%80%92%E5%87%8F%E7%9A%84%E8%BF%9E%E7%BB%AD%E5%80%BC.md,33.2%,中等,138 -1850,1800-1899,1850. 邻位交换的最小次数,邻位交换的最小次数,https://leetcode.cn/problems/minimum-adjacent-swaps-to-reach-the-kth-smallest-number/,minimum-adjacent-swaps-to-reach-the-kth-smallest-number,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/1800-1899/minimum-adjacent-swaps-to-reach-the-kth-smallest-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1850.%20%E9%82%BB%E4%BD%8D%E4%BA%A4%E6%8D%A2%E7%9A%84%E6%9C%80%E5%B0%8F%E6%AC%A1%E6%95%B0.md,62.7%,中等,69 -1851,1800-1899,1851. 包含每个查询的最小区间,包含每个查询的最小区间,https://leetcode.cn/problems/minimum-interval-to-include-each-query/,minimum-interval-to-include-each-query,数组、二分查找、排序、扫描线、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/minimum-interval-to-include-each-query/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1851.%20%E5%8C%85%E5%90%AB%E6%AF%8F%E4%B8%AA%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%9C%80%E5%B0%8F%E5%8C%BA%E9%97%B4.md,44.1%,困难,81 -1852,1800-1899,1852. 每个子数组的数字种类数,每个子数组的数字种类数,https://leetcode.cn/problems/distinct-numbers-in-each-subarray/,distinct-numbers-in-each-subarray,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/1800-1899/distinct-numbers-in-each-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1852.%20%E6%AF%8F%E4%B8%AA%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E5%AD%97%E7%A7%8D%E7%B1%BB%E6%95%B0.md,59.4%,中等,21 -1853,1800-1899,1853. 转换日期格式,转换日期格式,https://leetcode.cn/problems/convert-date-format/,convert-date-format,数据库,https://algo.itcharge.cn/Solutions/1800-1899/convert-date-format/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1853.%20%E8%BD%AC%E6%8D%A2%E6%97%A5%E6%9C%9F%E6%A0%BC%E5%BC%8F.md,62.8%,简单,31 -1854,1800-1899,1854. 人口最多的年份,人口最多的年份,https://leetcode.cn/problems/maximum-population-year/,maximum-population-year,数组、计数,https://algo.itcharge.cn/Solutions/1800-1899/maximum-population-year/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1854.%20%E4%BA%BA%E5%8F%A3%E6%9C%80%E5%A4%9A%E7%9A%84%E5%B9%B4%E4%BB%BD.md,72.3%,简单,224 -1855,1800-1899,1855. 下标对中的最大距离,下标对中的最大距离,https://leetcode.cn/problems/maximum-distance-between-a-pair-of-values/,maximum-distance-between-a-pair-of-values,贪心、数组、双指针、二分查找,https://algo.itcharge.cn/Solutions/1800-1899/maximum-distance-between-a-pair-of-values/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1855.%20%E4%B8%8B%E6%A0%87%E5%AF%B9%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,59.4%,中等,243 -1856,1800-1899,1856. 子数组最小乘积的最大值,子数组最小乘积的最大值,https://leetcode.cn/problems/maximum-subarray-min-product/,maximum-subarray-min-product,栈、数组、前缀和、单调栈,https://algo.itcharge.cn/Solutions/1800-1899/maximum-subarray-min-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1856.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E4%B9%98%E7%A7%AF%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,37.5%,中等,145 -1857,1800-1899,1857. 有向图中最大颜色值,有向图中最大颜色值,https://leetcode.cn/problems/largest-color-value-in-a-directed-graph/,largest-color-value-in-a-directed-graph,图、拓扑排序、记忆化搜索、哈希表、动态规划、计数,https://algo.itcharge.cn/Solutions/1800-1899/largest-color-value-in-a-directed-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1857.%20%E6%9C%89%E5%90%91%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E9%A2%9C%E8%89%B2%E5%80%BC.md,49.0%,困难,66 -1858,1800-1899,1858. 包含所有前缀的最长单词,包含所有前缀的最长单词,https://leetcode.cn/problems/longest-word-with-all-prefixes/,longest-word-with-all-prefixes,深度优先搜索、字典树,https://algo.itcharge.cn/Solutions/1800-1899/longest-word-with-all-prefixes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1858.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E5%89%8D%E7%BC%80%E7%9A%84%E6%9C%80%E9%95%BF%E5%8D%95%E8%AF%8D.md,66.0%,中等,45 -1859,1800-1899,1859. 将句子排序,将句子排序,https://leetcode.cn/problems/sorting-the-sentence/,sorting-the-sentence,字符串、排序,https://algo.itcharge.cn/Solutions/1800-1899/sorting-the-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1859.%20%E5%B0%86%E5%8F%A5%E5%AD%90%E6%8E%92%E5%BA%8F.md,72.8%,简单,321 -1860,1800-1899,1860. 增长的内存泄露,增长的内存泄露,https://leetcode.cn/problems/incremental-memory-leak/,incremental-memory-leak,模拟,https://algo.itcharge.cn/Solutions/1800-1899/incremental-memory-leak/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1860.%20%E5%A2%9E%E9%95%BF%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2.md,76.0%,中等,124 -1861,1800-1899,1861. 旋转盒子,旋转盒子,https://leetcode.cn/problems/rotating-the-box/,rotating-the-box,数组、双指针、矩阵,https://algo.itcharge.cn/Solutions/1800-1899/rotating-the-box/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1861.%20%E6%97%8B%E8%BD%AC%E7%9B%92%E5%AD%90.md,63.4%,中等,114 -1862,1800-1899,1862. 向下取整数对和,向下取整数对和,https://leetcode.cn/problems/sum-of-floored-pairs/,sum-of-floored-pairs,数组、数学、二分查找、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/sum-of-floored-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1862.%20%E5%90%91%E4%B8%8B%E5%8F%96%E6%95%B4%E6%95%B0%E5%AF%B9%E5%92%8C.md,34.7%,困难,64 -1863,1800-1899,1863. 找出所有子集的异或总和再求和,找出所有子集的异或总和再求和,https://leetcode.cn/problems/sum-of-all-subset-xor-totals/,sum-of-all-subset-xor-totals,位运算、数组、数学、回溯、组合数学,https://algo.itcharge.cn/Solutions/1800-1899/sum-of-all-subset-xor-totals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1863.%20%E6%89%BE%E5%87%BA%E6%89%80%E6%9C%89%E5%AD%90%E9%9B%86%E7%9A%84%E5%BC%82%E6%88%96%E6%80%BB%E5%92%8C%E5%86%8D%E6%B1%82%E5%92%8C.md,84.0%,简单,270 -1864,1800-1899,1864. 构成交替字符串需要的最小交换次数,构成交替字符串需要的最小交换次数,https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-binary-string-alternating/,minimum-number-of-swaps-to-make-the-binary-string-alternating,贪心、字符串,https://algo.itcharge.cn/Solutions/1800-1899/minimum-number-of-swaps-to-make-the-binary-string-alternating/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1864.%20%E6%9E%84%E6%88%90%E4%BA%A4%E6%9B%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,40.8%,中等,158 -1865,1800-1899,1865. 找出和为指定值的下标对,找出和为指定值的下标对,https://leetcode.cn/problems/finding-pairs-with-a-certain-sum/,finding-pairs-with-a-certain-sum,设计、数组、哈希表,https://algo.itcharge.cn/Solutions/1800-1899/finding-pairs-with-a-certain-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1865.%20%E6%89%BE%E5%87%BA%E5%92%8C%E4%B8%BA%E6%8C%87%E5%AE%9A%E5%80%BC%E7%9A%84%E4%B8%8B%E6%A0%87%E5%AF%B9.md,50.9%,中等,86 -1866,1800-1899,1866. 恰有 K 根木棍可以看到的排列数目,恰有 K 根木棍可以看到的排列数目,https://leetcode.cn/problems/number-of-ways-to-rearrange-sticks-with-k-sticks-visible/,number-of-ways-to-rearrange-sticks-with-k-sticks-visible,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/1800-1899/number-of-ways-to-rearrange-sticks-with-k-sticks-visible/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1866.%20%E6%81%B0%E6%9C%89%20K%20%E6%A0%B9%E6%9C%A8%E6%A3%8D%E5%8F%AF%E4%BB%A5%E7%9C%8B%E5%88%B0%E7%9A%84%E6%8E%92%E5%88%97%E6%95%B0%E7%9B%AE.md,61.9%,困难,73 -1867,1800-1899,1867. 最大数量高于平均水平的订单,最大数量高于平均水平的订单,https://leetcode.cn/problems/orders-with-maximum-quantity-above-average/,orders-with-maximum-quantity-above-average,数据库,https://algo.itcharge.cn/Solutions/1800-1899/orders-with-maximum-quantity-above-average/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1867.%20%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F%E9%AB%98%E4%BA%8E%E5%B9%B3%E5%9D%87%E6%B0%B4%E5%B9%B3%E7%9A%84%E8%AE%A2%E5%8D%95.md,68.0%,中等,58 -1868,1800-1899,1868. 两个行程编码数组的积,两个行程编码数组的积,https://leetcode.cn/problems/product-of-two-run-length-encoded-arrays/,product-of-two-run-length-encoded-arrays,数组、双指针,https://algo.itcharge.cn/Solutions/1800-1899/product-of-two-run-length-encoded-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1868.%20%E4%B8%A4%E4%B8%AA%E8%A1%8C%E7%A8%8B%E7%BC%96%E7%A0%81%E6%95%B0%E7%BB%84%E7%9A%84%E7%A7%AF.md,48.8%,中等,50 -1869,1800-1899,1869. 哪种连续子字符串更长,哪种连续子字符串更长,https://leetcode.cn/problems/longer-contiguous-segments-of-ones-than-zeros/,longer-contiguous-segments-of-ones-than-zeros,字符串,https://algo.itcharge.cn/Solutions/1800-1899/longer-contiguous-segments-of-ones-than-zeros/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1869.%20%E5%93%AA%E7%A7%8D%E8%BF%9E%E7%BB%AD%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9B%B4%E9%95%BF.md,70.6%,简单,260 -1870,1800-1899,1870. 准时到达的列车最小时速,准时到达的列车最小时速,https://leetcode.cn/problems/minimum-speed-to-arrive-on-time/,minimum-speed-to-arrive-on-time,数组、二分查找,https://algo.itcharge.cn/Solutions/1800-1899/minimum-speed-to-arrive-on-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1870.%20%E5%87%86%E6%97%B6%E5%88%B0%E8%BE%BE%E7%9A%84%E5%88%97%E8%BD%A6%E6%9C%80%E5%B0%8F%E6%97%B6%E9%80%9F.md,42.2%,中等,142 -1871,1800-1899,1871. 跳跃游戏 VII,跳跃游戏 VII,https://leetcode.cn/problems/jump-game-vii/,jump-game-vii,双指针、字符串、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/jump-game-vii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1871.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20VII.md,28.3%,中等,180 -1872,1800-1899,1872. 石子游戏 VIII,石子游戏 VIII,https://leetcode.cn/problems/stone-game-viii/,stone-game-viii,数组、数学、动态规划、博弈、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/stone-game-viii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1872.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20VIII.md,61.2%,困难,46 -1873,1800-1899,1873. 计算特殊奖金,计算特殊奖金,https://leetcode.cn/problems/calculate-special-bonus/,calculate-special-bonus,数据库,https://algo.itcharge.cn/Solutions/1800-1899/calculate-special-bonus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1873.%20%E8%AE%A1%E7%AE%97%E7%89%B9%E6%AE%8A%E5%A5%96%E9%87%91.md,62.7%,简单,393 -1874,1800-1899,1874. 两个数组的最小乘积和,两个数组的最小乘积和,https://leetcode.cn/problems/minimize-product-sum-of-two-arrays/,minimize-product-sum-of-two-arrays,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1800-1899/minimize-product-sum-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1874.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E4%B9%98%E7%A7%AF%E5%92%8C.md,86.5%,中等,37 -1875,1800-1899,1875. 将工资相同的雇员分组,将工资相同的雇员分组,https://leetcode.cn/problems/group-employees-of-the-same-salary/,group-employees-of-the-same-salary,数据库,https://algo.itcharge.cn/Solutions/1800-1899/group-employees-of-the-same-salary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1875.%20%E5%B0%86%E5%B7%A5%E8%B5%84%E7%9B%B8%E5%90%8C%E7%9A%84%E9%9B%87%E5%91%98%E5%88%86%E7%BB%84.md,64.4%,中等,45 -1876,1800-1899,1876. 长度为三且各字符不同的子字符串,长度为三且各字符不同的子字符串,https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/,substrings-of-size-three-with-distinct-characters,哈希表、字符串、计数、滑动窗口,https://algo.itcharge.cn/Solutions/1800-1899/substrings-of-size-three-with-distinct-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1876.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%E4%B8%89%E4%B8%94%E5%90%84%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,70.4%,简单,258 -1877,1800-1899,1877. 数组中最大数对和的最小值,数组中最大数对和的最小值,https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/,minimize-maximum-pair-sum-in-array,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/1800-1899/minimize-maximum-pair-sum-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1877.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AF%B9%E5%92%8C%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,81.2%,中等,484 -1878,1800-1899,1878. 矩阵中最大的三个菱形和,矩阵中最大的三个菱形和,https://leetcode.cn/problems/get-biggest-three-rhombus-sums-in-a-grid/,get-biggest-three-rhombus-sums-in-a-grid,数组、数学、矩阵、前缀和、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/get-biggest-three-rhombus-sums-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1878.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E4%B8%89%E4%B8%AA%E8%8F%B1%E5%BD%A2%E5%92%8C.md,45.5%,中等,75 -1879,1800-1899,1879. 两个数组最小的异或值之和,两个数组最小的异或值之和,https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/,minimum-xor-sum-of-two-arrays,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1800-1899/minimum-xor-sum-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1879.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E7%9A%84%E5%BC%82%E6%88%96%E5%80%BC%E4%B9%8B%E5%92%8C.md,49.8%,困难,70 -1880,1800-1899,1880. 检查某单词是否等于两单词之和,检查某单词是否等于两单词之和,https://leetcode.cn/problems/check-if-word-equals-summation-of-two-words/,check-if-word-equals-summation-of-two-words,字符串,https://algo.itcharge.cn/Solutions/1800-1899/check-if-word-equals-summation-of-two-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1880.%20%E6%A3%80%E6%9F%A5%E6%9F%90%E5%8D%95%E8%AF%8D%E6%98%AF%E5%90%A6%E7%AD%89%E4%BA%8E%E4%B8%A4%E5%8D%95%E8%AF%8D%E4%B9%8B%E5%92%8C.md,76.3%,简单,210 -1881,1800-1899,1881. 插入后的最大值,插入后的最大值,https://leetcode.cn/problems/maximum-value-after-insertion/,maximum-value-after-insertion,贪心、字符串,https://algo.itcharge.cn/Solutions/1800-1899/maximum-value-after-insertion/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1881.%20%E6%8F%92%E5%85%A5%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,37.7%,中等,100 -1882,1800-1899,1882. 使用服务器处理任务,使用服务器处理任务,https://leetcode.cn/problems/process-tasks-using-servers/,process-tasks-using-servers,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1800-1899/process-tasks-using-servers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1882.%20%E4%BD%BF%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%A4%84%E7%90%86%E4%BB%BB%E5%8A%A1.md,30.8%,中等,146 -1883,1800-1899,1883. 准时抵达会议现场的最小跳过休息次数,准时抵达会议现场的最小跳过休息次数,https://leetcode.cn/problems/minimum-skips-to-arrive-at-meeting-on-time/,minimum-skips-to-arrive-at-meeting-on-time,数组、动态规划,https://algo.itcharge.cn/Solutions/1800-1899/minimum-skips-to-arrive-at-meeting-on-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1883.%20%E5%87%86%E6%97%B6%E6%8A%B5%E8%BE%BE%E4%BC%9A%E8%AE%AE%E7%8E%B0%E5%9C%BA%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%B3%E8%BF%87%E4%BC%91%E6%81%AF%E6%AC%A1%E6%95%B0.md,40.2%,困难,39 -1884,1800-1899,1884. 鸡蛋掉落-两枚鸡蛋,鸡蛋掉落-两枚鸡蛋,https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors/,egg-drop-with-2-eggs-and-n-floors,数学、动态规划,https://algo.itcharge.cn/Solutions/1800-1899/egg-drop-with-2-eggs-and-n-floors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1884.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD-%E4%B8%A4%E6%9E%9A%E9%B8%A1%E8%9B%8B.md,70.4%,中等,111 -1885,1800-1899,1885. 统计数对,统计数对,https://leetcode.cn/problems/count-pairs-in-two-arrays/,count-pairs-in-two-arrays,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/1800-1899/count-pairs-in-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1885.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AF%B9.md,59.0%,中等,29 -1886,1800-1899,1886. 判断矩阵经轮转后是否一致,判断矩阵经轮转后是否一致,https://leetcode.cn/problems/determine-whether-matrix-can-be-obtained-by-rotation/,determine-whether-matrix-can-be-obtained-by-rotation,数组、矩阵,https://algo.itcharge.cn/Solutions/1800-1899/determine-whether-matrix-can-be-obtained-by-rotation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1886.%20%E5%88%A4%E6%96%AD%E7%9F%A9%E9%98%B5%E7%BB%8F%E8%BD%AE%E8%BD%AC%E5%90%8E%E6%98%AF%E5%90%A6%E4%B8%80%E8%87%B4.md,59.4%,简单,172 -1887,1800-1899,1887. 使数组元素相等的减少操作次数,使数组元素相等的减少操作次数,https://leetcode.cn/problems/reduction-operations-to-make-the-array-elements-equal/,reduction-operations-to-make-the-array-elements-equal,数组、排序,https://algo.itcharge.cn/Solutions/1800-1899/reduction-operations-to-make-the-array-elements-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1887.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%E7%9A%84%E5%87%8F%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,65.6%,中等,112 -1888,1800-1899,1888. 使二进制字符串字符交替的最少反转次数,使二进制字符串字符交替的最少反转次数,https://leetcode.cn/problems/minimum-number-of-flips-to-make-the-binary-string-alternating/,minimum-number-of-flips-to-make-the-binary-string-alternating,贪心、字符串、动态规划、滑动窗口,https://algo.itcharge.cn/Solutions/1800-1899/minimum-number-of-flips-to-make-the-binary-string-alternating/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1888.%20%E4%BD%BF%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AD%97%E7%AC%A6%E4%BA%A4%E6%9B%BF%E7%9A%84%E6%9C%80%E5%B0%91%E5%8F%8D%E8%BD%AC%E6%AC%A1%E6%95%B0.md,36.5%,中等,94 -1889,1800-1899,1889. 装包裹的最小浪费空间,装包裹的最小浪费空间,https://leetcode.cn/problems/minimum-space-wasted-from-packaging/,minimum-space-wasted-from-packaging,数组、二分查找、前缀和、排序,https://algo.itcharge.cn/Solutions/1800-1899/minimum-space-wasted-from-packaging/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1889.%20%E8%A3%85%E5%8C%85%E8%A3%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B5%AA%E8%B4%B9%E7%A9%BA%E9%97%B4.md,29.6%,困难,67 -1890,1800-1899,1890. 2020年最后一次登录,2020年最后一次登录,https://leetcode.cn/problems/the-latest-login-in-2020/,the-latest-login-in-2020,数据库,https://algo.itcharge.cn/Solutions/1800-1899/the-latest-login-in-2020/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1890.%202020%E5%B9%B4%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E7%99%BB%E5%BD%95.md,70.0%,简单,199 -1891,1800-1899,1891. 割绳子,割绳子,https://leetcode.cn/problems/cutting-ribbons/,cutting-ribbons,数组、二分查找,https://algo.itcharge.cn/Solutions/1800-1899/cutting-ribbons/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1891.%20%E5%89%B2%E7%BB%B3%E5%AD%90.md,42.1%,中等,50 -1892,1800-1899,1892. 页面推荐Ⅱ,页面推荐Ⅱ,https://leetcode.cn/problems/page-recommendations-ii/,page-recommendations-ii,数据库,https://algo.itcharge.cn/Solutions/1800-1899/page-recommendations-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1892.%20%E9%A1%B5%E9%9D%A2%E6%8E%A8%E8%8D%90%E2%85%A1.md,37.6%,困难,34 -1893,1800-1899,1893. 检查是否区域内所有整数都被覆盖,检查是否区域内所有整数都被覆盖,https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/,check-if-all-the-integers-in-a-range-are-covered,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1893.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8C%BA%E5%9F%9F%E5%86%85%E6%89%80%E6%9C%89%E6%95%B4%E6%95%B0%E9%83%BD%E8%A2%AB%E8%A6%86%E7%9B%96.md,58.9%,简单,530 -1894,1800-1899,1894. 找到需要补充粉笔的学生编号,找到需要补充粉笔的学生编号,https://leetcode.cn/problems/find-the-student-that-will-replace-the-chalk/,find-the-student-that-will-replace-the-chalk,数组、二分查找、前缀和、模拟,https://algo.itcharge.cn/Solutions/1800-1899/find-the-student-that-will-replace-the-chalk/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1894.%20%E6%89%BE%E5%88%B0%E9%9C%80%E8%A6%81%E8%A1%A5%E5%85%85%E7%B2%89%E7%AC%94%E7%9A%84%E5%AD%A6%E7%94%9F%E7%BC%96%E5%8F%B7.md,45.8%,中等,619 -1895,1800-1899,1895. 最大的幻方,最大的幻方,https://leetcode.cn/problems/largest-magic-square/,largest-magic-square,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/1800-1899/largest-magic-square/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1895.%20%E6%9C%80%E5%A4%A7%E7%9A%84%E5%B9%BB%E6%96%B9.md,56.3%,中等,62 -1896,1800-1899,1896. 反转表达式值的最少操作次数,反转表达式值的最少操作次数,https://leetcode.cn/problems/minimum-cost-to-change-the-final-value-of-expression/,minimum-cost-to-change-the-final-value-of-expression,栈、数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/1800-1899/minimum-cost-to-change-the-final-value-of-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1896.%20%E5%8F%8D%E8%BD%AC%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,51.5%,困难,36 -1897,1800-1899,1897. 重新分配字符使所有字符串都相等,重新分配字符使所有字符串都相等,https://leetcode.cn/problems/redistribute-characters-to-make-all-strings-equal/,redistribute-characters-to-make-all-strings-equal,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1800-1899/redistribute-characters-to-make-all-strings-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1897.%20%E9%87%8D%E6%96%B0%E5%88%86%E9%85%8D%E5%AD%97%E7%AC%A6%E4%BD%BF%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%83%BD%E7%9B%B8%E7%AD%89.md,55.2%,简单,143 -1898,1800-1899,1898. 可移除字符的最大数目,可移除字符的最大数目,https://leetcode.cn/problems/maximum-number-of-removable-characters/,maximum-number-of-removable-characters,数组、字符串、二分查找,https://algo.itcharge.cn/Solutions/1800-1899/maximum-number-of-removable-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1898.%20%E5%8F%AF%E7%A7%BB%E9%99%A4%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,38.0%,中等,119 -1899,1800-1899,1899. 合并若干三元组以形成目标三元组,合并若干三元组以形成目标三元组,https://leetcode.cn/problems/merge-triplets-to-form-target-triplet/,merge-triplets-to-form-target-triplet,贪心、数组,https://algo.itcharge.cn/Solutions/1800-1899/merge-triplets-to-form-target-triplet/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1899.%20%E5%90%88%E5%B9%B6%E8%8B%A5%E5%B9%B2%E4%B8%89%E5%85%83%E7%BB%84%E4%BB%A5%E5%BD%A2%E6%88%90%E7%9B%AE%E6%A0%87%E4%B8%89%E5%85%83%E7%BB%84.md,65.3%,中等,84 -1900,1900-1999,1900. 最佳运动员的比拼回合,最佳运动员的比拼回合,https://leetcode.cn/problems/the-earliest-and-latest-rounds-where-players-compete/,the-earliest-and-latest-rounds-where-players-compete,记忆化搜索、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/the-earliest-and-latest-rounds-where-players-compete/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1900.%20%E6%9C%80%E4%BD%B3%E8%BF%90%E5%8A%A8%E5%91%98%E7%9A%84%E6%AF%94%E6%8B%BC%E5%9B%9E%E5%90%88.md,45.8%,困难,46 -1901,1900-1999,1901. 寻找峰值 II,寻找峰值 II,https://leetcode.cn/problems/find-a-peak-element-ii/,find-a-peak-element-ii,数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/find-a-peak-element-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1901.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC%20II.md,58.9%,中等,130 -1902,1900-1999,1902. 给定二叉搜索树的插入顺序求深度,给定二叉搜索树的插入顺序求深度,https://leetcode.cn/problems/depth-of-bst-given-insertion-order/,depth-of-bst-given-insertion-order,树、二叉搜索树、二叉树、有序集合,https://algo.itcharge.cn/Solutions/1900-1999/depth-of-bst-given-insertion-order/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1902.%20%E7%BB%99%E5%AE%9A%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%8F%92%E5%85%A5%E9%A1%BA%E5%BA%8F%E6%B1%82%E6%B7%B1%E5%BA%A6.md,50.9%,中等,14 -1903,1900-1999,1903. 字符串中的最大奇数,字符串中的最大奇数,https://leetcode.cn/problems/largest-odd-number-in-string/,largest-odd-number-in-string,贪心、数学、字符串,https://algo.itcharge.cn/Solutions/1900-1999/largest-odd-number-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1903.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%A5%87%E6%95%B0.md,60.5%,简单,206 -1904,1900-1999,1904. 你完成的完整对局数,你完成的完整对局数,https://leetcode.cn/problems/the-number-of-full-rounds-you-have-played/,the-number-of-full-rounds-you-have-played,数学、字符串,https://algo.itcharge.cn/Solutions/1900-1999/the-number-of-full-rounds-you-have-played/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1904.%20%E4%BD%A0%E5%AE%8C%E6%88%90%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AF%B9%E5%B1%80%E6%95%B0.md,31.4%,中等,106 -1905,1900-1999,1905. 统计子岛屿,统计子岛屿,https://leetcode.cn/problems/count-sub-islands/,count-sub-islands,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/count-sub-islands/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1905.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E5%B2%9B%E5%B1%BF.md,67.2%,中等,419 -1906,1900-1999,1906. 查询差绝对值的最小值,查询差绝对值的最小值,https://leetcode.cn/problems/minimum-absolute-difference-queries/,minimum-absolute-difference-queries,数组、哈希表,https://algo.itcharge.cn/Solutions/1900-1999/minimum-absolute-difference-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1906.%20%E6%9F%A5%E8%AF%A2%E5%B7%AE%E7%BB%9D%E5%AF%B9%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,44.9%,中等,73 -1907,1900-1999,1907. 按分类统计薪水,按分类统计薪水,https://leetcode.cn/problems/count-salary-categories/,count-salary-categories,数据库,https://algo.itcharge.cn/Solutions/1900-1999/count-salary-categories/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1907.%20%E6%8C%89%E5%88%86%E7%B1%BB%E7%BB%9F%E8%AE%A1%E8%96%AA%E6%B0%B4.md,63.6%,中等,60 -1908,1900-1999,1908. Nim 游戏 II,Nim 游戏 II,https://leetcode.cn/problems/game-of-nim/,game-of-nim,位运算、脑筋急转弯、数组、数学、动态规划、博弈,https://algo.itcharge.cn/Solutions/1900-1999/game-of-nim/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1908.%20Nim%20%E6%B8%B8%E6%88%8F%20II.md,62.3%,中等,19 -1909,1900-1999,1909. 删除一个元素使数组严格递增,删除一个元素使数组严格递增,https://leetcode.cn/problems/remove-one-element-to-make-the-array-strictly-increasing/,remove-one-element-to-make-the-array-strictly-increasing,数组,https://algo.itcharge.cn/Solutions/1900-1999/remove-one-element-to-make-the-array-strictly-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1909.%20%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%A5%E6%A0%BC%E9%80%92%E5%A2%9E.md,30.0%,简单,173 -1910,1900-1999,1910. 删除一个字符串中所有出现的给定子字符串,删除一个字符串中所有出现的给定子字符串,https://leetcode.cn/problems/remove-all-occurrences-of-a-substring/,remove-all-occurrences-of-a-substring,字符串,https://algo.itcharge.cn/Solutions/1900-1999/remove-all-occurrences-of-a-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1910.%20%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%87%BA%E7%8E%B0%E7%9A%84%E7%BB%99%E5%AE%9A%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,66.5%,中等,122 -1911,1900-1999,1911. 最大子序列交替和,最大子序列交替和,https://leetcode.cn/problems/maximum-alternating-subsequence-sum/,maximum-alternating-subsequence-sum,数组、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/maximum-alternating-subsequence-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1911.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%88%97%E4%BA%A4%E6%9B%BF%E5%92%8C.md,59.1%,中等,119 -1912,1900-1999,1912. 设计电影租借系统,设计电影租借系统,https://leetcode.cn/problems/design-movie-rental-system/,design-movie-rental-system,设计、数组、哈希表、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/1900-1999/design-movie-rental-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1912.%20%E8%AE%BE%E8%AE%A1%E7%94%B5%E5%BD%B1%E7%A7%9F%E5%80%9F%E7%B3%BB%E7%BB%9F.md,24.1%,困难,84 -1913,1900-1999,1913. 两个数对之间的最大乘积差,两个数对之间的最大乘积差,https://leetcode.cn/problems/maximum-product-difference-between-two-pairs/,maximum-product-difference-between-two-pairs,数组、排序,https://algo.itcharge.cn/Solutions/1900-1999/maximum-product-difference-between-two-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1913.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AF%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF%E5%B7%AE.md,81.5%,简单,238 -1914,1900-1999,1914. 循环轮转矩阵,循环轮转矩阵,https://leetcode.cn/problems/cyclically-rotating-a-grid/,cyclically-rotating-a-grid,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/1900-1999/cyclically-rotating-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1914.%20%E5%BE%AA%E7%8E%AF%E8%BD%AE%E8%BD%AC%E7%9F%A9%E9%98%B5.md,46.5%,中等,100 -1915,1900-1999,1915. 最美子字符串的数目,最美子字符串的数目,https://leetcode.cn/problems/number-of-wonderful-substrings/,number-of-wonderful-substrings,位运算、哈希表、字符串、前缀和,https://algo.itcharge.cn/Solutions/1900-1999/number-of-wonderful-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1915.%20%E6%9C%80%E7%BE%8E%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE.md,44.7%,中等,83 -1916,1900-1999,1916. 统计为蚁群构筑房间的不同顺序,统计为蚁群构筑房间的不同顺序,https://leetcode.cn/problems/count-ways-to-build-rooms-in-an-ant-colony/,count-ways-to-build-rooms-in-an-ant-colony,树、图、拓扑排序、数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/1900-1999/count-ways-to-build-rooms-in-an-ant-colony/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1916.%20%E7%BB%9F%E8%AE%A1%E4%B8%BA%E8%9A%81%E7%BE%A4%E6%9E%84%E7%AD%91%E6%88%BF%E9%97%B4%E7%9A%84%E4%B8%8D%E5%90%8C%E9%A1%BA%E5%BA%8F.md,55.5%,困难,32 -1917,1900-1999,1917. Leetcodify 好友推荐,Leetcodify 好友推荐,https://leetcode.cn/problems/leetcodify-friends-recommendations/,leetcodify-friends-recommendations,数据库,https://algo.itcharge.cn/Solutions/1900-1999/leetcodify-friends-recommendations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1917.%20Leetcodify%20%E5%A5%BD%E5%8F%8B%E6%8E%A8%E8%8D%90.md,31.6%,困难,30 -1918,1900-1999,1918. 第 K 小的子数组和·,第 K 小的子数组和·,https://leetcode.cn/problems/kth-smallest-subarray-sum/,kth-smallest-subarray-sum,数组、二分查找、滑动窗口,https://algo.itcharge.cn/Solutions/1900-1999/kth-smallest-subarray-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1918.%20%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%C2%B7.md,48.0%,中等,11 -1919,1900-1999,1919. 兴趣相同的朋友,兴趣相同的朋友,https://leetcode.cn/problems/leetcodify-similar-friends/,leetcodify-similar-friends,数据库,https://algo.itcharge.cn/Solutions/1900-1999/leetcodify-similar-friends/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1919.%20%E5%85%B4%E8%B6%A3%E7%9B%B8%E5%90%8C%E7%9A%84%E6%9C%8B%E5%8F%8B.md,43.3%,困难,39 -1920,1900-1999,1920. 基于排列构建数组,基于排列构建数组,https://leetcode.cn/problems/build-array-from-permutation/,build-array-from-permutation,数组、模拟,https://algo.itcharge.cn/Solutions/1900-1999/build-array-from-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1920.%20%E5%9F%BA%E4%BA%8E%E6%8E%92%E5%88%97%E6%9E%84%E5%BB%BA%E6%95%B0%E7%BB%84.md,86.6%,简单,316 -1921,1900-1999,1921. 消灭怪物的最大数量,消灭怪物的最大数量,https://leetcode.cn/problems/eliminate-maximum-number-of-monsters/,eliminate-maximum-number-of-monsters,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1900-1999/eliminate-maximum-number-of-monsters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1921.%20%E6%B6%88%E7%81%AD%E6%80%AA%E7%89%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,37.0%,中等,124 -1922,1900-1999,1922. 统计好数字的数目,统计好数字的数目,https://leetcode.cn/problems/count-good-numbers/,count-good-numbers,递归、数学,https://algo.itcharge.cn/Solutions/1900-1999/count-good-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1922.%20%E7%BB%9F%E8%AE%A1%E5%A5%BD%E6%95%B0%E5%AD%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,36.2%,中等,104 -1923,1900-1999,1923. 最长公共子路径,最长公共子路径,https://leetcode.cn/problems/longest-common-subpath/,longest-common-subpath,数组、二分查找、后缀数组、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1900-1999/longest-common-subpath/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1923.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E8%B7%AF%E5%BE%84.md,26.9%,困难,48 -1924,1900-1999,1924. 安装栅栏 II,安装栅栏 II,https://leetcode.cn/problems/erect-the-fence-ii/,erect-the-fence-ii,几何、数组、数学,https://algo.itcharge.cn/Solutions/1900-1999/erect-the-fence-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1924.%20%E5%AE%89%E8%A3%85%E6%A0%85%E6%A0%8F%20II.md,46.4%,困难,11 -1925,1900-1999,1925. 统计平方和三元组的数目,统计平方和三元组的数目,https://leetcode.cn/problems/count-square-sum-triples/,count-square-sum-triples,数学、枚举,https://algo.itcharge.cn/Solutions/1900-1999/count-square-sum-triples/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1925.%20%E7%BB%9F%E8%AE%A1%E5%B9%B3%E6%96%B9%E5%92%8C%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,69.6%,简单,144 -1926,1900-1999,1926. 迷宫中离入口最近的出口,迷宫中离入口最近的出口,https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/,nearest-exit-from-entrance-in-maze,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/nearest-exit-from-entrance-in-maze/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1926.%20%E8%BF%B7%E5%AE%AB%E4%B8%AD%E7%A6%BB%E5%85%A5%E5%8F%A3%E6%9C%80%E8%BF%91%E7%9A%84%E5%87%BA%E5%8F%A3.md,39.5%,中等,189 -1927,1900-1999,1927. 求和游戏,求和游戏,https://leetcode.cn/problems/sum-game/,sum-game,贪心、数学、博弈,https://algo.itcharge.cn/Solutions/1900-1999/sum-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1927.%20%E6%B1%82%E5%92%8C%E6%B8%B8%E6%88%8F.md,43.6%,中等,67 -1928,1900-1999,1928. 规定时间内到达终点的最小花费,规定时间内到达终点的最小花费,https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time/,minimum-cost-to-reach-destination-in-time,图、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/minimum-cost-to-reach-destination-in-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1928.%20%E8%A7%84%E5%AE%9A%E6%97%B6%E9%97%B4%E5%86%85%E5%88%B0%E8%BE%BE%E7%BB%88%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9.md,45.9%,困难,84 -1929,1900-1999,1929. 数组串联,数组串联,https://leetcode.cn/problems/concatenation-of-array/,concatenation-of-array,数组,https://algo.itcharge.cn/Solutions/1900-1999/concatenation-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1929.%20%E6%95%B0%E7%BB%84%E4%B8%B2%E8%81%94.md,86.2%,简单,347 -1930,1900-1999,1930. 长度为 3 的不同回文子序列,长度为 3 的不同回文子序列,https://leetcode.cn/problems/unique-length-3-palindromic-subsequences/,unique-length-3-palindromic-subsequences,哈希表、字符串、前缀和,https://algo.itcharge.cn/Solutions/1900-1999/unique-length-3-palindromic-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1930.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%203%20%E7%9A%84%E4%B8%8D%E5%90%8C%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md,51.6%,中等,153 -1931,1900-1999,1931. 用三种不同颜色为网格涂色,用三种不同颜色为网格涂色,https://leetcode.cn/problems/painting-a-grid-with-three-different-colors/,painting-a-grid-with-three-different-colors,动态规划,https://algo.itcharge.cn/Solutions/1900-1999/painting-a-grid-with-three-different-colors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1931.%20%E7%94%A8%E4%B8%89%E7%A7%8D%E4%B8%8D%E5%90%8C%E9%A2%9C%E8%89%B2%E4%B8%BA%E7%BD%91%E6%A0%BC%E6%B6%82%E8%89%B2.md,59.3%,困难,93 -1932,1900-1999,1932. 合并多棵二叉搜索树,合并多棵二叉搜索树,https://leetcode.cn/problems/merge-bsts-to-create-single-bst/,merge-bsts-to-create-single-bst,树、深度优先搜索、哈希表、二分查找、二叉树,https://algo.itcharge.cn/Solutions/1900-1999/merge-bsts-to-create-single-bst/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1932.%20%E5%90%88%E5%B9%B6%E5%A4%9A%E6%A3%B5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,33.3%,困难,54 -1933,1900-1999,1933. 判断字符串是否可分解为值均等的子串,判断字符串是否可分解为值均等的子串,https://leetcode.cn/problems/check-if-string-is-decomposable-into-value-equal-substrings/,check-if-string-is-decomposable-into-value-equal-substrings,字符串,https://algo.itcharge.cn/Solutions/1900-1999/check-if-string-is-decomposable-into-value-equal-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1933.%20%E5%88%A4%E6%96%AD%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E5%8F%AF%E5%88%86%E8%A7%A3%E4%B8%BA%E5%80%BC%E5%9D%87%E7%AD%89%E7%9A%84%E5%AD%90%E4%B8%B2.md,49.3%,简单,42 -1934,1900-1999,1934. 确认率,确认率,https://leetcode.cn/problems/confirmation-rate/,confirmation-rate,数据库,https://algo.itcharge.cn/Solutions/1900-1999/confirmation-rate/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1934.%20%E7%A1%AE%E8%AE%A4%E7%8E%87.md,67.8%,中等,63 -1935,1900-1999,1935. 可以输入的最大单词数,可以输入的最大单词数,https://leetcode.cn/problems/maximum-number-of-words-you-can-type/,maximum-number-of-words-you-can-type,哈希表、字符串,https://algo.itcharge.cn/Solutions/1900-1999/maximum-number-of-words-you-can-type/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1935.%20%E5%8F%AF%E4%BB%A5%E8%BE%93%E5%85%A5%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%95%E8%AF%8D%E6%95%B0.md,70.9%,简单,233 -1936,1900-1999,1936. 新增的最少台阶数,新增的最少台阶数,https://leetcode.cn/problems/add-minimum-number-of-rungs/,add-minimum-number-of-rungs,贪心、数组,https://algo.itcharge.cn/Solutions/1900-1999/add-minimum-number-of-rungs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1936.%20%E6%96%B0%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%91%E5%8F%B0%E9%98%B6%E6%95%B0.md,46.3%,中等,118 -1937,1900-1999,1937. 扣分后的最大得分,扣分后的最大得分,https://leetcode.cn/problems/maximum-number-of-points-with-cost/,maximum-number-of-points-with-cost,数组、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/maximum-number-of-points-with-cost/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1937.%20%E6%89%A3%E5%88%86%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,28.9%,中等,91 -1938,1900-1999,1938. 查询最大基因差,查询最大基因差,https://leetcode.cn/problems/maximum-genetic-difference-query/,maximum-genetic-difference-query,位运算、字典树、数组,https://algo.itcharge.cn/Solutions/1900-1999/maximum-genetic-difference-query/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1938.%20%E6%9F%A5%E8%AF%A2%E6%9C%80%E5%A4%A7%E5%9F%BA%E5%9B%A0%E5%B7%AE.md,40.2%,困难,51 -1939,1900-1999,1939. 主动请求确认消息的用户,主动请求确认消息的用户,https://leetcode.cn/problems/users-that-actively-request-confirmation-messages/,users-that-actively-request-confirmation-messages,数据库,https://algo.itcharge.cn/Solutions/1900-1999/users-that-actively-request-confirmation-messages/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1939.%20%E4%B8%BB%E5%8A%A8%E8%AF%B7%E6%B1%82%E7%A1%AE%E8%AE%A4%E6%B6%88%E6%81%AF%E7%9A%84%E7%94%A8%E6%88%B7.md,59.8%,简单,28 -1940,1900-1999,1940. 排序数组之间的最长公共子序列,排序数组之间的最长公共子序列,https://leetcode.cn/problems/longest-common-subsequence-between-sorted-arrays/,longest-common-subsequence-between-sorted-arrays,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/1900-1999/longest-common-subsequence-between-sorted-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1940.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md,74.1%,中等,40 -1941,1900-1999,1941. 检查是否所有字符出现次数相同,检查是否所有字符出现次数相同,https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/,check-if-all-characters-have-equal-number-of-occurrences,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1941.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E7%9B%B8%E5%90%8C.md,73.6%,简单,193 -1942,1900-1999,1942. 最小未被占据椅子的编号,最小未被占据椅子的编号,https://leetcode.cn/problems/the-number-of-the-smallest-unoccupied-chair/,the-number-of-the-smallest-unoccupied-chair,数组、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/1900-1999/the-number-of-the-smallest-unoccupied-chair/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1942.%20%E6%9C%80%E5%B0%8F%E6%9C%AA%E8%A2%AB%E5%8D%A0%E6%8D%AE%E6%A4%85%E5%AD%90%E7%9A%84%E7%BC%96%E5%8F%B7.md,41.9%,中等,110 -1943,1900-1999,1943. 描述绘画结果,描述绘画结果,https://leetcode.cn/problems/describe-the-painting/,describe-the-painting,数组、前缀和,https://algo.itcharge.cn/Solutions/1900-1999/describe-the-painting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1943.%20%E6%8F%8F%E8%BF%B0%E7%BB%98%E7%94%BB%E7%BB%93%E6%9E%9C.md,43.9%,中等,75 -1944,1900-1999,1944. 队列中可以看到的人数,队列中可以看到的人数,https://leetcode.cn/problems/number-of-visible-people-in-a-queue/,number-of-visible-people-in-a-queue,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/1900-1999/number-of-visible-people-in-a-queue/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1944.%20%E9%98%9F%E5%88%97%E4%B8%AD%E5%8F%AF%E4%BB%A5%E7%9C%8B%E5%88%B0%E7%9A%84%E4%BA%BA%E6%95%B0.md,63.4%,困难,112 -1945,1900-1999,1945. 字符串转化后的各位数字之和,字符串转化后的各位数字之和,https://leetcode.cn/problems/sum-of-digits-of-string-after-convert/,sum-of-digits-of-string-after-convert,字符串、模拟,https://algo.itcharge.cn/Solutions/1900-1999/sum-of-digits-of-string-after-convert/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1945.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E5%8C%96%E5%90%8E%E7%9A%84%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md,70.4%,简单,379 -1946,1900-1999,1946. 子字符串突变后可能得到的最大整数,子字符串突变后可能得到的最大整数,https://leetcode.cn/problems/largest-number-after-mutating-substring/,largest-number-after-mutating-substring,贪心、数组、字符串,https://algo.itcharge.cn/Solutions/1900-1999/largest-number-after-mutating-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1946.%20%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%AA%81%E5%8F%98%E5%90%8E%E5%8F%AF%E8%83%BD%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B4%E6%95%B0.md,32.2%,中等,113 -1947,1900-1999,1947. 最大兼容性评分和,最大兼容性评分和,https://leetcode.cn/problems/maximum-compatibility-score-sum/,maximum-compatibility-score-sum,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1900-1999/maximum-compatibility-score-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md,58.0%,中等,128 -1948,1900-1999,1948. 删除系统中的重复文件夹,删除系统中的重复文件夹,https://leetcode.cn/problems/delete-duplicate-folders-in-system/,delete-duplicate-folders-in-system,字典树、数组、哈希表、字符串、哈希函数,https://algo.itcharge.cn/Solutions/1900-1999/delete-duplicate-folders-in-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1948.%20%E5%88%A0%E9%99%A4%E7%B3%BB%E7%BB%9F%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E6%96%87%E4%BB%B6%E5%A4%B9.md,54.4%,困难,29 -1949,1900-1999,1949. 坚定的友谊,坚定的友谊,https://leetcode.cn/problems/strong-friendship/,strong-friendship,数据库,https://algo.itcharge.cn/Solutions/1900-1999/strong-friendship/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1949.%20%E5%9D%9A%E5%AE%9A%E7%9A%84%E5%8F%8B%E8%B0%8A.md,52.1%,中等,48 -1950,1900-1999,1950. 所有子数组最小值中的最大值,所有子数组最小值中的最大值,https://leetcode.cn/problems/maximum-of-minimum-values-in-all-subarrays/,maximum-of-minimum-values-in-all-subarrays,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/1900-1999/maximum-of-minimum-values-in-all-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1950.%20%E6%89%80%E6%9C%89%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E5%80%BC%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,51.3%,中等,16 -1951,1900-1999,1951. 查询具有最多共同关注者的所有两两结对组,查询具有最多共同关注者的所有两两结对组,https://leetcode.cn/problems/all-the-pairs-with-the-maximum-number-of-common-followers/,all-the-pairs-with-the-maximum-number-of-common-followers,数据库,https://algo.itcharge.cn/Solutions/1900-1999/all-the-pairs-with-the-maximum-number-of-common-followers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1951.%20%E6%9F%A5%E8%AF%A2%E5%85%B7%E6%9C%89%E6%9C%80%E5%A4%9A%E5%85%B1%E5%90%8C%E5%85%B3%E6%B3%A8%E8%80%85%E7%9A%84%E6%89%80%E6%9C%89%E4%B8%A4%E4%B8%A4%E7%BB%93%E5%AF%B9%E7%BB%84.md,65.2%,中等,53 -1952,1900-1999,1952. 三除数,三除数,https://leetcode.cn/problems/three-divisors/,three-divisors,数学,https://algo.itcharge.cn/Solutions/1900-1999/three-divisors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1952.%20%E4%B8%89%E9%99%A4%E6%95%B0.md,54.8%,简单,175 -1953,1900-1999,1953. 你可以工作的最大周数,你可以工作的最大周数,https://leetcode.cn/problems/maximum-number-of-weeks-for-which-you-can-work/,maximum-number-of-weeks-for-which-you-can-work,贪心、数组,https://algo.itcharge.cn/Solutions/1900-1999/maximum-number-of-weeks-for-which-you-can-work/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1953.%20%E4%BD%A0%E5%8F%AF%E4%BB%A5%E5%B7%A5%E4%BD%9C%E7%9A%84%E6%9C%80%E5%A4%A7%E5%91%A8%E6%95%B0.md,37.2%,中等,114 -1954,1900-1999,1954. 收集足够苹果的最小花园周长,收集足够苹果的最小花园周长,https://leetcode.cn/problems/minimum-garden-perimeter-to-collect-enough-apples/,minimum-garden-perimeter-to-collect-enough-apples,数学、二分查找,https://algo.itcharge.cn/Solutions/1900-1999/minimum-garden-perimeter-to-collect-enough-apples/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1954.%20%E6%94%B6%E9%9B%86%E8%B6%B3%E5%A4%9F%E8%8B%B9%E6%9E%9C%E7%9A%84%E6%9C%80%E5%B0%8F%E8%8A%B1%E5%9B%AD%E5%91%A8%E9%95%BF.md,49.7%,中等,93 -1955,1900-1999,1955. 统计特殊子序列的数目,统计特殊子序列的数目,https://leetcode.cn/problems/count-number-of-special-subsequences/,count-number-of-special-subsequences,数组、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/count-number-of-special-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1955.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,52.5%,困难,60 -1956,1900-1999,1956. 感染 K 种病毒所需的最短时间,感染 K 种病毒所需的最短时间,https://leetcode.cn/problems/minimum-time-for-k-virus-variants-to-spread/,minimum-time-for-k-virus-variants-to-spread,几何、数组、数学、二分查找、枚举,https://algo.itcharge.cn/Solutions/1900-1999/minimum-time-for-k-virus-variants-to-spread/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1956.%20%E6%84%9F%E6%9F%93%20K%20%E7%A7%8D%E7%97%85%E6%AF%92%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,38.0%,困难,15 -1957,1900-1999,1957. 删除字符使字符串变好,删除字符使字符串变好,https://leetcode.cn/problems/delete-characters-to-make-fancy-string/,delete-characters-to-make-fancy-string,字符串,https://algo.itcharge.cn/Solutions/1900-1999/delete-characters-to-make-fancy-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1957.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8F%98%E5%A5%BD.md,60.0%,简单,141 -1958,1900-1999,1958. 检查操作是否合法,检查操作是否合法,https://leetcode.cn/problems/check-if-move-is-legal/,check-if-move-is-legal,数组、枚举、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/check-if-move-is-legal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1958.%20%E6%A3%80%E6%9F%A5%E6%93%8D%E4%BD%9C%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95.md,44.7%,中等,65 -1959,1900-1999,1959. K 次调整数组大小浪费的最小总空间,K 次调整数组大小浪费的最小总空间,https://leetcode.cn/problems/minimum-total-space-wasted-with-k-resizing-operations/,minimum-total-space-wasted-with-k-resizing-operations,数组、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/minimum-total-space-wasted-with-k-resizing-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1959.%20K%20%E6%AC%A1%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F%E6%B5%AA%E8%B4%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%80%BB%E7%A9%BA%E9%97%B4.md,45.9%,中等,51 -1960,1900-1999,1960. 两个回文子字符串长度的最大乘积,两个回文子字符串长度的最大乘积,https://leetcode.cn/problems/maximum-product-of-the-length-of-two-palindromic-substrings/,maximum-product-of-the-length-of-two-palindromic-substrings,字符串、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/1900-1999/maximum-product-of-the-length-of-two-palindromic-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1960.%20%E4%B8%A4%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%95%BF%E5%BA%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,32.3%,困难,37 -1961,1900-1999,1961. 检查字符串是否为数组前缀,检查字符串是否为数组前缀,https://leetcode.cn/problems/check-if-string-is-a-prefix-of-array/,check-if-string-is-a-prefix-of-array,数组、字符串,https://algo.itcharge.cn/Solutions/1900-1999/check-if-string-is-a-prefix-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1961.%20%E6%A3%80%E6%9F%A5%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E4%B8%BA%E6%95%B0%E7%BB%84%E5%89%8D%E7%BC%80.md,52.7%,简单,152 -1962,1900-1999,1962. 移除石子使总数最小,移除石子使总数最小,https://leetcode.cn/problems/remove-stones-to-minimize-the-total/,remove-stones-to-minimize-the-total,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/1900-1999/remove-stones-to-minimize-the-total/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1962.%20%E7%A7%BB%E9%99%A4%E7%9F%B3%E5%AD%90%E4%BD%BF%E6%80%BB%E6%95%B0%E6%9C%80%E5%B0%8F.md,46.6%,中等,104 -1963,1900-1999,1963. 使字符串平衡的最小交换次数,使字符串平衡的最小交换次数,https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced/,minimum-number-of-swaps-to-make-the-string-balanced,栈、贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/1900-1999/minimum-number-of-swaps-to-make-the-string-balanced/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1963.%20%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B9%B3%E8%A1%A1%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,64.5%,中等,84 -1964,1900-1999,1964. 找出到每个位置为止最长的有效障碍赛跑路线,找出到每个位置为止最长的有效障碍赛跑路线,https://leetcode.cn/problems/find-the-longest-valid-obstacle-course-at-each-position/,find-the-longest-valid-obstacle-course-at-each-position,树状数组、数组、二分查找,https://algo.itcharge.cn/Solutions/1900-1999/find-the-longest-valid-obstacle-course-at-each-position/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1964.%20%E6%89%BE%E5%87%BA%E5%88%B0%E6%AF%8F%E4%B8%AA%E4%BD%8D%E7%BD%AE%E4%B8%BA%E6%AD%A2%E6%9C%80%E9%95%BF%E7%9A%84%E6%9C%89%E6%95%88%E9%9A%9C%E7%A2%8D%E8%B5%9B%E8%B7%91%E8%B7%AF%E7%BA%BF.md,43.6%,困难,73 -1965,1900-1999,1965. 丢失信息的雇员,丢失信息的雇员,https://leetcode.cn/problems/employees-with-missing-information/,employees-with-missing-information,数据库,https://algo.itcharge.cn/Solutions/1900-1999/employees-with-missing-information/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1965.%20%E4%B8%A2%E5%A4%B1%E4%BF%A1%E6%81%AF%E7%9A%84%E9%9B%87%E5%91%98.md,71.0%,简单,292 -1966,1900-1999,1966. 未排序数组中的可被二分搜索的数,未排序数组中的可被二分搜索的数,https://leetcode.cn/problems/binary-searchable-numbers-in-an-unsorted-array/,binary-searchable-numbers-in-an-unsorted-array,数组、二分查找,https://algo.itcharge.cn/Solutions/1900-1999/binary-searchable-numbers-in-an-unsorted-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1966.%20%E6%9C%AA%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%8F%AF%E8%A2%AB%E4%BA%8C%E5%88%86%E6%90%9C%E7%B4%A2%E7%9A%84%E6%95%B0.md,64.4%,中等,10 -1967,1900-1999,1967. 作为子字符串出现在单词中的字符串数目,作为子字符串出现在单词中的字符串数目,https://leetcode.cn/problems/number-of-strings-that-appear-as-substrings-in-word/,number-of-strings-that-appear-as-substrings-in-word,字符串,https://algo.itcharge.cn/Solutions/1900-1999/number-of-strings-that-appear-as-substrings-in-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1967.%20%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%87%BA%E7%8E%B0%E5%9C%A8%E5%8D%95%E8%AF%8D%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md,79.3%,简单,159 -1968,1900-1999,1968. 构造元素不等于两相邻元素平均值的数组,构造元素不等于两相邻元素平均值的数组,https://leetcode.cn/problems/array-with-elements-not-equal-to-average-of-neighbors/,array-with-elements-not-equal-to-average-of-neighbors,贪心、数组、排序,https://algo.itcharge.cn/Solutions/1900-1999/array-with-elements-not-equal-to-average-of-neighbors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1968.%20%E6%9E%84%E9%80%A0%E5%85%83%E7%B4%A0%E4%B8%8D%E7%AD%89%E4%BA%8E%E4%B8%A4%E7%9B%B8%E9%82%BB%E5%85%83%E7%B4%A0%E5%B9%B3%E5%9D%87%E5%80%BC%E7%9A%84%E6%95%B0%E7%BB%84.md,39.2%,中等,97 -1969,1900-1999,1969. 数组元素的最小非零乘积,数组元素的最小非零乘积,https://leetcode.cn/problems/minimum-non-zero-product-of-the-array-elements/,minimum-non-zero-product-of-the-array-elements,贪心、递归、数学,https://algo.itcharge.cn/Solutions/1900-1999/minimum-non-zero-product-of-the-array-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1969.%20%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%B0%8F%E9%9D%9E%E9%9B%B6%E4%B9%98%E7%A7%AF.md,29.8%,中等,57 -1970,1900-1999,1970. 你能穿过矩阵的最后一天,你能穿过矩阵的最后一天,https://leetcode.cn/problems/last-day-where-you-can-still-cross/,last-day-where-you-can-still-cross,深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/last-day-where-you-can-still-cross/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1970.%20%E4%BD%A0%E8%83%BD%E7%A9%BF%E8%BF%87%E7%9F%A9%E9%98%B5%E7%9A%84%E6%9C%80%E5%90%8E%E4%B8%80%E5%A4%A9.md,50.5%,困难,71 -1971,1900-1999,1971. 寻找图中是否存在路径,寻找图中是否存在路径,https://leetcode.cn/problems/find-if-path-exists-in-graph/,find-if-path-exists-in-graph,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/1900-1999/find-if-path-exists-in-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1971.%20%E5%AF%BB%E6%89%BE%E5%9B%BE%E4%B8%AD%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E8%B7%AF%E5%BE%84.md,54.2%,简单,316 -1972,1900-1999,1972. 同一天的第一个电话和最后一个电话,同一天的第一个电话和最后一个电话,https://leetcode.cn/problems/first-and-last-call-on-the-same-day/,first-and-last-call-on-the-same-day,数据库,https://algo.itcharge.cn/Solutions/1900-1999/first-and-last-call-on-the-same-day/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1972.%20%E5%90%8C%E4%B8%80%E5%A4%A9%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E7%94%B5%E8%AF%9D%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E7%94%B5%E8%AF%9D.md,42.5%,困难,46 -1973,1900-1999,1973. 值等于子节点值之和的节点数量,值等于子节点值之和的节点数量,https://leetcode.cn/problems/count-nodes-equal-to-sum-of-descendants/,count-nodes-equal-to-sum-of-descendants,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/1900-1999/count-nodes-equal-to-sum-of-descendants/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1973.%20%E5%80%BC%E7%AD%89%E4%BA%8E%E5%AD%90%E8%8A%82%E7%82%B9%E5%80%BC%E4%B9%8B%E5%92%8C%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F.md,60.2%,中等,21 -1974,1900-1999,1974. 使用特殊打字机键入单词的最少时间,使用特殊打字机键入单词的最少时间,https://leetcode.cn/problems/minimum-time-to-type-word-using-special-typewriter/,minimum-time-to-type-word-using-special-typewriter,贪心、字符串,https://algo.itcharge.cn/Solutions/1900-1999/minimum-time-to-type-word-using-special-typewriter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1974.%20%E4%BD%BF%E7%94%A8%E7%89%B9%E6%AE%8A%E6%89%93%E5%AD%97%E6%9C%BA%E9%94%AE%E5%85%A5%E5%8D%95%E8%AF%8D%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,71.3%,简单,142 -1975,1900-1999,1975. 最大方阵和,最大方阵和,https://leetcode.cn/problems/maximum-matrix-sum/,maximum-matrix-sum,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/maximum-matrix-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1975.%20%E6%9C%80%E5%A4%A7%E6%96%B9%E9%98%B5%E5%92%8C.md,41.6%,中等,76 -1976,1900-1999,1976. 到达目的地的方案数,到达目的地的方案数,https://leetcode.cn/problems/number-of-ways-to-arrive-at-destination/,number-of-ways-to-arrive-at-destination,图、拓扑排序、动态规划、最短路,https://algo.itcharge.cn/Solutions/1900-1999/number-of-ways-to-arrive-at-destination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1976.%20%E5%88%B0%E8%BE%BE%E7%9B%AE%E7%9A%84%E5%9C%B0%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,36.5%,中等,107 -1977,1900-1999,1977. 划分数字的方案数,划分数字的方案数,https://leetcode.cn/problems/number-of-ways-to-separate-numbers/,number-of-ways-to-separate-numbers,字符串、动态规划、后缀数组,https://algo.itcharge.cn/Solutions/1900-1999/number-of-ways-to-separate-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1977.%20%E5%88%92%E5%88%86%E6%95%B0%E5%AD%97%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,30.1%,困难,28 -1978,1900-1999,1978. 上级经理已离职的公司员工,上级经理已离职的公司员工,https://leetcode.cn/problems/employees-whose-manager-left-the-company/,employees-whose-manager-left-the-company,数据库,https://algo.itcharge.cn/Solutions/1900-1999/employees-whose-manager-left-the-company/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1978.%20%E4%B8%8A%E7%BA%A7%E7%BB%8F%E7%90%86%E5%B7%B2%E7%A6%BB%E8%81%8C%E7%9A%84%E5%85%AC%E5%8F%B8%E5%91%98%E5%B7%A5.md,50.1%,简单,49 -1979,1900-1999,1979. 找出数组的最大公约数,找出数组的最大公约数,https://leetcode.cn/problems/find-greatest-common-divisor-of-array/,find-greatest-common-divisor-of-array,数组、数学、数论,https://algo.itcharge.cn/Solutions/1900-1999/find-greatest-common-divisor-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1979.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0.md,77.1%,简单,272 -1980,1900-1999,1980. 找出不同的二进制字符串,找出不同的二进制字符串,https://leetcode.cn/problems/find-unique-binary-string/,find-unique-binary-string,数组、字符串、回溯,https://algo.itcharge.cn/Solutions/1900-1999/find-unique-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1980.%20%E6%89%BE%E5%87%BA%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.md,59.8%,中等,149 -1981,1900-1999,1981. 最小化目标值与所选元素的差,最小化目标值与所选元素的差,https://leetcode.cn/problems/minimize-the-difference-between-target-and-chosen-elements/,minimize-the-difference-between-target-and-chosen-elements,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/minimize-the-difference-between-target-and-chosen-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1981.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E7%9B%AE%E6%A0%87%E5%80%BC%E4%B8%8E%E6%89%80%E9%80%89%E5%85%83%E7%B4%A0%E7%9A%84%E5%B7%AE.md,33.2%,中等,108 -1982,1900-1999,1982. 从子集的和还原数组,从子集的和还原数组,https://leetcode.cn/problems/find-array-given-subset-sums/,find-array-given-subset-sums,数组、分治,https://algo.itcharge.cn/Solutions/1900-1999/find-array-given-subset-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1982.%20%E4%BB%8E%E5%AD%90%E9%9B%86%E7%9A%84%E5%92%8C%E8%BF%98%E5%8E%9F%E6%95%B0%E7%BB%84.md,48.4%,困难,28 -1983,1900-1999,1983. 范围和相等的最宽索引对,范围和相等的最宽索引对,https://leetcode.cn/problems/widest-pair-of-indices-with-equal-range-sum/,widest-pair-of-indices-with-equal-range-sum,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/1900-1999/widest-pair-of-indices-with-equal-range-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1983.%20%E8%8C%83%E5%9B%B4%E5%92%8C%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%AE%BD%E7%B4%A2%E5%BC%95%E5%AF%B9.md,47.7%,中等,13 -1984,1900-1999,1984. 学生分数的最小差值,学生分数的最小差值,https://leetcode.cn/problems/minimum-difference-between-highest-and-lowest-of-k-scores/,minimum-difference-between-highest-and-lowest-of-k-scores,数组、排序、滑动窗口,https://algo.itcharge.cn/Solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1984.%20%E5%AD%A6%E7%94%9F%E5%88%86%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC.md,61.8%,简单,549 -1985,1900-1999,1985. 找出数组中的第 K 大整数,找出数组中的第 K 大整数,https://leetcode.cn/problems/find-the-kth-largest-integer-in-the-array/,find-the-kth-largest-integer-in-the-array,数组、字符串、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/1900-1999/find-the-kth-largest-integer-in-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1985.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E6%95%B4%E6%95%B0.md,42.1%,中等,152 -1986,1900-1999,1986. 完成任务的最少工作时间段,完成任务的最少工作时间段,https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/,minimum-number-of-work-sessions-to-finish-the-tasks,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1986.%20%E5%AE%8C%E6%88%90%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%B7%A5%E4%BD%9C%E6%97%B6%E9%97%B4%E6%AE%B5.md,33.0%,中等,118 -1987,1900-1999,1987. 不同的好子序列数目,不同的好子序列数目,https://leetcode.cn/problems/number-of-unique-good-subsequences/,number-of-unique-good-subsequences,字符串、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/number-of-unique-good-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1987.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%A5%BD%E5%AD%90%E5%BA%8F%E5%88%97%E6%95%B0%E7%9B%AE.md,50.3%,困难,42 -1988,1900-1999,1988. 找出每所学校的最低分数要求,找出每所学校的最低分数要求,https://leetcode.cn/problems/find-cutoff-score-for-each-school/,find-cutoff-score-for-each-school,数据库,https://algo.itcharge.cn/Solutions/1900-1999/find-cutoff-score-for-each-school/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1988.%20%E6%89%BE%E5%87%BA%E6%AF%8F%E6%89%80%E5%AD%A6%E6%A0%A1%E7%9A%84%E6%9C%80%E4%BD%8E%E5%88%86%E6%95%B0%E8%A6%81%E6%B1%82.md,64.5%,中等,60 -1989,1900-1999,1989. 捉迷藏中可捕获的最大人数,捉迷藏中可捕获的最大人数,https://leetcode.cn/problems/maximum-number-of-people-that-can-be-caught-in-tag/,maximum-number-of-people-that-can-be-caught-in-tag,贪心、数组,https://algo.itcharge.cn/Solutions/1900-1999/maximum-number-of-people-that-can-be-caught-in-tag/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1989.%20%E6%8D%89%E8%BF%B7%E8%97%8F%E4%B8%AD%E5%8F%AF%E6%8D%95%E8%8E%B7%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BA%BA%E6%95%B0.md,55.8%,中等,14 -1990,1900-1999,1990. 统计实验的数量,统计实验的数量,https://leetcode.cn/problems/count-the-number-of-experiments/,count-the-number-of-experiments,数据库,https://algo.itcharge.cn/Solutions/1900-1999/count-the-number-of-experiments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1990.%20%E7%BB%9F%E8%AE%A1%E5%AE%9E%E9%AA%8C%E7%9A%84%E6%95%B0%E9%87%8F.md,47.0%,中等,28 -1991,1900-1999,1991. 找到数组的中间位置,找到数组的中间位置,https://leetcode.cn/problems/find-the-middle-index-in-array/,find-the-middle-index-in-array,数组、前缀和,https://algo.itcharge.cn/Solutions/1900-1999/find-the-middle-index-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1991.%20%E6%89%BE%E5%88%B0%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E9%97%B4%E4%BD%8D%E7%BD%AE.md,63.8%,简单,297 -1992,1900-1999,1992. 找到所有的农场组,找到所有的农场组,https://leetcode.cn/problems/find-all-groups-of-farmland/,find-all-groups-of-farmland,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/1900-1999/find-all-groups-of-farmland/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1992.%20%E6%89%BE%E5%88%B0%E6%89%80%E6%9C%89%E7%9A%84%E5%86%9C%E5%9C%BA%E7%BB%84.md,61.2%,中等,117 -1993,1900-1999,1993. 树上的操作,树上的操作,https://leetcode.cn/problems/operations-on-tree/,operations-on-tree,树、深度优先搜索、广度优先搜索、设计、哈希表,https://algo.itcharge.cn/Solutions/1900-1999/operations-on-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1993.%20%E6%A0%91%E4%B8%8A%E7%9A%84%E6%93%8D%E4%BD%9C.md,39.8%,中等,68 -1994,1900-1999,1994. 好子集的数目,好子集的数目,https://leetcode.cn/problems/the-number-of-good-subsets/,the-number-of-good-subsets,位运算、数组、数学、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/1900-1999/the-number-of-good-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1994.%20%E5%A5%BD%E5%AD%90%E9%9B%86%E7%9A%84%E6%95%B0%E7%9B%AE.md,56.1%,困难,190 -1995,1900-1999,1995. 统计特殊四元组,统计特殊四元组,https://leetcode.cn/problems/count-special-quadruplets/,count-special-quadruplets,数组、枚举,https://algo.itcharge.cn/Solutions/1900-1999/count-special-quadruplets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1995.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E5%9B%9B%E5%85%83%E7%BB%84.md,66.3%,简单,289 -1996,1900-1999,1996. 游戏中弱角色的数量,游戏中弱角色的数量,https://leetcode.cn/problems/the-number-of-weak-characters-in-the-game/,the-number-of-weak-characters-in-the-game,栈、贪心、数组、排序、单调栈,https://algo.itcharge.cn/Solutions/1900-1999/the-number-of-weak-characters-in-the-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1996.%20%E6%B8%B8%E6%88%8F%E4%B8%AD%E5%BC%B1%E8%A7%92%E8%89%B2%E7%9A%84%E6%95%B0%E9%87%8F.md,41.5%,中等,308 -1997,1900-1999,1997. 访问完所有房间的第一天,访问完所有房间的第一天,https://leetcode.cn/problems/first-day-where-you-have-been-in-all-the-rooms/,first-day-where-you-have-been-in-all-the-rooms,数组、动态规划,https://algo.itcharge.cn/Solutions/1900-1999/first-day-where-you-have-been-in-all-the-rooms/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1997.%20%E8%AE%BF%E9%97%AE%E5%AE%8C%E6%89%80%E6%9C%89%E6%88%BF%E9%97%B4%E7%9A%84%E7%AC%AC%E4%B8%80%E5%A4%A9.md,35.2%,中等,53 -1998,1900-1999,1998. 数组的最大公因数排序,数组的最大公因数排序,https://leetcode.cn/problems/gcd-sort-of-an-array/,gcd-sort-of-an-array,并查集、数组、数学、数论、排序,https://algo.itcharge.cn/Solutions/1900-1999/gcd-sort-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1998.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%AC%E5%9B%A0%E6%95%B0%E6%8E%92%E5%BA%8F.md,45.8%,困难,59 -1999,1900-1999,1999. 最小的仅由两个数组成的倍数,最小的仅由两个数组成的倍数,https://leetcode.cn/problems/smallest-greater-multiple-made-of-two-digits/,smallest-greater-multiple-made-of-two-digits,数学、枚举,https://algo.itcharge.cn/Solutions/1900-1999/smallest-greater-multiple-made-of-two-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1999.%20%E6%9C%80%E5%B0%8F%E7%9A%84%E4%BB%85%E7%94%B1%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%88%90%E7%9A%84%E5%80%8D%E6%95%B0.md,46.8%,中等,14 -2000,2000-2099,2000. 反转单词前缀,反转单词前缀,https://leetcode.cn/problems/reverse-prefix-of-word/,reverse-prefix-of-word,双指针、字符串,https://algo.itcharge.cn/Solutions/2000-2099/reverse-prefix-of-word/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2000.%20%E5%8F%8D%E8%BD%AC%E5%8D%95%E8%AF%8D%E5%89%8D%E7%BC%80.md,77.4%,简单,479 -2001,2000-2099,2001. 可互换矩形的组数,可互换矩形的组数,https://leetcode.cn/problems/number-of-pairs-of-interchangeable-rectangles/,number-of-pairs-of-interchangeable-rectangles,数组、哈希表、数学、计数、数论,https://algo.itcharge.cn/Solutions/2000-2099/number-of-pairs-of-interchangeable-rectangles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2001.%20%E5%8F%AF%E4%BA%92%E6%8D%A2%E7%9F%A9%E5%BD%A2%E7%9A%84%E7%BB%84%E6%95%B0.md,38.9%,中等,166 -2002,2000-2099,2002. 两个回文子序列长度的最大乘积,两个回文子序列长度的最大乘积,https://leetcode.cn/problems/maximum-product-of-the-length-of-two-palindromic-subsequences/,maximum-product-of-the-length-of-two-palindromic-subsequences,位运算、字符串、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/2000-2099/maximum-product-of-the-length-of-two-palindromic-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2002.%20%E4%B8%A4%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97%E9%95%BF%E5%BA%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,60.3%,中等,123 -2003,2000-2099,2003. 每棵子树内缺失的最小基因值,每棵子树内缺失的最小基因值,https://leetcode.cn/problems/smallest-missing-genetic-value-in-each-subtree/,smallest-missing-genetic-value-in-each-subtree,树、深度优先搜索、并查集、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/smallest-missing-genetic-value-in-each-subtree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2003.%20%E6%AF%8F%E6%A3%B5%E5%AD%90%E6%A0%91%E5%86%85%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%9C%80%E5%B0%8F%E5%9F%BA%E5%9B%A0%E5%80%BC.md,43.0%,困难,59 -2004,2000-2099,2004. 职员招聘人数,职员招聘人数,https://leetcode.cn/problems/the-number-of-seniors-and-juniors-to-join-the-company/,the-number-of-seniors-and-juniors-to-join-the-company,数据库,https://algo.itcharge.cn/Solutions/2000-2099/the-number-of-seniors-and-juniors-to-join-the-company/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2004.%20%E8%81%8C%E5%91%98%E6%8B%9B%E8%81%98%E4%BA%BA%E6%95%B0.md,41.9%,困难,32 -2005,2000-2099,2005. 斐波那契树的移除子树游戏,斐波那契树的移除子树游戏,https://leetcode.cn/problems/subtree-removal-game-with-fibonacci-tree/,subtree-removal-game-with-fibonacci-tree,树、数学、动态规划、二叉树、博弈,https://algo.itcharge.cn/Solutions/2000-2099/subtree-removal-game-with-fibonacci-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2005.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%A0%91%E7%9A%84%E7%A7%BB%E9%99%A4%E5%AD%90%E6%A0%91%E6%B8%B8%E6%88%8F.md,50.1%,困难,11 -2006,2000-2099,2006. 差的绝对值为 K 的数对数目,差的绝对值为 K 的数对数目,https://leetcode.cn/problems/count-number-of-pairs-with-absolute-difference-k/,count-number-of-pairs-with-absolute-difference-k,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2000-2099/count-number-of-pairs-with-absolute-difference-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2006.%20%E5%B7%AE%E7%9A%84%E7%BB%9D%E5%AF%B9%E5%80%BC%E4%B8%BA%20K%20%E7%9A%84%E6%95%B0%E5%AF%B9%E6%95%B0%E7%9B%AE.md,83.9%,简单,529 -2007,2000-2099,2007. 从双倍数组中还原原数组,从双倍数组中还原原数组,https://leetcode.cn/problems/find-original-array-from-doubled-array/,find-original-array-from-doubled-array,贪心、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/2000-2099/find-original-array-from-doubled-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2007.%20%E4%BB%8E%E5%8F%8C%E5%80%8D%E6%95%B0%E7%BB%84%E4%B8%AD%E8%BF%98%E5%8E%9F%E5%8E%9F%E6%95%B0%E7%BB%84.md,33.5%,中等,126 -2008,2000-2099,2008. 出租车的最大盈利,出租车的最大盈利,https://leetcode.cn/problems/maximum-earnings-from-taxi/,maximum-earnings-from-taxi,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/2000-2099/maximum-earnings-from-taxi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2008.%20%E5%87%BA%E7%A7%9F%E8%BD%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E7%9B%88%E5%88%A9.md,46.0%,中等,104 -2009,2000-2099,2009. 使数组连续的最少操作数,使数组连续的最少操作数,https://leetcode.cn/problems/minimum-number-of-operations-to-make-array-continuous/,minimum-number-of-operations-to-make-array-continuous,数组、二分查找,https://algo.itcharge.cn/Solutions/2000-2099/minimum-number-of-operations-to-make-array-continuous/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2009.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E8%BF%9E%E7%BB%AD%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,43.3%,困难,76 -2010,2000-2099,2010. 职员招聘人数 II,职员招聘人数 II,https://leetcode.cn/problems/the-number-of-seniors-and-juniors-to-join-the-company-ii/,the-number-of-seniors-and-juniors-to-join-the-company-ii,数据库,https://algo.itcharge.cn/Solutions/2000-2099/the-number-of-seniors-and-juniors-to-join-the-company-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2010.%20%E8%81%8C%E5%91%98%E6%8B%9B%E8%81%98%E4%BA%BA%E6%95%B0%20II.md,61.1%,困难,45 -2011,2000-2099,2011. 执行操作后的变量值,执行操作后的变量值,https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/,final-value-of-variable-after-performing-operations,数组、字符串、模拟,https://algo.itcharge.cn/Solutions/2000-2099/final-value-of-variable-after-performing-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2011.%20%E6%89%A7%E8%A1%8C%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E5%8F%98%E9%87%8F%E5%80%BC.md,86.9%,简单,403 -2012,2000-2099,2012. 数组美丽值求和,数组美丽值求和,https://leetcode.cn/problems/sum-of-beauty-in-the-array/,sum-of-beauty-in-the-array,数组,https://algo.itcharge.cn/Solutions/2000-2099/sum-of-beauty-in-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2012.%20%E6%95%B0%E7%BB%84%E7%BE%8E%E4%B8%BD%E5%80%BC%E6%B1%82%E5%92%8C.md,38.9%,中等,104 -2013,2000-2099,2013. 检测正方形,检测正方形,https://leetcode.cn/problems/detect-squares/,detect-squares,设计、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2000-2099/detect-squares/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2013.%20%E6%A3%80%E6%B5%8B%E6%AD%A3%E6%96%B9%E5%BD%A2.md,56.6%,中等,244 -2014,2000-2099,2014. 重复 K 次的最长子序列,重复 K 次的最长子序列,https://leetcode.cn/problems/longest-subsequence-repeated-k-times/,longest-subsequence-repeated-k-times,贪心、字符串、回溯、计数、枚举,https://algo.itcharge.cn/Solutions/2000-2099/longest-subsequence-repeated-k-times/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2014.%20%E9%87%8D%E5%A4%8D%20K%20%E6%AC%A1%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%BA%8F%E5%88%97.md,54.6%,困难,31 -2015,2000-2099,2015. 每段建筑物的平均高度,每段建筑物的平均高度,https://leetcode.cn/problems/average-height-of-buildings-in-each-segment/,average-height-of-buildings-in-each-segment,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2000-2099/average-height-of-buildings-in-each-segment/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2015.%20%E6%AF%8F%E6%AE%B5%E5%BB%BA%E7%AD%91%E7%89%A9%E7%9A%84%E5%B9%B3%E5%9D%87%E9%AB%98%E5%BA%A6.md,57.3%,中等,16 -2016,2000-2099,2016. 增量元素之间的最大差值,增量元素之间的最大差值,https://leetcode.cn/problems/maximum-difference-between-increasing-elements/,maximum-difference-between-increasing-elements,数组,https://algo.itcharge.cn/Solutions/2000-2099/maximum-difference-between-increasing-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2016.%20%E5%A2%9E%E9%87%8F%E5%85%83%E7%B4%A0%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B7%AE%E5%80%BC.md,59.7%,简单,506 -2017,2000-2099,2017. 网格游戏,网格游戏,https://leetcode.cn/problems/grid-game/,grid-game,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/grid-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2017.%20%E7%BD%91%E6%A0%BC%E6%B8%B8%E6%88%8F.md,38.5%,中等,127 -2018,2000-2099,2018. 判断单词是否能放入填字游戏内,判断单词是否能放入填字游戏内,https://leetcode.cn/problems/check-if-word-can-be-placed-in-crossword/,check-if-word-can-be-placed-in-crossword,数组、枚举、矩阵,https://algo.itcharge.cn/Solutions/2000-2099/check-if-word-can-be-placed-in-crossword/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2018.%20%E5%88%A4%E6%96%AD%E5%8D%95%E8%AF%8D%E6%98%AF%E5%90%A6%E8%83%BD%E6%94%BE%E5%85%A5%E5%A1%AB%E5%AD%97%E6%B8%B8%E6%88%8F%E5%86%85.md,42.2%,中等,68 -2019,2000-2099,2019. 解出数学表达式的学生分数,解出数学表达式的学生分数,https://leetcode.cn/problems/the-score-of-students-solving-math-expression/,the-score-of-students-solving-math-expression,栈、记忆化搜索、数组、数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/the-score-of-students-solving-math-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2019.%20%E8%A7%A3%E5%87%BA%E6%95%B0%E5%AD%A6%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%AD%A6%E7%94%9F%E5%88%86%E6%95%B0.md,37.6%,困难,45 -2020,2000-2099,2020. 无流量的帐户数,无流量的帐户数,https://leetcode.cn/problems/number-of-accounts-that-did-not-stream/,number-of-accounts-that-did-not-stream,数据库,https://algo.itcharge.cn/Solutions/2000-2099/number-of-accounts-that-did-not-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2020.%20%E6%97%A0%E6%B5%81%E9%87%8F%E7%9A%84%E5%B8%90%E6%88%B7%E6%95%B0.md,70.9%,中等,34 -2021,2000-2099,2021. 街上最亮的位置,街上最亮的位置,https://leetcode.cn/problems/brightest-position-on-street/,brightest-position-on-street,数组、有序集合、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/brightest-position-on-street/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2021.%20%E8%A1%97%E4%B8%8A%E6%9C%80%E4%BA%AE%E7%9A%84%E4%BD%8D%E7%BD%AE.md,63.0%,中等,23 -2022,2000-2099,2022. 将一维数组转变成二维数组,将一维数组转变成二维数组,https://leetcode.cn/problems/convert-1d-array-into-2d-array/,convert-1d-array-into-2d-array,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/2000-2099/convert-1d-array-into-2d-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2022.%20%E5%B0%86%E4%B8%80%E7%BB%B4%E6%95%B0%E7%BB%84%E8%BD%AC%E5%8F%98%E6%88%90%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84.md,65.5%,简单,483 -2023,2000-2099,2023. 连接后等于目标字符串的字符串对,连接后等于目标字符串的字符串对,https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/,number-of-pairs-of-strings-with-concatenation-equal-to-target,数组、字符串,https://algo.itcharge.cn/Solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2023.%20%E8%BF%9E%E6%8E%A5%E5%90%8E%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9.md,74.2%,中等,103 -2024,2000-2099,2024. 考试的最大困扰度,考试的最大困扰度,https://leetcode.cn/problems/maximize-the-confusion-of-an-exam/,maximize-the-confusion-of-an-exam,字符串、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/2000-2099/maximize-the-confusion-of-an-exam/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2024.%20%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%9B%B0%E6%89%B0%E5%BA%A6.md,57.3%,中等,441 -2025,2000-2099,2025. 分割数组的最多方案数,分割数组的最多方案数,https://leetcode.cn/problems/maximum-number-of-ways-to-partition-an-array/,maximum-number-of-ways-to-partition-an-array,数组、哈希表、计数、枚举、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/maximum-number-of-ways-to-partition-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2025.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%9A%E6%96%B9%E6%A1%88%E6%95%B0.md,30.4%,困难,63 -2026,2000-2099,2026. 低质量的问题,低质量的问题,https://leetcode.cn/problems/low-quality-problems/,low-quality-problems,数据库,https://algo.itcharge.cn/Solutions/2000-2099/low-quality-problems/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2026.%20%E4%BD%8E%E8%B4%A8%E9%87%8F%E7%9A%84%E9%97%AE%E9%A2%98.md,79.9%,简单,33 -2027,2000-2099,2027. 转换字符串的最少操作次数,转换字符串的最少操作次数,https://leetcode.cn/problems/minimum-moves-to-convert-string/,minimum-moves-to-convert-string,贪心、字符串,https://algo.itcharge.cn/Solutions/2000-2099/minimum-moves-to-convert-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2027.%20%E8%BD%AC%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,67.0%,简单,305 -2028,2000-2099,2028. 找出缺失的观测数据,找出缺失的观测数据,https://leetcode.cn/problems/find-missing-observations/,find-missing-observations,数组、数学、模拟,https://algo.itcharge.cn/Solutions/2000-2099/find-missing-observations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2028.%20%E6%89%BE%E5%87%BA%E7%BC%BA%E5%A4%B1%E7%9A%84%E8%A7%82%E6%B5%8B%E6%95%B0%E6%8D%AE.md,50.7%,中等,400 -2029,2000-2099,2029. 石子游戏 IX,石子游戏 IX,https://leetcode.cn/problems/stone-game-ix/,stone-game-ix,贪心、数组、数学、计数、博弈,https://algo.itcharge.cn/Solutions/2000-2099/stone-game-ix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2029.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F%20IX.md,46.9%,中等,198 -2030,2000-2099,2030. 含特定字母的最小子序列,含特定字母的最小子序列,https://leetcode.cn/problems/smallest-k-length-subsequence-with-occurrences-of-a-letter/,smallest-k-length-subsequence-with-occurrences-of-a-letter,栈、贪心、字符串、单调栈,https://algo.itcharge.cn/Solutions/2000-2099/smallest-k-length-subsequence-with-occurrences-of-a-letter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2030.%20%E5%90%AB%E7%89%B9%E5%AE%9A%E5%AD%97%E6%AF%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E5%BA%8F%E5%88%97.md,36.3%,困难,53 -2031,2000-2099,2031. 1 比 0 多的子数组个数,1 比 0 多的子数组个数,https://leetcode.cn/problems/count-subarrays-with-more-ones-than-zeros/,count-subarrays-with-more-ones-than-zeros,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/2000-2099/count-subarrays-with-more-ones-than-zeros/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2031.%201%20%E6%AF%94%200%20%E5%A4%9A%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md,55.2%,中等,22 -2032,2000-2099,2032. 至少在两个数组中出现的值,至少在两个数组中出现的值,https://leetcode.cn/problems/two-out-of-three/,two-out-of-three,数组、哈希表,https://algo.itcharge.cn/Solutions/2000-2099/two-out-of-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2032.%20%E8%87%B3%E5%B0%91%E5%9C%A8%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E7%9A%84%E5%80%BC.md,73.1%,简单,412 -2033,2000-2099,2033. 获取单值网格的最小操作数,获取单值网格的最小操作数,https://leetcode.cn/problems/minimum-operations-to-make-a-uni-value-grid/,minimum-operations-to-make-a-uni-value-grid,数组、数学、矩阵、排序,https://algo.itcharge.cn/Solutions/2000-2099/minimum-operations-to-make-a-uni-value-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2033.%20%E8%8E%B7%E5%8F%96%E5%8D%95%E5%80%BC%E7%BD%91%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md,43.2%,中等,123 -2034,2000-2099,2034. 股票价格波动,股票价格波动,https://leetcode.cn/problems/stock-price-fluctuation/,stock-price-fluctuation,设计、哈希表、数据流、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/2000-2099/stock-price-fluctuation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2034.%20%E8%82%A1%E7%A5%A8%E4%BB%B7%E6%A0%BC%E6%B3%A2%E5%8A%A8.md,45.8%,中等,244 -2035,2000-2099,2035. 将数组分成两个数组并最小化数组和的差,将数组分成两个数组并最小化数组和的差,https://leetcode.cn/problems/partition-array-into-two-arrays-to-minimize-sum-difference/,partition-array-into-two-arrays-to-minimize-sum-difference,位运算、数组、双指针、二分查找、动态规划、状态压缩、有序集合,https://algo.itcharge.cn/Solutions/2000-2099/partition-array-into-two-arrays-to-minimize-sum-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2035.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%86%E6%88%90%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E5%B9%B6%E6%9C%80%E5%B0%8F%E5%8C%96%E6%95%B0%E7%BB%84%E5%92%8C%E7%9A%84%E5%B7%AE.md,34.7%,困难,71 -2036,2000-2099,2036. 最大交替子数组和,最大交替子数组和,https://leetcode.cn/problems/maximum-alternating-subarray-sum/,maximum-alternating-subarray-sum,数组、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/maximum-alternating-subarray-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2036.%20%E6%9C%80%E5%A4%A7%E4%BA%A4%E6%9B%BF%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md,42.3%,中等,19 -2037,2000-2099,2037. 使每位学生都有座位的最少移动次数,使每位学生都有座位的最少移动次数,https://leetcode.cn/problems/minimum-number-of-moves-to-seat-everyone/,minimum-number-of-moves-to-seat-everyone,数组、排序,https://algo.itcharge.cn/Solutions/2000-2099/minimum-number-of-moves-to-seat-everyone/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2037.%20%E4%BD%BF%E6%AF%8F%E4%BD%8D%E5%AD%A6%E7%94%9F%E9%83%BD%E6%9C%89%E5%BA%A7%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%91%E7%A7%BB%E5%8A%A8%E6%AC%A1%E6%95%B0.md,85.1%,简单,260 -2038,2000-2099,2038. 如果相邻两个颜色均相同则删除当前颜色,如果相邻两个颜色均相同则删除当前颜色,https://leetcode.cn/problems/remove-colored-pieces-if-both-neighbors-are-the-same-color/,remove-colored-pieces-if-both-neighbors-are-the-same-color,贪心、数学、字符串、博弈,https://algo.itcharge.cn/Solutions/2000-2099/remove-colored-pieces-if-both-neighbors-are-the-same-color/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2038.%20%E5%A6%82%E6%9E%9C%E7%9B%B8%E9%82%BB%E4%B8%A4%E4%B8%AA%E9%A2%9C%E8%89%B2%E5%9D%87%E7%9B%B8%E5%90%8C%E5%88%99%E5%88%A0%E9%99%A4%E5%BD%93%E5%89%8D%E9%A2%9C%E8%89%B2.md,63.4%,中等,452 -2039,2000-2099,2039. 网络空闲的时刻,网络空闲的时刻,https://leetcode.cn/problems/the-time-when-the-network-becomes-idle/,the-time-when-the-network-becomes-idle,广度优先搜索、图、数组,https://algo.itcharge.cn/Solutions/2000-2099/the-time-when-the-network-becomes-idle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2039.%20%E7%BD%91%E7%BB%9C%E7%A9%BA%E9%97%B2%E7%9A%84%E6%97%B6%E5%88%BB.md,55.9%,中等,181 -2040,2000-2099,2040. 两个有序数组的第 K 小乘积,两个有序数组的第 K 小乘积,https://leetcode.cn/problems/kth-smallest-product-of-two-sorted-arrays/,kth-smallest-product-of-two-sorted-arrays,数组、二分查找,https://algo.itcharge.cn/Solutions/2000-2099/kth-smallest-product-of-two-sorted-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2040.%20%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E7%AC%AC%20K%20%E5%B0%8F%E4%B9%98%E7%A7%AF.md,33.4%,困难,35 -2041,2000-2099,2041. 面试中被录取的候选人,面试中被录取的候选人,https://leetcode.cn/problems/accepted-candidates-from-the-interviews/,accepted-candidates-from-the-interviews,数据库,https://algo.itcharge.cn/Solutions/2000-2099/accepted-candidates-from-the-interviews/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2041.%20%E9%9D%A2%E8%AF%95%E4%B8%AD%E8%A2%AB%E5%BD%95%E5%8F%96%E7%9A%84%E5%80%99%E9%80%89%E4%BA%BA.md,76.3%,中等,29 -2042,2000-2099,2042. 检查句子中的数字是否递增,检查句子中的数字是否递增,https://leetcode.cn/problems/check-if-numbers-are-ascending-in-a-sentence/,check-if-numbers-are-ascending-in-a-sentence,字符串,https://algo.itcharge.cn/Solutions/2000-2099/check-if-numbers-are-ascending-in-a-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2042.%20%E6%A3%80%E6%9F%A5%E5%8F%A5%E5%AD%90%E4%B8%AD%E7%9A%84%E6%95%B0%E5%AD%97%E6%98%AF%E5%90%A6%E9%80%92%E5%A2%9E.md,72.0%,简单,499 -2043,2000-2099,2043. 简易银行系统,简易银行系统,https://leetcode.cn/problems/simple-bank-system/,simple-bank-system,设计、数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2000-2099/simple-bank-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2043.%20%E7%AE%80%E6%98%93%E9%93%B6%E8%A1%8C%E7%B3%BB%E7%BB%9F.md,66.0%,中等,312 -2044,2000-2099,2044. 统计按位或能得到最大值的子集数目,统计按位或能得到最大值的子集数目,https://leetcode.cn/problems/count-number-of-maximum-bitwise-or-subsets/,count-number-of-maximum-bitwise-or-subsets,位运算、数组、回溯,https://algo.itcharge.cn/Solutions/2000-2099/count-number-of-maximum-bitwise-or-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2044.%20%E7%BB%9F%E8%AE%A1%E6%8C%89%E4%BD%8D%E6%88%96%E8%83%BD%E5%BE%97%E5%88%B0%E6%9C%80%E5%A4%A7%E5%80%BC%E7%9A%84%E5%AD%90%E9%9B%86%E6%95%B0%E7%9B%AE.md,81.8%,中等,400 -2045,2000-2099,2045. 到达目的地的第二短时间,到达目的地的第二短时间,https://leetcode.cn/problems/second-minimum-time-to-reach-destination/,second-minimum-time-to-reach-destination,广度优先搜索、图、最短路,https://algo.itcharge.cn/Solutions/2000-2099/second-minimum-time-to-reach-destination/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2045.%20%E5%88%B0%E8%BE%BE%E7%9B%AE%E7%9A%84%E5%9C%B0%E7%9A%84%E7%AC%AC%E4%BA%8C%E7%9F%AD%E6%97%B6%E9%97%B4.md,53.3%,困难,166 -2046,2000-2099,2046. 给按照绝对值排序的链表排序,给按照绝对值排序的链表排序,https://leetcode.cn/problems/sort-linked-list-already-sorted-using-absolute-values/,sort-linked-list-already-sorted-using-absolute-values,链表、双指针、排序,https://algo.itcharge.cn/Solutions/2000-2099/sort-linked-list-already-sorted-using-absolute-values/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2046.%20%E7%BB%99%E6%8C%89%E7%85%A7%E7%BB%9D%E5%AF%B9%E5%80%BC%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F.md,63.9%,中等,32 -2047,2000-2099,2047. 句子中的有效单词数,句子中的有效单词数,https://leetcode.cn/problems/number-of-valid-words-in-a-sentence/,number-of-valid-words-in-a-sentence,字符串,https://algo.itcharge.cn/Solutions/2000-2099/number-of-valid-words-in-a-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2047.%20%E5%8F%A5%E5%AD%90%E4%B8%AD%E7%9A%84%E6%9C%89%E6%95%88%E5%8D%95%E8%AF%8D%E6%95%B0.md,38.6%,简单,434 -2048,2000-2099,2048. 下一个更大的数值平衡数,下一个更大的数值平衡数,https://leetcode.cn/problems/next-greater-numerically-balanced-number/,next-greater-numerically-balanced-number,数学、回溯、枚举,https://algo.itcharge.cn/Solutions/2000-2099/next-greater-numerically-balanced-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2048.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E7%9A%84%E6%95%B0%E5%80%BC%E5%B9%B3%E8%A1%A1%E6%95%B0.md,45.4%,中等,121 -2049,2000-2099,2049. 统计最高分的节点数目,统计最高分的节点数目,https://leetcode.cn/problems/count-nodes-with-the-highest-score/,count-nodes-with-the-highest-score,树、深度优先搜索、数组、二叉树,https://algo.itcharge.cn/Solutions/2000-2099/count-nodes-with-the-highest-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2049.%20%E7%BB%9F%E8%AE%A1%E6%9C%80%E9%AB%98%E5%88%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0%E7%9B%AE.md,51.8%,中等,315 -2050,2000-2099,2050. 并行课程 III,并行课程 III,https://leetcode.cn/problems/parallel-courses-iii/,parallel-courses-iii,图、拓扑排序、数组、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/parallel-courses-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2050.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B%20III.md,58.5%,困难,108 -2051,2000-2099,2051. 商店中每个成员的级别,商店中每个成员的级别,https://leetcode.cn/problems/the-category-of-each-member-in-the-store/,the-category-of-each-member-in-the-store,数据库,https://algo.itcharge.cn/Solutions/2000-2099/the-category-of-each-member-in-the-store/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2051.%20%E5%95%86%E5%BA%97%E4%B8%AD%E6%AF%8F%E4%B8%AA%E6%88%90%E5%91%98%E7%9A%84%E7%BA%A7%E5%88%AB.md,65.2%,中等,21 -2052,2000-2099,2052. 将句子分隔成行的最低成本,将句子分隔成行的最低成本,https://leetcode.cn/problems/minimum-cost-to-separate-sentence-into-rows/,minimum-cost-to-separate-sentence-into-rows,数组、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/minimum-cost-to-separate-sentence-into-rows/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2052.%20%E5%B0%86%E5%8F%A5%E5%AD%90%E5%88%86%E9%9A%94%E6%88%90%E8%A1%8C%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md,48.0%,中等,19 -2053,2000-2099,2053. 数组中第 K 个独一无二的字符串,数组中第 K 个独一无二的字符串,https://leetcode.cn/problems/kth-distinct-string-in-an-array/,kth-distinct-string-in-an-array,数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2000-2099/kth-distinct-string-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2053.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%AC%AC%20K%20%E4%B8%AA%E7%8B%AC%E4%B8%80%E6%97%A0%E4%BA%8C%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,71.1%,简单,176 -2054,2000-2099,2054. 两个最好的不重叠活动,两个最好的不重叠活动,https://leetcode.cn/problems/two-best-non-overlapping-events/,two-best-non-overlapping-events,数组、二分查找、动态规划、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2000-2099/two-best-non-overlapping-events/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2054.%20%E4%B8%A4%E4%B8%AA%E6%9C%80%E5%A5%BD%E7%9A%84%E4%B8%8D%E9%87%8D%E5%8F%A0%E6%B4%BB%E5%8A%A8.md,38.2%,中等,91 -2055,2000-2099,2055. 蜡烛之间的盘子,蜡烛之间的盘子,https://leetcode.cn/problems/plates-between-candles/,plates-between-candles,数组、字符串、二分查找、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/plates-between-candles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2055.%20%E8%9C%A1%E7%83%9B%E4%B9%8B%E9%97%B4%E7%9A%84%E7%9B%98%E5%AD%90.md,43.4%,中等,438 -2056,2000-2099,2056. 棋盘上有效移动组合的数目,棋盘上有效移动组合的数目,https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard/,number-of-valid-move-combinations-on-chessboard,数组、字符串、回溯、模拟,https://algo.itcharge.cn/Solutions/2000-2099/number-of-valid-move-combinations-on-chessboard/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2056.%20%E6%A3%8B%E7%9B%98%E4%B8%8A%E6%9C%89%E6%95%88%E7%A7%BB%E5%8A%A8%E7%BB%84%E5%90%88%E7%9A%84%E6%95%B0%E7%9B%AE.md,58.6%,困难,22 -2057,2000-2099,2057. 值相等的最小索引,值相等的最小索引,https://leetcode.cn/problems/smallest-index-with-equal-value/,smallest-index-with-equal-value,数组,https://algo.itcharge.cn/Solutions/2000-2099/smallest-index-with-equal-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2057.%20%E5%80%BC%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B4%A2%E5%BC%95.md,75.4%,简单,122 -2058,2000-2099,2058. 找出临界点之间的最小和最大距离,找出临界点之间的最小和最大距离,https://leetcode.cn/problems/find-the-minimum-and-maximum-number-of-nodes-between-critical-points/,find-the-minimum-and-maximum-number-of-nodes-between-critical-points,链表,https://algo.itcharge.cn/Solutions/2000-2099/find-the-minimum-and-maximum-number-of-nodes-between-critical-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2058.%20%E6%89%BE%E5%87%BA%E4%B8%B4%E7%95%8C%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E6%9C%80%E5%B0%8F%E5%92%8C%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md,56.4%,中等,123 -2059,2000-2099,2059. 转化数字的最小运算数,转化数字的最小运算数,https://leetcode.cn/problems/minimum-operations-to-convert-number/,minimum-operations-to-convert-number,广度优先搜索、数组,https://algo.itcharge.cn/Solutions/2000-2099/minimum-operations-to-convert-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2059.%20%E8%BD%AC%E5%8C%96%E6%95%B0%E5%AD%97%E7%9A%84%E6%9C%80%E5%B0%8F%E8%BF%90%E7%AE%97%E6%95%B0.md,48.1%,中等,123 -2060,2000-2099,2060. 同源字符串检测,同源字符串检测,https://leetcode.cn/problems/check-if-an-original-string-exists-given-two-encoded-strings/,check-if-an-original-string-exists-given-two-encoded-strings,字符串、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/check-if-an-original-string-exists-given-two-encoded-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2060.%20%E5%90%8C%E6%BA%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%A3%80%E6%B5%8B.md,40.3%,困难,27 -2061,2000-2099,2061. 扫地机器人清扫过的空间个数,扫地机器人清扫过的空间个数,https://leetcode.cn/problems/number-of-spaces-cleaning-robot-cleaned/,number-of-spaces-cleaning-robot-cleaned,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/2000-2099/number-of-spaces-cleaning-robot-cleaned/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2061.%20%E6%89%AB%E5%9C%B0%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%B8%85%E6%89%AB%E8%BF%87%E7%9A%84%E7%A9%BA%E9%97%B4%E4%B8%AA%E6%95%B0.md,50.2%,中等,26 -2062,2000-2099,2062. 统计字符串中的元音子字符串,统计字符串中的元音子字符串,https://leetcode.cn/problems/count-vowel-substrings-of-a-string/,count-vowel-substrings-of-a-string,哈希表、字符串,https://algo.itcharge.cn/Solutions/2000-2099/count-vowel-substrings-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2062.%20%E7%BB%9F%E8%AE%A1%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,62.8%,简单,157 -2063,2000-2099,2063. 所有子字符串中的元音,所有子字符串中的元音,https://leetcode.cn/problems/vowels-of-all-substrings/,vowels-of-all-substrings,数学、字符串、动态规划、组合数学,https://algo.itcharge.cn/Solutions/2000-2099/vowels-of-all-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2063.%20%E6%89%80%E6%9C%89%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3.md,51.6%,中等,137 -2064,2000-2099,2064. 分配给商店的最多商品的最小值,分配给商店的最多商品的最小值,https://leetcode.cn/problems/minimized-maximum-of-products-distributed-to-any-store/,minimized-maximum-of-products-distributed-to-any-store,数组、二分查找,https://algo.itcharge.cn/Solutions/2000-2099/minimized-maximum-of-products-distributed-to-any-store/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2064.%20%E5%88%86%E9%85%8D%E7%BB%99%E5%95%86%E5%BA%97%E7%9A%84%E6%9C%80%E5%A4%9A%E5%95%86%E5%93%81%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,45.0%,中等,106 -2065,2000-2099,2065. 最大化一张图中的路径价值,最大化一张图中的路径价值,https://leetcode.cn/problems/maximum-path-quality-of-a-graph/,maximum-path-quality-of-a-graph,图、数组、回溯,https://algo.itcharge.cn/Solutions/2000-2099/maximum-path-quality-of-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2065.%20%E6%9C%80%E5%A4%A7%E5%8C%96%E4%B8%80%E5%BC%A0%E5%9B%BE%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84%E4%BB%B7%E5%80%BC.md,54.2%,困难,68 -2066,2000-2099,2066. 账户余额,账户余额,https://leetcode.cn/problems/account-balance/,account-balance,数据库,https://algo.itcharge.cn/Solutions/2000-2099/account-balance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2066.%20%E8%B4%A6%E6%88%B7%E4%BD%99%E9%A2%9D.md,78.7%,中等,28 -2067,2000-2099,2067. 等计数子串的数量,等计数子串的数量,https://leetcode.cn/problems/number-of-equal-count-substrings/,number-of-equal-count-substrings,字符串、计数、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/number-of-equal-count-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2067.%20%E7%AD%89%E8%AE%A1%E6%95%B0%E5%AD%90%E4%B8%B2%E7%9A%84%E6%95%B0%E9%87%8F.md,54.5%,中等,12 -2068,2000-2099,2068. 检查两个字符串是否几乎相等,检查两个字符串是否几乎相等,https://leetcode.cn/problems/check-whether-two-strings-are-almost-equivalent/,check-whether-two-strings-are-almost-equivalent,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2000-2099/check-whether-two-strings-are-almost-equivalent/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2068.%20%E6%A3%80%E6%9F%A5%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E5%87%A0%E4%B9%8E%E7%9B%B8%E7%AD%89.md,70.0%,简单,156 -2069,2000-2099,2069. 模拟行走机器人 II,模拟行走机器人 II,https://leetcode.cn/problems/walking-robot-simulation-ii/,walking-robot-simulation-ii,设计、模拟,https://algo.itcharge.cn/Solutions/2000-2099/walking-robot-simulation-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2069.%20%E6%A8%A1%E6%8B%9F%E8%A1%8C%E8%B5%B0%E6%9C%BA%E5%99%A8%E4%BA%BA%20II.md,22.1%,中等,113 -2070,2000-2099,2070. 每一个查询的最大美丽值,每一个查询的最大美丽值,https://leetcode.cn/problems/most-beautiful-item-for-each-query/,most-beautiful-item-for-each-query,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2000-2099/most-beautiful-item-for-each-query/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2070.%20%E6%AF%8F%E4%B8%80%E4%B8%AA%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%9C%80%E5%A4%A7%E7%BE%8E%E4%B8%BD%E5%80%BC.md,45.1%,中等,117 -2071,2000-2099,2071. 你可以安排的最多任务数目,你可以安排的最多任务数目,https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign/,maximum-number-of-tasks-you-can-assign,贪心、队列、数组、二分查找、排序、单调队列,https://algo.itcharge.cn/Solutions/2000-2099/maximum-number-of-tasks-you-can-assign/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2071.%20%E4%BD%A0%E5%8F%AF%E4%BB%A5%E5%AE%89%E6%8E%92%E7%9A%84%E6%9C%80%E5%A4%9A%E4%BB%BB%E5%8A%A1%E6%95%B0%E7%9B%AE.md,29.9%,困难,51 -2072,2000-2099,2072. 赢得比赛的大学,赢得比赛的大学,https://leetcode.cn/problems/the-winner-university/,the-winner-university,数据库,https://algo.itcharge.cn/Solutions/2000-2099/the-winner-university/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2072.%20%E8%B5%A2%E5%BE%97%E6%AF%94%E8%B5%9B%E7%9A%84%E5%A4%A7%E5%AD%A6.md,68.9%,简单,31 -2073,2000-2099,2073. 买票需要的时间,买票需要的时间,https://leetcode.cn/problems/time-needed-to-buy-tickets/,time-needed-to-buy-tickets,队列、数组、模拟,https://algo.itcharge.cn/Solutions/2000-2099/time-needed-to-buy-tickets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2073.%20%E4%B9%B0%E7%A5%A8%E9%9C%80%E8%A6%81%E7%9A%84%E6%97%B6%E9%97%B4.md,61.7%,简单,220 -2074,2000-2099,2074. 反转偶数长度组的节点,反转偶数长度组的节点,https://leetcode.cn/problems/reverse-nodes-in-even-length-groups/,reverse-nodes-in-even-length-groups,链表,https://algo.itcharge.cn/Solutions/2000-2099/reverse-nodes-in-even-length-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2074.%20%E5%8F%8D%E8%BD%AC%E5%81%B6%E6%95%B0%E9%95%BF%E5%BA%A6%E7%BB%84%E7%9A%84%E8%8A%82%E7%82%B9.md,45.7%,中等,158 -2075,2000-2099,2075. 解码斜向换位密码,解码斜向换位密码,https://leetcode.cn/problems/decode-the-slanted-ciphertext/,decode-the-slanted-ciphertext,字符串、模拟,https://algo.itcharge.cn/Solutions/2000-2099/decode-the-slanted-ciphertext/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2075.%20%E8%A7%A3%E7%A0%81%E6%96%9C%E5%90%91%E6%8D%A2%E4%BD%8D%E5%AF%86%E7%A0%81.md,46.9%,中等,86 -2076,2000-2099,2076. 处理含限制条件的好友请求,处理含限制条件的好友请求,https://leetcode.cn/problems/process-restricted-friend-requests/,process-restricted-friend-requests,并查集、图,https://algo.itcharge.cn/Solutions/2000-2099/process-restricted-friend-requests/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2076.%20%E5%A4%84%E7%90%86%E5%90%AB%E9%99%90%E5%88%B6%E6%9D%A1%E4%BB%B6%E7%9A%84%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82.md,51.0%,困难,89 -2077,2000-2099,2077. 殊途同归,殊途同归,https://leetcode.cn/problems/paths-in-maze-that-lead-to-same-room/,paths-in-maze-that-lead-to-same-room,图,https://algo.itcharge.cn/Solutions/2000-2099/paths-in-maze-that-lead-to-same-room/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2077.%20%E6%AE%8A%E9%80%94%E5%90%8C%E5%BD%92.md,62.9%,中等,9 -2078,2000-2099,2078. 两栋颜色不同且距离最远的房子,两栋颜色不同且距离最远的房子,https://leetcode.cn/problems/two-furthest-houses-with-different-colors/,two-furthest-houses-with-different-colors,贪心、数组,https://algo.itcharge.cn/Solutions/2000-2099/two-furthest-houses-with-different-colors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2078.%20%E4%B8%A4%E6%A0%8B%E9%A2%9C%E8%89%B2%E4%B8%8D%E5%90%8C%E4%B8%94%E8%B7%9D%E7%A6%BB%E6%9C%80%E8%BF%9C%E7%9A%84%E6%88%BF%E5%AD%90.md,72.3%,简单,208 -2079,2000-2099,2079. 给植物浇水,给植物浇水,https://leetcode.cn/problems/watering-plants/,watering-plants,数组,https://algo.itcharge.cn/Solutions/2000-2099/watering-plants/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2079.%20%E7%BB%99%E6%A4%8D%E7%89%A9%E6%B5%87%E6%B0%B4.md,77.7%,中等,161 -2080,2000-2099,2080. 区间内查询数字的频率,区间内查询数字的频率,https://leetcode.cn/problems/range-frequency-queries/,range-frequency-queries,设计、线段树、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/2000-2099/range-frequency-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2080.%20%E5%8C%BA%E9%97%B4%E5%86%85%E6%9F%A5%E8%AF%A2%E6%95%B0%E5%AD%97%E7%9A%84%E9%A2%91%E7%8E%87.md,31.4%,中等,179 -2081,2000-2099,2081. k 镜像数字的和,k 镜像数字的和,https://leetcode.cn/problems/sum-of-k-mirror-numbers/,sum-of-k-mirror-numbers,数学、枚举,https://algo.itcharge.cn/Solutions/2000-2099/sum-of-k-mirror-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2081.%20k%20%E9%95%9C%E5%83%8F%E6%95%B0%E5%AD%97%E7%9A%84%E5%92%8C.md,43.5%,困难,84 -2082,2000-2099,2082. 富有客户的数量,富有客户的数量,https://leetcode.cn/problems/the-number-of-rich-customers/,the-number-of-rich-customers,数据库,https://algo.itcharge.cn/Solutions/2000-2099/the-number-of-rich-customers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2082.%20%E5%AF%8C%E6%9C%89%E5%AE%A2%E6%88%B7%E7%9A%84%E6%95%B0%E9%87%8F.md,74.1%,简单,25 -2083,2000-2099,2083. 求以相同字母开头和结尾的子串总数,求以相同字母开头和结尾的子串总数,https://leetcode.cn/problems/substrings-that-begin-and-end-with-the-same-letter/,substrings-that-begin-and-end-with-the-same-letter,哈希表、数学、字符串、计数、前缀和,https://algo.itcharge.cn/Solutions/2000-2099/substrings-that-begin-and-end-with-the-same-letter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2083.%20%E6%B1%82%E4%BB%A5%E7%9B%B8%E5%90%8C%E5%AD%97%E6%AF%8D%E5%BC%80%E5%A4%B4%E5%92%8C%E7%BB%93%E5%B0%BE%E7%9A%84%E5%AD%90%E4%B8%B2%E6%80%BB%E6%95%B0.md,60.9%,中等,16 -2084,2000-2099,2084. 为订单类型为 0 的客户删除类型为 1 的订单,为订单类型为 0 的客户删除类型为 1 的订单,https://leetcode.cn/problems/drop-type-1-orders-for-customers-with-type-0-orders/,drop-type-1-orders-for-customers-with-type-0-orders,数据库,https://algo.itcharge.cn/Solutions/2000-2099/drop-type-1-orders-for-customers-with-type-0-orders/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2084.%20%E4%B8%BA%E8%AE%A2%E5%8D%95%E7%B1%BB%E5%9E%8B%E4%B8%BA%200%20%E7%9A%84%E5%AE%A2%E6%88%B7%E5%88%A0%E9%99%A4%E7%B1%BB%E5%9E%8B%E4%B8%BA%201%20%E7%9A%84%E8%AE%A2%E5%8D%95.md,82.4%,中等,40 -2085,2000-2099,2085. 统计出现过一次的公共字符串,统计出现过一次的公共字符串,https://leetcode.cn/problems/count-common-words-with-one-occurrence/,count-common-words-with-one-occurrence,数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2000-2099/count-common-words-with-one-occurrence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2085.%20%E7%BB%9F%E8%AE%A1%E5%87%BA%E7%8E%B0%E8%BF%87%E4%B8%80%E6%AC%A1%E7%9A%84%E5%85%AC%E5%85%B1%E5%AD%97%E7%AC%A6%E4%B8%B2.md,71.4%,简单,143 -2086,2000-2099,2086. 从房屋收集雨水需要的最少水桶数,从房屋收集雨水需要的最少水桶数,https://leetcode.cn/problems/minimum-number-of-food-buckets-to-feed-the-hamsters/,minimum-number-of-food-buckets-to-feed-the-hamsters,贪心、字符串、动态规划,https://algo.itcharge.cn/Solutions/2000-2099/minimum-number-of-food-buckets-to-feed-the-hamsters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2086.%20%E4%BB%8E%E6%88%BF%E5%B1%8B%E6%94%B6%E9%9B%86%E9%9B%A8%E6%B0%B4%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%B0%B4%E6%A1%B6%E6%95%B0.md,46.7%,中等,110 -2087,2000-2099,2087. 网格图中机器人回家的最小代价,网格图中机器人回家的最小代价,https://leetcode.cn/problems/minimum-cost-homecoming-of-a-robot-in-a-grid/,minimum-cost-homecoming-of-a-robot-in-a-grid,贪心、数组、矩阵,https://algo.itcharge.cn/Solutions/2000-2099/minimum-cost-homecoming-of-a-robot-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2087.%20%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%9C%BA%E5%99%A8%E4%BA%BA%E5%9B%9E%E5%AE%B6%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7.md,50.1%,中等,88 -2088,2000-2099,2088. 统计农场中肥沃金字塔的数目,统计农场中肥沃金字塔的数目,https://leetcode.cn/problems/count-fertile-pyramids-in-a-land/,count-fertile-pyramids-in-a-land,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2000-2099/count-fertile-pyramids-in-a-land/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2088.%20%E7%BB%9F%E8%AE%A1%E5%86%9C%E5%9C%BA%E4%B8%AD%E8%82%A5%E6%B2%83%E9%87%91%E5%AD%97%E5%A1%94%E7%9A%84%E6%95%B0%E7%9B%AE.md,63.4%,困难,57 -2089,2000-2099,2089. 找出数组排序后的目标下标,找出数组排序后的目标下标,https://leetcode.cn/problems/find-target-indices-after-sorting-array/,find-target-indices-after-sorting-array,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2000-2099/find-target-indices-after-sorting-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2089.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F%E5%90%8E%E7%9A%84%E7%9B%AE%E6%A0%87%E4%B8%8B%E6%A0%87.md,78.6%,简单,241 -2090,2000-2099,2090. 半径为 k 的子数组平均值,半径为 k 的子数组平均值,https://leetcode.cn/problems/k-radius-subarray-averages/,k-radius-subarray-averages,数组、滑动窗口,https://algo.itcharge.cn/Solutions/2000-2099/k-radius-subarray-averages/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2090.%20%E5%8D%8A%E5%BE%84%E4%B8%BA%20k%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E5%B9%B3%E5%9D%87%E5%80%BC.md,37.5%,中等,120 -2091,2000-2099,2091. 从数组中移除最大值和最小值,从数组中移除最大值和最小值,https://leetcode.cn/problems/removing-minimum-and-maximum-from-array/,removing-minimum-and-maximum-from-array,贪心、数组,https://algo.itcharge.cn/Solutions/2000-2099/removing-minimum-and-maximum-from-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2091.%20%E4%BB%8E%E6%95%B0%E7%BB%84%E4%B8%AD%E7%A7%BB%E9%99%A4%E6%9C%80%E5%A4%A7%E5%80%BC%E5%92%8C%E6%9C%80%E5%B0%8F%E5%80%BC.md,56.6%,中等,150 -2092,2000-2099,2092. 找出知晓秘密的所有专家,找出知晓秘密的所有专家,https://leetcode.cn/problems/find-all-people-with-secret/,find-all-people-with-secret,深度优先搜索、广度优先搜索、并查集、图、排序,https://algo.itcharge.cn/Solutions/2000-2099/find-all-people-with-secret/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2092.%20%E6%89%BE%E5%87%BA%E7%9F%A5%E6%99%93%E7%A7%98%E5%AF%86%E7%9A%84%E6%89%80%E6%9C%89%E4%B8%93%E5%AE%B6.md,29.1%,困难,131 -2093,2000-2099,2093. 前往目标城市的最小费用,前往目标城市的最小费用,https://leetcode.cn/problems/minimum-cost-to-reach-city-with-discounts/,minimum-cost-to-reach-city-with-discounts,图、最短路,https://algo.itcharge.cn/Solutions/2000-2099/minimum-cost-to-reach-city-with-discounts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2093.%20%E5%89%8D%E5%BE%80%E7%9B%AE%E6%A0%87%E5%9F%8E%E5%B8%82%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8.md,58.9%,中等,20 -2094,2000-2099,2094. 找出 3 位偶数,找出 3 位偶数,https://leetcode.cn/problems/finding-3-digit-even-numbers/,finding-3-digit-even-numbers,数组、哈希表、枚举、排序,https://algo.itcharge.cn/Solutions/2000-2099/finding-3-digit-even-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2094.%20%E6%89%BE%E5%87%BA%203%20%E4%BD%8D%E5%81%B6%E6%95%B0.md,55.8%,简单,188 -2095,2000-2099,2095. 删除链表的中间节点,删除链表的中间节点,https://leetcode.cn/problems/delete-the-middle-node-of-a-linked-list/,delete-the-middle-node-of-a-linked-list,链表、双指针,https://algo.itcharge.cn/Solutions/2000-2099/delete-the-middle-node-of-a-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2095.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E8%8A%82%E7%82%B9.md,57.5%,中等,222 -2096,2000-2099,2096. 从二叉树一个节点到另一个节点每一步的方向,从二叉树一个节点到另一个节点每一步的方向,https://leetcode.cn/problems/step-by-step-directions-from-a-binary-tree-node-to-another/,step-by-step-directions-from-a-binary-tree-node-to-another,树、深度优先搜索、字符串、二叉树,https://algo.itcharge.cn/Solutions/2000-2099/step-by-step-directions-from-a-binary-tree-node-to-another/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2096.%20%E4%BB%8E%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%A6%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%E6%AF%8F%E4%B8%80%E6%AD%A5%E7%9A%84%E6%96%B9%E5%90%91.md,44.6%,中等,210 -2097,2000-2099,2097. 合法重新排列数对,合法重新排列数对,https://leetcode.cn/problems/valid-arrangement-of-pairs/,valid-arrangement-of-pairs,深度优先搜索、图、欧拉回路,https://algo.itcharge.cn/Solutions/2000-2099/valid-arrangement-of-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2097.%20%E5%90%88%E6%B3%95%E9%87%8D%E6%96%B0%E6%8E%92%E5%88%97%E6%95%B0%E5%AF%B9.md,38.0%,困难,56 -2098,2000-2099,2098. 长度为 K 的最大偶数和子序列,长度为 K 的最大偶数和子序列,https://leetcode.cn/problems/subsequence-of-size-k-with-the-largest-even-sum/,subsequence-of-size-k-with-the-largest-even-sum,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2000-2099/subsequence-of-size-k-with-the-largest-even-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2098.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%81%B6%E6%95%B0%E5%92%8C%E5%AD%90%E5%BA%8F%E5%88%97.md,35.0%,中等,11 -2099,2000-2099,2099. 找到和最大的长度为 K 的子序列,找到和最大的长度为 K 的子序列,https://leetcode.cn/problems/find-subsequence-of-length-k-with-the-largest-sum/,find-subsequence-of-length-k-with-the-largest-sum,数组、哈希表、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2000-2099/find-subsequence-of-length-k-with-the-largest-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2099.%20%E6%89%BE%E5%88%B0%E5%92%8C%E6%9C%80%E5%A4%A7%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md,48.5%,简单,189 -2100,2100-2199,2100. 适合打劫银行的日子,适合打劫银行的日子,https://leetcode.cn/problems/find-good-days-to-rob-the-bank/,find-good-days-to-rob-the-bank,数组、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2100-2199/find-good-days-to-rob-the-bank/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2100.%20%E9%80%82%E5%90%88%E6%89%93%E5%8A%AB%E9%93%B6%E8%A1%8C%E7%9A%84%E6%97%A5%E5%AD%90.md,48.7%,中等,433 -2101,2100-2199,2101. 引爆最多的炸弹,引爆最多的炸弹,https://leetcode.cn/problems/detonate-the-maximum-bombs/,detonate-the-maximum-bombs,深度优先搜索、广度优先搜索、图、几何、数组、数学,https://algo.itcharge.cn/Solutions/2100-2199/detonate-the-maximum-bombs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2101.%20%E5%BC%95%E7%88%86%E6%9C%80%E5%A4%9A%E7%9A%84%E7%82%B8%E5%BC%B9.md,39.7%,中等,104 -2102,2100-2199,2102. 序列顺序查询,序列顺序查询,https://leetcode.cn/problems/sequentially-ordinal-rank-tracker/,sequentially-ordinal-rank-tracker,设计、数据流、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/2100-2199/sequentially-ordinal-rank-tracker/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2102.%20%E5%BA%8F%E5%88%97%E9%A1%BA%E5%BA%8F%E6%9F%A5%E8%AF%A2.md,55.9%,困难,87 -2103,2100-2199,2103. 环和杆,环和杆,https://leetcode.cn/problems/rings-and-rods/,rings-and-rods,哈希表、字符串,https://algo.itcharge.cn/Solutions/2100-2199/rings-and-rods/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2103.%20%E7%8E%AF%E5%92%8C%E6%9D%86.md,79.7%,简单,198 -2104,2100-2199,2104. 子数组范围和,子数组范围和,https://leetcode.cn/problems/sum-of-subarray-ranges/,sum-of-subarray-ranges,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/2100-2199/sum-of-subarray-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2104.%20%E5%AD%90%E6%95%B0%E7%BB%84%E8%8C%83%E5%9B%B4%E5%92%8C.md,62.9%,中等,403 -2105,2100-2199,2105. 给植物浇水 II,给植物浇水 II,https://leetcode.cn/problems/watering-plants-ii/,watering-plants-ii,数组、双指针、模拟,https://algo.itcharge.cn/Solutions/2100-2199/watering-plants-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2105.%20%E7%BB%99%E6%A4%8D%E7%89%A9%E6%B5%87%E6%B0%B4%20II.md,53.0%,中等,143 -2106,2100-2199,2106. 摘水果,摘水果,https://leetcode.cn/problems/maximum-fruits-harvested-after-at-most-k-steps/,maximum-fruits-harvested-after-at-most-k-steps,数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/2100-2199/maximum-fruits-harvested-after-at-most-k-steps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2106.%20%E6%91%98%E6%B0%B4%E6%9E%9C.md,45.1%,困难,202 -2107,2100-2199,2107. 分享 K 个糖果后独特口味的数量,分享 K 个糖果后独特口味的数量,https://leetcode.cn/problems/number-of-unique-flavors-after-sharing-k-candies/,number-of-unique-flavors-after-sharing-k-candies,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/2100-2199/number-of-unique-flavors-after-sharing-k-candies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2107.%20%E5%88%86%E4%BA%AB%20K%20%E4%B8%AA%E7%B3%96%E6%9E%9C%E5%90%8E%E7%8B%AC%E7%89%B9%E5%8F%A3%E5%91%B3%E7%9A%84%E6%95%B0%E9%87%8F.md,38.6%,中等,14 -2108,2100-2199,2108. 找出数组中的第一个回文字符串,找出数组中的第一个回文字符串,https://leetcode.cn/problems/find-first-palindromic-string-in-the-array/,find-first-palindromic-string-in-the-array,数组、双指针、字符串,https://algo.itcharge.cn/Solutions/2100-2199/find-first-palindromic-string-in-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2108.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md,77.5%,简单,210 -2109,2100-2199,2109. 向字符串添加空格,向字符串添加空格,https://leetcode.cn/problems/adding-spaces-to-a-string/,adding-spaces-to-a-string,数组、字符串、模拟,https://algo.itcharge.cn/Solutions/2100-2199/adding-spaces-to-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2109.%20%E5%90%91%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%B7%BB%E5%8A%A0%E7%A9%BA%E6%A0%BC.md,61.5%,中等,121 -2110,2100-2199,2110. 股票平滑下跌阶段的数目,股票平滑下跌阶段的数目,https://leetcode.cn/problems/number-of-smooth-descent-periods-of-a-stock/,number-of-smooth-descent-periods-of-a-stock,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/number-of-smooth-descent-periods-of-a-stock/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2110.%20%E8%82%A1%E7%A5%A8%E5%B9%B3%E6%BB%91%E4%B8%8B%E8%B7%8C%E9%98%B6%E6%AE%B5%E7%9A%84%E6%95%B0%E7%9B%AE.md,52.8%,中等,163 -2111,2100-2199,2111. 使数组 K 递增的最少操作次数,使数组 K 递增的最少操作次数,https://leetcode.cn/problems/minimum-operations-to-make-the-array-k-increasing/,minimum-operations-to-make-the-array-k-increasing,数组、二分查找,https://algo.itcharge.cn/Solutions/2100-2199/minimum-operations-to-make-the-array-k-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2111.%20%E4%BD%BF%E6%95%B0%E7%BB%84%20K%20%E9%80%92%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,35.0%,困难,97 -2112,2100-2199,2112. 最繁忙的机场,最繁忙的机场,https://leetcode.cn/problems/the-airport-with-the-most-traffic/,the-airport-with-the-most-traffic,数据库,https://algo.itcharge.cn/Solutions/2100-2199/the-airport-with-the-most-traffic/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2112.%20%E6%9C%80%E7%B9%81%E5%BF%99%E7%9A%84%E6%9C%BA%E5%9C%BA.md,68.3%,中等,22 -2113,2100-2199,2113. 查询删除和添加元素后的数组,查询删除和添加元素后的数组,https://leetcode.cn/problems/elements-in-array-after-removing-and-replacing-elements/,elements-in-array-after-removing-and-replacing-elements,数组,https://algo.itcharge.cn/Solutions/2100-2199/elements-in-array-after-removing-and-replacing-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2113.%20%E6%9F%A5%E8%AF%A2%E5%88%A0%E9%99%A4%E5%92%8C%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84.md,61.6%,中等,13 -2114,2100-2199,2114. 句子中的最多单词数,句子中的最多单词数,https://leetcode.cn/problems/maximum-number-of-words-found-in-sentences/,maximum-number-of-words-found-in-sentences,数组、字符串,https://algo.itcharge.cn/Solutions/2100-2199/maximum-number-of-words-found-in-sentences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2114.%20%E5%8F%A5%E5%AD%90%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%9A%E5%8D%95%E8%AF%8D%E6%95%B0.md,85.0%,简单,249 -2115,2100-2199,2115. 从给定原材料中找到所有可以做出的菜,从给定原材料中找到所有可以做出的菜,https://leetcode.cn/problems/find-all-possible-recipes-from-given-supplies/,find-all-possible-recipes-from-given-supplies,图、拓扑排序、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2100-2199/find-all-possible-recipes-from-given-supplies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2115.%20%E4%BB%8E%E7%BB%99%E5%AE%9A%E5%8E%9F%E6%9D%90%E6%96%99%E4%B8%AD%E6%89%BE%E5%88%B0%E6%89%80%E6%9C%89%E5%8F%AF%E4%BB%A5%E5%81%9A%E5%87%BA%E7%9A%84%E8%8F%9C.md,42.4%,中等,120 -2116,2100-2199,2116. 判断一个括号字符串是否有效,判断一个括号字符串是否有效,https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid/,check-if-a-parentheses-string-can-be-valid,栈、贪心、字符串,https://algo.itcharge.cn/Solutions/2100-2199/check-if-a-parentheses-string-can-be-valid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2116.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF%E5%90%A6%E6%9C%89%E6%95%88.md,32.0%,中等,75 -2117,2100-2199,2117. 一个区间内所有数乘积的缩写,一个区间内所有数乘积的缩写,https://leetcode.cn/problems/abbreviating-the-product-of-a-range/,abbreviating-the-product-of-a-range,数学,https://algo.itcharge.cn/Solutions/2100-2199/abbreviating-the-product-of-a-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2117.%20%E4%B8%80%E4%B8%AA%E5%8C%BA%E9%97%B4%E5%86%85%E6%89%80%E6%9C%89%E6%95%B0%E4%B9%98%E7%A7%AF%E7%9A%84%E7%BC%A9%E5%86%99.md,30.8%,困难,27 -2118,2100-2199,2118. 建立方程,建立方程,https://leetcode.cn/problems/build-the-equation/,build-the-equation,数据库,https://algo.itcharge.cn/Solutions/2100-2199/build-the-equation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2118.%20%E5%BB%BA%E7%AB%8B%E6%96%B9%E7%A8%8B.md,54.6%,困难,15 -2119,2100-2199,2119. 反转两次的数字,反转两次的数字,https://leetcode.cn/problems/a-number-after-a-double-reversal/,a-number-after-a-double-reversal,数学,https://algo.itcharge.cn/Solutions/2100-2199/a-number-after-a-double-reversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2119.%20%E5%8F%8D%E8%BD%AC%E4%B8%A4%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md,73.7%,简单,194 -2120,2100-2199,2120. 执行所有后缀指令,执行所有后缀指令,https://leetcode.cn/problems/execution-of-all-suffix-instructions-staying-in-a-grid/,execution-of-all-suffix-instructions-staying-in-a-grid,字符串、模拟,https://algo.itcharge.cn/Solutions/2100-2199/execution-of-all-suffix-instructions-staying-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2120.%20%E6%89%A7%E8%A1%8C%E6%89%80%E6%9C%89%E5%90%8E%E7%BC%80%E6%8C%87%E4%BB%A4.md,82.5%,中等,124 -2121,2100-2199,2121. 相同元素的间隔之和,相同元素的间隔之和,https://leetcode.cn/problems/intervals-between-identical-elements/,intervals-between-identical-elements,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/2100-2199/intervals-between-identical-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2121.%20%E7%9B%B8%E5%90%8C%E5%85%83%E7%B4%A0%E7%9A%84%E9%97%B4%E9%9A%94%E4%B9%8B%E5%92%8C.md,38.6%,中等,163 -2122,2100-2199,2122. 还原原数组,还原原数组,https://leetcode.cn/problems/recover-the-original-array/,recover-the-original-array,数组、哈希表、枚举、排序,https://algo.itcharge.cn/Solutions/2100-2199/recover-the-original-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2122.%20%E8%BF%98%E5%8E%9F%E5%8E%9F%E6%95%B0%E7%BB%84.md,42.0%,困难,69 -2123,2100-2199,2123. 使矩阵中的 1 互不相邻的最小操作数,使矩阵中的 1 互不相邻的最小操作数,https://leetcode.cn/problems/minimum-operations-to-remove-adjacent-ones-in-matrix/,minimum-operations-to-remove-adjacent-ones-in-matrix,图、数组、矩阵,https://algo.itcharge.cn/Solutions/2100-2199/minimum-operations-to-remove-adjacent-ones-in-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2123.%20%E4%BD%BF%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%201%20%E4%BA%92%E4%B8%8D%E7%9B%B8%E9%82%BB%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md,53.0%,困难,6 -2124,2100-2199,2124. 检查是否所有 A 都在 B 之前,检查是否所有 A 都在 B 之前,https://leetcode.cn/problems/check-if-all-as-appears-before-all-bs/,check-if-all-as-appears-before-all-bs,字符串,https://algo.itcharge.cn/Solutions/2100-2199/check-if-all-as-appears-before-all-bs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2124.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%20A%20%E9%83%BD%E5%9C%A8%20B%20%E4%B9%8B%E5%89%8D.md,69.4%,简单,213 -2125,2100-2199,2125. 银行中的激光束数量,银行中的激光束数量,https://leetcode.cn/problems/number-of-laser-beams-in-a-bank/,number-of-laser-beams-in-a-bank,数组、数学、字符串、矩阵,https://algo.itcharge.cn/Solutions/2100-2199/number-of-laser-beams-in-a-bank/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2125.%20%E9%93%B6%E8%A1%8C%E4%B8%AD%E7%9A%84%E6%BF%80%E5%85%89%E6%9D%9F%E6%95%B0%E9%87%8F.md,83.1%,中等,128 -2126,2100-2199,2126. 摧毁小行星,摧毁小行星,https://leetcode.cn/problems/destroying-asteroids/,destroying-asteroids,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/destroying-asteroids/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2126.%20%E6%91%A7%E6%AF%81%E5%B0%8F%E8%A1%8C%E6%98%9F.md,48.8%,中等,113 -2127,2100-2199,2127. 参加会议的最多员工数,参加会议的最多员工数,https://leetcode.cn/problems/maximum-employees-to-be-invited-to-a-meeting/,maximum-employees-to-be-invited-to-a-meeting,深度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/2100-2199/maximum-employees-to-be-invited-to-a-meeting/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2127.%20%E5%8F%82%E5%8A%A0%E4%BC%9A%E8%AE%AE%E7%9A%84%E6%9C%80%E5%A4%9A%E5%91%98%E5%B7%A5%E6%95%B0.md,34.1%,困难,68 -2128,2100-2199,2128. 通过翻转行或列来去除所有的 1,通过翻转行或列来去除所有的 1,https://leetcode.cn/problems/remove-all-ones-with-row-and-column-flips/,remove-all-ones-with-row-and-column-flips,位运算、数组、数学、矩阵,https://algo.itcharge.cn/Solutions/2100-2199/remove-all-ones-with-row-and-column-flips/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2128.%20%E9%80%9A%E8%BF%87%E7%BF%BB%E8%BD%AC%E8%A1%8C%E6%88%96%E5%88%97%E6%9D%A5%E5%8E%BB%E9%99%A4%E6%89%80%E6%9C%89%E7%9A%84%201.md,75.5%,中等,15 -2129,2100-2199,2129. 将标题首字母大写,将标题首字母大写,https://leetcode.cn/problems/capitalize-the-title/,capitalize-the-title,字符串,https://algo.itcharge.cn/Solutions/2100-2199/capitalize-the-title/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2129.%20%E5%B0%86%E6%A0%87%E9%A2%98%E9%A6%96%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%86%99.md,60.7%,简单,157 -2130,2100-2199,2130. 链表最大孪生和,链表最大孪生和,https://leetcode.cn/problems/maximum-twin-sum-of-a-linked-list/,maximum-twin-sum-of-a-linked-list,栈、链表、双指针,https://algo.itcharge.cn/Solutions/2100-2199/maximum-twin-sum-of-a-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2130.%20%E9%93%BE%E8%A1%A8%E6%9C%80%E5%A4%A7%E5%AD%AA%E7%94%9F%E5%92%8C.md,80.4%,中等,177 -2131,2100-2199,2131. 连接两字母单词得到的最长回文串,连接两字母单词得到的最长回文串,https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words/,longest-palindrome-by-concatenating-two-letter-words,贪心、数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2100-2199/longest-palindrome-by-concatenating-two-letter-words/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2131.%20%E8%BF%9E%E6%8E%A5%E4%B8%A4%E5%AD%97%E6%AF%8D%E5%8D%95%E8%AF%8D%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E4%B8%B2.md,42.9%,中等,152 -2132,2100-2199,2132. 用邮票贴满网格图,用邮票贴满网格图,https://leetcode.cn/problems/stamping-the-grid/,stamping-the-grid,贪心、数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/2100-2199/stamping-the-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2132.%20%E7%94%A8%E9%82%AE%E7%A5%A8%E8%B4%B4%E6%BB%A1%E7%BD%91%E6%A0%BC%E5%9B%BE.md,30.7%,困难,67 -2133,2100-2199,2133. 检查是否每一行每一列都包含全部整数,检查是否每一行每一列都包含全部整数,https://leetcode.cn/problems/check-if-every-row-and-column-contains-all-numbers/,check-if-every-row-and-column-contains-all-numbers,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/2100-2199/check-if-every-row-and-column-contains-all-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2133.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%AF%8F%E4%B8%80%E8%A1%8C%E6%AF%8F%E4%B8%80%E5%88%97%E9%83%BD%E5%8C%85%E5%90%AB%E5%85%A8%E9%83%A8%E6%95%B4%E6%95%B0.md,55.7%,简单,160 -2134,2100-2199,2134. 最少交换次数来组合所有的 1 II,最少交换次数来组合所有的 1 II,https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together-ii/,minimum-swaps-to-group-all-1s-together-ii,数组、滑动窗口,https://algo.itcharge.cn/Solutions/2100-2199/minimum-swaps-to-group-all-1s-together-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2134.%20%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0%E6%9D%A5%E7%BB%84%E5%90%88%E6%89%80%E6%9C%89%E7%9A%84%201%20II.md,48.8%,中等,173 -2135,2100-2199,2135. 统计追加字母可以获得的单词数,统计追加字母可以获得的单词数,https://leetcode.cn/problems/count-words-obtained-after-adding-a-letter/,count-words-obtained-after-adding-a-letter,位运算、数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/2100-2199/count-words-obtained-after-adding-a-letter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2135.%20%E7%BB%9F%E8%AE%A1%E8%BF%BD%E5%8A%A0%E5%AD%97%E6%AF%8D%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E7%9A%84%E5%8D%95%E8%AF%8D%E6%95%B0.md,36.1%,中等,106 -2136,2100-2199,2136. 全部开花的最早一天,全部开花的最早一天,https://leetcode.cn/problems/earliest-possible-day-of-full-bloom/,earliest-possible-day-of-full-bloom,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/earliest-possible-day-of-full-bloom/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2136.%20%E5%85%A8%E9%83%A8%E5%BC%80%E8%8A%B1%E7%9A%84%E6%9C%80%E6%97%A9%E4%B8%80%E5%A4%A9.md,66.0%,困难,76 -2137,2100-2199,2137. 通过倒水操作让所有的水桶所含水量相等,通过倒水操作让所有的水桶所含水量相等,https://leetcode.cn/problems/pour-water-between-buckets-to-make-water-levels-equal/,pour-water-between-buckets-to-make-water-levels-equal,数组、二分查找,https://algo.itcharge.cn/Solutions/2100-2199/pour-water-between-buckets-to-make-water-levels-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2137.%20%E9%80%9A%E8%BF%87%E5%80%92%E6%B0%B4%E6%93%8D%E4%BD%9C%E8%AE%A9%E6%89%80%E6%9C%89%E7%9A%84%E6%B0%B4%E6%A1%B6%E6%89%80%E5%90%AB%E6%B0%B4%E9%87%8F%E7%9B%B8%E7%AD%89.md,64.5%,中等,13 -2138,2100-2199,2138. 将字符串拆分为若干长度为 k 的组,将字符串拆分为若干长度为 k 的组,https://leetcode.cn/problems/divide-a-string-into-groups-of-size-k/,divide-a-string-into-groups-of-size-k,字符串、模拟,https://algo.itcharge.cn/Solutions/2100-2199/divide-a-string-into-groups-of-size-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2138.%20%E5%B0%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%86%E5%88%86%E4%B8%BA%E8%8B%A5%E5%B9%B2%E9%95%BF%E5%BA%A6%E4%B8%BA%20k%20%E7%9A%84%E7%BB%84.md,66.4%,简单,157 -2139,2100-2199,2139. 得到目标值的最少行动次数,得到目标值的最少行动次数,https://leetcode.cn/problems/minimum-moves-to-reach-target-score/,minimum-moves-to-reach-target-score,贪心、数学,https://algo.itcharge.cn/Solutions/2100-2199/minimum-moves-to-reach-target-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2139.%20%E5%BE%97%E5%88%B0%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%B0%91%E8%A1%8C%E5%8A%A8%E6%AC%A1%E6%95%B0.md,51.5%,中等,165 -2140,2100-2199,2140. 解决智力问题,解决智力问题,https://leetcode.cn/problems/solving-questions-with-brainpower/,solving-questions-with-brainpower,数组、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/solving-questions-with-brainpower/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2140.%20%E8%A7%A3%E5%86%B3%E6%99%BA%E5%8A%9B%E9%97%AE%E9%A2%98.md,44.0%,中等,166 -2141,2100-2199,2141. 同时运行 N 台电脑的最长时间,同时运行 N 台电脑的最长时间,https://leetcode.cn/problems/maximum-running-time-of-n-computers/,maximum-running-time-of-n-computers,贪心、数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2100-2199/maximum-running-time-of-n-computers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2141.%20%E5%90%8C%E6%97%B6%E8%BF%90%E8%A1%8C%20N%20%E5%8F%B0%E7%94%B5%E8%84%91%E7%9A%84%E6%9C%80%E9%95%BF%E6%97%B6%E9%97%B4.md,40.5%,困难,84 -2142,2100-2199,2142. 每辆车的乘客人数 I,每辆车的乘客人数 I,https://leetcode.cn/problems/the-number-of-passengers-in-each-bus-i/,the-number-of-passengers-in-each-bus-i,数据库,https://algo.itcharge.cn/Solutions/2100-2199/the-number-of-passengers-in-each-bus-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2142.%20%E6%AF%8F%E8%BE%86%E8%BD%A6%E7%9A%84%E4%B9%98%E5%AE%A2%E4%BA%BA%E6%95%B0%20I.md,46.6%,中等,21 -2143,2100-2199,2143. 在两个数组的区间中选取数字,在两个数组的区间中选取数字,https://leetcode.cn/problems/choose-numbers-from-two-arrays-in-range/,choose-numbers-from-two-arrays-in-range,数组、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/choose-numbers-from-two-arrays-in-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2143.%20%E5%9C%A8%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E5%8C%BA%E9%97%B4%E4%B8%AD%E9%80%89%E5%8F%96%E6%95%B0%E5%AD%97.md,57.1%,困难,11 -2144,2100-2199,2144. 打折购买糖果的最小开销,打折购买糖果的最小开销,https://leetcode.cn/problems/minimum-cost-of-buying-candies-with-discount/,minimum-cost-of-buying-candies-with-discount,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/minimum-cost-of-buying-candies-with-discount/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2144.%20%E6%89%93%E6%8A%98%E8%B4%AD%E4%B9%B0%E7%B3%96%E6%9E%9C%E7%9A%84%E6%9C%80%E5%B0%8F%E5%BC%80%E9%94%80.md,66.9%,简单,174 -2145,2100-2199,2145. 统计隐藏数组数目,统计隐藏数组数目,https://leetcode.cn/problems/count-the-hidden-sequences/,count-the-hidden-sequences,数组、前缀和,https://algo.itcharge.cn/Solutions/2100-2199/count-the-hidden-sequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2145.%20%E7%BB%9F%E8%AE%A1%E9%9A%90%E8%97%8F%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,35.8%,中等,117 -2146,2100-2199,2146. 价格范围内最高排名的 K 样物品,价格范围内最高排名的 K 样物品,https://leetcode.cn/problems/k-highest-ranked-items-within-a-price-range/,k-highest-ranked-items-within-a-price-range,广度优先搜索、数组、矩阵、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2100-2199/k-highest-ranked-items-within-a-price-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2146.%20%E4%BB%B7%E6%A0%BC%E8%8C%83%E5%9B%B4%E5%86%85%E6%9C%80%E9%AB%98%E6%8E%92%E5%90%8D%E7%9A%84%20K%20%E6%A0%B7%E7%89%A9%E5%93%81.md,40.1%,中等,98 -2147,2100-2199,2147. 分隔长廊的方案数,分隔长廊的方案数,https://leetcode.cn/problems/number-of-ways-to-divide-a-long-corridor/,number-of-ways-to-divide-a-long-corridor,数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/number-of-ways-to-divide-a-long-corridor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2147.%20%E5%88%86%E9%9A%94%E9%95%BF%E5%BB%8A%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,41.5%,困难,89 -2148,2100-2199,2148. 元素计数,元素计数,https://leetcode.cn/problems/count-elements-with-strictly-smaller-and-greater-elements/,count-elements-with-strictly-smaller-and-greater-elements,数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/count-elements-with-strictly-smaller-and-greater-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2148.%20%E5%85%83%E7%B4%A0%E8%AE%A1%E6%95%B0.md,57.8%,简单,166 -2149,2100-2199,2149. 按符号重排数组,按符号重排数组,https://leetcode.cn/problems/rearrange-array-elements-by-sign/,rearrange-array-elements-by-sign,数组、双指针、模拟,https://algo.itcharge.cn/Solutions/2100-2199/rearrange-array-elements-by-sign/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2149.%20%E6%8C%89%E7%AC%A6%E5%8F%B7%E9%87%8D%E6%8E%92%E6%95%B0%E7%BB%84.md,79.7%,中等,130 -2150,2100-2199,2150. 找出数组中的所有孤独数字,找出数组中的所有孤独数字,https://leetcode.cn/problems/find-all-lonely-numbers-in-the-array/,find-all-lonely-numbers-in-the-array,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2100-2199/find-all-lonely-numbers-in-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2150.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%AD%A4%E7%8B%AC%E6%95%B0%E5%AD%97.md,59.6%,中等,113 -2151,2100-2199,2151. 基于陈述统计最多好人数,基于陈述统计最多好人数,https://leetcode.cn/problems/maximum-good-people-based-on-statements/,maximum-good-people-based-on-statements,位运算、数组、回溯、枚举,https://algo.itcharge.cn/Solutions/2100-2199/maximum-good-people-based-on-statements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2151.%20%E5%9F%BA%E4%BA%8E%E9%99%88%E8%BF%B0%E7%BB%9F%E8%AE%A1%E6%9C%80%E5%A4%9A%E5%A5%BD%E4%BA%BA%E6%95%B0.md,50.3%,困难,93 -2152,2100-2199,2152. 穿过所有点的所需最少直线数量,穿过所有点的所需最少直线数量,https://leetcode.cn/problems/minimum-number-of-lines-to-cover-points/,minimum-number-of-lines-to-cover-points,位运算、几何、数组、哈希表、数学、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/2100-2199/minimum-number-of-lines-to-cover-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2152.%20%E7%A9%BF%E8%BF%87%E6%89%80%E6%9C%89%E7%82%B9%E7%9A%84%E6%89%80%E9%9C%80%E6%9C%80%E5%B0%91%E7%9B%B4%E7%BA%BF%E6%95%B0%E9%87%8F.md,53.0%,中等,17 -2153,2100-2199,2153. 每辆车的乘客人数 II,每辆车的乘客人数 II,https://leetcode.cn/problems/the-number-of-passengers-in-each-bus-ii/,the-number-of-passengers-in-each-bus-ii,数据库,https://algo.itcharge.cn/Solutions/2100-2199/the-number-of-passengers-in-each-bus-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2153.%20%E6%AF%8F%E8%BE%86%E8%BD%A6%E7%9A%84%E4%B9%98%E5%AE%A2%E4%BA%BA%E6%95%B0%20II.md,44.5%,困难,9 -2154,2100-2199,2154. 将找到的值乘以 2,将找到的值乘以 2,https://leetcode.cn/problems/keep-multiplying-found-values-by-two/,keep-multiplying-found-values-by-two,数组、哈希表、排序、模拟,https://algo.itcharge.cn/Solutions/2100-2199/keep-multiplying-found-values-by-two/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2154.%20%E5%B0%86%E6%89%BE%E5%88%B0%E7%9A%84%E5%80%BC%E4%B9%98%E4%BB%A5%202.md,73.5%,简单,193 -2155,2100-2199,2155. 分组得分最高的所有下标,分组得分最高的所有下标,https://leetcode.cn/problems/all-divisions-with-the-highest-score-of-a-binary-array/,all-divisions-with-the-highest-score-of-a-binary-array,数组,https://algo.itcharge.cn/Solutions/2100-2199/all-divisions-with-the-highest-score-of-a-binary-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2155.%20%E5%88%86%E7%BB%84%E5%BE%97%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E6%89%80%E6%9C%89%E4%B8%8B%E6%A0%87.md,64.6%,中等,118 -2156,2100-2199,2156. 查找给定哈希值的子串,查找给定哈希值的子串,https://leetcode.cn/problems/find-substring-with-given-hash-value/,find-substring-with-given-hash-value,字符串、滑动窗口、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/2100-2199/find-substring-with-given-hash-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2156.%20%E6%9F%A5%E6%89%BE%E7%BB%99%E5%AE%9A%E5%93%88%E5%B8%8C%E5%80%BC%E7%9A%84%E5%AD%90%E4%B8%B2.md,25.2%,困难,97 -2157,2100-2199,2157. 字符串分组,字符串分组,https://leetcode.cn/problems/groups-of-strings/,groups-of-strings,位运算、并查集、字符串,https://algo.itcharge.cn/Solutions/2100-2199/groups-of-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2157.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%86%E7%BB%84.md,31.3%,困难,79 -2158,2100-2199,2158. 每天绘制新区域的数量,每天绘制新区域的数量,https://leetcode.cn/problems/amount-of-new-area-painted-each-day/,amount-of-new-area-painted-each-day,线段树、数组、有序集合,https://algo.itcharge.cn/Solutions/2100-2199/amount-of-new-area-painted-each-day/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2158.%20%E6%AF%8F%E5%A4%A9%E7%BB%98%E5%88%B6%E6%96%B0%E5%8C%BA%E5%9F%9F%E7%9A%84%E6%95%B0%E9%87%8F.md,55.3%,困难,19 -2159,2100-2199,2159. 分别排序两列,分别排序两列,https://leetcode.cn/problems/order-two-columns-independently/,order-two-columns-independently,数据库,https://algo.itcharge.cn/Solutions/2100-2199/order-two-columns-independently/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2159.%20%E5%88%86%E5%88%AB%E6%8E%92%E5%BA%8F%E4%B8%A4%E5%88%97.md,61.2%,中等,10 -2160,2100-2199,2160. 拆分数位后四位数字的最小和,拆分数位后四位数字的最小和,https://leetcode.cn/problems/minimum-sum-of-four-digit-number-after-splitting-digits/,minimum-sum-of-four-digit-number-after-splitting-digits,贪心、数学、排序,https://algo.itcharge.cn/Solutions/2100-2199/minimum-sum-of-four-digit-number-after-splitting-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2160.%20%E6%8B%86%E5%88%86%E6%95%B0%E4%BD%8D%E5%90%8E%E5%9B%9B%E4%BD%8D%E6%95%B0%E5%AD%97%E7%9A%84%E6%9C%80%E5%B0%8F%E5%92%8C.md,84.2%,简单,219 -2161,2100-2199,2161. 根据给定数字划分数组,根据给定数字划分数组,https://leetcode.cn/problems/partition-array-according-to-given-pivot/,partition-array-according-to-given-pivot,数组、双指针、模拟,https://algo.itcharge.cn/Solutions/2100-2199/partition-array-according-to-given-pivot/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2161.%20%E6%A0%B9%E6%8D%AE%E7%BB%99%E5%AE%9A%E6%95%B0%E5%AD%97%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84.md,82.5%,中等,120 -2162,2100-2199,2162. 设置时间的最少代价,设置时间的最少代价,https://leetcode.cn/problems/minimum-cost-to-set-cooking-time/,minimum-cost-to-set-cooking-time,数学、枚举,https://algo.itcharge.cn/Solutions/2100-2199/minimum-cost-to-set-cooking-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2162.%20%E8%AE%BE%E7%BD%AE%E6%97%B6%E9%97%B4%E7%9A%84%E6%9C%80%E5%B0%91%E4%BB%A3%E4%BB%B7.md,34.3%,中等,82 -2163,2100-2199,2163. 删除元素后和的最小差值,删除元素后和的最小差值,https://leetcode.cn/problems/minimum-difference-in-sums-after-removal-of-elements/,minimum-difference-in-sums-after-removal-of-elements,数组、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/2100-2199/minimum-difference-in-sums-after-removal-of-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2163.%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%E5%90%8E%E5%92%8C%E7%9A%84%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC.md,49.1%,困难,63 -2164,2100-2199,2164. 对奇偶下标分别排序,对奇偶下标分别排序,https://leetcode.cn/problems/sort-even-and-odd-indices-independently/,sort-even-and-odd-indices-independently,数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/sort-even-and-odd-indices-independently/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2164.%20%E5%AF%B9%E5%A5%87%E5%81%B6%E4%B8%8B%E6%A0%87%E5%88%86%E5%88%AB%E6%8E%92%E5%BA%8F.md,68.6%,简单,147 -2165,2100-2199,2165. 重排数字的最小值,重排数字的最小值,https://leetcode.cn/problems/smallest-value-of-the-rearranged-number/,smallest-value-of-the-rearranged-number,数学、排序,https://algo.itcharge.cn/Solutions/2100-2199/smallest-value-of-the-rearranged-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2165.%20%E9%87%8D%E6%8E%92%E6%95%B0%E5%AD%97%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,55.3%,中等,141 -2166,2100-2199,2166. 设计位集,设计位集,https://leetcode.cn/problems/design-bitset/,design-bitset,设计、数组、哈希表,https://algo.itcharge.cn/Solutions/2100-2199/design-bitset/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2166.%20%E8%AE%BE%E8%AE%A1%E4%BD%8D%E9%9B%86.md,30.7%,中等,160 -2167,2100-2199,2167. 移除所有载有违禁货物车厢所需的最少时间,移除所有载有违禁货物车厢所需的最少时间,https://leetcode.cn/problems/minimum-time-to-remove-all-cars-containing-illegal-goods/,minimum-time-to-remove-all-cars-containing-illegal-goods,字符串、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/minimum-time-to-remove-all-cars-containing-illegal-goods/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2167.%20%E7%A7%BB%E9%99%A4%E6%89%80%E6%9C%89%E8%BD%BD%E6%9C%89%E8%BF%9D%E7%A6%81%E8%B4%A7%E7%89%A9%E8%BD%A6%E5%8E%A2%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,49.2%,困难,79 -2168,2100-2199,2168. 每个数字的频率都相同的独特子字符串的数量,每个数字的频率都相同的独特子字符串的数量,https://leetcode.cn/problems/unique-substrings-with-equal-digit-frequency/,unique-substrings-with-equal-digit-frequency,哈希表、字符串、计数、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/2100-2199/unique-substrings-with-equal-digit-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2168.%20%E6%AF%8F%E4%B8%AA%E6%95%B0%E5%AD%97%E7%9A%84%E9%A2%91%E7%8E%87%E9%83%BD%E7%9B%B8%E5%90%8C%E7%9A%84%E7%8B%AC%E7%89%B9%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E9%87%8F.md,63.2%,中等,15 -2169,2100-2199,2169. 得到 0 的操作数,得到 0 的操作数,https://leetcode.cn/problems/count-operations-to-obtain-zero/,count-operations-to-obtain-zero,数学、模拟,https://algo.itcharge.cn/Solutions/2100-2199/count-operations-to-obtain-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2169.%20%E5%BE%97%E5%88%B0%200%20%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0.md,73.6%,简单,145 -2170,2100-2199,2170. 使数组变成交替数组的最少操作数,使数组变成交替数组的最少操作数,https://leetcode.cn/problems/minimum-operations-to-make-the-array-alternating/,minimum-operations-to-make-the-array-alternating,贪心、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2100-2199/minimum-operations-to-make-the-array-alternating/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2170.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%8F%98%E6%88%90%E4%BA%A4%E6%9B%BF%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,31.2%,中等,163 -2171,2100-2199,2171. 拿出最少数目的魔法豆,拿出最少数目的魔法豆,https://leetcode.cn/problems/removing-minimum-number-of-magic-beans/,removing-minimum-number-of-magic-beans,数组、前缀和、排序,https://algo.itcharge.cn/Solutions/2100-2199/removing-minimum-number-of-magic-beans/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2171.%20%E6%8B%BF%E5%87%BA%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE%E7%9A%84%E9%AD%94%E6%B3%95%E8%B1%86.md,40.4%,中等,184 -2172,2100-2199,2172. 数组的最大与和,数组的最大与和,https://leetcode.cn/problems/maximum-and-sum-of-array/,maximum-and-sum-of-array,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/2100-2199/maximum-and-sum-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2172.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B8%8E%E5%92%8C.md,50.6%,困难,79 -2173,2100-2199,2173. 最多连胜的次数,最多连胜的次数,https://leetcode.cn/problems/longest-winning-streak/,longest-winning-streak,数据库,https://algo.itcharge.cn/Solutions/2100-2199/longest-winning-streak/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2173.%20%E6%9C%80%E5%A4%9A%E8%BF%9E%E8%83%9C%E7%9A%84%E6%AC%A1%E6%95%B0.md,56.6%,困难,27 -2174,2100-2199,2174. 通过翻转行或列来去除所有的 1 II,通过翻转行或列来去除所有的 1 II,https://leetcode.cn/problems/remove-all-ones-with-row-and-column-flips-ii/,remove-all-ones-with-row-and-column-flips-ii,位运算、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/2100-2199/remove-all-ones-with-row-and-column-flips-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2174.%20%E9%80%9A%E8%BF%87%E7%BF%BB%E8%BD%AC%E8%A1%8C%E6%88%96%E5%88%97%E6%9D%A5%E5%8E%BB%E9%99%A4%E6%89%80%E6%9C%89%E7%9A%84%201%20II.md,66.7%,中等,9 -2175,2100-2199,2175. 世界排名的变化,世界排名的变化,https://leetcode.cn/problems/the-change-in-global-rankings/,the-change-in-global-rankings,数据库,https://algo.itcharge.cn/Solutions/2100-2199/the-change-in-global-rankings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2175.%20%E4%B8%96%E7%95%8C%E6%8E%92%E5%90%8D%E7%9A%84%E5%8F%98%E5%8C%96.md,54.8%,中等,12 -2176,2100-2199,2176. 统计数组中相等且可以被整除的数对,统计数组中相等且可以被整除的数对,https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array/,count-equal-and-divisible-pairs-in-an-array,数组,https://algo.itcharge.cn/Solutions/2100-2199/count-equal-and-divisible-pairs-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2176.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9B%B8%E7%AD%89%E4%B8%94%E5%8F%AF%E4%BB%A5%E8%A2%AB%E6%95%B4%E9%99%A4%E7%9A%84%E6%95%B0%E5%AF%B9.md,78.8%,简单,123 -2177,2100-2199,2177. 找到和为给定整数的三个连续整数,找到和为给定整数的三个连续整数,https://leetcode.cn/problems/find-three-consecutive-integers-that-sum-to-a-given-number/,find-three-consecutive-integers-that-sum-to-a-given-number,数学、模拟,https://algo.itcharge.cn/Solutions/2100-2199/find-three-consecutive-integers-that-sum-to-a-given-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2177.%20%E6%89%BE%E5%88%B0%E5%92%8C%E4%B8%BA%E7%BB%99%E5%AE%9A%E6%95%B4%E6%95%B0%E7%9A%84%E4%B8%89%E4%B8%AA%E8%BF%9E%E7%BB%AD%E6%95%B4%E6%95%B0.md,70.0%,中等,99 -2178,2100-2199,2178. 拆分成最多数目的正偶数之和,拆分成最多数目的正偶数之和,https://leetcode.cn/problems/maximum-split-of-positive-even-integers/,maximum-split-of-positive-even-integers,贪心、数学、回溯,https://algo.itcharge.cn/Solutions/2100-2199/maximum-split-of-positive-even-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2178.%20%E6%8B%86%E5%88%86%E6%88%90%E6%9C%80%E5%A4%9A%E6%95%B0%E7%9B%AE%E7%9A%84%E6%AD%A3%E5%81%B6%E6%95%B0%E4%B9%8B%E5%92%8C.md,56.5%,中等,112 -2179,2100-2199,2179. 统计数组中好三元组数目,统计数组中好三元组数目,https://leetcode.cn/problems/count-good-triplets-in-an-array/,count-good-triplets-in-an-array,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/2100-2199/count-good-triplets-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2179.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E4%B8%AD%E5%A5%BD%E4%B8%89%E5%85%83%E7%BB%84%E6%95%B0%E7%9B%AE.md,37.4%,困难,79 -2180,2100-2199,2180. 统计各位数字之和为偶数的整数个数,统计各位数字之和为偶数的整数个数,https://leetcode.cn/problems/count-integers-with-even-digit-sum/,count-integers-with-even-digit-sum,数学、模拟,https://algo.itcharge.cn/Solutions/2100-2199/count-integers-with-even-digit-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2180.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C%E4%B8%BA%E5%81%B6%E6%95%B0%E7%9A%84%E6%95%B4%E6%95%B0%E4%B8%AA%E6%95%B0.md,68.0%,简单,377 -2181,2100-2199,2181. 合并零之间的节点,合并零之间的节点,https://leetcode.cn/problems/merge-nodes-in-between-zeros/,merge-nodes-in-between-zeros,链表、模拟,https://algo.itcharge.cn/Solutions/2100-2199/merge-nodes-in-between-zeros/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2181.%20%E5%90%88%E5%B9%B6%E9%9B%B6%E4%B9%8B%E9%97%B4%E7%9A%84%E8%8A%82%E7%82%B9.md,84.7%,中等,211 -2182,2100-2199,2182. 构造限制重复的字符串,构造限制重复的字符串,https://leetcode.cn/problems/construct-string-with-repeat-limit/,construct-string-with-repeat-limit,贪心、字符串、计数、堆(优先队列),https://algo.itcharge.cn/Solutions/2100-2199/construct-string-with-repeat-limit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2182.%20%E6%9E%84%E9%80%A0%E9%99%90%E5%88%B6%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,48.5%,中等,145 -2183,2100-2199,2183. 统计可以被 K 整除的下标对数目,统计可以被 K 整除的下标对数目,https://leetcode.cn/problems/count-array-pairs-divisible-by-k/,count-array-pairs-divisible-by-k,数组、数学、数论,https://algo.itcharge.cn/Solutions/2100-2199/count-array-pairs-divisible-by-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2183.%20%E7%BB%9F%E8%AE%A1%E5%8F%AF%E4%BB%A5%E8%A2%AB%20K%20%E6%95%B4%E9%99%A4%E7%9A%84%E4%B8%8B%E6%A0%87%E5%AF%B9%E6%95%B0%E7%9B%AE.md,28.3%,困难,60 -2184,2100-2199,2184. 建造坚实的砖墙的方法数,建造坚实的砖墙的方法数,https://leetcode.cn/problems/number-of-ways-to-build-sturdy-brick-wall/,number-of-ways-to-build-sturdy-brick-wall,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/2100-2199/number-of-ways-to-build-sturdy-brick-wall/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2184.%20%E5%BB%BA%E9%80%A0%E5%9D%9A%E5%AE%9E%E7%9A%84%E7%A0%96%E5%A2%99%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,55.1%,中等,17 -2185,2100-2199,2185. 统计包含给定前缀的字符串,统计包含给定前缀的字符串,https://leetcode.cn/problems/counting-words-with-a-given-prefix/,counting-words-with-a-given-prefix,数组、字符串,https://algo.itcharge.cn/Solutions/2100-2199/counting-words-with-a-given-prefix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2185.%20%E7%BB%9F%E8%AE%A1%E5%8C%85%E5%90%AB%E7%BB%99%E5%AE%9A%E5%89%8D%E7%BC%80%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,81.6%,简单,381 -2186,2100-2199,2186. 使两字符串互为字母异位词的最少步骤数,使两字符串互为字母异位词的最少步骤数,https://leetcode.cn/problems/minimum-number-of-steps-to-make-two-strings-anagram-ii/,minimum-number-of-steps-to-make-two-strings-anagram-ii,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2100-2199/minimum-number-of-steps-to-make-two-strings-anagram-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2186.%20%E4%BD%BF%E4%B8%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%92%E4%B8%BA%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E7%9A%84%E6%9C%80%E5%B0%91%E6%AD%A5%E9%AA%A4%E6%95%B0.md,72.7%,中等,126 -2187,2100-2199,2187. 完成旅途的最少时间,完成旅途的最少时间,https://leetcode.cn/problems/minimum-time-to-complete-trips/,minimum-time-to-complete-trips,数组、二分查找,https://algo.itcharge.cn/Solutions/2100-2199/minimum-time-to-complete-trips/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2187.%20%E5%AE%8C%E6%88%90%E6%97%85%E9%80%94%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,28.4%,中等,155 -2188,2100-2199,2188. 完成比赛的最少时间,完成比赛的最少时间,https://leetcode.cn/problems/minimum-time-to-finish-the-race/,minimum-time-to-finish-the-race,数组、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/minimum-time-to-finish-the-race/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2188.%20%E5%AE%8C%E6%88%90%E6%AF%94%E8%B5%9B%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,44.4%,困难,69 -2189,2100-2199,2189. 建造纸牌屋的方法数,建造纸牌屋的方法数,https://leetcode.cn/problems/number-of-ways-to-build-house-of-cards/,number-of-ways-to-build-house-of-cards,数学、动态规划,https://algo.itcharge.cn/Solutions/2100-2199/number-of-ways-to-build-house-of-cards/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2189.%20%E5%BB%BA%E9%80%A0%E7%BA%B8%E7%89%8C%E5%B1%8B%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,64.9%,中等,15 -2190,2100-2199,2190. 数组中紧跟 key 之后出现最频繁的数字,数组中紧跟 key 之后出现最频繁的数字,https://leetcode.cn/problems/most-frequent-number-following-key-in-an-array/,most-frequent-number-following-key-in-an-array,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2100-2199/most-frequent-number-following-key-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2190.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%B4%A7%E8%B7%9F%20key%20%E4%B9%8B%E5%90%8E%E5%87%BA%E7%8E%B0%E6%9C%80%E9%A2%91%E7%B9%81%E7%9A%84%E6%95%B0%E5%AD%97.md,58.7%,简单,116 -2191,2100-2199,2191. 将杂乱无章的数字排序,将杂乱无章的数字排序,https://leetcode.cn/problems/sort-the-jumbled-numbers/,sort-the-jumbled-numbers,数组、排序,https://algo.itcharge.cn/Solutions/2100-2199/sort-the-jumbled-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2191.%20%E5%B0%86%E6%9D%82%E4%B9%B1%E6%97%A0%E7%AB%A0%E7%9A%84%E6%95%B0%E5%AD%97%E6%8E%92%E5%BA%8F.md,43.5%,中等,111 -2192,2100-2199,2192. 有向无环图中一个节点的所有祖先,有向无环图中一个节点的所有祖先,https://leetcode.cn/problems/all-ancestors-of-a-node-in-a-directed-acyclic-graph/,all-ancestors-of-a-node-in-a-directed-acyclic-graph,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/2100-2199/all-ancestors-of-a-node-in-a-directed-acyclic-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2192.%20%E6%9C%89%E5%90%91%E6%97%A0%E7%8E%AF%E5%9B%BE%E4%B8%AD%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E7%A5%96%E5%85%88.md,45.1%,中等,114 -2193,2100-2199,2193. 得到回文串的最少操作次数,得到回文串的最少操作次数,https://leetcode.cn/problems/minimum-number-of-moves-to-make-palindrome/,minimum-number-of-moves-to-make-palindrome,贪心、树状数组、双指针、字符串,https://algo.itcharge.cn/Solutions/2100-2199/minimum-number-of-moves-to-make-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2193.%20%E5%BE%97%E5%88%B0%E5%9B%9E%E6%96%87%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,49.1%,困难,64 -2194,2100-2199,2194. Excel 表中某个范围内的单元格,Excel 表中某个范围内的单元格,https://leetcode.cn/problems/cells-in-a-range-on-an-excel-sheet/,cells-in-a-range-on-an-excel-sheet,字符串,https://algo.itcharge.cn/Solutions/2100-2199/cells-in-a-range-on-an-excel-sheet/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2194.%20Excel%20%E8%A1%A8%E4%B8%AD%E6%9F%90%E4%B8%AA%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E5%8D%95%E5%85%83%E6%A0%BC.md,84.2%,简单,150 -2195,2100-2199,2195. 向数组中追加 K 个整数,向数组中追加 K 个整数,https://leetcode.cn/problems/append-k-integers-with-minimal-sum/,append-k-integers-with-minimal-sum,贪心、数组、数学、排序,https://algo.itcharge.cn/Solutions/2100-2199/append-k-integers-with-minimal-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2195.%20%E5%90%91%E6%95%B0%E7%BB%84%E4%B8%AD%E8%BF%BD%E5%8A%A0%20K%20%E4%B8%AA%E6%95%B4%E6%95%B0.md,24.0%,中等,228 -2196,2100-2199,2196. 根据描述创建二叉树,根据描述创建二叉树,https://leetcode.cn/problems/create-binary-tree-from-descriptions/,create-binary-tree-from-descriptions,树、深度优先搜索、广度优先搜索、数组、哈希表、二叉树,https://algo.itcharge.cn/Solutions/2100-2199/create-binary-tree-from-descriptions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2196.%20%E6%A0%B9%E6%8D%AE%E6%8F%8F%E8%BF%B0%E5%88%9B%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md,72.8%,中等,160 -2197,2100-2199,2197. 替换数组中的非互质数,替换数组中的非互质数,https://leetcode.cn/problems/replace-non-coprime-numbers-in-array/,replace-non-coprime-numbers-in-array,栈、数组、数学、数论,https://algo.itcharge.cn/Solutions/2100-2199/replace-non-coprime-numbers-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2197.%20%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%9D%9E%E4%BA%92%E8%B4%A8%E6%95%B0.md,34.7%,困难,91 -2198,2100-2199,2198. 单因数三元组,单因数三元组,https://leetcode.cn/problems/number-of-single-divisor-triplets/,number-of-single-divisor-triplets,数学,https://algo.itcharge.cn/Solutions/2100-2199/number-of-single-divisor-triplets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2198.%20%E5%8D%95%E5%9B%A0%E6%95%B0%E4%B8%89%E5%85%83%E7%BB%84.md,57.5%,中等,7 -2199,2100-2199,2199. 找到每篇文章的主题,找到每篇文章的主题,https://leetcode.cn/problems/finding-the-topic-of-each-post/,finding-the-topic-of-each-post,数据库,https://algo.itcharge.cn/Solutions/2100-2199/finding-the-topic-of-each-post/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2199.%20%E6%89%BE%E5%88%B0%E6%AF%8F%E7%AF%87%E6%96%87%E7%AB%A0%E7%9A%84%E4%B8%BB%E9%A2%98.md,59.4%,困难,12 -2200,2200-2299,2200. 找出数组中的所有 K 近邻下标,找出数组中的所有 K 近邻下标,https://leetcode.cn/problems/find-all-k-distant-indices-in-an-array/,find-all-k-distant-indices-in-an-array,数组,https://algo.itcharge.cn/Solutions/2200-2299/find-all-k-distant-indices-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2200.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%20K%20%E8%BF%91%E9%82%BB%E4%B8%8B%E6%A0%87.md,53.8%,简单,148 -2201,2200-2299,2201. 统计可以提取的工件,统计可以提取的工件,https://leetcode.cn/problems/count-artifacts-that-can-be-extracted/,count-artifacts-that-can-be-extracted,数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2200-2299/count-artifacts-that-can-be-extracted/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2201.%20%E7%BB%9F%E8%AE%A1%E5%8F%AF%E4%BB%A5%E6%8F%90%E5%8F%96%E7%9A%84%E5%B7%A5%E4%BB%B6.md,49.4%,中等,138 -2202,2200-2299,2202. K 次操作后最大化顶端元素,K 次操作后最大化顶端元素,https://leetcode.cn/problems/maximize-the-topmost-element-after-k-moves/,maximize-the-topmost-element-after-k-moves,贪心、数组,https://algo.itcharge.cn/Solutions/2200-2299/maximize-the-topmost-element-after-k-moves/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2202.%20K%20%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E9%A1%B6%E7%AB%AF%E5%85%83%E7%B4%A0.md,21.7%,中等,150 -2203,2200-2299,2203. 得到要求路径的最小带权子图,得到要求路径的最小带权子图,https://leetcode.cn/problems/minimum-weighted-subgraph-with-the-required-paths/,minimum-weighted-subgraph-with-the-required-paths,图、最短路,https://algo.itcharge.cn/Solutions/2200-2299/minimum-weighted-subgraph-with-the-required-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2203.%20%E5%BE%97%E5%88%B0%E8%A6%81%E6%B1%82%E8%B7%AF%E5%BE%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%B8%A6%E6%9D%83%E5%AD%90%E5%9B%BE.md,37.7%,困难,72 -2204,2200-2299,2204. 无向图中到环的距离,无向图中到环的距离,https://leetcode.cn/problems/distance-to-a-cycle-in-undirected-graph/,distance-to-a-cycle-in-undirected-graph,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/2200-2299/distance-to-a-cycle-in-undirected-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2204.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E5%88%B0%E7%8E%AF%E7%9A%84%E8%B7%9D%E7%A6%BB.md,75.5%,困难,18 -2205,2200-2299,2205. 有资格享受折扣的用户数量,有资格享受折扣的用户数量,https://leetcode.cn/problems/the-number-of-users-that-are-eligible-for-discount/,the-number-of-users-that-are-eligible-for-discount,数据库,https://algo.itcharge.cn/Solutions/2200-2299/the-number-of-users-that-are-eligible-for-discount/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2205.%20%E6%9C%89%E8%B5%84%E6%A0%BC%E4%BA%AB%E5%8F%97%E6%8A%98%E6%89%A3%E7%9A%84%E7%94%A8%E6%88%B7%E6%95%B0%E9%87%8F.md,44.6%,简单,14 -2206,2200-2299,2206. 将数组划分成相等数对,将数组划分成相等数对,https://leetcode.cn/problems/divide-array-into-equal-pairs/,divide-array-into-equal-pairs,位运算、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2200-2299/divide-array-into-equal-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2206.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%92%E5%88%86%E6%88%90%E7%9B%B8%E7%AD%89%E6%95%B0%E5%AF%B9.md,73.8%,简单,163 -2207,2200-2299,2207. 字符串中最多数目的子字符串,字符串中最多数目的子字符串,https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string/,maximize-number-of-subsequences-in-a-string,贪心、字符串、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/maximize-number-of-subsequences-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2207.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%9C%80%E5%A4%9A%E6%95%B0%E7%9B%AE%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,31.5%,中等,107 -2208,2200-2299,2208. 将数组和减半的最少操作次数,将数组和减半的最少操作次数,https://leetcode.cn/problems/minimum-operations-to-halve-array-sum/,minimum-operations-to-halve-array-sum,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/2200-2299/minimum-operations-to-halve-array-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2208.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%92%8C%E5%87%8F%E5%8D%8A%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,40.9%,中等,99 -2209,2200-2299,2209. 用地毯覆盖后的最少白色砖块,用地毯覆盖后的最少白色砖块,https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets/,minimum-white-tiles-after-covering-with-carpets,字符串、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/minimum-white-tiles-after-covering-with-carpets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2209.%20%E7%94%A8%E5%9C%B0%E6%AF%AF%E8%A6%86%E7%9B%96%E5%90%8E%E7%9A%84%E6%9C%80%E5%B0%91%E7%99%BD%E8%89%B2%E7%A0%96%E5%9D%97.md,39.1%,困难,52 -2210,2200-2299,2210. 统计数组中峰和谷的数量,统计数组中峰和谷的数量,https://leetcode.cn/problems/count-hills-and-valleys-in-an-array/,count-hills-and-valleys-in-an-array,数组,https://algo.itcharge.cn/Solutions/2200-2299/count-hills-and-valleys-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2210.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E4%B8%AD%E5%B3%B0%E5%92%8C%E8%B0%B7%E7%9A%84%E6%95%B0%E9%87%8F.md,58.8%,简单,161 -2211,2200-2299,2211. 统计道路上的碰撞次数,统计道路上的碰撞次数,https://leetcode.cn/problems/count-collisions-on-a-road/,count-collisions-on-a-road,栈、字符串,https://algo.itcharge.cn/Solutions/2200-2299/count-collisions-on-a-road/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2211.%20%E7%BB%9F%E8%AE%A1%E9%81%93%E8%B7%AF%E4%B8%8A%E7%9A%84%E7%A2%B0%E6%92%9E%E6%AC%A1%E6%95%B0.md,41.0%,中等,147 -2212,2200-2299,2212. 射箭比赛中的最大得分,射箭比赛中的最大得分,https://leetcode.cn/problems/maximum-points-in-an-archery-competition/,maximum-points-in-an-archery-competition,位运算、递归、数组、枚举,https://algo.itcharge.cn/Solutions/2200-2299/maximum-points-in-an-archery-competition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2212.%20%E5%B0%84%E7%AE%AD%E6%AF%94%E8%B5%9B%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,47.8%,中等,166 -2213,2200-2299,2213. 由单个字符重复的最长子字符串,由单个字符重复的最长子字符串,https://leetcode.cn/problems/longest-substring-of-one-repeating-character/,longest-substring-of-one-repeating-character,线段树、数组、字符串、有序集合,https://algo.itcharge.cn/Solutions/2200-2299/longest-substring-of-one-repeating-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2213.%20%E7%94%B1%E5%8D%95%E4%B8%AA%E5%AD%97%E7%AC%A6%E9%87%8D%E5%A4%8D%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,40.6%,困难,86 -2214,2200-2299,2214. 通关游戏所需的最低生命值,通关游戏所需的最低生命值,https://leetcode.cn/problems/minimum-health-to-beat-game/,minimum-health-to-beat-game,贪心、数组、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/minimum-health-to-beat-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2214.%20%E9%80%9A%E5%85%B3%E6%B8%B8%E6%88%8F%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E4%BD%8E%E7%94%9F%E5%91%BD%E5%80%BC.md,55.4%,中等,11 -2215,2200-2299,2215. 找出两数组的不同,找出两数组的不同,https://leetcode.cn/problems/find-the-difference-of-two-arrays/,find-the-difference-of-two-arrays,数组、哈希表,https://algo.itcharge.cn/Solutions/2200-2299/find-the-difference-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2215.%20%E6%89%BE%E5%87%BA%E4%B8%A4%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%8D%E5%90%8C.md,66.8%,简单,182 -2216,2200-2299,2216. 美化数组的最少删除数,美化数组的最少删除数,https://leetcode.cn/problems/minimum-deletions-to-make-array-beautiful/,minimum-deletions-to-make-array-beautiful,栈、贪心、数组,https://algo.itcharge.cn/Solutions/2200-2299/minimum-deletions-to-make-array-beautiful/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2216.%20%E7%BE%8E%E5%8C%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%A0%E9%99%A4%E6%95%B0.md,48.6%,中等,171 -2217,2200-2299,2217. 找到指定长度的回文数,找到指定长度的回文数,https://leetcode.cn/problems/find-palindrome-with-fixed-length/,find-palindrome-with-fixed-length,数组、数学,https://algo.itcharge.cn/Solutions/2200-2299/find-palindrome-with-fixed-length/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2217.%20%E6%89%BE%E5%88%B0%E6%8C%87%E5%AE%9A%E9%95%BF%E5%BA%A6%E7%9A%84%E5%9B%9E%E6%96%87%E6%95%B0.md,33.6%,中等,154 -2218,2200-2299,2218. 从栈中取出 K 个硬币的最大面值和,从栈中取出 K 个硬币的最大面值和,https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles/,maximum-value-of-k-coins-from-piles,数组、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/maximum-value-of-k-coins-from-piles/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2218.%20%E4%BB%8E%E6%A0%88%E4%B8%AD%E5%8F%96%E5%87%BA%20K%20%E4%B8%AA%E7%A1%AC%E5%B8%81%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E5%80%BC%E5%92%8C.md,55.3%,困难,94 -2219,2200-2299,2219. 数组的最大总分,数组的最大总分,https://leetcode.cn/problems/maximum-sum-score-of-array/,maximum-sum-score-of-array,数组、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/maximum-sum-score-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2219.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E6%80%BB%E5%88%86.md,64.0%,中等,17 -2220,2200-2299,2220. 转换数字的最少位翻转次数,转换数字的最少位翻转次数,https://leetcode.cn/problems/minimum-bit-flips-to-convert-number/,minimum-bit-flips-to-convert-number,位运算,https://algo.itcharge.cn/Solutions/2200-2299/minimum-bit-flips-to-convert-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2220.%20%E8%BD%AC%E6%8D%A2%E6%95%B0%E5%AD%97%E7%9A%84%E6%9C%80%E5%B0%91%E4%BD%8D%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md,82.6%,简单,161 -2221,2200-2299,2221. 数组的三角和,数组的三角和,https://leetcode.cn/problems/find-triangular-sum-of-an-array/,find-triangular-sum-of-an-array,数组、数学、组合数学、模拟,https://algo.itcharge.cn/Solutions/2200-2299/find-triangular-sum-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2221.%20%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%89%E8%A7%92%E5%92%8C.md,79.7%,中等,123 -2222,2200-2299,2222. 选择建筑的方案数,选择建筑的方案数,https://leetcode.cn/problems/number-of-ways-to-select-buildings/,number-of-ways-to-select-buildings,字符串、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/number-of-ways-to-select-buildings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2222.%20%E9%80%89%E6%8B%A9%E5%BB%BA%E7%AD%91%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,49.9%,中等,138 -2223,2200-2299,2223. 构造字符串的总得分和,构造字符串的总得分和,https://leetcode.cn/problems/sum-of-scores-of-built-strings/,sum-of-scores-of-built-strings,字符串、二分查找、字符串匹配、后缀数组、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/2200-2299/sum-of-scores-of-built-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2223.%20%E6%9E%84%E9%80%A0%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%80%BB%E5%BE%97%E5%88%86%E5%92%8C.md,38.3%,困难,57 -2224,2200-2299,2224. 转化时间需要的最少操作数,转化时间需要的最少操作数,https://leetcode.cn/problems/minimum-number-of-operations-to-convert-time/,minimum-number-of-operations-to-convert-time,贪心、字符串,https://algo.itcharge.cn/Solutions/2200-2299/minimum-number-of-operations-to-convert-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2224.%20%E8%BD%AC%E5%8C%96%E6%97%B6%E9%97%B4%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,69.4%,简单,162 -2225,2200-2299,2225. 找出输掉零场或一场比赛的玩家,找出输掉零场或一场比赛的玩家,https://leetcode.cn/problems/find-players-with-zero-or-one-losses/,find-players-with-zero-or-one-losses,数组、哈希表、计数、排序,https://algo.itcharge.cn/Solutions/2200-2299/find-players-with-zero-or-one-losses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2225.%20%E6%89%BE%E5%87%BA%E8%BE%93%E6%8E%89%E9%9B%B6%E5%9C%BA%E6%88%96%E4%B8%80%E5%9C%BA%E6%AF%94%E8%B5%9B%E7%9A%84%E7%8E%A9%E5%AE%B6.md,63.0%,中等,119 -2226,2200-2299,2226. 每个小孩最多能分到多少糖果,每个小孩最多能分到多少糖果,https://leetcode.cn/problems/maximum-candies-allocated-to-k-children/,maximum-candies-allocated-to-k-children,数组、二分查找,https://algo.itcharge.cn/Solutions/2200-2299/maximum-candies-allocated-to-k-children/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2226.%20%E6%AF%8F%E4%B8%AA%E5%B0%8F%E5%AD%A9%E6%9C%80%E5%A4%9A%E8%83%BD%E5%88%86%E5%88%B0%E5%A4%9A%E5%B0%91%E7%B3%96%E6%9E%9C.md,34.1%,中等,182 -2227,2200-2299,2227. 加密解密字符串,加密解密字符串,https://leetcode.cn/problems/encrypt-and-decrypt-strings/,encrypt-and-decrypt-strings,设计、字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2200-2299/encrypt-and-decrypt-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2227.%20%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86%E5%AD%97%E7%AC%A6%E4%B8%B2.md,39.5%,困难,89 -2228,2200-2299,2228. 7 天内两次购买的用户,7 天内两次购买的用户,https://leetcode.cn/problems/users-with-two-purchases-within-seven-days/,users-with-two-purchases-within-seven-days,数据库,https://algo.itcharge.cn/Solutions/2200-2299/users-with-two-purchases-within-seven-days/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2228.%207%20%E5%A4%A9%E5%86%85%E4%B8%A4%E6%AC%A1%E8%B4%AD%E4%B9%B0%E7%9A%84%E7%94%A8%E6%88%B7.md,47.2%,中等,19 -2229,2200-2299,2229. 检查数组是否连贯,检查数组是否连贯,https://leetcode.cn/problems/check-if-an-array-is-consecutive/,check-if-an-array-is-consecutive,数组,https://algo.itcharge.cn/Solutions/2200-2299/check-if-an-array-is-consecutive/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2229.%20%E6%A3%80%E6%9F%A5%E6%95%B0%E7%BB%84%E6%98%AF%E5%90%A6%E8%BF%9E%E8%B4%AF.md,69.8%,简单,29 -2230,2200-2299,2230. 查找可享受优惠的用户,查找可享受优惠的用户,https://leetcode.cn/problems/the-users-that-are-eligible-for-discount/,the-users-that-are-eligible-for-discount,数据库,https://algo.itcharge.cn/Solutions/2200-2299/the-users-that-are-eligible-for-discount/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2230.%20%E6%9F%A5%E6%89%BE%E5%8F%AF%E4%BA%AB%E5%8F%97%E4%BC%98%E6%83%A0%E7%9A%84%E7%94%A8%E6%88%B7.md,49.4%,简单,9 -2231,2200-2299,2231. 按奇偶性交换后的最大数字,按奇偶性交换后的最大数字,https://leetcode.cn/problems/largest-number-after-digit-swaps-by-parity/,largest-number-after-digit-swaps-by-parity,排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2200-2299/largest-number-after-digit-swaps-by-parity/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2231.%20%E6%8C%89%E5%A5%87%E5%81%B6%E6%80%A7%E4%BA%A4%E6%8D%A2%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md,64.3%,简单,195 -2232,2200-2299,2232. 向表达式添加括号后的最小结果,向表达式添加括号后的最小结果,https://leetcode.cn/problems/minimize-result-by-adding-parentheses-to-expression/,minimize-result-by-adding-parentheses-to-expression,字符串、枚举,https://algo.itcharge.cn/Solutions/2200-2299/minimize-result-by-adding-parentheses-to-expression/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2232.%20%E5%90%91%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B7%BB%E5%8A%A0%E6%8B%AC%E5%8F%B7%E5%90%8E%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%93%E6%9E%9C.md,61.3%,中等,134 -2233,2200-2299,2233. K 次增加后的最大乘积,K 次增加后的最大乘积,https://leetcode.cn/problems/maximum-product-after-k-increments/,maximum-product-after-k-increments,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/2200-2299/maximum-product-after-k-increments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2233.%20K%20%E6%AC%A1%E5%A2%9E%E5%8A%A0%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,36.8%,中等,169 -2234,2200-2299,2234. 花园的最大总美丽值,花园的最大总美丽值,https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens/,maximum-total-beauty-of-the-gardens,贪心、数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/2200-2299/maximum-total-beauty-of-the-gardens/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2234.%20%E8%8A%B1%E5%9B%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E6%80%BB%E7%BE%8E%E4%B8%BD%E5%80%BC.md,28.0%,困难,76 -2235,2200-2299,2235. 两整数相加,两整数相加,https://leetcode.cn/problems/add-two-integers/,add-two-integers,数学,https://algo.itcharge.cn/Solutions/2200-2299/add-two-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2235.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E7%9B%B8%E5%8A%A0.md,85.2%,简单,155 -2236,2200-2299,2236. 判断根结点是否等于子结点之和,判断根结点是否等于子结点之和,https://leetcode.cn/problems/root-equals-sum-of-children/,root-equals-sum-of-children,树、二叉树,https://algo.itcharge.cn/Solutions/2200-2299/root-equals-sum-of-children/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2236.%20%E5%88%A4%E6%96%AD%E6%A0%B9%E7%BB%93%E7%82%B9%E6%98%AF%E5%90%A6%E7%AD%89%E4%BA%8E%E5%AD%90%E7%BB%93%E7%82%B9%E4%B9%8B%E5%92%8C.md,85.9%,简单,162 -2237,2200-2299,2237. 计算街道上满足所需亮度的位置数量,计算街道上满足所需亮度的位置数量,https://leetcode.cn/problems/count-positions-on-street-with-required-brightness/,count-positions-on-street-with-required-brightness,数组、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/count-positions-on-street-with-required-brightness/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2237.%20%E8%AE%A1%E7%AE%97%E8%A1%97%E9%81%93%E4%B8%8A%E6%BB%A1%E8%B6%B3%E6%89%80%E9%9C%80%E4%BA%AE%E5%BA%A6%E7%9A%84%E4%BD%8D%E7%BD%AE%E6%95%B0%E9%87%8F.md,74.5%,中等,16 -2238,2200-2299,2238. 司机成为乘客的次数,司机成为乘客的次数,https://leetcode.cn/problems/number-of-times-a-driver-was-a-passenger/,number-of-times-a-driver-was-a-passenger,数据库,https://algo.itcharge.cn/Solutions/2200-2299/number-of-times-a-driver-was-a-passenger/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2238.%20%E5%8F%B8%E6%9C%BA%E6%88%90%E4%B8%BA%E4%B9%98%E5%AE%A2%E7%9A%84%E6%AC%A1%E6%95%B0.md,69.3%,中等,14 -2239,2200-2299,2239. 找到最接近 0 的数字,找到最接近 0 的数字,https://leetcode.cn/problems/find-closest-number-to-zero/,find-closest-number-to-zero,数组,https://algo.itcharge.cn/Solutions/2200-2299/find-closest-number-to-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2239.%20%E6%89%BE%E5%88%B0%E6%9C%80%E6%8E%A5%E8%BF%91%200%20%E7%9A%84%E6%95%B0%E5%AD%97.md,53.5%,简单,138 -2240,2200-2299,2240. 买钢笔和铅笔的方案数,买钢笔和铅笔的方案数,https://leetcode.cn/problems/number-of-ways-to-buy-pens-and-pencils/,number-of-ways-to-buy-pens-and-pencils,数学、枚举,https://algo.itcharge.cn/Solutions/2200-2299/number-of-ways-to-buy-pens-and-pencils/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2240.%20%E4%B9%B0%E9%92%A2%E7%AC%94%E5%92%8C%E9%93%85%E7%AC%94%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,57.4%,中等,82 -2241,2200-2299,2241. 设计一个 ATM 机器,设计一个 ATM 机器,https://leetcode.cn/problems/design-an-atm-machine/,design-an-atm-machine,贪心、设计、数组,https://algo.itcharge.cn/Solutions/2200-2299/design-an-atm-machine/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2241.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%20ATM%20%E6%9C%BA%E5%99%A8.md,36.3%,中等,80 -2242,2200-2299,2242. 节点序列的最大得分,节点序列的最大得分,https://leetcode.cn/problems/maximum-score-of-a-node-sequence/,maximum-score-of-a-node-sequence,图、数组、枚举、排序,https://algo.itcharge.cn/Solutions/2200-2299/maximum-score-of-a-node-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2242.%20%E8%8A%82%E7%82%B9%E5%BA%8F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md,33.9%,困难,46 -2243,2200-2299,2243. 计算字符串的数字和,计算字符串的数字和,https://leetcode.cn/problems/calculate-digit-sum-of-a-string/,calculate-digit-sum-of-a-string,字符串、模拟,https://algo.itcharge.cn/Solutions/2200-2299/calculate-digit-sum-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2243.%20%E8%AE%A1%E7%AE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E5%AD%97%E5%92%8C.md,63.5%,简单,164 -2244,2200-2299,2244. 完成所有任务需要的最少轮数,完成所有任务需要的最少轮数,https://leetcode.cn/problems/minimum-rounds-to-complete-all-tasks/,minimum-rounds-to-complete-all-tasks,贪心、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2200-2299/minimum-rounds-to-complete-all-tasks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2244.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E4%BB%BB%E5%8A%A1%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E8%BD%AE%E6%95%B0.md,57.1%,中等,160 -2245,2200-2299,2245. 转角路径的乘积中最多能有几个尾随零,转角路径的乘积中最多能有几个尾随零,https://leetcode.cn/problems/maximum-trailing-zeros-in-a-cornered-path/,maximum-trailing-zeros-in-a-cornered-path,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/maximum-trailing-zeros-in-a-cornered-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2245.%20%E8%BD%AC%E8%A7%92%E8%B7%AF%E5%BE%84%E7%9A%84%E4%B9%98%E7%A7%AF%E4%B8%AD%E6%9C%80%E5%A4%9A%E8%83%BD%E6%9C%89%E5%87%A0%E4%B8%AA%E5%B0%BE%E9%9A%8F%E9%9B%B6.md,36.1%,中等,98 -2246,2200-2299,2246. 相邻字符不同的最长路径,相邻字符不同的最长路径,https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/,longest-path-with-different-adjacent-characters,树、深度优先搜索、图、拓扑排序、数组、字符串,https://algo.itcharge.cn/Solutions/2200-2299/longest-path-with-different-adjacent-characters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2246.%20%E7%9B%B8%E9%82%BB%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%80%E9%95%BF%E8%B7%AF%E5%BE%84.md,47.0%,困难,69 -2247,2200-2299,2247. K 条高速公路的最大旅行费用,K 条高速公路的最大旅行费用,https://leetcode.cn/problems/maximum-cost-of-trip-with-k-highways/,maximum-cost-of-trip-with-k-highways,位运算、图、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/2200-2299/maximum-cost-of-trip-with-k-highways/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2247.%20K%20%E6%9D%A1%E9%AB%98%E9%80%9F%E5%85%AC%E8%B7%AF%E7%9A%84%E6%9C%80%E5%A4%A7%E6%97%85%E8%A1%8C%E8%B4%B9%E7%94%A8.md,56.7%,困难,14 -2248,2200-2299,2248. 多个数组求交集,多个数组求交集,https://leetcode.cn/problems/intersection-of-multiple-arrays/,intersection-of-multiple-arrays,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2200-2299/intersection-of-multiple-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2248.%20%E5%A4%9A%E4%B8%AA%E6%95%B0%E7%BB%84%E6%B1%82%E4%BA%A4%E9%9B%86.md,67.1%,简单,193 -2249,2200-2299,2249. 统计圆内格点数目,统计圆内格点数目,https://leetcode.cn/problems/count-lattice-points-inside-a-circle/,count-lattice-points-inside-a-circle,几何、数组、哈希表、数学、枚举,https://algo.itcharge.cn/Solutions/2200-2299/count-lattice-points-inside-a-circle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2249.%20%E7%BB%9F%E8%AE%A1%E5%9C%86%E5%86%85%E6%A0%BC%E7%82%B9%E6%95%B0%E7%9B%AE.md,53.3%,中等,133 -2250,2200-2299,2250. 统计包含每个点的矩形数目,统计包含每个点的矩形数目,https://leetcode.cn/problems/count-number-of-rectangles-containing-each-point/,count-number-of-rectangles-containing-each-point,树状数组、数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2200-2299/count-number-of-rectangles-containing-each-point/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2250.%20%E7%BB%9F%E8%AE%A1%E5%8C%85%E5%90%AB%E6%AF%8F%E4%B8%AA%E7%82%B9%E7%9A%84%E7%9F%A9%E5%BD%A2%E6%95%B0%E7%9B%AE.md,34.9%,中等,119 -2251,2200-2299,2251. 花期内花的数目,花期内花的数目,https://leetcode.cn/problems/number-of-flowers-in-full-bloom/,number-of-flowers-in-full-bloom,数组、哈希表、二分查找、有序集合、前缀和、排序,https://algo.itcharge.cn/Solutions/2200-2299/number-of-flowers-in-full-bloom/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2251.%20%E8%8A%B1%E6%9C%9F%E5%86%85%E8%8A%B1%E7%9A%84%E6%95%B0%E7%9B%AE.md,49.1%,困难,131 -2252,2200-2299,2252. 表的动态旋转,表的动态旋转,https://leetcode.cn/problems/dynamic-pivoting-of-a-table/,dynamic-pivoting-of-a-table,数据库,https://algo.itcharge.cn/Solutions/2200-2299/dynamic-pivoting-of-a-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2252.%20%E8%A1%A8%E7%9A%84%E5%8A%A8%E6%80%81%E6%97%8B%E8%BD%AC.md,57.4%,困难,3 -2253,2200-2299,2253. 动态取消表的旋转,动态取消表的旋转,https://leetcode.cn/problems/dynamic-unpivoting-of-a-table/,dynamic-unpivoting-of-a-table,数据库,https://algo.itcharge.cn/Solutions/2200-2299/dynamic-unpivoting-of-a-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2253.%20%E5%8A%A8%E6%80%81%E5%8F%96%E6%B6%88%E8%A1%A8%E7%9A%84%E6%97%8B%E8%BD%AC.md,71.1%,困难,3 -2254,2200-2299,2254. 设计视频共享平台,设计视频共享平台,https://leetcode.cn/problems/design-video-sharing-platform/,design-video-sharing-platform,栈、设计、哈希表、有序集合,https://algo.itcharge.cn/Solutions/2200-2299/design-video-sharing-platform/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2254.%20%E8%AE%BE%E8%AE%A1%E8%A7%86%E9%A2%91%E5%85%B1%E4%BA%AB%E5%B9%B3%E5%8F%B0.md,61.3%,困难,8 -2255,2200-2299,2255. 统计是给定字符串前缀的字符串数目,统计是给定字符串前缀的字符串数目,https://leetcode.cn/problems/count-prefixes-of-a-given-string/,count-prefixes-of-a-given-string,数组、字符串,https://algo.itcharge.cn/Solutions/2200-2299/count-prefixes-of-a-given-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2255.%20%E7%BB%9F%E8%AE%A1%E6%98%AF%E7%BB%99%E5%AE%9A%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%89%8D%E7%BC%80%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md,78.6%,简单,152 -2256,2200-2299,2256. 最小平均差,最小平均差,https://leetcode.cn/problems/minimum-average-difference/,minimum-average-difference,数组、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/minimum-average-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2256.%20%E6%9C%80%E5%B0%8F%E5%B9%B3%E5%9D%87%E5%B7%AE.md,36.8%,中等,80 -2257,2200-2299,2257. 统计网格图中没有被保卫的格子数,统计网格图中没有被保卫的格子数,https://leetcode.cn/problems/count-unguarded-cells-in-the-grid/,count-unguarded-cells-in-the-grid,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/2200-2299/count-unguarded-cells-in-the-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2257.%20%E7%BB%9F%E8%AE%A1%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%B2%A1%E6%9C%89%E8%A2%AB%E4%BF%9D%E5%8D%AB%E7%9A%84%E6%A0%BC%E5%AD%90%E6%95%B0.md,52.7%,中等,107 -2258,2200-2299,2258. 逃离火灾,逃离火灾,https://leetcode.cn/problems/escape-the-spreading-fire/,escape-the-spreading-fire,广度优先搜索、数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/2200-2299/escape-the-spreading-fire/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2258.%20%E9%80%83%E7%A6%BB%E7%81%AB%E7%81%BE.md,35.9%,困难,88 -2259,2200-2299,2259. 移除指定数字得到的最大结果,移除指定数字得到的最大结果,https://leetcode.cn/problems/remove-digit-from-number-to-maximize-result/,remove-digit-from-number-to-maximize-result,贪心、字符串、枚举,https://algo.itcharge.cn/Solutions/2200-2299/remove-digit-from-number-to-maximize-result/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2259.%20%E7%A7%BB%E9%99%A4%E6%8C%87%E5%AE%9A%E6%95%B0%E5%AD%97%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E7%BB%93%E6%9E%9C.md,50.1%,简单,155 -2260,2200-2299,2260. 必须拿起的最小连续卡牌数,必须拿起的最小连续卡牌数,https://leetcode.cn/problems/minimum-consecutive-cards-to-pick-up/,minimum-consecutive-cards-to-pick-up,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/2200-2299/minimum-consecutive-cards-to-pick-up/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2260.%20%E5%BF%85%E9%A1%BB%E6%8B%BF%E8%B5%B7%E7%9A%84%E6%9C%80%E5%B0%8F%E8%BF%9E%E7%BB%AD%E5%8D%A1%E7%89%8C%E6%95%B0.md,51.2%,中等,153 -2261,2200-2299,2261. 含最多 K 个可整除元素的子数组,含最多 K 个可整除元素的子数组,https://leetcode.cn/problems/k-divisible-elements-subarrays/,k-divisible-elements-subarrays,字典树、数组、哈希表、枚举、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/2200-2299/k-divisible-elements-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2261.%20%E5%90%AB%E6%9C%80%E5%A4%9A%20K%20%E4%B8%AA%E5%8F%AF%E6%95%B4%E9%99%A4%E5%85%83%E7%B4%A0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,52.9%,中等,132 -2262,2200-2299,2262. 字符串的总引力,字符串的总引力,https://leetcode.cn/problems/total-appeal-of-a-string/,total-appeal-of-a-string,哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/2200-2299/total-appeal-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2262.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%80%BB%E5%BC%95%E5%8A%9B.md,57.5%,困难,121 -2263,2200-2299,2263. 数组变为有序的最小操作次数,数组变为有序的最小操作次数,https://leetcode.cn/problems/make-array-non-decreasing-or-non-increasing/,make-array-non-decreasing-or-non-increasing,贪心、动态规划,https://algo.itcharge.cn/Solutions/2200-2299/make-array-non-decreasing-or-non-increasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2263.%20%E6%95%B0%E7%BB%84%E5%8F%98%E4%B8%BA%E6%9C%89%E5%BA%8F%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,74.0%,困难,15 -2264,2200-2299,2264. 字符串中最大的 3 位相同数字,字符串中最大的 3 位相同数字,https://leetcode.cn/problems/largest-3-same-digit-number-in-string/,largest-3-same-digit-number-in-string,字符串,https://algo.itcharge.cn/Solutions/2200-2299/largest-3-same-digit-number-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2264.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%203%20%E4%BD%8D%E7%9B%B8%E5%90%8C%E6%95%B0%E5%AD%97.md,62.0%,简单,170 -2265,2200-2299,2265. 统计值等于子树平均值的节点数,统计值等于子树平均值的节点数,https://leetcode.cn/problems/count-nodes-equal-to-average-of-subtree/,count-nodes-equal-to-average-of-subtree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2200-2299/count-nodes-equal-to-average-of-subtree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2265.%20%E7%BB%9F%E8%AE%A1%E5%80%BC%E7%AD%89%E4%BA%8E%E5%AD%90%E6%A0%91%E5%B9%B3%E5%9D%87%E5%80%BC%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0.md,82.6%,中等,200 -2266,2200-2299,2266. 统计打字方案数,统计打字方案数,https://leetcode.cn/problems/count-number-of-texts/,count-number-of-texts,哈希表、数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/2200-2299/count-number-of-texts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2266.%20%E7%BB%9F%E8%AE%A1%E6%89%93%E5%AD%97%E6%96%B9%E6%A1%88%E6%95%B0.md,43.6%,中等,138 -2267,2200-2299,2267. 检查是否有合法括号字符串路径,检查是否有合法括号字符串路径,https://leetcode.cn/problems/check-if-there-is-a-valid-parentheses-string-path/,check-if-there-is-a-valid-parentheses-string-path,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2200-2299/check-if-there-is-a-valid-parentheses-string-path/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2267.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%9C%89%E5%90%88%E6%B3%95%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%B7%AF%E5%BE%84.md,37.9%,困难,132 -2268,2200-2299,2268. 最少按键次数,最少按键次数,https://leetcode.cn/problems/minimum-number-of-keypresses/,minimum-number-of-keypresses,贪心、数组、字符串、计数、排序,https://algo.itcharge.cn/Solutions/2200-2299/minimum-number-of-keypresses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2268.%20%E6%9C%80%E5%B0%91%E6%8C%89%E9%94%AE%E6%AC%A1%E6%95%B0.md,68.0%,中等,18 -2269,2200-2299,2269. 找到一个数字的 K 美丽值,找到一个数字的 K 美丽值,https://leetcode.cn/problems/find-the-k-beauty-of-a-number/,find-the-k-beauty-of-a-number,数学、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/2200-2299/find-the-k-beauty-of-a-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2269.%20%E6%89%BE%E5%88%B0%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%E7%9A%84%20K%20%E7%BE%8E%E4%B8%BD%E5%80%BC.md,62.7%,简单,124 -2270,2200-2299,2270. 分割数组的方案数,分割数组的方案数,https://leetcode.cn/problems/number-of-ways-to-split-array/,number-of-ways-to-split-array,数组、前缀和,https://algo.itcharge.cn/Solutions/2200-2299/number-of-ways-to-split-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2270.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,46.4%,中等,76 -2271,2200-2299,2271. 毯子覆盖的最多白色砖块数,毯子覆盖的最多白色砖块数,https://leetcode.cn/problems/maximum-white-tiles-covered-by-a-carpet/,maximum-white-tiles-covered-by-a-carpet,贪心、数组、二分查找、前缀和、排序,https://algo.itcharge.cn/Solutions/2200-2299/maximum-white-tiles-covered-by-a-carpet/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2271.%20%E6%AF%AF%E5%AD%90%E8%A6%86%E7%9B%96%E7%9A%84%E6%9C%80%E5%A4%9A%E7%99%BD%E8%89%B2%E7%A0%96%E5%9D%97%E6%95%B0.md,32.8%,中等,101 -2272,2200-2299,2272. 最大波动的子字符串,最大波动的子字符串,https://leetcode.cn/problems/substring-with-largest-variance/,substring-with-largest-variance,数组、动态规划,https://algo.itcharge.cn/Solutions/2200-2299/substring-with-largest-variance/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2272.%20%E6%9C%80%E5%A4%A7%E6%B3%A2%E5%8A%A8%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,38.8%,困难,42 -2273,2200-2299,2273. 移除字母异位词后的结果数组,移除字母异位词后的结果数组,https://leetcode.cn/problems/find-resultant-array-after-removing-anagrams/,find-resultant-array-after-removing-anagrams,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/2200-2299/find-resultant-array-after-removing-anagrams/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2273.%20%E7%A7%BB%E9%99%A4%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84.md,59.7%,简单,154 -2274,2200-2299,2274. 不含特殊楼层的最大连续楼层数,不含特殊楼层的最大连续楼层数,https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors/,maximum-consecutive-floors-without-special-floors,数组、排序,https://algo.itcharge.cn/Solutions/2200-2299/maximum-consecutive-floors-without-special-floors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2274.%20%E4%B8%8D%E5%90%AB%E7%89%B9%E6%AE%8A%E6%A5%BC%E5%B1%82%E7%9A%84%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%E6%A5%BC%E5%B1%82%E6%95%B0.md,52.6%,中等,110 -2275,2200-2299,2275. 按位与结果大于零的最长组合,按位与结果大于零的最长组合,https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero/,largest-combination-with-bitwise-and-greater-than-zero,位运算、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2200-2299/largest-combination-with-bitwise-and-greater-than-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2275.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E7%BB%93%E6%9E%9C%E5%A4%A7%E4%BA%8E%E9%9B%B6%E7%9A%84%E6%9C%80%E9%95%BF%E7%BB%84%E5%90%88.md,60.1%,中等,102 -2276,2200-2299,2276. 统计区间中的整数数目,统计区间中的整数数目,https://leetcode.cn/problems/count-integers-in-intervals/,count-integers-in-intervals,设计、线段树、有序集合,https://algo.itcharge.cn/Solutions/2200-2299/count-integers-in-intervals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2276.%20%E7%BB%9F%E8%AE%A1%E5%8C%BA%E9%97%B4%E4%B8%AD%E7%9A%84%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md,36.9%,困难,102 -2277,2200-2299,2277. 树中最接近路径的节点,树中最接近路径的节点,https://leetcode.cn/problems/closest-node-to-path-in-tree/,closest-node-to-path-in-tree,树、深度优先搜索、广度优先搜索、数组,https://algo.itcharge.cn/Solutions/2200-2299/closest-node-to-path-in-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2277.%20%E6%A0%91%E4%B8%AD%E6%9C%80%E6%8E%A5%E8%BF%91%E8%B7%AF%E5%BE%84%E7%9A%84%E8%8A%82%E7%82%B9.md,71.9%,困难,13 -2278,2200-2299,2278. 字母在字符串中的百分比,字母在字符串中的百分比,https://leetcode.cn/problems/percentage-of-letter-in-string/,percentage-of-letter-in-string,字符串,https://algo.itcharge.cn/Solutions/2200-2299/percentage-of-letter-in-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2278.%20%E5%AD%97%E6%AF%8D%E5%9C%A8%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%99%BE%E5%88%86%E6%AF%94.md,80.8%,简单,136 -2279,2200-2299,2279. 装满石头的背包的最大数量,装满石头的背包的最大数量,https://leetcode.cn/problems/maximum-bags-with-full-capacity-of-rocks/,maximum-bags-with-full-capacity-of-rocks,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2200-2299/maximum-bags-with-full-capacity-of-rocks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2279.%20%E8%A3%85%E6%BB%A1%E7%9F%B3%E5%A4%B4%E7%9A%84%E8%83%8C%E5%8C%85%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,63.8%,中等,137 -2280,2200-2299,2280. 表示一个折线图的最少线段数,表示一个折线图的最少线段数,https://leetcode.cn/problems/minimum-lines-to-represent-a-line-chart/,minimum-lines-to-represent-a-line-chart,几何、数组、数学、数论、排序,https://algo.itcharge.cn/Solutions/2200-2299/minimum-lines-to-represent-a-line-chart/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2280.%20%E8%A1%A8%E7%A4%BA%E4%B8%80%E4%B8%AA%E6%8A%98%E7%BA%BF%E5%9B%BE%E7%9A%84%E6%9C%80%E5%B0%91%E7%BA%BF%E6%AE%B5%E6%95%B0.md,22.4%,中等,155 -2281,2200-2299,2281. 巫师的总力量和,巫师的总力量和,https://leetcode.cn/problems/sum-of-total-strength-of-wizards/,sum-of-total-strength-of-wizards,栈、数组、前缀和、单调栈,https://algo.itcharge.cn/Solutions/2200-2299/sum-of-total-strength-of-wizards/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2281.%20%E5%B7%AB%E5%B8%88%E7%9A%84%E6%80%BB%E5%8A%9B%E9%87%8F%E5%92%8C.md,26.6%,困难,73 -2282,2200-2299,2282. 在一个网格中可以看到的人数,在一个网格中可以看到的人数,https://leetcode.cn/problems/number-of-people-that-can-be-seen-in-a-grid/,number-of-people-that-can-be-seen-in-a-grid,栈、数组、矩阵、单调栈,https://algo.itcharge.cn/Solutions/2200-2299/number-of-people-that-can-be-seen-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2282.%20%E5%9C%A8%E4%B8%80%E4%B8%AA%E7%BD%91%E6%A0%BC%E4%B8%AD%E5%8F%AF%E4%BB%A5%E7%9C%8B%E5%88%B0%E7%9A%84%E4%BA%BA%E6%95%B0.md,48.3%,中等,10 -2283,2200-2299,2283. 判断一个数的数字计数是否等于数位的值,判断一个数的数字计数是否等于数位的值,https://leetcode.cn/problems/check-if-number-has-equal-digit-count-and-digit-value/,check-if-number-has-equal-digit-count-and-digit-value,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2200-2299/check-if-number-has-equal-digit-count-and-digit-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2283.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%95%B0%E7%9A%84%E6%95%B0%E5%AD%97%E8%AE%A1%E6%95%B0%E6%98%AF%E5%90%A6%E7%AD%89%E4%BA%8E%E6%95%B0%E4%BD%8D%E7%9A%84%E5%80%BC.md,79.0%,简单,379 -2284,2200-2299,2284. 最多单词数的发件人,最多单词数的发件人,https://leetcode.cn/problems/sender-with-largest-word-count/,sender-with-largest-word-count,数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2200-2299/sender-with-largest-word-count/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2284.%20%E6%9C%80%E5%A4%9A%E5%8D%95%E8%AF%8D%E6%95%B0%E7%9A%84%E5%8F%91%E4%BB%B6%E4%BA%BA.md,56.8%,中等,92 -2285,2200-2299,2285. 道路的最大总重要性,道路的最大总重要性,https://leetcode.cn/problems/maximum-total-importance-of-roads/,maximum-total-importance-of-roads,贪心、图、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2200-2299/maximum-total-importance-of-roads/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2285.%20%E9%81%93%E8%B7%AF%E7%9A%84%E6%9C%80%E5%A4%A7%E6%80%BB%E9%87%8D%E8%A6%81%E6%80%A7.md,57.6%,中等,89 -2286,2200-2299,2286. 以组为单位订音乐会的门票,以组为单位订音乐会的门票,https://leetcode.cn/problems/booking-concert-tickets-in-groups/,booking-concert-tickets-in-groups,设计、树状数组、线段树、二分查找,https://algo.itcharge.cn/Solutions/2200-2299/booking-concert-tickets-in-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2286.%20%E4%BB%A5%E7%BB%84%E4%B8%BA%E5%8D%95%E4%BD%8D%E8%AE%A2%E9%9F%B3%E4%B9%90%E4%BC%9A%E7%9A%84%E9%97%A8%E7%A5%A8.md,23.4%,困难,59 -2287,2200-2299,2287. 重排字符形成目标字符串,重排字符形成目标字符串,https://leetcode.cn/problems/rearrange-characters-to-make-target-string/,rearrange-characters-to-make-target-string,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2200-2299/rearrange-characters-to-make-target-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2287.%20%E9%87%8D%E6%8E%92%E5%AD%97%E7%AC%A6%E5%BD%A2%E6%88%90%E7%9B%AE%E6%A0%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md,65.1%,简单,422 -2288,2200-2299,2288. 价格减免,价格减免,https://leetcode.cn/problems/apply-discount-to-prices/,apply-discount-to-prices,字符串,https://algo.itcharge.cn/Solutions/2200-2299/apply-discount-to-prices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2288.%20%E4%BB%B7%E6%A0%BC%E5%87%8F%E5%85%8D.md,31.5%,中等,114 -2289,2200-2299,2289. 使数组按非递减顺序排列,使数组按非递减顺序排列,https://leetcode.cn/problems/steps-to-make-array-non-decreasing/,steps-to-make-array-non-decreasing,栈、数组、链表、单调栈,https://algo.itcharge.cn/Solutions/2200-2299/steps-to-make-array-non-decreasing/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2289.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E6%8C%89%E9%9D%9E%E9%80%92%E5%87%8F%E9%A1%BA%E5%BA%8F%E6%8E%92%E5%88%97.md,21.9%,中等,99 -2290,2200-2299,2290. 到达角落需要移除障碍物的最小数目,到达角落需要移除障碍物的最小数目,https://leetcode.cn/problems/minimum-obstacle-removal-to-reach-corner/,minimum-obstacle-removal-to-reach-corner,广度优先搜索、图、数组、矩阵、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2200-2299/minimum-obstacle-removal-to-reach-corner/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2290.%20%E5%88%B0%E8%BE%BE%E8%A7%92%E8%90%BD%E9%9C%80%E8%A6%81%E7%A7%BB%E9%99%A4%E9%9A%9C%E7%A2%8D%E7%89%A9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E7%9B%AE.md,56.1%,困难,98 -2291,2200-2299,2291. 最大股票收益,最大股票收益,https://leetcode.cn/problems/maximum-profit-from-trading-stocks/,maximum-profit-from-trading-stocks,数组、动态规划,https://algo.itcharge.cn/Solutions/2200-2299/maximum-profit-from-trading-stocks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2291.%20%E6%9C%80%E5%A4%A7%E8%82%A1%E7%A5%A8%E6%94%B6%E7%9B%8A.md,58.6%,中等,15 -2292,2200-2299,2292. 连续两年有 3 个及以上订单的产品,连续两年有 3 个及以上订单的产品,https://leetcode.cn/problems/products-with-three-or-more-orders-in-two-consecutive-years/,products-with-three-or-more-orders-in-two-consecutive-years,数据库,https://algo.itcharge.cn/Solutions/2200-2299/products-with-three-or-more-orders-in-two-consecutive-years/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2292.%20%E8%BF%9E%E7%BB%AD%E4%B8%A4%E5%B9%B4%E6%9C%89%203%20%E4%B8%AA%E5%8F%8A%E4%BB%A5%E4%B8%8A%E8%AE%A2%E5%8D%95%E7%9A%84%E4%BA%A7%E5%93%81.md,44.0%,中等,13 -2293,2200-2299,2293. 极大极小游戏,极大极小游戏,https://leetcode.cn/problems/min-max-game/,min-max-game,数组、模拟,https://algo.itcharge.cn/Solutions/2200-2299/min-max-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2293.%20%E6%9E%81%E5%A4%A7%E6%9E%81%E5%B0%8F%E6%B8%B8%E6%88%8F.md,73.0%,简单,316 -2294,2200-2299,2294. 划分数组使最大差为 K,划分数组使最大差为 K,https://leetcode.cn/problems/partition-array-such-that-maximum-difference-is-k/,partition-array-such-that-maximum-difference-is-k,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2200-2299/partition-array-such-that-maximum-difference-is-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2294.%20%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84%E4%BD%BF%E6%9C%80%E5%A4%A7%E5%B7%AE%E4%B8%BA%20K.md,68.0%,中等,111 -2295,2200-2299,2295. 替换数组中的元素,替换数组中的元素,https://leetcode.cn/problems/replace-elements-in-an-array/,replace-elements-in-an-array,数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2200-2299/replace-elements-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2295.%20%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md,57.8%,中等,114 -2296,2200-2299,2296. 设计一个文本编辑器,设计一个文本编辑器,https://leetcode.cn/problems/design-a-text-editor/,design-a-text-editor,栈、设计、链表、字符串、双向链表、模拟,https://algo.itcharge.cn/Solutions/2200-2299/design-a-text-editor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2296.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%99%A8.md,43.8%,困难,122 -2297,2200-2299,2297. 跳跃游戏 VIII,跳跃游戏 VIII,https://leetcode.cn/problems/jump-game-viii/,jump-game-viii,栈、图、数组、动态规划、最短路、单调栈,https://algo.itcharge.cn/Solutions/2200-2299/jump-game-viii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2297.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20VIII.md,56.3%,中等,14 -2298,2200-2299,2298. 周末任务计数,周末任务计数,https://leetcode.cn/problems/tasks-count-in-the-weekend/,tasks-count-in-the-weekend,数据库,https://algo.itcharge.cn/Solutions/2200-2299/tasks-count-in-the-weekend/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2298.%20%E5%91%A8%E6%9C%AB%E4%BB%BB%E5%8A%A1%E8%AE%A1%E6%95%B0.md,78.9%,中等,11 -2299,2200-2299,2299. 强密码检验器 II,强密码检验器 II,https://leetcode.cn/problems/strong-password-checker-ii/,strong-password-checker-ii,字符串,https://algo.itcharge.cn/Solutions/2200-2299/strong-password-checker-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2299.%20%E5%BC%BA%E5%AF%86%E7%A0%81%E6%A3%80%E9%AA%8C%E5%99%A8%20II.md,65.6%,简单,291 -2300,2300-2399,2300. 咒语和药水的成功对数,咒语和药水的成功对数,https://leetcode.cn/problems/successful-pairs-of-spells-and-potions/,successful-pairs-of-spells-and-potions,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/2300-2399/successful-pairs-of-spells-and-potions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2300.%20%E5%92%92%E8%AF%AD%E5%92%8C%E8%8D%AF%E6%B0%B4%E7%9A%84%E6%88%90%E5%8A%9F%E5%AF%B9%E6%95%B0.md,39.3%,中等,115 -2301,2300-2399,2301. 替换字符后匹配,替换字符后匹配,https://leetcode.cn/problems/match-substring-after-replacement/,match-substring-after-replacement,数组、哈希表、字符串、字符串匹配,https://algo.itcharge.cn/Solutions/2300-2399/match-substring-after-replacement/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2301.%20%E6%9B%BF%E6%8D%A2%E5%AD%97%E7%AC%A6%E5%90%8E%E5%8C%B9%E9%85%8D.md,45.3%,困难,67 -2302,2300-2399,2302. 统计得分小于 K 的子数组数目,统计得分小于 K 的子数组数目,https://leetcode.cn/problems/count-subarrays-with-score-less-than-k/,count-subarrays-with-score-less-than-k,数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/2300-2399/count-subarrays-with-score-less-than-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2302.%20%E7%BB%9F%E8%AE%A1%E5%BE%97%E5%88%86%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,50.9%,困难,83 -2303,2300-2399,2303. 计算应缴税款总额,计算应缴税款总额,https://leetcode.cn/problems/calculate-amount-paid-in-taxes/,calculate-amount-paid-in-taxes,数组、模拟,https://algo.itcharge.cn/Solutions/2300-2399/calculate-amount-paid-in-taxes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2303.%20%E8%AE%A1%E7%AE%97%E5%BA%94%E7%BC%B4%E7%A8%8E%E6%AC%BE%E6%80%BB%E9%A2%9D.md,70.5%,简单,211 -2304,2300-2399,2304. 网格中的最小路径代价,网格中的最小路径代价,https://leetcode.cn/problems/minimum-path-cost-in-a-grid/,minimum-path-cost-in-a-grid,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/minimum-path-cost-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2304.%20%E7%BD%91%E6%A0%BC%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7.md,64.2%,中等,125 -2305,2300-2399,2305. 公平分发饼干,公平分发饼干,https://leetcode.cn/problems/fair-distribution-of-cookies/,fair-distribution-of-cookies,位运算、数组、动态规划、回溯、状态压缩,https://algo.itcharge.cn/Solutions/2300-2399/fair-distribution-of-cookies/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2305.%20%E5%85%AC%E5%B9%B3%E5%88%86%E5%8F%91%E9%A5%BC%E5%B9%B2.md,72.5%,中等,135 -2306,2300-2399,2306. 公司命名,公司命名,https://leetcode.cn/problems/naming-a-company/,naming-a-company,位运算、数组、哈希表、字符串、枚举,https://algo.itcharge.cn/Solutions/2300-2399/naming-a-company/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2306.%20%E5%85%AC%E5%8F%B8%E5%91%BD%E5%90%8D.md,44.5%,困难,68 -2307,2300-2399,2307. 检查方程中的矛盾之处,检查方程中的矛盾之处,https://leetcode.cn/problems/check-for-contradictions-in-equations/,check-for-contradictions-in-equations,深度优先搜索、并查集、图、数组,https://algo.itcharge.cn/Solutions/2300-2399/check-for-contradictions-in-equations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2307.%20%E6%A3%80%E6%9F%A5%E6%96%B9%E7%A8%8B%E4%B8%AD%E7%9A%84%E7%9F%9B%E7%9B%BE%E4%B9%8B%E5%A4%84.md,44.0%,困难,6 -2308,2300-2399,2308. 按性别排列表格,按性别排列表格,https://leetcode.cn/problems/arrange-table-by-gender/,arrange-table-by-gender,数据库,https://algo.itcharge.cn/Solutions/2300-2399/arrange-table-by-gender/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2308.%20%E6%8C%89%E6%80%A7%E5%88%AB%E6%8E%92%E5%88%97%E8%A1%A8%E6%A0%BC.md,77.5%,中等,13 -2309,2300-2399,2309. 兼具大小写的最好英文字母,兼具大小写的最好英文字母,https://leetcode.cn/problems/greatest-english-letter-in-upper-and-lower-case/,greatest-english-letter-in-upper-and-lower-case,哈希表、字符串、枚举,https://algo.itcharge.cn/Solutions/2300-2399/greatest-english-letter-in-upper-and-lower-case/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2309.%20%E5%85%BC%E5%85%B7%E5%A4%A7%E5%B0%8F%E5%86%99%E7%9A%84%E6%9C%80%E5%A5%BD%E8%8B%B1%E6%96%87%E5%AD%97%E6%AF%8D.md,71.8%,简单,328 -2310,2300-2399,2310. 个位数字为 K 的整数之和,个位数字为 K 的整数之和,https://leetcode.cn/problems/sum-of-numbers-with-units-digit-k/,sum-of-numbers-with-units-digit-k,贪心、数学、动态规划、枚举,https://algo.itcharge.cn/Solutions/2300-2399/sum-of-numbers-with-units-digit-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2310.%20%E4%B8%AA%E4%BD%8D%E6%95%B0%E5%AD%97%E4%B8%BA%20K%20%E7%9A%84%E6%95%B4%E6%95%B0%E4%B9%8B%E5%92%8C.md,27.6%,中等,128 -2311,2300-2399,2311. 小于等于 K 的最长二进制子序列,小于等于 K 的最长二进制子序列,https://leetcode.cn/problems/longest-binary-subsequence-less-than-or-equal-to-k/,longest-binary-subsequence-less-than-or-equal-to-k,贪心、记忆化搜索、字符串、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/longest-binary-subsequence-less-than-or-equal-to-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2311.%20%E5%B0%8F%E4%BA%8E%E7%AD%89%E4%BA%8E%20K%20%E7%9A%84%E6%9C%80%E9%95%BF%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%90%E5%BA%8F%E5%88%97.md,37.0%,中等,133 -2312,2300-2399,2312. 卖木头块,卖木头块,https://leetcode.cn/problems/selling-pieces-of-wood/,selling-pieces-of-wood,记忆化搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/selling-pieces-of-wood/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2312.%20%E5%8D%96%E6%9C%A8%E5%A4%B4%E5%9D%97.md,53.7%,困难,60 -2313,2300-2399,2313. 二叉树中得到结果所需的最少翻转次数,二叉树中得到结果所需的最少翻转次数,https://leetcode.cn/problems/minimum-flips-in-binary-tree-to-get-result/,minimum-flips-in-binary-tree-to-get-result,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/2300-2399/minimum-flips-in-binary-tree-to-get-result/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2313.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%BE%97%E5%88%B0%E7%BB%93%E6%9E%9C%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md,67.8%,困难,15 -2314,2300-2399,2314. 每个城市最高气温的第一天,每个城市最高气温的第一天,https://leetcode.cn/problems/the-first-day-of-the-maximum-recorded-degree-in-each-city/,the-first-day-of-the-maximum-recorded-degree-in-each-city,数据库,https://algo.itcharge.cn/Solutions/2300-2399/the-first-day-of-the-maximum-recorded-degree-in-each-city/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2314.%20%E6%AF%8F%E4%B8%AA%E5%9F%8E%E5%B8%82%E6%9C%80%E9%AB%98%E6%B0%94%E6%B8%A9%E7%9A%84%E7%AC%AC%E4%B8%80%E5%A4%A9.md,71.5%,中等,12 -2315,2300-2399,2315. 统计星号,统计星号,https://leetcode.cn/problems/count-asterisks/,count-asterisks,字符串,https://algo.itcharge.cn/Solutions/2300-2399/count-asterisks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2315.%20%E7%BB%9F%E8%AE%A1%E6%98%9F%E5%8F%B7.md,85.8%,简单,464 -2316,2300-2399,2316. 统计无向图中无法互相到达点对数,统计无向图中无法互相到达点对数,https://leetcode.cn/problems/count-unreachable-pairs-of-nodes-in-an-undirected-graph/,count-unreachable-pairs-of-nodes-in-an-undirected-graph,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/2300-2399/count-unreachable-pairs-of-nodes-in-an-undirected-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2316.%20%E7%BB%9F%E8%AE%A1%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E6%97%A0%E6%B3%95%E4%BA%92%E7%9B%B8%E5%88%B0%E8%BE%BE%E7%82%B9%E5%AF%B9%E6%95%B0.md,39.1%,中等,118 -2317,2300-2399,2317. 操作后的最大异或和,操作后的最大异或和,https://leetcode.cn/problems/maximum-xor-after-operations/,maximum-xor-after-operations,位运算、数组、数学,https://algo.itcharge.cn/Solutions/2300-2399/maximum-xor-after-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2317.%20%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%92%8C.md,85.2%,中等,67 -2318,2300-2399,2318. 不同骰子序列的数目,不同骰子序列的数目,https://leetcode.cn/problems/number-of-distinct-roll-sequences/,number-of-distinct-roll-sequences,记忆化搜索、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/number-of-distinct-roll-sequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2318.%20%E4%B8%8D%E5%90%8C%E9%AA%B0%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,57.1%,困难,70 -2319,2300-2399,2319. 判断矩阵是否是一个 X 矩阵,判断矩阵是否是一个 X 矩阵,https://leetcode.cn/problems/check-if-matrix-is-x-matrix/,check-if-matrix-is-x-matrix,数组、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/check-if-matrix-is-x-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2319.%20%E5%88%A4%E6%96%AD%E7%9F%A9%E9%98%B5%E6%98%AF%E5%90%A6%E6%98%AF%E4%B8%80%E4%B8%AA%20X%20%E7%9F%A9%E9%98%B5.md,76.2%,简单,340 -2320,2300-2399,2320. 统计放置房子的方式数,统计放置房子的方式数,https://leetcode.cn/problems/count-number-of-ways-to-place-houses/,count-number-of-ways-to-place-houses,动态规划,https://algo.itcharge.cn/Solutions/2300-2399/count-number-of-ways-to-place-houses/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2320.%20%E7%BB%9F%E8%AE%A1%E6%94%BE%E7%BD%AE%E6%88%BF%E5%AD%90%E7%9A%84%E6%96%B9%E5%BC%8F%E6%95%B0.md,40.4%,中等,94 -2321,2300-2399,2321. 拼接数组的最大分数,拼接数组的最大分数,https://leetcode.cn/problems/maximum-score-of-spliced-array/,maximum-score-of-spliced-array,数组、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/maximum-score-of-spliced-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2321.%20%E6%8B%BC%E6%8E%A5%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0.md,51.3%,困难,106 -2322,2300-2399,2322. 从树中删除边的最小分数,从树中删除边的最小分数,https://leetcode.cn/problems/minimum-score-after-removals-on-a-tree/,minimum-score-after-removals-on-a-tree,位运算、树、深度优先搜索、数组,https://algo.itcharge.cn/Solutions/2300-2399/minimum-score-after-removals-on-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2322.%20%E4%BB%8E%E6%A0%91%E4%B8%AD%E5%88%A0%E9%99%A4%E8%BE%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E5%88%86%E6%95%B0.md,58.9%,困难,55 -2323,2300-2399,2323. 完成所有工作的最短时间 II,完成所有工作的最短时间 II,https://leetcode.cn/problems/find-minimum-time-to-finish-all-jobs-ii/,find-minimum-time-to-finish-all-jobs-ii,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2300-2399/find-minimum-time-to-finish-all-jobs-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2323.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E5%B7%A5%E4%BD%9C%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4%20II.md,70.6%,中等,10 -2324,2300-2399,2324. 产品销售分析 IV,产品销售分析 IV,https://leetcode.cn/problems/product-sales-analysis-iv/,product-sales-analysis-iv,数据库,https://algo.itcharge.cn/Solutions/2300-2399/product-sales-analysis-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2324.%20%E4%BA%A7%E5%93%81%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%20IV.md,70.7%,中等,10 -2325,2300-2399,2325. 解密消息,解密消息,https://leetcode.cn/problems/decode-the-message/,decode-the-message,哈希表、字符串,https://algo.itcharge.cn/Solutions/2300-2399/decode-the-message/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2325.%20%E8%A7%A3%E5%AF%86%E6%B6%88%E6%81%AF.md,86.0%,简单,429 -2326,2300-2399,2326. 螺旋矩阵 IV,螺旋矩阵 IV,https://leetcode.cn/problems/spiral-matrix-iv/,spiral-matrix-iv,数组、链表、矩阵、模拟,https://algo.itcharge.cn/Solutions/2300-2399/spiral-matrix-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2326.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20IV.md,66.7%,中等,138 -2327,2300-2399,2327. 知道秘密的人数,知道秘密的人数,https://leetcode.cn/problems/number-of-people-aware-of-a-secret/,number-of-people-aware-of-a-secret,队列、动态规划、模拟,https://algo.itcharge.cn/Solutions/2300-2399/number-of-people-aware-of-a-secret/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2327.%20%E7%9F%A5%E9%81%93%E7%A7%98%E5%AF%86%E7%9A%84%E4%BA%BA%E6%95%B0.md,45.7%,中等,197 -2328,2300-2399,2328. 网格图中递增路径的数目,网格图中递增路径的数目,https://leetcode.cn/problems/number-of-increasing-paths-in-a-grid/,number-of-increasing-paths-in-a-grid,深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/number-of-increasing-paths-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2328.%20%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,50.9%,困难,115 -2329,2300-2399,2329. 产品销售分析Ⅴ,产品销售分析Ⅴ,https://leetcode.cn/problems/product-sales-analysis-v/,product-sales-analysis-v,数据库,https://algo.itcharge.cn/Solutions/2300-2399/product-sales-analysis-v/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2329.%20%E4%BA%A7%E5%93%81%E9%94%80%E5%94%AE%E5%88%86%E6%9E%90%E2%85%A4.md,72.4%,简单,10 -2330,2300-2399,2330. 有效的回文 IV,有效的回文 IV,https://leetcode.cn/problems/valid-palindrome-iv/,valid-palindrome-iv,双指针、字符串,https://algo.itcharge.cn/Solutions/2300-2399/valid-palindrome-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2330.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%9B%9E%E6%96%87%20IV.md,81.5%,中等,15 -2331,2300-2399,2331. 计算布尔二叉树的值,计算布尔二叉树的值,https://leetcode.cn/problems/evaluate-boolean-binary-tree/,evaluate-boolean-binary-tree,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2300-2399/evaluate-boolean-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2331.%20%E8%AE%A1%E7%AE%97%E5%B8%83%E5%B0%94%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%80%BC.md,84.0%,简单,362 -2332,2300-2399,2332. 坐上公交的最晚时间,坐上公交的最晚时间,https://leetcode.cn/problems/the-latest-time-to-catch-a-bus/,the-latest-time-to-catch-a-bus,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/2300-2399/the-latest-time-to-catch-a-bus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2332.%20%E5%9D%90%E4%B8%8A%E5%85%AC%E4%BA%A4%E7%9A%84%E6%9C%80%E6%99%9A%E6%97%B6%E9%97%B4.md,24.3%,中等,92 -2333,2300-2399,2333. 最小差值平方和,最小差值平方和,https://leetcode.cn/problems/minimum-sum-of-squared-difference/,minimum-sum-of-squared-difference,数组、数学、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/minimum-sum-of-squared-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2333.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%E5%B9%B3%E6%96%B9%E5%92%8C.md,26.7%,中等,74 -2334,2300-2399,2334. 元素值大于变化阈值的子数组,元素值大于变化阈值的子数组,https://leetcode.cn/problems/subarray-with-elements-greater-than-varying-threshold/,subarray-with-elements-greater-than-varying-threshold,栈、并查集、数组、单调栈,https://algo.itcharge.cn/Solutions/2300-2399/subarray-with-elements-greater-than-varying-threshold/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2334.%20%E5%85%83%E7%B4%A0%E5%80%BC%E5%A4%A7%E4%BA%8E%E5%8F%98%E5%8C%96%E9%98%88%E5%80%BC%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,47.4%,困难,54 -2335,2300-2399,2335. 装满杯子需要的最短总时长,装满杯子需要的最短总时长,https://leetcode.cn/problems/minimum-amount-of-time-to-fill-cups/,minimum-amount-of-time-to-fill-cups,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/minimum-amount-of-time-to-fill-cups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2335.%20%E8%A3%85%E6%BB%A1%E6%9D%AF%E5%AD%90%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E7%9F%AD%E6%80%BB%E6%97%B6%E9%95%BF.md,64.5%,简单,392 -2336,2300-2399,2336. 无限集中的最小数字,无限集中的最小数字,https://leetcode.cn/problems/smallest-number-in-infinite-set/,smallest-number-in-infinite-set,设计、哈希表、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/smallest-number-in-infinite-set/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2336.%20%E6%97%A0%E9%99%90%E9%9B%86%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md,69.8%,中等,143 -2337,2300-2399,2337. 移动片段得到字符串,移动片段得到字符串,https://leetcode.cn/problems/move-pieces-to-obtain-a-string/,move-pieces-to-obtain-a-string,双指针、字符串,https://algo.itcharge.cn/Solutions/2300-2399/move-pieces-to-obtain-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2337.%20%E7%A7%BB%E5%8A%A8%E7%89%87%E6%AE%B5%E5%BE%97%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2.md,39.3%,中等,124 -2338,2300-2399,2338. 统计理想数组的数目,统计理想数组的数目,https://leetcode.cn/problems/count-the-number-of-ideal-arrays/,count-the-number-of-ideal-arrays,数学、动态规划、组合数学、数论,https://algo.itcharge.cn/Solutions/2300-2399/count-the-number-of-ideal-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2338.%20%E7%BB%9F%E8%AE%A1%E7%90%86%E6%83%B3%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,30.7%,困难,46 -2339,2300-2399,2339. 联赛的所有比赛,联赛的所有比赛,https://leetcode.cn/problems/all-the-matches-of-the-league/,all-the-matches-of-the-league,数据库,https://algo.itcharge.cn/Solutions/2300-2399/all-the-matches-of-the-league/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2339.%20%E8%81%94%E8%B5%9B%E7%9A%84%E6%89%80%E6%9C%89%E6%AF%94%E8%B5%9B.md,75.8%,简单,11 -2340,2300-2399,2340. 生成有效数组的最少交换次数,生成有效数组的最少交换次数,https://leetcode.cn/problems/minimum-adjacent-swaps-to-make-a-valid-array/,minimum-adjacent-swaps-to-make-a-valid-array,贪心、数组,https://algo.itcharge.cn/Solutions/2300-2399/minimum-adjacent-swaps-to-make-a-valid-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2340.%20%E7%94%9F%E6%88%90%E6%9C%89%E6%95%88%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md,76.5%,中等,10 -2341,2300-2399,2341. 数组能形成多少数对,数组能形成多少数对,https://leetcode.cn/problems/maximum-number-of-pairs-in-array/,maximum-number-of-pairs-in-array,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2300-2399/maximum-number-of-pairs-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2341.%20%E6%95%B0%E7%BB%84%E8%83%BD%E5%BD%A2%E6%88%90%E5%A4%9A%E5%B0%91%E6%95%B0%E5%AF%B9.md,81.2%,简单,473 -2342,2300-2399,2342. 数位和相等数对的最大和,数位和相等数对的最大和,https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/,max-sum-of-a-pair-with-equal-sum-of-digits,数组、哈希表、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/max-sum-of-a-pair-with-equal-sum-of-digits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2342.%20%E6%95%B0%E4%BD%8D%E5%92%8C%E7%9B%B8%E7%AD%89%E6%95%B0%E5%AF%B9%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,53.6%,中等,143 -2343,2300-2399,2343. 裁剪数字后查询第 K 小的数字,裁剪数字后查询第 K 小的数字,https://leetcode.cn/problems/query-kth-smallest-trimmed-number/,query-kth-smallest-trimmed-number,数组、字符串、分治、快速选择、基数排序、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/query-kth-smallest-trimmed-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2343.%20%E8%A3%81%E5%89%AA%E6%95%B0%E5%AD%97%E5%90%8E%E6%9F%A5%E8%AF%A2%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AD%97.md,41.3%,中等,143 -2344,2300-2399,2344. 使数组可以被整除的最少删除次数,使数组可以被整除的最少删除次数,https://leetcode.cn/problems/minimum-deletions-to-make-array-divisible/,minimum-deletions-to-make-array-divisible,数组、数学、数论、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/minimum-deletions-to-make-array-divisible/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2344.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BB%A5%E8%A2%AB%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%A0%E9%99%A4%E6%AC%A1%E6%95%B0.md,53.3%,困难,117 -2345,2300-2399,2345. 寻找可见山的数量,寻找可见山的数量,https://leetcode.cn/problems/finding-the-number-of-visible-mountains/,finding-the-number-of-visible-mountains,栈、数组、排序、单调栈,https://algo.itcharge.cn/Solutions/2300-2399/finding-the-number-of-visible-mountains/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2345.%20%E5%AF%BB%E6%89%BE%E5%8F%AF%E8%A7%81%E5%B1%B1%E7%9A%84%E6%95%B0%E9%87%8F.md,47.9%,中等,6 -2346,2300-2399,2346. 以百分比计算排名,以百分比计算排名,https://leetcode.cn/problems/compute-the-rank-as-a-percentage/,compute-the-rank-as-a-percentage,数据库,https://algo.itcharge.cn/Solutions/2300-2399/compute-the-rank-as-a-percentage/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2346.%20%E4%BB%A5%E7%99%BE%E5%88%86%E6%AF%94%E8%AE%A1%E7%AE%97%E6%8E%92%E5%90%8D.md,37.5%,中等,9 -2347,2300-2399,2347. 最好的扑克手牌,最好的扑克手牌,https://leetcode.cn/problems/best-poker-hand/,best-poker-hand,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2300-2399/best-poker-hand/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2347.%20%E6%9C%80%E5%A5%BD%E7%9A%84%E6%89%91%E5%85%8B%E6%89%8B%E7%89%8C.md,59.4%,简单,284 -2348,2300-2399,2348. 全 0 子数组的数目,全 0 子数组的数目,https://leetcode.cn/problems/number-of-zero-filled-subarrays/,number-of-zero-filled-subarrays,数组、数学,https://algo.itcharge.cn/Solutions/2300-2399/number-of-zero-filled-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2348.%20%E5%85%A8%200%20%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,56.3%,中等,116 -2349,2300-2399,2349. 设计数字容器系统,设计数字容器系统,https://leetcode.cn/problems/design-a-number-container-system/,design-a-number-container-system,设计、哈希表、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/design-a-number-container-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2349.%20%E8%AE%BE%E8%AE%A1%E6%95%B0%E5%AD%97%E5%AE%B9%E5%99%A8%E7%B3%BB%E7%BB%9F.md,37.3%,中等,93 -2350,2300-2399,2350. 不可能得到的最短骰子序列,不可能得到的最短骰子序列,https://leetcode.cn/problems/shortest-impossible-sequence-of-rolls/,shortest-impossible-sequence-of-rolls,贪心、数组、哈希表,https://algo.itcharge.cn/Solutions/2300-2399/shortest-impossible-sequence-of-rolls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2350.%20%E4%B8%8D%E5%8F%AF%E8%83%BD%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E7%9F%AD%E9%AA%B0%E5%AD%90%E5%BA%8F%E5%88%97.md,64.8%,困难,74 -2351,2300-2399,2351. 第一个出现两次的字母,第一个出现两次的字母,https://leetcode.cn/problems/first-letter-to-appear-twice/,first-letter-to-appear-twice,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2300-2399/first-letter-to-appear-twice/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2351.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%87%BA%E7%8E%B0%E4%B8%A4%E6%AC%A1%E7%9A%84%E5%AD%97%E6%AF%8D.md,84.8%,简单,408 -2352,2300-2399,2352. 相等行列对,相等行列对,https://leetcode.cn/problems/equal-row-and-column-pairs/,equal-row-and-column-pairs,数组、哈希表、矩阵、模拟,https://algo.itcharge.cn/Solutions/2300-2399/equal-row-and-column-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2352.%20%E7%9B%B8%E7%AD%89%E8%A1%8C%E5%88%97%E5%AF%B9.md,74.1%,中等,337 -2353,2300-2399,2353. 设计食物评分系统,设计食物评分系统,https://leetcode.cn/problems/design-a-food-rating-system/,design-a-food-rating-system,设计、哈希表、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/design-a-food-rating-system/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2353.%20%E8%AE%BE%E8%AE%A1%E9%A3%9F%E7%89%A9%E8%AF%84%E5%88%86%E7%B3%BB%E7%BB%9F.md,30.6%,中等,117 -2354,2300-2399,2354. 优质数对的数目,优质数对的数目,https://leetcode.cn/problems/number-of-excellent-pairs/,number-of-excellent-pairs,位运算、数组、哈希表、二分查找,https://algo.itcharge.cn/Solutions/2300-2399/number-of-excellent-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2354.%20%E4%BC%98%E8%B4%A8%E6%95%B0%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,44.5%,困难,76 -2355,2300-2399,2355. 你能拿走的最大图书数量,你能拿走的最大图书数量,https://leetcode.cn/problems/maximum-number-of-books-you-can-take/,maximum-number-of-books-you-can-take,栈、数组、动态规划、单调栈,https://algo.itcharge.cn/Solutions/2300-2399/maximum-number-of-books-you-can-take/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2355.%20%E4%BD%A0%E8%83%BD%E6%8B%BF%E8%B5%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%9B%BE%E4%B9%A6%E6%95%B0%E9%87%8F.md,59.0%,困难,10 -2356,2300-2399,2356. 每位教师所教授的科目种类的数量,每位教师所教授的科目种类的数量,https://leetcode.cn/problems/number-of-unique-subjects-taught-by-each-teacher/,number-of-unique-subjects-taught-by-each-teacher,数据库,https://algo.itcharge.cn/Solutions/2300-2399/number-of-unique-subjects-taught-by-each-teacher/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2356.%20%E6%AF%8F%E4%BD%8D%E6%95%99%E5%B8%88%E6%89%80%E6%95%99%E6%8E%88%E7%9A%84%E7%A7%91%E7%9B%AE%E7%A7%8D%E7%B1%BB%E7%9A%84%E6%95%B0%E9%87%8F.md,81.0%,简单,29 -2357,2300-2399,2357. 使数组中所有元素都等于零,使数组中所有元素都等于零,https://leetcode.cn/problems/make-array-zero-by-subtracting-equal-amounts/,make-array-zero-by-subtracting-equal-amounts,贪心、数组、哈希表、排序、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/make-array-zero-by-subtracting-equal-amounts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2357.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E9%83%BD%E7%AD%89%E4%BA%8E%E9%9B%B6.md,75.9%,简单,471 -2358,2300-2399,2358. 分组的最大数量,分组的最大数量,https://leetcode.cn/problems/maximum-number-of-groups-entering-a-competition/,maximum-number-of-groups-entering-a-competition,贪心、数组、数学、二分查找,https://algo.itcharge.cn/Solutions/2300-2399/maximum-number-of-groups-entering-a-competition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2358.%20%E5%88%86%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md,64.3%,中等,171 -2359,2300-2399,2359. 找到离给定两个节点最近的节点,找到离给定两个节点最近的节点,https://leetcode.cn/problems/find-closest-node-to-given-two-nodes/,find-closest-node-to-given-two-nodes,深度优先搜索、图,https://algo.itcharge.cn/Solutions/2300-2399/find-closest-node-to-given-two-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2359.%20%E6%89%BE%E5%88%B0%E7%A6%BB%E7%BB%99%E5%AE%9A%E4%B8%A4%E4%B8%AA%E8%8A%82%E7%82%B9%E6%9C%80%E8%BF%91%E7%9A%84%E8%8A%82%E7%82%B9.md,30.3%,中等,110 -2360,2300-2399,2360. 图中的最长环,图中的最长环,https://leetcode.cn/problems/longest-cycle-in-a-graph/,longest-cycle-in-a-graph,深度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/2300-2399/longest-cycle-in-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2360.%20%E5%9B%BE%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E7%8E%AF.md,38.0%,困难,140 -2361,2300-2399,2361. 乘坐火车路线的最少费用,乘坐火车路线的最少费用,https://leetcode.cn/problems/minimum-costs-using-the-train-line/,minimum-costs-using-the-train-line,数组、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/minimum-costs-using-the-train-line/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2361.%20%E4%B9%98%E5%9D%90%E7%81%AB%E8%BD%A6%E8%B7%AF%E7%BA%BF%E7%9A%84%E6%9C%80%E5%B0%91%E8%B4%B9%E7%94%A8.md,77.7%,困难,13 -2362,2300-2399,2362. 生成发票,生成发票,https://leetcode.cn/problems/generate-the-invoice/,generate-the-invoice,数据库,https://algo.itcharge.cn/Solutions/2300-2399/generate-the-invoice/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2362.%20%E7%94%9F%E6%88%90%E5%8F%91%E7%A5%A8.md,72.9%,困难,14 -2363,2300-2399,2363. 合并相似的物品,合并相似的物品,https://leetcode.cn/problems/merge-similar-items/,merge-similar-items,数组、哈希表、有序集合、排序,https://algo.itcharge.cn/Solutions/2300-2399/merge-similar-items/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2363.%20%E5%90%88%E5%B9%B6%E7%9B%B8%E4%BC%BC%E7%9A%84%E7%89%A9%E5%93%81.md,78.2%,简单,298 -2364,2300-2399,2364. 统计坏数对的数目,统计坏数对的数目,https://leetcode.cn/problems/count-number-of-bad-pairs/,count-number-of-bad-pairs,数组、哈希表,https://algo.itcharge.cn/Solutions/2300-2399/count-number-of-bad-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2364.%20%E7%BB%9F%E8%AE%A1%E5%9D%8F%E6%95%B0%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,42.2%,中等,86 -2365,2300-2399,2365. 任务调度器 II,任务调度器 II,https://leetcode.cn/problems/task-scheduler-ii/,task-scheduler-ii,数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2300-2399/task-scheduler-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2365.%20%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8%20II.md,48.0%,中等,79 -2366,2300-2399,2366. 将数组排序的最少替换次数,将数组排序的最少替换次数,https://leetcode.cn/problems/minimum-replacements-to-sort-the-array/,minimum-replacements-to-sort-the-array,贪心、数组、数学,https://algo.itcharge.cn/Solutions/2300-2399/minimum-replacements-to-sort-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2366.%20%E5%B0%86%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F%E7%9A%84%E6%9C%80%E5%B0%91%E6%9B%BF%E6%8D%A2%E6%AC%A1%E6%95%B0.md,42.7%,困难,47 -2367,2300-2399,2367. 算术三元组的数目,算术三元组的数目,https://leetcode.cn/problems/number-of-arithmetic-triplets/,number-of-arithmetic-triplets,数组、哈希表、双指针、枚举,https://algo.itcharge.cn/Solutions/2300-2399/number-of-arithmetic-triplets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2367.%20%E7%AE%97%E6%9C%AF%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,83.4%,简单,381 -2368,2300-2399,2368. 受限条件下可到达节点的数目,受限条件下可到达节点的数目,https://leetcode.cn/problems/reachable-nodes-with-restrictions/,reachable-nodes-with-restrictions,树、深度优先搜索、广度优先搜索、图、数组、哈希表,https://algo.itcharge.cn/Solutions/2300-2399/reachable-nodes-with-restrictions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2368.%20%E5%8F%97%E9%99%90%E6%9D%A1%E4%BB%B6%E4%B8%8B%E5%8F%AF%E5%88%B0%E8%BE%BE%E8%8A%82%E7%82%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,46.1%,中等,148 -2369,2300-2399,2369. 检查数组是否存在有效划分,检查数组是否存在有效划分,https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/,check-if-there-is-a-valid-partition-for-the-array,数组、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/check-if-there-is-a-valid-partition-for-the-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2369.%20%E6%A3%80%E6%9F%A5%E6%95%B0%E7%BB%84%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E6%9C%89%E6%95%88%E5%88%92%E5%88%86.md,38.4%,中等,115 -2370,2300-2399,2370. 最长理想子序列,最长理想子序列,https://leetcode.cn/problems/longest-ideal-subsequence/,longest-ideal-subsequence,哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/longest-ideal-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2370.%20%E6%9C%80%E9%95%BF%E7%90%86%E6%83%B3%E5%AD%90%E5%BA%8F%E5%88%97.md,43.1%,中等,116 -2371,2300-2399,2371. 最小化网格中的最大值,最小化网格中的最大值,https://leetcode.cn/problems/minimize-maximum-value-in-a-grid/,minimize-maximum-value-in-a-grid,贪心、并查集、图、拓扑排序、数组、矩阵、排序,https://algo.itcharge.cn/Solutions/2300-2399/minimize-maximum-value-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2371.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E7%BD%91%E6%A0%BC%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,74.1%,困难,11 -2372,2300-2399,2372. 计算每个销售人员的影响力,计算每个销售人员的影响力,https://leetcode.cn/problems/calculate-the-influence-of-each-salesperson/,calculate-the-influence-of-each-salesperson,数据库,https://algo.itcharge.cn/Solutions/2300-2399/calculate-the-influence-of-each-salesperson/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2372.%20%E8%AE%A1%E7%AE%97%E6%AF%8F%E4%B8%AA%E9%94%80%E5%94%AE%E4%BA%BA%E5%91%98%E7%9A%84%E5%BD%B1%E5%93%8D%E5%8A%9B.md,77.7%,中等,8 -2373,2300-2399,2373. 矩阵中的局部最大值,矩阵中的局部最大值,https://leetcode.cn/problems/largest-local-values-in-a-matrix/,largest-local-values-in-a-matrix,数组、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/largest-local-values-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2373.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E5%B1%80%E9%83%A8%E6%9C%80%E5%A4%A7%E5%80%BC.md,85.2%,简单,312 -2374,2300-2399,2374. 边积分最高的节点,边积分最高的节点,https://leetcode.cn/problems/node-with-highest-edge-score/,node-with-highest-edge-score,图、哈希表,https://algo.itcharge.cn/Solutions/2300-2399/node-with-highest-edge-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2374.%20%E8%BE%B9%E7%A7%AF%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E8%8A%82%E7%82%B9.md,42.3%,中等,105 -2375,2300-2399,2375. 根据模式串构造最小数字,根据模式串构造最小数字,https://leetcode.cn/problems/construct-smallest-number-from-di-string/,construct-smallest-number-from-di-string,栈、贪心、字符串、回溯,https://algo.itcharge.cn/Solutions/2300-2399/construct-smallest-number-from-di-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2375.%20%E6%A0%B9%E6%8D%AE%E6%A8%A1%E5%BC%8F%E4%B8%B2%E6%9E%84%E9%80%A0%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md,70.0%,中等,212 -2376,2300-2399,2376. 统计特殊整数,统计特殊整数,https://leetcode.cn/problems/count-special-integers/,count-special-integers,数学、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/count-special-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2376.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E6%95%B4%E6%95%B0.md,51.6%,困难,113 -2377,2300-2399,2377. 整理奥运表,整理奥运表,https://leetcode.cn/problems/sort-the-olympic-table/,sort-the-olympic-table,数据库,https://algo.itcharge.cn/Solutions/2300-2399/sort-the-olympic-table/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2377.%20%E6%95%B4%E7%90%86%E5%A5%A5%E8%BF%90%E8%A1%A8.md,80.1%,简单,9 -2378,2300-2399,2378. 选择边来最大化树的得分,选择边来最大化树的得分,https://leetcode.cn/problems/choose-edges-to-maximize-score-in-a-tree/,choose-edges-to-maximize-score-in-a-tree,树、深度优先搜索、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/choose-edges-to-maximize-score-in-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2378.%20%E9%80%89%E6%8B%A9%E8%BE%B9%E6%9D%A5%E6%9C%80%E5%A4%A7%E5%8C%96%E6%A0%91%E7%9A%84%E5%BE%97%E5%88%86.md,68.4%,中等,8 -2379,2300-2399,2379. 得到 K 个黑块的最少涂色次数,得到 K 个黑块的最少涂色次数,https://leetcode.cn/problems/minimum-recolors-to-get-k-consecutive-black-blocks/,minimum-recolors-to-get-k-consecutive-black-blocks,字符串、滑动窗口,https://algo.itcharge.cn/Solutions/2300-2399/minimum-recolors-to-get-k-consecutive-black-blocks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2379.%20%E5%BE%97%E5%88%B0%20K%20%E4%B8%AA%E9%BB%91%E5%9D%97%E7%9A%84%E6%9C%80%E5%B0%91%E6%B6%82%E8%89%B2%E6%AC%A1%E6%95%B0.md,61.8%,简单,429 -2380,2300-2399,2380. 二进制字符串重新安排顺序需要的时间,二进制字符串重新安排顺序需要的时间,https://leetcode.cn/problems/time-needed-to-rearrange-a-binary-string/,time-needed-to-rearrange-a-binary-string,字符串、动态规划、模拟,https://algo.itcharge.cn/Solutions/2300-2399/time-needed-to-rearrange-a-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2380.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8D%E6%96%B0%E5%AE%89%E6%8E%92%E9%A1%BA%E5%BA%8F%E9%9C%80%E8%A6%81%E7%9A%84%E6%97%B6%E9%97%B4.md,56.1%,中等,73 -2381,2300-2399,2381. 字母移位 II,字母移位 II,https://leetcode.cn/problems/shifting-letters-ii/,shifting-letters-ii,数组、字符串、前缀和,https://algo.itcharge.cn/Solutions/2300-2399/shifting-letters-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2381.%20%E5%AD%97%E6%AF%8D%E7%A7%BB%E4%BD%8D%20II.md,36.8%,中等,100 -2382,2300-2399,2382. 删除操作后的最大子段和,删除操作后的最大子段和,https://leetcode.cn/problems/maximum-segment-sum-after-removals/,maximum-segment-sum-after-removals,并查集、数组、有序集合、前缀和,https://algo.itcharge.cn/Solutions/2300-2399/maximum-segment-sum-after-removals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2382.%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%90%E6%AE%B5%E5%92%8C.md,56.3%,困难,94 -2383,2300-2399,2383. 赢得比赛需要的最少训练时长,赢得比赛需要的最少训练时长,https://leetcode.cn/problems/minimum-hours-of-training-to-win-a-competition/,minimum-hours-of-training-to-win-a-competition,贪心、数组,https://algo.itcharge.cn/Solutions/2300-2399/minimum-hours-of-training-to-win-a-competition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2383.%20%E8%B5%A2%E5%BE%97%E6%AF%94%E8%B5%9B%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E8%AE%AD%E7%BB%83%E6%97%B6%E9%95%BF.md,47.2%,简单,360 -2384,2300-2399,2384. 最大回文数字,最大回文数字,https://leetcode.cn/problems/largest-palindromic-number/,largest-palindromic-number,贪心、哈希表、字符串,https://algo.itcharge.cn/Solutions/2300-2399/largest-palindromic-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2384.%20%E6%9C%80%E5%A4%A7%E5%9B%9E%E6%96%87%E6%95%B0%E5%AD%97.md,31.1%,中等,150 -2385,2300-2399,2385. 感染二叉树需要的总时间,感染二叉树需要的总时间,https://leetcode.cn/problems/amount-of-time-for-binary-tree-to-be-infected/,amount-of-time-for-binary-tree-to-be-infected,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2300-2399/amount-of-time-for-binary-tree-to-be-infected/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2385.%20%E6%84%9F%E6%9F%93%E4%BA%8C%E5%8F%89%E6%A0%91%E9%9C%80%E8%A6%81%E7%9A%84%E6%80%BB%E6%97%B6%E9%97%B4.md,46.2%,中等,157 -2386,2300-2399,2386. 找出数组的第 K 大和,找出数组的第 K 大和,https://leetcode.cn/problems/find-the-k-sum-of-an-array/,find-the-k-sum-of-an-array,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/find-the-k-sum-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2386.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%92%8C.md,42.3%,困难,51 -2387,2300-2399,2387. 行排序矩阵的中位数,行排序矩阵的中位数,https://leetcode.cn/problems/median-of-a-row-wise-sorted-matrix/,median-of-a-row-wise-sorted-matrix,数组、二分查找、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/median-of-a-row-wise-sorted-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2387.%20%E8%A1%8C%E6%8E%92%E5%BA%8F%E7%9F%A9%E9%98%B5%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md,76.0%,中等,5 -2388,2300-2399,2388. 将表中的空值更改为前一个值,将表中的空值更改为前一个值,https://leetcode.cn/problems/change-null-values-in-a-table-to-the-previous-value/,change-null-values-in-a-table-to-the-previous-value,数据库,https://algo.itcharge.cn/Solutions/2300-2399/change-null-values-in-a-table-to-the-previous-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2388.%20%E5%B0%86%E8%A1%A8%E4%B8%AD%E7%9A%84%E7%A9%BA%E5%80%BC%E6%9B%B4%E6%94%B9%E4%B8%BA%E5%89%8D%E4%B8%80%E4%B8%AA%E5%80%BC.md,67.7%,中等,13 -2389,2300-2399,2389. 和有限的最长子序列,和有限的最长子序列,https://leetcode.cn/problems/longest-subsequence-with-limited-sum/,longest-subsequence-with-limited-sum,贪心、数组、二分查找、前缀和、排序,https://algo.itcharge.cn/Solutions/2300-2399/longest-subsequence-with-limited-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2389.%20%E5%92%8C%E6%9C%89%E9%99%90%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%BA%8F%E5%88%97.md,70.9%,简单,343 -2390,2300-2399,2390. 从字符串中移除星号,从字符串中移除星号,https://leetcode.cn/problems/removing-stars-from-a-string/,removing-stars-from-a-string,栈、字符串、模拟,https://algo.itcharge.cn/Solutions/2300-2399/removing-stars-from-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2390.%20%E4%BB%8E%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%A7%BB%E9%99%A4%E6%98%9F%E5%8F%B7.md,69.1%,中等,166 -2391,2300-2399,2391. 收集垃圾的最少总时间,收集垃圾的最少总时间,https://leetcode.cn/problems/minimum-amount-of-time-to-collect-garbage/,minimum-amount-of-time-to-collect-garbage,数组、字符串、前缀和,https://algo.itcharge.cn/Solutions/2300-2399/minimum-amount-of-time-to-collect-garbage/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2391.%20%E6%94%B6%E9%9B%86%E5%9E%83%E5%9C%BE%E7%9A%84%E6%9C%80%E5%B0%91%E6%80%BB%E6%97%B6%E9%97%B4.md,85.7%,中等,159 -2392,2300-2399,2392. 给定条件下构造矩阵,给定条件下构造矩阵,https://leetcode.cn/problems/build-a-matrix-with-conditions/,build-a-matrix-with-conditions,图、拓扑排序、数组、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/build-a-matrix-with-conditions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2392.%20%E7%BB%99%E5%AE%9A%E6%9D%A1%E4%BB%B6%E4%B8%8B%E6%9E%84%E9%80%A0%E7%9F%A9%E9%98%B5.md,55.7%,困难,94 -2393,2300-2399,2393. 严格递增的子数组个数,严格递增的子数组个数,https://leetcode.cn/problems/count-strictly-increasing-subarrays/,count-strictly-increasing-subarrays,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/2300-2399/count-strictly-increasing-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2393.%20%E4%B8%A5%E6%A0%BC%E9%80%92%E5%A2%9E%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md,78.5%,中等,14 -2394,2300-2399,2394. 开除员工,开除员工,https://leetcode.cn/problems/employees-with-deductions/,employees-with-deductions,数据库,https://algo.itcharge.cn/Solutions/2300-2399/employees-with-deductions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2394.%20%E5%BC%80%E9%99%A4%E5%91%98%E5%B7%A5.md,53.0%,中等,13 -2395,2300-2399,2395. 和相等的子数组,和相等的子数组,https://leetcode.cn/problems/find-subarrays-with-equal-sum/,find-subarrays-with-equal-sum,数组、哈希表,https://algo.itcharge.cn/Solutions/2300-2399/find-subarrays-with-equal-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2395.%20%E5%92%8C%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,76.3%,简单,237 -2396,2300-2399,2396. 严格回文的数字,严格回文的数字,https://leetcode.cn/problems/strictly-palindromic-number/,strictly-palindromic-number,脑筋急转弯、数学、双指针,https://algo.itcharge.cn/Solutions/2300-2399/strictly-palindromic-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2396.%20%E4%B8%A5%E6%A0%BC%E5%9B%9E%E6%96%87%E7%9A%84%E6%95%B0%E5%AD%97.md,87.9%,中等,115 -2397,2300-2399,2397. 被列覆盖的最多行数,被列覆盖的最多行数,https://leetcode.cn/problems/maximum-rows-covered-by-columns/,maximum-rows-covered-by-columns,位运算、数组、回溯、枚举、矩阵,https://algo.itcharge.cn/Solutions/2300-2399/maximum-rows-covered-by-columns/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2397.%20%E8%A2%AB%E5%88%97%E8%A6%86%E7%9B%96%E7%9A%84%E6%9C%80%E5%A4%9A%E8%A1%8C%E6%95%B0.md,54.7%,中等,103 -2398,2300-2399,2398. 预算内的最多机器人数目,预算内的最多机器人数目,https://leetcode.cn/problems/maximum-number-of-robots-within-budget/,maximum-number-of-robots-within-budget,队列、数组、二分查找、前缀和、滑动窗口、堆(优先队列),https://algo.itcharge.cn/Solutions/2300-2399/maximum-number-of-robots-within-budget/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2398.%20%E9%A2%84%E7%AE%97%E5%86%85%E7%9A%84%E6%9C%80%E5%A4%9A%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%95%B0%E7%9B%AE.md,34.4%,困难,85 -2399,2300-2399,2399. 检查相同字母间的距离,检查相同字母间的距离,https://leetcode.cn/problems/check-distances-between-same-letters/,check-distances-between-same-letters,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2300-2399/check-distances-between-same-letters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2399.%20%E6%A3%80%E6%9F%A5%E7%9B%B8%E5%90%8C%E5%AD%97%E6%AF%8D%E9%97%B4%E7%9A%84%E8%B7%9D%E7%A6%BB.md,75.3%,简单,304 -2400,2400-2499,2400. 恰好移动 k 步到达某一位置的方法数目,恰好移动 k 步到达某一位置的方法数目,https://leetcode.cn/problems/number-of-ways-to-reach-a-position-after-exactly-k-steps/,number-of-ways-to-reach-a-position-after-exactly-k-steps,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/2400-2499/number-of-ways-to-reach-a-position-after-exactly-k-steps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2400.%20%E6%81%B0%E5%A5%BD%E7%A7%BB%E5%8A%A8%20k%20%E6%AD%A5%E5%88%B0%E8%BE%BE%E6%9F%90%E4%B8%80%E4%BD%8D%E7%BD%AE%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0%E7%9B%AE.md,32.7%,中等,156 -2401,2400-2499,2401. 最长优雅子数组,最长优雅子数组,https://leetcode.cn/problems/longest-nice-subarray/,longest-nice-subarray,位运算、数组、滑动窗口,https://algo.itcharge.cn/Solutions/2400-2499/longest-nice-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2401.%20%E6%9C%80%E9%95%BF%E4%BC%98%E9%9B%85%E5%AD%90%E6%95%B0%E7%BB%84.md,49.6%,中等,159 -2402,2400-2499,2402. 会议室 III,会议室 III,https://leetcode.cn/problems/meeting-rooms-iii/,meeting-rooms-iii,数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/meeting-rooms-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2402.%20%E4%BC%9A%E8%AE%AE%E5%AE%A4%20III.md,32.6%,困难,119 -2403,2400-2499,2403. 杀死所有怪物的最短时间,杀死所有怪物的最短时间,https://leetcode.cn/problems/minimum-time-to-kill-all-monsters/,minimum-time-to-kill-all-monsters,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/2400-2499/minimum-time-to-kill-all-monsters/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2403.%20%E6%9D%80%E6%AD%BB%E6%89%80%E6%9C%89%E6%80%AA%E7%89%A9%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,70.2%,困难,17 -2404,2400-2499,2404. 出现最频繁的偶数元素,出现最频繁的偶数元素,https://leetcode.cn/problems/most-frequent-even-element/,most-frequent-even-element,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2400-2499/most-frequent-even-element/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2404.%20%E5%87%BA%E7%8E%B0%E6%9C%80%E9%A2%91%E7%B9%81%E7%9A%84%E5%81%B6%E6%95%B0%E5%85%83%E7%B4%A0.md,58.3%,简单,345 -2405,2400-2499,2405. 子字符串的最优划分,子字符串的最优划分,https://leetcode.cn/problems/optimal-partition-of-string/,optimal-partition-of-string,贪心、哈希表、字符串,https://algo.itcharge.cn/Solutions/2400-2499/optimal-partition-of-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2405.%20%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E4%BC%98%E5%88%92%E5%88%86.md,74.9%,中等,127 -2406,2400-2499,2406. 将区间分为最少组数,将区间分为最少组数,https://leetcode.cn/problems/divide-intervals-into-minimum-number-of-groups/,divide-intervals-into-minimum-number-of-groups,贪心、数组、双指针、前缀和、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/divide-intervals-into-minimum-number-of-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2406.%20%E5%B0%86%E5%8C%BA%E9%97%B4%E5%88%86%E4%B8%BA%E6%9C%80%E5%B0%91%E7%BB%84%E6%95%B0.md,44.6%,中等,128 -2407,2400-2499,2407. 最长递增子序列 II,最长递增子序列 II,https://leetcode.cn/problems/longest-increasing-subsequence-ii/,longest-increasing-subsequence-ii,树状数组、线段树、队列、数组、分治、动态规划、单调队列,https://algo.itcharge.cn/Solutions/2400-2499/longest-increasing-subsequence-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2407.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%20II.md,30.5%,困难,93 -2408,2400-2499,2408. 设计 SQL,设计 SQL,https://leetcode.cn/problems/design-sql/,design-sql,设计、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2400-2499/design-sql/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2408.%20%E8%AE%BE%E8%AE%A1%20SQL.md,63.9%,中等,7 -2409,2400-2499,2409. 统计共同度过的日子数,统计共同度过的日子数,https://leetcode.cn/problems/count-days-spent-together/,count-days-spent-together,数学、字符串,https://algo.itcharge.cn/Solutions/2400-2499/count-days-spent-together/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2409.%20%E7%BB%9F%E8%AE%A1%E5%85%B1%E5%90%8C%E5%BA%A6%E8%BF%87%E7%9A%84%E6%97%A5%E5%AD%90%E6%95%B0.md,55.9%,简单,316 -2410,2400-2499,2410. 运动员和训练师的最大匹配数,运动员和训练师的最大匹配数,https://leetcode.cn/problems/maximum-matching-of-players-with-trainers/,maximum-matching-of-players-with-trainers,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/2400-2499/maximum-matching-of-players-with-trainers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2410.%20%E8%BF%90%E5%8A%A8%E5%91%98%E5%92%8C%E8%AE%AD%E7%BB%83%E5%B8%88%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8C%B9%E9%85%8D%E6%95%B0.md,64.7%,中等,81 -2411,2400-2499,2411. 按位或最大的最小子数组长度,按位或最大的最小子数组长度,https://leetcode.cn/problems/smallest-subarrays-with-maximum-bitwise-or/,smallest-subarrays-with-maximum-bitwise-or,位运算、数组、二分查找、滑动窗口,https://algo.itcharge.cn/Solutions/2400-2499/smallest-subarrays-with-maximum-bitwise-or/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2411.%20%E6%8C%89%E4%BD%8D%E6%88%96%E6%9C%80%E5%A4%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6.md,43.2%,中等,63 -2412,2400-2499,2412. 完成所有交易的初始最少钱数,完成所有交易的初始最少钱数,https://leetcode.cn/problems/minimum-money-required-before-transactions/,minimum-money-required-before-transactions,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2400-2499/minimum-money-required-before-transactions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2412.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E4%BA%A4%E6%98%93%E7%9A%84%E5%88%9D%E5%A7%8B%E6%9C%80%E5%B0%91%E9%92%B1%E6%95%B0.md,48.2%,困难,62 -2413,2400-2499,2413. 最小偶倍数,最小偶倍数,https://leetcode.cn/problems/smallest-even-multiple/,smallest-even-multiple,数学、数论,https://algo.itcharge.cn/Solutions/2400-2499/smallest-even-multiple/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2413.%20%E6%9C%80%E5%B0%8F%E5%81%B6%E5%80%8D%E6%95%B0.md,87.6%,简单,291 -2414,2400-2499,2414. 最长的字母序连续子字符串的长度,最长的字母序连续子字符串的长度,https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring/,length-of-the-longest-alphabetical-continuous-substring,字符串,https://algo.itcharge.cn/Solutions/2400-2499/length-of-the-longest-alphabetical-continuous-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2414.%20%E6%9C%80%E9%95%BF%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BA%8F%E8%BF%9E%E7%BB%AD%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E9%95%BF%E5%BA%A6.md,60.4%,中等,122 -2415,2400-2499,2415. 反转二叉树的奇数层,反转二叉树的奇数层,https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/,reverse-odd-levels-of-binary-tree,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2400-2499/reverse-odd-levels-of-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2415.%20%E5%8F%8D%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%A5%87%E6%95%B0%E5%B1%82.md,70.0%,中等,139 -2416,2400-2499,2416. 字符串的前缀分数和,字符串的前缀分数和,https://leetcode.cn/problems/sum-of-prefix-scores-of-strings/,sum-of-prefix-scores-of-strings,字典树、数组、字符串、计数,https://algo.itcharge.cn/Solutions/2400-2499/sum-of-prefix-scores-of-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2416.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%89%8D%E7%BC%80%E5%88%86%E6%95%B0%E5%92%8C.md,40.5%,困难,124 -2417,2400-2499,2417. 最近的公平整数,最近的公平整数,https://leetcode.cn/problems/closest-fair-integer/,closest-fair-integer,数学、枚举,https://algo.itcharge.cn/Solutions/2400-2499/closest-fair-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2417.%20%E6%9C%80%E8%BF%91%E7%9A%84%E5%85%AC%E5%B9%B3%E6%95%B4%E6%95%B0.md,45.2%,中等,9 -2418,2400-2499,2418. 按身高排序,按身高排序,https://leetcode.cn/problems/sort-the-people/,sort-the-people,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/2400-2499/sort-the-people/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2418.%20%E6%8C%89%E8%BA%AB%E9%AB%98%E6%8E%92%E5%BA%8F.md,79.4%,简单,373 -2419,2400-2499,2419. 按位与最大的最长子数组,按位与最大的最长子数组,https://leetcode.cn/problems/longest-subarray-with-maximum-bitwise-and/,longest-subarray-with-maximum-bitwise-and,位运算、脑筋急转弯、数组,https://algo.itcharge.cn/Solutions/2400-2499/longest-subarray-with-maximum-bitwise-and/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2419.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E6%9C%80%E5%A4%A7%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md,42.5%,中等,107 -2420,2400-2499,2420. 找到所有好下标,找到所有好下标,https://leetcode.cn/problems/find-all-good-indices/,find-all-good-indices,数组、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/find-all-good-indices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2420.%20%E6%89%BE%E5%88%B0%E6%89%80%E6%9C%89%E5%A5%BD%E4%B8%8B%E6%A0%87.md,31.4%,中等,140 -2421,2400-2499,2421. 好路径的数目,好路径的数目,https://leetcode.cn/problems/number-of-good-paths/,number-of-good-paths,树、并查集、图、数组,https://algo.itcharge.cn/Solutions/2400-2499/number-of-good-paths/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2421.%20%E5%A5%BD%E8%B7%AF%E5%BE%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,42.7%,困难,52 -2422,2400-2499,2422. 使用合并操作将数组转换为回文序列,使用合并操作将数组转换为回文序列,https://leetcode.cn/problems/merge-operations-to-turn-array-into-a-palindrome/,merge-operations-to-turn-array-into-a-palindrome,贪心、数组、双指针,https://algo.itcharge.cn/Solutions/2400-2499/merge-operations-to-turn-array-into-a-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2422.%20%E4%BD%BF%E7%94%A8%E5%90%88%E5%B9%B6%E6%93%8D%E4%BD%9C%E5%B0%86%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%9B%9E%E6%96%87%E5%BA%8F%E5%88%97.md,71.5%,中等,11 -2423,2400-2499,2423. 删除字符使频率相同,删除字符使频率相同,https://leetcode.cn/problems/remove-letter-to-equalize-frequency/,remove-letter-to-equalize-frequency,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2400-2499/remove-letter-to-equalize-frequency/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2423.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%BD%BF%E9%A2%91%E7%8E%87%E7%9B%B8%E5%90%8C.md,25.2%,简单,270 -2424,2400-2499,2424. 最长上传前缀,最长上传前缀,https://leetcode.cn/problems/longest-uploaded-prefix/,longest-uploaded-prefix,并查集、设计、树状数组、线段树、二分查找、有序集合、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/longest-uploaded-prefix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2424.%20%E6%9C%80%E9%95%BF%E4%B8%8A%E4%BC%A0%E5%89%8D%E7%BC%80.md,56.1%,中等,84 -2425,2400-2499,2425. 所有数对的异或和,所有数对的异或和,https://leetcode.cn/problems/bitwise-xor-of-all-pairings/,bitwise-xor-of-all-pairings,位运算、脑筋急转弯、数组,https://algo.itcharge.cn/Solutions/2400-2499/bitwise-xor-of-all-pairings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2425.%20%E6%89%80%E6%9C%89%E6%95%B0%E5%AF%B9%E7%9A%84%E5%BC%82%E6%88%96%E5%92%8C.md,64.8%,中等,71 -2426,2400-2499,2426. 满足不等式的数对数目,满足不等式的数对数目,https://leetcode.cn/problems/number-of-pairs-satisfying-inequality/,number-of-pairs-satisfying-inequality,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/2400-2499/number-of-pairs-satisfying-inequality/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2426.%20%E6%BB%A1%E8%B6%B3%E4%B8%8D%E7%AD%89%E5%BC%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E6%95%B0%E7%9B%AE.md,46.0%,困难,82 -2427,2400-2499,2427. 公因子的数目,公因子的数目,https://leetcode.cn/problems/number-of-common-factors/,number-of-common-factors,数学、枚举、数论,https://algo.itcharge.cn/Solutions/2400-2499/number-of-common-factors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2427.%20%E5%85%AC%E5%9B%A0%E5%AD%90%E7%9A%84%E6%95%B0%E7%9B%AE.md,81.7%,简单,237 -2428,2400-2499,2428. 沙漏的最大总和,沙漏的最大总和,https://leetcode.cn/problems/maximum-sum-of-an-hourglass/,maximum-sum-of-an-hourglass,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/maximum-sum-of-an-hourglass/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2428.%20%E6%B2%99%E6%BC%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E6%80%BB%E5%92%8C.md,74.7%,中等,96 -2429,2400-2499,2429. 最小 XOR,最小 XOR,https://leetcode.cn/problems/minimize-xor/,minimize-xor,贪心、位运算,https://algo.itcharge.cn/Solutions/2400-2499/minimize-xor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2429.%20%E6%9C%80%E5%B0%8F%20XOR.md,44.5%,中等,110 -2430,2400-2499,2430. 对字母串可执行的最大删除数,对字母串可执行的最大删除数,https://leetcode.cn/problems/maximum-deletions-on-a-string/,maximum-deletions-on-a-string,字符串、动态规划、字符串匹配、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/2400-2499/maximum-deletions-on-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2430.%20%E5%AF%B9%E5%AD%97%E6%AF%8D%E4%B8%B2%E5%8F%AF%E6%89%A7%E8%A1%8C%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A0%E9%99%A4%E6%95%B0.md,47.7%,困难,105 -2431,2400-2499,2431. 最大限度地提高购买水果的口味,最大限度地提高购买水果的口味,https://leetcode.cn/problems/maximize-total-tastiness-of-purchased-fruits/,maximize-total-tastiness-of-purchased-fruits,数组、动态规划,https://algo.itcharge.cn/Solutions/2400-2499/maximize-total-tastiness-of-purchased-fruits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2431.%20%E6%9C%80%E5%A4%A7%E9%99%90%E5%BA%A6%E5%9C%B0%E6%8F%90%E9%AB%98%E8%B4%AD%E4%B9%B0%E6%B0%B4%E6%9E%9C%E7%9A%84%E5%8F%A3%E5%91%B3.md,58.8%,中等,12 -2432,2400-2499,2432. 处理用时最长的那个任务的员工,处理用时最长的那个任务的员工,https://leetcode.cn/problems/the-employee-that-worked-on-the-longest-task/,the-employee-that-worked-on-the-longest-task,数组,https://algo.itcharge.cn/Solutions/2400-2499/the-employee-that-worked-on-the-longest-task/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2432.%20%E5%A4%84%E7%90%86%E7%94%A8%E6%97%B6%E6%9C%80%E9%95%BF%E7%9A%84%E9%82%A3%E4%B8%AA%E4%BB%BB%E5%8A%A1%E7%9A%84%E5%91%98%E5%B7%A5.md,55.3%,简单,227 -2433,2400-2499,2433. 找出前缀异或的原始数组,找出前缀异或的原始数组,https://leetcode.cn/problems/find-the-original-array-of-prefix-xor/,find-the-original-array-of-prefix-xor,位运算、数组,https://algo.itcharge.cn/Solutions/2400-2499/find-the-original-array-of-prefix-xor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2433.%20%E6%89%BE%E5%87%BA%E5%89%8D%E7%BC%80%E5%BC%82%E6%88%96%E7%9A%84%E5%8E%9F%E5%A7%8B%E6%95%B0%E7%BB%84.md,84.8%,中等,86 -2434,2400-2499,2434. 使用机器人打印字典序最小的字符串,使用机器人打印字典序最小的字符串,https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string/,using-a-robot-to-print-the-lexicographically-smallest-string,栈、贪心、哈希表、字符串,https://algo.itcharge.cn/Solutions/2400-2499/using-a-robot-to-print-the-lexicographically-smallest-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2434.%20%E4%BD%BF%E7%94%A8%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%89%93%E5%8D%B0%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,42.1%,中等,114 -2435,2400-2499,2435. 矩阵中和能被 K 整除的路径,矩阵中和能被 K 整除的路径,https://leetcode.cn/problems/paths-in-matrix-whose-sum-is-divisible-by-k/,paths-in-matrix-whose-sum-is-divisible-by-k,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2400-2499/paths-in-matrix-whose-sum-is-divisible-by-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2435.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%92%8C%E8%83%BD%E8%A2%AB%20K%20%E6%95%B4%E9%99%A4%E7%9A%84%E8%B7%AF%E5%BE%84.md,51.5%,困难,97 -2436,2400-2499,2436. 使子数组最大公约数大于一的最小分割数,使子数组最大公约数大于一的最小分割数,https://leetcode.cn/problems/minimum-split-into-subarrays-with-gcd-greater-than-one/,minimum-split-into-subarrays-with-gcd-greater-than-one,贪心、数组、数学、动态规划、数论,https://algo.itcharge.cn/Solutions/2400-2499/minimum-split-into-subarrays-with-gcd-greater-than-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2436.%20%E4%BD%BF%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0%E5%A4%A7%E4%BA%8E%E4%B8%80%E7%9A%84%E6%9C%80%E5%B0%8F%E5%88%86%E5%89%B2%E6%95%B0.md,76.8%,中等,11 -2437,2400-2499,2437. 有效时间的数目,有效时间的数目,https://leetcode.cn/problems/number-of-valid-clock-times/,number-of-valid-clock-times,字符串、枚举,https://algo.itcharge.cn/Solutions/2400-2499/number-of-valid-clock-times/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2437.%20%E6%9C%89%E6%95%88%E6%97%B6%E9%97%B4%E7%9A%84%E6%95%B0%E7%9B%AE.md,50.5%,简单,285 -2438,2400-2499,2438. 二的幂数组中查询范围内的乘积,二的幂数组中查询范围内的乘积,https://leetcode.cn/problems/range-product-queries-of-powers/,range-product-queries-of-powers,位运算、数组、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/range-product-queries-of-powers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2438.%20%E4%BA%8C%E7%9A%84%E5%B9%82%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E8%AF%A2%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E4%B9%98%E7%A7%AF.md,41.2%,中等,83 -2439,2400-2499,2439. 最小化数组中的最大值,最小化数组中的最大值,https://leetcode.cn/problems/minimize-maximum-of-array/,minimize-maximum-of-array,贪心、数组、二分查找、动态规划、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/minimize-maximum-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2439.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,39.1%,中等,120 -2440,2400-2499,2440. 创建价值相同的连通块,创建价值相同的连通块,https://leetcode.cn/problems/create-components-with-same-value/,create-components-with-same-value,树、深度优先搜索、数组、数学、枚举,https://algo.itcharge.cn/Solutions/2400-2499/create-components-with-same-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2440.%20%E5%88%9B%E5%BB%BA%E4%BB%B7%E5%80%BC%E7%9B%B8%E5%90%8C%E7%9A%84%E8%BF%9E%E9%80%9A%E5%9D%97.md,61.4%,困难,32 -2441,2400-2499,2441. 与对应负数同时存在的最大正整数,与对应负数同时存在的最大正整数,https://leetcode.cn/problems/largest-positive-integer-that-exists-with-its-negative/,largest-positive-integer-that-exists-with-its-negative,数组、哈希表、双指针、排序,https://algo.itcharge.cn/Solutions/2400-2499/largest-positive-integer-that-exists-with-its-negative/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2441.%20%E4%B8%8E%E5%AF%B9%E5%BA%94%E8%B4%9F%E6%95%B0%E5%90%8C%E6%97%B6%E5%AD%98%E5%9C%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%95%B4%E6%95%B0.md,72.5%,简单,272 -2442,2400-2499,2442. 反转之后不同整数的数目,反转之后不同整数的数目,https://leetcode.cn/problems/count-number-of-distinct-integers-after-reverse-operations/,count-number-of-distinct-integers-after-reverse-operations,数组、哈希表、数学,https://algo.itcharge.cn/Solutions/2400-2499/count-number-of-distinct-integers-after-reverse-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2442.%20%E5%8F%8D%E8%BD%AC%E4%B9%8B%E5%90%8E%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E6%95%B0%E7%9B%AE.md,75.1%,中等,93 -2443,2400-2499,2443. 反转之后的数字和,反转之后的数字和,https://leetcode.cn/problems/sum-of-number-and-its-reverse/,sum-of-number-and-its-reverse,数学、枚举,https://algo.itcharge.cn/Solutions/2400-2499/sum-of-number-and-its-reverse/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2443.%20%E5%8F%8D%E8%BD%AC%E4%B9%8B%E5%90%8E%E7%9A%84%E6%95%B0%E5%AD%97%E5%92%8C.md,46.4%,中等,78 -2444,2400-2499,2444. 统计定界子数组的数目,统计定界子数组的数目,https://leetcode.cn/problems/count-subarrays-with-fixed-bounds/,count-subarrays-with-fixed-bounds,队列、数组、滑动窗口、单调队列,https://algo.itcharge.cn/Solutions/2400-2499/count-subarrays-with-fixed-bounds/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2444.%20%E7%BB%9F%E8%AE%A1%E5%AE%9A%E7%95%8C%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,44.4%,困难,99 -2445,2400-2499,2445. 值为 1 的节点数,值为 1 的节点数,https://leetcode.cn/problems/number-of-nodes-with-value-one/,number-of-nodes-with-value-one,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2400-2499/number-of-nodes-with-value-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2445.%20%E5%80%BC%E4%B8%BA%201%20%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0.md,78.2%,中等,15 -2446,2400-2499,2446. 判断两个事件是否存在冲突,判断两个事件是否存在冲突,https://leetcode.cn/problems/determine-if-two-events-have-conflict/,determine-if-two-events-have-conflict,数组、字符串,https://algo.itcharge.cn/Solutions/2400-2499/determine-if-two-events-have-conflict/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2446.%20%E5%88%A4%E6%96%AD%E4%B8%A4%E4%B8%AA%E4%BA%8B%E4%BB%B6%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E5%86%B2%E7%AA%81.md,63.5%,简单,213 -2447,2400-2499,2447. 最大公因数等于 K 的子数组数目,最大公因数等于 K 的子数组数目,https://leetcode.cn/problems/number-of-subarrays-with-gcd-equal-to-k/,number-of-subarrays-with-gcd-equal-to-k,数组、数学、数论,https://algo.itcharge.cn/Solutions/2400-2499/number-of-subarrays-with-gcd-equal-to-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2447.%20%E6%9C%80%E5%A4%A7%E5%85%AC%E5%9B%A0%E6%95%B0%E7%AD%89%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,41.2%,中等,65 -2448,2400-2499,2448. 使数组相等的最小开销,使数组相等的最小开销,https://leetcode.cn/problems/minimum-cost-to-make-array-equal/,minimum-cost-to-make-array-equal,贪心、数组、二分查找、前缀和、排序,https://algo.itcharge.cn/Solutions/2400-2499/minimum-cost-to-make-array-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2448.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E5%BC%80%E9%94%80.md,36.1%,困难,80 -2449,2400-2499,2449. 使数组相似的最少操作次数,使数组相似的最少操作次数,https://leetcode.cn/problems/minimum-number-of-operations-to-make-arrays-similar/,minimum-number-of-operations-to-make-arrays-similar,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2400-2499/minimum-number-of-operations-to-make-arrays-similar/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2449.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E7%9B%B8%E4%BC%BC%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,65.5%,困难,48 -2450,2400-2499,2450. 应用操作后不同二进制字符串的数量,应用操作后不同二进制字符串的数量,https://leetcode.cn/problems/number-of-distinct-binary-strings-after-applying-operations/,number-of-distinct-binary-strings-after-applying-operations,数学、字符串,https://algo.itcharge.cn/Solutions/2400-2499/number-of-distinct-binary-strings-after-applying-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2450.%20%E5%BA%94%E7%94%A8%E6%93%8D%E4%BD%9C%E5%90%8E%E4%B8%8D%E5%90%8C%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E9%87%8F.md,75.5%,中等,8 -2451,2400-2499,2451. 差值数组不同的字符串,差值数组不同的字符串,https://leetcode.cn/problems/odd-string-difference/,odd-string-difference,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2400-2499/odd-string-difference/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2451.%20%E5%B7%AE%E5%80%BC%E6%95%B0%E7%BB%84%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,66.3%,简单,239 -2452,2400-2499,2452. 距离字典两次编辑以内的单词,距离字典两次编辑以内的单词,https://leetcode.cn/problems/words-within-two-edits-of-dictionary/,words-within-two-edits-of-dictionary,数组、字符串,https://algo.itcharge.cn/Solutions/2400-2499/words-within-two-edits-of-dictionary/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2452.%20%E8%B7%9D%E7%A6%BB%E5%AD%97%E5%85%B8%E4%B8%A4%E6%AC%A1%E7%BC%96%E8%BE%91%E4%BB%A5%E5%86%85%E7%9A%84%E5%8D%95%E8%AF%8D.md,65.1%,中等,43 -2453,2400-2499,2453. 摧毁一系列目标,摧毁一系列目标,https://leetcode.cn/problems/destroy-sequential-targets/,destroy-sequential-targets,数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2400-2499/destroy-sequential-targets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2453.%20%E6%91%A7%E6%AF%81%E4%B8%80%E7%B3%BB%E5%88%97%E7%9B%AE%E6%A0%87.md,35.5%,中等,69 -2454,2400-2499,2454. 下一个更大元素 IV,下一个更大元素 IV,https://leetcode.cn/problems/next-greater-element-iv/,next-greater-element-iv,栈、数组、二分查找、排序、单调栈、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/next-greater-element-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2454.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20IV.md,49.0%,困难,82 -2455,2400-2499,2455. 可被三整除的偶数的平均值,可被三整除的偶数的平均值,https://leetcode.cn/problems/average-value-of-even-numbers-that-are-divisible-by-three/,average-value-of-even-numbers-that-are-divisible-by-three,数组、数学,https://algo.itcharge.cn/Solutions/2400-2499/average-value-of-even-numbers-that-are-divisible-by-three/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2455.%20%E5%8F%AF%E8%A2%AB%E4%B8%89%E6%95%B4%E9%99%A4%E7%9A%84%E5%81%B6%E6%95%B0%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC.md,63.6%,简单,177 -2456,2400-2499,2456. 最流行的视频创作者,最流行的视频创作者,https://leetcode.cn/problems/most-popular-video-creator/,most-popular-video-creator,数组、哈希表、字符串、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/most-popular-video-creator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2456.%20%E6%9C%80%E6%B5%81%E8%A1%8C%E7%9A%84%E8%A7%86%E9%A2%91%E5%88%9B%E4%BD%9C%E8%80%85.md,38.3%,中等,119 -2457,2400-2499,2457. 美丽整数的最小增量,美丽整数的最小增量,https://leetcode.cn/problems/minimum-addition-to-make-integer-beautiful/,minimum-addition-to-make-integer-beautiful,贪心、数学,https://algo.itcharge.cn/Solutions/2400-2499/minimum-addition-to-make-integer-beautiful/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2457.%20%E7%BE%8E%E4%B8%BD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%A2%9E%E9%87%8F.md,39.5%,中等,121 -2458,2400-2499,2458. 移除子树后的二叉树高度,移除子树后的二叉树高度,https://leetcode.cn/problems/height-of-binary-tree-after-subtree-removal-queries/,height-of-binary-tree-after-subtree-removal-queries,树、深度优先搜索、广度优先搜索、数组、二叉树,https://algo.itcharge.cn/Solutions/2400-2499/height-of-binary-tree-after-subtree-removal-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2458.%20%E7%A7%BB%E9%99%A4%E5%AD%90%E6%A0%91%E5%90%8E%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91%E9%AB%98%E5%BA%A6.md,41.1%,困难,81 -2459,2400-2499,2459. 通过移动项目到空白区域来排序数组,通过移动项目到空白区域来排序数组,https://leetcode.cn/problems/sort-array-by-moving-items-to-empty-space/,sort-array-by-moving-items-to-empty-space,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2400-2499/sort-array-by-moving-items-to-empty-space/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2459.%20%E9%80%9A%E8%BF%87%E7%A7%BB%E5%8A%A8%E9%A1%B9%E7%9B%AE%E5%88%B0%E7%A9%BA%E7%99%BD%E5%8C%BA%E5%9F%9F%E6%9D%A5%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md,60.1%,困难,3 -2460,2400-2499,2460. 对数组执行操作,对数组执行操作,https://leetcode.cn/problems/apply-operations-to-an-array/,apply-operations-to-an-array,数组、模拟,https://algo.itcharge.cn/Solutions/2400-2499/apply-operations-to-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2460.%20%E5%AF%B9%E6%95%B0%E7%BB%84%E6%89%A7%E8%A1%8C%E6%93%8D%E4%BD%9C.md,68.2%,简单,242 -2461,2400-2499,2461. 长度为 K 子数组中的最大和,长度为 K 子数组中的最大和,https://leetcode.cn/problems/maximum-sum-of-distinct-subarrays-with-length-k/,maximum-sum-of-distinct-subarrays-with-length-k,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/2400-2499/maximum-sum-of-distinct-subarrays-with-length-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2461.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,30.6%,中等,142 -2462,2400-2499,2462. 雇佣 K 位工人的总代价,雇佣 K 位工人的总代价,https://leetcode.cn/problems/total-cost-to-hire-k-workers/,total-cost-to-hire-k-workers,数组、双指针、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/total-cost-to-hire-k-workers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2462.%20%E9%9B%87%E4%BD%A3%20K%20%E4%BD%8D%E5%B7%A5%E4%BA%BA%E7%9A%84%E6%80%BB%E4%BB%A3%E4%BB%B7.md,37.6%,中等,120 -2463,2400-2499,2463. 最小移动总距离,最小移动总距离,https://leetcode.cn/problems/minimum-total-distance-traveled/,minimum-total-distance-traveled,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/2400-2499/minimum-total-distance-traveled/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2463.%20%E6%9C%80%E5%B0%8F%E7%A7%BB%E5%8A%A8%E6%80%BB%E8%B7%9D%E7%A6%BB.md,46.3%,困难,59 -2464,2400-2499,2464. 有效分割中的最少子数组数目,有效分割中的最少子数组数目,https://leetcode.cn/problems/minimum-subarrays-in-a-valid-split/,minimum-subarrays-in-a-valid-split,数组、数学、动态规划、数论,https://algo.itcharge.cn/Solutions/2400-2499/minimum-subarrays-in-a-valid-split/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2464.%20%E6%9C%89%E6%95%88%E5%88%86%E5%89%B2%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%91%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,63.7%,中等,7 -2465,2400-2499,2465. 不同的平均值数目,不同的平均值数目,https://leetcode.cn/problems/number-of-distinct-averages/,number-of-distinct-averages,数组、哈希表、双指针、排序,https://algo.itcharge.cn/Solutions/2400-2499/number-of-distinct-averages/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2465.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC%E6%95%B0%E7%9B%AE.md,72.5%,简单,217 -2466,2400-2499,2466. 统计构造好字符串的方案数,统计构造好字符串的方案数,https://leetcode.cn/problems/count-ways-to-build-good-strings/,count-ways-to-build-good-strings,动态规划,https://algo.itcharge.cn/Solutions/2400-2499/count-ways-to-build-good-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2466.%20%E7%BB%9F%E8%AE%A1%E6%9E%84%E9%80%A0%E5%A5%BD%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,46.2%,中等,69 -2467,2400-2499,2467. 树上最大得分和路径,树上最大得分和路径,https://leetcode.cn/problems/most-profitable-path-in-a-tree/,most-profitable-path-in-a-tree,树、深度优先搜索、广度优先搜索、图、数组,https://algo.itcharge.cn/Solutions/2400-2499/most-profitable-path-in-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2467.%20%E6%A0%91%E4%B8%8A%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86%E5%92%8C%E8%B7%AF%E5%BE%84.md,50.3%,中等,46 -2468,2400-2499,2468. 根据限制分割消息,根据限制分割消息,https://leetcode.cn/problems/split-message-based-on-limit/,split-message-based-on-limit,字符串、二分查找,https://algo.itcharge.cn/Solutions/2400-2499/split-message-based-on-limit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2468.%20%E6%A0%B9%E6%8D%AE%E9%99%90%E5%88%B6%E5%88%86%E5%89%B2%E6%B6%88%E6%81%AF.md,44.4%,困难,53 -2469,2400-2499,2469. 温度转换,温度转换,https://leetcode.cn/problems/convert-the-temperature/,convert-the-temperature,数学,https://algo.itcharge.cn/Solutions/2400-2499/convert-the-temperature/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2469.%20%E6%B8%A9%E5%BA%A6%E8%BD%AC%E6%8D%A2.md,87.1%,简单,178 -2470,2400-2499,2470. 最小公倍数为 K 的子数组数目,最小公倍数为 K 的子数组数目,https://leetcode.cn/problems/number-of-subarrays-with-lcm-equal-to-k/,number-of-subarrays-with-lcm-equal-to-k,数组、数学、数论,https://algo.itcharge.cn/Solutions/2400-2499/number-of-subarrays-with-lcm-equal-to-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2470.%20%E6%9C%80%E5%B0%8F%E5%85%AC%E5%80%8D%E6%95%B0%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,40.7%,中等,88 -2471,2400-2499,2471. 逐层排序二叉树所需的最少操作数目,逐层排序二叉树所需的最少操作数目,https://leetcode.cn/problems/minimum-number-of-operations-to-sort-a-binary-tree-by-level/,minimum-number-of-operations-to-sort-a-binary-tree-by-level,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/2400-2499/minimum-number-of-operations-to-sort-a-binary-tree-by-level/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2471.%20%E9%80%90%E5%B1%82%E6%8E%92%E5%BA%8F%E4%BA%8C%E5%8F%89%E6%A0%91%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0%E7%9B%AE.md,55.7%,中等,91 -2472,2400-2499,2472. 不重叠回文子字符串的最大数目,不重叠回文子字符串的最大数目,https://leetcode.cn/problems/maximum-number-of-non-overlapping-palindrome-substrings/,maximum-number-of-non-overlapping-palindrome-substrings,字符串、动态规划,https://algo.itcharge.cn/Solutions/2400-2499/maximum-number-of-non-overlapping-palindrome-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2472.%20%E4%B8%8D%E9%87%8D%E5%8F%A0%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,43.9%,困难,85 -2473,2400-2499,2473. 购买苹果的最低成本,购买苹果的最低成本,https://leetcode.cn/problems/minimum-cost-to-buy-apples/,minimum-cost-to-buy-apples,图、数组、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/minimum-cost-to-buy-apples/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2473.%20%E8%B4%AD%E4%B9%B0%E8%8B%B9%E6%9E%9C%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md,59.8%,中等,7 -2474,2400-2499,2474. 购买量严格增加的客户,购买量严格增加的客户,https://leetcode.cn/problems/customers-with-strictly-increasing-purchases/,customers-with-strictly-increasing-purchases,数据库,https://algo.itcharge.cn/Solutions/2400-2499/customers-with-strictly-increasing-purchases/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2474.%20%E8%B4%AD%E4%B9%B0%E9%87%8F%E4%B8%A5%E6%A0%BC%E5%A2%9E%E5%8A%A0%E7%9A%84%E5%AE%A2%E6%88%B7.md,49.6%,困难,14 -2475,2400-2499,2475. 数组中不等三元组的数目,数组中不等三元组的数目,https://leetcode.cn/problems/number-of-unequal-triplets-in-array/,number-of-unequal-triplets-in-array,数组、哈希表,https://algo.itcharge.cn/Solutions/2400-2499/number-of-unequal-triplets-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2475.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%8D%E7%AD%89%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,77.8%,简单,162 -2476,2400-2499,2476. 二叉搜索树最近节点查询,二叉搜索树最近节点查询,https://leetcode.cn/problems/closest-nodes-queries-in-a-binary-search-tree/,closest-nodes-queries-in-a-binary-search-tree,树、深度优先搜索、数组、二分查找、二叉树,https://algo.itcharge.cn/Solutions/2400-2499/closest-nodes-queries-in-a-binary-search-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2476.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E6%9C%80%E8%BF%91%E8%8A%82%E7%82%B9%E6%9F%A5%E8%AF%A2.md,41.4%,中等,100 -2477,2400-2499,2477. 到达首都的最少油耗,到达首都的最少油耗,https://leetcode.cn/problems/minimum-fuel-cost-to-report-to-the-capital/,minimum-fuel-cost-to-report-to-the-capital,树、深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/2400-2499/minimum-fuel-cost-to-report-to-the-capital/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2477.%20%E5%88%B0%E8%BE%BE%E9%A6%96%E9%83%BD%E7%9A%84%E6%9C%80%E5%B0%91%E6%B2%B9%E8%80%97.md,53.0%,中等,74 -2478,2400-2499,2478. 完美分割的方案数,完美分割的方案数,https://leetcode.cn/problems/number-of-beautiful-partitions/,number-of-beautiful-partitions,字符串、动态规划,https://algo.itcharge.cn/Solutions/2400-2499/number-of-beautiful-partitions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2478.%20%E5%AE%8C%E7%BE%8E%E5%88%86%E5%89%B2%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,40.0%,困难,48 -2479,2400-2499,2479. 两个不重叠子树的最大异或值,两个不重叠子树的最大异或值,https://leetcode.cn/problems/maximum-xor-of-two-non-overlapping-subtrees/,maximum-xor-of-two-non-overlapping-subtrees,树、深度优先搜索、图、字典树,https://algo.itcharge.cn/Solutions/2400-2499/maximum-xor-of-two-non-overlapping-subtrees/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2479.%20%E4%B8%A4%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%8F%A0%E5%AD%90%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md,65.9%,困难,7 -2480,2400-2499,2480. 形成化学键,形成化学键,https://leetcode.cn/problems/form-a-chemical-bond/,form-a-chemical-bond,数据库,https://algo.itcharge.cn/Solutions/2400-2499/form-a-chemical-bond/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2480.%20%E5%BD%A2%E6%88%90%E5%8C%96%E5%AD%A6%E9%94%AE.md,84.2%,简单,5 -2481,2400-2499,2481. 分割圆的最少切割次数,分割圆的最少切割次数,https://leetcode.cn/problems/minimum-cuts-to-divide-a-circle/,minimum-cuts-to-divide-a-circle,几何、数学,https://algo.itcharge.cn/Solutions/2400-2499/minimum-cuts-to-divide-a-circle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2481.%20%E5%88%86%E5%89%B2%E5%9C%86%E7%9A%84%E6%9C%80%E5%B0%91%E5%88%87%E5%89%B2%E6%AC%A1%E6%95%B0.md,58.8%,简单,127 -2482,2400-2499,2482. 行和列中一和零的差值,行和列中一和零的差值,https://leetcode.cn/problems/difference-between-ones-and-zeros-in-row-and-column/,difference-between-ones-and-zeros-in-row-and-column,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/2400-2499/difference-between-ones-and-zeros-in-row-and-column/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2482.%20%E8%A1%8C%E5%92%8C%E5%88%97%E4%B8%AD%E4%B8%80%E5%92%8C%E9%9B%B6%E7%9A%84%E5%B7%AE%E5%80%BC.md,82.5%,中等,60 -2483,2400-2499,2483. 商店的最少代价,商店的最少代价,https://leetcode.cn/problems/minimum-penalty-for-a-shop/,minimum-penalty-for-a-shop,字符串、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/minimum-penalty-for-a-shop/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2483.%20%E5%95%86%E5%BA%97%E7%9A%84%E6%9C%80%E5%B0%91%E4%BB%A3%E4%BB%B7.md,63.3%,中等,78 -2484,2400-2499,2484. 统计回文子序列数目,统计回文子序列数目,https://leetcode.cn/problems/count-palindromic-subsequences/,count-palindromic-subsequences,字符串、动态规划,https://algo.itcharge.cn/Solutions/2400-2499/count-palindromic-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2484.%20%E7%BB%9F%E8%AE%A1%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97%E6%95%B0%E7%9B%AE.md,47.1%,困难,48 -2485,2400-2499,2485. 找出中枢整数,找出中枢整数,https://leetcode.cn/problems/find-the-pivot-integer/,find-the-pivot-integer,数学、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/find-the-pivot-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2485.%20%E6%89%BE%E5%87%BA%E4%B8%AD%E6%9E%A2%E6%95%B4%E6%95%B0.md,81.0%,简单,285 -2486,2400-2499,2486. 追加字符以获得子序列,追加字符以获得子序列,https://leetcode.cn/problems/append-characters-to-string-to-make-subsequence/,append-characters-to-string-to-make-subsequence,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/2400-2499/append-characters-to-string-to-make-subsequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2486.%20%E8%BF%BD%E5%8A%A0%E5%AD%97%E7%AC%A6%E4%BB%A5%E8%8E%B7%E5%BE%97%E5%AD%90%E5%BA%8F%E5%88%97.md,65.2%,中等,85 -2487,2400-2499,2487. 从链表中移除节点,从链表中移除节点,https://leetcode.cn/problems/remove-nodes-from-linked-list/,remove-nodes-from-linked-list,栈、递归、链表、单调栈,https://algo.itcharge.cn/Solutions/2400-2499/remove-nodes-from-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2487.%20%E4%BB%8E%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%A7%BB%E9%99%A4%E8%8A%82%E7%82%B9.md,68.8%,中等,122 -2488,2400-2499,2488. 统计中位数为 K 的子数组,统计中位数为 K 的子数组,https://leetcode.cn/problems/count-subarrays-with-median-k/,count-subarrays-with-median-k,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/count-subarrays-with-median-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2488.%20%E7%BB%9F%E8%AE%A1%E4%B8%AD%E4%BD%8D%E6%95%B0%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,50.8%,困难,201 -2489,2400-2499,2489. 固定比率的子字符串数,固定比率的子字符串数,https://leetcode.cn/problems/number-of-substrings-with-fixed-ratio/,number-of-substrings-with-fixed-ratio,哈希表、数学、字符串、前缀和,https://algo.itcharge.cn/Solutions/2400-2499/number-of-substrings-with-fixed-ratio/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2489.%20%E5%9B%BA%E5%AE%9A%E6%AF%94%E7%8E%87%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0.md,68.8%,中等,3 -2490,2400-2499,2490. 回环句,回环句,https://leetcode.cn/problems/circular-sentence/,circular-sentence,字符串,https://algo.itcharge.cn/Solutions/2400-2499/circular-sentence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2490.%20%E5%9B%9E%E7%8E%AF%E5%8F%A5.md,69.7%,简单,91 -2491,2400-2499,2491. 划分技能点相等的团队,划分技能点相等的团队,https://leetcode.cn/problems/divide-players-into-teams-of-equal-skill/,divide-players-into-teams-of-equal-skill,数组、哈希表、双指针、排序,https://algo.itcharge.cn/Solutions/2400-2499/divide-players-into-teams-of-equal-skill/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2491.%20%E5%88%92%E5%88%86%E6%8A%80%E8%83%BD%E7%82%B9%E7%9B%B8%E7%AD%89%E7%9A%84%E5%9B%A2%E9%98%9F.md,55.4%,中等,110 -2492,2400-2499,2492. 两个城市间路径的最小分数,两个城市间路径的最小分数,https://leetcode.cn/problems/minimum-score-of-a-path-between-two-cities/,minimum-score-of-a-path-between-two-cities,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/2400-2499/minimum-score-of-a-path-between-two-cities/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2492.%20%E4%B8%A4%E4%B8%AA%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B7%AF%E5%BE%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%88%86%E6%95%B0.md,41.4%,中等,121 -2493,2400-2499,2493. 将节点分成尽可能多的组,将节点分成尽可能多的组,https://leetcode.cn/problems/divide-nodes-into-the-maximum-number-of-groups/,divide-nodes-into-the-maximum-number-of-groups,广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/2400-2499/divide-nodes-into-the-maximum-number-of-groups/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2493.%20%E5%B0%86%E8%8A%82%E7%82%B9%E5%88%86%E6%88%90%E5%B0%BD%E5%8F%AF%E8%83%BD%E5%A4%9A%E7%9A%84%E7%BB%84.md,43.7%,困难,44 -2494,2400-2499,2494. 合并在同一个大厅重叠的活动,合并在同一个大厅重叠的活动,https://leetcode.cn/problems/merge-overlapping-events-in-the-same-hall/,merge-overlapping-events-in-the-same-hall,数据库,https://algo.itcharge.cn/Solutions/2400-2499/merge-overlapping-events-in-the-same-hall/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2494.%20%E5%90%88%E5%B9%B6%E5%9C%A8%E5%90%8C%E4%B8%80%E4%B8%AA%E5%A4%A7%E5%8E%85%E9%87%8D%E5%8F%A0%E7%9A%84%E6%B4%BB%E5%8A%A8.md,39.2%,困难,7 -2495,2400-2499,2495. 乘积为偶数的子数组数,乘积为偶数的子数组数,https://leetcode.cn/problems/number-of-subarrays-having-even-product/,number-of-subarrays-having-even-product,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/2400-2499/number-of-subarrays-having-even-product/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2495.%20%E4%B9%98%E7%A7%AF%E4%B8%BA%E5%81%B6%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0.md,68.8%,中等,12 -2496,2400-2499,2496. 数组中字符串的最大值,数组中字符串的最大值,https://leetcode.cn/problems/maximum-value-of-a-string-in-an-array/,maximum-value-of-a-string-in-an-array,数组、字符串,https://algo.itcharge.cn/Solutions/2400-2499/maximum-value-of-a-string-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2496.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,79.6%,简单,191 -2497,2400-2499,2497. 图中最大星和,图中最大星和,https://leetcode.cn/problems/maximum-star-sum-of-a-graph/,maximum-star-sum-of-a-graph,贪心、图、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2400-2499/maximum-star-sum-of-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2497.%20%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E6%98%9F%E5%92%8C.md,38.3%,中等,50 -2498,2400-2499,2498. 青蛙过河 II,青蛙过河 II,https://leetcode.cn/problems/frog-jump-ii/,frog-jump-ii,贪心、数组、二分查找,https://algo.itcharge.cn/Solutions/2400-2499/frog-jump-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2498.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3%20II.md,64.4%,中等,49 -2499,2400-2499,2499. 让数组不相等的最小总代价,让数组不相等的最小总代价,https://leetcode.cn/problems/minimum-total-cost-to-make-arrays-unequal/,minimum-total-cost-to-make-arrays-unequal,贪心、数组、哈希表、计数,https://algo.itcharge.cn/Solutions/2400-2499/minimum-total-cost-to-make-arrays-unequal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2499.%20%E8%AE%A9%E6%95%B0%E7%BB%84%E4%B8%8D%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%80%BB%E4%BB%A3%E4%BB%B7.md,42.0%,困难,28 -2500,2500-2599,2500. 删除每行中的最大值,删除每行中的最大值,https://leetcode.cn/problems/delete-greatest-value-in-each-row/,delete-greatest-value-in-each-row,数组、矩阵、排序,https://algo.itcharge.cn/Solutions/2500-2599/delete-greatest-value-in-each-row/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2500.%20%E5%88%A0%E9%99%A4%E6%AF%8F%E8%A1%8C%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,82.8%,简单,114 -2501,2500-2599,2501. 数组中最长的方波,数组中最长的方波,https://leetcode.cn/problems/longest-square-streak-in-an-array/,longest-square-streak-in-an-array,数组、哈希表、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/2500-2599/longest-square-streak-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2501.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%B9%E6%B3%A2.md,41.3%,中等,85 -2502,2500-2599,2502. 设计内存分配器,设计内存分配器,https://leetcode.cn/problems/design-memory-allocator/,design-memory-allocator,设计、数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2500-2599/design-memory-allocator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2502.%20%E8%AE%BE%E8%AE%A1%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%99%A8.md,52.2%,中等,73 -2503,2500-2599,2503. 矩阵查询可获得的最大分数,矩阵查询可获得的最大分数,https://leetcode.cn/problems/maximum-number-of-points-from-grid-queries/,maximum-number-of-points-from-grid-queries,广度优先搜索、并查集、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/maximum-number-of-points-from-grid-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2503.%20%E7%9F%A9%E9%98%B5%E6%9F%A5%E8%AF%A2%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0.md,43.4%,困难,65 -2504,2500-2599,2504. 把名字和职业联系起来,把名字和职业联系起来,https://leetcode.cn/problems/concatenate-the-name-and-the-profession/,concatenate-the-name-and-the-profession,数据库,https://algo.itcharge.cn/Solutions/2500-2599/concatenate-the-name-and-the-profession/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2504.%20%E6%8A%8A%E5%90%8D%E5%AD%97%E5%92%8C%E8%81%8C%E4%B8%9A%E8%81%94%E7%B3%BB%E8%B5%B7%E6%9D%A5.md,76.4%,简单,8 -2505,2500-2599,2505. 所有子序列和的按位或,所有子序列和的按位或,https://leetcode.cn/problems/bitwise-or-of-all-subsequence-sums/,bitwise-or-of-all-subsequence-sums,位运算、脑筋急转弯、数组、数学,https://algo.itcharge.cn/Solutions/2500-2599/bitwise-or-of-all-subsequence-sums/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2505.%20%E6%89%80%E6%9C%89%E5%AD%90%E5%BA%8F%E5%88%97%E5%92%8C%E7%9A%84%E6%8C%89%E4%BD%8D%E6%88%96.md,58.8%,中等,4 -2506,2500-2599,2506. 统计相似字符串对的数目,统计相似字符串对的数目,https://leetcode.cn/problems/count-pairs-of-similar-strings/,count-pairs-of-similar-strings,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2500-2599/count-pairs-of-similar-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2506.%20%E7%BB%9F%E8%AE%A1%E7%9B%B8%E4%BC%BC%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,78.3%,简单,87 -2507,2500-2599,2507. 使用质因数之和替换后可以取到的最小值,使用质因数之和替换后可以取到的最小值,https://leetcode.cn/problems/smallest-value-after-replacing-with-sum-of-prime-factors/,smallest-value-after-replacing-with-sum-of-prime-factors,数学、数论,https://algo.itcharge.cn/Solutions/2500-2599/smallest-value-after-replacing-with-sum-of-prime-factors/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2507.%20%E4%BD%BF%E7%94%A8%E8%B4%A8%E5%9B%A0%E6%95%B0%E4%B9%8B%E5%92%8C%E6%9B%BF%E6%8D%A2%E5%90%8E%E5%8F%AF%E4%BB%A5%E5%8F%96%E5%88%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,49.9%,中等,60 -2508,2500-2599,2508. 添加边使所有节点度数都为偶数,添加边使所有节点度数都为偶数,https://leetcode.cn/problems/add-edges-to-make-degrees-of-all-nodes-even/,add-edges-to-make-degrees-of-all-nodes-even,图、哈希表,https://algo.itcharge.cn/Solutions/2500-2599/add-edges-to-make-degrees-of-all-nodes-even/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2508.%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%E4%BD%BF%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%E5%BA%A6%E6%95%B0%E9%83%BD%E4%B8%BA%E5%81%B6%E6%95%B0.md,29.4%,困难,54 -2509,2500-2599,2509. 查询树中环的长度,查询树中环的长度,https://leetcode.cn/problems/cycle-length-queries-in-a-tree/,cycle-length-queries-in-a-tree,树、二叉树,https://algo.itcharge.cn/Solutions/2500-2599/cycle-length-queries-in-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2509.%20%E6%9F%A5%E8%AF%A2%E6%A0%91%E4%B8%AD%E7%8E%AF%E7%9A%84%E9%95%BF%E5%BA%A6.md,65.2%,困难,60 -2510,2500-2599,2510. 检查是否有路径经过相同数量的 0 和 1,检查是否有路径经过相同数量的 0 和 1,https://leetcode.cn/problems/check-if-there-is-a-path-with-equal-number-of-0s-and-1s/,check-if-there-is-a-path-with-equal-number-of-0s-and-1s,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2500-2599/check-if-there-is-a-path-with-equal-number-of-0s-and-1s/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2510.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%9C%89%E8%B7%AF%E5%BE%84%E7%BB%8F%E8%BF%87%E7%9B%B8%E5%90%8C%E6%95%B0%E9%87%8F%E7%9A%84%200%20%E5%92%8C%201.md,69.1%,中等,7 -2511,2500-2599,2511. 最多可以摧毁的敌人城堡数目,最多可以摧毁的敌人城堡数目,https://leetcode.cn/problems/maximum-enemy-forts-that-can-be-captured/,maximum-enemy-forts-that-can-be-captured,数组、双指针,https://algo.itcharge.cn/Solutions/2500-2599/maximum-enemy-forts-that-can-be-captured/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2511.%20%E6%9C%80%E5%A4%9A%E5%8F%AF%E4%BB%A5%E6%91%A7%E6%AF%81%E7%9A%84%E6%95%8C%E4%BA%BA%E5%9F%8E%E5%A0%A1%E6%95%B0%E7%9B%AE.md,49.8%,简单,66 -2512,2500-2599,2512. 奖励最顶尖的 K 名学生,奖励最顶尖的 K 名学生,https://leetcode.cn/problems/reward-top-k-students/,reward-top-k-students,数组、哈希表、字符串、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/reward-top-k-students/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2512.%20%E5%A5%96%E5%8A%B1%E6%9C%80%E9%A1%B6%E5%B0%96%E7%9A%84%20K%20%E5%90%8D%E5%AD%A6%E7%94%9F.md,46.1%,中等,51 -2513,2500-2599,2513. 最小化两个数组中的最大值,最小化两个数组中的最大值,https://leetcode.cn/problems/minimize-the-maximum-of-two-arrays/,minimize-the-maximum-of-two-arrays,数学、二分查找、数论,https://algo.itcharge.cn/Solutions/2500-2599/minimize-the-maximum-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2513.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,34.6%,中等,34 -2514,2500-2599,2514. 统计同位异构字符串数目,统计同位异构字符串数目,https://leetcode.cn/problems/count-anagrams/,count-anagrams,哈希表、数学、字符串、组合数学、计数,https://algo.itcharge.cn/Solutions/2500-2599/count-anagrams/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2514.%20%E7%BB%9F%E8%AE%A1%E5%90%8C%E4%BD%8D%E5%BC%82%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md,46.3%,困难,35 -2515,2500-2599,2515. 到目标字符串的最短距离,到目标字符串的最短距离,https://leetcode.cn/problems/shortest-distance-to-target-string-in-a-circular-array/,shortest-distance-to-target-string-in-a-circular-array,数组、字符串,https://algo.itcharge.cn/Solutions/2500-2599/shortest-distance-to-target-string-in-a-circular-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2515.%20%E5%88%B0%E7%9B%AE%E6%A0%87%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%9D%E7%A6%BB.md,55.7%,简单,69 -2516,2500-2599,2516. 每种字符至少取 K 个,每种字符至少取 K 个,https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right/,take-k-of-each-character-from-left-and-right,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/2500-2599/take-k-of-each-character-from-left-and-right/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2516.%20%E6%AF%8F%E7%A7%8D%E5%AD%97%E7%AC%A6%E8%87%B3%E5%B0%91%E5%8F%96%20K%20%E4%B8%AA.md,37.2%,中等,74 -2517,2500-2599,2517. 礼盒的最大甜蜜度,礼盒的最大甜蜜度,https://leetcode.cn/problems/maximum-tastiness-of-candy-basket/,maximum-tastiness-of-candy-basket,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/maximum-tastiness-of-candy-basket/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2517.%20%E7%A4%BC%E7%9B%92%E7%9A%84%E6%9C%80%E5%A4%A7%E7%94%9C%E8%9C%9C%E5%BA%A6.md,72.0%,中等,135 -2518,2500-2599,2518. 好分区的数目,好分区的数目,https://leetcode.cn/problems/number-of-great-partitions/,number-of-great-partitions,数组、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/number-of-great-partitions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2518.%20%E5%A5%BD%E5%88%86%E5%8C%BA%E7%9A%84%E6%95%B0%E7%9B%AE.md,42.6%,困难,31 -2519,2500-2599,2519. 统计 K-Big 索引的数量,统计 K-Big 索引的数量,https://leetcode.cn/problems/count-the-number-of-k-big-indices/,count-the-number-of-k-big-indices,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-k-big-indices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2519.%20%E7%BB%9F%E8%AE%A1%20K-Big%20%E7%B4%A2%E5%BC%95%E7%9A%84%E6%95%B0%E9%87%8F.md,75.1%,困难,11 -2520,2500-2599,2520. 统计能整除数字的位数,统计能整除数字的位数,https://leetcode.cn/problems/count-the-digits-that-divide-a-number/,count-the-digits-that-divide-a-number,数学,https://algo.itcharge.cn/Solutions/2500-2599/count-the-digits-that-divide-a-number/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2520.%20%E7%BB%9F%E8%AE%A1%E8%83%BD%E6%95%B4%E9%99%A4%E6%95%B0%E5%AD%97%E7%9A%84%E4%BD%8D%E6%95%B0.md,83.2%,简单,81 -2521,2500-2599,2521. 数组乘积中的不同质因数数目,数组乘积中的不同质因数数目,https://leetcode.cn/problems/distinct-prime-factors-of-product-of-array/,distinct-prime-factors-of-product-of-array,数组、哈希表、数学、数论,https://algo.itcharge.cn/Solutions/2500-2599/distinct-prime-factors-of-product-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2521.%20%E6%95%B0%E7%BB%84%E4%B9%98%E7%A7%AF%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%90%8C%E8%B4%A8%E5%9B%A0%E6%95%B0%E6%95%B0%E7%9B%AE.md,62.6%,中等,63 -2522,2500-2599,2522. 将字符串分割成值不超过 K 的子字符串,将字符串分割成值不超过 K 的子字符串,https://leetcode.cn/problems/partition-string-into-substrings-with-values-at-most-k/,partition-string-into-substrings-with-values-at-most-k,贪心、字符串、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/partition-string-into-substrings-with-values-at-most-k/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2522.%20%E5%B0%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%86%E5%89%B2%E6%88%90%E5%80%BC%E4%B8%8D%E8%B6%85%E8%BF%87%20K%20%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,49.5%,中等,59 -2523,2500-2599,2523. 范围内最接近的两个质数,范围内最接近的两个质数,https://leetcode.cn/problems/closest-prime-numbers-in-range/,closest-prime-numbers-in-range,数学、数论,https://algo.itcharge.cn/Solutions/2500-2599/closest-prime-numbers-in-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2523.%20%E8%8C%83%E5%9B%B4%E5%86%85%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%A4%E4%B8%AA%E8%B4%A8%E6%95%B0.md,41.3%,中等,64 -2524,2500-2599,2524. 子数组的最大频率分数,子数组的最大频率分数,https://leetcode.cn/problems/maximum-frequency-score-of-a-subarray/,maximum-frequency-score-of-a-subarray,数组、哈希表、数学、滑动窗口,https://algo.itcharge.cn/Solutions/2500-2599/maximum-frequency-score-of-a-subarray/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2524.%20%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E9%A2%91%E7%8E%87%E5%88%86%E6%95%B0.md,52.2%,困难,3 -2525,2500-2599,2525. 根据规则将箱子分类,根据规则将箱子分类,https://leetcode.cn/problems/categorize-box-according-to-criteria/,categorize-box-according-to-criteria,数学,https://algo.itcharge.cn/Solutions/2500-2599/categorize-box-according-to-criteria/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2525.%20%E6%A0%B9%E6%8D%AE%E8%A7%84%E5%88%99%E5%B0%86%E7%AE%B1%E5%AD%90%E5%88%86%E7%B1%BB.md,47.5%,简单,37 -2526,2500-2599,2526. 找到数据流中的连续整数,找到数据流中的连续整数,https://leetcode.cn/problems/find-consecutive-integers-from-a-data-stream/,find-consecutive-integers-from-a-data-stream,设计、队列、哈希表、计数、数据流,https://algo.itcharge.cn/Solutions/2500-2599/find-consecutive-integers-from-a-data-stream/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2526.%20%E6%89%BE%E5%88%B0%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%95%B4%E6%95%B0.md,54.0%,中等,44 -2527,2500-2599,2527. 查询数组 Xor 美丽值,查询数组 Xor 美丽值,https://leetcode.cn/problems/find-xor-beauty-of-array/,find-xor-beauty-of-array,位运算、数组、数学,https://algo.itcharge.cn/Solutions/2500-2599/find-xor-beauty-of-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2527.%20%E6%9F%A5%E8%AF%A2%E6%95%B0%E7%BB%84%20Xor%20%E7%BE%8E%E4%B8%BD%E5%80%BC.md,70.3%,中等,40 -2528,2500-2599,2528. 最大化城市的最小供电站数目,最大化城市的最小供电站数目,https://leetcode.cn/problems/maximize-the-minimum-powered-city/,maximize-the-minimum-powered-city,贪心、队列、数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/2500-2599/maximize-the-minimum-powered-city/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2528.%20%E6%9C%80%E5%A4%A7%E5%8C%96%E5%9F%8E%E5%B8%82%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BE%9B%E7%94%B5%E7%AB%99%E6%95%B0%E7%9B%AE.md,40.5%,困难,37 -2529,2500-2599,2529. 正整数和负整数的最大计数,正整数和负整数的最大计数,https://leetcode.cn/problems/maximum-count-of-positive-integer-and-negative-integer/,maximum-count-of-positive-integer-and-negative-integer,数组、二分查找、计数,https://algo.itcharge.cn/Solutions/2500-2599/maximum-count-of-positive-integer-and-negative-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2529.%20%E6%AD%A3%E6%95%B4%E6%95%B0%E5%92%8C%E8%B4%9F%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E8%AE%A1%E6%95%B0.md,76.3%,简单,74 -2530,2500-2599,2530. 执行 K 次操作后的最大分数,执行 K 次操作后的最大分数,https://leetcode.cn/problems/maximal-score-after-applying-k-operations/,maximal-score-after-applying-k-operations,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/maximal-score-after-applying-k-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2530.%20%E6%89%A7%E8%A1%8C%20K%20%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%86%E6%95%B0.md,44.9%,中等,68 -2531,2500-2599,2531. 使字符串总不同字符的数目相等,使字符串总不同字符的数目相等,https://leetcode.cn/problems/make-number-of-distinct-characters-equal/,make-number-of-distinct-characters-equal,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/2500-2599/make-number-of-distinct-characters-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2531.%20%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%80%BB%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%95%B0%E7%9B%AE%E7%9B%B8%E7%AD%89.md,29.7%,中等,87 -2532,2500-2599,2532. 过桥的时间,过桥的时间,https://leetcode.cn/problems/time-to-cross-a-bridge/,time-to-cross-a-bridge,数组、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/time-to-cross-a-bridge/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2532.%20%E8%BF%87%E6%A1%A5%E7%9A%84%E6%97%B6%E9%97%B4.md,51.7%,困难,51 -2533,2500-2599,2533. 好二进制字符串的数量,好二进制字符串的数量,https://leetcode.cn/problems/number-of-good-binary-strings/,number-of-good-binary-strings,动态规划,https://algo.itcharge.cn/Solutions/2500-2599/number-of-good-binary-strings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2533.%20%E5%A5%BD%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E9%87%8F.md,69.7%,中等,3 -2534,2500-2599,2534. 通过门的时间,通过门的时间,https://leetcode.cn/problems/time-taken-to-cross-the-door/,time-taken-to-cross-the-door,队列、数组、模拟,https://algo.itcharge.cn/Solutions/2500-2599/time-taken-to-cross-the-door/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2534.%20%E9%80%9A%E8%BF%87%E9%97%A8%E7%9A%84%E6%97%B6%E9%97%B4.md,63.0%,困难,8 -2535,2500-2599,2535. 数组元素和与数字和的绝对差,数组元素和与数字和的绝对差,https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array/,difference-between-element-sum-and-digit-sum-of-an-array,数组、数学,https://algo.itcharge.cn/Solutions/2500-2599/difference-between-element-sum-and-digit-sum-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2535.%20%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%92%8C%E4%B8%8E%E6%95%B0%E5%AD%97%E5%92%8C%E7%9A%84%E7%BB%9D%E5%AF%B9%E5%B7%AE.md,84.3%,简单,110 -2536,2500-2599,2536. 子矩阵元素加 1,子矩阵元素加 1,https://leetcode.cn/problems/increment-submatrices-by-one/,increment-submatrices-by-one,数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/2500-2599/increment-submatrices-by-one/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2536.%20%E5%AD%90%E7%9F%A9%E9%98%B5%E5%85%83%E7%B4%A0%E5%8A%A0%201.md,60.6%,中等,77 -2537,2500-2599,2537. 统计好子数组的数目,统计好子数组的数目,https://leetcode.cn/problems/count-the-number-of-good-subarrays/,count-the-number-of-good-subarrays,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-good-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2537.%20%E7%BB%9F%E8%AE%A1%E5%A5%BD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,48.6%,中等,80 -2538,2500-2599,2538. 最大价值和与最小价值和的差值,最大价值和与最小价值和的差值,https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/,difference-between-maximum-and-minimum-price-sum,树、深度优先搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/difference-between-maximum-and-minimum-price-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2538.%20%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%E5%92%8C%E4%B8%8E%E6%9C%80%E5%B0%8F%E4%BB%B7%E5%80%BC%E5%92%8C%E7%9A%84%E5%B7%AE%E5%80%BC.md,48.5%,困难,43 -2539,2500-2599,2539. 好子序列的个数,好子序列的个数,https://leetcode.cn/problems/count-the-number-of-good-subsequences/,count-the-number-of-good-subsequences,哈希表、数学、字符串、组合数学、计数,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-good-subsequences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2539.%20%E5%A5%BD%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md,62.9%,中等,5 -2540,2500-2599,2540. 最小公共值,最小公共值,https://leetcode.cn/problems/minimum-common-value/,minimum-common-value,数组、哈希表、双指针、二分查找,https://algo.itcharge.cn/Solutions/2500-2599/minimum-common-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2540.%20%E6%9C%80%E5%B0%8F%E5%85%AC%E5%85%B1%E5%80%BC.md,60.5%,简单,60 -2541,2500-2599,2541. 使数组中所有元素相等的最小操作数 II,使数组中所有元素相等的最小操作数 II,https://leetcode.cn/problems/minimum-operations-to-make-array-equal-ii/,minimum-operations-to-make-array-equal-ii,贪心、数组、数学,https://algo.itcharge.cn/Solutions/2500-2599/minimum-operations-to-make-array-equal-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2541.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0%20II.md,31.2%,中等,32 -2542,2500-2599,2542. 最大子序列的分数,最大子序列的分数,https://leetcode.cn/problems/maximum-subsequence-score/,maximum-subsequence-score,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/maximum-subsequence-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2542.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E5%88%86%E6%95%B0.md,51.2%,中等,35 -2543,2500-2599,2543. 判断一个点是否可以到达,判断一个点是否可以到达,https://leetcode.cn/problems/check-if-point-is-reachable/,check-if-point-is-reachable,数学、数论,https://algo.itcharge.cn/Solutions/2500-2599/check-if-point-is-reachable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2543.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E7%82%B9%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%88%B0%E8%BE%BE.md,46.0%,困难,15 -2544,2500-2599,2544. 交替数字和,交替数字和,https://leetcode.cn/problems/alternating-digit-sum/,alternating-digit-sum,数学,https://algo.itcharge.cn/Solutions/2500-2599/alternating-digit-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2544.%20%E4%BA%A4%E6%9B%BF%E6%95%B0%E5%AD%97%E5%92%8C.md,79.8%,简单,76 -2545,2500-2599,2545. 根据第 K 场考试的分数排序,根据第 K 场考试的分数排序,https://leetcode.cn/problems/sort-the-students-by-their-kth-score/,sort-the-students-by-their-kth-score,数组、矩阵、排序,https://algo.itcharge.cn/Solutions/2500-2599/sort-the-students-by-their-kth-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2545.%20%E6%A0%B9%E6%8D%AE%E7%AC%AC%20K%20%E5%9C%BA%E8%80%83%E8%AF%95%E7%9A%84%E5%88%86%E6%95%B0%E6%8E%92%E5%BA%8F.md,86.5%,中等,68 -2546,2500-2599,2546. 执行逐位运算使字符串相等,执行逐位运算使字符串相等,https://leetcode.cn/problems/apply-bitwise-operations-to-make-strings-equal/,apply-bitwise-operations-to-make-strings-equal,位运算、字符串,https://algo.itcharge.cn/Solutions/2500-2599/apply-bitwise-operations-to-make-strings-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2546.%20%E6%89%A7%E8%A1%8C%E9%80%90%E4%BD%8D%E8%BF%90%E7%AE%97%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md,42.2%,中等,50 -2547,2500-2599,2547. 拆分数组的最小代价,拆分数组的最小代价,https://leetcode.cn/problems/minimum-cost-to-split-an-array/,minimum-cost-to-split-an-array,数组、哈希表、动态规划、计数,https://algo.itcharge.cn/Solutions/2500-2599/minimum-cost-to-split-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2547.%20%E6%8B%86%E5%88%86%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7.md,56.5%,困难,37 -2548,2500-2599,2548. 填满背包的最大价格,填满背包的最大价格,https://leetcode.cn/problems/maximum-price-to-fill-a-bag/,maximum-price-to-fill-a-bag,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2500-2599/maximum-price-to-fill-a-bag/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2548.%20%E5%A1%AB%E6%BB%A1%E8%83%8C%E5%8C%85%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E6%A0%BC.md,58.3%,中等,3 -2549,2500-2599,2549. 统计桌面上的不同数字,统计桌面上的不同数字,https://leetcode.cn/problems/count-distinct-numbers-on-board/,count-distinct-numbers-on-board,数组、哈希表、数学、模拟,https://algo.itcharge.cn/Solutions/2500-2599/count-distinct-numbers-on-board/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2549.%20%E7%BB%9F%E8%AE%A1%E6%A1%8C%E9%9D%A2%E4%B8%8A%E7%9A%84%E4%B8%8D%E5%90%8C%E6%95%B0%E5%AD%97.md,62.9%,简单,46 -2550,2500-2599,2550. 猴子碰撞的方法数,猴子碰撞的方法数,https://leetcode.cn/problems/count-collisions-of-monkeys-on-a-polygon/,count-collisions-of-monkeys-on-a-polygon,递归、数学,https://algo.itcharge.cn/Solutions/2500-2599/count-collisions-of-monkeys-on-a-polygon/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2550.%20%E7%8C%B4%E5%AD%90%E7%A2%B0%E6%92%9E%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,28.9%,中等,45 -2551,2500-2599,2551. 将珠子放入背包中,将珠子放入背包中,https://leetcode.cn/problems/put-marbles-in-bags/,put-marbles-in-bags,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/put-marbles-in-bags/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2551.%20%E5%B0%86%E7%8F%A0%E5%AD%90%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%E4%B8%AD.md,56.5%,困难,38 -2552,2500-2599,2552. 统计上升四元组,统计上升四元组,https://leetcode.cn/problems/count-increasing-quadruplets/,count-increasing-quadruplets,树状数组、数组、动态规划、枚举、前缀和,https://algo.itcharge.cn/Solutions/2500-2599/count-increasing-quadruplets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2552.%20%E7%BB%9F%E8%AE%A1%E4%B8%8A%E5%8D%87%E5%9B%9B%E5%85%83%E7%BB%84.md,44.2%,困难,37 -2553,2500-2599,2553. 分割数组中数字的数位,分割数组中数字的数位,https://leetcode.cn/problems/separate-the-digits-in-an-array/,separate-the-digits-in-an-array,数组、模拟,https://algo.itcharge.cn/Solutions/2500-2599/separate-the-digits-in-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2553.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E7%9A%84%E6%95%B0%E4%BD%8D.md,82.5%,简单,61 -2554,2500-2599,2554. 从一个范围内选择最多整数 I,从一个范围内选择最多整数 I,https://leetcode.cn/problems/maximum-number-of-integers-to-choose-from-a-range-i/,maximum-number-of-integers-to-choose-from-a-range-i,贪心、数组、哈希表、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/maximum-number-of-integers-to-choose-from-a-range-i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2554.%20%E4%BB%8E%E4%B8%80%E4%B8%AA%E8%8C%83%E5%9B%B4%E5%86%85%E9%80%89%E6%8B%A9%E6%9C%80%E5%A4%9A%E6%95%B4%E6%95%B0%20I.md,58.2%,中等,40 -2555,2500-2599,2555. 两个线段获得的最多奖品,两个线段获得的最多奖品,https://leetcode.cn/problems/maximize-win-from-two-segments/,maximize-win-from-two-segments,数组、二分查找、滑动窗口,https://algo.itcharge.cn/Solutions/2500-2599/maximize-win-from-two-segments/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2555.%20%E4%B8%A4%E4%B8%AA%E7%BA%BF%E6%AE%B5%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%9A%E5%A5%96%E5%93%81.md,39.9%,中等,31 -2556,2500-2599,2556. 二进制矩阵中翻转最多一次使路径不连通,二进制矩阵中翻转最多一次使路径不连通,https://leetcode.cn/problems/disconnect-path-in-a-binary-matrix-by-at-most-one-flip/,disconnect-path-in-a-binary-matrix-by-at-most-one-flip,深度优先搜索、广度优先搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2500-2599/disconnect-path-in-a-binary-matrix-by-at-most-one-flip/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2556.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%BF%BB%E8%BD%AC%E6%9C%80%E5%A4%9A%E4%B8%80%E6%AC%A1%E4%BD%BF%E8%B7%AF%E5%BE%84%E4%B8%8D%E8%BF%9E%E9%80%9A.md,30.2%,中等,47 -2557,2500-2599,2557. 从一个范围内选择最多整数 II,从一个范围内选择最多整数 II,https://leetcode.cn/problems/maximum-number-of-integers-to-choose-from-a-range-ii/,maximum-number-of-integers-to-choose-from-a-range-ii,贪心、数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/maximum-number-of-integers-to-choose-from-a-range-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2557.%20%E4%BB%8E%E4%B8%80%E4%B8%AA%E8%8C%83%E5%9B%B4%E5%86%85%E9%80%89%E6%8B%A9%E6%9C%80%E5%A4%9A%E6%95%B4%E6%95%B0%20II.md,43.1%,中等,7 -2558,2500-2599,2558. 从数量最多的堆取走礼物,从数量最多的堆取走礼物,https://leetcode.cn/problems/take-gifts-from-the-richest-pile/,take-gifts-from-the-richest-pile,数组、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/take-gifts-from-the-richest-pile/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2558.%20%E4%BB%8E%E6%95%B0%E9%87%8F%E6%9C%80%E5%A4%9A%E7%9A%84%E5%A0%86%E5%8F%96%E8%B5%B0%E7%A4%BC%E7%89%A9.md,68.1%,简单,71 -2559,2500-2599,2559. 统计范围内的元音字符串数,统计范围内的元音字符串数,https://leetcode.cn/problems/count-vowel-strings-in-ranges/,count-vowel-strings-in-ranges,数组、字符串、前缀和,https://algo.itcharge.cn/Solutions/2500-2599/count-vowel-strings-in-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2559.%20%E7%BB%9F%E8%AE%A1%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0.md,64.3%,中等,257 -2560,2500-2599,2560. 打家劫舍 IV,打家劫舍 IV,https://leetcode.cn/problems/house-robber-iv/,house-robber-iv,数组、二分查找,https://algo.itcharge.cn/Solutions/2500-2599/house-robber-iv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2560.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20IV.md,49.6%,中等,66 -2561,2500-2599,2561. 重排水果,重排水果,https://leetcode.cn/problems/rearranging-fruits/,rearranging-fruits,贪心、数组、哈希表,https://algo.itcharge.cn/Solutions/2500-2599/rearranging-fruits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2561.%20%E9%87%8D%E6%8E%92%E6%B0%B4%E6%9E%9C.md,36.5%,困难,36 -2562,2500-2599,2562. 找出数组的串联值,找出数组的串联值,https://leetcode.cn/problems/find-the-array-concatenation-value/,find-the-array-concatenation-value,数组、双指针、模拟,https://algo.itcharge.cn/Solutions/2500-2599/find-the-array-concatenation-value/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2562.%20%E6%89%BE%E5%87%BA%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%B2%E8%81%94%E5%80%BC.md,73.2%,简单,77 -2563,2500-2599,2563. 统计公平数对的数目,统计公平数对的数目,https://leetcode.cn/problems/count-the-number-of-fair-pairs/,count-the-number-of-fair-pairs,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-fair-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2563.%20%E7%BB%9F%E8%AE%A1%E5%85%AC%E5%B9%B3%E6%95%B0%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,33.5%,中等,77 -2564,2500-2599,2564. 子字符串异或查询,子字符串异或查询,https://leetcode.cn/problems/substring-xor-queries/,substring-xor-queries,位运算、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/2500-2599/substring-xor-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2564.%20%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md,36.4%,中等,61 -2565,2500-2599,2565. 最少得分子序列,最少得分子序列,https://leetcode.cn/problems/subsequence-with-the-minimum-score/,subsequence-with-the-minimum-score,双指针、字符串、二分查找,https://algo.itcharge.cn/Solutions/2500-2599/subsequence-with-the-minimum-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2565.%20%E6%9C%80%E5%B0%91%E5%BE%97%E5%88%86%E5%AD%90%E5%BA%8F%E5%88%97.md,35.8%,困难,34 -2566,2500-2599,2566. 替换一个数字后的最大差值,替换一个数字后的最大差值,https://leetcode.cn/problems/maximum-difference-by-remapping-a-digit/,maximum-difference-by-remapping-a-digit,贪心、数学,https://algo.itcharge.cn/Solutions/2500-2599/maximum-difference-by-remapping-a-digit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2566.%20%E6%9B%BF%E6%8D%A2%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B7%AE%E5%80%BC.md,63.7%,简单,50 -2567,2500-2599,2567. 修改两个元素的最小分数,修改两个元素的最小分数,https://leetcode.cn/problems/minimum-score-by-changing-two-elements/,minimum-score-by-changing-two-elements,贪心、数组、排序,https://algo.itcharge.cn/Solutions/2500-2599/minimum-score-by-changing-two-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2567.%20%E4%BF%AE%E6%94%B9%E4%B8%A4%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%88%86%E6%95%B0.md,51.8%,中等,36 -2568,2500-2599,2568. 最小无法得到的或值,最小无法得到的或值,https://leetcode.cn/problems/minimum-impossible-or/,minimum-impossible-or,位运算、脑筋急转弯、数组,https://algo.itcharge.cn/Solutions/2500-2599/minimum-impossible-or/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2568.%20%E6%9C%80%E5%B0%8F%E6%97%A0%E6%B3%95%E5%BE%97%E5%88%B0%E7%9A%84%E6%88%96%E5%80%BC.md,59.8%,中等,28 -2569,2500-2599,2569. 更新数组后处理求和查询,更新数组后处理求和查询,https://leetcode.cn/problems/handling-sum-queries-after-update/,handling-sum-queries-after-update,线段树、数组,https://algo.itcharge.cn/Solutions/2500-2599/handling-sum-queries-after-update/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2569.%20%E6%9B%B4%E6%96%B0%E6%95%B0%E7%BB%84%E5%90%8E%E5%A4%84%E7%90%86%E6%B1%82%E5%92%8C%E6%9F%A5%E8%AF%A2.md,39.7%,困难,43 -2570,2500-2599,2570. 合并两个二维数组 - 求和法,合并两个二维数组 - 求和法,https://leetcode.cn/problems/merge-two-2d-arrays-by-summing-values/,merge-two-2d-arrays-by-summing-values,数组、哈希表、双指针,https://algo.itcharge.cn/Solutions/2500-2599/merge-two-2d-arrays-by-summing-values/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2570.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%20-%20%E6%B1%82%E5%92%8C%E6%B3%95.md,69.6%,简单,71 -2571,2500-2599,2571. 将整数减少到零需要的最少操作数,将整数减少到零需要的最少操作数,https://leetcode.cn/problems/minimum-operations-to-reduce-an-integer-to-0/,minimum-operations-to-reduce-an-integer-to-0,贪心、位运算、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/minimum-operations-to-reduce-an-integer-to-0/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2571.%20%E5%B0%86%E6%95%B4%E6%95%B0%E5%87%8F%E5%B0%91%E5%88%B0%E9%9B%B6%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,54.0%,中等,116 -2572,2500-2599,2572. 无平方子集计数,无平方子集计数,https://leetcode.cn/problems/count-the-number-of-square-free-subsets/,count-the-number-of-square-free-subsets,位运算、数组、数学、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-square-free-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2572.%20%E6%97%A0%E5%B9%B3%E6%96%B9%E5%AD%90%E9%9B%86%E8%AE%A1%E6%95%B0.md,29.8%,中等,43 -2573,2500-2599,2573. 找出对应 LCP 矩阵的字符串,找出对应 LCP 矩阵的字符串,https://leetcode.cn/problems/find-the-string-with-lcp/,find-the-string-with-lcp,贪心、并查集、字符串、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/find-the-string-with-lcp/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2573.%20%E6%89%BE%E5%87%BA%E5%AF%B9%E5%BA%94%20LCP%20%E7%9F%A9%E9%98%B5%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,35.3%,困难,31 -2574,2500-2599,2574. 左右元素和的差值,左右元素和的差值,https://leetcode.cn/problems/left-and-right-sum-differences/,left-and-right-sum-differences,数组、前缀和,https://algo.itcharge.cn/Solutions/2500-2599/left-and-right-sum-differences/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2574.%20%E5%B7%A6%E5%8F%B3%E5%85%83%E7%B4%A0%E5%92%8C%E7%9A%84%E5%B7%AE%E5%80%BC.md,85.0%,简单,104 -2575,2500-2599,2575. 找出字符串的可整除数组,找出字符串的可整除数组,https://leetcode.cn/problems/find-the-divisibility-array-of-a-string/,find-the-divisibility-array-of-a-string,数组、数学、字符串,https://algo.itcharge.cn/Solutions/2500-2599/find-the-divisibility-array-of-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2575.%20%E6%89%BE%E5%87%BA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%8F%AF%E6%95%B4%E9%99%A4%E6%95%B0%E7%BB%84.md,32.8%,中等,75 -2576,2500-2599,2576. 求出最多标记下标,求出最多标记下标,https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices/,find-the-maximum-number-of-marked-indices,贪心、数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/find-the-maximum-number-of-marked-indices/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2576.%20%E6%B1%82%E5%87%BA%E6%9C%80%E5%A4%9A%E6%A0%87%E8%AE%B0%E4%B8%8B%E6%A0%87.md,36.8%,中等,78 -2577,2500-2599,2577. 在网格图中访问一个格子的最少时间,在网格图中访问一个格子的最少时间,https://leetcode.cn/problems/minimum-time-to-visit-a-cell-in-a-grid/,minimum-time-to-visit-a-cell-in-a-grid,广度优先搜索、图、数组、矩阵、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/minimum-time-to-visit-a-cell-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2577.%20%E5%9C%A8%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E8%AE%BF%E9%97%AE%E4%B8%80%E4%B8%AA%E6%A0%BC%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,39.4%,困难,46 -2578,2500-2599,2578. 最小和分割,最小和分割,https://leetcode.cn/problems/split-with-minimum-sum/,split-with-minimum-sum,贪心、数学、排序,https://algo.itcharge.cn/Solutions/2500-2599/split-with-minimum-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2578.%20%E6%9C%80%E5%B0%8F%E5%92%8C%E5%88%86%E5%89%B2.md,76.7%,简单,67 -2579,2500-2599,2579. 统计染色格子数,统计染色格子数,https://leetcode.cn/problems/count-total-number-of-colored-cells/,count-total-number-of-colored-cells,数学,https://algo.itcharge.cn/Solutions/2500-2599/count-total-number-of-colored-cells/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2579.%20%E7%BB%9F%E8%AE%A1%E6%9F%93%E8%89%B2%E6%A0%BC%E5%AD%90%E6%95%B0.md,66.4%,中等,36 -2580,2500-2599,2580. 统计将重叠区间合并成组的方案数,统计将重叠区间合并成组的方案数,https://leetcode.cn/problems/count-ways-to-group-overlapping-ranges/,count-ways-to-group-overlapping-ranges,数组、排序,https://algo.itcharge.cn/Solutions/2500-2599/count-ways-to-group-overlapping-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2580.%20%E7%BB%9F%E8%AE%A1%E5%B0%86%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4%E5%90%88%E5%B9%B6%E6%88%90%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88%E6%95%B0.md,35.4%,中等,50 -2581,2500-2599,2581. 统计可能的树根数目,统计可能的树根数目,https://leetcode.cn/problems/count-number-of-possible-root-nodes/,count-number-of-possible-root-nodes,树、深度优先搜索、哈希表、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/count-number-of-possible-root-nodes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2581.%20%E7%BB%9F%E8%AE%A1%E5%8F%AF%E8%83%BD%E7%9A%84%E6%A0%91%E6%A0%B9%E6%95%B0%E7%9B%AE.md,57.7%,困难,26 -2582,2500-2599,2582. 递枕头,递枕头,https://leetcode.cn/problems/pass-the-pillow/,pass-the-pillow,数学、模拟,https://algo.itcharge.cn/Solutions/2500-2599/pass-the-pillow/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2582.%20%E9%80%92%E6%9E%95%E5%A4%B4.md,55.9%,简单,88 -2583,2500-2599,2583. 二叉树中的第 K 大层和,二叉树中的第 K 大层和,https://leetcode.cn/problems/kth-largest-sum-in-a-binary-tree/,kth-largest-sum-in-a-binary-tree,树、广度优先搜索、二分查找,https://algo.itcharge.cn/Solutions/2500-2599/kth-largest-sum-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2583.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%B1%82%E5%92%8C.md,44.0%,中等,68 -2584,2500-2599,2584. 分割数组使乘积互质,分割数组使乘积互质,https://leetcode.cn/problems/split-the-array-to-make-coprime-products/,split-the-array-to-make-coprime-products,数组、哈希表、数学、数论,https://algo.itcharge.cn/Solutions/2500-2599/split-the-array-to-make-coprime-products/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2584.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E4%BD%BF%E4%B9%98%E7%A7%AF%E4%BA%92%E8%B4%A8.md,25.4%,困难,72 -2585,2500-2599,2585. 获得分数的方法数,获得分数的方法数,https://leetcode.cn/problems/number-of-ways-to-earn-points/,number-of-ways-to-earn-points,数组、动态规划,https://algo.itcharge.cn/Solutions/2500-2599/number-of-ways-to-earn-points/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2585.%20%E8%8E%B7%E5%BE%97%E5%88%86%E6%95%B0%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md,64.5%,困难,56 -2586,2500-2599,2586. 统计范围内的元音字符串数,统计范围内的元音字符串数,https://leetcode.cn/problems/count-the-number-of-vowel-strings-in-range/,count-the-number-of-vowel-strings-in-range,数组、字符串,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-vowel-strings-in-range/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2586.%20%E7%BB%9F%E8%AE%A1%E8%8C%83%E5%9B%B4%E5%86%85%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0.md,81.6%,简单,55 -2587,2500-2599,2587. 重排数组以得到最大前缀分数,重排数组以得到最大前缀分数,https://leetcode.cn/problems/rearrange-array-to-maximize-prefix-score/,rearrange-array-to-maximize-prefix-score,贪心、数组、前缀和、排序,https://algo.itcharge.cn/Solutions/2500-2599/rearrange-array-to-maximize-prefix-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2587.%20%E9%87%8D%E6%8E%92%E6%95%B0%E7%BB%84%E4%BB%A5%E5%BE%97%E5%88%B0%E6%9C%80%E5%A4%A7%E5%89%8D%E7%BC%80%E5%88%86%E6%95%B0.md,40.6%,中等,44 -2588,2500-2599,2588. 统计美丽子数组数目,统计美丽子数组数目,https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays/,count-the-number-of-beautiful-subarrays,位运算、数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/2500-2599/count-the-number-of-beautiful-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2588.%20%E7%BB%9F%E8%AE%A1%E7%BE%8E%E4%B8%BD%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md,41.9%,中等,60 -2589,2500-2599,2589. 完成所有任务的最少时间,完成所有任务的最少时间,https://leetcode.cn/problems/minimum-time-to-complete-all-tasks/,minimum-time-to-complete-all-tasks,栈、贪心、数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2500-2599/minimum-time-to-complete-all-tasks/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2589.%20%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,42.7%,困难,34 -2590,2500-2599,2590. 设计一个待办事项清单,设计一个待办事项清单,https://leetcode.cn/problems/design-a-todo-list/,design-a-todo-list,设计、数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/2500-2599/design-a-todo-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2590.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E5%BE%85%E5%8A%9E%E4%BA%8B%E9%A1%B9%E6%B8%85%E5%8D%95.md,55.5%,中等,4 -2591,2500-2599,2591. 将钱分给最多的儿童,将钱分给最多的儿童,https://leetcode.cn/problems/distribute-money-to-maximum-children/,distribute-money-to-maximum-children,贪心、数学,https://algo.itcharge.cn/Solutions/2500-2599/distribute-money-to-maximum-children/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2591.%20%E5%B0%86%E9%92%B1%E5%88%86%E7%BB%99%E6%9C%80%E5%A4%9A%E7%9A%84%E5%84%BF%E7%AB%A5.md,20.8%,简单,59 -2592,2500-2599,2592. 最大化数组的伟大值,最大化数组的伟大值,https://leetcode.cn/problems/maximize-greatness-of-an-array/,maximize-greatness-of-an-array,贪心、数组、双指针、排序,https://algo.itcharge.cn/Solutions/2500-2599/maximize-greatness-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2592.%20%E6%9C%80%E5%A4%A7%E5%8C%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%BC%9F%E5%A4%A7%E5%80%BC.md,57.7%,中等,57 -2593,2500-2599,2593. 标记所有元素后数组的分数,标记所有元素后数组的分数,https://leetcode.cn/problems/find-score-of-an-array-after-marking-all-elements/,find-score-of-an-array-after-marking-all-elements,数组、排序、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/find-score-of-an-array-after-marking-all-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2593.%20%E6%A0%87%E8%AE%B0%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%8E%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E6%95%B0.md,51.9%,中等,51 -2594,2500-2599,2594. 修车的最少时间,修车的最少时间,https://leetcode.cn/problems/minimum-time-to-repair-cars/,minimum-time-to-repair-cars,数组、二分查找,https://algo.itcharge.cn/Solutions/2500-2599/minimum-time-to-repair-cars/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2594.%20%E4%BF%AE%E8%BD%A6%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4.md,45.6%,中等,33 -2595,2500-2599,2595. 奇偶位数,奇偶位数,https://leetcode.cn/problems/number-of-even-and-odd-bits/,number-of-even-and-odd-bits,位运算,https://algo.itcharge.cn/Solutions/2500-2599/number-of-even-and-odd-bits/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2595.%20%E5%A5%87%E5%81%B6%E4%BD%8D%E6%95%B0.md,72.8%,简单,58 -2596,2500-2599,2596. 检查骑士巡视方案,检查骑士巡视方案,https://leetcode.cn/problems/check-knight-tour-configuration/,check-knight-tour-configuration,深度优先搜索、广度优先搜索、数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/2500-2599/check-knight-tour-configuration/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2596.%20%E6%A3%80%E6%9F%A5%E9%AA%91%E5%A3%AB%E5%B7%A1%E8%A7%86%E6%96%B9%E6%A1%88.md,52.5%,中等,99 -2597,2500-2599,2597. 美丽子集的数目,美丽子集的数目,https://leetcode.cn/problems/the-number-of-beautiful-subsets/,the-number-of-beautiful-subsets,数组、动态规划、回溯,https://algo.itcharge.cn/Solutions/2500-2599/the-number-of-beautiful-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2597.%20%E7%BE%8E%E4%B8%BD%E5%AD%90%E9%9B%86%E7%9A%84%E6%95%B0%E7%9B%AE.md,34.7%,中等,56 -2598,2500-2599,2598. 执行操作后的最大 MEX,执行操作后的最大 MEX,https://leetcode.cn/problems/smallest-missing-non-negative-integer-after-operations/,smallest-missing-non-negative-integer-after-operations,贪心、数组、哈希表、数学,https://algo.itcharge.cn/Solutions/2500-2599/smallest-missing-non-negative-integer-after-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2598.%20%E6%89%A7%E8%A1%8C%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E6%9C%80%E5%A4%A7%20MEX.md,39.1%,中等,55 -2599,2500-2599,2599. 使前缀和数组非负,使前缀和数组非负,https://leetcode.cn/problems/make-the-prefix-sum-non-negative/,make-the-prefix-sum-non-negative,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/2500-2599/make-the-prefix-sum-non-negative/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2599.%20%E4%BD%BF%E5%89%8D%E7%BC%80%E5%92%8C%E6%95%B0%E7%BB%84%E9%9D%9E%E8%B4%9F.md,55.4%,中等,5 -2600,2600-2699,2600. K 件物品的最大和,K 件物品的最大和,https://leetcode.cn/problems/k-items-with-the-maximum-sum/,k-items-with-the-maximum-sum,贪心、数学,https://algo.itcharge.cn/Solutions/2600-2699/k-items-with-the-maximum-sum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2600.%20K%20%E4%BB%B6%E7%89%A9%E5%93%81%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,66.0%,简单,59 -2601,2600-2699,2601. 质数减法运算,质数减法运算,https://leetcode.cn/problems/prime-subtraction-operation/,prime-subtraction-operation,贪心、数组、数学、二分查找、数论,https://algo.itcharge.cn/Solutions/2600-2699/prime-subtraction-operation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2601.%20%E8%B4%A8%E6%95%B0%E5%87%8F%E6%B3%95%E8%BF%90%E7%AE%97.md,38.6%,中等,86 -2602,2600-2699,2602. 使数组元素全部相等的最少操作次数,使数组元素全部相等的最少操作次数,https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/,minimum-operations-to-make-all-array-elements-equal,数组、二分查找、前缀和、排序,https://algo.itcharge.cn/Solutions/2600-2699/minimum-operations-to-make-all-array-elements-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2602.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%85%A8%E9%83%A8%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,33.4%,中等,99 -2603,2600-2699,2603. 收集树中金币,收集树中金币,https://leetcode.cn/problems/collect-coins-in-a-tree/,collect-coins-in-a-tree,树、图、拓扑排序、数组,https://algo.itcharge.cn/Solutions/2600-2699/collect-coins-in-a-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2603.%20%E6%94%B6%E9%9B%86%E6%A0%91%E4%B8%AD%E9%87%91%E5%B8%81.md,43.6%,困难,42 -2604,2600-2699,2604. 吃掉所有谷子的最短时间,吃掉所有谷子的最短时间,https://leetcode.cn/problems/minimum-time-to-eat-all-grains/,minimum-time-to-eat-all-grains,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/2600-2699/minimum-time-to-eat-all-grains/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2604.%20%E5%90%83%E6%8E%89%E6%89%80%E6%9C%89%E8%B0%B7%E5%AD%90%E7%9A%84%E6%9C%80%E7%9F%AD%E6%97%B6%E9%97%B4.md,38.0%,困难,3 -2605,2600-2699,2605. 从两个数字数组里生成最小数字,从两个数字数组里生成最小数字,https://leetcode.cn/problems/form-smallest-number-from-two-digit-arrays/,form-smallest-number-from-two-digit-arrays,数组、哈希表、枚举,https://algo.itcharge.cn/Solutions/2600-2699/form-smallest-number-from-two-digit-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2605.%20%E4%BB%8E%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97%E6%95%B0%E7%BB%84%E9%87%8C%E7%94%9F%E6%88%90%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md,63.3%,简单,48 -2606,2600-2699,2606. 找到最大开销的子字符串,找到最大开销的子字符串,https://leetcode.cn/problems/find-the-substring-with-maximum-cost/,find-the-substring-with-maximum-cost,数组、哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/2600-2699/find-the-substring-with-maximum-cost/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2606.%20%E6%89%BE%E5%88%B0%E6%9C%80%E5%A4%A7%E5%BC%80%E9%94%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,53.3%,中等,36 -2607,2600-2699,2607. 使子数组元素和相等,使子数组元素和相等,https://leetcode.cn/problems/make-k-subarray-sums-equal/,make-k-subarray-sums-equal,数组、数学、数论、排序,https://algo.itcharge.cn/Solutions/2600-2699/make-k-subarray-sums-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2607.%20%E4%BD%BF%E5%AD%90%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%92%8C%E7%9B%B8%E7%AD%89.md,38.6%,中等,23 -2608,2600-2699,2608. 图中的最短环,图中的最短环,https://leetcode.cn/problems/shortest-cycle-in-a-graph/,shortest-cycle-in-a-graph,广度优先搜索、图,https://algo.itcharge.cn/Solutions/2600-2699/shortest-cycle-in-a-graph/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2608.%20%E5%9B%BE%E4%B8%AD%E7%9A%84%E6%9C%80%E7%9F%AD%E7%8E%AF.md,40.8%,困难,31 -2609,2600-2699,2609. 最长平衡子字符串,最长平衡子字符串,https://leetcode.cn/problems/find-the-longest-balanced-substring-of-a-binary-string/,find-the-longest-balanced-substring-of-a-binary-string,字符串,https://algo.itcharge.cn/Solutions/2600-2699/find-the-longest-balanced-substring-of-a-binary-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2609.%20%E6%9C%80%E9%95%BF%E5%B9%B3%E8%A1%A1%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,49.3%,简单,95 -2610,2600-2699,2610. 转换二维数组,转换二维数组,https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions/,convert-an-array-into-a-2d-array-with-conditions,数组、哈希表,https://algo.itcharge.cn/Solutions/2600-2699/convert-an-array-into-a-2d-array-with-conditions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2610.%20%E8%BD%AC%E6%8D%A2%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84.md,85.1%,中等,82 -2611,2600-2699,2611. 老鼠和奶酪,老鼠和奶酪,https://leetcode.cn/problems/mice-and-cheese/,mice-and-cheese,贪心、数组、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/2600-2699/mice-and-cheese/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2611.%20%E8%80%81%E9%BC%A0%E5%92%8C%E5%A5%B6%E9%85%AA.md,58.7%,中等,291 -2612,2600-2699,2612. 最少翻转操作数,最少翻转操作数,https://leetcode.cn/problems/minimum-reverse-operations/,minimum-reverse-operations,广度优先搜索、数组、有序集合,https://algo.itcharge.cn/Solutions/2600-2699/minimum-reverse-operations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2612.%20%E6%9C%80%E5%B0%91%E7%BF%BB%E8%BD%AC%E6%93%8D%E4%BD%9C%E6%95%B0.md,20.8%,困难,33 -2613,2600-2699,2613. 美数对,美数对,https://leetcode.cn/problems/beautiful-pairs/,beautiful-pairs,几何、数组、数学、分治、有序集合、排序,https://algo.itcharge.cn/Solutions/2600-2699/beautiful-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2613.%20%E7%BE%8E%E6%95%B0%E5%AF%B9.md,48.2%,困难,4 -2614,2600-2699,2614. 对角线上的质数,对角线上的质数,https://leetcode.cn/problems/prime-in-diagonal/,prime-in-diagonal,数组、数学、矩阵、数论,https://algo.itcharge.cn/Solutions/2600-2699/prime-in-diagonal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2614.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E7%9A%84%E8%B4%A8%E6%95%B0.md,32.9%,简单,49 -2615,2600-2699,2615. 等值距离和,等值距离和,https://leetcode.cn/problems/sum-of-distances/,sum-of-distances,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/2600-2699/sum-of-distances/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2615.%20%E7%AD%89%E5%80%BC%E8%B7%9D%E7%A6%BB%E5%92%8C.md,35.4%,中等,64 -2616,2600-2699,2616. 最小化数对的最大差值,最小化数对的最大差值,https://leetcode.cn/problems/minimize-the-maximum-difference-of-pairs/,minimize-the-maximum-difference-of-pairs,贪心、数组、二分查找,https://algo.itcharge.cn/Solutions/2600-2699/minimize-the-maximum-difference-of-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2616.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E6%95%B0%E5%AF%B9%E7%9A%84%E6%9C%80%E5%A4%A7%E5%B7%AE%E5%80%BC.md,39.0%,中等,34 -2617,2600-2699,2617. 网格图中最少访问的格子数,网格图中最少访问的格子数,https://leetcode.cn/problems/minimum-number-of-visited-cells-in-a-grid/,minimum-number-of-visited-cells-in-a-grid,栈、并查集、树状数组、线段树、数组、二分查找、动态规划,https://algo.itcharge.cn/Solutions/2600-2699/minimum-number-of-visited-cells-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2617.%20%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%B0%91%E8%AE%BF%E9%97%AE%E7%9A%84%E6%A0%BC%E5%AD%90%E6%95%B0.md,32.6%,困难,62 -2618,2600-2699,2618. 检查是否是类的对象实例,检查是否是类的对象实例,https://leetcode.cn/problems/check-if-object-instance-of-class/,check-if-object-instance-of-class,,https://algo.itcharge.cn/Solutions/2600-2699/check-if-object-instance-of-class/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2618.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%98%AF%E7%B1%BB%E7%9A%84%E5%AF%B9%E8%B1%A1%E5%AE%9E%E4%BE%8B.md,34.0%,中等,43 -2619,2600-2699,2619. 数组原型对象的最后一个元素,数组原型对象的最后一个元素,https://leetcode.cn/problems/array-prototype-last/,array-prototype-last,,https://algo.itcharge.cn/Solutions/2600-2699/array-prototype-last/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2619.%20%E6%95%B0%E7%BB%84%E5%8E%9F%E5%9E%8B%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0.md,72.2%,简单,60 -2620,2600-2699,2620. 计数器,计数器,https://leetcode.cn/problems/counter/,counter,,https://algo.itcharge.cn/Solutions/2600-2699/counter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2620.%20%E8%AE%A1%E6%95%B0%E5%99%A8.md,81.9%,简单,57 -2621,2600-2699,2621. 睡眠函数,睡眠函数,https://leetcode.cn/problems/sleep/,sleep,,https://algo.itcharge.cn/Solutions/2600-2699/sleep/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2621.%20%E7%9D%A1%E7%9C%A0%E5%87%BD%E6%95%B0.md,84.0%,简单,38 -2622,2600-2699,2622. 有时间限制的缓存,有时间限制的缓存,https://leetcode.cn/problems/cache-with-time-limit/,cache-with-time-limit,,https://algo.itcharge.cn/Solutions/2600-2699/cache-with-time-limit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2622.%20%E6%9C%89%E6%97%B6%E9%97%B4%E9%99%90%E5%88%B6%E7%9A%84%E7%BC%93%E5%AD%98.md,55.8%,中等,48 -2623,2600-2699,2623. 记忆函数,记忆函数,https://leetcode.cn/problems/memoize/,memoize,,https://algo.itcharge.cn/Solutions/2600-2699/memoize/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2623.%20%E8%AE%B0%E5%BF%86%E5%87%BD%E6%95%B0.md,61.2%,中等,37 -2624,2600-2699,2624. 蜗牛排序,蜗牛排序,https://leetcode.cn/problems/snail-traversal/,snail-traversal,,https://algo.itcharge.cn/Solutions/2600-2699/snail-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2624.%20%E8%9C%97%E7%89%9B%E6%8E%92%E5%BA%8F.md,66.0%,中等,47 -2625,2600-2699,2625. 扁平化嵌套数组,扁平化嵌套数组,https://leetcode.cn/problems/flatten-deeply-nested-array/,flatten-deeply-nested-array,,https://algo.itcharge.cn/Solutions/2600-2699/flatten-deeply-nested-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2625.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%B5%8C%E5%A5%97%E6%95%B0%E7%BB%84.md,51.0%,中等,52 -2626,2600-2699,2626. 数组归约运算,数组归约运算,https://leetcode.cn/problems/array-reduce-transformation/,array-reduce-transformation,,https://algo.itcharge.cn/Solutions/2600-2699/array-reduce-transformation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2626.%20%E6%95%B0%E7%BB%84%E5%BD%92%E7%BA%A6%E8%BF%90%E7%AE%97.md,77.7%,简单,42 -2627,2600-2699,2627. 函数防抖,函数防抖,https://leetcode.cn/problems/debounce/,debounce,,https://algo.itcharge.cn/Solutions/2600-2699/debounce/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2627.%20%E5%87%BD%E6%95%B0%E9%98%B2%E6%8A%96.md,77.0%,中等,27 -2628,2600-2699,2628. 完全相等的 JSON 字符串,完全相等的 JSON 字符串,https://leetcode.cn/problems/json-deep-equal/,json-deep-equal,,https://algo.itcharge.cn/Solutions/2600-2699/json-deep-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2628.%20%E5%AE%8C%E5%85%A8%E7%9B%B8%E7%AD%89%E7%9A%84%20JSON%20%E5%AD%97%E7%AC%A6%E4%B8%B2.md,32.1%,中等,42 -2629,2600-2699,2629. 复合函数,复合函数,https://leetcode.cn/problems/function-composition/,function-composition,,https://algo.itcharge.cn/Solutions/2600-2699/function-composition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2629.%20%E5%A4%8D%E5%90%88%E5%87%BD%E6%95%B0.md,81.8%,简单,55 -2630,2600-2699,2630. 记忆函数 II,记忆函数 II,https://leetcode.cn/problems/memoize-ii/,memoize-ii,,https://algo.itcharge.cn/Solutions/2600-2699/memoize-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2630.%20%E8%AE%B0%E5%BF%86%E5%87%BD%E6%95%B0%20II.md,42.5%,困难,16 -2631,2600-2699,2631. 分组,分组,https://leetcode.cn/problems/group-by/,group-by,,https://algo.itcharge.cn/Solutions/2600-2699/group-by/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2631.%20%E5%88%86%E7%BB%84.md,79.8%,中等,31 -2632,2600-2699,2632. 柯里化,柯里化,https://leetcode.cn/problems/curry/,curry,,https://algo.itcharge.cn/Solutions/2600-2699/curry/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2632.%20%E6%9F%AF%E9%87%8C%E5%8C%96.md,81.6%,中等,35 -2633,2600-2699,2633. 将对象转换为 JSON 字符串,将对象转换为 JSON 字符串,https://leetcode.cn/problems/convert-object-to-json-string/,convert-object-to-json-string,,https://algo.itcharge.cn/Solutions/2600-2699/convert-object-to-json-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2633.%20%E5%B0%86%E5%AF%B9%E8%B1%A1%E8%BD%AC%E6%8D%A2%E4%B8%BA%20JSON%20%E5%AD%97%E7%AC%A6%E4%B8%B2.md,57.3%,中等,39 -2634,2600-2699,2634. 过滤数组中的元素,过滤数组中的元素,https://leetcode.cn/problems/filter-elements-from-array/,filter-elements-from-array,,https://algo.itcharge.cn/Solutions/2600-2699/filter-elements-from-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2634.%20%E8%BF%87%E6%BB%A4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md,69.3%,简单,49 -2635,2600-2699,2635. 转换数组中的每个元素,转换数组中的每个元素,https://leetcode.cn/problems/apply-transform-over-each-element-in-array/,apply-transform-over-each-element-in-array,,https://algo.itcharge.cn/Solutions/2600-2699/apply-transform-over-each-element-in-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2635.%20%E8%BD%AC%E6%8D%A2%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%AF%8F%E4%B8%AA%E5%85%83%E7%B4%A0.md,73.8%,简单,37 -2636,2600-2699,2636. Promise 对象池,Promise 对象池,https://leetcode.cn/problems/promise-pool/,promise-pool,,https://algo.itcharge.cn/Solutions/2600-2699/promise-pool/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2636.%20Promise%20%E5%AF%B9%E8%B1%A1%E6%B1%A0.md,61.8%,中等,26 -2637,2600-2699,2637. 有时间限制的 Promise 对象,有时间限制的 Promise 对象,https://leetcode.cn/problems/promise-time-limit/,promise-time-limit,,https://algo.itcharge.cn/Solutions/2600-2699/promise-time-limit/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2637.%20%E6%9C%89%E6%97%B6%E9%97%B4%E9%99%90%E5%88%B6%E7%9A%84%20Promise%20%E5%AF%B9%E8%B1%A1.md,62.2%,简单,25 -2638,2600-2699,2638. 统计 K-Free 子集的总数,统计 K-Free 子集的总数,https://leetcode.cn/problems/count-the-number-of-k-free-subsets/,count-the-number-of-k-free-subsets,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/2600-2699/count-the-number-of-k-free-subsets/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2638.%20%E7%BB%9F%E8%AE%A1%20K-Free%20%E5%AD%90%E9%9B%86%E7%9A%84%E6%80%BB%E6%95%B0.md,59.3%,中等,4 -2639,2600-2699,2639. 查询网格图中每一列的宽度,查询网格图中每一列的宽度,https://leetcode.cn/problems/find-the-width-of-columns-of-a-grid/,find-the-width-of-columns-of-a-grid,数组、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/find-the-width-of-columns-of-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2639.%20%E6%9F%A5%E8%AF%A2%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%AF%8F%E4%B8%80%E5%88%97%E7%9A%84%E5%AE%BD%E5%BA%A6.md,76.5%,简单,34 -2640,2600-2699,2640. 一个数组所有前缀的分数,一个数组所有前缀的分数,https://leetcode.cn/problems/find-the-score-of-all-prefixes-of-an-array/,find-the-score-of-all-prefixes-of-an-array,数组、前缀和,https://algo.itcharge.cn/Solutions/2600-2699/find-the-score-of-all-prefixes-of-an-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2640.%20%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%E6%89%80%E6%9C%89%E5%89%8D%E7%BC%80%E7%9A%84%E5%88%86%E6%95%B0.md,79.9%,中等,30 -2641,2600-2699,2641. 二叉树的堂兄弟节点 II,二叉树的堂兄弟节点 II,https://leetcode.cn/problems/cousins-in-binary-tree-ii/,cousins-in-binary-tree-ii,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/2600-2699/cousins-in-binary-tree-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2641.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%A0%82%E5%85%84%E5%BC%9F%E8%8A%82%E7%82%B9%20II.md,70.1%,中等,38 -2642,2600-2699,2642. 设计可以求最短路径的图类,设计可以求最短路径的图类,https://leetcode.cn/problems/design-graph-with-shortest-path-calculator/,design-graph-with-shortest-path-calculator,图、设计、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2600-2699/design-graph-with-shortest-path-calculator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2642.%20%E8%AE%BE%E8%AE%A1%E5%8F%AF%E4%BB%A5%E6%B1%82%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E7%9A%84%E5%9B%BE%E7%B1%BB.md,55.4%,困难,47 -2643,2600-2699,2643. 一最多的行,一最多的行,https://leetcode.cn/problems/row-with-maximum-ones/,row-with-maximum-ones,数组、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/row-with-maximum-ones/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2643.%20%E4%B8%80%E6%9C%80%E5%A4%9A%E7%9A%84%E8%A1%8C.md,80.3%,简单,40 -2644,2600-2699,2644. 找出可整除性得分最大的整数,找出可整除性得分最大的整数,https://leetcode.cn/problems/find-the-maximum-divisibility-score/,find-the-maximum-divisibility-score,数组,https://algo.itcharge.cn/Solutions/2600-2699/find-the-maximum-divisibility-score/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2644.%20%E6%89%BE%E5%87%BA%E5%8F%AF%E6%95%B4%E9%99%A4%E6%80%A7%E5%BE%97%E5%88%86%E6%9C%80%E5%A4%A7%E7%9A%84%E6%95%B4%E6%95%B0.md,50.3%,简单,29 -2645,2600-2699,2645. 构造有效字符串的最少插入数,构造有效字符串的最少插入数,https://leetcode.cn/problems/minimum-additions-to-make-valid-string/,minimum-additions-to-make-valid-string,栈、贪心、字符串、动态规划,https://algo.itcharge.cn/Solutions/2600-2699/minimum-additions-to-make-valid-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2645.%20%E6%9E%84%E9%80%A0%E6%9C%89%E6%95%88%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%8F%92%E5%85%A5%E6%95%B0.md,56.5%,中等,101 -2646,2600-2699,2646. 最小化旅行的价格总和,最小化旅行的价格总和,https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/,minimize-the-total-price-of-the-trips,树、深度优先搜索、图、数组、动态规划,https://algo.itcharge.cn/Solutions/2600-2699/minimize-the-total-price-of-the-trips/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2646.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E6%97%85%E8%A1%8C%E7%9A%84%E4%BB%B7%E6%A0%BC%E6%80%BB%E5%92%8C.md,49.8%,困难,50 -2647,2600-2699,2647. 把三角形染成红色,把三角形染成红色,https://leetcode.cn/problems/color-the-triangle-red/,color-the-triangle-red,数组、数学,https://algo.itcharge.cn/Solutions/2600-2699/color-the-triangle-red/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2647.%20%E6%8A%8A%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9F%93%E6%88%90%E7%BA%A2%E8%89%B2.md,68.6%,困难,1 -2648,2600-2699,2648. 生成斐波那契数列,生成斐波那契数列,https://leetcode.cn/problems/generate-fibonacci-sequence/,generate-fibonacci-sequence,,https://algo.itcharge.cn/Solutions/2600-2699/generate-fibonacci-sequence/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2648.%20%E7%94%9F%E6%88%90%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md,83.9%,简单,34 -2649,2600-2699,2649. 嵌套数组生成器,嵌套数组生成器,https://leetcode.cn/problems/nested-array-generator/,nested-array-generator,,https://algo.itcharge.cn/Solutions/2600-2699/nested-array-generator/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2649.%20%E5%B5%8C%E5%A5%97%E6%95%B0%E7%BB%84%E7%94%9F%E6%88%90%E5%99%A8.md,78.3%,中等,30 -2650,2600-2699,2650. 设计可取消函数,设计可取消函数,https://leetcode.cn/problems/design-cancellable-function/,design-cancellable-function,,https://algo.itcharge.cn/Solutions/2600-2699/design-cancellable-function/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2650.%20%E8%AE%BE%E8%AE%A1%E5%8F%AF%E5%8F%96%E6%B6%88%E5%87%BD%E6%95%B0.md,50.6%,困难,17 -2651,2600-2699,2651. 计算列车到站时间,计算列车到站时间,https://leetcode.cn/problems/calculate-delayed-arrival-time/,calculate-delayed-arrival-time,数学,https://algo.itcharge.cn/Solutions/2600-2699/calculate-delayed-arrival-time/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2651.%20%E8%AE%A1%E7%AE%97%E5%88%97%E8%BD%A6%E5%88%B0%E7%AB%99%E6%97%B6%E9%97%B4.md,86.8%,简单,33 -2652,2600-2699,2652. 倍数求和,倍数求和,https://leetcode.cn/problems/sum-multiples/,sum-multiples,数组、数学、数论,https://algo.itcharge.cn/Solutions/2600-2699/sum-multiples/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2652.%20%E5%80%8D%E6%95%B0%E6%B1%82%E5%92%8C.md,83.6%,简单,42 -2653,2600-2699,2653. 滑动子数组的美丽值,滑动子数组的美丽值,https://leetcode.cn/problems/sliding-subarray-beauty/,sliding-subarray-beauty,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/2600-2699/sliding-subarray-beauty/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2653.%20%E6%BB%91%E5%8A%A8%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E7%BE%8E%E4%B8%BD%E5%80%BC.md,35.0%,中等,62 -2654,2600-2699,2654. 使数组所有元素变成 1 的最少操作次数,使数组所有元素变成 1 的最少操作次数,https://leetcode.cn/problems/minimum-number-of-operations-to-make-all-array-elements-equal-to-1/,minimum-number-of-operations-to-make-all-array-elements-equal-to-1,数组、数学、数论,https://algo.itcharge.cn/Solutions/2600-2699/minimum-number-of-operations-to-make-all-array-elements-equal-to-1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2654.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%8F%98%E6%88%90%201%20%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,40.1%,中等,28 -2655,2600-2699,2655. 寻找最大长度的未覆盖区间,寻找最大长度的未覆盖区间,https://leetcode.cn/problems/find-maximal-uncovered-ranges/,find-maximal-uncovered-ranges,数组、排序,https://algo.itcharge.cn/Solutions/2600-2699/find-maximal-uncovered-ranges/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2655.%20%E5%AF%BB%E6%89%BE%E6%9C%80%E5%A4%A7%E9%95%BF%E5%BA%A6%E7%9A%84%E6%9C%AA%E8%A6%86%E7%9B%96%E5%8C%BA%E9%97%B4.md,63.2%,中等,4 -2656,2600-2699,2656. K 个元素的最大和,K 个元素的最大和,https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/,maximum-sum-with-exactly-k-elements,贪心、数组,https://algo.itcharge.cn/Solutions/2600-2699/maximum-sum-with-exactly-k-elements/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2656.%20K%20%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,84.1%,简单,49 -2657,2600-2699,2657. 找到两个数组的前缀公共数组,找到两个数组的前缀公共数组,https://leetcode.cn/problems/find-the-prefix-common-array-of-two-arrays/,find-the-prefix-common-array-of-two-arrays,数组、哈希表,https://algo.itcharge.cn/Solutions/2600-2699/find-the-prefix-common-array-of-two-arrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2657.%20%E6%89%BE%E5%88%B0%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%8D%E7%BC%80%E5%85%AC%E5%85%B1%E6%95%B0%E7%BB%84.md,83.4%,中等,35 -2658,2600-2699,2658. 网格图中鱼的最大数目,网格图中鱼的最大数目,https://leetcode.cn/problems/maximum-number-of-fish-in-a-grid/,maximum-number-of-fish-in-a-grid,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/maximum-number-of-fish-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2658.%20%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E9%B1%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md,60.4%,中等,36 -2659,2600-2699,2659. 将数组清空,将数组清空,https://leetcode.cn/problems/make-array-empty/,make-array-empty,贪心、树状数组、线段树、数组、二分查找、有序集合、排序,https://algo.itcharge.cn/Solutions/2600-2699/make-array-empty/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2659.%20%E5%B0%86%E6%95%B0%E7%BB%84%E6%B8%85%E7%A9%BA.md,38.4%,困难,28 -2660,2600-2699,2660. 保龄球游戏的获胜者,保龄球游戏的获胜者,https://leetcode.cn/problems/determine-the-winner-of-a-bowling-game/,determine-the-winner-of-a-bowling-game,数组、模拟,https://algo.itcharge.cn/Solutions/2600-2699/determine-the-winner-of-a-bowling-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2660.%20%E4%BF%9D%E9%BE%84%E7%90%83%E6%B8%B8%E6%88%8F%E7%9A%84%E8%8E%B7%E8%83%9C%E8%80%85.md,33.5%,简单,39 -2661,2600-2699,2661. 找出叠涂元素,找出叠涂元素,https://leetcode.cn/problems/first-completely-painted-row-or-column/,first-completely-painted-row-or-column,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/first-completely-painted-row-or-column/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2661.%20%E6%89%BE%E5%87%BA%E5%8F%A0%E6%B6%82%E5%85%83%E7%B4%A0.md,51.2%,中等,46 -2662,2600-2699,2662. 前往目标的最小代价,前往目标的最小代价,https://leetcode.cn/problems/minimum-cost-of-a-path-with-special-roads/,minimum-cost-of-a-path-with-special-roads,图、数组、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2600-2699/minimum-cost-of-a-path-with-special-roads/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2662.%20%E5%89%8D%E5%BE%80%E7%9B%AE%E6%A0%87%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7.md,36.4%,中等,52 -2663,2600-2699,2663. 字典序最小的美丽字符串,字典序最小的美丽字符串,https://leetcode.cn/problems/lexicographically-smallest-beautiful-string/,lexicographically-smallest-beautiful-string,贪心、字符串,https://algo.itcharge.cn/Solutions/2600-2699/lexicographically-smallest-beautiful-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2663.%20%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E7%9A%84%E7%BE%8E%E4%B8%BD%E5%AD%97%E7%AC%A6%E4%B8%B2.md,45.7%,困难,34 -2664,2600-2699,2664. 巡逻的骑士,巡逻的骑士,https://leetcode.cn/problems/the-knights-tour/,the-knights-tour,递归、数组、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/the-knights-tour/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2664.%20%E5%B7%A1%E9%80%BB%E7%9A%84%E9%AA%91%E5%A3%AB.md,63.6%,中等,6 -2665,2600-2699,2665. 计数器 II,计数器 II,https://leetcode.cn/problems/counter-ii/,counter-ii,,https://algo.itcharge.cn/Solutions/2600-2699/counter-ii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2665.%20%E8%AE%A1%E6%95%B0%E5%99%A8%20II.md,63.8%,简单,38 -2666,2600-2699,2666. 只允许一次函数调用,只允许一次函数调用,https://leetcode.cn/problems/allow-one-function-call/,allow-one-function-call,,https://algo.itcharge.cn/Solutions/2600-2699/allow-one-function-call/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2666.%20%E5%8F%AA%E5%85%81%E8%AE%B8%E4%B8%80%E6%AC%A1%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8.md,82.6%,简单,37 -2667,2600-2699,2667. 创建 Hello World 函数,创建 Hello World 函数,https://leetcode.cn/problems/create-hello-world-function/,create-hello-world-function,,https://algo.itcharge.cn/Solutions/2600-2699/create-hello-world-function/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2667.%20%E5%88%9B%E5%BB%BA%20Hello%20World%20%E5%87%BD%E6%95%B0.md,87.1%,简单,29 -2668,2600-2699,2668. 查询员工当前薪水,查询员工当前薪水,https://leetcode.cn/problems/find-latest-salaries/,find-latest-salaries,数据库,https://algo.itcharge.cn/Solutions/2600-2699/find-latest-salaries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2668.%20%E6%9F%A5%E8%AF%A2%E5%91%98%E5%B7%A5%E5%BD%93%E5%89%8D%E8%96%AA%E6%B0%B4.md,68.0%,简单,4 -2669,2600-2699,2669. 统计 Spotify 排行榜上艺术家出现次数,统计 Spotify 排行榜上艺术家出现次数,https://leetcode.cn/problems/count-artist-occurrences-on-spotify-ranking-list/,count-artist-occurrences-on-spotify-ranking-list,数据库,https://algo.itcharge.cn/Solutions/2600-2699/count-artist-occurrences-on-spotify-ranking-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2669.%20%E7%BB%9F%E8%AE%A1%20Spotify%20%E6%8E%92%E8%A1%8C%E6%A6%9C%E4%B8%8A%E8%89%BA%E6%9C%AF%E5%AE%B6%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0.md,73.9%,简单,2 -2670,2600-2699,2670. 找出不同元素数目差数组,找出不同元素数目差数组,https://leetcode.cn/problems/find-the-distinct-difference-array/,find-the-distinct-difference-array,数组、哈希表,https://algo.itcharge.cn/Solutions/2600-2699/find-the-distinct-difference-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2670.%20%E6%89%BE%E5%87%BA%E4%B8%8D%E5%90%8C%E5%85%83%E7%B4%A0%E6%95%B0%E7%9B%AE%E5%B7%AE%E6%95%B0%E7%BB%84.md,75.7%,简单,50 -2671,2600-2699,2671. 频率跟踪器,频率跟踪器,https://leetcode.cn/problems/frequency-tracker/,frequency-tracker,设计、哈希表,https://algo.itcharge.cn/Solutions/2600-2699/frequency-tracker/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2671.%20%E9%A2%91%E7%8E%87%E8%B7%9F%E8%B8%AA%E5%99%A8.md,32.4%,中等,52 -2672,2600-2699,2672. 有相同颜色的相邻元素数目,有相同颜色的相邻元素数目,https://leetcode.cn/problems/number-of-adjacent-elements-with-the-same-color/,number-of-adjacent-elements-with-the-same-color,数组,https://algo.itcharge.cn/Solutions/2600-2699/number-of-adjacent-elements-with-the-same-color/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2672.%20%E6%9C%89%E7%9B%B8%E5%90%8C%E9%A2%9C%E8%89%B2%E7%9A%84%E7%9B%B8%E9%82%BB%E5%85%83%E7%B4%A0%E6%95%B0%E7%9B%AE.md,58.3%,中等,48 -2673,2600-2699,2673. 使二叉树所有路径值相等的最小代价,使二叉树所有路径值相等的最小代价,https://leetcode.cn/problems/make-costs-of-paths-equal-in-a-binary-tree/,make-costs-of-paths-equal-in-a-binary-tree,贪心、树、数组、动态规划、二叉树,https://algo.itcharge.cn/Solutions/2600-2699/make-costs-of-paths-equal-in-a-binary-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2673.%20%E4%BD%BF%E4%BA%8C%E5%8F%89%E6%A0%91%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84%E5%80%BC%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7.md,66.2%,中等,67 -2674,2600-2699,2674. 拆分循环链表,拆分循环链表,https://leetcode.cn/problems/split-a-circular-linked-list/,split-a-circular-linked-list,,https://algo.itcharge.cn/Solutions/2600-2699/split-a-circular-linked-list/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2674.%20%E6%8B%86%E5%88%86%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.md,82.8%,中等,5 -2675,2600-2699,2675. 将对象数组转换为矩阵,将对象数组转换为矩阵,https://leetcode.cn/problems/array-of-objects-to-matrix/,array-of-objects-to-matrix,,https://algo.itcharge.cn/Solutions/2600-2699/array-of-objects-to-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2675.%20%E5%B0%86%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E7%9F%A9%E9%98%B5.md,60.9%,中等,21 -2676,2600-2699,2676. 节流,节流,https://leetcode.cn/problems/throttle/,throttle,,https://algo.itcharge.cn/Solutions/2600-2699/throttle/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2676.%20%E8%8A%82%E6%B5%81.md,43.6%,中等,24 -2677,2600-2699,2677. 分块数组,分块数组,https://leetcode.cn/problems/chunk-array/,chunk-array,,https://algo.itcharge.cn/Solutions/2600-2699/chunk-array/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2677.%20%E5%88%86%E5%9D%97%E6%95%B0%E7%BB%84.md,72.8%,简单,31 -2678,2600-2699,2678. 老人的数目,老人的数目,https://leetcode.cn/problems/number-of-senior-citizens/,number-of-senior-citizens,数组、字符串,https://algo.itcharge.cn/Solutions/2600-2699/number-of-senior-citizens/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2678.%20%E8%80%81%E4%BA%BA%E7%9A%84%E6%95%B0%E7%9B%AE.md,83.8%,简单,35 -2679,2600-2699,2679. 矩阵中的和,矩阵中的和,https://leetcode.cn/problems/sum-in-a-matrix/,sum-in-a-matrix,数组、矩阵、排序、模拟、堆(优先队列),https://algo.itcharge.cn/Solutions/2600-2699/sum-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2679.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E5%92%8C.md,76.2%,中等,33 -2680,2600-2699,2680. 最大或值,最大或值,https://leetcode.cn/problems/maximum-or/,maximum-or,贪心、位运算、数组、前缀和,https://algo.itcharge.cn/Solutions/2600-2699/maximum-or/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2680.%20%E6%9C%80%E5%A4%A7%E6%88%96%E5%80%BC.md,42.6%,中等,32 -2681,2600-2699,2681. 英雄的力量,英雄的力量,https://leetcode.cn/problems/power-of-heroes/,power-of-heroes,数组、数学、前缀和、排序,https://algo.itcharge.cn/Solutions/2600-2699/power-of-heroes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2681.%20%E8%8B%B1%E9%9B%84%E7%9A%84%E5%8A%9B%E9%87%8F.md,35.8%,困难,24 -2682,2600-2699,2682. 找出转圈游戏输家,找出转圈游戏输家,https://leetcode.cn/problems/find-the-losers-of-the-circular-game/,find-the-losers-of-the-circular-game,数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/2600-2699/find-the-losers-of-the-circular-game/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2682.%20%E6%89%BE%E5%87%BA%E8%BD%AC%E5%9C%88%E6%B8%B8%E6%88%8F%E8%BE%93%E5%AE%B6.md,54.5%,简单,36 -2683,2600-2699,2683. 相邻值的按位异或,相邻值的按位异或,https://leetcode.cn/problems/neighboring-bitwise-xor/,neighboring-bitwise-xor,位运算、数组,https://algo.itcharge.cn/Solutions/2600-2699/neighboring-bitwise-xor/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2683.%20%E7%9B%B8%E9%82%BB%E5%80%BC%E7%9A%84%E6%8C%89%E4%BD%8D%E5%BC%82%E6%88%96.md,69.0%,中等,44 -2684,2600-2699,2684. 矩阵中移动的最大次数,矩阵中移动的最大次数,https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/,maximum-number-of-moves-in-a-grid,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/2600-2699/maximum-number-of-moves-in-a-grid/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2684.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%A7%BB%E5%8A%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E6%AC%A1%E6%95%B0.md,40.9%,中等,63 -2685,2600-2699,2685. 统计完全连通分量的数量,统计完全连通分量的数量,https://leetcode.cn/problems/count-the-number-of-complete-components/,count-the-number-of-complete-components,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/2600-2699/count-the-number-of-complete-components/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2685.%20%E7%BB%9F%E8%AE%A1%E5%AE%8C%E5%85%A8%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E9%87%8F.md,67.3%,中等,49 -2686,2600-2699,2686. 即时食物配送 III,即时食物配送 III,https://leetcode.cn/problems/immediate-food-delivery-iii/,immediate-food-delivery-iii,数据库,https://algo.itcharge.cn/Solutions/2600-2699/immediate-food-delivery-iii/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2686.%20%E5%8D%B3%E6%97%B6%E9%A3%9F%E7%89%A9%E9%85%8D%E9%80%81%20III.md,67.2%,中等,5 -2687,2600-2699,2687. 自行车的最后使用时间,自行车的最后使用时间,https://leetcode.cn/problems/bikes-last-time-used/,bikes-last-time-used,数据库,https://algo.itcharge.cn/Solutions/2600-2699/bikes-last-time-used/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2687.%20%E8%87%AA%E8%A1%8C%E8%BD%A6%E7%9A%84%E6%9C%80%E5%90%8E%E4%BD%BF%E7%94%A8%E6%97%B6%E9%97%B4.md,85.8%,简单,2 -2688,2600-2699,2688. 查找活跃用户,查找活跃用户,https://leetcode.cn/problems/find-active-users/,find-active-users,数据库,https://algo.itcharge.cn/Solutions/2600-2699/find-active-users/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2688.%20%E6%9F%A5%E6%89%BE%E6%B4%BB%E8%B7%83%E7%94%A8%E6%88%B7.md,44.6%,中等,7 -2689,2600-2699,2689. 从 Rope 树中提取第 K 个字符,从 Rope 树中提取第 K 个字符,https://leetcode.cn/problems/extract-kth-character-from-the-rope-tree/,extract-kth-character-from-the-rope-tree,树、深度优先搜索,https://algo.itcharge.cn/Solutions/2600-2699/extract-kth-character-from-the-rope-tree/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2689.%20%E4%BB%8E%20Rope%20%E6%A0%91%E4%B8%AD%E6%8F%90%E5%8F%96%E7%AC%AC%20K%20%E4%B8%AA%E5%AD%97%E7%AC%A6.md,78.6%,简单,3 -2690,2600-2699,2690. 无穷方法对象,无穷方法对象,https://leetcode.cn/problems/infinite-method-object/,infinite-method-object,,https://algo.itcharge.cn/Solutions/2600-2699/infinite-method-object/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2690.%20%E6%97%A0%E7%A9%B7%E6%96%B9%E6%B3%95%E5%AF%B9%E8%B1%A1.md,95.0%,简单,2 -2691,2600-2699,2691. 不可变辅助工具,不可变辅助工具,https://leetcode.cn/problems/immutability-helper/,immutability-helper,,https://algo.itcharge.cn/Solutions/2600-2699/immutability-helper/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2691.%20%E4%B8%8D%E5%8F%AF%E5%8F%98%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7.md,27.0%,困难,2 -2692,2600-2699,2692. 使对象不可变,使对象不可变,https://leetcode.cn/problems/make-object-immutable/,make-object-immutable,,https://algo.itcharge.cn/Solutions/2600-2699/make-object-immutable/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2692.%20%E4%BD%BF%E5%AF%B9%E8%B1%A1%E4%B8%8D%E5%8F%AF%E5%8F%98.md,48.0%,中等,3 -2693,2600-2699,2693. 使用自定义上下文调用函数,使用自定义上下文调用函数,https://leetcode.cn/problems/call-function-with-custom-context/,call-function-with-custom-context,,https://algo.itcharge.cn/Solutions/2600-2699/call-function-with-custom-context/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2693.%20%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%8A%E4%B8%8B%E6%96%87%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0.md,73.0%,中等,16 -2694,2600-2699,2694. 事件发射器,事件发射器,https://leetcode.cn/problems/event-emitter/,event-emitter,,https://algo.itcharge.cn/Solutions/2600-2699/event-emitter/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2694.%20%E4%BA%8B%E4%BB%B6%E5%8F%91%E5%B0%84%E5%99%A8.md,64.7%,中等,8 -2695,2600-2699,2695. 包装数组,包装数组,https://leetcode.cn/problems/array-wrapper/,array-wrapper,,https://algo.itcharge.cn/Solutions/2600-2699/array-wrapper/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2695.%20%E5%8C%85%E8%A3%85%E6%95%B0%E7%BB%84.md,72.2%,简单,12 -2696,2600-2699,2696. 删除子串后的字符串最小长度,删除子串后的字符串最小长度,https://leetcode.cn/problems/minimum-string-length-after-removing-substrings/,minimum-string-length-after-removing-substrings,栈、字符串、模拟,https://algo.itcharge.cn/Solutions/2600-2699/minimum-string-length-after-removing-substrings/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2696.%20%E5%88%A0%E9%99%A4%E5%AD%90%E4%B8%B2%E5%90%8E%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9C%80%E5%B0%8F%E9%95%BF%E5%BA%A6.md,72.5%,简单,67 -2697,2600-2699,2697. 字典序最小回文串,字典序最小回文串,https://leetcode.cn/problems/lexicographically-smallest-palindrome/,lexicographically-smallest-palindrome,双指针、字符串,https://algo.itcharge.cn/Solutions/2600-2699/lexicographically-smallest-palindrome/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2697.%20%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E5%9B%9E%E6%96%87%E4%B8%B2.md,83.1%,简单,67 -2698,2600-2699,2698. 求一个整数的惩罚数,求一个整数的惩罚数,https://leetcode.cn/problems/find-the-punishment-number-of-an-integer/,find-the-punishment-number-of-an-integer,递归、数学,https://algo.itcharge.cn/Solutions/2600-2699/find-the-punishment-number-of-an-integer/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2698.%20%E6%B1%82%E4%B8%80%E4%B8%AA%E6%95%B4%E6%95%B0%E7%9A%84%E6%83%A9%E7%BD%9A%E6%95%B0.md,66.6%,中等,76 -2699,2600-2699,2699. 修改图中的边权,修改图中的边权,https://leetcode.cn/problems/modify-graph-edge-weights/,modify-graph-edge-weights,图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2600-2699/modify-graph-edge-weights/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2699.%20%E4%BF%AE%E6%94%B9%E5%9B%BE%E4%B8%AD%E7%9A%84%E8%BE%B9%E6%9D%83.md,52.5%,困难,58 -2700,2700-2799,2700. 两个对象之间的差异,两个对象之间的差异,https://leetcode.cn/problems/differences-between-two-objects/,differences-between-two-objects,,https://algo.itcharge.cn/Solutions/2700-2799/differences-between-two-objects/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2700.%20%E4%B8%A4%E4%B8%AA%E5%AF%B9%E8%B1%A1%E4%B9%8B%E9%97%B4%E7%9A%84%E5%B7%AE%E5%BC%82.md,59.3%,中等,20 -2701,2700-2799,2701. 连续递增交易,连续递增交易,https://leetcode.cn/problems/consecutive-transactions-with-increasing-amounts/,consecutive-transactions-with-increasing-amounts,数据库,https://algo.itcharge.cn/Solutions/2700-2799/consecutive-transactions-with-increasing-amounts/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2701.%20%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E4%BA%A4%E6%98%93.md,32.2%,困难,6 -2702,2700-2799,2702. 使数字变为非正数的最小操作次数,使数字变为非正数的最小操作次数,https://leetcode.cn/problems/minimum-operations-to-make-numbers-non-positive/,minimum-operations-to-make-numbers-non-positive,数组、二分查找,https://algo.itcharge.cn/Solutions/2700-2799/minimum-operations-to-make-numbers-non-positive/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2702.%20%E4%BD%BF%E6%95%B0%E5%AD%97%E5%8F%98%E4%B8%BA%E9%9D%9E%E6%AD%A3%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md,48.8%,困难,3 -2703,2700-2799,2703. 返回传递的参数的长度,返回传递的参数的长度,https://leetcode.cn/problems/return-length-of-arguments-passed/,return-length-of-arguments-passed,,https://algo.itcharge.cn/Solutions/2700-2799/return-length-of-arguments-passed/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2703.%20%E8%BF%94%E5%9B%9E%E4%BC%A0%E9%80%92%E7%9A%84%E5%8F%82%E6%95%B0%E7%9A%84%E9%95%BF%E5%BA%A6.md,93.7%,简单,12 -2704,2700-2799,2704. 相等还是不相等,相等还是不相等,https://leetcode.cn/problems/to-be-or-not-to-be/,to-be-or-not-to-be,,https://algo.itcharge.cn/Solutions/2700-2799/to-be-or-not-to-be/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2704.%20%E7%9B%B8%E7%AD%89%E8%BF%98%E6%98%AF%E4%B8%8D%E7%9B%B8%E7%AD%89.md,52.7%,简单,16 -2705,2700-2799,2705. 精简对象,精简对象,https://leetcode.cn/problems/compact-object/,compact-object,,https://algo.itcharge.cn/Solutions/2700-2799/compact-object/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2705.%20%E7%B2%BE%E7%AE%80%E5%AF%B9%E8%B1%A1.md,63.4%,中等,9 -2706,2700-2799,2706. 购买两块巧克力,购买两块巧克力,https://leetcode.cn/problems/buy-two-chocolates/,buy-two-chocolates,数组、排序,https://algo.itcharge.cn/Solutions/2700-2799/buy-two-chocolates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2706.%20%E8%B4%AD%E4%B9%B0%E4%B8%A4%E5%9D%97%E5%B7%A7%E5%85%8B%E5%8A%9B.md,80.0%,简单,32 -2707,2700-2799,2707. 字符串中的额外字符,字符串中的额外字符,https://leetcode.cn/problems/extra-characters-in-a-string/,extra-characters-in-a-string,字典树、数组、哈希表、字符串、动态规划,https://algo.itcharge.cn/Solutions/2700-2799/extra-characters-in-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2707.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E9%A2%9D%E5%A4%96%E5%AD%97%E7%AC%A6.md,41.9%,中等,38 -2708,2700-2799,2708. 一个小组的最大实力值,一个小组的最大实力值,https://leetcode.cn/problems/maximum-strength-of-a-group/,maximum-strength-of-a-group,贪心、递归、数组、排序,https://algo.itcharge.cn/Solutions/2700-2799/maximum-strength-of-a-group/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2708.%20%E4%B8%80%E4%B8%AA%E5%B0%8F%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AE%9E%E5%8A%9B%E5%80%BC.md,30.2%,中等,52 -2709,2700-2799,2709. 最大公约数遍历,最大公约数遍历,https://leetcode.cn/problems/greatest-common-divisor-traversal/,greatest-common-divisor-traversal,并查集、数组、数学、数论,https://algo.itcharge.cn/Solutions/2700-2799/greatest-common-divisor-traversal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2709.%20%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0%E9%81%8D%E5%8E%86.md,24.1%,困难,34 -2710,2700-2799,2710. 移除字符串中的尾随零,移除字符串中的尾随零,https://leetcode.cn/problems/remove-trailing-zeros-from-a-string/,remove-trailing-zeros-from-a-string,字符串,https://algo.itcharge.cn/Solutions/2700-2799/remove-trailing-zeros-from-a-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2710.%20%E7%A7%BB%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%B0%BE%E9%9A%8F%E9%9B%B6.md,83.5%,简单,59 -2711,2700-2799,2711. 对角线上不同值的数量差,对角线上不同值的数量差,https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals/,difference-of-number-of-distinct-values-on-diagonals,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/2700-2799/difference-of-number-of-distinct-values-on-diagonals/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2711.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E4%B8%8D%E5%90%8C%E5%80%BC%E7%9A%84%E6%95%B0%E9%87%8F%E5%B7%AE.md,72.1%,中等,56 -2712,2700-2799,2712. 使所有字符相等的最小成本,使所有字符相等的最小成本,https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal/,minimum-cost-to-make-all-characters-equal,贪心、字符串、动态规划,https://algo.itcharge.cn/Solutions/2700-2799/minimum-cost-to-make-all-characters-equal/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2712.%20%E4%BD%BF%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md,56.1%,中等,58 -2713,2700-2799,2713. 矩阵中严格递增的单元格数,矩阵中严格递增的单元格数,https://leetcode.cn/problems/maximum-strictly-increasing-cells-in-a-matrix/,maximum-strictly-increasing-cells-in-a-matrix,记忆化搜索、数组、二分查找、动态规划、矩阵、排序,https://algo.itcharge.cn/Solutions/2700-2799/maximum-strictly-increasing-cells-in-a-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2713.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E4%B8%A5%E6%A0%BC%E9%80%92%E5%A2%9E%E7%9A%84%E5%8D%95%E5%85%83%E6%A0%BC%E6%95%B0.md,35.0%,困难,28 -2714,2700-2799,2714. 找到最短路径的 K 次跨越,找到最短路径的 K 次跨越,https://leetcode.cn/problems/find-shortest-path-with-k-hops/,find-shortest-path-with-k-hops,图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2700-2799/find-shortest-path-with-k-hops/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2714.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E7%9A%84%20K%20%E6%AC%A1%E8%B7%A8%E8%B6%8A.md,72.2%,困难,4 -2715,2700-2799,2715. 执行可取消的延迟函数,执行可取消的延迟函数,https://leetcode.cn/problems/execute-cancellable-function-with-delay/,execute-cancellable-function-with-delay,,https://algo.itcharge.cn/Solutions/2700-2799/execute-cancellable-function-with-delay/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2715.%20%E6%89%A7%E8%A1%8C%E5%8F%AF%E5%8F%96%E6%B6%88%E7%9A%84%E5%BB%B6%E8%BF%9F%E5%87%BD%E6%95%B0.md,81.6%,简单,6 -2716,2700-2799,2716. 最小化字符串长度,最小化字符串长度,https://leetcode.cn/problems/minimize-string-length/,minimize-string-length,哈希表、字符串,https://algo.itcharge.cn/Solutions/2700-2799/minimize-string-length/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2716.%20%E6%9C%80%E5%B0%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%95%BF%E5%BA%A6.md,72.2%,简单,41 -2717,2700-2799,2717. 半有序排列,半有序排列,https://leetcode.cn/problems/semi-ordered-permutation/,semi-ordered-permutation,数组、模拟,https://algo.itcharge.cn/Solutions/2700-2799/semi-ordered-permutation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2717.%20%E5%8D%8A%E6%9C%89%E5%BA%8F%E6%8E%92%E5%88%97.md,72.8%,简单,35 -2718,2700-2799,2718. 查询后矩阵的和,查询后矩阵的和,https://leetcode.cn/problems/sum-of-matrix-after-queries/,sum-of-matrix-after-queries,数组、哈希表,https://algo.itcharge.cn/Solutions/2700-2799/sum-of-matrix-after-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2718.%20%E6%9F%A5%E8%AF%A2%E5%90%8E%E7%9F%A9%E9%98%B5%E7%9A%84%E5%92%8C.md,32.9%,中等,88 -2719,2700-2799,2719. 统计整数数目,统计整数数目,https://leetcode.cn/problems/count-of-integers/,count-of-integers,数学、字符串、动态规划,https://algo.itcharge.cn/Solutions/2700-2799/count-of-integers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2719.%20%E7%BB%9F%E8%AE%A1%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md,47.3%,困难,36 -2720,2700-2799,2720. 受欢迎度百分比,受欢迎度百分比,https://leetcode.cn/problems/popularity-percentage/,popularity-percentage,数据库,https://algo.itcharge.cn/Solutions/2700-2799/popularity-percentage/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2720.%20%E5%8F%97%E6%AC%A2%E8%BF%8E%E5%BA%A6%E7%99%BE%E5%88%86%E6%AF%94.md,61.2%,困难,3 -2721,2700-2799,2721. 并行执行异步函数,并行执行异步函数,https://leetcode.cn/problems/execute-asynchronous-functions-in-parallel/,execute-asynchronous-functions-in-parallel,,https://algo.itcharge.cn/Solutions/2700-2799/execute-asynchronous-functions-in-parallel/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2721.%20%E5%B9%B6%E8%A1%8C%E6%89%A7%E8%A1%8C%E5%BC%82%E6%AD%A5%E5%87%BD%E6%95%B0.md,58.6%,中等,8 -2722,2700-2799,2722. 根据 ID 合并两个数组,根据 ID 合并两个数组,https://leetcode.cn/problems/join-two-arrays-by-id/,join-two-arrays-by-id,,https://algo.itcharge.cn/Solutions/2700-2799/join-two-arrays-by-id/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2722.%20%E6%A0%B9%E6%8D%AE%20ID%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84.md,47.4%,中等,6 -2723,2700-2799,2723. 添加两个 Promise 对象,添加两个 Promise 对象,https://leetcode.cn/problems/add-two-promises/,add-two-promises,,https://algo.itcharge.cn/Solutions/2700-2799/add-two-promises/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2723.%20%E6%B7%BB%E5%8A%A0%E4%B8%A4%E4%B8%AA%20Promise%20%E5%AF%B9%E8%B1%A1.md,86.5%,简单,8 -2724,2700-2799,2724. 排序方式,排序方式,https://leetcode.cn/problems/sort-by/,sort-by,,https://algo.itcharge.cn/Solutions/2700-2799/sort-by/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2724.%20%E6%8E%92%E5%BA%8F%E6%96%B9%E5%BC%8F.md,81.2%,简单,5 -2725,2700-2799,2725. 间隔取消,间隔取消,https://leetcode.cn/problems/interval-cancellation/,interval-cancellation,,https://algo.itcharge.cn/Solutions/2700-2799/interval-cancellation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2725.%20%E9%97%B4%E9%9A%94%E5%8F%96%E6%B6%88.md,75.0%,简单,9 -2726,2700-2799,2726. 使用方法链的计算器,使用方法链的计算器,https://leetcode.cn/problems/calculator-with-method-chaining/,calculator-with-method-chaining,,https://algo.itcharge.cn/Solutions/2700-2799/calculator-with-method-chaining/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2726.%20%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%E9%93%BE%E7%9A%84%E8%AE%A1%E7%AE%97%E5%99%A8.md,60.9%,简单,8 -2727,2700-2799,2727. 判断对象是否为空,判断对象是否为空,https://leetcode.cn/problems/is-object-empty/,is-object-empty,,https://algo.itcharge.cn/Solutions/2700-2799/is-object-empty/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2727.%20%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA.md,71.1%,简单,12 -2728,2700-2799,2728. 计算一个环形街道上的房屋数量,计算一个环形街道上的房屋数量,https://leetcode.cn/problems/count-houses-in-a-circular-street/,count-houses-in-a-circular-street,数组,https://algo.itcharge.cn/Solutions/2700-2799/count-houses-in-a-circular-street/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2728.%20%E8%AE%A1%E7%AE%97%E4%B8%80%E4%B8%AA%E7%8E%AF%E5%BD%A2%E8%A1%97%E9%81%93%E4%B8%8A%E7%9A%84%E6%88%BF%E5%B1%8B%E6%95%B0%E9%87%8F.md,89.9%,简单,5 -2729,2700-2799,2729. 判断一个数是否迷人,判断一个数是否迷人,https://leetcode.cn/problems/check-if-the-number-is-fascinating/,check-if-the-number-is-fascinating,哈希表、数学,https://algo.itcharge.cn/Solutions/2700-2799/check-if-the-number-is-fascinating/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2729.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%95%B0%E6%98%AF%E5%90%A6%E8%BF%B7%E4%BA%BA.md,62.5%,简单,37 -2730,2700-2799,2730. 找到最长的半重复子字符串,找到最长的半重复子字符串,https://leetcode.cn/problems/find-the-longest-semi-repetitive-substring/,find-the-longest-semi-repetitive-substring,字符串、滑动窗口,https://algo.itcharge.cn/Solutions/2700-2799/find-the-longest-semi-repetitive-substring/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2730.%20%E6%89%BE%E5%88%B0%E6%9C%80%E9%95%BF%E7%9A%84%E5%8D%8A%E9%87%8D%E5%A4%8D%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,45.6%,中等,29 -2731,2700-2799,2731. 移动机器人,移动机器人,https://leetcode.cn/problems/movement-of-robots/,movement-of-robots,脑筋急转弯、数组、前缀和、排序,https://algo.itcharge.cn/Solutions/2700-2799/movement-of-robots/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2731.%20%E7%A7%BB%E5%8A%A8%E6%9C%BA%E5%99%A8%E4%BA%BA.md,33.7%,中等,29 -2732,2700-2799,2732. 找到矩阵中的好子集,找到矩阵中的好子集,https://leetcode.cn/problems/find-a-good-subset-of-the-matrix/,find-a-good-subset-of-the-matrix,贪心、位运算、数组、矩阵,https://algo.itcharge.cn/Solutions/2700-2799/find-a-good-subset-of-the-matrix/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2732.%20%E6%89%BE%E5%88%B0%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E5%A5%BD%E5%AD%90%E9%9B%86.md,56.1%,困难,15 -2733,2700-2799,2733. 既不是最小值也不是最大值,既不是最小值也不是最大值,https://leetcode.cn/problems/neither-minimum-nor-maximum/,neither-minimum-nor-maximum,数组、排序,https://algo.itcharge.cn/Solutions/2700-2799/neither-minimum-nor-maximum/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2733.%20%E6%97%A2%E4%B8%8D%E6%98%AF%E6%9C%80%E5%B0%8F%E5%80%BC%E4%B9%9F%E4%B8%8D%E6%98%AF%E6%9C%80%E5%A4%A7%E5%80%BC.md,80.2%,简单,40 -2734,2700-2799,2734. 执行子串操作后的字典序最小字符串,执行子串操作后的字典序最小字符串,https://leetcode.cn/problems/lexicographically-smallest-string-after-substring-operation/,lexicographically-smallest-string-after-substring-operation,贪心、字符串,https://algo.itcharge.cn/Solutions/2700-2799/lexicographically-smallest-string-after-substring-operation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2734.%20%E6%89%A7%E8%A1%8C%E5%AD%90%E4%B8%B2%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E5%AD%97%E7%AC%A6%E4%B8%B2.md,32.8%,中等,42 -2735,2700-2799,2735. 收集巧克力,收集巧克力,https://leetcode.cn/problems/collecting-chocolates/,collecting-chocolates,数组、枚举,https://algo.itcharge.cn/Solutions/2700-2799/collecting-chocolates/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2735.%20%E6%94%B6%E9%9B%86%E5%B7%A7%E5%85%8B%E5%8A%9B.md,43.1%,中等,45 -2736,2700-2799,2736. 最大和查询,最大和查询,https://leetcode.cn/problems/maximum-sum-queries/,maximum-sum-queries,栈、树状数组、线段树、数组、二分查找、排序、单调栈,https://algo.itcharge.cn/Solutions/2700-2799/maximum-sum-queries/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2736.%20%E6%9C%80%E5%A4%A7%E5%92%8C%E6%9F%A5%E8%AF%A2.md,38.1%,困难,46 -2737,2700-2799,2737. 找到最近的标记节点,找到最近的标记节点,https://leetcode.cn/problems/find-the-closest-marked-node/,find-the-closest-marked-node,图、数组、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/2700-2799/find-the-closest-marked-node/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2737.%20%E6%89%BE%E5%88%B0%E6%9C%80%E8%BF%91%E7%9A%84%E6%A0%87%E8%AE%B0%E8%8A%82%E7%82%B9.md,46.9%,中等,3 -2738,2700-2799,2738. 统计文本中单词的出现次数,统计文本中单词的出现次数,https://leetcode.cn/problems/count-occurrences-in-text/,count-occurrences-in-text,数据库,https://algo.itcharge.cn/Solutions/2700-2799/count-occurrences-in-text/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2738.%20%E7%BB%9F%E8%AE%A1%E6%96%87%E6%9C%AC%E4%B8%AD%E5%8D%95%E8%AF%8D%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0.md,29.9%,中等,1 -2739,2700-2799,2739. 总行驶距离,总行驶距离,https://leetcode.cn/problems/total-distance-traveled/,total-distance-traveled,数学、模拟,https://algo.itcharge.cn/Solutions/2700-2799/total-distance-traveled/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2739.%20%E6%80%BB%E8%A1%8C%E9%A9%B6%E8%B7%9D%E7%A6%BB.md,52.8%,简单,43 -2740,2700-2799,2740. 找出分区值,找出分区值,https://leetcode.cn/problems/find-the-value-of-the-partition/,find-the-value-of-the-partition,数组、排序,https://algo.itcharge.cn/Solutions/2700-2799/find-the-value-of-the-partition/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2740.%20%E6%89%BE%E5%87%BA%E5%88%86%E5%8C%BA%E5%80%BC.md,74.3%,中等,39 -2741,2700-2799,2741. 特别的排列,特别的排列,https://leetcode.cn/problems/special-permutations/,special-permutations,位运算、数组、状态压缩,https://algo.itcharge.cn/Solutions/2700-2799/special-permutations/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2741.%20%E7%89%B9%E5%88%AB%E7%9A%84%E6%8E%92%E5%88%97.md,35.1%,中等,47 -2742,2700-2799,2742. 给墙壁刷油漆,给墙壁刷油漆,https://leetcode.cn/problems/painting-the-walls/,painting-the-walls,数组、动态规划,https://algo.itcharge.cn/Solutions/2700-2799/painting-the-walls/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2742.%20%E7%BB%99%E5%A2%99%E5%A3%81%E5%88%B7%E6%B2%B9%E6%BC%86.md,34.2%,困难,24 -2743,2700-2799,2743. ,,https://leetcode.cn/problems/count-substrings-without-repeating-character/,count-substrings-without-repeating-character,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/2700-2799/count-substrings-without-repeating-character/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2743.%20.md,82.1%,中等,1 -2744,2700-2799,2744. 最大字符串配对数目,最大字符串配对数目,https://leetcode.cn/problems/find-maximum-number-of-string-pairs/,find-maximum-number-of-string-pairs,数组、哈希表、字符串、模拟,https://algo.itcharge.cn/Solutions/2700-2799/find-maximum-number-of-string-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2744.%20%E6%9C%80%E5%A4%A7%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%85%8D%E5%AF%B9%E6%95%B0%E7%9B%AE.md,85.5%,简单,26 -2745,2700-2799,2745. 构造最长的新字符串,构造最长的新字符串,https://leetcode.cn/problems/construct-the-longest-new-string/,construct-the-longest-new-string,贪心、脑筋急转弯、数学,https://algo.itcharge.cn/Solutions/2700-2799/construct-the-longest-new-string/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2745.%20%E6%9E%84%E9%80%A0%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%B0%E5%AD%97%E7%AC%A6%E4%B8%B2.md,55.8%,中等,29 -2746,2700-2799,2746. 字符串连接删减字母,字符串连接删减字母,https://leetcode.cn/problems/decremental-string-concatenation/,decremental-string-concatenation,数组、字符串,https://algo.itcharge.cn/Solutions/2700-2799/decremental-string-concatenation/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2746.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BF%9E%E6%8E%A5%E5%88%A0%E5%87%8F%E5%AD%97%E6%AF%8D.md,34.7%,中等,27 -2747,2700-2799,2747. 统计没有收到请求的服务器数目,统计没有收到请求的服务器数目,https://leetcode.cn/problems/count-zero-request-servers/,count-zero-request-servers,数组、哈希表、排序、滑动窗口,https://algo.itcharge.cn/Solutions/2700-2799/count-zero-request-servers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2747.%20%E7%BB%9F%E8%AE%A1%E6%B2%A1%E6%9C%89%E6%94%B6%E5%88%B0%E8%AF%B7%E6%B1%82%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E7%9B%AE.md,37.1%,中等,20 -2748,2700-2799,2748. 美丽下标对的数目,美丽下标对的数目,https://leetcode.cn/problems/number-of-beautiful-pairs/,number-of-beautiful-pairs,数组、数学、数论,https://algo.itcharge.cn/Solutions/2700-2799/number-of-beautiful-pairs/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2748.%20%E7%BE%8E%E4%B8%BD%E4%B8%8B%E6%A0%87%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.md,56.2%,简单,28 -2749,2700-2799,2749. 得到整数零需要执行的最少操作数,得到整数零需要执行的最少操作数,https://leetcode.cn/problems/minimum-operations-to-make-the-integer-zero/,minimum-operations-to-make-the-integer-zero,位运算、脑筋急转弯,https://algo.itcharge.cn/Solutions/2700-2799/minimum-operations-to-make-the-integer-zero/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2749.%20%E5%BE%97%E5%88%B0%E6%95%B4%E6%95%B0%E9%9B%B6%E9%9C%80%E8%A6%81%E6%89%A7%E8%A1%8C%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0.md,29.5%,中等,23 -2750,2700-2799,2750. 将数组划分成若干好子数组的方式,将数组划分成若干好子数组的方式,https://leetcode.cn/problems/ways-to-split-array-into-good-subarrays/,ways-to-split-array-into-good-subarrays,数组、数学,https://algo.itcharge.cn/Solutions/2700-2799/ways-to-split-array-into-good-subarrays/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2750.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%88%92%E5%88%86%E6%88%90%E8%8B%A5%E5%B9%B2%E5%A5%BD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E5%BC%8F.md,37.4%,中等,30 -2751,2700-2799,2751. 机器人碰撞,机器人碰撞,https://leetcode.cn/problems/robot-collisions/,robot-collisions,栈、数组、排序、模拟,https://algo.itcharge.cn/Solutions/2700-2799/robot-collisions/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2751.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%A2%B0%E6%92%9E.md,48.9%,困难,26 -2752,2700-2799,2752. ,,https://leetcode.cn/problems/customers-with-maximum-number-of-transactions-on-consecutive-days/,customers-with-maximum-number-of-transactions-on-consecutive-days,,https://algo.itcharge.cn/Solutions/2700-2799/customers-with-maximum-number-of-transactions-on-consecutive-days/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2752.%20.md,57.1%,困难,0 -LCP 01,LCP,LCP 01. 猜数字,猜数字,https://leetcode.cn/problems/guess-numbers/,guess-numbers,数组,https://algo.itcharge.cn/Solutions/LCP/guess-numbers/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2001.%20%E7%8C%9C%E6%95%B0%E5%AD%97.md,84.7%,简单,806 -LCP 02,LCP,LCP 02. 分式化简,分式化简,https://leetcode.cn/problems/deep-dark-fraction/,deep-dark-fraction,数组、数学、数论、模拟,https://algo.itcharge.cn/Solutions/LCP/deep-dark-fraction/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2002.%20%E5%88%86%E5%BC%8F%E5%8C%96%E7%AE%80.md,70.2%,简单,329 -LCP 03,LCP,LCP 03. 机器人大冒险,机器人大冒险,https://leetcode.cn/problems/programmable-robot/,programmable-robot,数组、哈希表、模拟,https://algo.itcharge.cn/Solutions/LCP/programmable-robot/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2003.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E5%A4%A7%E5%86%92%E9%99%A9.md,23.2%,中等,243 -LCP 04,LCP,LCP 04. 覆盖,覆盖,https://leetcode.cn/problems/broken-board-dominoes/,broken-board-dominoes,位运算、图、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/LCP/broken-board-dominoes/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2004.%20%E8%A6%86%E7%9B%96.md,41.2%,困难,93 -LCP 05,LCP,LCP 05. 发 LeetCoin,发 LeetCoin,https://leetcode.cn/problems/coin-bonus/,coin-bonus,树状数组、线段树、数组,https://algo.itcharge.cn/Solutions/LCP/coin-bonus/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2005.%20%E5%8F%91%20LeetCoin.md,22.4%,困难,68 -LCP 06,LCP,LCP 06. 拿硬币,拿硬币,https://leetcode.cn/problems/na-ying-bi/,na-ying-bi,数组、数学,https://algo.itcharge.cn/Solutions/LCP/na-ying-bi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2006.%20%E6%8B%BF%E7%A1%AC%E5%B8%81.md,83.9%,简单,871 -LCP 07,LCP,LCP 07. 传递信息,传递信息,https://leetcode.cn/problems/chuan-di-xin-xi/,chuan-di-xin-xi,深度优先搜索、广度优先搜索、图、动态规划,https://algo.itcharge.cn/Solutions/LCP/chuan-di-xin-xi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2007.%20%E4%BC%A0%E9%80%92%E4%BF%A1%E6%81%AF.md,75.5%,简单,626 -LCP 08,LCP,LCP 08. 剧情触发时间,剧情触发时间,https://leetcode.cn/problems/ju-qing-hong-fa-shi-jian/,ju-qing-hong-fa-shi-jian,数组、二分查找、排序,https://algo.itcharge.cn/Solutions/LCP/ju-qing-hong-fa-shi-jian/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2008.%20%E5%89%A7%E6%83%85%E8%A7%A6%E5%8F%91%E6%97%B6%E9%97%B4.md,32.9%,中等,131 -LCP 09,LCP,LCP 09. 最小跳跃次数,最小跳跃次数,https://leetcode.cn/problems/zui-xiao-tiao-yue-ci-shu/,zui-xiao-tiao-yue-ci-shu,广度优先搜索、线段树、数组、动态规划,https://algo.itcharge.cn/Solutions/LCP/zui-xiao-tiao-yue-ci-shu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2009.%20%E6%9C%80%E5%B0%8F%E8%B7%B3%E8%B7%83%E6%AC%A1%E6%95%B0.md,31.9%,困难,134 -LCP 10,LCP,LCP 10. 二叉树任务调度,二叉树任务调度,https://leetcode.cn/problems/er-cha-shu-ren-wu-diao-du/,er-cha-shu-ren-wu-diao-du,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/LCP/er-cha-shu-ren-wu-diao-du/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2010.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6.md,61.6%,困难,53 -LCP 11,LCP,LCP 11. 期望个数统计,期望个数统计,https://leetcode.cn/problems/qi-wang-ge-shu-tong-ji/,qi-wang-ge-shu-tong-ji,数组、哈希表、数学、概率与统计,https://algo.itcharge.cn/Solutions/LCP/qi-wang-ge-shu-tong-ji/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2011.%20%E6%9C%9F%E6%9C%9B%E4%B8%AA%E6%95%B0%E7%BB%9F%E8%AE%A1.md,72.6%,简单,99 -LCP 12,LCP,LCP 12. 小张刷题计划,小张刷题计划,https://leetcode.cn/problems/xiao-zhang-shua-ti-ji-hua/,xiao-zhang-shua-ti-ji-hua,数组、二分查找,https://algo.itcharge.cn/Solutions/LCP/xiao-zhang-shua-ti-ji-hua/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2012.%20%E5%B0%8F%E5%BC%A0%E5%88%B7%E9%A2%98%E8%AE%A1%E5%88%92.md,43.9%,中等,155 -LCP 13,LCP,LCP 13. 寻宝,寻宝,https://leetcode.cn/problems/xun-bao/,xun-bao,位运算、广度优先搜索、数组、动态规划、状态压缩、矩阵,https://algo.itcharge.cn/Solutions/LCP/xun-bao/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2013.%20%E5%AF%BB%E5%AE%9D.md,59.7%,困难,100 -LCP 14,LCP,LCP 14. 切分数组,切分数组,https://leetcode.cn/problems/qie-fen-shu-zu/,qie-fen-shu-zu,数组、数学、动态规划、数论,https://algo.itcharge.cn/Solutions/LCP/qie-fen-shu-zu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2014.%20%E5%88%87%E5%88%86%E6%95%B0%E7%BB%84.md,24.1%,困难,59 -LCP 15,LCP,LCP 15. 游乐园的迷宫,游乐园的迷宫,https://leetcode.cn/problems/you-le-yuan-de-mi-gong/,you-le-yuan-de-mi-gong,贪心、几何、数组、数学,https://algo.itcharge.cn/Solutions/LCP/you-le-yuan-de-mi-gong/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2015.%20%E6%B8%B8%E4%B9%90%E5%9B%AD%E7%9A%84%E8%BF%B7%E5%AE%AB.md,62.6%,困难,30 -LCP 16,LCP,LCP 16. 游乐园的游览计划,游乐园的游览计划,https://leetcode.cn/problems/you-le-yuan-de-you-lan-ji-hua/,you-le-yuan-de-you-lan-ji-hua,图、几何、数学,https://algo.itcharge.cn/Solutions/LCP/you-le-yuan-de-you-lan-ji-hua/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2016.%20%E6%B8%B8%E4%B9%90%E5%9B%AD%E7%9A%84%E6%B8%B8%E8%A7%88%E8%AE%A1%E5%88%92.md,34.9%,困难,22 -LCP 17,LCP,LCP 17. 速算机器人,速算机器人,https://leetcode.cn/problems/nGK0Fy/,nGK0Fy,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/LCP/nGK0Fy/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2017.%20%E9%80%9F%E7%AE%97%E6%9C%BA%E5%99%A8%E4%BA%BA.md,80.2%,简单,356 -LCP 18,LCP,LCP 18. 早餐组合,早餐组合,https://leetcode.cn/problems/2vYnGI/,2vYnGI,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/LCP/2vYnGI/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2018.%20%E6%97%A9%E9%A4%90%E7%BB%84%E5%90%88.md,30.2%,简单,356 -LCP 19,LCP,LCP 19. 秋叶收藏集,秋叶收藏集,https://leetcode.cn/problems/UlBDOe/,UlBDOe,字符串、动态规划,https://algo.itcharge.cn/Solutions/LCP/UlBDOe/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2019.%20%E7%A7%8B%E5%8F%B6%E6%94%B6%E8%97%8F%E9%9B%86.md,51.8%,中等,234 -LCP 20,LCP,LCP 20. 快速公交,快速公交,https://leetcode.cn/problems/meChtZ/,meChtZ,记忆化搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/LCP/meChtZ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2020.%20%E5%BF%AB%E9%80%9F%E5%85%AC%E4%BA%A4.md,36.0%,困难,39 -LCP 21,LCP,LCP 21. 追逐游戏,追逐游戏,https://leetcode.cn/problems/Za25hA/,Za25hA,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/LCP/Za25hA/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2021.%20%E8%BF%BD%E9%80%90%E6%B8%B8%E6%88%8F.md,38.6%,困难,35 -LCP 22,LCP,LCP 22. 黑白方格画,黑白方格画,https://leetcode.cn/problems/ccw6C7/,ccw6C7,数学,https://algo.itcharge.cn/Solutions/LCP/ccw6C7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2022.%20%E9%BB%91%E7%99%BD%E6%96%B9%E6%A0%BC%E7%94%BB.md,35.0%,简单,221 -LCP 23,LCP,LCP 23. 魔术排列,魔术排列,https://leetcode.cn/problems/er94lq/,er94lq,队列、数组、模拟,https://algo.itcharge.cn/Solutions/LCP/er94lq/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2023.%20%E9%AD%94%E6%9C%AF%E6%8E%92%E5%88%97.md,37.1%,中等,68 -LCP 24,LCP,LCP 24. 数字游戏,数字游戏,https://leetcode.cn/problems/5TxKeK/,5TxKeK,数组、数学、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/5TxKeK/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2024.%20%E6%95%B0%E5%AD%97%E6%B8%B8%E6%88%8F.md,32.5%,困难,30 -LCP 25,LCP,LCP 25. 古董键盘,古董键盘,https://leetcode.cn/problems/Uh984O/,Uh984O,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/LCP/Uh984O/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2025.%20%E5%8F%A4%E8%91%A3%E9%94%AE%E7%9B%98.md,37.5%,困难,36 -LCP 26,LCP,LCP 26. 导航装置,导航装置,https://leetcode.cn/problems/hSRGyL/,hSRGyL,树、动态规划、二叉树,https://algo.itcharge.cn/Solutions/LCP/hSRGyL/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2026.%20%E5%AF%BC%E8%88%AA%E8%A3%85%E7%BD%AE.md,37.6%,困难,23 -LCP 27,LCP,LCP 27. 黑盒光线反射,黑盒光线反射,https://leetcode.cn/problems/IQvJ9i/,IQvJ9i,设计、线段树、数学、有序集合,https://algo.itcharge.cn/Solutions/LCP/IQvJ9i/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2027.%20%E9%BB%91%E7%9B%92%E5%85%89%E7%BA%BF%E5%8F%8D%E5%B0%84.md,34.2%,困难,30 -LCP 28,LCP,LCP 28. 采购方案,采购方案,https://leetcode.cn/problems/4xy4Wx/,4xy4Wx,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/LCP/4xy4Wx/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2028.%20%E9%87%87%E8%B4%AD%E6%96%B9%E6%A1%88.md,31.8%,简单,293 -LCP 29,LCP,LCP 29. 乐团站位,乐团站位,https://leetcode.cn/problems/SNJvJP/,SNJvJP,数学,https://algo.itcharge.cn/Solutions/LCP/SNJvJP/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2029.%20%E4%B9%90%E5%9B%A2%E7%AB%99%E4%BD%8D.md,21.2%,中等,171 -LCP 30,LCP,LCP 30. 魔塔游戏,魔塔游戏,https://leetcode.cn/problems/p0NxJO/,p0NxJO,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/p0NxJO/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2030.%20%E9%AD%94%E5%A1%94%E6%B8%B8%E6%88%8F.md,37.7%,中等,142 -LCP 31,LCP,LCP 31. 变换的迷宫,变换的迷宫,https://leetcode.cn/problems/Db3wC1/,Db3wC1,深度优先搜索、广度优先搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/LCP/Db3wC1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2031.%20%E5%8F%98%E6%8D%A2%E7%9A%84%E8%BF%B7%E5%AE%AB.md,29.4%,困难,42 -LCP 32,LCP,LCP 32. 批量处理任务,批量处理任务,https://leetcode.cn/problems/t3fKg1/,t3fKg1,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/t3fKg1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2032.%20%E6%89%B9%E9%87%8F%E5%A4%84%E7%90%86%E4%BB%BB%E5%8A%A1.md,42.6%,困难,32 -LCP 33,LCP,LCP 33. 蓄水,蓄水,https://leetcode.cn/problems/o8SXZn/,o8SXZn,贪心、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/o8SXZn/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2033.%20%E8%93%84%E6%B0%B4.md,34.9%,简单,198 -LCP 34,LCP,LCP 34. 二叉树染色,二叉树染色,https://leetcode.cn/problems/er-cha-shu-ran-se-UGC/,er-cha-shu-ran-se-UGC,树、动态规划、二叉树,https://algo.itcharge.cn/Solutions/LCP/er-cha-shu-ran-se-UGC/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2034.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9F%93%E8%89%B2.md,56.1%,中等,103 -LCP 35,LCP,LCP 35. 电动车游城市,电动车游城市,https://leetcode.cn/problems/DFPeFJ/,DFPeFJ,图、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/DFPeFJ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2035.%20%E7%94%B5%E5%8A%A8%E8%BD%A6%E6%B8%B8%E5%9F%8E%E5%B8%82.md,48.2%,困难,39 -LCP 36,LCP,LCP 36. 最多牌组数,最多牌组数,https://leetcode.cn/problems/Up5XYM/,Up5XYM,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/LCP/Up5XYM/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2036.%20%E6%9C%80%E5%A4%9A%E7%89%8C%E7%BB%84%E6%95%B0.md,34.6%,困难,25 -LCP 37,LCP,LCP 37. 最小矩形面积,最小矩形面积,https://leetcode.cn/problems/zui-xiao-ju-xing-mian-ji/,zui-xiao-ju-xing-mian-ji,贪心、几何、数组、数学、组合数学、排序,https://algo.itcharge.cn/Solutions/LCP/zui-xiao-ju-xing-mian-ji/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2037.%20%E6%9C%80%E5%B0%8F%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md,24.8%,困难,27 -LCP 38,LCP,LCP 38. 守卫城堡,守卫城堡,https://leetcode.cn/problems/7rLGCR/,7rLGCR,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/LCP/7rLGCR/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2038.%20%E5%AE%88%E5%8D%AB%E5%9F%8E%E5%A0%A1.md,56.2%,困难,19 -LCP 39,LCP,LCP 39. 无人机方阵,无人机方阵,https://leetcode.cn/problems/0jQkd0/,0jQkd0,数组、哈希表、计数、矩阵,https://algo.itcharge.cn/Solutions/LCP/0jQkd0/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2039.%20%E6%97%A0%E4%BA%BA%E6%9C%BA%E6%96%B9%E9%98%B5.md,55.5%,简单,98 -LCP 40,LCP,LCP 40. 心算挑战,心算挑战,https://leetcode.cn/problems/uOAnQW/,uOAnQW,贪心、数组、排序,https://algo.itcharge.cn/Solutions/LCP/uOAnQW/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2040.%20%E5%BF%83%E7%AE%97%E6%8C%91%E6%88%98.md,31.1%,简单,185 -LCP 41,LCP,LCP 41. 黑白翻转棋,黑白翻转棋,https://leetcode.cn/problems/fHi6rV/,fHi6rV,广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/LCP/fHi6rV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2041.%20%E9%BB%91%E7%99%BD%E7%BF%BB%E8%BD%AC%E6%A3%8B.md,68.3%,中等,136 -LCP 42,LCP,LCP 42. 玩具套圈,玩具套圈,https://leetcode.cn/problems/vFjcfV/,vFjcfV,几何、数组、哈希表、数学、二分查找、排序,https://algo.itcharge.cn/Solutions/LCP/vFjcfV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2042.%20%E7%8E%A9%E5%85%B7%E5%A5%97%E5%9C%88.md,28.5%,困难,48 -LCP 43,LCP,LCP 43. 十字路口的交通,十字路口的交通,https://leetcode.cn/problems/Y1VbOX/,Y1VbOX,数组、字符串、动态规划,https://algo.itcharge.cn/Solutions/LCP/Y1VbOX/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2043.%20%E5%8D%81%E5%AD%97%E8%B7%AF%E5%8F%A3%E7%9A%84%E4%BA%A4%E9%80%9A.md,51.6%,困难,30 -LCP 44,LCP,LCP 44. 开幕式焰火,开幕式焰火,https://leetcode.cn/problems/sZ59z6/,sZ59z6,树、深度优先搜索、广度优先搜索、哈希表、二叉树,https://algo.itcharge.cn/Solutions/LCP/sZ59z6/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2044.%20%E5%BC%80%E5%B9%95%E5%BC%8F%E7%84%B0%E7%81%AB.md,78.6%,简单,215 -LCP 45,LCP,LCP 45. 自行车炫技赛场,自行车炫技赛场,https://leetcode.cn/problems/kplEvH/,kplEvH,深度优先搜索、广度优先搜索、记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/LCP/kplEvH/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2045.%20%E8%87%AA%E8%A1%8C%E8%BD%A6%E7%82%AB%E6%8A%80%E8%B5%9B%E5%9C%BA.md,29.6%,中等,58 -LCP 46,LCP,LCP 46. 志愿者调配,志愿者调配,https://leetcode.cn/problems/05ZEDJ/,05ZEDJ,图、数组、数学,https://algo.itcharge.cn/Solutions/LCP/05ZEDJ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2046.%20%E5%BF%97%E6%84%BF%E8%80%85%E8%B0%83%E9%85%8D.md,49.2%,中等,45 -LCP 47,LCP,LCP 47. 入场安检,入场安检,https://leetcode.cn/problems/oPs9Bm/,oPs9Bm,数组、动态规划,https://algo.itcharge.cn/Solutions/LCP/oPs9Bm/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2047.%20%E5%85%A5%E5%9C%BA%E5%AE%89%E6%A3%80.md,45.0%,困难,33 -LCP 48,LCP,LCP 48. 无限棋局,无限棋局,https://leetcode.cn/problems/fsa7oZ/,fsa7oZ,数组、数学、枚举、博弈,https://algo.itcharge.cn/Solutions/LCP/fsa7oZ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2048.%20%E6%97%A0%E9%99%90%E6%A3%8B%E5%B1%80.md,27.3%,困难,17 -LCP 49,LCP,LCP 49. 环形闯关游戏,环形闯关游戏,https://leetcode.cn/problems/K8GULz/,K8GULz,位运算、并查集、数组、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/K8GULz/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2049.%20%E7%8E%AF%E5%BD%A2%E9%97%AF%E5%85%B3%E6%B8%B8%E6%88%8F.md,35.5%,困难,20 -LCP 50,LCP,LCP 50. 宝石补给,宝石补给,https://leetcode.cn/problems/WHnhjV/,WHnhjV,数组、模拟,https://algo.itcharge.cn/Solutions/LCP/WHnhjV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2050.%20%E5%AE%9D%E7%9F%B3%E8%A1%A5%E7%BB%99.md,68.4%,简单,79 -LCP 51,LCP,LCP 51. 烹饪料理,烹饪料理,https://leetcode.cn/problems/UEcfPD/,UEcfPD,位运算、数组、回溯、枚举,https://algo.itcharge.cn/Solutions/LCP/UEcfPD/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2051.%20%E7%83%B9%E9%A5%AA%E6%96%99%E7%90%86.md,48.1%,简单,111 -LCP 52,LCP,LCP 52. 二叉搜索树染色,二叉搜索树染色,https://leetcode.cn/problems/QO5KpG/,QO5KpG,树、线段树、二叉搜索树、数组、二分查找、二叉树、有序集合,https://algo.itcharge.cn/Solutions/LCP/QO5KpG/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2052.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E6%9F%93%E8%89%B2.md,28.6%,中等,95 -LCP 53,LCP,LCP 53. 守护太空城,守护太空城,https://leetcode.cn/problems/EJvmW4/,EJvmW4,位运算、数组、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/LCP/EJvmW4/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2053.%20%E5%AE%88%E6%8A%A4%E5%A4%AA%E7%A9%BA%E5%9F%8E.md,44.6%,困难,17 -LCP 54,LCP,LCP 54. 夺回据点,夺回据点,https://leetcode.cn/problems/s5kipK/,s5kipK,图、数组、双连通分量,https://algo.itcharge.cn/Solutions/LCP/s5kipK/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2054.%20%E5%A4%BA%E5%9B%9E%E6%8D%AE%E7%82%B9.md,40.9%,困难,12 -LCP 55,LCP,LCP 55. 采集果实,采集果实,https://leetcode.cn/problems/PTXy4P/,PTXy4P,数组,https://algo.itcharge.cn/Solutions/LCP/PTXy4P/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2055.%20%E9%87%87%E9%9B%86%E6%9E%9C%E5%AE%9E.md,73.6%,简单,78 -LCP 56,LCP,LCP 56. 信物传送,信物传送,https://leetcode.cn/problems/6UEx57/,6UEx57,广度优先搜索、图、数组、矩阵、最短路、堆(优先队列),https://algo.itcharge.cn/Solutions/LCP/6UEx57/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2056.%20%E4%BF%A1%E7%89%A9%E4%BC%A0%E9%80%81.md,43.7%,中等,75 -LCP 57,LCP,LCP 57. 打地鼠,打地鼠,https://leetcode.cn/problems/ZbAuEH/,ZbAuEH,数组、动态规划、矩阵、排序,https://algo.itcharge.cn/Solutions/LCP/ZbAuEH/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2057.%20%E6%89%93%E5%9C%B0%E9%BC%A0.md,27.2%,困难,44 -LCP 58,LCP,LCP 58. 积木拼接,积木拼接,https://leetcode.cn/problems/De4qBB/,De4qBB,数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/LCP/De4qBB/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2058.%20%E7%A7%AF%E6%9C%A8%E6%8B%BC%E6%8E%A5.md,35.3%,困难,18 -LCP 59,LCP,LCP 59. 搭桥过河,搭桥过河,https://leetcode.cn/problems/NfY1m5/,NfY1m5,数组、动态规划,https://algo.itcharge.cn/Solutions/LCP/NfY1m5/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2059.%20%E6%90%AD%E6%A1%A5%E8%BF%87%E6%B2%B3.md,36.0%,困难,10 -LCP 60,LCP,LCP 60. 力扣泡泡龙,力扣泡泡龙,https://leetcode.cn/problems/WInSav/,WInSav,树、动态规划、二叉树,https://algo.itcharge.cn/Solutions/LCP/WInSav/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2060.%20%E5%8A%9B%E6%89%A3%E6%B3%A1%E6%B3%A1%E9%BE%99.md,20.2%,困难,27 -LCP 61,LCP,LCP 61. 气温变化趋势,气温变化趋势,https://leetcode.cn/problems/6CE719/,6CE719,数组,https://algo.itcharge.cn/Solutions/LCP/6CE719/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2061.%20%E6%B0%94%E6%B8%A9%E5%8F%98%E5%8C%96%E8%B6%8B%E5%8A%BF.md,61.4%,简单,82 -LCP 62,LCP,LCP 62. 交通枢纽,交通枢纽,https://leetcode.cn/problems/D9PW8w/,D9PW8w,图,https://algo.itcharge.cn/Solutions/LCP/D9PW8w/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2062.%20%E4%BA%A4%E9%80%9A%E6%9E%A2%E7%BA%BD.md,62.5%,中等,72 -LCP 63,LCP,LCP 63. 弹珠游戏,弹珠游戏,https://leetcode.cn/problems/EXvqDp/,EXvqDp,深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/LCP/EXvqDp/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2063.%20%E5%BC%B9%E7%8F%A0%E6%B8%B8%E6%88%8F.md,26.5%,中等,114 -LCP 64,LCP,LCP 64. 二叉树灯饰,二叉树灯饰,https://leetcode.cn/problems/U7WvvU/,U7WvvU,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/LCP/U7WvvU/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2064.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%81%AF%E9%A5%B0.md,35.7%,中等,51 -LCP 65,LCP,LCP 65. 舒适的湿度,舒适的湿度,https://leetcode.cn/problems/3aqs1c/,3aqs1c,数组、动态规划,https://algo.itcharge.cn/Solutions/LCP/3aqs1c/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2065.%20%E8%88%92%E9%80%82%E7%9A%84%E6%B9%BF%E5%BA%A6.md,45.7%,困难,16 -LCP 66,LCP,LCP 66. 最小展台数量,最小展台数量,https://leetcode.cn/problems/600YaG/,600YaG,数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/LCP/600YaG/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2066.%20%E6%9C%80%E5%B0%8F%E5%B1%95%E5%8F%B0%E6%95%B0%E9%87%8F.md,77.5%,简单,85 -LCP 67,LCP,LCP 67. 装饰树,装饰树,https://leetcode.cn/problems/KnLfVT/,KnLfVT,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/LCP/KnLfVT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2067.%20%E8%A3%85%E9%A5%B0%E6%A0%91.md,87.8%,中等,80 -LCP 68,LCP,LCP 68. 美观的花束,美观的花束,https://leetcode.cn/problems/1GxJYY/,1GxJYY,数组、滑动窗口,https://algo.itcharge.cn/Solutions/LCP/1GxJYY/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2068.%20%E7%BE%8E%E8%A7%82%E7%9A%84%E8%8A%B1%E6%9D%9F.md,50.7%,中等,52 -LCP 69,LCP,LCP 69. Hello LeetCode!,Hello LeetCode!,https://leetcode.cn/problems/rMeRt2/,rMeRt2,位运算、数组、字符串、动态规划、状态压缩,https://algo.itcharge.cn/Solutions/LCP/rMeRt2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2069.%20Hello%20LeetCode%21.md,34.1%,困难,21 -LCP 70,LCP,LCP 70. 沙地治理,沙地治理,https://leetcode.cn/problems/XxZZjK/,XxZZjK,数组、数学,https://algo.itcharge.cn/Solutions/LCP/XxZZjK/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2070.%20%E6%B2%99%E5%9C%B0%E6%B2%BB%E7%90%86.md,27.8%,困难,15 -LCP 71,LCP,LCP 71. 集水器,集水器,https://leetcode.cn/problems/kskhHQ/,kskhHQ,并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/LCP/kskhHQ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2071.%20%E9%9B%86%E6%B0%B4%E5%99%A8.md,55.4%,困难,13 -LCP 72,LCP,LCP 72. 补给马车,补给马车,https://leetcode.cn/problems/hqCnmP/,hqCnmP,,https://algo.itcharge.cn/Solutions/LCP/hqCnmP/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2072.%20%E8%A1%A5%E7%BB%99%E9%A9%AC%E8%BD%A6.md,69.7%,简单,42 -LCP 73,LCP,LCP 73. 探险营地,探险营地,https://leetcode.cn/problems/0Zeoeg/,0Zeoeg,,https://algo.itcharge.cn/Solutions/LCP/0Zeoeg/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2073.%20%E6%8E%A2%E9%99%A9%E8%90%A5%E5%9C%B0.md,45.7%,中等,25 -LCP 74,LCP,LCP 74. 最强祝福力场,最强祝福力场,https://leetcode.cn/problems/xepqZ5/,xepqZ5,,https://algo.itcharge.cn/Solutions/LCP/xepqZ5/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2074.%20%E6%9C%80%E5%BC%BA%E7%A5%9D%E7%A6%8F%E5%8A%9B%E5%9C%BA.md,29.0%,中等,35 -LCP 75,LCP,LCP 75. 传送卷轴,传送卷轴,https://leetcode.cn/problems/rdmXM7/,rdmXM7,,https://algo.itcharge.cn/Solutions/LCP/rdmXM7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2075.%20%E4%BC%A0%E9%80%81%E5%8D%B7%E8%BD%B4.md,36.2%,困难,15 -LCP 76,LCP,LCP 76. 魔法棋盘,魔法棋盘,https://leetcode.cn/problems/1ybDKD/,1ybDKD,,https://algo.itcharge.cn/Solutions/LCP/1ybDKD/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2076.%20%E9%AD%94%E6%B3%95%E6%A3%8B%E7%9B%98.md,36.8%,困难,12 -LCP 77,LCP,LCP 77. 符文储备,符文储备,https://leetcode.cn/problems/W2ZX4X/,W2ZX4X,,https://algo.itcharge.cn/Solutions/LCP/W2ZX4X/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2077.%20%E7%AC%A6%E6%96%87%E5%82%A8%E5%A4%87.md,72.3%,简单,22 -LCP 78,LCP,LCP 78. 城墙防线,城墙防线,https://leetcode.cn/problems/Nsibyl/,Nsibyl,,https://algo.itcharge.cn/Solutions/LCP/Nsibyl/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2078.%20%E5%9F%8E%E5%A2%99%E9%98%B2%E7%BA%BF.md,45.5%,中等,21 -LCP 79,LCP,LCP 79. 提取咒文,提取咒文,https://leetcode.cn/problems/kjpLFZ/,kjpLFZ,,https://algo.itcharge.cn/Solutions/LCP/kjpLFZ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2079.%20%E6%8F%90%E5%8F%96%E5%92%92%E6%96%87.md,27.5%,中等,18 -LCP 80,LCP,LCP 80. 生物进化录,生物进化录,https://leetcode.cn/problems/qoQAMX/,qoQAMX,,https://algo.itcharge.cn/Solutions/LCP/qoQAMX/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2080.%20%E7%94%9F%E7%89%A9%E8%BF%9B%E5%8C%96%E5%BD%95.md,50.1%,困难,14 -LCP 81,LCP,LCP 81. 与非的谜题,与非的谜题,https://leetcode.cn/problems/ryfUiz/,ryfUiz,,https://algo.itcharge.cn/Solutions/LCP/ryfUiz/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2081.%20%E4%B8%8E%E9%9D%9E%E7%9A%84%E8%B0%9C%E9%A2%98.md,48.0%,困难,15 -LCP 82,LCP,LCP 82. 万灵之树,万灵之树,https://leetcode.cn/problems/cnHoX6/,cnHoX6,,https://algo.itcharge.cn/Solutions/LCP/cnHoX6/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCP%2082.%20%E4%B8%87%E7%81%B5%E4%B9%8B%E6%A0%91.md,14.0%,困难,5 -LCS 01,LCS,LCS 01. 下载插件,下载插件,https://leetcode.cn/problems/Ju9Xwi/,Ju9Xwi,贪心、数学、动态规划,https://algo.itcharge.cn/Solutions/LCS/Ju9Xwi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCS%2001.%20%E4%B8%8B%E8%BD%BD%E6%8F%92%E4%BB%B6.md,53.8%,简单,267 -LCS 02,LCS,LCS 02. 完成一半题目,完成一半题目,https://leetcode.cn/problems/WqXACV/,WqXACV,贪心、数组、哈希表、排序,https://algo.itcharge.cn/Solutions/LCS/WqXACV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCS%2002.%20%E5%AE%8C%E6%88%90%E4%B8%80%E5%8D%8A%E9%A2%98%E7%9B%AE.md,64.5%,简单,183 -LCS 03,LCS,LCS 03. 主题空间,主题空间,https://leetcode.cn/problems/YesdPw/,YesdPw,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/LCS/YesdPw/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/LCS%2003.%20%E4%B8%BB%E9%A2%98%E7%A9%BA%E9%97%B4.md,41.3%,中等,96 -剑指 Offer 03,Offer,剑指 Offer 03. 数组中重复的数字,数组中重复的数字,https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/,shu-zu-zhong-zhong-fu-de-shu-zi-lcof,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/Offer/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2003.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md,67.1%,简单,4369 -剑指 Offer 04,Offer,剑指 Offer 04. 二维数组中的查找,二维数组中的查找,https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/,er-wei-shu-zu-zhong-de-cha-zhao-lcof,数组、二分查找、分治、矩阵,https://algo.itcharge.cn/Solutions/Offer/er-wei-shu-zu-zhong-de-cha-zhao-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2004.%20%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE.md,39.5%,中等,3514 -剑指 Offer 05,Offer,剑指 Offer 05. 替换空格,替换空格,https://leetcode.cn/problems/ti-huan-kong-ge-lcof/,ti-huan-kong-ge-lcof,字符串,https://algo.itcharge.cn/Solutions/Offer/ti-huan-kong-ge-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2005.%20%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.md,75.2%,简单,3525 -剑指 Offer 06,Offer,剑指 Offer 06. 从尾到头打印链表,从尾到头打印链表,https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/,cong-wei-dao-tou-da-yin-lian-biao-lcof,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/Offer/cong-wei-dao-tou-da-yin-lian-biao-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2006.%20%E4%BB%8E%E5%B0%BE%E5%88%B0%E5%A4%B4%E6%89%93%E5%8D%B0%E9%93%BE%E8%A1%A8.md,74.4%,简单,3619 -剑指 Offer 07,Offer,剑指 Offer 07. 重建二叉树,重建二叉树,https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/,zhong-jian-er-cha-shu-lcof,树、数组、哈希表、分治、二叉树,https://algo.itcharge.cn/Solutions/Offer/zhong-jian-er-cha-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2007.%20%E9%87%8D%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md,70.3%,中等,2159 -剑指 Offer 09,Offer,剑指 Offer 09. 用两个栈实现队列,用两个栈实现队列,https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/,yong-liang-ge-zhan-shi-xian-dui-lie-lcof,栈、设计、队列,https://algo.itcharge.cn/Solutions/Offer/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2009.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md,70.4%,简单,3003 -剑指 Offer 10- I,Offer,剑指 Offer 10- I. 斐波那契数列,斐波那契数列,https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/,fei-bo-na-qi-shu-lie-lcof,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/Offer/fei-bo-na-qi-shu-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2010-%20I.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md,35.9%,简单,2830 -剑指 Offer 10- II,Offer,剑指 Offer 10- II. 青蛙跳台阶问题,青蛙跳台阶问题,https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/,qing-wa-tiao-tai-jie-wen-ti-lcof,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/Offer/qing-wa-tiao-tai-jie-wen-ti-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2010-%20II.%20%E9%9D%92%E8%9B%99%E8%B7%B3%E5%8F%B0%E9%98%B6%E9%97%AE%E9%A2%98.md,45.7%,简单,2071 -剑指 Offer 11,Offer,剑指 Offer 11. 旋转数组的最小数字,旋转数组的最小数字,https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/,xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2011.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md,49.5%,简单,2603 -剑指 Offer 12,Offer,剑指 Offer 12. 矩阵中的路径,矩阵中的路径,https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/,ju-zhen-zhong-de-lu-jing-lcof,数组、回溯、矩阵,https://algo.itcharge.cn/Solutions/Offer/ju-zhen-zhong-de-lu-jing-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2012.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84.md,45.7%,中等,1859 -剑指 Offer 13,Offer,剑指 Offer 13. 机器人的运动范围,机器人的运动范围,https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/,ji-qi-ren-de-yun-dong-fan-wei-lcof,深度优先搜索、广度优先搜索、动态规划,https://algo.itcharge.cn/Solutions/Offer/ji-qi-ren-de-yun-dong-fan-wei-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md,53.6%,中等,2542 -剑指 Offer 14- I,Offer,剑指 Offer 14- I. 剪绳子,剪绳子,https://leetcode.cn/problems/jian-sheng-zi-lcof/,jian-sheng-zi-lcof,数学、动态规划,https://algo.itcharge.cn/Solutions/Offer/jian-sheng-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2014-%20I.%20%E5%89%AA%E7%BB%B3%E5%AD%90.md,57.4%,中等,2032 -剑指 Offer 14- II,Offer,剑指 Offer 14- II. 剪绳子 II,剪绳子 II,https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/,jian-sheng-zi-ii-lcof,数学、动态规划,https://algo.itcharge.cn/Solutions/Offer/jian-sheng-zi-ii-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2014-%20II.%20%E5%89%AA%E7%BB%B3%E5%AD%90%20II.md,31.4%,中等,779 -剑指 Offer 15,Offer,剑指 Offer 15. 二进制中1的个数,二进制中1的个数,https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/,er-jin-zhi-zhong-1de-ge-shu-lcof,位运算,https://algo.itcharge.cn/Solutions/Offer/er-jin-zhi-zhong-1de-ge-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2015.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD1%E7%9A%84%E4%B8%AA%E6%95%B0.md,75.7%,简单,1828 -剑指 Offer 16,Offer,剑指 Offer 16. 数值的整数次方,数值的整数次方,https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/,shu-zhi-de-zheng-shu-ci-fang-lcof,递归、数学,https://algo.itcharge.cn/Solutions/Offer/shu-zhi-de-zheng-shu-ci-fang-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2016.%20%E6%95%B0%E5%80%BC%E7%9A%84%E6%95%B4%E6%95%B0%E6%AC%A1%E6%96%B9.md,34.8%,中等,1283 -剑指 Offer 17,Offer,剑指 Offer 17. 打印从1到最大的n位数,打印从1到最大的n位数,https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/,da-yin-cong-1dao-zui-da-de-nwei-shu-lcof,数组、数学,https://algo.itcharge.cn/Solutions/Offer/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2017.%20%E6%89%93%E5%8D%B0%E4%BB%8E1%E5%88%B0%E6%9C%80%E5%A4%A7%E7%9A%84n%E4%BD%8D%E6%95%B0.md,77.7%,简单,1594 -剑指 Offer 18,Offer,剑指 Offer 18. 删除链表的节点,删除链表的节点,https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/,shan-chu-lian-biao-de-jie-dian-lcof,链表,https://algo.itcharge.cn/Solutions/Offer/shan-chu-lian-biao-de-jie-dian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2018.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9.md,59.9%,简单,2313 -剑指 Offer 19,Offer,剑指 Offer 19. 正则表达式匹配,正则表达式匹配,https://leetcode.cn/problems/zheng-ze-biao-da-shi-pi-pei-lcof/,zheng-ze-biao-da-shi-pi-pei-lcof,递归、字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer/zheng-ze-biao-da-shi-pi-pei-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2019.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md,38.5%,困难,721 -剑指 Offer 20,Offer,剑指 Offer 20. 表示数值的字符串,表示数值的字符串,https://leetcode.cn/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/,biao-shi-shu-zhi-de-zi-fu-chuan-lcof,字符串,https://algo.itcharge.cn/Solutions/Offer/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2020.%20%E8%A1%A8%E7%A4%BA%E6%95%B0%E5%80%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,25.1%,中等,1545 -剑指 Offer 21,Offer,剑指 Offer 21. 调整数组顺序使奇数位于偶数前面,调整数组顺序使奇数位于偶数前面,https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/,diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof,数组、双指针、排序,https://algo.itcharge.cn/Solutions/Offer/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2021.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md,65.0%,简单,2649 -剑指 Offer 22,Offer,剑指 Offer 22. 链表中倒数第k个节点,链表中倒数第k个节点,https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/,lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof,链表、双指针,https://algo.itcharge.cn/Solutions/Offer/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md,80.1%,简单,3606 -剑指 Offer 24,Offer,剑指 Offer 24. 反转链表,反转链表,https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/,fan-zhuan-lian-biao-lcof,递归、链表,https://algo.itcharge.cn/Solutions/Offer/fan-zhuan-lian-biao-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2024.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md,74.2%,简单,2950 -剑指 Offer 25,Offer,剑指 Offer 25. 合并两个排序的链表,合并两个排序的链表,https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/,he-bing-liang-ge-pai-xu-de-lian-biao-lcof,递归、链表,https://algo.itcharge.cn/Solutions/Offer/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2025.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8.md,72.2%,简单,1930 -剑指 Offer 26,Offer,剑指 Offer 26. 树的子结构,树的子结构,https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/,shu-de-zi-jie-gou-lcof,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/shu-de-zi-jie-gou-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2026.%20%E6%A0%91%E7%9A%84%E5%AD%90%E7%BB%93%E6%9E%84.md,46.4%,中等,2091 -剑指 Offer 27,Offer,剑指 Offer 27. 二叉树的镜像,二叉树的镜像,https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/,er-cha-shu-de-jing-xiang-lcof,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-shu-de-jing-xiang-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2027.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F.md,79.6%,简单,2240 -剑指 Offer 28,Offer,剑指 Offer 28. 对称的二叉树,对称的二叉树,https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/,dui-cheng-de-er-cha-shu-lcof,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/dui-cheng-de-er-cha-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2028.%20%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md,57.6%,简单,1874 -剑指 Offer 29,Offer,剑指 Offer 29. 顺时针打印矩阵,顺时针打印矩阵,https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/,shun-shi-zhen-da-yin-ju-zhen-lcof,数组、矩阵、模拟,https://algo.itcharge.cn/Solutions/Offer/shun-shi-zhen-da-yin-ju-zhen-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2029.%20%E9%A1%BA%E6%97%B6%E9%92%88%E6%89%93%E5%8D%B0%E7%9F%A9%E9%98%B5.md,43.0%,简单,2204 -剑指 Offer 30,Offer,剑指 Offer 30. 包含min函数的栈,包含min函数的栈,https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/,bao-han-minhan-shu-de-zhan-lcof,栈、设计,https://algo.itcharge.cn/Solutions/Offer/bao-han-minhan-shu-de-zhan-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2030.%20%E5%8C%85%E5%90%ABmin%E5%87%BD%E6%95%B0%E7%9A%84%E6%A0%88.md,55.3%,简单,2034 -剑指 Offer 31,Offer,剑指 Offer 31. 栈的压入、弹出序列,栈的压入、弹出序列,https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/,zhan-de-ya-ru-dan-chu-xu-lie-lcof,栈、数组、模拟,https://algo.itcharge.cn/Solutions/Offer/zhan-de-ya-ru-dan-chu-xu-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2031.%20%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5%E3%80%81%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97.md,61.0%,中等,1561 -剑指 Offer 32 - I,Offer,剑指 Offer 32 - I. 从上到下打印二叉树,从上到下打印二叉树,https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/,cong-shang-dao-xia-da-yin-er-cha-shu-lcof,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20I.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md,63.1%,中等,1719 -剑指 Offer 32 - II,Offer,剑指 Offer 32 - II. 从上到下打印二叉树 II,从上到下打印二叉树 II,https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/,cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20II.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20II.md,68.9%,简单,1907 -剑指 Offer 32 - III,Offer,剑指 Offer 32 - III. 从上到下打印二叉树 III,从上到下打印二叉树 III,https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/,cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof,树、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md,58.2%,中等,2124 -剑指 Offer 33,Offer,剑指 Offer 33. 二叉搜索树的后序遍历序列,二叉搜索树的后序遍历序列,https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/,er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof,栈、树、二叉搜索树、递归、二叉树、单调栈,https://algo.itcharge.cn/Solutions/Offer/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2033.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md,56.9%,中等,1653 -剑指 Offer 34,Offer,剑指 Offer 34. 二叉树中和为某一值的路径,二叉树中和为某一值的路径,https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/,er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof,树、深度优先搜索、回溯、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2034.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84.md,59.0%,中等,1515 -剑指 Offer 35,Offer,剑指 Offer 35. 复杂链表的复制,复杂链表的复制,https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/,fu-za-lian-biao-de-fu-zhi-lcof,哈希表、链表,https://algo.itcharge.cn/Solutions/Offer/fu-za-lian-biao-de-fu-zhi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2035.%20%E5%A4%8D%E6%9D%82%E9%93%BE%E8%A1%A8%E7%9A%84%E5%A4%8D%E5%88%B6.md,71.6%,中等,1794 -剑指 Offer 36,Offer,剑指 Offer 36. 二叉搜索树与双向链表,二叉搜索树与双向链表,https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/,er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof,栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表,https://algo.itcharge.cn/Solutions/Offer/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2036.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%8E%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md,64.9%,中等,1651 -剑指 Offer 37,Offer,剑指 Offer 37. 序列化二叉树,序列化二叉树,https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/,xu-lie-hua-er-cha-shu-lcof,树、深度优先搜索、广度优先搜索、设计、字符串、二叉树,https://algo.itcharge.cn/Solutions/Offer/xu-lie-hua-er-cha-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2037.%20%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91.md,57.4%,困难,861 -剑指 Offer 38,Offer,剑指 Offer 38. 字符串的排列,字符串的排列,https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/,zi-fu-chuan-de-pai-lie-lcof,字符串、回溯,https://algo.itcharge.cn/Solutions/Offer/zi-fu-chuan-de-pai-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2038.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md,57.6%,中等,1637 -剑指 Offer 39,Offer,剑指 Offer 39. 数组中出现次数超过一半的数字,数组中出现次数超过一半的数字,https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/,shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof,数组、哈希表、分治、计数、排序,https://algo.itcharge.cn/Solutions/Offer/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2039.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%87%E4%B8%80%E5%8D%8A%E7%9A%84%E6%95%B0%E5%AD%97.md,70.0%,简单,1595 -剑指 Offer 40,Offer,剑指 Offer 40. 最小的k个数,最小的k个数,https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/,zui-xiao-de-kge-shu-lcof,数组、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer/zui-xiao-de-kge-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2040.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md,57.7%,简单,2564 -剑指 Offer 41,Offer,剑指 Offer 41. 数据流中的中位数,数据流中的中位数,https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/,shu-ju-liu-zhong-de-zhong-wei-shu-lcof,设计、双指针、数据流、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2041.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md,58.3%,困难,883 -剑指 Offer 42,Offer,剑指 Offer 42. 连续子数组的最大和,连续子数组的最大和,https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/,lian-xu-zi-shu-zu-de-zui-da-he-lcof,数组、分治、动态规划,https://algo.itcharge.cn/Solutions/Offer/lian-xu-zi-shu-zu-de-zui-da-he-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2042.%20%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md,60.4%,简单,2297 -剑指 Offer 43,Offer,剑指 Offer 43. 1~n 整数中 1 出现的次数,1~n 整数中 1 出现的次数,https://leetcode.cn/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/,1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof,递归、数学、动态规划,https://algo.itcharge.cn/Solutions/Offer/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2043.%201%EF%BD%9En%20%E6%95%B4%E6%95%B0%E4%B8%AD%201%20%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md,50.5%,困难,784 -剑指 Offer 44,Offer,剑指 Offer 44. 数字序列中某一位的数字,数字序列中某一位的数字,https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/,shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof,数学、二分查找,https://algo.itcharge.cn/Solutions/Offer/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2044.%20%E6%95%B0%E5%AD%97%E5%BA%8F%E5%88%97%E4%B8%AD%E6%9F%90%E4%B8%80%E4%BD%8D%E7%9A%84%E6%95%B0%E5%AD%97.md,43.1%,中等,799 -剑指 Offer 45,Offer,剑指 Offer 45. 把数组排成最小的数,把数组排成最小的数,https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/,ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof,贪心、字符串、排序,https://algo.itcharge.cn/Solutions/Offer/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md,55.0%,中等,1472 -剑指 Offer 46,Offer,剑指 Offer 46. 把数字翻译成字符串,把数字翻译成字符串,https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/,ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2046.%20%E6%8A%8A%E6%95%B0%E5%AD%97%E7%BF%BB%E8%AF%91%E6%88%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,51.3%,中等,2341 -剑指 Offer 47,Offer,剑指 Offer 47. 礼物的最大价值,礼物的最大价值,https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/,li-wu-de-zui-da-jie-zhi-lcof,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/Offer/li-wu-de-zui-da-jie-zhi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2047.%20%E7%A4%BC%E7%89%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC.md,69.4%,中等,1918 -剑指 Offer 48,Offer,剑指 Offer 48. 最长不含重复字符的子字符串,最长不含重复字符的子字符串,https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/,zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/Offer/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2048.%20%E6%9C%80%E9%95%BF%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,46.1%,中等,1862 -剑指 Offer 49,Offer,剑指 Offer 49. 丑数,丑数,https://leetcode.cn/problems/chou-shu-lcof/,chou-shu-lcof,哈希表、数学、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer/chou-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2049.%20%E4%B8%91%E6%95%B0.md,64.7%,中等,879 -剑指 Offer 50,Offer,剑指 Offer 50. 第一个只出现一次的字符,第一个只出现一次的字符,https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/,di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof,队列、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/Offer/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2050.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E5%AD%97%E7%AC%A6.md,61.9%,简单,1842 -剑指 Offer 51,Offer,剑指 Offer 51. 数组中的逆序对,数组中的逆序对,https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/,shu-zu-zhong-de-ni-xu-dui-lcof,树状数组、线段树、数组、二分查找、分治、有序集合、归并排序,https://algo.itcharge.cn/Solutions/Offer/shu-zu-zhong-de-ni-xu-dui-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2051.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md,49.5%,困难,1476 -剑指 Offer 52,Offer,剑指 Offer 52. 两个链表的第一个公共节点,两个链表的第一个公共节点,https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/,liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Offer/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2052.%20%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%AC%E5%85%B1%E8%8A%82%E7%82%B9.md,65.7%,简单,1589 -剑指 Offer 53 - I,Offer,剑指 Offer 53 - I. 在排序数组中查找数字 I,在排序数组中查找数字 I,https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/,zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2053%20-%20I.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%95%B0%E5%AD%97%20I.md,52.8%,简单,2647 -剑指 Offer 53 - II,Offer,剑指 Offer 53 - II. 0~n-1中缺失的数字,0~n-1中缺失的数字,https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/,que-shi-de-shu-zi-lcof,位运算、数组、哈希表、数学、二分查找,https://algo.itcharge.cn/Solutions/Offer/que-shi-de-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2053%20-%20II.%200%EF%BD%9En-1%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md,44.8%,简单,2541 -剑指 Offer 54,Offer,剑指 Offer 54. 二叉搜索树的第k大节点,二叉搜索树的第k大节点,https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/,er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2054.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md,76.4%,简单,1870 -剑指 Offer 55 - I,Offer,剑指 Offer 55 - I. 二叉树的深度,二叉树的深度,https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/,er-cha-shu-de-shen-du-lcof,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-shu-de-shen-du-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2055%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6.md,79.5%,简单,1844 -剑指 Offer 55 - II,Offer,剑指 Offer 55 - II. 平衡二叉树,平衡二叉树,https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/,ping-heng-er-cha-shu-lcof,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/ping-heng-er-cha-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2055%20-%20II.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md,59.7%,简单,1478 -剑指 Offer 56 - I,Offer,剑指 Offer 56 - I. 数组中数字出现的次数,数组中数字出现的次数,https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/,shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof,位运算、数组,https://algo.itcharge.cn/Solutions/Offer/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2056%20-%20I.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md,68.8%,中等,1572 -剑指 Offer 56 - II,Offer,剑指 Offer 56 - II. 数组中数字出现的次数 II,数组中数字出现的次数 II,https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/,shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof,位运算、数组,https://algo.itcharge.cn/Solutions/Offer/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2056%20-%20II.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0%20II.md,80.7%,中等,1105 -剑指 Offer 57,Offer,剑指 Offer 57. 和为s的两个数字,和为s的两个数字,https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/,he-wei-sde-liang-ge-shu-zi-lcof,数组、双指针、二分查找,https://algo.itcharge.cn/Solutions/Offer/he-wei-sde-liang-ge-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97.md,68.0%,简单,1472 -剑指 Offer 57 - II,Offer,剑指 Offer 57 - II. 和为s的连续正数序列,和为s的连续正数序列,https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/,he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof,数学、双指针、枚举,https://algo.itcharge.cn/Solutions/Offer/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057%20-%20II.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%AD%A3%E6%95%B0%E5%BA%8F%E5%88%97.md,71.3%,简单,2459 -剑指 Offer 58 - I,Offer,剑指 Offer 58 - I. 翻转单词顺序,翻转单词顺序,https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/,fan-zhuan-dan-ci-shun-xu-lcof,双指针、字符串,https://algo.itcharge.cn/Solutions/Offer/fan-zhuan-dan-ci-shun-xu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2058%20-%20I.%20%E7%BF%BB%E8%BD%AC%E5%8D%95%E8%AF%8D%E9%A1%BA%E5%BA%8F.md,44.7%,简单,1661 -剑指 Offer 58 - II,Offer,剑指 Offer 58 - II. 左旋转字符串,左旋转字符串,https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/,zuo-xuan-zhuan-zi-fu-chuan-lcof,数学、双指针、字符串,https://algo.itcharge.cn/Solutions/Offer/zuo-xuan-zhuan-zi-fu-chuan-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2058%20-%20II.%20%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md,85.8%,简单,3234 -剑指 Offer 59 - I,Offer,剑指 Offer 59 - I. 滑动窗口的最大值,滑动窗口的最大值,https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/,hua-dong-chuang-kou-de-zui-da-zhi-lcof,队列、滑动窗口、单调队列、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer/hua-dong-chuang-kou-de-zui-da-zhi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2059%20-%20I.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,45.0%,困难,1731 -剑指 Offer 59 - II,Offer,剑指 Offer 59 - II. 队列的最大值,队列的最大值,https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/,dui-lie-de-zui-da-zhi-lcof,设计、队列、单调队列,https://algo.itcharge.cn/Solutions/Offer/dui-lie-de-zui-da-zhi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2059%20-%20II.%20%E9%98%9F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,48.0%,中等,1453 -剑指 Offer 60,Offer,剑指 Offer 60. n个骰子的点数,n个骰子的点数,https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/,nge-tou-zi-de-dian-shu-lcof,数学、动态规划、概率与统计,https://algo.itcharge.cn/Solutions/Offer/nge-tou-zi-de-dian-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2060.%20n%E4%B8%AA%E9%AA%B0%E5%AD%90%E7%9A%84%E7%82%B9%E6%95%B0.md,57.4%,中等,1005 -剑指 Offer 61,Offer,剑指 Offer 61. 扑克牌中的顺子,扑克牌中的顺子,https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/,bu-ke-pai-zhong-de-shun-zi-lcof,数组、排序,https://algo.itcharge.cn/Solutions/Offer/bu-ke-pai-zhong-de-shun-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2061.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md,45.4%,简单,1749 -剑指 Offer 62,Offer,剑指 Offer 62. 圆圈中最后剩下的数字,圆圈中最后剩下的数字,https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/,yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof,递归、数学,https://algo.itcharge.cn/Solutions/Offer/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md,65.5%,简单,1105 -剑指 Offer 63,Offer,剑指 Offer 63. 股票的最大利润,股票的最大利润,https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/,gu-piao-de-zui-da-li-run-lcof,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer/gu-piao-de-zui-da-li-run-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2063.%20%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A9%E6%B6%A6.md,63.0%,中等,1627 -剑指 Offer 64,Offer,剑指 Offer 64. 求1+2+…+n,求1+2+…+n,https://leetcode.cn/problems/qiu-12n-lcof/,qiu-12n-lcof,位运算、递归、脑筋急转弯,https://algo.itcharge.cn/Solutions/Offer/qiu-12n-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2064.%20%E6%B1%821%2B2%2B%E2%80%A6%2Bn.md,85.9%,中等,1790 -剑指 Offer 65,Offer,剑指 Offer 65. 不用加减乘除做加法,不用加减乘除做加法,https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/,bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof,位运算、数学,https://algo.itcharge.cn/Solutions/Offer/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2065.%20%E4%B8%8D%E7%94%A8%E5%8A%A0%E5%87%8F%E4%B9%98%E9%99%A4%E5%81%9A%E5%8A%A0%E6%B3%95.md,59.6%,简单,847 -剑指 Offer 66,Offer,剑指 Offer 66. 构建乘积数组,构建乘积数组,https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/,gou-jian-cheng-ji-shu-zu-lcof,数组、前缀和,https://algo.itcharge.cn/Solutions/Offer/gou-jian-cheng-ji-shu-zu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2066.%20%E6%9E%84%E5%BB%BA%E4%B9%98%E7%A7%AF%E6%95%B0%E7%BB%84.md,58.6%,中等,957 -剑指 Offer 67,Offer,剑指 Offer 67. 把字符串转换成整数,把字符串转换成整数,https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/,ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof,字符串,https://algo.itcharge.cn/Solutions/Offer/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2067.%20%E6%8A%8A%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%88%90%E6%95%B4%E6%95%B0.md,28.5%,中等,1021 -剑指 Offer 68 - I,Offer,剑指 Offer 68 - I. 二叉搜索树的最近公共祖先,二叉搜索树的最近公共祖先,https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/,er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2068%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md,69.3%,简单,1125 -剑指 Offer 68 - II,Offer,剑指 Offer 68 - II. 二叉树的最近公共祖先,二叉树的最近公共祖先,https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/,er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2068%20-%20II.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md,70.3%,简单,1119 -剑指 Offer II 001,Offer-II,剑指 Offer II 001. 整数除法,整数除法,https://leetcode.cn/problems/xoh6Oh/,xoh6Oh,位运算、数学,https://algo.itcharge.cn/Solutions/Offer-II/xoh6Oh/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20001.%20%E6%95%B4%E6%95%B0%E9%99%A4%E6%B3%95.md,21.1%,简单,515 -剑指 Offer II 002,Offer-II,剑指 Offer II 002. 二进制加法,二进制加法,https://leetcode.cn/problems/JFETK5/,JFETK5,位运算、数学、字符串、模拟,https://algo.itcharge.cn/Solutions/Offer-II/JFETK5/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20002.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8A%A0%E6%B3%95.md,53.8%,简单,575 -剑指 Offer II 003,Offer-II,剑指 Offer II 003. 前 n 个数字二进制中 1 的个数,前 n 个数字二进制中 1 的个数,https://leetcode.cn/problems/w3tCBm/,w3tCBm,位运算、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/w3tCBm/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20003.%20%E5%89%8D%20n%20%E4%B8%AA%E6%95%B0%E5%AD%97%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md,78.2%,简单,620 -剑指 Offer II 004,Offer-II,剑指 Offer II 004. 只出现一次的数字,只出现一次的数字,https://leetcode.cn/problems/WGki4K/,WGki4K,位运算、数组,https://algo.itcharge.cn/Solutions/Offer-II/WGki4K/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20004.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md,70.8%,中等,483 -剑指 Offer II 005,Offer-II,剑指 Offer II 005. 单词长度的最大乘积,单词长度的最大乘积,https://leetcode.cn/problems/aseY1I/,aseY1I,位运算、数组、字符串,https://algo.itcharge.cn/Solutions/Offer-II/aseY1I/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20005.%20%E5%8D%95%E8%AF%8D%E9%95%BF%E5%BA%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md,71.2%,中等,411 -剑指 Offer II 006,Offer-II,剑指 Offer II 006. 排序数组中两个数字之和,排序数组中两个数字之和,https://leetcode.cn/problems/kLl5u1/,kLl5u1,数组、双指针、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/kLl5u1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20006.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md,67.3%,简单,478 -剑指 Offer II 007,Offer-II,剑指 Offer II 007. 数组中和为 0 的三个数,数组中和为 0 的三个数,https://leetcode.cn/problems/1fGaJU/,1fGaJU,数组、双指针、排序,https://algo.itcharge.cn/Solutions/Offer-II/1fGaJU/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20007.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%92%8C%E4%B8%BA%200%20%E7%9A%84%E4%B8%89%E4%B8%AA%E6%95%B0.md,43.6%,中等,361 -剑指 Offer II 008,Offer-II,剑指 Offer II 008. 和大于等于 target 的最短子数组,和大于等于 target 的最短子数组,https://leetcode.cn/problems/2VG8Kg/,2VG8Kg,数组、二分查找、前缀和、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/2VG8Kg/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20008.%20%E5%92%8C%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%20target%20%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%90%E6%95%B0%E7%BB%84.md,49.8%,中等,468 -剑指 Offer II 009,Offer-II,剑指 Offer II 009. 乘积小于 K 的子数组,乘积小于 K 的子数组,https://leetcode.cn/problems/ZVAVXX/,ZVAVXX,数组、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/ZVAVXX/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20009.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,53.2%,中等,391 -剑指 Offer II 010,Offer-II,剑指 Offer II 010. 和为 k 的子数组,和为 k 的子数组,https://leetcode.cn/problems/QTMn0o/,QTMn0o,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/Offer-II/QTMn0o/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20010.%20%E5%92%8C%E4%B8%BA%20k%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,43.0%,中等,375 -剑指 Offer II 011,Offer-II,剑指 Offer II 011. 0 和 1 个数相同的子数组,0 和 1 个数相同的子数组,https://leetcode.cn/problems/A1NYOS/,A1NYOS,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/Offer-II/A1NYOS/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20011.%200%20%E5%92%8C%201%20%E4%B8%AA%E6%95%B0%E7%9B%B8%E5%90%8C%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md,54.9%,中等,345 -剑指 Offer II 012,Offer-II,剑指 Offer II 012. 左右两边子数组的和相等,左右两边子数组的和相等,https://leetcode.cn/problems/tvdfij/,tvdfij,数组、前缀和,https://algo.itcharge.cn/Solutions/Offer-II/tvdfij/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20012.%20%E5%B7%A6%E5%8F%B3%E4%B8%A4%E8%BE%B9%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%92%8C%E7%9B%B8%E7%AD%89.md,67.0%,简单,411 -剑指 Offer II 013,Offer-II,剑指 Offer II 013. 二维子矩阵的和,二维子矩阵的和,https://leetcode.cn/problems/O4NDxx/,O4NDxx,设计、数组、矩阵、前缀和,https://algo.itcharge.cn/Solutions/Offer-II/O4NDxx/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20013.%20%E4%BA%8C%E7%BB%B4%E5%AD%90%E7%9F%A9%E9%98%B5%E7%9A%84%E5%92%8C.md,68.9%,中等,303 -剑指 Offer II 014,Offer-II,剑指 Offer II 014. 字符串中的变位词,字符串中的变位词,https://leetcode.cn/problems/MPnaiL/,MPnaiL,哈希表、双指针、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/MPnaiL/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20014.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8F%98%E4%BD%8D%E8%AF%8D.md,51.5%,中等,343 -剑指 Offer II 015,Offer-II,剑指 Offer II 015. 字符串中的所有变位词,字符串中的所有变位词,https://leetcode.cn/problems/VabMRr/,VabMRr,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/VabMRr/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20015.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%8F%98%E4%BD%8D%E8%AF%8D.md,61.7%,中等,302 -剑指 Offer II 016,Offer-II,剑指 Offer II 016. 不含重复字符的最长子字符串,不含重复字符的最长子字符串,https://leetcode.cn/problems/wtcaE1/,wtcaE1,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/wtcaE1/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20016.%20%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,47.6%,中等,457 -剑指 Offer II 017,Offer-II,剑指 Offer II 017. 含有所有字符的最短字符串,含有所有字符的最短字符串,https://leetcode.cn/problems/M1oyTv/,M1oyTv,哈希表、字符串、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/M1oyTv/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20017.%20%E5%90%AB%E6%9C%89%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%97%E7%AC%A6%E4%B8%B2.md,50.9%,困难,316 -剑指 Offer II 018,Offer-II,剑指 Offer II 018. 有效的回文,有效的回文,https://leetcode.cn/problems/XltzEq/,XltzEq,双指针、字符串,https://algo.itcharge.cn/Solutions/Offer-II/XltzEq/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20018.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%9B%9E%E6%96%87.md,51.6%,简单,357 -剑指 Offer II 019,Offer-II,剑指 Offer II 019. 最多删除一个字符得到回文,最多删除一个字符得到回文,https://leetcode.cn/problems/RQku0D/,RQku0D,贪心、双指针、字符串,https://algo.itcharge.cn/Solutions/Offer-II/RQku0D/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20019.%20%E6%9C%80%E5%A4%9A%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E5%BE%97%E5%88%B0%E5%9B%9E%E6%96%87.md,45.7%,简单,343 -剑指 Offer II 020,Offer-II,剑指 Offer II 020. 回文子字符串的个数,回文子字符串的个数,https://leetcode.cn/problems/a7VOhD/,a7VOhD,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/a7VOhD/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20020.%20%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%AA%E6%95%B0.md,71.7%,中等,313 -剑指 Offer II 021,Offer-II,剑指 Offer II 021. 删除链表的倒数第 n 个结点,删除链表的倒数第 n 个结点,https://leetcode.cn/problems/SLwz0R/,SLwz0R,链表、双指针,https://algo.itcharge.cn/Solutions/Offer-II/SLwz0R/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20021.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20n%20%E4%B8%AA%E7%BB%93%E7%82%B9.md,54.1%,中等,478 -剑指 Offer II 022,Offer-II,剑指 Offer II 022. 链表中环的入口节点,链表中环的入口节点,https://leetcode.cn/problems/c32eOV/,c32eOV,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Offer-II/c32eOV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%8E%AF%E7%9A%84%E5%85%A5%E5%8F%A3%E8%8A%82%E7%82%B9.md,55.8%,中等,373 -剑指 Offer II 023,Offer-II,剑指 Offer II 023. 两个链表的第一个重合节点,两个链表的第一个重合节点,https://leetcode.cn/problems/3u1WK4/,3u1WK4,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Offer-II/3u1WK4/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20023.%20%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%87%8D%E5%90%88%E8%8A%82%E7%82%B9.md,70.1%,简单,301 -剑指 Offer II 024,Offer-II,剑指 Offer II 024. 反转链表,反转链表,https://leetcode.cn/problems/UHnkqh/,UHnkqh,递归、链表,https://algo.itcharge.cn/Solutions/Offer-II/UHnkqh/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20024.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md,75.4%,简单,584 -剑指 Offer II 025,Offer-II,剑指 Offer II 025. 链表中的两数相加,链表中的两数相加,https://leetcode.cn/problems/lMSNwu/,lMSNwu,栈、链表、数学,https://algo.itcharge.cn/Solutions/Offer-II/lMSNwu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20025.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md,58.3%,中等,414 -剑指 Offer II 026,Offer-II,剑指 Offer II 026. 重排链表,重排链表,https://leetcode.cn/problems/LGjMqU/,LGjMqU,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/Offer-II/LGjMqU/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20026.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md,66.5%,中等,391 -剑指 Offer II 027,Offer-II,剑指 Offer II 027. 回文链表,回文链表,https://leetcode.cn/problems/aMhZSa/,aMhZSa,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/Offer-II/aMhZSa/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20027.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md,61.1%,简单,505 -剑指 Offer II 028,Offer-II,剑指 Offer II 028. 展平多级双向链表,展平多级双向链表,https://leetcode.cn/problems/Qv1Da2/,Qv1Da2,深度优先搜索、链表、双向链表,https://algo.itcharge.cn/Solutions/Offer-II/Qv1Da2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20028.%20%E5%B1%95%E5%B9%B3%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md,60.0%,中等,347 -剑指 Offer II 029,Offer-II,剑指 Offer II 029. 排序的循环链表,排序的循环链表,https://leetcode.cn/problems/4ueAj6/,4ueAj6,链表,https://algo.itcharge.cn/Solutions/Offer-II/4ueAj6/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20029.%20%E6%8E%92%E5%BA%8F%E7%9A%84%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.md,33.3%,中等,630 -剑指 Offer II 030,Offer-II,剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器,插入、删除和随机访问都是 O(1) 的容器,https://leetcode.cn/problems/FortPu/,FortPu,设计、数组、哈希表、数学、随机化,https://algo.itcharge.cn/Solutions/Offer-II/FortPu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20030.%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E9%83%BD%E6%98%AF%20O%281%29%20%E7%9A%84%E5%AE%B9%E5%99%A8.md,53.8%,中等,176 -剑指 Offer II 031,Offer-II,剑指 Offer II 031. 最近最少使用缓存,最近最少使用缓存,https://leetcode.cn/problems/OrIXps/,OrIXps,设计、哈希表、链表、双向链表,https://algo.itcharge.cn/Solutions/Offer-II/OrIXps/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20031.%20%E6%9C%80%E8%BF%91%E6%9C%80%E5%B0%91%E4%BD%BF%E7%94%A8%E7%BC%93%E5%AD%98.md,55.6%,中等,266 -剑指 Offer II 032,Offer-II,剑指 Offer II 032. 有效的变位词,有效的变位词,https://leetcode.cn/problems/dKk3P7/,dKk3P7,哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/Offer-II/dKk3P7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20032.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%8F%98%E4%BD%8D%E8%AF%8D.md,59.8%,简单,273 -剑指 Offer II 033,Offer-II,剑指 Offer II 033. 变位词组,变位词组,https://leetcode.cn/problems/sfvd7V/,sfvd7V,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/Offer-II/sfvd7V/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20033.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84.md,74.7%,中等,234 -剑指 Offer II 034,Offer-II,剑指 Offer II 034. 外星语言是否排序,外星语言是否排序,https://leetcode.cn/problems/lwyVBB/,lwyVBB,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/lwyVBB/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20034.%20%E5%A4%96%E6%98%9F%E8%AF%AD%E8%A8%80%E6%98%AF%E5%90%A6%E6%8E%92%E5%BA%8F.md,55.7%,简单,306 -剑指 Offer II 035,Offer-II,剑指 Offer II 035. 最小时间差,最小时间差,https://leetcode.cn/problems/569nqc/,569nqc,数组、数学、字符串、排序,https://algo.itcharge.cn/Solutions/Offer-II/569nqc/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20035.%20%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4%E5%B7%AE.md,66.0%,中等,220 -剑指 Offer II 036,Offer-II,剑指 Offer II 036. 后缀表达式,后缀表达式,https://leetcode.cn/problems/8Zf90G/,8Zf90G,栈、数组、数学,https://algo.itcharge.cn/Solutions/Offer-II/8Zf90G/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20036.%20%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.md,55.5%,中等,283 -剑指 Offer II 037,Offer-II,剑指 Offer II 037. 小行星碰撞,小行星碰撞,https://leetcode.cn/problems/XagZNi/,XagZNi,栈、数组、模拟,https://algo.itcharge.cn/Solutions/Offer-II/XagZNi/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20037.%20%E5%B0%8F%E8%A1%8C%E6%98%9F%E7%A2%B0%E6%92%9E.md,44.8%,中等,336 -剑指 Offer II 038,Offer-II,剑指 Offer II 038. 每日温度,每日温度,https://leetcode.cn/problems/iIQa4I/,iIQa4I,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/Offer-II/iIQa4I/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20038.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md,76.2%,中等,338 -剑指 Offer II 039,Offer-II,剑指 Offer II 039. 直方图最大矩形面积,直方图最大矩形面积,https://leetcode.cn/problems/0ynMMM/,0ynMMM,栈、数组、单调栈,https://algo.itcharge.cn/Solutions/Offer-II/0ynMMM/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20039.%20%E7%9B%B4%E6%96%B9%E5%9B%BE%E6%9C%80%E5%A4%A7%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md,50.7%,困难,206 -剑指 Offer II 040,Offer-II,剑指 Offer II 040. 矩阵中最大的矩形,矩阵中最大的矩形,https://leetcode.cn/problems/PLYXKQ/,PLYXKQ,栈、数组、动态规划、矩阵、单调栈,https://algo.itcharge.cn/Solutions/Offer-II/PLYXKQ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20040.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.md,58.2%,困难,191 -剑指 Offer II 041,Offer-II,剑指 Offer II 041. 滑动窗口的平均值,滑动窗口的平均值,https://leetcode.cn/problems/qIsx9U/,qIsx9U,设计、队列、数组、数据流,https://algo.itcharge.cn/Solutions/Offer-II/qIsx9U/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20041.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC.md,77.8%,简单,479 -剑指 Offer II 042,Offer-II,剑指 Offer II 042. 最近请求次数,最近请求次数,https://leetcode.cn/problems/H8086Q/,H8086Q,设计、队列、数据流,https://algo.itcharge.cn/Solutions/Offer-II/H8086Q/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20042.%20%E6%9C%80%E8%BF%91%E8%AF%B7%E6%B1%82%E6%AC%A1%E6%95%B0.md,82.1%,简单,218 -剑指 Offer II 043,Offer-II,剑指 Offer II 043. 往完全二叉树添加节点,往完全二叉树添加节点,https://leetcode.cn/problems/NaqhDT/,NaqhDT,树、广度优先搜索、设计、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/NaqhDT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20043.%20%E5%BE%80%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9.md,62.4%,中等,264 -剑指 Offer II 044,Offer-II,剑指 Offer II 044. 二叉树每层的最大值,二叉树每层的最大值,https://leetcode.cn/problems/hPov7L/,hPov7L,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/hPov7L/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20044.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%AF%8F%E5%B1%82%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md,64.1%,中等,302 -剑指 Offer II 045,Offer-II,剑指 Offer II 045. 二叉树最底层最左边的值,二叉树最底层最左边的值,https://leetcode.cn/problems/LwUNpT/,LwUNpT,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/LwUNpT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20045.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%BA%95%E5%B1%82%E6%9C%80%E5%B7%A6%E8%BE%B9%E7%9A%84%E5%80%BC.md,79.2%,中等,341 -剑指 Offer II 046,Offer-II,剑指 Offer II 046. 二叉树的右侧视图,二叉树的右侧视图,https://leetcode.cn/problems/WNC0Lk/,WNC0Lk,树、深度优先搜索、广度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/WNC0Lk/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20046.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E4%BE%A7%E8%A7%86%E5%9B%BE.md,70.6%,中等,339 -剑指 Offer II 047,Offer-II,剑指 Offer II 047. 二叉树剪枝,二叉树剪枝,https://leetcode.cn/problems/pOCWxh/,pOCWxh,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/pOCWxh/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20047.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D.md,68.5%,中等,339 -剑指 Offer II 048,Offer-II,剑指 Offer II 048. 序列化与反序列化二叉树,序列化与反序列化二叉树,https://leetcode.cn/problems/h54YBf/,h54YBf,树、深度优先搜索、广度优先搜索、设计、字符串、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/h54YBf/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20048.%20%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91.md,67.0%,困难,185 -剑指 Offer II 049,Offer-II,剑指 Offer II 049. 从根节点到叶节点的路径数字之和,从根节点到叶节点的路径数字之和,https://leetcode.cn/problems/3Etpl5/,3Etpl5,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/3Etpl5/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20049.%20%E4%BB%8E%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md,73.8%,中等,291 -剑指 Offer II 050,Offer-II,剑指 Offer II 050. 向下的路径节点之和,向下的路径节点之和,https://leetcode.cn/problems/6eUYwP/,6eUYwP,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/6eUYwP/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20050.%20%E5%90%91%E4%B8%8B%E7%9A%84%E8%B7%AF%E5%BE%84%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C.md,51.2%,中等,220 -剑指 Offer II 051,Offer-II,剑指 Offer II 051. 节点之和最大的路径,节点之和最大的路径,https://leetcode.cn/problems/jC7MId/,jC7MId,树、深度优先搜索、动态规划、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/jC7MId/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20051.%20%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C%E6%9C%80%E5%A4%A7%E7%9A%84%E8%B7%AF%E5%BE%84.md,49.2%,困难,172 -剑指 Offer II 052,Offer-II,剑指 Offer II 052. 展平二叉搜索树,展平二叉搜索树,https://leetcode.cn/problems/NYBBNL/,NYBBNL,栈、树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/NYBBNL/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20052.%20%E5%B1%95%E5%B9%B3%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,73.6%,简单,275 -剑指 Offer II 053,Offer-II,剑指 Offer II 053. 二叉搜索树中的中序后继,二叉搜索树中的中序后继,https://leetcode.cn/problems/P5rCT8/,P5rCT8,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/P5rCT8/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20053.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%B8%AD%E5%BA%8F%E5%90%8E%E7%BB%A7.md,63.3%,中等,278 -剑指 Offer II 054,Offer-II,剑指 Offer II 054. 所有大于等于节点的值之和,所有大于等于节点的值之和,https://leetcode.cn/problems/w6cpku/,w6cpku,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/w6cpku/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20054.%20%E6%89%80%E6%9C%89%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%E4%B9%8B%E5%92%8C.md,85.6%,中等,277 -剑指 Offer II 055,Offer-II,剑指 Offer II 055. 二叉搜索树迭代器,二叉搜索树迭代器,https://leetcode.cn/problems/kTOapQ/,kTOapQ,栈、树、设计、二叉搜索树、二叉树、迭代器,https://algo.itcharge.cn/Solutions/Offer-II/kTOapQ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20055.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md,85.3%,中等,199 -剑指 Offer II 056,Offer-II,剑指 Offer II 056. 二叉搜索树中两个节点之和,二叉搜索树中两个节点之和,https://leetcode.cn/problems/opLdQZ/,opLdQZ,树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树,https://algo.itcharge.cn/Solutions/Offer-II/opLdQZ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20056.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E4%B8%A4%E4%B8%AA%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C.md,73.4%,简单,309 -剑指 Offer II 057,Offer-II,剑指 Offer II 057. 值和下标之差都在给定的范围内,值和下标之差都在给定的范围内,https://leetcode.cn/problems/7WqeDu/,7WqeDu,数组、桶排序、有序集合、排序、滑动窗口,https://algo.itcharge.cn/Solutions/Offer-II/7WqeDu/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20057.%20%E5%80%BC%E5%92%8C%E4%B8%8B%E6%A0%87%E4%B9%8B%E5%B7%AE%E9%83%BD%E5%9C%A8%E7%BB%99%E5%AE%9A%E7%9A%84%E8%8C%83%E5%9B%B4%E5%86%85.md,35.0%,中等,144 -剑指 Offer II 058,Offer-II,剑指 Offer II 058. 日程表,日程表,https://leetcode.cn/problems/fi9suh/,fi9suh,设计、线段树、二分查找、有序集合,https://algo.itcharge.cn/Solutions/Offer-II/fi9suh/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20058.%20%E6%97%A5%E7%A8%8B%E8%A1%A8.md,61.3%,中等,174 -剑指 Offer II 059,Offer-II,剑指 Offer II 059. 数据流的第 K 大数值,数据流的第 K 大数值,https://leetcode.cn/problems/jBjn9C/,jBjn9C,树、设计、二叉搜索树、二叉树、数据流、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer-II/jBjn9C/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20059.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E6%95%B0%E5%80%BC.md,62.8%,简单,199 -剑指 Offer II 060,Offer-II,剑指 Offer II 060. 出现频率最高的 k 个数字,出现频率最高的 k 个数字,https://leetcode.cn/problems/g5c51o/,g5c51o,数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer-II/g5c51o/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20060.%20%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%9C%80%E9%AB%98%E7%9A%84%20k%20%E4%B8%AA%E6%95%B0%E5%AD%97.md,68.8%,中等,273 -剑指 Offer II 061,Offer-II,剑指 Offer II 061. 和最小的 k 个数对,和最小的 k 个数对,https://leetcode.cn/problems/qn8gGX/,qn8gGX,数组、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer-II/qn8gGX/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20061.%20%E5%92%8C%E6%9C%80%E5%B0%8F%E7%9A%84%20k%20%E4%B8%AA%E6%95%B0%E5%AF%B9.md,53.5%,中等,186 -剑指 Offer II 062,Offer-II,剑指 Offer II 062. 实现前缀树,实现前缀树,https://leetcode.cn/problems/QC3q1f/,QC3q1f,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/QC3q1f/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20062.%20%E5%AE%9E%E7%8E%B0%E5%89%8D%E7%BC%80%E6%A0%91.md,75.6%,中等,205 -剑指 Offer II 063,Offer-II,剑指 Offer II 063. 替换单词,替换单词,https://leetcode.cn/problems/UhWRSj/,UhWRSj,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/UhWRSj/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20063.%20%E6%9B%BF%E6%8D%A2%E5%8D%95%E8%AF%8D.md,71.1%,中等,259 -剑指 Offer II 064,Offer-II,剑指 Offer II 064. 神奇的字典,神奇的字典,https://leetcode.cn/problems/US1pGT/,US1pGT,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/US1pGT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20064.%20%E7%A5%9E%E5%A5%87%E7%9A%84%E5%AD%97%E5%85%B8.md,61.0%,中等,174 -剑指 Offer II 065,Offer-II,剑指 Offer II 065. 最短的单词编码,最短的单词编码,https://leetcode.cn/problems/iSwD2y/,iSwD2y,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/iSwD2y/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20065.%20%E6%9C%80%E7%9F%AD%E7%9A%84%E5%8D%95%E8%AF%8D%E7%BC%96%E7%A0%81.md,63.3%,中等,175 -剑指 Offer II 066,Offer-II,剑指 Offer II 066. 单词之和,单词之和,https://leetcode.cn/problems/z1R5dt/,z1R5dt,设计、字典树、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/z1R5dt/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20066.%20%E5%8D%95%E8%AF%8D%E4%B9%8B%E5%92%8C.md,64.4%,中等,229 -剑指 Offer II 067,Offer-II,剑指 Offer II 067. 最大的异或,最大的异或,https://leetcode.cn/problems/ms70jA/,ms70jA,位运算、字典树、数组、哈希表,https://algo.itcharge.cn/Solutions/Offer-II/ms70jA/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20067.%20%E6%9C%80%E5%A4%A7%E7%9A%84%E5%BC%82%E6%88%96.md,65.7%,中等,150 -剑指 Offer II 068,Offer-II,剑指 Offer II 068. 查找插入位置,查找插入位置,https://leetcode.cn/problems/N6YdxV/,N6YdxV,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/N6YdxV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20068.%20%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md,49.1%,简单,275 -剑指 Offer II 069,Offer-II,剑指 Offer II 069. 山峰数组的顶部,山峰数组的顶部,https://leetcode.cn/problems/B1IidL/,B1IidL,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/B1IidL/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20069.%20%E5%B1%B1%E5%B3%B0%E6%95%B0%E7%BB%84%E7%9A%84%E9%A1%B6%E9%83%A8.md,70.6%,简单,610 -剑指 Offer II 070,Offer-II,剑指 Offer II 070. 排序数组中只出现一次的数字,排序数组中只出现一次的数字,https://leetcode.cn/problems/skFtm2/,skFtm2,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/skFtm2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20070.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md,61.4%,中等,262 -剑指 Offer II 071,Offer-II,剑指 Offer II 071. 按权重生成随机数,按权重生成随机数,https://leetcode.cn/problems/cuyjEf/,cuyjEf,数组、数学、二分查找、前缀和、随机化,https://algo.itcharge.cn/Solutions/Offer-II/cuyjEf/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20071.%20%E6%8C%89%E6%9D%83%E9%87%8D%E7%94%9F%E6%88%90%E9%9A%8F%E6%9C%BA%E6%95%B0.md,49.8%,中等,112 -剑指 Offer II 072,Offer-II,剑指 Offer II 072. 求平方根,求平方根,https://leetcode.cn/problems/jJ0w9p/,jJ0w9p,数学、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/jJ0w9p/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20072.%20%E6%B1%82%E5%B9%B3%E6%96%B9%E6%A0%B9.md,43.3%,简单,248 -剑指 Offer II 073,Offer-II,剑指 Offer II 073. 狒狒吃香蕉,狒狒吃香蕉,https://leetcode.cn/problems/nZZqjQ/,nZZqjQ,数组、二分查找,https://algo.itcharge.cn/Solutions/Offer-II/nZZqjQ/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20073.%20%E7%8B%92%E7%8B%92%E5%90%83%E9%A6%99%E8%95%89.md,53.3%,中等,189 -剑指 Offer II 074,Offer-II,剑指 Offer II 074. 合并区间,合并区间,https://leetcode.cn/problems/SsGoHC/,SsGoHC,数组、排序,https://algo.itcharge.cn/Solutions/Offer-II/SsGoHC/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20074.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md,56.6%,中等,246 -剑指 Offer II 075,Offer-II,剑指 Offer II 075. 数组相对排序,数组相对排序,https://leetcode.cn/problems/0H97ZC/,0H97ZC,数组、哈希表、计数排序、排序,https://algo.itcharge.cn/Solutions/Offer-II/0H97ZC/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20075.%20%E6%95%B0%E7%BB%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md,70.2%,简单,334 -剑指 Offer II 076,Offer-II,剑指 Offer II 076. 数组中的第 k 大的数字,数组中的第 k 大的数字,https://leetcode.cn/problems/xx4gT2/,xx4gT2,数组、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Offer-II/xx4gT2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20076.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%AC%20k%20%E5%A4%A7%E7%9A%84%E6%95%B0%E5%AD%97.md,67.1%,中等,302 -剑指 Offer II 077,Offer-II,剑指 Offer II 077. 链表排序,链表排序,https://leetcode.cn/problems/7WHec2/,7WHec2,链表、双指针、分治、排序、归并排序,https://algo.itcharge.cn/Solutions/Offer-II/7WHec2/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20077.%20%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F.md,62.9%,中等,364 -剑指 Offer II 078,Offer-II,剑指 Offer II 078. 合并排序链表,合并排序链表,https://leetcode.cn/problems/vvXgSW/,vvXgSW,链表、分治、堆(优先队列)、归并排序,https://algo.itcharge.cn/Solutions/Offer-II/vvXgSW/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20078.%20%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md,64.0%,困难,268 -剑指 Offer II 079,Offer-II,剑指 Offer II 079. 所有子集,所有子集,https://leetcode.cn/problems/TVdhkn/,TVdhkn,位运算、数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/TVdhkn/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20079.%20%E6%89%80%E6%9C%89%E5%AD%90%E9%9B%86.md,85.3%,中等,310 -剑指 Offer II 080,Offer-II,剑指 Offer II 080. 含有 k 个元素的组合,含有 k 个元素的组合,https://leetcode.cn/problems/uUsW3B/,uUsW3B,数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/uUsW3B/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20080.%20%E5%90%AB%E6%9C%89%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E7%BB%84%E5%90%88.md,83.3%,中等,204 -剑指 Offer II 081,Offer-II,剑指 Offer II 081. 允许重复选择元素的组合,允许重复选择元素的组合,https://leetcode.cn/problems/Ygoe9J/,Ygoe9J,数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/Ygoe9J/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20081.%20%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%E7%9A%84%E7%BB%84%E5%90%88.md,80.7%,中等,240 -剑指 Offer II 082,Offer-II,剑指 Offer II 082. 含有重复元素集合的组合,含有重复元素集合的组合,https://leetcode.cn/problems/4sjJUc/,4sjJUc,数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/4sjJUc/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20082.%20%E5%90%AB%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E7%BB%84%E5%90%88.md,65.9%,中等,200 -剑指 Offer II 083,Offer-II,剑指 Offer II 083. 没有重复元素集合的全排列,没有重复元素集合的全排列,https://leetcode.cn/problems/VvJkup/,VvJkup,数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/VvJkup/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20083.%20%E6%B2%A1%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E5%85%A8%E6%8E%92%E5%88%97.md,85.8%,中等,281 -剑指 Offer II 084,Offer-II,剑指 Offer II 084. 含有重复元素集合的全排列,含有重复元素集合的全排列,https://leetcode.cn/problems/7p8L0Z/,7p8L0Z,数组、回溯,https://algo.itcharge.cn/Solutions/Offer-II/7p8L0Z/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20084.%20%E5%90%AB%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E5%85%A8%E6%8E%92%E5%88%97.md,69.4%,中等,202 -剑指 Offer II 085,Offer-II,剑指 Offer II 085. 生成匹配的括号,生成匹配的括号,https://leetcode.cn/problems/IDBivT/,IDBivT,字符串、动态规划、回溯,https://algo.itcharge.cn/Solutions/Offer-II/IDBivT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20085.%20%E7%94%9F%E6%88%90%E5%8C%B9%E9%85%8D%E7%9A%84%E6%8B%AC%E5%8F%B7.md,84.9%,中等,286 -剑指 Offer II 086,Offer-II,剑指 Offer II 086. 分割回文子字符串,分割回文子字符串,https://leetcode.cn/problems/M99OJA/,M99OJA,深度优先搜索、广度优先搜索、图、哈希表,https://algo.itcharge.cn/Solutions/Offer-II/M99OJA/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20086.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md,75.6%,中等,164 -剑指 Offer II 087,Offer-II,剑指 Offer II 087. 复原 IP,复原 IP,https://leetcode.cn/problems/0on3uN/,0on3uN,字符串、回溯,https://algo.itcharge.cn/Solutions/Offer-II/0on3uN/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20087.%20%E5%A4%8D%E5%8E%9F%20IP.md,63.2%,中等,216 -剑指 Offer II 088,Offer-II,剑指 Offer II 088. 爬楼梯的最少成本,爬楼梯的最少成本,https://leetcode.cn/problems/GzCJIP/,GzCJIP,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/GzCJIP/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20088.%20%E7%88%AC%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E5%B0%91%E6%88%90%E6%9C%AC.md,72.4%,简单,300 -剑指 Offer II 089,Offer-II,剑指 Offer II 089. 房屋偷盗,房屋偷盗,https://leetcode.cn/problems/Gu0c2T/,Gu0c2T,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/Gu0c2T/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20089.%20%E6%88%BF%E5%B1%8B%E5%81%B7%E7%9B%97.md,60.6%,中等,205 -剑指 Offer II 090,Offer-II,剑指 Offer II 090. 环形房屋偷盗,环形房屋偷盗,https://leetcode.cn/problems/PzWKhm/,PzWKhm,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/PzWKhm/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20090.%20%E7%8E%AF%E5%BD%A2%E6%88%BF%E5%B1%8B%E5%81%B7%E7%9B%97.md,48.8%,中等,157 -剑指 Offer II 091,Offer-II,剑指 Offer II 091. 粉刷房子,粉刷房子,https://leetcode.cn/problems/JEj789/,JEj789,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/JEj789/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20091.%20%E7%B2%89%E5%88%B7%E6%88%BF%E5%AD%90.md,77.5%,中等,550 -剑指 Offer II 092,Offer-II,剑指 Offer II 092. 翻转字符,翻转字符,https://leetcode.cn/problems/cyJERH/,cyJERH,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/cyJERH/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20092.%20%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6.md,68.1%,中等,243 -剑指 Offer II 093,Offer-II,剑指 Offer II 093. 最长斐波那契数列,最长斐波那契数列,https://leetcode.cn/problems/Q91FMA/,Q91FMA,数组、哈希表、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/Q91FMA/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20093.%20%E6%9C%80%E9%95%BF%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md,58.4%,中等,137 -剑指 Offer II 094,Offer-II,剑指 Offer II 094. 最少回文分割,最少回文分割,https://leetcode.cn/problems/omKAoA/,omKAoA,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/omKAoA/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20094.%20%E6%9C%80%E5%B0%91%E5%9B%9E%E6%96%87%E5%88%86%E5%89%B2.md,57.7%,困难,108 -剑指 Offer II 095,Offer-II,剑指 Offer II 095. 最长公共子序列,最长公共子序列,https://leetcode.cn/problems/qJnOS7/,qJnOS7,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/qJnOS7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20095.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md,65.6%,中等,272 -剑指 Offer II 096,Offer-II,剑指 Offer II 096. 字符串交织,字符串交织,https://leetcode.cn/problems/IY6buf/,IY6buf,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/IY6buf/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20096.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%A4%E7%BB%87.md,48.1%,中等,123 -剑指 Offer II 097,Offer-II,剑指 Offer II 097. 子序列的数目,子序列的数目,https://leetcode.cn/problems/21dk04/,21dk04,字符串、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/21dk04/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20097.%20%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,55.3%,困难,121 -剑指 Offer II 098,Offer-II,剑指 Offer II 098. 路径的数目,路径的数目,https://leetcode.cn/problems/2AoeFn/,2AoeFn,数学、动态规划、组合数学,https://algo.itcharge.cn/Solutions/Offer-II/2AoeFn/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20098.%20%E8%B7%AF%E5%BE%84%E7%9A%84%E6%95%B0%E7%9B%AE.md,76.2%,中等,209 -剑指 Offer II 099,Offer-II,剑指 Offer II 099. 最小路径之和,最小路径之和,https://leetcode.cn/problems/0i0mDW/,0i0mDW,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/Offer-II/0i0mDW/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20099.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%B9%8B%E5%92%8C.md,73.4%,中等,220 -剑指 Offer II 100,Offer-II,剑指 Offer II 100. 三角形中最小路径之和,三角形中最小路径之和,https://leetcode.cn/problems/IlPe0q/,IlPe0q,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/IlPe0q/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20100.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E4%B8%AD%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%B9%8B%E5%92%8C.md,74.4%,中等,220 -剑指 Offer II 101,Offer-II,剑指 Offer II 101. 分割等和子集,分割等和子集,https://leetcode.cn/problems/NUPfPr/,NUPfPr,数学、字符串、模拟,https://algo.itcharge.cn/Solutions/Offer-II/NUPfPr/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20101.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md,49.4%,简单,163 -剑指 Offer II 102,Offer-II,剑指 Offer II 102. 加减的目标值,加减的目标值,https://leetcode.cn/problems/YaVDxD/,YaVDxD,数组、动态规划、回溯,https://algo.itcharge.cn/Solutions/Offer-II/YaVDxD/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20102.%20%E5%8A%A0%E5%87%8F%E7%9A%84%E7%9B%AE%E6%A0%87%E5%80%BC.md,56.7%,中等,139 -剑指 Offer II 103,Offer-II,剑指 Offer II 103. 最少的硬币数目,最少的硬币数目,https://leetcode.cn/problems/gaM7Ch/,gaM7Ch,广度优先搜索、数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/gaM7Ch/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20103.%20%E6%9C%80%E5%B0%91%E7%9A%84%E7%A1%AC%E5%B8%81%E6%95%B0%E7%9B%AE.md,50.9%,中等,185 -剑指 Offer II 104,Offer-II,剑指 Offer II 104. 排列的数目,排列的数目,https://leetcode.cn/problems/D0F0SV/,D0F0SV,数组、动态规划,https://algo.itcharge.cn/Solutions/Offer-II/D0F0SV/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20104.%20%E6%8E%92%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md,57.6%,中等,112 -剑指 Offer II 105,Offer-II,剑指 Offer II 105. 岛屿的最大面积,岛屿的最大面积,https://leetcode.cn/problems/ZL6zAn/,ZL6zAn,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/Offer-II/ZL6zAn/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20105.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md,69.7%,中等,295 -剑指 Offer II 106,Offer-II,剑指 Offer II 106. 二分图,二分图,https://leetcode.cn/problems/vEAB3K/,vEAB3K,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/Offer-II/vEAB3K/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20106.%20%E4%BA%8C%E5%88%86%E5%9B%BE.md,55.3%,中等,149 -剑指 Offer II 107,Offer-II,剑指 Offer II 107. 矩阵中的距离,矩阵中的距离,https://leetcode.cn/problems/2bCMpM/,2bCMpM,广度优先搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/Offer-II/2bCMpM/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20107.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%9D%E7%A6%BB.md,51.1%,中等,118 -剑指 Offer II 108,Offer-II,剑指 Offer II 108. 单词演变,单词演变,https://leetcode.cn/problems/om3reC/,om3reC,广度优先搜索、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/om3reC/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20108.%20%E5%8D%95%E8%AF%8D%E6%BC%94%E5%8F%98.md,59.5%,困难,87 -剑指 Offer II 109,Offer-II,剑指 Offer II 109. 开密码锁,开密码锁,https://leetcode.cn/problems/zlDJc7/,zlDJc7,广度优先搜索、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Offer-II/zlDJc7/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20109.%20%E5%BC%80%E5%AF%86%E7%A0%81%E9%94%81.md,57.4%,中等,125 -剑指 Offer II 110,Offer-II,剑指 Offer II 110. 所有路径,所有路径,https://leetcode.cn/problems/bP4bmD/,bP4bmD,深度优先搜索、广度优先搜索、图、回溯,https://algo.itcharge.cn/Solutions/Offer-II/bP4bmD/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20110.%20%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84.md,81.0%,中等,210 -剑指 Offer II 111,Offer-II,剑指 Offer II 111. 计算除法,计算除法,https://leetcode.cn/problems/vlzXQL/,vlzXQL,深度优先搜索、广度优先搜索、并查集、图、数组、最短路,https://algo.itcharge.cn/Solutions/Offer-II/vlzXQL/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20111.%20%E8%AE%A1%E7%AE%97%E9%99%A4%E6%B3%95.md,64.9%,中等,115 -剑指 Offer II 112,Offer-II,剑指 Offer II 112. 最长递增路径,最长递增路径,https://leetcode.cn/problems/fpTFWP/,fpTFWP,深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/Offer-II/fpTFWP/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20112.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md,57.9%,困难,130 -剑指 Offer II 113,Offer-II,剑指 Offer II 113. 课程顺序,课程顺序,https://leetcode.cn/problems/QA2IGt/,QA2IGt,深度优先搜索、广度优先搜索、图、拓扑排序,https://algo.itcharge.cn/Solutions/Offer-II/QA2IGt/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20113.%20%E8%AF%BE%E7%A8%8B%E9%A1%BA%E5%BA%8F.md,56.7%,中等,150 -剑指 Offer II 114,Offer-II,剑指 Offer II 114. 外星文字典,外星文字典,https://leetcode.cn/problems/Jf1JuT/,Jf1JuT,深度优先搜索、广度优先搜索、图、拓扑排序、数组、字符串,https://algo.itcharge.cn/Solutions/Offer-II/Jf1JuT/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20114.%20%E5%A4%96%E6%98%9F%E6%96%87%E5%AD%97%E5%85%B8.md,51.8%,困难,215 -剑指 Offer II 115,Offer-II,剑指 Offer II 115. 重建序列,重建序列,https://leetcode.cn/problems/ur2n8P/,ur2n8P,图、拓扑排序、数组,https://algo.itcharge.cn/Solutions/Offer-II/ur2n8P/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20115.%20%E9%87%8D%E5%BB%BA%E5%BA%8F%E5%88%97.md,51.1%,中等,280 -剑指 Offer II 116,Offer-II,剑指 Offer II 116. 省份数量,省份数量,https://leetcode.cn/problems/bLyHh0/,bLyHh0,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/Offer-II/bLyHh0/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20116.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md,67.4%,中等,213 -剑指 Offer II 117,Offer-II,剑指 Offer II 117. 相似的字符串,相似的字符串,https://leetcode.cn/problems/H6lPxb/,H6lPxb,深度优先搜索、广度优先搜索、并查集、数组、字符串,https://algo.itcharge.cn/Solutions/Offer-II/H6lPxb/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20117.%20%E7%9B%B8%E4%BC%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md,61.9%,困难,96 -剑指 Offer II 118,Offer-II,剑指 Offer II 118. 多余的边,多余的边,https://leetcode.cn/problems/7LpjUW/,7LpjUW,深度优先搜索、广度优先搜索、并查集、图,https://algo.itcharge.cn/Solutions/Offer-II/7LpjUW/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20118.%20%E5%A4%9A%E4%BD%99%E7%9A%84%E8%BE%B9.md,69.4%,中等,126 -剑指 Offer II 119,Offer-II,剑指 Offer II 119. 最长连续序列,最长连续序列,https://leetcode.cn/problems/WhsWhI/,WhsWhI,并查集、数组、哈希表,https://algo.itcharge.cn/Solutions/Offer-II/WhsWhI/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20119.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md,49.2%,中等,228 -面试题 01.01,Interviews,面试题 01.01. 判定字符是否唯一,判定字符是否唯一,https://leetcode.cn/problems/is-unique-lcci/,is-unique-lcci,位运算、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/Interviews/is-unique-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.01.%20%E5%88%A4%E5%AE%9A%E5%AD%97%E7%AC%A6%E6%98%AF%E5%90%A6%E5%94%AF%E4%B8%80.md,71.0%,简单,2189 -面试题 01.02,Interviews,面试题 01.02. 判定是否互为字符重排,判定是否互为字符重排,https://leetcode.cn/problems/check-permutation-lcci/,check-permutation-lcci,哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/Interviews/check-permutation-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.02.%20%E5%88%A4%E5%AE%9A%E6%98%AF%E5%90%A6%E4%BA%92%E4%B8%BA%E5%AD%97%E7%AC%A6%E9%87%8D%E6%8E%92.md,65.7%,简单,1868 -面试题 01.03,Interviews,面试题 01.03. URL化,URL化,https://leetcode.cn/problems/string-to-url-lcci/,string-to-url-lcci,字符串,https://algo.itcharge.cn/Solutions/Interviews/string-to-url-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.03.%20URL%E5%8C%96.md,57.4%,简单,744 -面试题 01.04,Interviews,面试题 01.04. 回文排列,回文排列,https://leetcode.cn/problems/palindrome-permutation-lcci/,palindrome-permutation-lcci,位运算、哈希表、字符串,https://algo.itcharge.cn/Solutions/Interviews/palindrome-permutation-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.04.%20%E5%9B%9E%E6%96%87%E6%8E%92%E5%88%97.md,53.7%,简单,1144 -面试题 01.05,Interviews,面试题 01.05. 一次编辑,一次编辑,https://leetcode.cn/problems/one-away-lcci/,one-away-lcci,双指针、字符串,https://algo.itcharge.cn/Solutions/Interviews/one-away-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.05.%20%E4%B8%80%E6%AC%A1%E7%BC%96%E8%BE%91.md,35.2%,中等,1339 -面试题 01.06,Interviews,面试题 01.06. 字符串压缩,字符串压缩,https://leetcode.cn/problems/compress-string-lcci/,compress-string-lcci,双指针、字符串,https://algo.itcharge.cn/Solutions/Interviews/compress-string-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.06.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8E%8B%E7%BC%A9.md,46.2%,简单,1464 -面试题 01.07,Interviews,面试题 01.07. 旋转矩阵,旋转矩阵,https://leetcode.cn/problems/rotate-matrix-lcci/,rotate-matrix-lcci,数组、数学、矩阵,https://algo.itcharge.cn/Solutions/Interviews/rotate-matrix-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.07.%20%E6%97%8B%E8%BD%AC%E7%9F%A9%E9%98%B5.md,73.2%,中等,1224 -面试题 01.08,Interviews,面试题 01.08. 零矩阵,零矩阵,https://leetcode.cn/problems/zero-matrix-lcci/,zero-matrix-lcci,数组、哈希表、矩阵,https://algo.itcharge.cn/Solutions/Interviews/zero-matrix-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.08.%20%E9%9B%B6%E7%9F%A9%E9%98%B5.md,64.4%,中等,997 -面试题 01.09,Interviews,面试题 01.09. 字符串轮转,字符串轮转,https://leetcode.cn/problems/string-rotation-lcci/,string-rotation-lcci,字符串、字符串匹配,https://algo.itcharge.cn/Solutions/Interviews/string-rotation-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.09.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AE%E8%BD%AC.md,54.2%,简单,852 -面试题 02.01,Interviews,面试题 02.01. 移除重复节点,移除重复节点,https://leetcode.cn/problems/remove-duplicate-node-lcci/,remove-duplicate-node-lcci,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/remove-duplicate-node-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.01.%20%E7%A7%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9.md,66.8%,简单,880 -面试题 02.02,Interviews,面试题 02.02. 返回倒数第 k 个节点,返回倒数第 k 个节点,https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/,kth-node-from-end-of-list-lcci,链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/kth-node-from-end-of-list-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.02.%20%E8%BF%94%E5%9B%9E%E5%80%92%E6%95%B0%E7%AC%AC%20k%20%E4%B8%AA%E8%8A%82%E7%82%B9.md,78.1%,简单,1223 -面试题 02.03,Interviews,面试题 02.03. 删除中间节点,删除中间节点,https://leetcode.cn/problems/delete-middle-node-lcci/,delete-middle-node-lcci,链表,https://algo.itcharge.cn/Solutions/Interviews/delete-middle-node-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.03.%20%E5%88%A0%E9%99%A4%E4%B8%AD%E9%97%B4%E8%8A%82%E7%82%B9.md,86.0%,简单,861 -面试题 02.04,Interviews,面试题 02.04. 分割链表,分割链表,https://leetcode.cn/problems/partition-list-lcci/,partition-list-lcci,链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/partition-list-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.04.%20%E5%88%86%E5%89%B2%E9%93%BE%E8%A1%A8.md,61.1%,中等,567 -面试题 02.05,Interviews,面试题 02.05. 链表求和,链表求和,https://leetcode.cn/problems/sum-lists-lcci/,sum-lists-lcci,递归、链表、数学,https://algo.itcharge.cn/Solutions/Interviews/sum-lists-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.05.%20%E9%93%BE%E8%A1%A8%E6%B1%82%E5%92%8C.md,47.0%,中等,758 -面试题 02.06,Interviews,面试题 02.06. 回文链表,回文链表,https://leetcode.cn/problems/palindrome-linked-list-lcci/,palindrome-linked-list-lcci,栈、递归、链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/palindrome-linked-list-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.06.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md,48.8%,简单,686 -面试题 02.07,Interviews,面试题 02.07. 链表相交,链表相交,https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/,intersection-of-two-linked-lists-lcci,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/intersection-of-two-linked-lists-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.07.%20%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.md,66.3%,简单,1137 -面试题 02.08,Interviews,面试题 02.08. 环路检测,环路检测,https://leetcode.cn/problems/linked-list-cycle-lcci/,linked-list-cycle-lcci,哈希表、链表、双指针,https://algo.itcharge.cn/Solutions/Interviews/linked-list-cycle-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.08.%20%E7%8E%AF%E8%B7%AF%E6%A3%80%E6%B5%8B.md,55.1%,中等,408 -面试题 03.01,Interviews,面试题 03.01. 三合一,三合一,https://leetcode.cn/problems/three-in-one-lcci/,three-in-one-lcci,栈、设计、数组,https://algo.itcharge.cn/Solutions/Interviews/three-in-one-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.01.%20%E4%B8%89%E5%90%88%E4%B8%80.md,53.6%,简单,286 -面试题 03.02,Interviews,面试题 03.02. 栈的最小值,栈的最小值,https://leetcode.cn/problems/min-stack-lcci/,min-stack-lcci,栈、设计,https://algo.itcharge.cn/Solutions/Interviews/min-stack-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.02.%20%E6%A0%88%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md,62.1%,简单,414 -面试题 03.03,Interviews,面试题 03.03. 堆盘子,堆盘子,https://leetcode.cn/problems/stack-of-plates-lcci/,stack-of-plates-lcci,栈、设计、链表,https://algo.itcharge.cn/Solutions/Interviews/stack-of-plates-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.03.%20%E5%A0%86%E7%9B%98%E5%AD%90.md,38.5%,中等,210 -面试题 03.04,Interviews,面试题 03.04. 化栈为队,化栈为队,https://leetcode.cn/problems/implement-queue-using-stacks-lcci/,implement-queue-using-stacks-lcci,栈、设计、队列,https://algo.itcharge.cn/Solutions/Interviews/implement-queue-using-stacks-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.04.%20%E5%8C%96%E6%A0%88%E4%B8%BA%E9%98%9F.md,71.8%,简单,441 -面试题 03.05,Interviews,面试题 03.05. 栈排序,栈排序,https://leetcode.cn/problems/sort-of-stacks-lcci/,sort-of-stacks-lcci,栈、设计、单调栈,https://algo.itcharge.cn/Solutions/Interviews/sort-of-stacks-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.05.%20%E6%A0%88%E6%8E%92%E5%BA%8F.md,53.6%,中等,346 -面试题 03.06,Interviews,面试题 03.06. 动物收容所,动物收容所,https://leetcode.cn/problems/animal-shelter-lcci/,animal-shelter-lcci,设计、队列,https://algo.itcharge.cn/Solutions/Interviews/animal-shelter-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.06.%20%E5%8A%A8%E7%89%A9%E6%94%B6%E5%AE%B9%E6%89%80.md,58.3%,简单,259 -面试题 04.01,Interviews,面试题 04.01. 节点间通路,节点间通路,https://leetcode.cn/problems/route-between-nodes-lcci/,route-between-nodes-lcci,深度优先搜索、广度优先搜索、图,https://algo.itcharge.cn/Solutions/Interviews/route-between-nodes-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.01.%20%E8%8A%82%E7%82%B9%E9%97%B4%E9%80%9A%E8%B7%AF.md,53.4%,中等,385 -面试题 04.02,Interviews,面试题 04.02. 最小高度树,最小高度树,https://leetcode.cn/problems/minimum-height-tree-lcci/,minimum-height-tree-lcci,树、二叉搜索树、数组、分治、二叉树,https://algo.itcharge.cn/Solutions/Interviews/minimum-height-tree-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.02.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md,78.9%,简单,562 -面试题 04.03,Interviews,面试题 04.03. 特定深度节点链表,特定深度节点链表,https://leetcode.cn/problems/list-of-depth-lcci/,list-of-depth-lcci,树、广度优先搜索、链表、二叉树,https://algo.itcharge.cn/Solutions/Interviews/list-of-depth-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.03.%20%E7%89%B9%E5%AE%9A%E6%B7%B1%E5%BA%A6%E8%8A%82%E7%82%B9%E9%93%BE%E8%A1%A8.md,80.7%,中等,717 -面试题 04.04,Interviews,面试题 04.04. 检查平衡性,检查平衡性,https://leetcode.cn/problems/check-balance-lcci/,check-balance-lcci,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Interviews/check-balance-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.04.%20%E6%A3%80%E6%9F%A5%E5%B9%B3%E8%A1%A1%E6%80%A7.md,59.8%,简单,470 -面试题 04.05,Interviews,面试题 04.05. 合法二叉搜索树,合法二叉搜索树,https://leetcode.cn/problems/legal-binary-search-tree-lcci/,legal-binary-search-tree-lcci,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Interviews/legal-binary-search-tree-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.05.%20%E5%90%88%E6%B3%95%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md,35.7%,中等,445 -面试题 04.06,Interviews,面试题 04.06. 后继者,后继者,https://leetcode.cn/problems/successor-lcci/,successor-lcci,树、深度优先搜索、二叉搜索树、二叉树,https://algo.itcharge.cn/Solutions/Interviews/successor-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.06.%20%E5%90%8E%E7%BB%A7%E8%80%85.md,62.6%,中等,668 -面试题 04.08,Interviews,面试题 04.08. 首个共同祖先,首个共同祖先,https://leetcode.cn/problems/first-common-ancestor-lcci/,first-common-ancestor-lcci,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Interviews/first-common-ancestor-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.08.%20%E9%A6%96%E4%B8%AA%E5%85%B1%E5%90%8C%E7%A5%96%E5%85%88.md,71.6%,中等,272 -面试题 04.09,Interviews,面试题 04.09. 二叉搜索树序列,二叉搜索树序列,https://leetcode.cn/problems/bst-sequences-lcci/,bst-sequences-lcci,树、二叉搜索树、回溯、二叉树,https://algo.itcharge.cn/Solutions/Interviews/bst-sequences-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.09.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%BA%8F%E5%88%97.md,48.8%,困难,161 -面试题 04.10,Interviews,面试题 04.10. 检查子树,检查子树,https://leetcode.cn/problems/check-subtree-lcci/,check-subtree-lcci,树、深度优先搜索、二叉树、字符串匹配、哈希函数,https://algo.itcharge.cn/Solutions/Interviews/check-subtree-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.10.%20%E6%A3%80%E6%9F%A5%E5%AD%90%E6%A0%91.md,67.5%,中等,384 -面试题 04.12,Interviews,面试题 04.12. 求和路径,求和路径,https://leetcode.cn/problems/paths-with-sum-lcci/,paths-with-sum-lcci,树、深度优先搜索、二叉树,https://algo.itcharge.cn/Solutions/Interviews/paths-with-sum-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.12.%20%E6%B1%82%E5%92%8C%E8%B7%AF%E5%BE%84.md,49.0%,中等,353 -面试题 05.01,Interviews,面试题 05.01. 插入,插入,https://leetcode.cn/problems/insert-into-bits-lcci/,insert-into-bits-lcci,位运算,https://algo.itcharge.cn/Solutions/Interviews/insert-into-bits-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.01.%20%E6%8F%92%E5%85%A5.md,51.4%,简单,302 -面试题 05.02,Interviews,面试题 05.02. 二进制数转字符串,二进制数转字符串,https://leetcode.cn/problems/binary-number-to-string-lcci/,binary-number-to-string-lcci,位运算、数学、字符串,https://algo.itcharge.cn/Solutions/Interviews/binary-number-to-string-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.02.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md,76.8%,中等,425 -面试题 05.03,Interviews,面试题 05.03. 翻转数位,翻转数位,https://leetcode.cn/problems/reverse-bits-lcci/,reverse-bits-lcci,位运算、动态规划,https://algo.itcharge.cn/Solutions/Interviews/reverse-bits-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.03.%20%E7%BF%BB%E8%BD%AC%E6%95%B0%E4%BD%8D.md,37.6%,简单,358 -面试题 05.04,Interviews,面试题 05.04. 下一个数,下一个数,https://leetcode.cn/problems/closed-number-lcci/,closed-number-lcci,位运算,https://algo.itcharge.cn/Solutions/Interviews/closed-number-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.04.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%95%B0.md,35.6%,中等,198 -面试题 05.06,Interviews,面试题 05.06. 整数转换,整数转换,https://leetcode.cn/problems/convert-integer-lcci/,convert-integer-lcci,位运算,https://algo.itcharge.cn/Solutions/Interviews/convert-integer-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.06.%20%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2.md,51.6%,简单,334 -面试题 05.07,Interviews,面试题 05.07. 配对交换,配对交换,https://leetcode.cn/problems/exchange-lcci/,exchange-lcci,位运算,https://algo.itcharge.cn/Solutions/Interviews/exchange-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.07.%20%E9%85%8D%E5%AF%B9%E4%BA%A4%E6%8D%A2.md,71.0%,简单,294 -面试题 05.08,Interviews,面试题 05.08. 绘制直线,绘制直线,https://leetcode.cn/problems/draw-line-lcci/,draw-line-lcci,位运算、数组、数学,https://algo.itcharge.cn/Solutions/Interviews/draw-line-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2005.08.%20%E7%BB%98%E5%88%B6%E7%9B%B4%E7%BA%BF.md,53.7%,中等,134 -面试题 08.01,Interviews,面试题 08.01. 三步问题,三步问题,https://leetcode.cn/problems/three-steps-problem-lcci/,three-steps-problem-lcci,记忆化搜索、数学、动态规划,https://algo.itcharge.cn/Solutions/Interviews/three-steps-problem-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.01.%20%E4%B8%89%E6%AD%A5%E9%97%AE%E9%A2%98.md,36.7%,简单,656 -面试题 08.02,Interviews,面试题 08.02. 迷路的机器人,迷路的机器人,https://leetcode.cn/problems/robot-in-a-grid-lcci/,robot-in-a-grid-lcci,数组、动态规划、回溯、矩阵,https://algo.itcharge.cn/Solutions/Interviews/robot-in-a-grid-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.02.%20%E8%BF%B7%E8%B7%AF%E7%9A%84%E6%9C%BA%E5%99%A8%E4%BA%BA.md,36.2%,中等,308 -面试题 08.03,Interviews,面试题 08.03. 魔术索引,魔术索引,https://leetcode.cn/problems/magic-index-lcci/,magic-index-lcci,数组、二分查找,https://algo.itcharge.cn/Solutions/Interviews/magic-index-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.03.%20%E9%AD%94%E6%9C%AF%E7%B4%A2%E5%BC%95.md,67.4%,简单,490 -面试题 08.04,Interviews,面试题 08.04. 幂集,幂集,https://leetcode.cn/problems/power-set-lcci/,power-set-lcci,位运算、数组、回溯,https://algo.itcharge.cn/Solutions/Interviews/power-set-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.04.%20%E5%B9%82%E9%9B%86.md,82.1%,中等,459 -面试题 08.05,Interviews,面试题 08.05. 递归乘法,递归乘法,https://leetcode.cn/problems/recursive-mulitply-lcci/,recursive-mulitply-lcci,位运算、递归、数学,https://algo.itcharge.cn/Solutions/Interviews/recursive-mulitply-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.05.%20%E9%80%92%E5%BD%92%E4%B9%98%E6%B3%95.md,65.6%,中等,752 -面试题 08.06,Interviews,面试题 08.06. 汉诺塔问题,汉诺塔问题,https://leetcode.cn/problems/hanota-lcci/,hanota-lcci,递归、数组,https://algo.itcharge.cn/Solutions/Interviews/hanota-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.06.%20%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98.md,64.8%,简单,418 -面试题 08.07,Interviews,面试题 08.07. 无重复字符串的排列组合,无重复字符串的排列组合,https://leetcode.cn/problems/permutation-i-lcci/,permutation-i-lcci,字符串、回溯,https://algo.itcharge.cn/Solutions/Interviews/permutation-i-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.07.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88.md,81.0%,中等,464 -面试题 08.08,Interviews,面试题 08.08. 有重复字符串的排列组合,有重复字符串的排列组合,https://leetcode.cn/problems/permutation-ii-lcci/,permutation-ii-lcci,字符串、回溯,https://algo.itcharge.cn/Solutions/Interviews/permutation-ii-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.08.%20%E6%9C%89%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88.md,69.8%,中等,370 -面试题 08.09,Interviews,面试题 08.09. 括号,括号,https://leetcode.cn/problems/bracket-lcci/,bracket-lcci,字符串、动态规划、回溯,https://algo.itcharge.cn/Solutions/Interviews/bracket-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.09.%20%E6%8B%AC%E5%8F%B7.md,82.1%,中等,497 -面试题 08.10,Interviews,面试题 08.10. 颜色填充,颜色填充,https://leetcode.cn/problems/color-fill-lcci/,color-fill-lcci,深度优先搜索、广度优先搜索、数组、矩阵,https://algo.itcharge.cn/Solutions/Interviews/color-fill-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.10.%20%E9%A2%9C%E8%89%B2%E5%A1%AB%E5%85%85.md,55.7%,简单,335 -面试题 08.11,Interviews,面试题 08.11. 硬币,硬币,https://leetcode.cn/problems/coin-lcci/,coin-lcci,数组、数学、动态规划,https://algo.itcharge.cn/Solutions/Interviews/coin-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.11.%20%E7%A1%AC%E5%B8%81.md,49.7%,中等,376 -面试题 08.12,Interviews,面试题 08.12. 八皇后,八皇后,https://leetcode.cn/problems/eight-queens-lcci/,eight-queens-lcci,数组、回溯,https://algo.itcharge.cn/Solutions/Interviews/eight-queens-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.12.%20%E5%85%AB%E7%9A%87%E5%90%8E.md,76.4%,困难,419 -面试题 08.13,Interviews,面试题 08.13. 堆箱子,堆箱子,https://leetcode.cn/problems/pile-box-lcci/,pile-box-lcci,数组、动态规划、排序,https://algo.itcharge.cn/Solutions/Interviews/pile-box-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.13.%20%E5%A0%86%E7%AE%B1%E5%AD%90.md,51.3%,困难,133 -面试题 08.14,Interviews,面试题 08.14. 布尔运算,布尔运算,https://leetcode.cn/problems/boolean-evaluation-lcci/,boolean-evaluation-lcci,记忆化搜索、字符串、动态规划,https://algo.itcharge.cn/Solutions/Interviews/boolean-evaluation-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.14.%20%E5%B8%83%E5%B0%94%E8%BF%90%E7%AE%97.md,52.4%,中等,115 -面试题 10.01,Interviews,面试题 10.01. 合并排序的数组,合并排序的数组,https://leetcode.cn/problems/sorted-merge-lcci/,sorted-merge-lcci,数组、双指针、排序,https://algo.itcharge.cn/Solutions/Interviews/sorted-merge-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.01.%20%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E7%9A%84%E6%95%B0%E7%BB%84.md,56.1%,简单,2537 -面试题 10.02,Interviews,面试题 10.02. 变位词组,变位词组,https://leetcode.cn/problems/group-anagrams-lcci/,group-anagrams-lcci,数组、哈希表、字符串、排序,https://algo.itcharge.cn/Solutions/Interviews/group-anagrams-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.02.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84.md,74.3%,中等,480 -面试题 10.03,Interviews,面试题 10.03. 搜索旋转数组,搜索旋转数组,https://leetcode.cn/problems/search-rotate-array-lcci/,search-rotate-array-lcci,数组、二分查找,https://algo.itcharge.cn/Solutions/Interviews/search-rotate-array-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.03.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84.md,38.5%,中等,301 -面试题 10.05,Interviews,面试题 10.05. 稀疏数组搜索,稀疏数组搜索,https://leetcode.cn/problems/sparse-array-search-lcci/,sparse-array-search-lcci,数组、字符串、二分查找,https://algo.itcharge.cn/Solutions/Interviews/sparse-array-search-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.05.%20%E7%A8%80%E7%96%8F%E6%95%B0%E7%BB%84%E6%90%9C%E7%B4%A2.md,56.9%,简单,365 -面试题 10.09,Interviews,面试题 10.09. 排序矩阵查找,排序矩阵查找,https://leetcode.cn/problems/sorted-matrix-search-lcci/,sorted-matrix-search-lcci,数组、二分查找、分治、矩阵,https://algo.itcharge.cn/Solutions/Interviews/sorted-matrix-search-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.09.%20%E6%8E%92%E5%BA%8F%E7%9F%A9%E9%98%B5%E6%9F%A5%E6%89%BE.md,44.6%,中等,229 -面试题 10.10,Interviews,面试题 10.10. 数字流的秩,数字流的秩,https://leetcode.cn/problems/rank-from-stream-lcci/,rank-from-stream-lcci,设计、树状数组、二分查找、数据流,https://algo.itcharge.cn/Solutions/Interviews/rank-from-stream-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.10.%20%E6%95%B0%E5%AD%97%E6%B5%81%E7%9A%84%E7%A7%A9.md,62.1%,中等,148 -面试题 10.11,Interviews,面试题 10.11. 峰与谷,峰与谷,https://leetcode.cn/problems/peaks-and-valleys-lcci/,peaks-and-valleys-lcci,贪心、数组、排序,https://algo.itcharge.cn/Solutions/Interviews/peaks-and-valleys-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.11.%20%E5%B3%B0%E4%B8%8E%E8%B0%B7.md,65.9%,中等,169 -面试题 16.01,Interviews,面试题 16.01. 交换数字,交换数字,https://leetcode.cn/problems/swap-numbers-lcci/,swap-numbers-lcci,位运算、数学,https://algo.itcharge.cn/Solutions/Interviews/swap-numbers-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.01.%20%E4%BA%A4%E6%8D%A2%E6%95%B0%E5%AD%97.md,81.4%,中等,556 -面试题 16.02,Interviews,面试题 16.02. 单词频率,单词频率,https://leetcode.cn/problems/words-frequency-lcci/,words-frequency-lcci,设计、字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Interviews/words-frequency-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.02.%20%E5%8D%95%E8%AF%8D%E9%A2%91%E7%8E%87.md,76.9%,中等,273 -面试题 16.03,Interviews,面试题 16.03. 交点,交点,https://leetcode.cn/problems/intersection-lcci/,intersection-lcci,几何、数学,https://algo.itcharge.cn/Solutions/Interviews/intersection-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.03.%20%E4%BA%A4%E7%82%B9.md,43.9%,困难,253 -面试题 16.04,Interviews,面试题 16.04. 井字游戏,井字游戏,https://leetcode.cn/problems/tic-tac-toe-lcci/,tic-tac-toe-lcci,数组、计数、矩阵,https://algo.itcharge.cn/Solutions/Interviews/tic-tac-toe-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.04.%20%E4%BA%95%E5%AD%97%E6%B8%B8%E6%88%8F.md,46.9%,中等,219 -面试题 16.05,Interviews,面试题 16.05. 阶乘尾数,阶乘尾数,https://leetcode.cn/problems/factorial-zeros-lcci/,factorial-zeros-lcci,数学,https://algo.itcharge.cn/Solutions/Interviews/factorial-zeros-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.05.%20%E9%98%B6%E4%B9%98%E5%B0%BE%E6%95%B0.md,43.8%,简单,177 -面试题 16.06,Interviews,面试题 16.06. 最小差,最小差,https://leetcode.cn/problems/smallest-difference-lcci/,smallest-difference-lcci,数组、双指针、二分查找、排序,https://algo.itcharge.cn/Solutions/Interviews/smallest-difference-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.06.%20%E6%9C%80%E5%B0%8F%E5%B7%AE.md,42.7%,中等,286 -面试题 16.07,Interviews,面试题 16.07. 最大数值,最大数值,https://leetcode.cn/problems/maximum-lcci/,maximum-lcci,位运算、脑筋急转弯、数学,https://algo.itcharge.cn/Solutions/Interviews/maximum-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.07.%20%E6%9C%80%E5%A4%A7%E6%95%B0%E5%80%BC.md,74.0%,简单,427 -面试题 16.08,Interviews,面试题 16.08. 整数的英语表示,整数的英语表示,https://leetcode.cn/problems/english-int-lcci/,english-int-lcci,递归、数学、字符串,https://algo.itcharge.cn/Solutions/Interviews/english-int-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.08.%20%E6%95%B4%E6%95%B0%E7%9A%84%E8%8B%B1%E8%AF%AD%E8%A1%A8%E7%A4%BA.md,39.1%,困难,79 -面试题 16.09,Interviews,面试题 16.09. 运算,运算,https://leetcode.cn/problems/operations-lcci/,operations-lcci,设计、数学,https://algo.itcharge.cn/Solutions/Interviews/operations-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.09.%20%E8%BF%90%E7%AE%97.md,56.3%,中等,60 -面试题 16.10,Interviews,面试题 16.10. 生存人数,生存人数,https://leetcode.cn/problems/living-people-lcci/,living-people-lcci,数组、计数,https://algo.itcharge.cn/Solutions/Interviews/living-people-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.10.%20%E7%94%9F%E5%AD%98%E4%BA%BA%E6%95%B0.md,66.9%,中等,269 -面试题 16.11,Interviews,面试题 16.11. 跳水板,跳水板,https://leetcode.cn/problems/diving-board-lcci/,diving-board-lcci,数组、数学,https://algo.itcharge.cn/Solutions/Interviews/diving-board-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.11.%20%E8%B7%B3%E6%B0%B4%E6%9D%BF.md,43.9%,简单,674 -面试题 16.13,Interviews,面试题 16.13. 平分正方形,平分正方形,https://leetcode.cn/problems/bisect-squares-lcci/,bisect-squares-lcci,几何、数学,https://algo.itcharge.cn/Solutions/Interviews/bisect-squares-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.13.%20%E5%B9%B3%E5%88%86%E6%AD%A3%E6%96%B9%E5%BD%A2.md,43.6%,中等,83 -面试题 16.14,Interviews,面试题 16.14. 最佳直线,最佳直线,https://leetcode.cn/problems/best-line-lcci/,best-line-lcci,几何、数组、哈希表、数学,https://algo.itcharge.cn/Solutions/Interviews/best-line-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.14.%20%E6%9C%80%E4%BD%B3%E7%9B%B4%E7%BA%BF.md,55.6%,中等,88 -面试题 16.15,Interviews,面试题 16.15. 珠玑妙算,珠玑妙算,https://leetcode.cn/problems/master-mind-lcci/,master-mind-lcci,哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/Interviews/master-mind-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.15.%20%E7%8F%A0%E7%8E%91%E5%A6%99%E7%AE%97.md,46.4%,简单,340 -面试题 16.16,Interviews,面试题 16.16. 部分排序,部分排序,https://leetcode.cn/problems/sub-sort-lcci/,sub-sort-lcci,栈、贪心、数组、双指针、排序、单调栈,https://algo.itcharge.cn/Solutions/Interviews/sub-sort-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.16.%20%E9%83%A8%E5%88%86%E6%8E%92%E5%BA%8F.md,46.7%,中等,320 -面试题 16.17,Interviews,面试题 16.17. 连续数列,连续数列,https://leetcode.cn/problems/contiguous-sequence-lcci/,contiguous-sequence-lcci,数组、分治、动态规划,https://algo.itcharge.cn/Solutions/Interviews/contiguous-sequence-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.17.%20%E8%BF%9E%E7%BB%AD%E6%95%B0%E5%88%97.md,58.9%,简单,514 -面试题 16.18,Interviews,面试题 16.18. 模式匹配,模式匹配,https://leetcode.cn/problems/pattern-matching-lcci/,pattern-matching-lcci,数学、字符串、回溯、枚举,https://algo.itcharge.cn/Solutions/Interviews/pattern-matching-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.18.%20%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D.md,33.9%,中等,347 -面试题 16.19,Interviews,面试题 16.19. 水域大小,水域大小,https://leetcode.cn/problems/pond-sizes-lcci/,pond-sizes-lcci,深度优先搜索、广度优先搜索、并查集、数组、矩阵,https://algo.itcharge.cn/Solutions/Interviews/pond-sizes-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.19.%20%E6%B0%B4%E5%9F%9F%E5%A4%A7%E5%B0%8F.md,66.2%,中等,590 -面试题 16.20,Interviews,面试题 16.20. T9键盘,T9键盘,https://leetcode.cn/problems/t9-lcci/,t9-lcci,数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Interviews/t9-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.20.%20T9%E9%94%AE%E7%9B%98.md,71.2%,中等,261 -面试题 16.21,Interviews,面试题 16.21. 交换和,交换和,https://leetcode.cn/problems/sum-swap-lcci/,sum-swap-lcci,数组、哈希表、二分查找、排序,https://algo.itcharge.cn/Solutions/Interviews/sum-swap-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.21.%20%E4%BA%A4%E6%8D%A2%E5%92%8C.md,47.6%,中等,271 -面试题 16.22,Interviews,面试题 16.22. 兰顿蚂蚁,兰顿蚂蚁,https://leetcode.cn/problems/langtons-ant-lcci/,langtons-ant-lcci,数组、哈希表、字符串、矩阵、模拟,https://algo.itcharge.cn/Solutions/Interviews/langtons-ant-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.22.%20%E5%85%B0%E9%A1%BF%E8%9A%82%E8%9A%81.md,57.8%,中等,84 -面试题 16.24,Interviews,面试题 16.24. 数对和,数对和,https://leetcode.cn/problems/pairs-with-sum-lcci/,pairs-with-sum-lcci,数组、哈希表、双指针、计数、排序,https://algo.itcharge.cn/Solutions/Interviews/pairs-with-sum-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.24.%20%E6%95%B0%E5%AF%B9%E5%92%8C.md,48.3%,中等,248 -面试题 16.25,Interviews,面试题 16.25. LRU 缓存,LRU 缓存,https://leetcode.cn/problems/lru-cache-lcci/,lru-cache-lcci,设计、哈希表、链表、双向链表,https://algo.itcharge.cn/Solutions/Interviews/lru-cache-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.25.%20LRU%20%E7%BC%93%E5%AD%98.md,55.5%,中等,351 -面试题 16.26,Interviews,面试题 16.26. 计算器,计算器,https://leetcode.cn/problems/calculator-lcci/,calculator-lcci,栈、数学、字符串,https://algo.itcharge.cn/Solutions/Interviews/calculator-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.26.%20%E8%AE%A1%E7%AE%97%E5%99%A8.md,39.8%,中等,292 -面试题 17.01,Interviews,面试题 17.01. 不用加号的加法,不用加号的加法,https://leetcode.cn/problems/add-without-plus-lcci/,add-without-plus-lcci,位运算、数学,https://algo.itcharge.cn/Solutions/Interviews/add-without-plus-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.01.%20%E4%B8%8D%E7%94%A8%E5%8A%A0%E5%8F%B7%E7%9A%84%E5%8A%A0%E6%B3%95.md,61.6%,简单,234 -面试题 17.04,Interviews,面试题 17.04. 消失的数字,消失的数字,https://leetcode.cn/problems/missing-number-lcci/,missing-number-lcci,位运算、数组、哈希表、数学、排序,https://algo.itcharge.cn/Solutions/Interviews/missing-number-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.04.%20%E6%B6%88%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md,59.4%,简单,724 -面试题 17.05,Interviews,面试题 17.05. 字母与数字,字母与数字,https://leetcode.cn/problems/find-longest-subarray-lcci/,find-longest-subarray-lcci,数组、哈希表、前缀和,https://algo.itcharge.cn/Solutions/Interviews/find-longest-subarray-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.05.%20%E5%AD%97%E6%AF%8D%E4%B8%8E%E6%95%B0%E5%AD%97.md,47.5%,中等,283 -面试题 17.06,Interviews,面试题 17.06. 2出现的次数,2出现的次数,https://leetcode.cn/problems/number-of-2s-in-range-lcci/,number-of-2s-in-range-lcci,递归、数学、动态规划,https://algo.itcharge.cn/Solutions/Interviews/number-of-2s-in-range-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.06.%202%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md,49.6%,困难,183 -面试题 17.07,Interviews,面试题 17.07. 婴儿名字,婴儿名字,https://leetcode.cn/problems/baby-names-lcci/,baby-names-lcci,深度优先搜索、广度优先搜索、并查集、数组、哈希表、字符串、计数,https://algo.itcharge.cn/Solutions/Interviews/baby-names-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.07.%20%E5%A9%B4%E5%84%BF%E5%90%8D%E5%AD%97.md,41.6%,中等,284 -面试题 17.08,Interviews,面试题 17.08. 马戏团人塔,马戏团人塔,https://leetcode.cn/problems/circus-tower-lcci/,circus-tower-lcci,数组、二分查找、动态规划、排序,https://algo.itcharge.cn/Solutions/Interviews/circus-tower-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.08.%20%E9%A9%AC%E6%88%8F%E5%9B%A2%E4%BA%BA%E5%A1%94.md,28.5%,中等,168 -面试题 17.09,Interviews,面试题 17.09. 第 k 个数,第 k 个数,https://leetcode.cn/problems/get-kth-magic-number-lcci/,get-kth-magic-number-lcci,哈希表、数学、动态规划、堆(优先队列),https://algo.itcharge.cn/Solutions/Interviews/get-kth-magic-number-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.09.%20%E7%AC%AC%20k%20%E4%B8%AA%E6%95%B0.md,56.1%,中等,431 -面试题 17.10,Interviews,面试题 17.10. 主要元素,主要元素,https://leetcode.cn/problems/find-majority-element-lcci/,find-majority-element-lcci,数组、计数,https://algo.itcharge.cn/Solutions/Interviews/find-majority-element-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.10.%20%E4%B8%BB%E8%A6%81%E5%85%83%E7%B4%A0.md,56.2%,简单,1052 -面试题 17.11,Interviews,面试题 17.11. 单词距离,单词距离,https://leetcode.cn/problems/find-closest-lcci/,find-closest-lcci,数组、字符串,https://algo.itcharge.cn/Solutions/Interviews/find-closest-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.11.%20%E5%8D%95%E8%AF%8D%E8%B7%9D%E7%A6%BB.md,73.1%,中等,665 -面试题 17.12,Interviews,面试题 17.12. BiNode,BiNode,https://leetcode.cn/problems/binode-lcci/,binode-lcci,栈、树、深度优先搜索、二叉搜索树、链表、二叉树,https://algo.itcharge.cn/Solutions/Interviews/binode-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.12.%20BiNode.md,63.9%,简单,394 -面试题 17.13,Interviews,面试题 17.13. 恢复空格,恢复空格,https://leetcode.cn/problems/re-space-lcci/,re-space-lcci,字典树、数组、哈希表、字符串、动态规划、哈希函数、滚动哈希,https://algo.itcharge.cn/Solutions/Interviews/re-space-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.13.%20%E6%81%A2%E5%A4%8D%E7%A9%BA%E6%A0%BC.md,55.4%,中等,303 -面试题 17.14,Interviews,面试题 17.14. 最小K个数,最小K个数,https://leetcode.cn/problems/smallest-k-lcci/,smallest-k-lcci,数组、分治、快速选择、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Interviews/smallest-k-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.14.%20%E6%9C%80%E5%B0%8FK%E4%B8%AA%E6%95%B0.md,58.9%,中等,929 -面试题 17.15,Interviews,面试题 17.15. 最长单词,最长单词,https://leetcode.cn/problems/longest-word-lcci/,longest-word-lcci,字典树、数组、哈希表、字符串,https://algo.itcharge.cn/Solutions/Interviews/longest-word-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.15.%20%E6%9C%80%E9%95%BF%E5%8D%95%E8%AF%8D.md,40.9%,中等,189 -面试题 17.16,Interviews,面试题 17.16. 按摩师,按摩师,https://leetcode.cn/problems/the-masseuse-lcci/,the-masseuse-lcci,数组、动态规划,https://algo.itcharge.cn/Solutions/Interviews/the-masseuse-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.16.%20%E6%8C%89%E6%91%A9%E5%B8%88.md,51.1%,简单,1313 -面试题 17.17,Interviews,面试题 17.17. 多次搜索,多次搜索,https://leetcode.cn/problems/multi-search-lcci/,multi-search-lcci,字典树、数组、哈希表、字符串、字符串匹配、滑动窗口,https://algo.itcharge.cn/Solutions/Interviews/multi-search-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.17.%20%E5%A4%9A%E6%AC%A1%E6%90%9C%E7%B4%A2.md,44.7%,中等,253 -面试题 17.18,Interviews,面试题 17.18. 最短超串,最短超串,https://leetcode.cn/problems/shortest-supersequence-lcci/,shortest-supersequence-lcci,数组、哈希表、滑动窗口,https://algo.itcharge.cn/Solutions/Interviews/shortest-supersequence-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.18.%20%E6%9C%80%E7%9F%AD%E8%B6%85%E4%B8%B2.md,44.4%,中等,177 -面试题 17.19,Interviews,面试题 17.19. 消失的两个数字,消失的两个数字,https://leetcode.cn/problems/missing-two-lcci/,missing-two-lcci,位运算、数组、哈希表,https://algo.itcharge.cn/Solutions/Interviews/missing-two-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.19.%20%E6%B6%88%E5%A4%B1%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97.md,60.6%,困难,576 -面试题 17.20,Interviews,面试题 17.20. 连续中值,连续中值,https://leetcode.cn/problems/continuous-median-lcci/,continuous-median-lcci,设计、双指针、数据流、排序、堆(优先队列),https://algo.itcharge.cn/Solutions/Interviews/continuous-median-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.20.%20%E8%BF%9E%E7%BB%AD%E4%B8%AD%E5%80%BC.md,58.4%,困难,110 -面试题 17.21,Interviews,面试题 17.21. 直方图的水量,直方图的水量,https://leetcode.cn/problems/volume-of-histogram-lcci/,volume-of-histogram-lcci,栈、数组、双指针、动态规划、单调栈,https://algo.itcharge.cn/Solutions/Interviews/volume-of-histogram-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.21.%20%E7%9B%B4%E6%96%B9%E5%9B%BE%E7%9A%84%E6%B0%B4%E9%87%8F.md,63.8%,困难,591 -面试题 17.22,Interviews,面试题 17.22. 单词转换,单词转换,https://leetcode.cn/problems/word-transformer-lcci/,word-transformer-lcci,广度优先搜索、哈希表、字符串、回溯,https://algo.itcharge.cn/Solutions/Interviews/word-transformer-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.22.%20%E5%8D%95%E8%AF%8D%E8%BD%AC%E6%8D%A2.md,40.3%,中等,206 -面试题 17.23,Interviews,面试题 17.23. 最大黑方阵,最大黑方阵,https://leetcode.cn/problems/max-black-square-lcci/,max-black-square-lcci,数组、动态规划、矩阵,https://algo.itcharge.cn/Solutions/Interviews/max-black-square-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.23.%20%E6%9C%80%E5%A4%A7%E9%BB%91%E6%96%B9%E9%98%B5.md,37.8%,中等,155 -面试题 17.24,Interviews,面试题 17.24. 最大子矩阵,最大子矩阵,https://leetcode.cn/problems/max-submatrix-lcci/,max-submatrix-lcci,数组、动态规划、矩阵、前缀和,https://algo.itcharge.cn/Solutions/Interviews/max-submatrix-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.24.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E7%9F%A9%E9%98%B5.md,53.6%,困难,197 -面试题 17.25,Interviews,面试题 17.25. 单词矩阵,单词矩阵,https://leetcode.cn/problems/word-rectangle-lcci/,word-rectangle-lcci,字典树、数组、字符串、回溯,https://algo.itcharge.cn/Solutions/Interviews/word-rectangle-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.25.%20%E5%8D%95%E8%AF%8D%E7%9F%A9%E9%98%B5.md,50.8%,困难,58 -面试题 17.26,Interviews,面试题 17.26. 稀疏相似度,稀疏相似度,https://leetcode.cn/problems/sparse-similarity-lcci/,sparse-similarity-lcci,数组、哈希表、排序,https://algo.itcharge.cn/Solutions/Interviews/sparse-similarity-lcci/,https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.26.%20%E7%A8%80%E7%96%8F%E7%9B%B8%E4%BC%BC%E5%BA%A6.md,35.2%,困难,90 diff --git a/Contents/00.Introduction/01.Data-Structures-Algorithms.md b/Contents/00.Introduction/01.Data-Structures-Algorithms.md deleted file mode 100644 index de771c8d..00000000 --- a/Contents/00.Introduction/01.Data-Structures-Algorithms.md +++ /dev/null @@ -1,213 +0,0 @@ -![](https://qcdn.itcharge.cn/images/202109092112373.png) - -> 数据结构是程序的骨架,而算法则是程序的灵魂。 - -**《算法 + 数据结构 = 程序》** 是 Pascal 语言之父 [Niklaus Emil Wirth](https://zh.wikipedia.org/wiki/尼克劳斯·维尔特) 写过的一本非常著名的书。而作为书名的这句话也成为了计算机科学的经典名句。可见,对于程序设计来说,算法和数据结构的关系密不可分。 - -在学习之前,首先我们要弄清楚什么是算法?什么是数据结构?为什么要学习算法和数据结构? - -简单来说,**「算法」就是解决问题的方法或者过程**。如果我们把问题看成是函数,那么算法就是将输入转换为输出的过程。**「数据结构」是数据的计算机表示和相应的一组操作**。**「程序」则是算法和数据结构的具体实现**。 - -如果我们把「程序设计」比作是做菜的话,那么「数据结构」就是食材和调料,「算法」则是不同的烹饪方式,或者可以看作是菜谱。不同的食材和调料,不同的烹饪方式,有着不同的排列组合。同样的东西,由不同的人做出来,味道自然也是千差万别。 - -至于为什么要学习算法和数据结构? - -还是拿做菜举例子。我们做菜,讲究的是「色香味俱全」。**程序设计也是如此,对于待解决的问题,我们追求的是:选择更加合适的「数据结构」,使用花费时间更少、占用空间更小的「算法」。** - -我们学习算法和数据结构,是为了学会在编程中从时间复杂度、空间复杂度方面考虑解决方案,训练自己的逻辑思维,从而写出高质量的代码,以此提升自己的编程技能,获取更高的工作回报。 - -当然,这就像是做菜,掌握了食材和调料,学会了烹饪方式,并不意味着你就会做出一盘很好吃的炒菜。同样,掌握了算法和数据结构并不意味着你就会写程序。这需要不断的琢磨和思考,并持续学习,才能成为一名优秀的 ~~厨师~~(程序员)。 - -## 1. 数据结构 - -> **数据结构(Data Structure)**:带有结构特性的数据元素的集合。 - -简单而言,**「数据结构」**指的是:**数据的组织结构,用来组织、存储数据**。 - -展开来讲,数据结构研究的是数据的逻辑结构、物理结构以及它们之间的相互关系,并对这种结构定义相应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。 - -数据结构的作用,就是为了提高计算机硬件的利用率。比如说:操作系统想要查找应用程序 「Microsoft Word」 在硬盘中的哪一个位置存储。如果对硬盘全部扫描一遍的话肯定效率很低,但如果使用「B+ 树」作为索引,就能很容易的搜索到 `Microsoft Word` 这个单词,然后很快的定位到 「Microsoft Word」这个应用程序的文件信息,从而从文件信息中找到对应的磁盘位置。 - -而学习数据结构,就是为了帮助我们了解和掌握计算机中的数据是以何种方式进行组织、存储的。 - ---- - -对于数据结构,我们可以按照数据的 **「逻辑结构」** 和 **「物理结构」** 来进行分类。 - -### 1.1 数据的逻辑结构 - -> **逻辑结构(Logical Structure)**:数据元素之间的相互关系。 - -根据元素之间具有的不同关系,通常我们可以将数据的逻辑结构分为以下四种: - -#### 1. 集合结构 - -> **集合结构**:数据元素同属于一个集合,除此之外无其他关系。 - -集合结构中的数据元素是无序的,并且每个数据元素都是唯一的,集合中没有相同的数据元素。集合结构很像数学意义上的「集合」。 - -![](https://qcdn.itcharge.cn/images/202109092116404.png) - -#### 2. 线性结构 - -> **线性结构**:数据元素之间是「一对一」关系。 - -线性结构中的数据元素(除了第一个和最后一个元素),左侧和右侧分别只有一个数据与其相邻。线性结构类型包括:数组、链表,以及由它们衍生出来的栈、队列、哈希表。 - -![](https://qcdn.itcharge.cn/images/202109092117492.png) - -#### 3. 树形结构 - -> **树形结构**:数据元素之间是「一对多」的层次关系。 - -最简单的树形结构是二叉树。这种结构可以简单的表示为:根, 左子树, 右子树。 左子树和右子树又有自己的子树。当然除了二叉树,树形结构类型还包括:多叉树、字典树等。 - -![](https://qcdn.itcharge.cn/images/202109092118089.png) - -#### 4. 图形结构 - -> **图形结构**:数据元素之间是「多对多」的关系。 - -图形结构是一种比树形结构更复杂的非线性结构,用于表示物件与物件之间的关系。一张图由一些小圆点(称为 **「顶点」** 或 **「结点」**)和连结这些圆点的直线或曲线(称为 **「边」**)组成。 - -在图形结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的。图形结构类型包括:无向图、有向图、连通图等。 - -![](https://qcdn.itcharge.cn/images/202109092119090.png) - -### 1.2 数据的物理结构 - -> **物理结构(Physical Structure)**:数据的逻辑结构在计算机中的存储方式。 - -计算机内有多种存储结构,采用最多的是这两种结构:**「顺序存储结构」**、**「链式存储结构」**。 - -#### 1. 顺序存储结构 - -> **顺序存储结构(Sequential Storage Structure)**:将数据元素存放在一片地址连续的存储单元里,数据元素之间的逻辑关系通过数据元素的存储地址来直接反映。 - -![](https://qcdn.itcharge.cn/images/202109092121742.png) - -在顺序存储结构中,逻辑上相邻的数据元素在物理地址上也必然相邻 。 - -这种结构的优点是:简单、易理解,且实际占用最少的存储空间。缺点是:需要占用一片地址连续的存储单元;并且存储分配要事先进行;另外对于一些操作的时间效率较低(移动、删除元素等操作)。 - -#### 2. 链式存储结构 - -> **链式存储结构(Linked Storage Structure)**:将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。 - -![](https://qcdn.itcharge.cn/images/202109092120553.png) - -链式存储结构中,逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。链式存储结构中,一般将每个数据元素占用的若干单元的组合称为一个链结点。每个链结点不仅要存放一个数据元素的数据信息,还要存放一个指出这个数据元素在逻辑关系的直接后继元素所在链结点的地址,该地址被称为指针。换句话说,数据元素之间的逻辑关系是通过指针来间接反映的。 - -这种结构的优点是:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比顺序存储结构高(插入、移动、删除元素)。缺点是:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链式存储结构比顺序存储结构的空间开销大。 - -## 2. 算法 - -> **算法(Algorithm)**:解决特定问题求解步骤的准确而完整的描述,在计算机中表现为一系列指令的集合,算法代表着用系统的方法描述解决问题的策略机制。 - -简单而言,**「算法」** 指的就是解决问题的方法。 - -展开来讲,算法是某一系列运算步骤,它表达解决某一类计算问题的一般方法,对这类方法的任何一个输入,它可以按步骤一步一步计算,最终产生一个输出。它不依赖于任何一种语言,可以用 **自然语言、编程语言(Python、C、C++、Java 等)描述**,也可以用 **伪代码、流程图** 来表示。 - -下面我们举几个例子来说明什么是算法。 - -- 示例 1: - -> **问题描述**: -> -> - 从上海到北京,应该怎么去? -> -> **解决方法**: -> -> 1. 选择坐飞机,坐飞机用的时间最少,但费用最高。 -> 2. 选择坐长途汽车,坐长途汽车费用低,但花费时间长。 -> 3. 选择坐高铁或火车,花费时间不算太长,价格也不算太贵。 - -- 示例 2: - -> **问题描述**: -> -> - 如何计算 $1 + 2 + 3 + … + 100$ 的值? -> -> **解决方法**: -> -> 1. 用计算器从 $1$ 开始,不断向右依次加上 $2$,再加上 $3$,...,依次加到 $100$,得出结果为 $5050$。 -> 2. 根据高斯求和公式:**和 = (首项 + 末项) × 项数 ÷ 2**,直接算出结果为:$\frac{(1+100) \times 100}{2} = 5050$。 - -- 示例 3: - -> **问题描述**: -> -> - 如何对一个 $n$ 个整数构成的数组进行升序排序? -> -> **解决方法**: -> -> 1. 使用冒泡排序对 $n$ 个整数构成的数组进行升序排序。 -> 2. 选择插入排序、归并排序、快速排序等等其他排序算法对 $n$ 个整数构成的数组进行升序排序。 - -以上 $3$ 个示例中的解决方法都可以看做是算法。从上海去北京的解决方法可以看做是算法,对 $1 \sim 100$ 的数进行求和的计算方法也可以看做是算法。对数组进行排序的方法也可以看做是算法。并且从这 $3$ 个示例中可以看出对于一个特定的问题,往往有着不同的算法。 - -### 2.1 算法的基本特性 - -算法其实就是一系列的运算步骤,这些运算步骤可以解决特定的问题。除此之外,**算法** 应必须具备以下特性: - -1. **输入**:对于待解决的问题,都要以某种方式交给对应的算法。在算法开始之前最初赋给算法的参数称为输入。比如示例 $1$ 中的输入就是出发地和目的地的参数(北京,上海),示例 $3$ 中的输入就是 $n$ 个整数构成的数组。一个算法可以有多个输入,也可以没有输入。比如示例 $2$ 是对固定问题的求解,就可以看做没有输入。 -2. **输出**:算法是为了解决问题存在的,最终总需要返回一个结果。所以至少需要一个或多个参数作为算法的输出。比如示例 $1$ 中的输出就是最终选择的交通方式,示例 $2$ 中的输出就是和的结果。示例 $3$ 中的输出就是排好序的数组。 -3. **有穷性**:算法必须在有限的步骤内结束,并且应该在一个可接受的时间内完成。比如示例 $1$,如果我们选择五一从上海到北京去旅游,结果五一纠结了三天也没决定好怎么去北京,那么这个旅游计划也就泡汤了,这个算法自然也是不合理的。 -4. **确定性**:组成算法的每一条指令必须有着清晰明确的含义,不能令读者在理解时产生二义性或者多义性。就是说,算法的每一个步骤都必须准确定义而无歧义。 -5. **可行性**:算法的每一步操作必须具有可执行性,在当前环境条件下可以通过有限次运算实现。也就是说,每一步都能通过执行有限次数完成,并且可以转换为程序在计算机上运行并得到正确的结果。 - -### 2.2 算法追求的目标 - -研究算法的作用,就是为了使解决问题的方法变得更加高效。对于给定的问题,我们往往会有多种算法来解决。而不同算法的 **成本** 也是不同的。总体而言,一个优秀的算法至少应该追求以下两个目标: - -1. **所需运行时间更少(时间复杂度更低)**; -2. **占用内存空间更小(空间复杂度更低)**。 - -假设计算机执行一条命令的时间为 $1$ 纳秒(并不科学),第一种算法需要执行 $100$ 纳秒,第二种算法则需要执行 $3$ 纳秒。如果不考虑占用内存空间的话,很明显第二种算法比第一种算法要好很多。 - -假设计算机一个内存单元的大小为一个字节,第一种算法需要占用 $3$ 个字节大小的内存空间,第二种算法则需要占用 $100$ 个字节大小的内存空间,如果不考虑运行时间的话,很明显第一种算法比第二种算法要好很多。 - -现实中算法,往往是需要同时从运行时间、占用空间两个方面考虑问题。当然,运行时间越少,占用空间越小的算法肯定是越好的,但总是会有各种各样的因素导致了运行时间和占用空间不可兼顾。比如,在程序运行时间过高时,我们可以考虑在空间上做文章,牺牲一定量的空间,来换取更短的运行时间。或者在程序对运行时间要求不是很高,而设备内存又有限的情况下,选择占用空间更小,但需要牺牲一定量的时间的算法。 - -当然,除了对运行时间和占用内存空间的追求外,一个好的算法还应该追求以下目标: - -1. **正确性**:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。 -2. **可读性**:可读性指的是算法遵循标识符命名规则,简洁易懂,注释语句恰当,方便自己和他人阅读,便于后期修改和调试。 -3. **健壮性**:健壮性指的是算法对非法数据以及操作有较好的反应和处理。 - -这 $3$ 个目标是算法的基本标准,是所有算法所必须满足的。一般我们对好的算法的评判标准就是上边提到的 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 - - -## 3. 总结 - -### 3.1 数据结构总结 - -数据结构可以分为 **「逻辑结构」** 和 **「物理结构」**。 - -- 逻辑结构可分为:**集合结构**、**线性结构**、**树形结构**、**图形结构**。 - -- 物理结构可分为:**顺序存储结构**、**链式存储结构**。 - -「逻辑结构」指的是数据之间的 **关系**,「物理结构」指的是这种关系 **在计算机中的表现形式**。 - -例如:线性表中的「栈」,其数据元素之间的关系是一对一的,除头和尾结点之外的每个结点都有唯一的前驱和唯一的后继,这体现的是逻辑结构。而对于栈中的结点来说,可以使用顺序存储(也就是 **顺序栈**)的方式存储在计算机中,其结构在计算机中的表现形式就是一段连续的存储空间,栈中每个结点和它的前驱结点、后继结点在物理上都是相邻的。当然,栈中的结点也可以使用链式存储(也即是 **链式栈**),每个结点和它的前驱结点、后继结点在物理上不一定相邻,每个结点是靠前驱结点的指针域来进行访问的。 - -### 3.2 算法总结 - -**「算法」** 指的就是解决问题的方法。算法是一系列的运算步骤,这些运算步骤可以解决特定的问题。 - -算法拥有 5 个基本特性:**输入**、**输出**、**有穷性**、**确定性**、**可行性**。 - -算法追求的目标有 5 个:**正确性**、**可读性**、**健壮性**、**所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 - ---- - -以上就是本篇的全部内容,我们将在下一篇文章具体讲解算法的「时间复杂度」和「空间复杂度」。 - -## 参考资料 - -- 【文章】[数据结构与算法 · 看云](https://www.kancloud.cn/zxliu/algorithm/2088786) -- 【书籍】大话数据结构——程杰 著 -- 【书籍】趣学算法——陈小玉 著 -- 【书籍】计算机程序设计艺术(第一卷)基本算法(第三版)——苏运霖 译 -- 【书籍】算法艺术与信息学竞赛——刘汝佳、黄亮 著 diff --git a/Contents/00.Introduction/02.Algorithm-Complexity.md b/Contents/00.Introduction/02.Algorithm-Complexity.md deleted file mode 100644 index c86132e7..00000000 --- a/Contents/00.Introduction/02.Algorithm-Complexity.md +++ /dev/null @@ -1,304 +0,0 @@ -## 1. 算法复杂度简介 - -> **算法复杂度(Algorithm complexity)**:在问题的输入规模为 $n$ 的条件下,程序的时间使用情况和空间使用情况。 - -「算法分析」的目的在于改进算法。正如上文中所提到的那样:算法所追求的就是 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。所以进行「算法分析」,就是从运行时间情况、空间使用情况两方面对算法进行分析。 - -比较两个算法的优劣通常有两种方法: - -- **事后统计**:将两个算法各编写一个可执行程序,交给计算机执行,记录下各自的运行时间和占用存储空间的实际大小,从中挑选出最好的算法。 -- **预先估算**:在算法设计出来之后,根据算法中包含的步骤,估算出算法的运行时间和占用空间。比较两个算法的估算值,从中挑选出最好的算法。 - -大多数情况下,我们会选择第 $2$ 种方式。因为第 $1$ 种方式的工作量实在太大,得不偿失。另外,即便是同一个算法,用不同的语言实现,在不同的计算机上运行,所需要的运行时间都不尽相同。所以我们一般采用预先估算的方法来衡量算法的好坏。 - -采用预先估算的方式下,编译语言、计算机运行速度都不是我们所考虑的对象。我们只关心随着问题规模 $n$ 扩大时,时间开销、空间开销的增长情况。 - -这里的 **「问题规模 $n$」** 指的是:算法问题输入的数据量大小。对于不同的算法,定义也不相同。 - -- 排序算法中:$n$ 表示需要排序的元素数量。 -- 查找算法中:$n$ 表示查找范围内的元素总数:比如数组大小、二维矩阵大小、字符串长度、二叉树节点数、图的节点数、图的边界点等。 -- 二进制计算相关算法中:$n$ 表示二进制的展开宽度。 - -一般来说,问题的输入规模越接近,相应的计算成本也越接近。而随着问题输入规模的扩大,计算成本也呈上升趋势。 - -接下来,我们将具体讲解「时间复杂度」和「空间复杂度」。 - -## 2. 时间复杂度 - -### 2.1 时间复杂度简介 - -> **时间复杂度(Time Complexity)**:在问题的输入规模为 $n$ 的条件下,算法运行所需要花费的时间,可以记作为 $T(n)$。 - -我们将 **基本操作次数** 作为时间复杂度的度量标准。换句话说,时间复杂度跟算法中基本操作次数的数量正相关。 - -- **基本操作** :算法执行中的每一条语句。每一次基本操作都可在常数时间内完成。 - -基本操作是一个运行时间不依赖于操作数的操作。 - -比如两个整数相加的操作,如果两个数的规模不大,运行时间不依赖于整数的位数,则相加操作就可以看做是基本操作。 - -反之,如果两个数的规模很大,相加操作依赖于两个数的位数,则两个数的相加操作不是一个基本操作,而每一位数的相加操作才是一个基本操作。 - -下面通过一个具体例子来说明一下如何计算时间复杂度。 - -```python -def algorithm(n): - fact = 1 - for i in range(1, n + 1): - fact *= i - return fact -``` - -把上述算法中所有语句的执行次数加起来 $1 + n + n + 1 = 2n + 2$,可以用一个函数 $f(n)$ 来表达语句的执行次数:$f(n) = 2n + 2$。 - -则时间复杂度的函数可以表示为:$T(n) = O(f(n))$。它表示的是随着问题规模 n 的增大,算法执行时间的增长趋势跟 $f(n)$ 相同。$O$ 是一种渐进符号,$T(n)$ 称作算法的 **渐进时间复杂度(Asymptotic Time Complexity)**,简称为 **时间复杂度**。 - -所谓「算法执行时间的增长趋势」是一个模糊的概念,通常我们要借助像上边公式中 $O$ 这样的「渐进符号」来表示时间复杂度。 - -### 2.2 渐进符号 - -> **渐进符号(Asymptotic Symbol)**:专门用来刻画函数的增长速度的。简单来说,渐进符号只保留了 **最高阶幂**,忽略了一个函数中增长较慢的部分,比如 **低阶幂**、**系数**、**常量**。因为当问题规模变的很大时,这几部分并不能左右增长趋势,所以可以忽略掉。 - -经常用到的渐进符号有三种: $\Theta$ 渐进紧确界符号、$O$ 渐进上界符号、$\Omega$ 渐进下界符号。接下来我们将依次讲解。 - -#### 2.2.1 $\Theta$ 渐进紧确界符号 - -> **$\Theta$ 渐进紧确界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = \Theta(g(n))$。存在正常量 $c_1$、$c_2$ 和 $n_0$,使得对于所有 $n \ge n_0$ 时,有 $0 \le c_1 \cdot g(n) \le f(n) \le c_2 \cdot g(n)$。 - -也就是说,如果函数 $f(n) = \Theta(g(n))$,那么我们能找到两个正数 $c_1$、$c_2$,使得 $f(n)$ 被 $c_1 \cdot g(n)$ 和 $c_2 \cdot g(n)$ 夹在中间。 - -例如:$T(n) = 3n^2 + 4n + 5 = \Theta(n^2)$,可以找到 $c_1 = 1$,$c_2 = 12$,$n_0 = 1$,使得对于所有 $n \ge 1$,都有 $n^2 \le 3n^2 + 4n + 5 \le 12n^2$。 - -#### 2.2.2 $O$ 渐进上界符号 - -> **$O$ 渐进上界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = O(g(n))$。存在常量 $c$,$n_0$,使得当 $n > n_0$ 时,有 $0 \le f(n) \le c \cdot g(n)$。 - -$\Theta$ 符号渐进地给出了一个函数的上界和下界,如果我们只知道一个函数的上界,可以使用 $O$ 渐进上界符号。 - -#### 2.2.3 $\Omega$ 渐进下界符号 - -> **$\Omega$ 渐进下界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = \Omega(g(n))$。存在常量 $c$,$n_0$,使得当 $n > n_0$ 时,有 $0 \le c \cdot g(n) \le f(n)$。 - -同样,如果我们只知道函数的下界,可以使用 $\Omega$ 渐进下界符号。 - -![](https://qcdn.itcharge.cn/images/202109092356694.png) - -### 2.3 时间复杂度计算 - -渐进符号可以渐进地描述一个函数的上界、下界,同时也可以描述算法执行时间的增长趋势。 - -在计算时间复杂度的时候,我们经常使用 $O$ 渐进上界符号。因为我们关注的通常是算法用时的上界,而不用关心其用时的下界。 - -那么具体应该如何计算时间复杂度呢? - -求解时间复杂度一般分为以下几个步骤: - -- **找出算法中的基本操作(基本语句)**:算法中执行次数最多的语句就是基本语句,通常是最内层循环的循环体部分。 -- **计算基本语句执行次数的数量级**:只需要计算基本语句执行次数的数量级,即保证函数中的最高次幂正确即可。像最高次幂的系数和低次幂可以忽略。 -- **用大 O 表示法表示时间复杂度**:将上一步中计算的数量级放入 O 渐进上界符号中。 - -同时,在求解时间复杂度还要注意一些原则: - -- **加法原则**:总的时间复杂度等于量级最大的基本语句的时间复杂度。 - -如果 $T_1(n) = O(f_1(n))$,$T_2(n) = O(f_2(n))$,$T(n) = T_1(n) + T_2(n)$,则 $T(n) = O(f(n)) = max(O(f_1(n)), O(f_2(n))) = O(max(f_1(n), f_2(n)))$。 - -- **乘法原则**:循环嵌套代码的复杂度等于嵌套内外基本语句的时间复杂度乘积。 - -如果 $T_1 = O(f_1(n))$,$T_2 = O(f_2(n))$,$T(n) = T_1(n)T_2(n)$,则 $T(n) = O(f(n)) = O(f_1(n))O(f_2(n)) = O(f_1(n)f_2(n))$。 - -下面通过实例来说明如何计算时间复杂度。 - -#### 2.3.1 常数 $O(1)$ - -一般情况下,只要算法中不存在循环语句、递归语句,其时间复杂度都为 $O(1)$。 - -$O(1)$ 只是常数阶时间复杂度的一种表示方式,并不是指只执行了一行代码。只要代码的执行时间不随着问题规模 $n$ 的增大而增长,这样的算法时间复杂度都记为 $O(1)$。 - -```python -def algorithm(n): - a = 1 - b = 2 - res = a * b + n - return res -``` - -上述代码虽然有 $4$ 行代码,但时间复杂度也是 $O(1)$,而不是 $O(3)$。 - -#### 2.3.2 线性 $O(n)$ - -一般含有非嵌套循环,且单层循环下的语句执行次数为 $n$ 的算法涉及线性时间复杂度。这类算法随着问题规模 $n$ 的增大,对应计算次数呈线性增长。 - -```python -def algorithm(n): - sum = 0 - for i in range(n): - sum += 1 - return sum -``` - -上述代码中 `sum += 1` 的执行次数为 $n$ 次,所以这段代码的时间复杂度为 $O(n)$。 - - -#### 2.3.3 平方 $O(n^2)$ - -一般含有双层嵌套,且每层循环下的语句执行次数为 $n$ 的算法涉及平方时间复杂度。这类算法随着问题规模 $n$ 的增大,对应计算次数呈平方关系增长。 - -```python -def algorithm(n): - res = 0 - for i in range(n): - for j in range(n): - res += 1 - return res -``` - -上述代码中,`res += 1` 在两重循环中,根据时间复杂度的乘法原理,这段代码的执行次数为 $n^2$ 次,所以其时间复杂度为 $O(n^2)$。 - -#### 2.3.4 阶乘 $O(n!)$ - -阶乘时间复杂度一般出现在与「全排列」、「旅行商问题暴力解法」相关的算法中。这类算法随着问题规模 $n$ 的增大,对应计算次数呈阶乘关系增长。 - -```python -def permutations(arr, start, end): - if start == end: - print(arr) - return - - for i in range(start, end): - arr[i], arr[start] = arr[start], arr[i] - permutations(arr, start + 1, end) - arr[i], arr[start] = arr[start], arr[i] -``` - -上述代码中实现「全排列」使用了递归的方法。假设数组 $arr$ 长度为 $n$,第一层 `for` 循环执行了 $n$ 次,第二层 `for` 循环执行了 $n - 1$ 次。以此类推,最后一层 `for` 循环执行了 $1$ 次,将所有层 `for` 循环的执行次数累乘起来为 $n \times (n - 1) \times (n - 2) \times … \times 2 \times 1 = n!$ 次。则整个算法的 `for` 循环中基本语句的执行次数为 $n!$ 次,所以对应时间复杂度为 $O(n!)$。 - -#### 2.3.5 对数 $O(\log n)$ - -对数时间复杂度一般出现在「二分查找」、「分治」这种一分为二的算法中。这类算法随着问题规模 $n$ 的增大,对应的计算次数呈对数关系增长。 - -```python -def algorithm(n): - cnt = 1 - while cnt < n: - cnt *= 2 - return cnt -``` - -上述代码中 `cnt = 1` 的时间复杂度为 $O(1)$ 可以忽略不算。`while` 循环体中 $cnt$ 从 $1$ 开始,每循环一次都乘以 $2$。当大于等于 $n$ 时循环结束。变量 $cnt$ 的取值是一个等比数列:$2^0, 2^1, 2^2, …, 2^x$,根据 $2^x = n$,可以得出这段循环体的执行次数为 $\log_2n$,所以这段代码的时间复杂度为 $O(\log_2n)$。 - -因为 $\log n = k \times \log_2 n$,这里 $k = 3.322$,所以,$\log n$ 与 $\log_2 n$ 的差别比较小。为了方便书写,通常我们将对数时间复杂度写作是 $O(\log n)$。 - -#### 2.3.6 线性对数 $O(n \times \log n)$ - - 线性对数一般出现在排序算法中,例如「快速排序」、「归并排序」、「堆排序」等。这类算法随着问题规模 $n$ 的增大,对应的计算次数呈线性对数关系增长。 - -```python -def algorithm(n): - cnt = 1 - res = 0 - while cnt < n: - cnt *= 2 - for i in range(n): - res += 1 - return res -``` - -上述代码中外层循环的时间复杂度为 $O(\log n)$,内层循环的时间复杂度为 $O(n)$,且两层循环相互独立,则总体时间复杂度为 $O(n \times \log n)$。 - -#### 2.3.7 常见时间复杂度关系 - -根据从小到大排序,常见的时间复杂度主要有:$O(1)$ < $O(\log n)$ < $O(n)$ < $O(n \times \log n)$ < $O(n^2)$ < $O(n^3)$ < $O(2^n)$ < $O(n!)$ < $O(n^n)$。 - -### 2.4 最佳、最坏、平均时间复杂度 - -时间复杂度是一个关于输入问题规模 $n$ 的函数。但是因为输入问题的内容不同,习惯将「时间复杂度」分为「最佳」、「最坏」、「平均」三种情况。这三种情况的具体含义如下: - -- **最佳时间复杂度**:每个输入规模下用时最短的输入所对应的时间复杂度。 -- **最坏时间复杂度**:每个输入规模下用时最长的输入所对应的时间复杂度。 -- **平均时间复杂度**:每个输入规模下所有可能的输入所对应的平均用时复杂度(随机输入下期望用时的复杂度)。 - -我们通过一个例子来分析下最佳、最坏、最差时间复杂度。 - -```python -def find(nums, val): - pos = -1 - for i in range(n): - if nums[i] == val: - pos = i - break - return pos -``` - -这段代码要实现的功能是:从一个整数数组 $nums$ 中查找值为 $val$ 的变量出现的位置。如果不考虑 `break` 语句,根据「2.3 时间复杂度计算」中讲的分析步骤,这个算法的时间复杂度是 $O(n)$,其中 $n$ 代表数组的长度。 - -但是如果考虑 `break` 语句,那么就需要考虑输入的内容了。如果数组中第 $1$ 个元素值就是 $val$,那么剩下 $n - 1$ 个数据都不要遍历了,那么时间复杂度就是 $O(1)$,即最佳时间复杂度为 $O(1)$。如果数组中不存在值为 $val$ 的变量,那么就需要把整个数组遍历一遍,时间复杂度就变成了 $O(n)$,即最差时间复杂度为 $O(n)$。 - -这样下来,时间复杂度就不唯一了。怎么办? - -我们都知道,最佳时间复杂度和最坏时间复杂度都是极端条件下的时间复杂度,发生的概率其实很小。为了能更好的表示正常情况下的复杂度,所以我们一般采用平均时间复杂度作为时间复杂度的计算方式。 - -还是刚才的例子,在数组 $nums$ 中查找变量值为 $val$ 的位置,总共有 $n + 1$ 种情况:「在数组的的 $0 \sim n - 1$ 个位置上」和「不在数组中」。我们将所有情况下,需要执行的语句次数累加起来,再除以 $n + 1$,就可以得到平均需要执行的语句次数,即:$\frac{1 + 2 + 3 + ... + n + n}{n + 1} = \frac{n(n + 3)}{2(n + 1)}$。将公式简化后,得到的平均时间复杂度就是 $O(n)$。 - -通常只有同一个算法在输入内容不同,不同时间复杂度有量级的差距时,我们才会通过三种时间复杂度表示法来区分。一般情况下,使用其中一种就可以满足需求了。 - -## 3. 空间复杂度 - -### 3.1 空间复杂度简介 - -> **空间复杂度(Space Complexity)**:在问题的输入规模为 $n$ 的条件下,算法所占用的空间大小,可以记作为 $S(n)$。一般将 **算法的辅助空间** 作为衡量空间复杂度的标准。 - -除了执行时间的长短,算法所需储存空间的多少也是衡量性能的一个重要方面。而在「2. 时间复杂度」中提到的渐进符号,也同样适用于空间复杂度的度量。空间复杂度的函数可以表示为 $S(n) = O(f(n))$,它表示的是随着问题规模 $n$ 的增大,算法所占空间的增长趋势跟 $f(n)$ 相同。 - -相比于算法的时间复杂度计算来说,算法的空间复杂度更容易计算,主要包括「局部变量(算法范围内定义的变量)所占用的存储空间」和「系统为实现递归(如果算法是递归的话)所使用的堆栈空间」两个部分。 - -下面通过实例来说明如何计算空间复杂度。 - -### 3.1 空间复杂度计算 - -#### 3.1.1 常数 $O(1)$ - -```python -def algorithm(n): - a = 1 - b = 2 - res = a * b + n - return res -``` - -上述代码中使用 $a$、$b$、$res$ 这 $3$ 个局部变量,其所占空间大小为常数阶,并不会随着问题规模 $n$ 的在增大而增大,所以该算法的空间复杂度为 $O(1)$。 - -#### 3.1.2 线性 $O(n)$ - -```python -def algorithm(n): - if n <= 0: - return 1 - return n * algorithm(n - 1) -``` - -上述代码采用了递归调用的方式。每次递归调用都占用了 $1$ 个栈帧空间,总共调用了 $n$ 次,所以该算法的空间复杂度为 $O(n)$。 - -#### 3.1.3 常见空间复杂度关系 - -根据从小到大排序,常见的算法复杂度主要有:$O(1)$ < $O(\log n)$ < $O(n)$ < $O(n^2)$ < $O(2^n)$ 等。 - -## 算法复杂度总结 - -**「算法复杂度」** 包括 **「时间复杂度」** 和 **「空间复杂度」**,用来分析算法执行效率与输入问题规模 $n$ 的增长关系。通常采用 **「渐进符号」** 的形式来表示「算法复杂度」。 - -常见的时间复杂度有:$O(1)$、$O(\log n)$、$O(n)$、$O(n \times \log n)$、$O(n^2)$、$O(n^3)$、$O(2^n)$、$O(n!)$。 - -常见的空间复杂度有:$O(1)$、$O(\log n)$、$O(n)$、$O(n^2)$。 - -## 参考资料 - -- 【书籍】数据结构(C++ 语言版)- 邓俊辉 著 -- 【书籍】算法导论 第三版(中文版)- 殷建平等 译 -- 【书籍】算法艺术与信息学竞赛 - 刘汝佳、黄亮 著 -- 【书籍】数据结构(C 语言版)- 严蔚敏 著 -- 【书籍】趣学算法 - 陈小玉 著 -- 【文章】[复杂度分析 - 数据结构与算法之美 王争](https://time.geekbang.org/column/intro/126) -- 【文章】[算法复杂度(时间复杂度+空间复杂度)](https://www.biancheng.net/algorithm/complexity.html) -- 【文章】[算法基础 - 复杂度 - OI Wiki](https://oi-wiki.org/basic/complexity/) -- 【文章】[图解算法数据结构 - 算法复杂度 - LeetBook - 力扣](https://leetcode.cn/leetbook/read/illustration-of-algorithm/r84gmi/) diff --git a/Contents/00.Introduction/03.LeetCode-Guide.md b/Contents/00.Introduction/03.LeetCode-Guide.md deleted file mode 100644 index faff8d9b..00000000 --- a/Contents/00.Introduction/03.LeetCode-Guide.md +++ /dev/null @@ -1,309 +0,0 @@ -## 1. LeetCode 是什么 - -**「LeetCode」** 是一个代码在线评测平台(Online Judge),包含了 **算法**、**数据库**、**Shell**、**多线程** 等不同分类的题目,其中以算法题目为主。我们可以通过解决 LeetCode 题库中的问题来练习编程技能,以及提高算法能力。 - -LeetCode 上有 $3000+$ 道的编程问题,支持 $16+$ 种编程语言(C、C++、Java、Python 等),还有一个活跃的社区,可以用于分享技术话题、职业经历、题目交流等。 - -并且许多知名互联网公司在面试的时候喜欢考察 LeetCode 题目,通常会以手写代码的形式出现。需要面试者对给定问题进行分析并给出解答,有时还会要求面试者分析算法的时间复杂度和空间复杂度,以及算法思路。面试官通过考察面试者对常用算法的熟悉程度和实现能力来确定面试者解决问题的思维能力水平。 - -所以无论是面试国内还是国外的知名互联网公司,通过 LeetCode 刷题,充分准备好算法,对拿到一个好公司的好 offer 都是有帮助的。 - -## 2. LeetCode 新手入门 - -### 2.1 LeetCode 注册 - -1. 打开 LeetCode 中文主页,链接:[力扣(LeetCode)官网](https://leetcode.cn/)。 -2. 输入手机号,获取验证码。 -3. 输入验证码之后,点击「登录 / 注册」,就注册好了。 - -![](https://qcdn.itcharge.cn/images/20210901155409.png) - -### 2.2 LeetCode 题库 - -「[题库](https://leetcode.cn/problemset/algorithms/)」是 LeetCode 上最直接的练习入口,在这里可以根据题目的标签、难度、状态进行刷题。也可以按照随机一题开始刷题。 - -![](https://qcdn.itcharge.cn/images/20210901155423.png) - -#### 1. 题目标签 - -LeetCode 的题目涉及了许多算法和数据结构。有贪心,搜索,动态规划,链表,二叉树,哈希表等等,可以通过选择对应标签进行专项刷题,同时也可以看到对应专题的完成度情况。 - -![](https://qcdn.itcharge.cn/images/20210901155435.png) - -#### 2. 题目列表 - -LeetCode 提供了题目的搜索过滤功能。可以筛选相关题单、不同难易程度、题目完成状态、不同标签的题目。还可以根据题目编号、题解数目、通过率、难度、出现频率等进行排序。 - -![](https://qcdn.itcharge.cn/images/20210901155450.png) - -#### 3. 当前进度 - -当前进度提供了一个直观的进度展示。在这里可以看到自己的练习概况。进度会自动展现当前的做题情况。也可以点击「[进度设置](https://leetcode.cn/session/)」创建新的进度,在这里还可以修改、删除相关的进度。 - -![](https://qcdn.itcharge.cn/images/20210901155500.png) - -#### 4. 题目详情 - -从题目大相关题目点击进去,就可以看到这道题目的内容描述和代码编辑器。在这里还可以查看相关的题解和自己的提交记录。 - -![](https://qcdn.itcharge.cn/images/20210901155529.png) - -### 2.3 LeetCode 刷题语言 - -大厂在面试算法的时候考察的是基本功,用什么语言没有什么限制,也不会影响成绩。日常刷题建议使用自己熟悉的语言,或者语法简洁的语言刷题。 - -相对于 Java、Python 而言,C、C++ 相关的语法比较复杂,在做题的时候一方面需要思考思路,另一方面还要研究语法。并且复杂的语法也不利于看懂思路,耗费时间较多,不利于刷题效率。在面试的时候往往需要一个小时内尽可能的完成更多的题目,C++ 一旦语法出错很容易慌乱。当然 LeetCode 周赛的大神更偏向于使用 C++ 刷题,这是因为用 C++ 参加算法竞赛已经成为传统了,绝大多数的 OI / ACM 竞赛选手都是 C++ 大神。 - -就我个人经历而言,我大学参加 ACM 竞赛的时候,用的是 C、C++ 和一点点的 Java。现在刷 LeetCode 为了更高的刷题效率,选择了 Python。感觉用 Python 刷题能更加专注于算法与数据结构本身,也能获得更快的刷题效率。 - -> 人生苦短,我用 Python。 - -### 2.4 LeetCode 刷题流程 - -在「2.2 LeetCode 题库 —— 4. 题目详情」中我们介绍了题目的相关情况。 - -![](https://qcdn.itcharge.cn/images/20210901155529.png) - -可以看到左侧区域为题目内容描述区域,在这里可以看到题目的内容描述和一些示例数据。而右侧是代码编辑区域,代码编辑区域里边默认显示了待实现的方法。 - -我们需要在代码编辑器中根据方法给定的参数实现对应的算法,并返回题目要求的结果。然后还要经过「执行代码」测试结果,点击「提交」后,显示执行结果为「**通过**」时,才算完成一道题目。 - -![](https://qcdn.itcharge.cn/images/20210901155545.png) - -总结一下我们的刷题流程为: - -1. 在 LeetCode 题库中选择一道自己想要解决的题目。 -2. 查看题目左侧的题目描述,理解题目要求。 -3. 思考解决思路,并在右侧代码编辑区域实现对应的方法,并返回题目要求的结果。 -4. 如果实在想不出解决思路,可以查看题目相关的题解,努力理解他人的解题思路和代码。 -5. 点击「执行代码」按钮测试结果。 - - 如果输出结果与预期结果不符,则回到第 3 步重新思考解决思路,并改写代码。 -6. 如果输出结果与预期符合,则点击「提交」按钮。 - - 如果执行结果显示「编译出错」、「解答错误」、「执行出错」、「超出时间限制」、「超出内存限制」等情况,则需要回到第 3 步重新思考解决思路,或者思考特殊数据,并改写代码。 -7. 如果执行结果显示「通过」,恭喜你通过了这道题目。 - -接下来我们将通过「[1. 两数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum/)」这道题目来讲解如何在 LeetCode 上刷题。 - -### 2.5 LeetCode 第一题 - -#### 2.5.1 题目链接 - -- [1. 两数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum/) - -#### 2.5.2 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个整数目标值 $target$。 - -**要求**:在该数组中找出和为 $target$ 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。 - -**说明**: - -- $2 \le nums.length \le 10^4$。 -- $-10^9 \le nums[i] \le 10^9$。 -- $-10^9 \le target \le 10^9$。 -- 只会存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,7,11,15], target = 9 -输出:[0,1] -解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 -``` - -- 示例 2: - -```python -输入:nums = [3,2,4], target = 6 -输出:[1,2] -``` - -#### 2.5.3 解题思路 - -##### 思路 1:枚举算法 - -1. 使用两重循环枚举数组中每一个数 $nums[i]$、$nums[j]$,判断所有的 $nums[i] + nums[j]$ 是否等于 $target$。 -2. 如果出现 $nums[i] + nums[j] == target$,则说明数组中存在和为 $target$ 的两个整数,将两个整数的下标 $i$、$$j$ 输出即可。 - -##### 思路 1:代码 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - for i in range(len(nums)): - for j in range(i + 1, len(nums)): - if i != j and nums[i] + nums[j] == target: - return [i, j] - return [] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 是数组 $nums$ 的元素数量。 -- **空间复杂度**:$O(1)$。 - -##### 思路 2:哈希表 - -哈希表中键值对信息为 $target-nums[i] :i,其中 $i$ 为下标。 - -1. 遍历数组,对于每一个数 $nums[i]$: - 1. 先查找字典中是否存在 $target - nums[i]$,存在则输出 $target - nums[i]$ 对应的下标和当前数组的下标 $i$。 - 2. 不存在则在字典中存入 $target - nums[i]$ 的下标 $i$。 - -##### 思路 2:代码 - -```python -def twoSum(self, nums: List[int], target: int) -> List[int]: - numDict = dict() - for i in range(len(nums)): - if target-nums[i] in numDict: - return numDict[target-nums[i]], i - numDict[nums[i]] = i - return [0] -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的元素数量。 -- **空间复杂度**:$O(n)$。 - -理解了上面这道题的题意,就可以试着自己编写代码,并尝试提交通过。也可以通过我的解题思路和解题代码,理解之后进行编写代码,并尝试提交通过。 - -如果提交结果显示「通过」,那么恭喜你完成了 LeetCode 上的第一题。虽然只是一道题,但这意味着刷题计划的开始!希望你能坚持下去,得到应有的收获。 - -## 3. LeetCode 刷题攻略 - -### 3.1 LeetCode 前期准备 - -如果你是一个对基础算法和数据结构完全不懂的小白,那么在刷 LeetCode 之前,建议先学习一下基础的 **「数据结构」** 和 **「算法」** 知识,这样在开始刷题的时候才不会那么痛苦。 - -基础的 **「数据结构」** 和 **「算法」** 知识包括: - -- **常考的数据结构**:**数组**、**字符串**、**链表**、**树(如二叉树)** 等。 -- **常考的算法**:**分治算法**、**贪心算法**、**穷举算法**、**回溯算法**、**动态规划** 等。 - -这个阶段推荐看一些经典的算法基础书来进行学习。这里推荐一下我看过的感觉不错的算法书: - -- 【书籍】「[算法(第 4 版)- 谢路云 译](https://book.douban.com/subject/19952400/)」 -- 【书籍】「[大话数据结构 - 程杰 著](https://book.douban.com/subject/6424904/)」 -- 【书籍】「[趣学算法 - 陈小玉 著](https://book.douban.com/subject/27109832/)」 -- 【书籍】「[算法图解 - 袁国忠 译](https://book.douban.com/subject/26979890/)」 -- 【书籍】「[算法竞赛入门经典(第 2 版) - 刘汝佳 著](https://book.douban.com/subject/25902102/)」 -- 【书籍】「[数据结构与算法分析 - 冯舜玺 译](https://book.douban.com/subject/1139426/)」 -- 【书籍】「[算法导论(原书第 3 版) - 殷建平 / 徐云 / 王刚 / 刘晓光 / 苏明 / 邹恒明 / 王宏志 译](https://book.douban.com/subject/20432061/)」 - -当然,也可以直接看我写的「算法通关手册」,欢迎指正和提出建议,万分感谢。 - -- 「算法通关手册」GitHub 地址:[https://github.com/itcharge/LeetCode-Py](https://github.com/itcharge/LeetCode-Py) -- 「算法通关手册」电子书网站地址:[https://algo.itcharge.cn](https://algo.itcharge.cn) - -### 3.2 LeetCode 刷题顺序 - -讲个笑话,从前有个人以为 LeetCode 的题目是按照难易程度排序的,所以他从「[1. 两数之和](https://leetcode.cn/problems/two-sum)」 开始刷题,结果他卡在了 「[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays)」这道困难题上。 - -LeetCode 的题目序号并不是按照难易程度进行排序的,所以除非硬核人士,不建议按照序号顺序刷题。如果是新手刷题的话,推荐先从「简单」难度等级的算法题开始刷题。等简单题上手熟练之后,再开始按照标签类别,刷中等难度的题。中等难度的题刷差不多之后,可以考虑刷面试题或者难题。 - -其实 LeetCode 官方网站上就有整理好的题目不错的刷题清单。链接为:[https://leetcode.cn/leetbook/](https://leetcode.cn/leetbook/)。可以先刷这里边的题目卡片。我这里也做了一个整理。 - -推荐刷题顺序和目录如下: - -[1. 初级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-easy/)、[2. 数组类算法](https://leetcode.cn/leetbook/detail/all-about-array/)、[3. 数组和字符串](https://leetcode.cn/leetbook/detail/array-and-string/)、[4. 链表类算法](https://leetcode.cn/leetbook/detail/linked-list/)、[5. 哈希表](https://leetcode.cn/leetbook/detail/hash-table/)、[6. 队列 & 栈](https://leetcode.cn/leetbook/detail/queue-stack/)、[7. 递归](https://leetcode.cn/leetbook/detail/recursion/)、[8. 二分查找](https://leetcode.cn/leetbook/detail/binary-search/)、[9. 二叉树](https://leetcode.cn/leetbook/detail/data-structure-binary-tree/)、[10. 中级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-medium/)、[11. 高级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-hard/)、[12. 算法面试题汇总](https://leetcode.cn/leetbook/detail/top-interview-questions/)。 - -当然还可以通过官方新推出的「[学习计划 - 力扣](https://leetcode.cn/study-plan/)」按计划每天刷题。 - -或者直接按照我整理的分类刷题列表进行刷题: - -- 刷题列表(GitHub 版)链接:[点击打开「GitHub 版分类刷题列表」](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/05.Categories-List.md) -- 刷题列表(网页版)链接:[点击打开「网页版分类刷题列表」](https://algo.itcharge.cn/00.Introduction/05.Categories-List/) - -正在准备面试、没有太多时间刷题的小伙伴,可以按照我总结的「LeetCode 面试最常考 100 题」、「LeetCode 面试最常考 200 题」进行刷题。 - -> **说明**:「LeetCode 面试最常考 100 题」、「LeetCode 面试最常考 200 题」是笔者根据「[CodeTop 企业题库](https://codetop.cc/home)」按频度从高到低进行筛选,并且去除了一部分 LeetCode 上没有的题目和重复题目后得到的题目清单。 - -- 「LeetCode 面试最常考 100 题(GitHub 版)」链接:[点击打开「LeetCode 面试最常考 100 题(GitHub 版)」](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/06.Interview-100-List.md) -- 「LeetCode 面试最常考 200 题(GitHub 版)」链接:[点击打开「LeetCode 面试最常考 200 题(GitHub 版)」](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/07.Interview-200-List.md) - ---- - -- 「LeetCode 面试最常考 100 题(网页版)」链接:[点击打开「LeetCode 面试最常考 100 题(网页版)」](https://algo.itcharge.cn/00.Introduction/06.Interview-100-List/) -- 「LeetCode 面试最常考 200 题(网页版)」链接:[点击打开「LeetCode 面试最常考 200 题(网页版)」](https://algo.itcharge.cn/00.Introduction/07.Interview-200-List/) - -### 3.3 LeetCode 刷题技巧 - -下面分享一下我在刷题过程中用到的刷题技巧。简单来说,可以分为 $5$ 条: - -> 1. 五分钟思考法 -> 2. 重复刷题 -> 3. 按专题分类刷题 -> 4. 写解题报告 -> 5. 坚持刷题 - -#### 3.3.1 五分钟思考法 - -> **五分钟思考法**:如果一道题如果 $5$ 分钟之内有思路,就立即动手写代码解题。如果 $5$ 分钟之后还没有思路,就直接去看题解。然后根据题解的思路,自己去实现代码。如果发现自己看了题解也无法实现代码,就认真阅读题解的代码,并理解代码的逻辑。 - -这种刷题方法其实跟英语里边的背单词过程是类似的。 - -一开始零基础学英语的时候,先学最简单的字母,不用纠结为什么这个字母这么写。然后学习简单的单词,也不用去纠结这个单词为啥就是这个意思,学就完事。在掌握了基本词汇之后,再去学习词组,学习短句子,然后长句子,再然后再看文章。 - -而且,在学英语单词的时候,也不是学一遍就会了。而是不断的重复练习、重复记忆加深印象。 - -算法刷题也是一样,零基础刷题的时候,不要过分纠结怎么自己就想不出来算法的解法,怎么就想不到更加高效的方法。遇到没有思路的题目,老老实实去看题解区的高赞题解,尽可能的让自己快速入门。 - -#### 3.3.2 重复刷题 - -> **重复刷题**:遇见不会的题,多刷几遍,不断加深理解。 - -算法题有时候一遍刷过去,过的时间长了可能就忘了,看到之前做的题不能够立马想到解题思路。这其实还是跟背单词一样,单词也不是看一遍就完全记住了。所以题目刷完一遍并不是结束了,还需要不断的回顾。 - -而且,一道题目可能有多种解法,还可能有好的算法思路。 - -最开始做的时候,可能只能想到一种思路,再做第二遍的时候,很有可能会想到了新的解法,新的优化方式等等。 - -所以,算法题在做完一遍之后遇见不会的,还可以多刷几遍,不断加深理解。 - -#### 3.3.3 按专题分类刷题 - -> **按专题分类刷题**:按照不同专题分类刷题,既可以巩固刚学完的算法知识,还可以提高刷题效率。 - -在上边「3.2 LeetCode 刷题顺序」我们给出了刷题顺序和目录。这里的刷题顺序其实就是按照不同分类来进行排序的。 - -我们可以在学习相关算法和数据结构知识时,顺便做一下该算法和数据结构知识专题下对应的题目清单。比如在学习完「链表」相关的基础知识时,可以将「链表」相关的基础题目刷完,或者刷官方 LeetBook 清单 [4. 链表类算法](https://leetcode.cn/leetbook/detail/linked-list/) 中的对应题目。 - -按照专题分类刷题的第一个好处是:**可以巩固刚学完的算法知识。** 如果是第一次学习对应的算法知识,刚学完可能对里边的相关知识理解的不够透彻,或者说可能会遗漏一些关键知识点,这时候可以通过刷对应题目的方式来帮助我们巩固刚学完的算法知识。 - -按照专题分类刷题的第二个好处是:**可以提高刷题效率。** 因为同一类算法题目所用到的算法知识其实是相同或者相似的,同一种解题思路可以运用到多道题目中。通过不断求解同一类算法专题下的题目,可以大大的提升我们的刷题速度。 - -#### 3.3.4 写解题报告 - ->**写解题报告**:如果能够用简介清晰的语言让别人听懂这道题目的思路,那就说明你真正理解了这道题的解法。 - -刷算法题,有一个十分有用的技巧,就是 **「写解题报告」**。如果你刷完一道题,能把这道题的解题步骤,做题思路用通俗易懂的话写成解题报告,那么这道题就算是掌握了。这其实就相当于「费曼学习法」的思维。 - -这样,也可以减少刷题的遍数。如果在写题的时候遇到之前刷过的题,但一时之间没有思路的,就可以看看自己之前的解题报告。这样就节省了大量重复刷题的时间。 - -#### 3.3.5 坚持刷题 - -> **坚持刷题**:算法刷题没有捷径,只有不断的刷题、总结,再刷题,再总结。 - -千万不要相信很多机构宣传的「3 天带你精通数据结构」、「7 天从算法零基础到精通」能让你快速学会算法知识。 - -学习算法和数据结构知识,不能靠速成,只能靠不断的积累,一步一步的推敲算法步骤,一遍又一遍的理解算法思想,才能掌握一个又一个的算法知识。而且还要不断的去刷该算法对应专题下的题目,才能将算法知识应用到日常的解题过程中。这样才能算彻底掌握了一个算法或一种解题思路。 - -根据我过去一年多和小伙伴们一起刷题打卡的经验发现:**那些能够坚持每天刷题,并最终学会一整套「基础算法知识」和「基础数据结构知识」的人,总是少数人**。 - -大部分总会因为种种主观和客观原因而放弃了刷题(工作繁忙、学习任务繁重、个人精力有限、时间不足等)。 - -但不管怎么样,如果你当初选择了学习算法知识,选择了通过刷题来通过面试,以便获取更好的工作岗位。那我希望在达成自己的目标之前,可以一直坚持下去,去「刻意练习」。在刷题的过程中收获知识,通过刷题得到满足感,从而把刷题变成兴趣。 - -这些话有些鸡汤了,但都是我的心里话。希望大家能够一起坚持刷题,争取早日实现自己的目标。 - -## 参考资料 - -- 【文章】[What is LeetCode? - Quora](https://www.quora.com/What-is-Leetcode) -- 【文章】[LeetCode 帮助中心 - 力扣(LeetCode)](https://support.leetcode-cn.com/hc/) -- 【回答】[刷 leetcode 使用 python 还是 c++? - 知乎](https://www.zhihu.com/question/319448129) -- 【回答】[刷完 LeetCode 是什么水平?能拿到什么水平的 offer? - 知乎](https://www.zhihu.com/question/32019460) - diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md deleted file mode 100644 index f2f33c1a..00000000 --- a/Contents/00.Introduction/04.Solutions-List.md +++ /dev/null @@ -1,827 +0,0 @@ -# LeetCode 题解(已完成 823 道) - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0002 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 递归、链表、数学 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0007 | [整数反转](https://leetcode.cn/problems/reverse-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0007.%20%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC.md) | 数学 | 中等 | -| 0008 | [字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0008.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0%20%28atoi%29.md) | 字符串 | 中等 | -| 0009 | [回文数](https://leetcode.cn/problems/palindrome-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0009.%20%E5%9B%9E%E6%96%87%E6%95%B0.md) | 数学 | 简单 | -| 0010 | [正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0010.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 递归、字符串、动态规划 | 困难 | -| 0011 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0011.%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8.md) | 贪心、数组、双指针 | 中等 | -| 0012 | [整数转罗马数字](https://leetcode.cn/problems/integer-to-roman/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0012.%20%E6%95%B4%E6%95%B0%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97.md) | 哈希表、数学、字符串 | 中等 | -| 0013 | [罗马数字转整数](https://leetcode.cn/problems/roman-to-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0013.%20%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97%E8%BD%AC%E6%95%B4%E6%95%B0.md) | 哈希表、数学、字符串 | 简单 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0016 | [最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0016.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0017 | [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0017.%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.md) | 哈希表、字符串、回溯 | 中等 | -| 0018 | [四数之和](https://leetcode.cn/problems/4sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0019 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 0020 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 栈、字符串 | 简单 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0022 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md) | 字符串、动态规划、回溯 | 中等 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0024 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 递归、链表 | 中等 | -| 0025 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 困难 | -| 0026 | [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0026.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 数组、双指针 | 简单 | -| 0027 | [移除元素](https://leetcode.cn/problems/remove-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0027.%20%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md) | 数组、双指针 | 简单 | -| 0028 | [找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0028.%20%E6%89%BE%E5%87%BA%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8C%B9%E9%85%8D%E9%A1%B9%E7%9A%84%E4%B8%8B%E6%A0%87.md) | 双指针、字符串、字符串匹配 | 中等 | -| 0029 | [两数相除](https://leetcode.cn/problems/divide-two-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0029.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E9%99%A4.md) | 位运算、数学 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0033 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找 | 中等 | -| 0034 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 中等 | -| 0035 | [搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0035.%20%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 简单 | -| 0036 | [有效的数独](https://leetcode.cn/problems/valid-sudoku/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0036.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、矩阵 | 中等 | -| 0037 | [解数独](https://leetcode.cn/problems/sudoku-solver/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0037.%20%E8%A7%A3%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、回溯、矩阵 | 困难 | -| 0038 | [外观数列](https://leetcode.cn/problems/count-and-say/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0038.%20%E5%A4%96%E8%A7%82%E6%95%B0%E5%88%97.md) | 字符串 | 中等 | -| 0039 | [组合总和](https://leetcode.cn/problems/combination-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md) | 数组、回溯 | 中等 | -| 0040 | [组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0040.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20II.md) | 数组、回溯 | 中等 | -| 0041 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md) | 数组、哈希表 | 困难 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0043 | [字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md) | 数学、字符串、模拟 | 中等 | -| 0044 | [通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0044.%20%E9%80%9A%E9%85%8D%E7%AC%A6%E5%8C%B9%E9%85%8D.md) | 贪心、递归、字符串、动态规划 | 困难 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0046 | [全排列](https://leetcode.cn/problems/permutations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 0047 | [全排列 II](https://leetcode.cn/problems/permutations-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0047.%20%E5%85%A8%E6%8E%92%E5%88%97%20II.md) | 数组、回溯 | 中等 | -| 0048 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、数学、矩阵 | 中等 | -| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0051 | [N 皇后](https://leetcode.cn/problems/n-queens/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0051.%20N%20%E7%9A%87%E5%90%8E.md) | 数组、回溯 | 困难 | -| 0052 | [N 皇后 II](https://leetcode.cn/problems/n-queens-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0052.%20N%20%E7%9A%87%E5%90%8E%20II.md) | 回溯 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 中等 | -| 0055 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md) | 贪心、数组、动态规划 | 中等 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0058 | [最后一个单词的长度](https://leetcode.cn/problems/length-of-last-word/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0058.%20%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E5%8D%95%E8%AF%8D%E7%9A%84%E9%95%BF%E5%BA%A6.md) | 字符串 | 简单 | -| 0059 | [螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0059.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20II.md) | 数组、矩阵、模拟 | 中等 | -| 0061 | [旋转链表](https://leetcode.cn/problems/rotate-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0061.%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 链表、双指针 | 中等 | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | -| 0063 | [不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0063.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II.md) | 数组、动态规划、矩阵 | 中等 | -| 0064 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划、矩阵 | 中等 | -| 0066 | [加一](https://leetcode.cn/problems/plus-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0066.%20%E5%8A%A0%E4%B8%80.md) | 数组、数学 | 简单 | -| 0067 | [二进制求和](https://leetcode.cn/problems/add-binary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0067.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%B1%82%E5%92%8C.md) | 位运算、数学、字符串、模拟 | 简单 | -| 0069 | [x 的平方根](https://leetcode.cn/problems/sqrtx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0072 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md) | 字符串、动态规划 | 困难 | -| 0073 | [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0073.%20%E7%9F%A9%E9%98%B5%E7%BD%AE%E9%9B%B6.md) | 数组、哈希表、矩阵 | 中等 | -| 0074 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0074.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5.md) | 数组、二分查找、矩阵 | 中等 | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | -| 0076 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 0077 | [组合](https://leetcode.cn/problems/combinations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0077.%20%E7%BB%84%E5%90%88.md) | 回溯 | 中等 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0079 | [单词搜索](https://leetcode.cn/problems/word-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0079.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2.md) | 数组、回溯、矩阵 | 中等 | -| 0080 | [删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0080.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9%20II.md) | 数组、双指针 | 中等 | -| 0081 | [搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0081.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%20II.md) | 数组、二分查找 | 中等 | -| 0082 | [删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 链表、双指针 | 中等 | -| 0083 | [删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 链表 | 简单 | -| 0084 | [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0084.%20%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.md) | 栈、数组、单调栈 | 困难 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 0089 | [格雷编码](https://leetcode.cn/problems/gray-code/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0089.%20%E6%A0%BC%E9%9B%B7%E7%BC%96%E7%A0%81.md) | 位运算、数学、回溯 | 中等 | -| 0090 | [子集 II](https://leetcode.cn/problems/subsets-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md) | 位运算、数组、回溯 | 中等 | -| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0093 | [复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md) | 字符串、回溯 | 中等 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0095 | [不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0095.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%20II.md) | 树、二叉搜索树、动态规划、回溯、二叉树 | 中等 | -| 0096 | [不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0096.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | -| 0098 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0101 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0102 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0103 | [二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0105 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0106 | [从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0106.%20%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0107 | [二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0107.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%20II.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0108 | [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0108.%20%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | -| 0110 | [平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0112 | [路径总和](https://leetcode.cn/problems/path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0113 | [路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 0115 | [不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 困难 | -| 0116 | [填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0116.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0117 | [填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0117.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88%20II.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0118 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md) | 数组、动态规划 | 简单 | -| 0119 | [杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md) | 数组、动态规划 | 简单 | -| 0120 | [三角形最小路径和](https://leetcode.cn/problems/triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0120.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划 | 中等 | -| 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0123 | [买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0123.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20III.md) | 数组、动态规划 | 困难 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0127 | [单词接龙](https://leetcode.cn/problems/word-ladder/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0127.%20%E5%8D%95%E8%AF%8D%E6%8E%A5%E9%BE%99.md) | 广度优先搜索、哈希表、字符串 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 0129 | [求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0130 | [被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0130.%20%E8%A2%AB%E5%9B%B4%E7%BB%95%E7%9A%84%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0131 | [分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0131.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 字符串、动态规划、回溯 | 中等 | -| 0133 | [克隆图](https://leetcode.cn/problems/clone-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 0134 | [加油站](https://leetcode.cn/problems/gas-station/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0134.%20%E5%8A%A0%E6%B2%B9%E7%AB%99.md) | 贪心、数组 | 中等 | -| 0135 | [分发糖果](https://leetcode.cn/problems/candy/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0135.%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.md) | 贪心、数组 | 困难 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0137 | [只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0137.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20II.md) | 位运算、数组 | 中等 | -| 0138 | [复制带随机指针的链表](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0138.%20%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 哈希表、链表 | 中等 | -| 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | -| 0140 | [单词拆分 II](https://leetcode.cn/problems/word-break-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0140.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86%20II.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划、回溯 | 困难 | -| 0141 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0142 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md) | 哈希表、链表、双指针 | 中等 | -| 0143 | [重排链表](https://leetcode.cn/problems/reorder-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0147 | [对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0147.%20%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md) | 链表、排序 | 中等 | -| 0148 | [排序链表](https://leetcode.cn/problems/sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 0149 | [直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0149.%20%E7%9B%B4%E7%BA%BF%E4%B8%8A%E6%9C%80%E5%A4%9A%E7%9A%84%E7%82%B9%E6%95%B0.md) | 几何、数组、哈希表、数学 | 困难 | -| 0150 | [逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0150.%20%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.md) | 栈、数组、数学 | 中等 | -| 0151 | [反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 双指针、字符串 | 中等 | -| 0152 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划 | 中等 | -| 0153 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0154 | [寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0154.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%20II.md) | 数组、二分查找 | 困难 | -| 0155 | [最小栈](https://leetcode.cn/problems/min-stack/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md) | 栈、设计 | 中等 | -| 0159 | [至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0159.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0160 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0162 | [寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | -| 0166 | [分数到小数](https://leetcode.cn/problems/fraction-to-recurring-decimal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0166.%20%E5%88%86%E6%95%B0%E5%88%B0%E5%B0%8F%E6%95%B0.md) | 哈希表、数学、字符串 | 中等 | -| 0167 | [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、二分查找 | 中等 | -| 0168 | [Excel表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0168.%20Excel%E8%A1%A8%E5%88%97%E5%90%8D%E7%A7%B0.md) | 数学、字符串 | 简单 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 0170 | [两数之和 III - 数据结构设计](https://leetcode.cn/problems/two-sum-iii-data-structure-design/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0170.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20III%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md) | 设计、数组、哈希表、双指针、数据流 | 简单 | -| 0171 | [Excel 表列序号](https://leetcode.cn/problems/excel-sheet-column-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0171.%20Excel%20%E8%A1%A8%E5%88%97%E5%BA%8F%E5%8F%B7.md) | 数学、字符串 | 简单 | -| 0172 | [阶乘后的零](https://leetcode.cn/problems/factorial-trailing-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0172.%20%E9%98%B6%E4%B9%98%E5%90%8E%E7%9A%84%E9%9B%B6.md) | 数学 | 中等 | -| 0173 | [二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0173.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | -| 0179 | [最大数](https://leetcode.cn/problems/largest-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md) | 贪心、数组、字符串、排序 | 中等 | -| 0188 | [买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0188.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20IV.md) | 数组、动态规划 | 困难 | -| 0189 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0189.%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针 | 中等 | -| 0190 | [颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0190.%20%E9%A2%A0%E5%80%92%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BD%8D.md) | 位运算、分治 | 简单 | -| 0191 | [位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0191.%20%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算、分治 | 简单 | -| 0198 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md) | 数组、动态规划 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0201 | [数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0201.%20%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4%E6%8C%89%E4%BD%8D%E4%B8%8E.md) | 位运算 | 中等 | -| 0202 | [快乐数](https://leetcode.cn/problems/happy-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0202.%20%E5%BF%AB%E4%B9%90%E6%95%B0.md) | 哈希表、数学、双指针 | 简单 | -| 0203 | [移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0203.%20%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.md) | 递归、链表 | 简单 | -| 0204 | [计数质数](https://leetcode.cn/problems/count-primes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0204.%20%E8%AE%A1%E6%95%B0%E8%B4%A8%E6%95%B0.md) | 数组、数学、枚举、数论 | 中等 | -| 0205 | [同构字符串](https://leetcode.cn/problems/isomorphic-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0205.%20%E5%90%8C%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串 | 简单 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0208 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0208.%20%E5%AE%9E%E7%8E%B0%20Trie%20%28%E5%89%8D%E7%BC%80%E6%A0%91%29.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0211 | [添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0211.%20%E6%B7%BB%E5%8A%A0%E4%B8%8E%E6%90%9C%E7%B4%A2%E5%8D%95%E8%AF%8D%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md) | 深度优先搜索、设计、字典树、字符串 | 中等 | -| 0212 | [单词搜索 II](https://leetcode.cn/problems/word-search-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0212.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%20II.md) | 字典树、数组、字符串、回溯、矩阵 | 困难 | -| 0213 | [打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0213.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20II.md) | 数组、动态规划 | 中等 | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 0217 | [存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 数组、哈希表、排序 | 简单 | -| 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | -| 0219 | [存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0219.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 数组、哈希表、滑动窗口 | 简单 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0222 | [完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0222.%20%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%8A%82%E7%82%B9%E4%B8%AA%E6%95%B0.md) | 树、深度优先搜索、二分查找、二叉树 | 中等 | -| 0223 | [矩形面积](https://leetcode.cn/problems/rectangle-area/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0223.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md) | 几何、数学 | 中等 | -| 0225 | [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md) | 栈、设计、队列 | 简单 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0227 | [基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md) | 栈、数学、字符串 | 中等 | -| 0231 | [2 的幂](https://leetcode.cn/problems/power-of-two/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0231.%202%20%E7%9A%84%E5%B9%82.md) | 位运算、递归、数学 | 简单 | -| 0232 | [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 0233 | [数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0233.%20%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | -| 0234 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0235 | [二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0235.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0236 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0237 | [删除链表中的节点](https://leetcode.cn/problems/delete-node-in-a-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0237.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 链表 | 中等 | -| 0238 | [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0238.%20%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF.md) | 数组、前缀和 | 中等 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0240 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md) | 数组、二分查找、分治、矩阵 | 中等 | -| 0241 | [为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0241.%20%E4%B8%BA%E8%BF%90%E7%AE%97%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%BE%E8%AE%A1%E4%BC%98%E5%85%88%E7%BA%A7.md) | 递归、记忆化搜索、数学、字符串、动态规划 | 中等 | -| 0242 | [有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0242.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、排序 | 简单 | -| 0249 | [移位字符串分组](https://leetcode.cn/problems/group-shifted-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0249.%20%E7%A7%BB%E4%BD%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串 | 中等 | -| 0257 | [二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0257.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、字符串、回溯、二叉树 | 简单 | -| 0258 | [各位相加](https://leetcode.cn/problems/add-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0258.%20%E5%90%84%E4%BD%8D%E7%9B%B8%E5%8A%A0.md) | 数学、数论、模拟 | 简单 | -| 0259 | [较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 中等 | -| 0260 | [只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0260.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20III.md) | 位运算、数组 | 中等 | -| 0263 | [丑数](https://leetcode.cn/problems/ugly-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0263.%20%E4%B8%91%E6%95%B0.md) | 数学 | 简单 | -| 0264 | [丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0264.%20%E4%B8%91%E6%95%B0%20II.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 0270 | [最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0270.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%80%BC.md) | 树、深度优先搜索、二叉搜索树、二分查找、二叉树 | 简单 | -| 0278 | [第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0278.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E7%9A%84%E7%89%88%E6%9C%AC.md) | 二分查找、交互 | 简单 | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 0285 | [二叉搜索树中的中序后继](https://leetcode.cn/problems/inorder-successor-in-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0285.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%B8%AD%E5%BA%8F%E5%90%8E%E7%BB%A7.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0286 | [墙与门](https://leetcode.cn/problems/walls-and-gates/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0286.%20%E5%A2%99%E4%B8%8E%E9%97%A8.md) | 广度优先搜索、数组、矩阵 | 中等 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0288 | [单词的唯一缩写](https://leetcode.cn/problems/unique-word-abbreviation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0288.%20%E5%8D%95%E8%AF%8D%E7%9A%84%E5%94%AF%E4%B8%80%E7%BC%A9%E5%86%99.md) | 设计、数组、哈希表、字符串 | 中等 | -| 0289 | [生命游戏](https://leetcode.cn/problems/game-of-life/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0289.%20%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F.md) | 数组、矩阵、模拟 | 中等 | -| 0290 | [单词规律](https://leetcode.cn/problems/word-pattern/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0290.%20%E5%8D%95%E8%AF%8D%E8%A7%84%E5%BE%8B.md) | 哈希表、字符串 | 简单 | -| 0292 | [Nim 游戏](https://leetcode.cn/problems/nim-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0292.%20Nim%20%E6%B8%B8%E6%88%8F.md) | 脑筋急转弯、数学、博弈 | 简单 | -| 0295 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0295.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | -| 0297 | [二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0297.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 数组、二分查找、动态规划 | 中等 | -| 0303 | [区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、前缀和 | 简单 | -| 0304 | [二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0304.%20%E4%BA%8C%E7%BB%B4%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E7%9F%A9%E9%98%B5%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、矩阵、前缀和 | 中等 | -| 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | -| 0309 | [最佳买卖股票时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0309.%20%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.md) | 数组、动态规划 | 中等 | -| 0310 | [最小高度树](https://leetcode.cn/problems/minimum-height-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0310.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | -| 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 0316 | [去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0316.%20%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D.md) | 栈、贪心、字符串、单调栈 | 中等 | -| 0318 | [最大单词长度乘积](https://leetcode.cn/problems/maximum-product-of-word-lengths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0318.%20%E6%9C%80%E5%A4%A7%E5%8D%95%E8%AF%8D%E9%95%BF%E5%BA%A6%E4%B9%98%E7%A7%AF.md) | 位运算、数组、字符串 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0324 | [摆动排序 II](https://leetcode.cn/problems/wiggle-sort-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0324.%20%E6%91%86%E5%8A%A8%E6%8E%92%E5%BA%8F%20II.md) | 数组、分治、快速选择、排序 | 中等 | -| 0326 | [3 的幂](https://leetcode.cn/problems/power-of-three/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0326.%203%20%E7%9A%84%E5%B9%82.md) | 递归、数学 | 简单 | -| 0328 | [奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0328.%20%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md) | 链表 | 中等 | -| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | -| 0334 | [递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0334.%20%E9%80%92%E5%A2%9E%E7%9A%84%E4%B8%89%E5%85%83%E5%AD%90%E5%BA%8F%E5%88%97.md) | 贪心、数组 | 中等 | -| 0336 | [回文对](https://leetcode.cn/problems/palindrome-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0336.%20%E5%9B%9E%E6%96%87%E5%AF%B9.md) | 字典树、数组、哈希表、字符串 | 困难 | -| 0337 | [打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0337.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20III.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | -| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 0340 | [至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0340.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0341 | [扁平化嵌套列表迭代器](https://leetcode.cn/problems/flatten-nested-list-iterator/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0341.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%B5%8C%E5%A5%97%E5%88%97%E8%A1%A8%E8%BF%AD%E4%BB%A3%E5%99%A8.md) | 栈、树、深度优先搜索、设计、队列、迭代器 | 中等 | -| 0342 | [4的幂](https://leetcode.cn/problems/power-of-four/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0342.%204%E7%9A%84%E5%B9%82.md) | 位运算、递归、数学 | 简单 | -| 0343 | [整数拆分](https://leetcode.cn/problems/integer-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md) | 数学、动态规划 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0345 | [反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0345.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D.md) | 双指针、字符串 | 简单 | -| 0346 | [数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0346.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%A7%BB%E5%8A%A8%E5%B9%B3%E5%9D%87%E5%80%BC.md) | 设计、队列、数组、数据流 | 简单 | -| 0347 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0347.%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0351 | [安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md) | 动态规划、回溯 | 中等 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | -| 0357 | [统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0357.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E9%83%BD%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97%E4%B8%AA%E6%95%B0.md) | 数学、动态规划、回溯 | 中等 | -| 0359 | [日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0359.%20%E6%97%A5%E5%BF%97%E9%80%9F%E7%8E%87%E9%99%90%E5%88%B6%E5%99%A8.md) | 设计、哈希表 | 简单 | -| 0360 | [有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0360.%20%E6%9C%89%E5%BA%8F%E8%BD%AC%E5%8C%96%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针、排序 | 中等 | -| 0367 | [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0367.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 数学、二分查找 | 简单 | -| 0370 | [区间加法](https://leetcode.cn/problems/range-addition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0370.%20%E5%8C%BA%E9%97%B4%E5%8A%A0%E6%B3%95.md) | 数组、前缀和 | 中等 | -| 0371 | [两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0371.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 位运算、数学 | 中等 | -| 0374 | [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0374.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F.md) | 二分查找、交互 | 简单 | -| 0375 | [猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md) | 数学、动态规划、博弈 | 中等 | -| 0376 | [摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0376.%20%E6%91%86%E5%8A%A8%E5%BA%8F%E5%88%97.md) | 贪心、数组、动态规划 | 中等 | -| 0377 | [组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md) | 数组、动态规划 | 中等 | -| 0378 | [有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0378.%20%E6%9C%89%E5%BA%8F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、二分查找、矩阵、排序、堆(优先队列) | 中等 | -| 0380 | [O(1) 时间插入、删除和获取随机元素](https://leetcode.cn/problems/insert-delete-getrandom-o1/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0380.%20O%281%29%20%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0.md) | 设计、数组、哈希表、数学、随机化 | 中等 | -| 0383 | [赎金信](https://leetcode.cn/problems/ransom-note/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0383.%20%E8%B5%8E%E9%87%91%E4%BF%A1.md) | 哈希表、字符串、计数 | 简单 | -| 0384 | [打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0384.%20%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84.md) | 数组、数学、随机化 | 中等 | -| 0386 | [字典序排数](https://leetcode.cn/problems/lexicographical-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0386.%20%E5%AD%97%E5%85%B8%E5%BA%8F%E6%8E%92%E6%95%B0.md) | 深度优先搜索、字典树 | 中等 | -| 0387 | [字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0387.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%94%AF%E4%B8%80%E5%AD%97%E7%AC%A6.md) | 队列、哈希表、字符串、计数 | 简单 | -| 0389 | [找不同](https://leetcode.cn/problems/find-the-difference/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0389.%20%E6%89%BE%E4%B8%8D%E5%90%8C.md) | 位运算、哈希表、字符串、排序 | 简单 | -| 0391 | [完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md) | 数组、扫描线 | 困难 | -| 0392 | [判断子序列](https://leetcode.cn/problems/is-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0392.%20%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.md) | 双指针、字符串、动态规划 | 简单 | -| 0394 | [字符串解码](https://leetcode.cn/problems/decode-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md) | 栈、递归、字符串 | 中等 | -| 0395 | [至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0395.%20%E8%87%B3%E5%B0%91%E6%9C%89%20K%20%E4%B8%AA%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、分治、滑动窗口 | 中等 | -| 0399 | [除法求值](https://leetcode.cn/problems/evaluate-division/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0399.%20%E9%99%A4%E6%B3%95%E6%B1%82%E5%80%BC.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 | -| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | -| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 | -| 0404 | [左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0404.%20%E5%B7%A6%E5%8F%B6%E5%AD%90%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0405 | [数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0405.%20%E6%95%B0%E5%AD%97%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 位运算、数学 | 简单 | -| 0406 | [根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0406.%20%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.md) | 贪心、树状数组、线段树、数组、排序 | 中等 | -| 0409 | [最长回文串](https://leetcode.cn/problems/longest-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0409.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 贪心、哈希表、字符串 | 简单 | -| 0410 | [分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | -| 0412 | [Fizz Buzz](https://leetcode.cn/problems/fizz-buzz/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0412.%20Fizz%20Buzz.md) | 数学、字符串、模拟 | 简单 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | -| 0416 | [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0416.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md) | 数组、动态规划 | 中等 | -| 0417 | [太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0417.%20%E5%A4%AA%E5%B9%B3%E6%B4%8B%E5%A4%A7%E8%A5%BF%E6%B4%8B%E6%B0%B4%E6%B5%81%E9%97%AE%E9%A2%98.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 0421 | [数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0421.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md) | 位运算、字典树、数组、哈希表 | 中等 | -| 0424 | [替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0424.%20%E6%9B%BF%E6%8D%A2%E5%90%8E%E7%9A%84%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0425 | [单词方块](https://leetcode.cn/problems/word-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0425.%20%E5%8D%95%E8%AF%8D%E6%96%B9%E5%9D%97.md) | 字典树、数组、字符串、回溯 | 困难 | -| 0426 | [将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0426.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%8E%92%E5%BA%8F%E7%9A%84%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | -| 0428 | [序列化和反序列化 N 叉树](https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0428.%20%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20N%20%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、字符串 | 困难 | -| 0429 | [N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0429.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索 | 中等 | -| 0430 | [扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0430.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 深度优先搜索、链表、双向链表 | 中等 | -| 0435 | [无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0435.%20%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.md) | 贪心、数组、动态规划、排序 | 中等 | -| 0437 | [路径总和 III](https://leetcode.cn/problems/path-sum-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0437.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20III.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0438 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0443 | [压缩字符串](https://leetcode.cn/problems/string-compression/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0443.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 中等 | -| 0445 | [两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0445.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 栈、链表、数学 | 中等 | -| 0447 | [回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0447.%20%E5%9B%9E%E6%97%8B%E9%95%96%E7%9A%84%E6%95%B0%E9%87%8F.md) | 数组、哈希表、数学 | 中等 | -| 0450 | [删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0450.%20%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0451 | [根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | -| 0452 | [用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0452.%20%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88%86%E6%B0%94%E7%90%83.md) | 贪心、数组、排序 | 中等 | -| 0454 | [四数相加 II](https://leetcode.cn/problems/4sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0454.%20%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 数组、哈希表 | 中等 | -| 0455 | [分发饼干](https://leetcode.cn/problems/assign-cookies/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0455.%20%E5%88%86%E5%8F%91%E9%A5%BC%E5%B9%B2.md) | 贪心、数组、双指针、排序 | 简单 | -| 0459 | [重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0459.%20%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 0461 | [汉明距离](https://leetcode.cn/problems/hamming-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0461.%20%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB.md) | 位运算 | 简单 | -| 0463 | [岛屿的周长](https://leetcode.cn/problems/island-perimeter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0463.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E5%91%A8%E9%95%BF.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | -| 0464 | [我能赢吗](https://leetcode.cn/problems/can-i-win/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0464.%20%E6%88%91%E8%83%BD%E8%B5%A2%E5%90%97.md) | 位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 | 中等 | -| 0467 | [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0468 | [验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0468.%20%E9%AA%8C%E8%AF%81IP%E5%9C%B0%E5%9D%80.md) | 字符串 | 中等 | -| 0473 | [火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0473.%20%E7%81%AB%E6%9F%B4%E6%8B%BC%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 0474 | [一和零](https://leetcode.cn/problems/ones-and-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0474.%20%E4%B8%80%E5%92%8C%E9%9B%B6.md) | 数组、字符串、动态规划 | 中等 | -| 0480 | [滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | -| 0485 | [最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 数组 | 简单 | -| 0486 | [预测赢家](https://leetcode.cn/problems/predict-the-winner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0486.%20%E9%A2%84%E6%B5%8B%E8%B5%A2%E5%AE%B6.md) | 递归、数组、数学、动态规划、博弈 | 中等 | -| 0487 | [最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0487.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20II.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0491 | [递增子序列](https://leetcode.cn/problems/non-decreasing-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0491.%20%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 位运算、数组、哈希表、回溯 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 0496 | [下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0496.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20I.md) | 栈、数组、哈希表、单调栈 | 简单 | -| 0498 | [对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0498.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86.md) | 数组、矩阵、模拟 | 中等 | -| 0501 | [二叉搜索树中的众数](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0501.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%BC%97%E6%95%B0.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0503 | [下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II.md) | 栈、数组、单调栈 | 中等 | -| 0504 | [七进制数](https://leetcode.cn/problems/base-7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0504.%20%E4%B8%83%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 数学 | 简单 | -| 0506 | [相对名次](https://leetcode.cn/problems/relative-ranks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0506.%20%E7%9B%B8%E5%AF%B9%E5%90%8D%E6%AC%A1.md) | 数组、排序、堆(优先队列) | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0513 | [找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0513.%20%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0515 | [在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0515.%20%E5%9C%A8%E6%AF%8F%E4%B8%AA%E6%A0%91%E8%A1%8C%E4%B8%AD%E6%89%BE%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | -| 0525 | [连续数组](https://leetcode.cn/problems/contiguous-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0525.%20%E8%BF%9E%E7%BB%AD%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | -| 0526 | [优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0526.%20%E4%BC%98%E7%BE%8E%E7%9A%84%E6%8E%92%E5%88%97.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 0530 | [二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0530.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF%B9%E5%B7%AE.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0538 | [把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0538.%20%E6%8A%8A%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E6%8D%A2%E4%B8%BA%E7%B4%AF%E5%8A%A0%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0539 | [最小时间差](https://leetcode.cn/problems/minimum-time-difference/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0539.%20%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4%E5%B7%AE.md) | 数组、数学、字符串、排序 | 中等 | -| 0542 | [01 矩阵](https://leetcode.cn/problems/01-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0542.%2001%20%E7%9F%A9%E9%98%B5.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0546 | [移除盒子](https://leetcode.cn/problems/remove-boxes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0546.%20%E7%A7%BB%E9%99%A4%E7%9B%92%E5%AD%90.md) | 记忆化搜索、数组、动态规划 | 困难 | -| 0547 | [省份数量](https://leetcode.cn/problems/number-of-provinces/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0547.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0557 | [反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0557.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20III.md) | 双指针、字符串 | 简单 | -| 0560 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | -| 0561 | [数组拆分](https://leetcode.cn/problems/array-partition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md) | 贪心、数组、计数排序、排序 | 简单 | -| 0567 | [字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0567.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 哈希表、双指针、字符串、滑动窗口 | 中等 | -| 0575 | [分糖果](https://leetcode.cn/problems/distribute-candies/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0575.%20%E5%88%86%E7%B3%96%E6%9E%9C.md) | 数组、哈希表 | 简单 | -| 0576 | [出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md) | 动态规划 | 中等 | -| 0583 | [两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0583.%20%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.md) | 字符串、动态规划 | 中等 | -| 0589 | [N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0589.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0590 | [N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0590.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0599 | [两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0599.%20%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B4%A2%E5%BC%95%E6%80%BB%E5%92%8C.md) | 数组、哈希表、字符串 | 简单 | -| 0600 | [不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0600.%20%E4%B8%8D%E5%90%AB%E8%BF%9E%E7%BB%AD1%E7%9A%84%E9%9D%9E%E8%B4%9F%E6%95%B4%E6%95%B0.md) | 动态规划 | 困难 | -| 0611 | [有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0611.%20%E6%9C%89%E6%95%88%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | -| 0616 | [给字符串添加加粗标签](https://leetcode.cn/problems/add-bold-tag-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0616.%20%E7%BB%99%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%B7%BB%E5%8A%A0%E5%8A%A0%E7%B2%97%E6%A0%87%E7%AD%BE.md) | 字典树、数组、哈希表、字符串、字符串匹配 | 中等 | -| 0617 | [合并二叉树](https://leetcode.cn/problems/merge-two-binary-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0617.%20%E5%90%88%E5%B9%B6%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0621 | [任务调度器](https://leetcode.cn/problems/task-scheduler/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0621.%20%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8.md) | 贪心、数组、哈希表、计数、排序、堆(优先队列) | 中等 | -| 0622 | [设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0622.%20%E8%AE%BE%E8%AE%A1%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97.md) | 设计、队列、数组、链表 | 中等 | -| 0633 | [平方数之和](https://leetcode.cn/problems/sum-of-square-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0633.%20%E5%B9%B3%E6%96%B9%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数学、双指针、二分查找 | 中等 | -| 0639 | [解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0639.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95%20II.md) | 字符串、动态规划 | 困难 | -| 0642 | [设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0642.%20%E8%AE%BE%E8%AE%A1%E6%90%9C%E7%B4%A2%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E7%B3%BB%E7%BB%9F.md) | 设计、字典树、字符串、数据流 | 困难 | -| 0643 | [子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0643.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E6%95%B0%20I.md) | 数组、滑动窗口 | 简单 | -| 0647 | [回文子串](https://leetcode.cn/problems/palindromic-substrings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0647.%20%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0648 | [单词替换](https://leetcode.cn/problems/replace-words/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0648.%20%E5%8D%95%E8%AF%8D%E6%9B%BF%E6%8D%A2.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 0650 | [只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0650.%20%E5%8F%AA%E6%9C%89%E4%B8%A4%E4%B8%AA%E9%94%AE%E7%9A%84%E9%94%AE%E7%9B%98.md) | 数学、动态规划 | 中等 | -| 0652 | [寻找重复的子树](https://leetcode.cn/problems/find-duplicate-subtrees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0652.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E6%A0%91.md) | 树、深度优先搜索、哈希表、二叉树 | 中等 | -| 0653 | [两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0653.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20IV%20-%20%E8%BE%93%E5%85%A5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 | 简单 | -| 0654 | [最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0654.%20%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 栈、树、数组、分治、二叉树、单调栈 | 中等 | -| 0658 | [找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0664 | [奇怪的打印机](https://leetcode.cn/problems/strange-printer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0664.%20%E5%A5%87%E6%80%AA%E7%9A%84%E6%89%93%E5%8D%B0%E6%9C%BA.md) | 字符串、动态规划 | 困难 | -| 0665 | [非递减数列](https://leetcode.cn/problems/non-decreasing-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0665.%20%E9%9D%9E%E9%80%92%E5%87%8F%E6%95%B0%E5%88%97.md) | 数组 | 中等 | -| 0669 | [修剪二叉搜索树](https://leetcode.cn/problems/trim-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0669.%20%E4%BF%AE%E5%89%AA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 0674 | [最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0674.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.md) | 数组 | 简单 | -| 0676 | [实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0676.%20%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E9%AD%94%E6%B3%95%E5%AD%97%E5%85%B8.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0677 | [键值映射](https://leetcode.cn/problems/map-sum-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0677.%20%E9%94%AE%E5%80%BC%E6%98%A0%E5%B0%84.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | -| 0680 | [验证回文串 II](https://leetcode.cn/problems/valid-palindrome-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0680.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2%20II.md) | 贪心、双指针、字符串 | 简单 | -| 0683 | [K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md) | 树状数组、数组、有序集合、滑动窗口 | 困难 | -| 0684 | [冗余连接](https://leetcode.cn/problems/redundant-connection/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0686 | [重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0686.%20%E9%87%8D%E5%A4%8D%E5%8F%A0%E5%8A%A0%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 字符串、字符串匹配 | 中等 | -| 0687 | [最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0687.%20%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0688 | [骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0688.%20%E9%AA%91%E5%A3%AB%E5%9C%A8%E6%A3%8B%E7%9B%98%E4%B8%8A%E7%9A%84%E6%A6%82%E7%8E%87.md) | 动态规划 | 中等 | -| 0690 | [员工的重要性](https://leetcode.cn/problems/employee-importance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0690.%20%E5%91%98%E5%B7%A5%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7.md) | 深度优先搜索、广度优先搜索、哈希表 | 中等 | -| 0691 | [贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0691.%20%E8%B4%B4%E7%BA%B8%E6%8B%BC%E8%AF%8D.md) | 位运算、数组、字符串、动态规划、回溯、状态压缩 | 困难 | -| 0695 | [岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0698 | [划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0698.%20%E5%88%92%E5%88%86%E4%B8%BAk%E4%B8%AA%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E9%9B%86.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 0700 | [二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0700.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%90%9C%E7%B4%A2.md) | 树、二叉搜索树、二叉树 | 简单 | -| 0701 | [二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0701.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%8F%92%E5%85%A5%E6%93%8D%E4%BD%9C.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0702 | [搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0702.%20%E6%90%9C%E7%B4%A2%E9%95%BF%E5%BA%A6%E6%9C%AA%E7%9F%A5%E7%9A%84%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找、交互 | 中等 | -| 0703 | [数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 0704 | [二分查找](https://leetcode.cn/problems/binary-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md) | 数组、二分查找 | 简单 | -| 0705 | [设计哈希集合](https://leetcode.cn/problems/design-hashset/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0705.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E9%9B%86%E5%90%88.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0706 | [设计哈希映射](https://leetcode.cn/problems/design-hashmap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0706.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E6%98%A0%E5%B0%84.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0707 | [设计链表](https://leetcode.cn/problems/design-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0707.%20%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.md) | 设计、链表 | 中等 | -| 0708 | [循环有序列表的插入](https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0708.%20%E5%BE%AA%E7%8E%AF%E6%9C%89%E5%BA%8F%E5%88%97%E8%A1%A8%E7%9A%84%E6%8F%92%E5%85%A5.md) | 链表 | 中等 | -| 0709 | [转换成小写字母](https://leetcode.cn/problems/to-lower-case/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0709.%20%E8%BD%AC%E6%8D%A2%E6%88%90%E5%B0%8F%E5%86%99%E5%AD%97%E6%AF%8D.md) | 字符串 | 简单 | -| 0713 | [乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0713.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、滑动窗口 | 中等 | -| 0714 | [买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0714.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%E5%90%AB%E6%89%8B%E7%BB%AD%E8%B4%B9.md) | 贪心、数组 | 中等 | -| 0715 | [Range 模块](https://leetcode.cn/problems/range-module/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0715.%20Range%20%E6%A8%A1%E5%9D%97.md) | 设计、线段树、有序集合 | 困难 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0719 | [找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、二分查找、排序 | 困难 | -| 0720 | [词典中最长的单词](https://leetcode.cn/problems/longest-word-in-dictionary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0720.%20%E8%AF%8D%E5%85%B8%E4%B8%AD%E6%9C%80%E9%95%BF%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 字典树、数组、哈希表、字符串、排序 | 中等 | -| 0724 | [寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0724.%20%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E5%BF%83%E4%B8%8B%E6%A0%87.md) | 数组、前缀和 | 简单 | -| 0727 | [最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0727.%20%E6%9C%80%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划、滑动窗口 | 困难 | -| 0729 | [我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0729.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20I.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0731 | [我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0731.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20II.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0732 | [我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0732.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20III.md) | 设计、线段树、二分查找、有序集合 | 困难 | -| 0733 | [图像渲染](https://leetcode.cn/problems/flood-fill/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0733.%20%E5%9B%BE%E5%83%8F%E6%B8%B2%E6%9F%93.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | -| 0735 | [行星碰撞](https://leetcode.cn/problems/asteroid-collision/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0735.%20%E8%A1%8C%E6%98%9F%E7%A2%B0%E6%92%9E.md) | 栈、数组、模拟 | 中等 | -| 0738 | [单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0738.%20%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E%E7%9A%84%E6%95%B0%E5%AD%97.md) | 贪心、数学 | 中等 | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0744 | [寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0744.%20%E5%AF%BB%E6%89%BE%E6%AF%94%E7%9B%AE%E6%A0%87%E5%AD%97%E6%AF%8D%E5%A4%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E6%AF%8D.md) | 数组、二分查找 | 简单 | -| 0746 | [使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0746.%20%E4%BD%BF%E7%94%A8%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 数组、动态规划 | 简单 | -| 0752 | [打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0752.%20%E6%89%93%E5%BC%80%E8%BD%AC%E7%9B%98%E9%94%81.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | -| 0758 | [字符串中的加粗单词](https://leetcode.cn/problems/bold-words-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0758.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8A%A0%E7%B2%97%E5%8D%95%E8%AF%8D.md) | 字典树、数组、哈希表、字符串、字符串匹配 | 中等 | -| 0763 | [划分字母区间](https://leetcode.cn/problems/partition-labels/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0763.%20%E5%88%92%E5%88%86%E5%AD%97%E6%AF%8D%E5%8C%BA%E9%97%B4.md) | 贪心、哈希表、双指针、字符串 | 中等 | -| 0765 | [情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0765.%20%E6%83%85%E4%BE%A3%E7%89%B5%E6%89%8B.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | -| 0766 | [托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0766.%20%E6%89%98%E6%99%AE%E5%88%A9%E8%8C%A8%E7%9F%A9%E9%98%B5.md) | 数组、矩阵 | 简单 | -| 0771 | [宝石与石头](https://leetcode.cn/problems/jewels-and-stones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0771.%20%E5%AE%9D%E7%9F%B3%E4%B8%8E%E7%9F%B3%E5%A4%B4.md) | 哈希表、字符串 | 简单 | -| 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | -| 0779 | [第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0779.%20%E7%AC%ACK%E4%B8%AA%E8%AF%AD%E6%B3%95%E7%AC%A6%E5%8F%B7.md) | 位运算、递归、数学 | 中等 | -| 0784 | [字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0784.%20%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%B0%8F%E5%86%99%E5%85%A8%E6%8E%92%E5%88%97.md) | 位运算、字符串、回溯 | 中等 | -| 0785 | [判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0788 | [旋转数字](https://leetcode.cn/problems/rotated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0788.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 中等 | -| 0795 | [区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0795.%20%E5%8C%BA%E9%97%B4%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md) | 数组、双指针 | 中等 | -| 0796 | [旋转字符串](https://leetcode.cn/problems/rotate-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0796.%20%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 0797 | [所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | -| 0800 | [相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0800.%20%E7%9B%B8%E4%BC%BC%20RGB%20%E9%A2%9C%E8%89%B2.md) | 数学、字符串、枚举 | 简单 | -| 0801 | [使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0801.%20%E4%BD%BF%E5%BA%8F%E5%88%97%E9%80%92%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md) | 数组、动态规划 | 困难 | -| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0803 | [打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0803.%20%E6%89%93%E7%A0%96%E5%9D%97.md) | 并查集、数组、矩阵 | 困难 | -| 0806 | [写字符串需要的行数](https://leetcode.cn/problems/number-of-lines-to-write-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0806.%20%E5%86%99%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%9C%80%E8%A6%81%E7%9A%84%E8%A1%8C%E6%95%B0.md) | 数组、字符串 | 简单 | -| 0811 | [子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0811.%20%E5%AD%90%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E8%AE%A1%E6%95%B0.md) | 数组、哈希表、字符串、计数 | 中等 | -| 0814 | [二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0814.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0819 | [最常见的单词](https://leetcode.cn/problems/most-common-word/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0819.%20%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 哈希表、字符串、计数 | 简单 | -| 0820 | [单词的压缩编码](https://leetcode.cn/problems/short-encoding-of-words/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0820.%20%E5%8D%95%E8%AF%8D%E7%9A%84%E5%8E%8B%E7%BC%A9%E7%BC%96%E7%A0%81.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 0821 | [字符的最短距离](https://leetcode.cn/problems/shortest-distance-to-a-character/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0821.%20%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、字符串 | 简单 | -| 0824 | [山羊拉丁文](https://leetcode.cn/problems/goat-latin/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0824.%20%E5%B1%B1%E7%BE%8A%E6%8B%89%E4%B8%81%E6%96%87.md) | 字符串 | 简单 | -| 0830 | [较大分组的位置](https://leetcode.cn/problems/positions-of-large-groups/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0830.%20%E8%BE%83%E5%A4%A7%E5%88%86%E7%BB%84%E7%9A%84%E4%BD%8D%E7%BD%AE.md) | 字符串 | 简单 | -| 0832 | [翻转图像](https://leetcode.cn/problems/flipping-an-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0832.%20%E7%BF%BB%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、双指针、矩阵、模拟 | 简单 | -| 0834 | [树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0834.%20%E6%A0%91%E4%B8%AD%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、图、动态规划 | 困难 | -| 0836 | [矩形重叠](https://leetcode.cn/problems/rectangle-overlap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0836.%20%E7%9F%A9%E5%BD%A2%E9%87%8D%E5%8F%A0.md) | 几何、数学 | 简单 | -| 0841 | [钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0841.%20%E9%92%A5%E5%8C%99%E5%92%8C%E6%88%BF%E9%97%B4.md) | 深度优先搜索、广度优先搜索、图 | 中等 | -| 0844 | [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0844.%20%E6%AF%94%E8%BE%83%E5%90%AB%E9%80%80%E6%A0%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、双指针、字符串、模拟 | 简单 | -| 0845 | [数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0845.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E5%B1%B1%E8%84%89.md) | 数组、双指针、动态规划、枚举 | 中等 | -| 0846 | [一手顺子](https://leetcode.cn/problems/hand-of-straights/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0846.%20%E4%B8%80%E6%89%8B%E9%A1%BA%E5%AD%90.md) | 贪心、数组、哈希表、排序 | 中等 | -| 0847 | [访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0847.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md) | 位运算、广度优先搜索、图、动态规划、状态压缩 | 困难 | -| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | -| 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | -| 0852 | [山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0852.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E7%9A%84%E5%B3%B0%E9%A1%B6%E7%B4%A2%E5%BC%95.md) | 数组、二分查找 | 中等 | -| 0860 | [柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0860.%20%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.md) | 贪心、数组 | 简单 | -| 0861 | [翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0861.%20%E7%BF%BB%E8%BD%AC%E7%9F%A9%E9%98%B5%E5%90%8E%E7%9A%84%E5%BE%97%E5%88%86.md) | 贪心、位运算、数组、矩阵 | 中等 | -| 0867 | [转置矩阵](https://leetcode.cn/problems/transpose-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0867.%20%E8%BD%AC%E7%BD%AE%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 简单 | -| 0872 | [叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0872.%20%E5%8F%B6%E5%AD%90%E7%9B%B8%E4%BC%BC%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0873 | [最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0873.%20%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6.md) | 数组、哈希表、动态规划 | 中等 | -| 0875 | [爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0875.%20%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82.md) | 数组、二分查找 | 中等 | -| 0876 | [链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0876.%20%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 简单 | -| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | -| 0881 | [救生艇](https://leetcode.cn/problems/boats-to-save-people/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0881.%20%E6%95%91%E7%94%9F%E8%89%87.md) | 贪心、数组、双指针、排序 | 中等 | -| 0884 | [两句话中的不常见单词](https://leetcode.cn/problems/uncommon-words-from-two-sentences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0884.%20%E4%B8%A4%E5%8F%A5%E8%AF%9D%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%B8%B8%E8%A7%81%E5%8D%95%E8%AF%8D.md) | 哈希表、字符串 | 简单 | -| 0886 | [可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0886.%20%E5%8F%AF%E8%83%BD%E7%9A%84%E4%BA%8C%E5%88%86%E6%B3%95.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0887 | [鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0887.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD.md) | 数学、二分查找、动态规划 | 困难 | -| 0889 | [根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0889.%20%E6%A0%B9%E6%8D%AE%E5%89%8D%E5%BA%8F%E5%92%8C%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0897 | [递增顺序搜索树](https://leetcode.cn/problems/increasing-order-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0897.%20%E9%80%92%E5%A2%9E%E9%A1%BA%E5%BA%8F%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 栈、树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0901 | [股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0901.%20%E8%82%A1%E7%A5%A8%E4%BB%B7%E6%A0%BC%E8%B7%A8%E5%BA%A6.md) | 栈、设计、数据流、单调栈 | 中等 | -| 0902 | [最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0902.%20%E6%9C%80%E5%A4%A7%E4%B8%BA%20N%20%E7%9A%84%E6%95%B0%E5%AD%97%E7%BB%84%E5%90%88.md) | 数组、数学、字符串、二分查找、动态规划 | 困难 | -| 0904 | [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md) | 数组、哈希表、滑动窗口 | 中等 | -| 0908 | [最小差值 I](https://leetcode.cn/problems/smallest-range-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.md) | 数组、数学 | 简单 | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0918 | [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0918.%20%E7%8E%AF%E5%BD%A2%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 队列、数组、分治、动态规划、单调队列 | 中等 | -| 0919 | [完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0919.%20%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E6%8F%92%E5%85%A5%E5%99%A8.md) | 树、广度优先搜索、设计、二叉树 | 中等 | -| 0921 | [使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0921.%20%E4%BD%BF%E6%8B%AC%E5%8F%B7%E6%9C%89%E6%95%88%E7%9A%84%E6%9C%80%E5%B0%91%E6%B7%BB%E5%8A%A0.md) | 栈、贪心、字符串 | 中等 | -| 0925 | [长按键入](https://leetcode.cn/problems/long-pressed-name/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0925.%20%E9%95%BF%E6%8C%89%E9%94%AE%E5%85%A5.md) | 双指针、字符串 | 简单 | -| 0932 | [漂亮数组](https://leetcode.cn/problems/beautiful-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0932.%20%E6%BC%82%E4%BA%AE%E6%95%B0%E7%BB%84.md) | 数组、数学、分治 | 中等 | -| 0933 | [最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0933.%20%E6%9C%80%E8%BF%91%E7%9A%84%E8%AF%B7%E6%B1%82%E6%AC%A1%E6%95%B0.md) | 设计、队列、数据流 | 简单 | -| 0935 | [骑士拨号器](https://leetcode.cn/problems/knight-dialer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0935.%20%E9%AA%91%E5%A3%AB%E6%8B%A8%E5%8F%B7%E5%99%A8.md) | 动态规划 | 中等 | -| 0938 | [二叉搜索树的范围和](https://leetcode.cn/problems/range-sum-of-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0938.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E8%8C%83%E5%9B%B4%E5%92%8C.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0946 | [验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0946.%20%E9%AA%8C%E8%AF%81%E6%A0%88%E5%BA%8F%E5%88%97.md) | 栈、数组、模拟 | 中等 | -| 0947 | [移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0947.%20%E7%A7%BB%E9%99%A4%E6%9C%80%E5%A4%9A%E7%9A%84%E5%90%8C%E8%A1%8C%E6%88%96%E5%90%8C%E5%88%97%E7%9F%B3%E5%A4%B4.md) | 深度优先搜索、并查集、图 | 中等 | -| 0953 | [验证外星语词典](https://leetcode.cn/problems/verifying-an-alien-dictionary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0953.%20%E9%AA%8C%E8%AF%81%E5%A4%96%E6%98%9F%E8%AF%AD%E8%AF%8D%E5%85%B8.md) | 数组、哈希表、字符串 | 简单 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0959 | [由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0959.%20%E7%94%B1%E6%96%9C%E6%9D%A0%E5%88%92%E5%88%86%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0968 | [监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0968.%20%E7%9B%91%E6%8E%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0973 | [最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0973.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E5%8E%9F%E7%82%B9%E7%9A%84%20K%20%E4%B8%AA%E7%82%B9.md) | 几何、数组、数学、分治、快速选择、排序、堆(优先队列) | 中等 | -| 0974 | [和可被 K 整除的子数组](https://leetcode.cn/problems/subarray-sums-divisible-by-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0974.%20%E5%92%8C%E5%8F%AF%E8%A2%AB%20K%20%E6%95%B4%E9%99%A4%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | -| 0976 | [三角形的最大周长](https://leetcode.cn/problems/largest-perimeter-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0976.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%91%A8%E9%95%BF.md) | 贪心、数组、数学、排序 | 简单 | -| 0977 | [有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0977.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.md) | 数组、双指针、排序 | 简单 | -| 0978 | [最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0978.%20%E6%9C%80%E9%95%BF%E6%B9%8D%E6%B5%81%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0982 | [按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0982.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E4%B8%BA%E9%9B%B6%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84.md) | 位运算、数组、哈希表 | 困难 | -| 0990 | [等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0990.%20%E7%AD%89%E5%BC%8F%E6%96%B9%E7%A8%8B%E7%9A%84%E5%8F%AF%E6%BB%A1%E8%B6%B3%E6%80%A7.md) | 并查集、图、数组、字符串 | 中等 | -| 0992 | [K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0992.%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、计数、滑动窗口 | 困难 | -| 0993 | [二叉树的堂兄弟节点](https://leetcode.cn/problems/cousins-in-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0993.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%A0%82%E5%85%84%E5%BC%9F%E8%8A%82%E7%82%B9.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | -| 1000 | [合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1000.%20%E5%90%88%E5%B9%B6%E7%9F%B3%E5%A4%B4%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md) | 数组、动态规划、前缀和 | 困难 | -| 1002 | [查找共用字符](https://leetcode.cn/problems/find-common-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1002.%20%E6%9F%A5%E6%89%BE%E5%85%B1%E7%94%A8%E5%AD%97%E7%AC%A6.md) | 数组、哈希表、字符串 | 简单 | -| 1004 | [最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1004.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 1005 | [K 次取反后最大化的数组和](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1005.%20K%20%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md) | 贪心、数组、排序 | 简单 | -| 1008 | [前序遍历构造二叉搜索树](https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1008.%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 栈、树、二叉搜索树、数组、二叉树、单调栈 | 中等 | -| 1009 | [十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1009.%20%E5%8D%81%E8%BF%9B%E5%88%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%8F%8D%E7%A0%81.md) | 位运算 | 简单 | -| 1011 | [在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1011.%20%E5%9C%A8%20D%20%E5%A4%A9%E5%86%85%E9%80%81%E8%BE%BE%E5%8C%85%E8%A3%B9%E7%9A%84%E8%83%BD%E5%8A%9B.md) | 数组、二分查找 | 中等 | -| 1012 | [至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1012.%20%E8%87%B3%E5%B0%91%E6%9C%89%201%20%E4%BD%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 困难 | -| 1014 | [最佳观光组合](https://leetcode.cn/problems/best-sightseeing-pair/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1014.%20%E6%9C%80%E4%BD%B3%E8%A7%82%E5%85%89%E7%BB%84%E5%90%88.md) | 数组、动态规划 | 中等 | -| 1020 | [飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1020.%20%E9%A3%9E%E5%9C%B0%E7%9A%84%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1023 | [驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1023.%20%E9%A9%BC%E5%B3%B0%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 字典树、双指针、字符串、字符串匹配 | 中等 | -| 1025 | [除数博弈](https://leetcode.cn/problems/divisor-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1025.%20%E9%99%A4%E6%95%B0%E5%8D%9A%E5%BC%88.md) | 脑筋急转弯、数学、动态规划、博弈 | 简单 | -| 1028 | [从先序遍历还原二叉树](https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1028.%20%E4%BB%8E%E5%85%88%E5%BA%8F%E9%81%8D%E5%8E%86%E8%BF%98%E5%8E%9F%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、字符串、二叉树 | 困难 | -| 1029 | [两地调度](https://leetcode.cn/problems/two-city-scheduling/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1029.%20%E4%B8%A4%E5%9C%B0%E8%B0%83%E5%BA%A6.md) | 贪心、数组、排序 | 中等 | -| 1034 | [边界着色](https://leetcode.cn/problems/coloring-a-border/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1034.%20%E8%BE%B9%E7%95%8C%E7%9D%80%E8%89%B2.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 1035 | [不相交的线](https://leetcode.cn/problems/uncrossed-lines/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1035.%20%E4%B8%8D%E7%9B%B8%E4%BA%A4%E7%9A%84%E7%BA%BF.md) | 数组、动态规划 | 中等 | -| 1037 | [有效的回旋镖](https://leetcode.cn/problems/valid-boomerang/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1037.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%9B%9E%E6%97%8B%E9%95%96.md) | 几何、数组、数学 | 简单 | -| 1038 | [从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1038.%20%E4%BB%8E%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%88%B0%E6%9B%B4%E5%A4%A7%E5%92%8C%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 1039 | [多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1039.%20%E5%A4%9A%E8%BE%B9%E5%BD%A2%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%9A%84%E6%9C%80%E4%BD%8E%E5%BE%97%E5%88%86.md) | 数组、动态规划 | 中等 | -| 1047 | [删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1047.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 栈、字符串 | 简单 | -| 1049 | [最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1049.%20%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F%20II.md) | 数组、动态规划 | 中等 | -| 1052 | [爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1052.%20%E7%88%B1%E7%94%9F%E6%B0%94%E7%9A%84%E4%B9%A6%E5%BA%97%E8%80%81%E6%9D%BF.md) | 数组、滑动窗口 | 中等 | -| 1065 | [字符串的索引对](https://leetcode.cn/problems/index-pairs-of-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1065.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E7%B4%A2%E5%BC%95%E5%AF%B9.md) | 字典树、数组、字符串、排序 | 简单 | -| 1079 | [活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1079.%20%E6%B4%BB%E5%AD%97%E5%8D%B0%E5%88%B7.md) | 哈希表、字符串、回溯、计数 | 中等 | -| 1081 | [不同字符的最小子序列](https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1081.%20%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E5%BA%8F%E5%88%97.md) | 栈、贪心、字符串、单调栈 | 中等 | -| 1089 | [复写零](https://leetcode.cn/problems/duplicate-zeros/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1089.%20%E5%A4%8D%E5%86%99%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 1091 | [二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1091.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md) | 广度优先搜索、数组、矩阵 | 中等 | -| 1095 | [山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1095.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%80%BC.md) | 数组、二分查找、交互 | 困难 | -| 1099 | [小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1099.%20%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 简单 | -| 1100 | [长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1100.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1103 | [分糖果 II](https://leetcode.cn/problems/distribute-candies-to-people/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1103.%20%E5%88%86%E7%B3%96%E6%9E%9C%20II.md) | 数学、模拟 | 简单 | -| 1108 | [IP 地址无效化](https://leetcode.cn/problems/defanging-an-ip-address/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1108.%20IP%20%E5%9C%B0%E5%9D%80%E6%97%A0%E6%95%88%E5%8C%96.md) | 字符串 | 简单 | -| 1109 | [航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md) | 数组、前缀和 | 中等 | -| 1122 | [数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1122.%20%E6%95%B0%E7%BB%84%E7%9A%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md) | 数组、哈希表、计数排序、排序 | 简单 | -| 1136 | [并行课程](https://leetcode.cn/problems/parallel-courses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1136.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B.md) | 图、拓扑排序 | 中等 | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 1151 | [最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1151.%20%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0%E6%9D%A5%E7%BB%84%E5%90%88%E6%89%80%E6%9C%89%E7%9A%84%201.md) | 数组、滑动窗口 | 中等 | -| 1155 | [掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1155.%20%E6%8E%B7%E9%AA%B0%E5%AD%90%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%92%8C%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 动态规划 | 中等 | -| 1161 | [最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1161.%20%E6%9C%80%E5%A4%A7%E5%B1%82%E5%86%85%E5%85%83%E7%B4%A0%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 1176 | [健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1176.%20%E5%81%A5%E8%BA%AB%E8%AE%A1%E5%88%92%E8%AF%84%E4%BC%B0.md) | 数组、滑动窗口 | 简单 | -| 1184 | [公交站间的距离](https://leetcode.cn/problems/distance-between-bus-stops/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1184.%20%E5%85%AC%E4%BA%A4%E7%AB%99%E9%97%B4%E7%9A%84%E8%B7%9D%E7%A6%BB.md) | 数组 | 简单 | -| 1202 | [交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1202.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md) | 深度优先搜索、广度优先搜索、并查集、哈希表、字符串 | 中等 | -| 1208 | [尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1208.%20%E5%B0%BD%E5%8F%AF%E8%83%BD%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md) | 字符串、二分查找、前缀和、滑动窗口 | 中等 | -| 1217 | [玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1217.%20%E7%8E%A9%E7%AD%B9%E7%A0%81.md) | 贪心、数组、数学 | 简单 | -| 1220 | [统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1220.%20%E7%BB%9F%E8%AE%A1%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 动态规划 | 困难 | -| 1227 | [飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1227.%20%E9%A3%9E%E6%9C%BA%E5%BA%A7%E4%BD%8D%E5%88%86%E9%85%8D%E6%A6%82%E7%8E%87.md) | 脑筋急转弯、数学、动态规划、概率与统计 | 中等 | -| 1229 | [安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1229.%20%E5%AE%89%E6%8E%92%E4%BC%9A%E8%AE%AE%E6%97%A5%E7%A8%8B.md) | 数组、双指针、排序 | 中等 | -| 1232 | [缀点成线](https://leetcode.cn/problems/check-if-it-is-a-straight-line/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1232.%20%E7%BC%80%E7%82%B9%E6%88%90%E7%BA%BF.md) | 几何、数组、数学 | 简单 | -| 1245 | [树的直径](https://leetcode.cn/problems/tree-diameter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1245.%20%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 1247 | [交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1247.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%BD%BF%E5%BE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%90%8C.md) | 贪心、数学、字符串 | 中等 | -| 1254 | [统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1254.%20%E7%BB%9F%E8%AE%A1%E5%B0%81%E9%97%AD%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1266 | [访问所有点的最小时间](https://leetcode.cn/problems/minimum-time-visiting-all-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1266.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4.md) | 几何、数组、数学 | 简单 | -| 1268 | [搜索推荐系统](https://leetcode.cn/problems/search-suggestions-system/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1268.%20%E6%90%9C%E7%B4%A2%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F.md) | 字典树、数组、字符串 | 中等 | -| 1281 | [整数的各位积和之差](https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1281.%20%E6%95%B4%E6%95%B0%E7%9A%84%E5%90%84%E4%BD%8D%E7%A7%AF%E5%92%8C%E4%B9%8B%E5%B7%AE.md) | 数学 | 简单 | -| 1296 | [划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1296.%20%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84%E4%B8%BA%E8%BF%9E%E7%BB%AD%E6%95%B0%E5%AD%97%E7%9A%84%E9%9B%86%E5%90%88.md) | 贪心、数组、哈希表、排序 | 中等 | -| 1300 | [转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1300.%20%E8%BD%AC%E5%8F%98%E6%95%B0%E7%BB%84%E5%90%8E%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、二分查找、排序 | 中等 | -| 1305 | [两棵二叉搜索树中的所有元素](https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1305.%20%E4%B8%A4%E6%A3%B5%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0.md) | 树、深度优先搜索、二叉搜索树、二叉树、排序 | 中等 | -| 1310 | [子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md) | 位运算、数组、前缀和 | 中等 | -| 1317 | [将整数转换为两个无零整数的和](https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1317.%20%E5%B0%86%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%B8%A4%E4%B8%AA%E6%97%A0%E9%9B%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%92%8C.md) | 数学 | 简单 | -| 1319 | [连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1319.%20%E8%BF%9E%E9%80%9A%E7%BD%91%E7%BB%9C%E7%9A%84%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 1343 | [大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1343.%20%E5%A4%A7%E5%B0%8F%E4%B8%BA%20K%20%E4%B8%94%E5%B9%B3%E5%9D%87%E5%80%BC%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E9%98%88%E5%80%BC%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md) | 数组、滑动窗口 | 中等 | -| 1349 | [参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1381 | [设计一个支持增量操作的栈](https://leetcode.cn/problems/design-a-stack-with-increment-operation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1381.%20%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%94%AF%E6%8C%81%E5%A2%9E%E9%87%8F%E6%93%8D%E4%BD%9C%E7%9A%84%E6%A0%88.md) | 栈、设计、数组 | 中等 | -| 1400 | [构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1400.%20%E6%9E%84%E9%80%A0%20K%20%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 贪心、哈希表、字符串、计数 | 中等 | -| 1408 | [数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1408.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 数组、字符串、字符串匹配 | 简单 | -| 1422 | [分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1422.%20%E5%88%86%E5%89%B2%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md) | 字符串 | 简单 | -| 1423 | [可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1423.%20%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E6%95%B0.md) | 数组、前缀和、滑动窗口 | 中等 | -| 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | -| 1446 | [连续字符](https://leetcode.cn/problems/consecutive-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1446.%20%E8%BF%9E%E7%BB%AD%E5%AD%97%E7%AC%A6.md) | 字符串 | 简单 | -| 1447 | [最简分数](https://leetcode.cn/problems/simplified-fractions/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1447.%20%E6%9C%80%E7%AE%80%E5%88%86%E6%95%B0.md) | 数学、字符串、数论 | 中等 | -| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 1456 | [定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1456.%20%E5%AE%9A%E9%95%BF%E5%AD%90%E4%B8%B2%E4%B8%AD%E5%85%83%E9%9F%B3%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md) | 字符串、滑动窗口 | 中等 | -| 1480 | [一维数组的动态和](https://leetcode.cn/problems/running-sum-of-1d-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1480.%20%E4%B8%80%E7%BB%B4%E6%95%B0%E7%BB%84%E7%9A%84%E5%8A%A8%E6%80%81%E5%92%8C.md) | 数组、前缀和 | 简单 | -| 1482 | [制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1482.%20%E5%88%B6%E4%BD%9C%20m%20%E6%9D%9F%E8%8A%B1%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md) | 数组、二分查找 | 中等 | -| 1486 | [数组异或操作](https://leetcode.cn/problems/xor-operation-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1486.%20%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%93%8D%E4%BD%9C.md) | 位运算、数学 | 简单 | -| 1491 | [去掉最低工资和最高工资后的工资平均值](https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1491.%20%E5%8E%BB%E6%8E%89%E6%9C%80%E4%BD%8E%E5%B7%A5%E8%B5%84%E5%92%8C%E6%9C%80%E9%AB%98%E5%B7%A5%E8%B5%84%E5%90%8E%E7%9A%84%E5%B7%A5%E8%B5%84%E5%B9%B3%E5%9D%87%E5%80%BC.md) | 数组、排序 | 简单 | -| 1493 | [删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1493.%20%E5%88%A0%E6%8E%89%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BB%A5%E5%90%8E%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 1502 | [判断能否形成等差数列](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1502.%20%E5%88%A4%E6%96%AD%E8%83%BD%E5%90%A6%E5%BD%A2%E6%88%90%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97.md) | 数组、排序 | 简单 | -| 1507 | [转变日期格式](https://leetcode.cn/problems/reformat-date/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1507.%20%E8%BD%AC%E5%8F%98%E6%97%A5%E6%9C%9F%E6%A0%BC%E5%BC%8F.md) | 字符串 | 简单 | -| 1523 | [在区间范围内统计奇数数目](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1523.%20%E5%9C%A8%E5%8C%BA%E9%97%B4%E8%8C%83%E5%9B%B4%E5%86%85%E7%BB%9F%E8%AE%A1%E5%A5%87%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 数学 | 简单 | -| 1534 | [统计好三元组](https://leetcode.cn/problems/count-good-triplets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1534.%20%E7%BB%9F%E8%AE%A1%E5%A5%BD%E4%B8%89%E5%85%83%E7%BB%84.md) | 数组、枚举 | 简单 | -| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划、排序 | 困难 | -| 1551 | [使数组中所有元素相等的最小操作数](https://leetcode.cn/problems/minimum-operations-to-make-array-equal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1551.%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md) | 数学 | 中等 | -| 1556 | [千位分隔数](https://leetcode.cn/problems/thousand-separator/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1556.%20%E5%8D%83%E4%BD%8D%E5%88%86%E9%9A%94%E6%95%B0.md) | 字符串 | 简单 | -| 1561 | [你可以获得的最大硬币数目](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1561.%20%E4%BD%A0%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%A1%AC%E5%B8%81%E6%95%B0%E7%9B%AE.md) | 贪心、数组、数学、博弈、排序 | 中等 | -| 1567 | [乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1567.%20%E4%B9%98%E7%A7%AF%E4%B8%BA%E6%AD%A3%E6%95%B0%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6.md) | 贪心、数组、动态规划 | 中等 | -| 1593 | [拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1593.%20%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E5%94%AF%E4%B8%80%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE%E6%9C%80%E5%A4%A7.md) | 哈希表、字符串、回溯 | 中等 | -| 1595 | [连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 1603 | [设计停车系统](https://leetcode.cn/problems/design-parking-system/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1603.%20%E8%AE%BE%E8%AE%A1%E5%81%9C%E8%BD%A6%E7%B3%BB%E7%BB%9F.md) | 设计、计数、模拟 | 简单 | -| 1605 | [给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1605.%20%E7%BB%99%E5%AE%9A%E8%A1%8C%E5%92%8C%E5%88%97%E7%9A%84%E5%92%8C%E6%B1%82%E5%8F%AF%E8%A1%8C%E7%9F%A9%E9%98%B5.md) | 贪心、数组、矩阵 | 中等 | -| 1617 | [统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1617.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E6%A0%91%E4%B8%AD%E5%9F%8E%E5%B8%82%E4%B9%8B%E9%97%B4%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md) | 位运算、树、动态规划、状态压缩、枚举 | 困难 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 1646 | [获取生成数组中的最大值](https://leetcode.cn/problems/get-maximum-in-generated-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1646.%20%E8%8E%B7%E5%8F%96%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 数组、动态规划、模拟 | 简单 | -| 1658 | [将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1658.%20%E5%B0%86%20x%20%E5%87%8F%E5%88%B0%200%20%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md) | 数组、哈希表、二分查找、前缀和、滑动窗口 | 中等 | -| 1672 | [最富有客户的资产总量](https://leetcode.cn/problems/richest-customer-wealth/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1672.%20%E6%9C%80%E5%AF%8C%E6%9C%89%E5%AE%A2%E6%88%B7%E7%9A%84%E8%B5%84%E4%BA%A7%E6%80%BB%E9%87%8F.md) | 数组、矩阵 | 简单 | -| 1695 | [删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1695.%20%E5%88%A0%E9%99%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md) | 数组、哈希表、滑动窗口 | 中等 | -| 1698 | [字符串的不同子字符串个数](https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1698.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%8D%E5%90%8C%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AA%E6%95%B0.md) | 字典树、字符串、后缀数组、哈希函数、滚动哈希 | 中等 | -| 1710 | [卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1710.%20%E5%8D%A1%E8%BD%A6%E4%B8%8A%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%95%E5%85%83%E6%95%B0.md) | 贪心、数组、排序 | 简单 | -| 1716 | [计算力扣银行的钱](https://leetcode.cn/problems/calculate-money-in-leetcode-bank/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1716.%20%E8%AE%A1%E7%AE%97%E5%8A%9B%E6%89%A3%E9%93%B6%E8%A1%8C%E7%9A%84%E9%92%B1.md) | 数学 | 简单 | -| 1720 | [解码异或后的数组](https://leetcode.cn/problems/decode-xored-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1720.%20%E8%A7%A3%E7%A0%81%E5%BC%82%E6%88%96%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84.md) | 位运算、数组 | 简单 | -| 1736 | [替换隐藏数字得到的最晚时间](https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1736.%20%E6%9B%BF%E6%8D%A2%E9%9A%90%E8%97%8F%E6%95%B0%E5%AD%97%E5%BE%97%E5%88%B0%E7%9A%84%E6%9C%80%E6%99%9A%E6%97%B6%E9%97%B4.md) | 贪心、字符串 | 简单 | -| 1742 | [盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1742.%20%E7%9B%92%E5%AD%90%E4%B8%AD%E5%B0%8F%E7%90%83%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md) | 哈希表、数学、计数 | 简单 | -| 1749 | [任意子数组和的绝对值的最大值](https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1749.%20%E4%BB%BB%E6%84%8F%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E7%9A%84%E7%BB%9D%E5%AF%B9%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 数组、动态规划 | 中等 | -| 1779 | [找到最近的有相同 X 或 Y 坐标的点](https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1779.%20%E6%89%BE%E5%88%B0%E6%9C%80%E8%BF%91%E7%9A%84%E6%9C%89%E7%9B%B8%E5%90%8C%20X%20%E6%88%96%20Y%20%E5%9D%90%E6%A0%87%E7%9A%84%E7%82%B9.md) | 数组 | 简单 | -| 1790 | [仅执行一次字符串交换能否使两个字符串相等](https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1790.%20%E4%BB%85%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%A4%E6%8D%A2%E8%83%BD%E5%90%A6%E4%BD%BF%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md) | 哈希表、字符串、计数 | 简单 | -| 1791 | [找出星型图的中心节点](https://leetcode.cn/problems/find-center-of-star-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1791.%20%E6%89%BE%E5%87%BA%E6%98%9F%E5%9E%8B%E5%9B%BE%E7%9A%84%E4%B8%AD%E5%BF%83%E8%8A%82%E7%82%B9.md) | 图 | 简单 | -| 1822 | [数组元素积的符号](https://leetcode.cn/problems/sign-of-the-product-of-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1822.%20%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%A7%AF%E7%9A%84%E7%AC%A6%E5%8F%B7.md) | 数组、数学 | 简单 | -| 1833 | [雪糕的最大数量](https://leetcode.cn/problems/maximum-ice-cream-bars/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1833.%20%E9%9B%AA%E7%B3%95%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md) | 贪心、数组、排序 | 中等 | -| 1844 | [将所有数字用字符替换](https://leetcode.cn/problems/replace-all-digits-with-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1844.%20%E5%B0%86%E6%89%80%E6%9C%89%E6%95%B0%E5%AD%97%E7%94%A8%E5%AD%97%E7%AC%A6%E6%9B%BF%E6%8D%A2.md) | 字符串 | 简单 | -| 1858 | [包含所有前缀的最长单词](https://leetcode.cn/problems/longest-word-with-all-prefixes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1858.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E5%89%8D%E7%BC%80%E7%9A%84%E6%9C%80%E9%95%BF%E5%8D%95%E8%AF%8D.md) | 深度优先搜索、字典树 | 中等 | -| 1859 | [将句子排序](https://leetcode.cn/problems/sorting-the-sentence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1859.%20%E5%B0%86%E5%8F%A5%E5%AD%90%E6%8E%92%E5%BA%8F.md) | 字符串、排序 | 简单 | -| 1876 | [长度为三且各字符不同的子字符串](https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1876.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%E4%B8%89%E4%B8%94%E5%90%84%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串、计数、滑动窗口 | 简单 | -| 1877 | [数组中最大数对和的最小值](https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1877.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AF%B9%E5%92%8C%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 贪心、数组、双指针、排序 | 中等 | -| 1879 | [两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1879.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E7%9A%84%E5%BC%82%E6%88%96%E5%80%BC%E4%B9%8B%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 1903 | [字符串中的最大奇数](https://leetcode.cn/problems/largest-odd-number-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1903.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%A5%87%E6%95%B0.md) | 贪心、数学、字符串 | 简单 | -| 1925 | [统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1925.%20%E7%BB%9F%E8%AE%A1%E5%B9%B3%E6%96%B9%E5%92%8C%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数学、枚举 | 简单 | -| 1929 | [数组串联](https://leetcode.cn/problems/concatenation-of-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1929.%20%E6%95%B0%E7%BB%84%E4%B8%B2%E8%81%94.md) | 数组 | 简单 | -| 1941 | [检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1941.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E7%9B%B8%E5%90%8C.md) | 哈希表、字符串、计数 | 简单 | -| 1947 | [最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1986 | [完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1986.%20%E5%AE%8C%E6%88%90%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%B7%A5%E4%BD%9C%E6%97%B6%E9%97%B4%E6%AE%B5.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1991 | [找到数组的中间位置](https://leetcode.cn/problems/find-the-middle-index-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1991.%20%E6%89%BE%E5%88%B0%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E9%97%B4%E4%BD%8D%E7%BD%AE.md) | 数组、前缀和 | 简单 | -| 1994 | [好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1994.%20%E5%A5%BD%E5%AD%90%E9%9B%86%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 位运算、数组、数学、动态规划、状态压缩 | 困难 | -| 2011 | [执行操作后的变量值](https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2011.%20%E6%89%A7%E8%A1%8C%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E5%8F%98%E9%87%8F%E5%80%BC.md) | 数组、字符串、模拟 | 简单 | -| 2023 | [连接后等于目标字符串的字符串对](https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2023.%20%E8%BF%9E%E6%8E%A5%E5%90%8E%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9.md) | 数组、字符串 | 中等 | -| 2050 | [并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2050.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B%20III.md) | 图、拓扑排序、数组、动态规划 | 困难 | -| 2156 | [查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2156.%20%E6%9F%A5%E6%89%BE%E7%BB%99%E5%AE%9A%E5%93%88%E5%B8%8C%E5%80%BC%E7%9A%84%E5%AD%90%E4%B8%B2.md) | 字符串、滑动窗口、哈希函数、滚动哈希 | 困难 | -| 2172 | [数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2172.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B8%8E%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 2235 | [两整数相加](https://leetcode.cn/problems/add-two-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2235.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 数学 | 简单 | -| 2246 | [相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2246.%20%E7%9B%B8%E9%82%BB%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%80%E9%95%BF%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、图、拓扑排序、数组、字符串 | 困难 | -| 2249 | [统计圆内格点数目](https://leetcode.cn/problems/count-lattice-points-inside-a-circle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2249.%20%E7%BB%9F%E8%AE%A1%E5%9C%86%E5%86%85%E6%A0%BC%E7%82%B9%E6%95%B0%E7%9B%AE.md) | 几何、数组、哈希表、数学、枚举 | 中等 | -| 2276 | [统计区间中的整数数目](https://leetcode.cn/problems/count-integers-in-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2276.%20%E7%BB%9F%E8%AE%A1%E5%8C%BA%E9%97%B4%E4%B8%AD%E7%9A%84%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 设计、线段树、有序集合 | 困难 | -| 2376 | [统计特殊整数](https://leetcode.cn/problems/count-special-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2376.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E6%95%B4%E6%95%B0.md) | 数学、动态规划 | 困难 | -| 2427 | [公因子的数目](https://leetcode.cn/problems/number-of-common-factors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2427.%20%E5%85%AC%E5%9B%A0%E5%AD%90%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数学、枚举、数论 | 简单 | -| 2538 | [最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2538.%20%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%E5%92%8C%E4%B8%8E%E6%9C%80%E5%B0%8F%E4%BB%B7%E5%80%BC%E5%92%8C%E7%9A%84%E5%B7%AE%E5%80%BC.md) | 树、深度优先搜索、数组、动态规划 | 困难 | -| 2585 | [获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2585.%20%E8%8E%B7%E5%BE%97%E5%88%86%E6%95%B0%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 数组、动态规划 | 困难 | -| 2719 | [统计整数数目](https://leetcode.cn/problems/count-of-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2719.%20%E7%BB%9F%E8%AE%A1%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 数学、字符串、动态规划 | 困难 | -| 剑指 Offer 03 | [数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2003.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、哈希表、排序 | 简单 | -| 剑指 Offer 04 | [二维数组中的查找](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2004.%20%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE.md) | 数组、二分查找、分治、矩阵 | 中等 | -| 剑指 Offer 05 | [替换空格](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2005.%20%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.md) | 字符串 | 简单 | -| 剑指 Offer 06 | [从尾到头打印链表](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2006.%20%E4%BB%8E%E5%B0%BE%E5%88%B0%E5%A4%B4%E6%89%93%E5%8D%B0%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 剑指 Offer 07 | [重建二叉树](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2007.%20%E9%87%8D%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 剑指 Offer 09 | [用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2009.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 剑指 Offer 10- I | [斐波那契数列](https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2010-%20I.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 剑指 Offer 10- II | [青蛙跳台阶问题](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2010-%20II.%20%E9%9D%92%E8%9B%99%E8%B7%B3%E5%8F%B0%E9%98%B6%E9%97%AE%E9%A2%98.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 剑指 Offer 11 | [旋转数组的最小数字](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2011.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md) | 数组、二分查找 | 简单 | -| 剑指 Offer 12 | [矩阵中的路径](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2012.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 数组、回溯、矩阵 | 中等 | -| 剑指 Offer 13 | [机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | -| 剑指 Offer 14- I | [剪绳子](https://leetcode.cn/problems/jian-sheng-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2014-%20I.%20%E5%89%AA%E7%BB%B3%E5%AD%90.md) | 数学、动态规划 | 中等 | -| 剑指 Offer 15 | [二进制中1的个数](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2015.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算 | 简单 | -| 剑指 Offer 16 | [数值的整数次方](https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2016.%20%E6%95%B0%E5%80%BC%E7%9A%84%E6%95%B4%E6%95%B0%E6%AC%A1%E6%96%B9.md) | 递归、数学 | 中等 | -| 剑指 Offer 17 | [打印从1到最大的n位数](https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2017.%20%E6%89%93%E5%8D%B0%E4%BB%8E1%E5%88%B0%E6%9C%80%E5%A4%A7%E7%9A%84n%E4%BD%8D%E6%95%B0.md) | 数组、数学 | 简单 | -| 剑指 Offer 18 | [删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2018.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9.md) | 链表 | 简单 | -| 剑指 Offer 21 | [调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2021.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md) | 数组、双指针、排序 | 简单 | -| 剑指 Offer 22 | [链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 剑指 Offer 24 | [反转链表](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2024.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 剑指 Offer 25 | [合并两个排序的链表](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2025.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 剑指 Offer 26 | [树的子结构](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2026.%20%E6%A0%91%E7%9A%84%E5%AD%90%E7%BB%93%E6%9E%84.md) | 树、深度优先搜索、二叉树 | 中等 | -| 剑指 Offer 27 | [二叉树的镜像](https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2027.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 28 | [对称的二叉树](https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2028.%20%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 29 | [顺时针打印矩阵](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2029.%20%E9%A1%BA%E6%97%B6%E9%92%88%E6%89%93%E5%8D%B0%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 简单 | -| 剑指 Offer 30 | [包含min函数的栈](https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2030.%20%E5%8C%85%E5%90%ABmin%E5%87%BD%E6%95%B0%E7%9A%84%E6%A0%88.md) | 栈、设计 | 简单 | -| 剑指 Offer 31 | [栈的压入、弹出序列](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2031.%20%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5%E3%80%81%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97.md) | 栈、数组、模拟 | 中等 | -| 剑指 Offer 32 - I | [从上到下打印二叉树](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20I.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、广度优先搜索、二叉树 | 中等 | -| 剑指 Offer 32 - II | [从上到下打印二叉树 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20II.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20II.md) | 树、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 32 - III | [从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md) | 树、广度优先搜索、二叉树 | 中等 | -| 剑指 Offer 33 | [二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2033.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 栈、树、二叉搜索树、递归、二叉树、单调栈 | 中等 | -| 剑指 Offer 34 | [二叉树中和为某一值的路径](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2034.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 剑指 Offer 35 | [复杂链表的复制](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2035.%20%E5%A4%8D%E6%9D%82%E9%93%BE%E8%A1%A8%E7%9A%84%E5%A4%8D%E5%88%B6.md) | 哈希表、链表 | 中等 | -| 剑指 Offer 36 | [二叉搜索树与双向链表](https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2036.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%8E%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | -| 剑指 Offer 37 | [序列化二叉树](https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2037.%20%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 剑指 Offer 38 | [字符串的排列](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2038.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 字符串、回溯 | 中等 | -| 剑指 Offer 39 | [数组中出现次数超过一半的数字](https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2039.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%87%E4%B8%80%E5%8D%8A%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 剑指 Offer 40 | [最小的k个数](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2040.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | -| 剑指 Offer 41 | [数据流中的中位数](https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2041.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | -| 剑指 Offer 42 | [连续子数组的最大和](https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2042.%20%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 数组、分治、动态规划 | 简单 | -| 剑指 Offer 44 | [数字序列中某一位的数字](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2044.%20%E6%95%B0%E5%AD%97%E5%BA%8F%E5%88%97%E4%B8%AD%E6%9F%90%E4%B8%80%E4%BD%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | -| 剑指 Offer 46 | [把数字翻译成字符串](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2046.%20%E6%8A%8A%E6%95%B0%E5%AD%97%E7%BF%BB%E8%AF%91%E6%88%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 剑指 Offer 47 | [礼物的最大价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2047.%20%E7%A4%BC%E7%89%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC.md) | 数组、动态规划、矩阵 | 中等 | -| 剑指 Offer 48 | [最长不含重复字符的子字符串](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2048.%20%E6%9C%80%E9%95%BF%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 剑指 Offer 49 | [丑数](https://leetcode.cn/problems/chou-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2049.%20%E4%B8%91%E6%95%B0.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | -| 剑指 Offer 50 | [第一个只出现一次的字符](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2050.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E5%AD%97%E7%AC%A6.md) | 队列、哈希表、字符串、计数 | 简单 | -| 剑指 Offer 51 | [数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2051.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 剑指 Offer 52 | [两个链表的第一个公共节点](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2052.%20%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%AC%E5%85%B1%E8%8A%82%E7%82%B9.md) | 哈希表、链表、双指针 | 简单 | -| 剑指 Offer 53 - I | [在排序数组中查找数字 I](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2053%20-%20I.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%95%B0%E5%AD%97%20I.md) | 数组、二分查找 | 简单 | -| 剑指 Offer 53 - II | [0~n-1中缺失的数字](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2053%20-%20II.%200%EF%BD%9En-1%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找 | 简单 | -| 剑指 Offer 54 | [二叉搜索树的第k大节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2054.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 剑指 Offer 55 - I | [二叉树的深度](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2055%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 55 - II | [平衡二叉树](https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2055%20-%20II.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | -| 剑指 Offer 56 - I | [数组中数字出现的次数](https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2056%20-%20I.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md) | 位运算、数组 | 中等 | -| 剑指 Offer 57 | [和为s的两个数字](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97.md) | 数组、双指针、二分查找 | 简单 | -| 剑指 Offer 57 - II | [和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057%20-%20II.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%AD%A3%E6%95%B0%E5%BA%8F%E5%88%97.md) | 数学、双指针、枚举 | 简单 | -| 剑指 Offer 58 - I | [翻转单词顺序](https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2058%20-%20I.%20%E7%BF%BB%E8%BD%AC%E5%8D%95%E8%AF%8D%E9%A1%BA%E5%BA%8F.md) | 双指针、字符串 | 简单 | -| 剑指 Offer 58 - II | [左旋转字符串](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2058%20-%20II.%20%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 数学、双指针、字符串 | 简单 | -| 剑指 Offer 59 - I | [滑动窗口的最大值](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2059%20-%20I.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 剑指 Offer 59 - II | [队列的最大值](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2059%20-%20II.%20%E9%98%9F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 设计、队列、单调队列 | 中等 | -| 剑指 Offer 61 | [扑克牌中的顺子](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2061.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md) | 数组、排序 | 简单 | -| 剑指 Offer 62 | [圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 递归、数学 | 简单 | -| 剑指 Offer 63 | [股票的最大利润](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2063.%20%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E5%A4%A7%E5%88%A9%E6%B6%A6.md) | 数组、动态规划 | 中等 | -| 剑指 Offer 64 | [求1+2+…+n](https://leetcode.cn/problems/qiu-12n-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2064.%20%E6%B1%821%2B2%2B%E2%80%A6%2Bn.md) | 位运算、递归、脑筋急转弯 | 中等 | -| 剑指 Offer 65 | [不用加减乘除做加法](https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2065.%20%E4%B8%8D%E7%94%A8%E5%8A%A0%E5%87%8F%E4%B9%98%E9%99%A4%E5%81%9A%E5%8A%A0%E6%B3%95.md) | 位运算、数学 | 简单 | -| 剑指 Offer 66 | [构建乘积数组](https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2066.%20%E6%9E%84%E5%BB%BA%E4%B9%98%E7%A7%AF%E6%95%B0%E7%BB%84.md) | 数组、前缀和 | 中等 | -| 剑指 Offer 67 | [把字符串转换成整数](https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2067.%20%E6%8A%8A%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%88%90%E6%95%B4%E6%95%B0.md) | 字符串 | 中等 | -| 剑指 Offer 68 - I | [二叉搜索树的最近公共祖先](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2068%20-%20I.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 剑指 Offer 68 - II | [二叉树的最近公共祖先](https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2068%20-%20II.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 简单 | -| 剑指 Offer II 001 | [整数除法](https://leetcode.cn/problems/xoh6Oh/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20001.%20%E6%95%B4%E6%95%B0%E9%99%A4%E6%B3%95.md) | 位运算、数学 | 简单 | -| 剑指 Offer II 002 | [二进制加法](https://leetcode.cn/problems/JFETK5/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20002.%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8A%A0%E6%B3%95.md) | 位运算、数学、字符串、模拟 | 简单 | -| 剑指 Offer II 003 | [前 n 个数字二进制中 1 的个数](https://leetcode.cn/problems/w3tCBm/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20003.%20%E5%89%8D%20n%20%E4%B8%AA%E6%95%B0%E5%AD%97%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%B8%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 剑指 Offer II 004 | [只出现一次的数字](https://leetcode.cn/problems/WGki4K/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20004.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 中等 | -| 剑指 Offer II 005 | [单词长度的最大乘积](https://leetcode.cn/problems/aseY1I/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20005.%20%E5%8D%95%E8%AF%8D%E9%95%BF%E5%BA%A6%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF.md) | 位运算、数组、字符串 | 中等 | -| 剑指 Offer II 006 | [排序数组中两个数字之和](https://leetcode.cn/problems/kLl5u1/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20006.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找 | 简单 | -| 剑指 Offer II 007 | [数组中和为 0 的三个数](https://leetcode.cn/problems/1fGaJU/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20007.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%92%8C%E4%B8%BA%200%20%E7%9A%84%E4%B8%89%E4%B8%AA%E6%95%B0.md) | 数组、双指针、排序 | 中等 | -| 剑指 Offer II 008 | [和大于等于 target 的最短子数组](https://leetcode.cn/problems/2VG8Kg/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20008.%20%E5%92%8C%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%20target%20%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 剑指 Offer II 009 | [乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20009.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、滑动窗口 | 中等 | -| 剑指 Offer II 010 | [和为 k 的子数组](https://leetcode.cn/problems/QTMn0o/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20010.%20%E5%92%8C%E4%B8%BA%20k%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | -| 剑指 Offer II 011 | [0 和 1 个数相同的子数组](https://leetcode.cn/problems/A1NYOS/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20011.%200%20%E5%92%8C%201%20%E4%B8%AA%E6%95%B0%E7%9B%B8%E5%90%8C%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | -| 剑指 Offer II 012 | [左右两边子数组的和相等](https://leetcode.cn/problems/tvdfij/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20012.%20%E5%B7%A6%E5%8F%B3%E4%B8%A4%E8%BE%B9%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%92%8C%E7%9B%B8%E7%AD%89.md) | 数组、前缀和 | 简单 | -| 剑指 Offer II 013 | [二维子矩阵的和](https://leetcode.cn/problems/O4NDxx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20013.%20%E4%BA%8C%E7%BB%B4%E5%AD%90%E7%9F%A9%E9%98%B5%E7%9A%84%E5%92%8C.md) | 设计、数组、矩阵、前缀和 | 中等 | -| 剑指 Offer II 016 | [不含重复字符的最长子字符串](https://leetcode.cn/problems/wtcaE1/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20016.%20%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 剑指 Offer II 017 | [含有所有字符的最短字符串](https://leetcode.cn/problems/M1oyTv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20017.%20%E5%90%AB%E6%9C%89%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E7%9F%AD%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 剑指 Offer II 018 | [有效的回文](https://leetcode.cn/problems/XltzEq/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20018.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%9B%9E%E6%96%87.md) | 双指针、字符串 | 简单 | -| 剑指 Offer II 019 | [最多删除一个字符得到回文](https://leetcode.cn/problems/RQku0D/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20019.%20%E6%9C%80%E5%A4%9A%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E5%BE%97%E5%88%B0%E5%9B%9E%E6%96%87.md) | 贪心、双指针、字符串 | 简单 | -| 剑指 Offer II 020 | [回文子字符串的个数](https://leetcode.cn/problems/a7VOhD/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20020.%20%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 字符串、动态规划 | 中等 | -| 剑指 Offer II 021 | [删除链表的倒数第 n 个结点](https://leetcode.cn/problems/SLwz0R/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20021.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20n%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 剑指 Offer II 022 | [链表中环的入口节点](https://leetcode.cn/problems/c32eOV/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%8E%AF%E7%9A%84%E5%85%A5%E5%8F%A3%E8%8A%82%E7%82%B9.md) | 哈希表、链表、双指针 | 中等 | -| 剑指 Offer II 023 | [两个链表的第一个重合节点](https://leetcode.cn/problems/3u1WK4/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20023.%20%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%87%8D%E5%90%88%E8%8A%82%E7%82%B9.md) | 哈希表、链表、双指针 | 简单 | -| 剑指 Offer II 024 | [反转链表](https://leetcode.cn/problems/UHnkqh/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20024.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 剑指 Offer II 025 | [链表中的两数相加](https://leetcode.cn/problems/lMSNwu/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20025.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 栈、链表、数学 | 中等 | -| 剑指 Offer II 026 | [重排链表](https://leetcode.cn/problems/LGjMqU/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20026.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 剑指 Offer II 027 | [回文链表](https://leetcode.cn/problems/aMhZSa/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20027.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 剑指 Offer II 028 | [展平多级双向链表](https://leetcode.cn/problems/Qv1Da2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20028.%20%E5%B1%95%E5%B9%B3%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 深度优先搜索、链表、双向链表 | 中等 | -| 剑指 Offer II 029 | [排序的循环链表](https://leetcode.cn/problems/4ueAj6/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20029.%20%E6%8E%92%E5%BA%8F%E7%9A%84%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.md) | 链表 | 中等 | -| 剑指 Offer II 030 | [插入、删除和随机访问都是 O(1) 的容器](https://leetcode.cn/problems/FortPu/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20030.%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E9%83%BD%E6%98%AF%20O%281%29%20%E7%9A%84%E5%AE%B9%E5%99%A8.md) | 设计、数组、哈希表、数学、随机化 | 中等 | -| 剑指 Offer II 031 | [最近最少使用缓存](https://leetcode.cn/problems/OrIXps/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20031.%20%E6%9C%80%E8%BF%91%E6%9C%80%E5%B0%91%E4%BD%BF%E7%94%A8%E7%BC%93%E5%AD%98.md) | 设计、哈希表、链表、双向链表 | 中等 | -| 剑指 Offer II 032 | [有效的变位词](https://leetcode.cn/problems/dKk3P7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20032.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%8F%98%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、排序 | 简单 | -| 剑指 Offer II 033 | [变位词组](https://leetcode.cn/problems/sfvd7V/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20033.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 剑指 Offer II 034 | [外星语言是否排序](https://leetcode.cn/problems/lwyVBB/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20034.%20%E5%A4%96%E6%98%9F%E8%AF%AD%E8%A8%80%E6%98%AF%E5%90%A6%E6%8E%92%E5%BA%8F.md) | 数组、哈希表、字符串 | 简单 | -| 剑指 Offer II 035 | [最小时间差](https://leetcode.cn/problems/569nqc/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20035.%20%E6%9C%80%E5%B0%8F%E6%97%B6%E9%97%B4%E5%B7%AE.md) | 数组、数学、字符串、排序 | 中等 | -| 剑指 Offer II 036 | [后缀表达式](https://leetcode.cn/problems/8Zf90G/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20036.%20%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.md) | 栈、数组、数学 | 中等 | -| 剑指 Offer II 037 | [小行星碰撞](https://leetcode.cn/problems/XagZNi/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20037.%20%E5%B0%8F%E8%A1%8C%E6%98%9F%E7%A2%B0%E6%92%9E.md) | 栈、数组、模拟 | 中等 | -| 剑指 Offer II 038 | [每日温度](https://leetcode.cn/problems/iIQa4I/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20038.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 剑指 Offer II 039 | [直方图最大矩形面积](https://leetcode.cn/problems/0ynMMM/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20039.%20%E7%9B%B4%E6%96%B9%E5%9B%BE%E6%9C%80%E5%A4%A7%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF.md) | 栈、数组、单调栈 | 困难 | -| 剑指 Offer II 041 | [滑动窗口的平均值](https://leetcode.cn/problems/qIsx9U/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20041.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC.md) | 设计、队列、数组、数据流 | 简单 | -| 剑指 Offer II 042 | [最近请求次数](https://leetcode.cn/problems/H8086Q/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20042.%20%E6%9C%80%E8%BF%91%E8%AF%B7%E6%B1%82%E6%AC%A1%E6%95%B0.md) | 设计、队列、数据流 | 简单 | -| 剑指 Offer II 043 | [往完全二叉树添加节点](https://leetcode.cn/problems/NaqhDT/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20043.%20%E5%BE%80%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9.md) | 树、广度优先搜索、设计、二叉树 | 中等 | -| 剑指 Offer II 044 | [二叉树每层的最大值](https://leetcode.cn/problems/hPov7L/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20044.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%AF%8F%E5%B1%82%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 045 | [二叉树最底层最左边的值](https://leetcode.cn/problems/LwUNpT/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20045.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%BA%95%E5%B1%82%E6%9C%80%E5%B7%A6%E8%BE%B9%E7%9A%84%E5%80%BC.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 046 | [二叉树的右侧视图](https://leetcode.cn/problems/WNC0Lk/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20046.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E4%BE%A7%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 047 | [二叉树剪枝](https://leetcode.cn/problems/pOCWxh/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20047.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D.md) | 树、深度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 048 | [序列化与反序列化二叉树](https://leetcode.cn/problems/h54YBf/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20048.%20%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 剑指 Offer II 049 | [从根节点到叶节点的路径数字之和](https://leetcode.cn/problems/3Etpl5/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20049.%20%E4%BB%8E%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 050 | [向下的路径节点之和](https://leetcode.cn/problems/6eUYwP/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20050.%20%E5%90%91%E4%B8%8B%E7%9A%84%E8%B7%AF%E5%BE%84%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 剑指 Offer II 051 | [节点之和最大的路径](https://leetcode.cn/problems/jC7MId/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20051.%20%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C%E6%9C%80%E5%A4%A7%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 剑指 Offer II 052 | [展平二叉搜索树](https://leetcode.cn/problems/NYBBNL/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20052.%20%E5%B1%95%E5%B9%B3%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 栈、树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 剑指 Offer II 053 | [二叉搜索树中的中序后继](https://leetcode.cn/problems/P5rCT8/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20053.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E4%B8%AD%E5%BA%8F%E5%90%8E%E7%BB%A7.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 剑指 Offer II 054 | [所有大于等于节点的值之和](https://leetcode.cn/problems/w6cpku/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20054.%20%E6%89%80%E6%9C%89%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 剑指 Offer II 055 | [二叉搜索树迭代器](https://leetcode.cn/problems/kTOapQ/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20055.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | -| 剑指 Offer II 056 | [二叉搜索树中两个节点之和](https://leetcode.cn/problems/opLdQZ/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20056.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E4%B8%A4%E4%B8%AA%E8%8A%82%E7%82%B9%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 | 简单 | -| 剑指 Offer II 057 | [值和下标之差都在给定的范围内](https://leetcode.cn/problems/7WqeDu/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20057.%20%E5%80%BC%E5%92%8C%E4%B8%8B%E6%A0%87%E4%B9%8B%E5%B7%AE%E9%83%BD%E5%9C%A8%E7%BB%99%E5%AE%9A%E7%9A%84%E8%8C%83%E5%9B%B4%E5%86%85.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 中等 | -| 剑指 Offer II 059 | [数据流的第 K 大数值](https://leetcode.cn/problems/jBjn9C/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20059.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E6%95%B0%E5%80%BC.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 剑指 Offer II 060 | [出现频率最高的 k 个数字](https://leetcode.cn/problems/g5c51o/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20060.%20%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%9C%80%E9%AB%98%E7%9A%84%20k%20%E4%B8%AA%E6%95%B0%E5%AD%97.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | -| 剑指 Offer II 062 | [实现前缀树](https://leetcode.cn/problems/QC3q1f/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20062.%20%E5%AE%9E%E7%8E%B0%E5%89%8D%E7%BC%80%E6%A0%91.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 剑指 Offer II 063 | [替换单词](https://leetcode.cn/problems/UhWRSj/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20063.%20%E6%9B%BF%E6%8D%A2%E5%8D%95%E8%AF%8D.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 剑指 Offer II 064 | [神奇的字典](https://leetcode.cn/problems/US1pGT/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20064.%20%E7%A5%9E%E5%A5%87%E7%9A%84%E5%AD%97%E5%85%B8.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 剑指 Offer II 065 | [最短的单词编码](https://leetcode.cn/problems/iSwD2y/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20065.%20%E6%9C%80%E7%9F%AD%E7%9A%84%E5%8D%95%E8%AF%8D%E7%BC%96%E7%A0%81.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 剑指 Offer II 066 | [单词之和](https://leetcode.cn/problems/z1R5dt/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20066.%20%E5%8D%95%E8%AF%8D%E4%B9%8B%E5%92%8C.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 剑指 Offer II 067 | [最大的异或](https://leetcode.cn/problems/ms70jA/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20067.%20%E6%9C%80%E5%A4%A7%E7%9A%84%E5%BC%82%E6%88%96.md) | 位运算、字典树、数组、哈希表 | 中等 | -| 剑指 Offer II 068 | [查找插入位置](https://leetcode.cn/problems/N6YdxV/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20068.%20%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 简单 | -| 剑指 Offer II 072 | [求平方根](https://leetcode.cn/problems/jJ0w9p/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20072.%20%E6%B1%82%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | -| 剑指 Offer II 073 | [狒狒吃香蕉](https://leetcode.cn/problems/nZZqjQ/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20073.%20%E7%8B%92%E7%8B%92%E5%90%83%E9%A6%99%E8%95%89.md) | 数组、二分查找 | 中等 | -| 剑指 Offer II 074 | [合并区间](https://leetcode.cn/problems/SsGoHC/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20074.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 剑指 Offer II 075 | [数组相对排序](https://leetcode.cn/problems/0H97ZC/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20075.%20%E6%95%B0%E7%BB%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md) | 数组、哈希表、计数排序、排序 | 简单 | -| 剑指 Offer II 076 | [数组中的第 k 大的数字](https://leetcode.cn/problems/xx4gT2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20076.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%AC%20k%20%E5%A4%A7%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 剑指 Offer II 077 | [链表排序](https://leetcode.cn/problems/7WHec2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20077.%20%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 剑指 Offer II 078 | [合并排序链表](https://leetcode.cn/problems/vvXgSW/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20078.%20%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 剑指 Offer II 079 | [所有子集](https://leetcode.cn/problems/TVdhkn/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20079.%20%E6%89%80%E6%9C%89%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 剑指 Offer II 080 | [含有 k 个元素的组合](https://leetcode.cn/problems/uUsW3B/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20080.%20%E5%90%AB%E6%9C%89%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E7%BB%84%E5%90%88.md) | 数组、回溯 | 中等 | -| 剑指 Offer II 081 | [允许重复选择元素的组合](https://leetcode.cn/problems/Ygoe9J/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20081.%20%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%E7%9A%84%E7%BB%84%E5%90%88.md) | 数组、回溯 | 中等 | -| 剑指 Offer II 082 | [含有重复元素集合的组合](https://leetcode.cn/problems/4sjJUc/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20082.%20%E5%90%AB%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E7%BB%84%E5%90%88.md) | 数组、回溯 | 中等 | -| 剑指 Offer II 083 | [没有重复元素集合的全排列](https://leetcode.cn/problems/VvJkup/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20083.%20%E6%B2%A1%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 剑指 Offer II 084 | [含有重复元素集合的全排列](https://leetcode.cn/problems/7p8L0Z/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20084.%20%E5%90%AB%E6%9C%89%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E9%9B%86%E5%90%88%E7%9A%84%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 剑指 Offer II 085 | [生成匹配的括号](https://leetcode.cn/problems/IDBivT/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20085.%20%E7%94%9F%E6%88%90%E5%8C%B9%E9%85%8D%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 字符串、动态规划、回溯 | 中等 | -| 剑指 Offer II 086 | [分割回文子字符串](https://leetcode.cn/problems/M99OJA/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20086.%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 剑指 Offer II 087 | [复原 IP](https://leetcode.cn/problems/0on3uN/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20087.%20%E5%A4%8D%E5%8E%9F%20IP.md) | 字符串、回溯 | 中等 | -| 剑指 Offer II 088 | [爬楼梯的最少成本](https://leetcode.cn/problems/GzCJIP/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20088.%20%E7%88%AC%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E5%B0%91%E6%88%90%E6%9C%AC.md) | 数组、动态规划 | 简单 | -| 剑指 Offer II 089 | [房屋偷盗](https://leetcode.cn/problems/Gu0c2T/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20089.%20%E6%88%BF%E5%B1%8B%E5%81%B7%E7%9B%97.md) | 数组、动态规划 | 中等 | -| 剑指 Offer II 090 | [环形房屋偷盗](https://leetcode.cn/problems/PzWKhm/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20090.%20%E7%8E%AF%E5%BD%A2%E6%88%BF%E5%B1%8B%E5%81%B7%E7%9B%97.md) | 数组、动态规划 | 中等 | -| 剑指 Offer II 093 | [最长斐波那契数列](https://leetcode.cn/problems/Q91FMA/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20093.%20%E6%9C%80%E9%95%BF%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md) | 数组、哈希表、动态规划 | 中等 | -| 剑指 Offer II 095 | [最长公共子序列](https://leetcode.cn/problems/qJnOS7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20095.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 剑指 Offer II 097 | [子序列的数目](https://leetcode.cn/problems/21dk04/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20097.%20%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 字符串、动态规划 | 困难 | -| 剑指 Offer II 098 | [路径的数目](https://leetcode.cn/problems/2AoeFn/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20098.%20%E8%B7%AF%E5%BE%84%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数学、动态规划、组合数学 | 中等 | -| 剑指 Offer II 101 | [分割等和子集](https://leetcode.cn/problems/NUPfPr/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20101.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md) | 数学、字符串、模拟 | 简单 | -| 剑指 Offer II 102 | [加减的目标值](https://leetcode.cn/problems/YaVDxD/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20102.%20%E5%8A%A0%E5%87%8F%E7%9A%84%E7%9B%AE%E6%A0%87%E5%80%BC.md) | 数组、动态规划、回溯 | 中等 | -| 剑指 Offer II 103 | [最少的硬币数目](https://leetcode.cn/problems/gaM7Ch/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20103.%20%E6%9C%80%E5%B0%91%E7%9A%84%E7%A1%AC%E5%B8%81%E6%95%B0%E7%9B%AE.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 剑指 Offer II 104 | [排列的数目](https://leetcode.cn/problems/D0F0SV/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20104.%20%E6%8E%92%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数组、动态规划 | 中等 | -| 剑指 Offer II 105 | [岛屿的最大面积](https://leetcode.cn/problems/ZL6zAn/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20105.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 剑指 Offer II 106 | [二分图](https://leetcode.cn/problems/vEAB3K/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20106.%20%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 剑指 Offer II 107 | [矩阵中的距离](https://leetcode.cn/problems/2bCMpM/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20107.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%9D%E7%A6%BB.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | -| 剑指 Offer II 108 | [单词演变](https://leetcode.cn/problems/om3reC/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20108.%20%E5%8D%95%E8%AF%8D%E6%BC%94%E5%8F%98.md) | 广度优先搜索、哈希表、字符串 | 困难 | -| 剑指 Offer II 109 | [开密码锁](https://leetcode.cn/problems/zlDJc7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20109.%20%E5%BC%80%E5%AF%86%E7%A0%81%E9%94%81.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | -| 剑指 Offer II 111 | [计算除法](https://leetcode.cn/problems/vlzXQL/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20111.%20%E8%AE%A1%E7%AE%97%E9%99%A4%E6%B3%95.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 | -| 剑指 Offer II 112 | [最长递增路径](https://leetcode.cn/problems/fpTFWP/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20112.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | -| 剑指 Offer II 113 | [课程顺序](https://leetcode.cn/problems/QA2IGt/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20113.%20%E8%AF%BE%E7%A8%8B%E9%A1%BA%E5%BA%8F.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 剑指 Offer II 116 | [省份数量](https://leetcode.cn/problems/bLyHh0/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20116.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 剑指 Offer II 118 | [多余的边](https://leetcode.cn/problems/7LpjUW/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20118.%20%E5%A4%9A%E4%BD%99%E7%9A%84%E8%BE%B9.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 剑指 Offer II 119 | [最长连续序列](https://leetcode.cn/problems/WhsWhI/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%20II%20119.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 面试题 01.07 | [旋转矩阵](https://leetcode.cn/problems/rotate-matrix-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.07.%20%E6%97%8B%E8%BD%AC%E7%9F%A9%E9%98%B5.md) | 数组、数学、矩阵 | 中等 | -| 面试题 01.08 | [零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2001.08.%20%E9%9B%B6%E7%9F%A9%E9%98%B5.md) | 数组、哈希表、矩阵 | 中等 | -| 面试题 02.02 | [返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.02.%20%E8%BF%94%E5%9B%9E%E5%80%92%E6%95%B0%E7%AC%AC%20k%20%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 面试题 02.05 | [链表求和](https://leetcode.cn/problems/sum-lists-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.05.%20%E9%93%BE%E8%A1%A8%E6%B1%82%E5%92%8C.md) | 递归、链表、数学 | 中等 | -| 面试题 02.06 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.06.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 面试题 02.07 | [链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.07.%20%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.md) | 哈希表、链表、双指针 | 简单 | -| 面试题 02.08 | [环路检测](https://leetcode.cn/problems/linked-list-cycle-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.08.%20%E7%8E%AF%E8%B7%AF%E6%A3%80%E6%B5%8B.md) | 哈希表、链表、双指针 | 中等 | -| 面试题 03.02 | [栈的最小值](https://leetcode.cn/problems/min-stack-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.02.%20%E6%A0%88%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 栈、设计 | 简单 | -| 面试题 03.04 | [化栈为队](https://leetcode.cn/problems/implement-queue-using-stacks-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2003.04.%20%E5%8C%96%E6%A0%88%E4%B8%BA%E9%98%9F.md) | 栈、设计、队列 | 简单 | -| 面试题 04.02 | [最小高度树](https://leetcode.cn/problems/minimum-height-tree-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.02.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | -| 面试题 04.05 | [合法二叉搜索树](https://leetcode.cn/problems/legal-binary-search-tree-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.05.%20%E5%90%88%E6%B3%95%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 面试题 04.06 | [后继者](https://leetcode.cn/problems/successor-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.06.%20%E5%90%8E%E7%BB%A7%E8%80%85.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 面试题 04.08 | [首个共同祖先](https://leetcode.cn/problems/first-common-ancestor-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.08.%20%E9%A6%96%E4%B8%AA%E5%85%B1%E5%90%8C%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 面试题 04.12 | [求和路径](https://leetcode.cn/problems/paths-with-sum-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2004.12.%20%E6%B1%82%E5%92%8C%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 中等 | -| 面试题 08.04 | [幂集](https://leetcode.cn/problems/power-set-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.04.%20%E5%B9%82%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 面试题 08.07 | [无重复字符串的排列组合](https://leetcode.cn/problems/permutation-i-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.07.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88.md) | 字符串、回溯 | 中等 | -| 面试题 08.08 | [有重复字符串的排列组合](https://leetcode.cn/problems/permutation-ii-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.08.%20%E6%9C%89%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88.md) | 字符串、回溯 | 中等 | -| 面试题 08.09 | [括号](https://leetcode.cn/problems/bracket-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.09.%20%E6%8B%AC%E5%8F%B7.md) | 字符串、动态规划、回溯 | 中等 | -| 面试题 08.10 | [颜色填充](https://leetcode.cn/problems/color-fill-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.10.%20%E9%A2%9C%E8%89%B2%E5%A1%AB%E5%85%85.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | -| 面试题 08.12 | [八皇后](https://leetcode.cn/problems/eight-queens-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2008.12.%20%E5%85%AB%E7%9A%87%E5%90%8E.md) | 数组、回溯 | 困难 | -| 面试题 10.01 | [合并排序的数组](https://leetcode.cn/problems/sorted-merge-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.01.%20%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E7%9A%84%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 面试题 10.02 | [变位词组](https://leetcode.cn/problems/group-anagrams-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.02.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 面试题 10.09 | [排序矩阵查找](https://leetcode.cn/problems/sorted-matrix-search-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2010.09.%20%E6%8E%92%E5%BA%8F%E7%9F%A9%E9%98%B5%E6%9F%A5%E6%89%BE.md) | 数组、二分查找、分治、矩阵 | 中等 | -| 面试题 16.02 | [单词频率](https://leetcode.cn/problems/words-frequency-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.02.%20%E5%8D%95%E8%AF%8D%E9%A2%91%E7%8E%87.md) | 设计、字典树、数组、哈希表、字符串 | 中等 | -| 面试题 16.05 | [阶乘尾数](https://leetcode.cn/problems/factorial-zeros-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.05.%20%E9%98%B6%E4%B9%98%E5%B0%BE%E6%95%B0.md) | 数学 | 简单 | -| 面试题 16.26 | [计算器](https://leetcode.cn/problems/calculator-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2016.26.%20%E8%AE%A1%E7%AE%97%E5%99%A8.md) | 栈、数学、字符串 | 中等 | -| 面试题 17.06 | [2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.06.%202%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | -| 面试题 17.14 | [最小K个数](https://leetcode.cn/problems/smallest-k-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.14.%20%E6%9C%80%E5%B0%8FK%E4%B8%AA%E6%95%B0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 面试题 17.15 | [最长单词](https://leetcode.cn/problems/longest-word-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.15.%20%E6%9C%80%E9%95%BF%E5%8D%95%E8%AF%8D.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 面试题 17.17 | [多次搜索](https://leetcode.cn/problems/multi-search-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.17.%20%E5%A4%9A%E6%AC%A1%E6%90%9C%E7%B4%A2.md) | 字典树、数组、哈希表、字符串、字符串匹配、滑动窗口 | 中等 | \ No newline at end of file diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md deleted file mode 100644 index 1d49e6af..00000000 --- a/Contents/00.Introduction/05.Categories-List.md +++ /dev/null @@ -1,1098 +0,0 @@ -# LeetCode 题解(按分类排序,推荐刷题列表 ★★★) - -## 01. 数组 - -### 数组基础题目 - -#### 数组操作题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0189 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0189.%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针 | 中等 | -| 0066 | [加一](https://leetcode.cn/problems/plus-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0066.%20%E5%8A%A0%E4%B8%80.md) | 数组、数学 | 简单 | -| 0724 | [寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0724.%20%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E5%BF%83%E4%B8%8B%E6%A0%87.md) | 数组、前缀和 | 简单 | -| 0485 | [最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 数组 | 简单 | -| 0238 | [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0238.%20%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF.md) | 数组、前缀和 | 中等 | - -#### 二维数组题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0498 | [对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0498.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86.md) | 数组、矩阵、模拟 | 中等 | -| 0048 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、数学、矩阵 | 中等 | -| 0073 | [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0073.%20%E7%9F%A9%E9%98%B5%E7%BD%AE%E9%9B%B6.md) | 数组、哈希表、矩阵 | 中等 | -| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 中等 | -| 0059 | [螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0059.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20II.md) | 数组、矩阵、模拟 | 中等 | -| 0289 | [生命游戏](https://leetcode.cn/problems/game-of-life/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0289.%20%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F.md) | 数组、矩阵、模拟 | 中等 | - -### 排序算法题目 - -#### 冒泡排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | - -#### 选择排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | - -#### 插入排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | - -#### 希尔排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0506 | [相对名次](https://leetcode.cn/problems/relative-ranks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0506.%20%E7%9B%B8%E5%AF%B9%E5%90%8D%E6%AC%A1.md) | 数组、排序、堆(优先队列) | 简单 | - -#### 归并排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 剑指 Offer 51 | [数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2051.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | - -#### 快速排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | - -#### 堆排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 剑指 Offer 40 | [最小的k个数](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2040.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | - -#### 计数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 1122 | [数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1122.%20%E6%95%B0%E7%BB%84%E7%9A%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md) | 数组、哈希表、计数排序、排序 | 简单 | - -#### 桶排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | - -#### 基数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | -| 0561 | [数组拆分](https://leetcode.cn/problems/array-partition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md) | 贪心、数组、计数排序、排序 | 简单 | - -#### 其他排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0217 | [存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 数组、哈希表、排序 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0179 | [最大数](https://leetcode.cn/problems/largest-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md) | 贪心、数组、字符串、排序 | 中等 | -| 0384 | [打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0384.%20%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84.md) | 数组、数学、随机化 | 中等 | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | - -### 二分查找题目 - -#### 二分下标题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0704 | [二分查找](https://leetcode.cn/problems/binary-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md) | 数组、二分查找 | 简单 | -| 0374 | [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0374.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F.md) | 二分查找、交互 | 简单 | -| 0035 | [搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0035.%20%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 简单 | -| 0034 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 中等 | -| 0167 | [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、二分查找 | 中等 | -| 0153 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0154 | [寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0154.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%20II.md) | 数组、二分查找 | 困难 | -| 0033 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找 | 中等 | -| 0081 | [搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0081.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%20II.md) | 数组、二分查找 | 中等 | -| 0278 | [第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0278.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E7%9A%84%E7%89%88%E6%9C%AC.md) | 二分查找、交互 | 简单 | -| 0162 | [寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0852 | [山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0852.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E7%9A%84%E5%B3%B0%E9%A1%B6%E7%B4%A2%E5%BC%95.md) | 数组、二分查找 | 中等 | -| 1095 | [山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1095.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%80%BC.md) | 数组、二分查找、交互 | 困难 | -| 0744 | [寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0744.%20%E5%AF%BB%E6%89%BE%E6%AF%94%E7%9B%AE%E6%A0%87%E5%AD%97%E6%AF%8D%E5%A4%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E6%AF%8D.md) | 数组、二分查找 | 简单 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0074 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0074.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5.md) | 数组、二分查找、矩阵 | 中等 | -| 0240 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md) | 数组、二分查找、分治、矩阵 | 中等 | - -#### 二分答案题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0069 | [x 的平方根](https://leetcode.cn/problems/sqrtx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0367 | [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0367.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 数学、二分查找 | 简单 | -| 1300 | [转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1300.%20%E8%BD%AC%E5%8F%98%E6%95%B0%E7%BB%84%E5%90%8E%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、二分查找、排序 | 中等 | -| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | - -#### 复杂的二分查找问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0875 | [爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0875.%20%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82.md) | 数组、二分查找 | 中等 | -| 0410 | [分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0658 | [找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | -| 0270 | [最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0270.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%80%BC.md) | 树、深度优先搜索、二叉搜索树、二分查找、二叉树 | 简单 | -| 0702 | [搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0702.%20%E6%90%9C%E7%B4%A2%E9%95%BF%E5%BA%A6%E6%9C%AA%E7%9F%A5%E7%9A%84%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找、交互 | 中等 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0719 | [找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、二分查找、排序 | 困难 | -| 0259 | [较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 中等 | -| 1011 | [在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1011.%20%E5%9C%A8%20D%20%E5%A4%A9%E5%86%85%E9%80%81%E8%BE%BE%E5%8C%85%E8%A3%B9%E7%9A%84%E8%83%BD%E5%8A%9B.md) | 数组、二分查找 | 中等 | -| 1482 | [制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1482.%20%E5%88%B6%E4%BD%9C%20m%20%E6%9D%9F%E8%8A%B1%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md) | 数组、二分查找 | 中等 | - -### 双指针题目 - -#### 对撞指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0167 | [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、二分查找 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0345 | [反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0345.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D.md) | 双指针、字符串 | 简单 | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0011 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0011.%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8.md) | 贪心、数组、双指针 | 中等 | -| 0611 | [有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0611.%20%E6%9C%89%E6%95%88%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0016 | [最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0016.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0018 | [四数之和](https://leetcode.cn/problems/4sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0259 | [较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 中等 | -| 0658 | [找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | -| 1099 | [小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1099.%20%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 简单 | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | -| 0360 | [有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0360.%20%E6%9C%89%E5%BA%8F%E8%BD%AC%E5%8C%96%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针、排序 | 中等 | -| 0977 | [有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0977.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.md) | 数组、双指针、排序 | 简单 | -| 0881 | [救生艇](https://leetcode.cn/problems/boats-to-save-people/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0881.%20%E6%95%91%E7%94%9F%E8%89%87.md) | 贪心、数组、双指针、排序 | 中等 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0443 | [压缩字符串](https://leetcode.cn/problems/string-compression/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0443.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 中等 | - -#### 快慢指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0026 | [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0026.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 数组、双指针 | 简单 | -| 0080 | [删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0080.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9%20II.md) | 数组、双指针 | 中等 | -| 0027 | [移除元素](https://leetcode.cn/problems/remove-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0027.%20%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md) | 数组、双指针 | 简单 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 0845 | [数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0845.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E5%B1%B1%E8%84%89.md) | 数组、双指针、动态规划、枚举 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 0719 | [找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、二分查找、排序 | 困难 | -| 0334 | [递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0334.%20%E9%80%92%E5%A2%9E%E7%9A%84%E4%B8%89%E5%85%83%E5%AD%90%E5%BA%8F%E5%88%97.md) | 贪心、数组 | 中等 | -| 0978 | [最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0978.%20%E6%9C%80%E9%95%BF%E6%B9%8D%E6%B5%81%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 剑指 Offer 21 | [调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2021.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md) | 数组、双指针、排序 | 简单 | - -#### 分离双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0925 | [长按键入](https://leetcode.cn/problems/long-pressed-name/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0925.%20%E9%95%BF%E6%8C%89%E9%94%AE%E5%85%A5.md) | 双指针、字符串 | 简单 | -| 0844 | [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0844.%20%E6%AF%94%E8%BE%83%E5%90%AB%E9%80%80%E6%A0%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、双指针、字符串、模拟 | 简单 | -| 1229 | [安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1229.%20%E5%AE%89%E6%8E%92%E4%BC%9A%E8%AE%AE%E6%97%A5%E7%A8%8B.md) | 数组、双指针、排序 | 中等 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | - -### 滑动窗口题目 - -#### 固定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1343 | [大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1343.%20%E5%A4%A7%E5%B0%8F%E4%B8%BA%20K%20%E4%B8%94%E5%B9%B3%E5%9D%87%E5%80%BC%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E9%98%88%E5%80%BC%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md) | 数组、滑动窗口 | 中等 | -| 0643 | [子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0643.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E6%95%B0%20I.md) | 数组、滑动窗口 | 简单 | -| 1052 | [爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1052.%20%E7%88%B1%E7%94%9F%E6%B0%94%E7%9A%84%E4%B9%A6%E5%BA%97%E8%80%81%E6%9D%BF.md) | 数组、滑动窗口 | 中等 | -| 1423 | [可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1423.%20%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E6%95%B0.md) | 数组、前缀和、滑动窗口 | 中等 | -| 1456 | [定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1456.%20%E5%AE%9A%E9%95%BF%E5%AD%90%E4%B8%B2%E4%B8%AD%E5%85%83%E9%9F%B3%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md) | 字符串、滑动窗口 | 中等 | -| 0567 | [字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0567.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 哈希表、双指针、字符串、滑动窗口 | 中等 | -| 1100 | [长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1100.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1151 | [最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1151.%20%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0%E6%9D%A5%E7%BB%84%E5%90%88%E6%89%80%E6%9C%89%E7%9A%84%201.md) | 数组、滑动窗口 | 中等 | -| 1176 | [健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1176.%20%E5%81%A5%E8%BA%AB%E8%AE%A1%E5%88%92%E8%AF%84%E4%BC%B0.md) | 数组、滑动窗口 | 简单 | -| 0438 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | -| 0683 | [K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md) | 树状数组、数组、有序集合、滑动窗口 | 困难 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0480 | [滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | - -#### 不定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0674 | [最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0674.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.md) | 数组 | 简单 | -| 0485 | [最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 数组 | 简单 | -| 0487 | [最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0487.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20II.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0076 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0862 | [和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 1004 | [最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1004.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 1658 | [将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1658.%20%E5%B0%86%20x%20%E5%87%8F%E5%88%B0%200%20%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md) | 数组、哈希表、二分查找、前缀和、滑动窗口 | 中等 | -| 0424 | [替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0424.%20%E6%9B%BF%E6%8D%A2%E5%90%8E%E7%9A%84%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1695 | [删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1695.%20%E5%88%A0%E9%99%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md) | 数组、哈希表、滑动窗口 | 中等 | -| 1208 | [尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1208.%20%E5%B0%BD%E5%8F%AF%E8%83%BD%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md) | 字符串、二分查找、前缀和、滑动窗口 | 中等 | -| 1493 | [删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1493.%20%E5%88%A0%E6%8E%89%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BB%A5%E5%90%8E%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0727 | [最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0727.%20%E6%9C%80%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划、滑动窗口 | 困难 | -| 0159 | [至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0159.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0340 | [至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0340.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0795 | [区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0795.%20%E5%8C%BA%E9%97%B4%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md) | 数组、双指针 | 中等 | -| 0992 | [K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0992.%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、计数、滑动窗口 | 困难 | -| 0713 | [乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0713.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、滑动窗口 | 中等 | -| 0904 | [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md) | 数组、哈希表、滑动窗口 | 中等 | -| 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0467 | [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | - -## 02. 链表 - -### 链表经典题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0707 | [设计链表](https://leetcode.cn/problems/design-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0707.%20%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.md) | 设计、链表 | 中等 | -| 0083 | [删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 链表 | 简单 | -| 0082 | [删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 链表、双指针 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0025 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 困难 | -| 0203 | [移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0203.%20%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.md) | 递归、链表 | 简单 | -| 0328 | [奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0328.%20%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md) | 链表 | 中等 | -| 0234 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0430 | [扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0430.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 深度优先搜索、链表、双向链表 | 中等 | -| 0138 | [复制带随机指针的链表](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0138.%20%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 哈希表、链表 | 中等 | -| 0061 | [旋转链表](https://leetcode.cn/problems/rotate-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0061.%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 链表、双指针 | 中等 | - -### 链表排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0148 | [排序链表](https://leetcode.cn/problems/sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0147 | [对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0147.%20%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md) | 链表、排序 | 中等 | - -### 链表双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0141 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0142 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md) | 哈希表、链表、双指针 | 中等 | -| 0160 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0019 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 0876 | [链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0876.%20%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 简单 | -| 剑指 Offer 22 | [链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 0143 | [重排链表](https://leetcode.cn/problems/reorder-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 0002 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 递归、链表、数学 | 中等 | -| 0445 | [两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0445.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 栈、链表、数学 | 中等 | - -## 03. 堆栈 - -### 堆栈基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1047 | [删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1047.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 栈、字符串 | 简单 | -| 0155 | [最小栈](https://leetcode.cn/problems/min-stack/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md) | 栈、设计 | 中等 | -| 0020 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 栈、字符串 | 简单 | -| 0227 | [基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md) | 栈、数学、字符串 | 中等 | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0150 | [逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0150.%20%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.md) | 栈、数组、数学 | 中等 | -| 0232 | [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 剑指 Offer 09 | [用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2009.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 0394 | [字符串解码](https://leetcode.cn/problems/decode-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md) | 栈、递归、字符串 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0946 | [验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0946.%20%E9%AA%8C%E8%AF%81%E6%A0%88%E5%BA%8F%E5%88%97.md) | 栈、数组、模拟 | 中等 | -| 剑指 Offer 06 | [从尾到头打印链表](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2006.%20%E4%BB%8E%E5%B0%BE%E5%88%B0%E5%A4%B4%E6%89%93%E5%8D%B0%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0071 | [简化路径](https://leetcode.cn/problems/simplify-path/) | | 栈、字符串 | 中等 | - -### 单调栈 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0496 | [下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0496.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20I.md) | 栈、数组、哈希表、单调栈 | 简单 | -| 0503 | [下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II.md) | 栈、数组、单调栈 | 中等 | -| 0901 | [股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0901.%20%E8%82%A1%E7%A5%A8%E4%BB%B7%E6%A0%BC%E8%B7%A8%E5%BA%A6.md) | 栈、设计、数据流、单调栈 | 中等 | -| 0084 | [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0084.%20%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.md) | 栈、数组、单调栈 | 困难 | -| 0316 | [去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0316.%20%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D.md) | 栈、贪心、字符串、单调栈 | 中等 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0085 | [最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | | 栈、数组、动态规划、矩阵、单调栈 | 困难 | - -## 04. 队列 - -### 队列基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0622 | [设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0622.%20%E8%AE%BE%E8%AE%A1%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97.md) | 设计、队列、数组、链表 | 中等 | -| 0346 | [数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0346.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%A7%BB%E5%8A%A8%E5%B9%B3%E5%9D%87%E5%80%BC.md) | 设计、队列、数组、数据流 | 简单 | -| 0225 | [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md) | 栈、设计、队列 | 简单 | - -### 优先队列题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0703 | [数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 0347 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0347.%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | -| 0451 | [根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | -| 0973 | [最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0973.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E5%8E%9F%E7%82%B9%E7%9A%84%20K%20%E4%B8%AA%E7%82%B9.md) | 几何、数组、数学、分治、快速选择、排序、堆(优先队列) | 中等 | -| 1296 | [划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1296.%20%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84%E4%B8%BA%E8%BF%9E%E7%BB%AD%E6%95%B0%E5%AD%97%E7%9A%84%E9%9B%86%E5%90%88.md) | 贪心、数组、哈希表、排序 | 中等 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0295 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0295.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | - -## 05. 哈希表 - -### 哈希表题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0705 | [设计哈希集合](https://leetcode.cn/problems/design-hashset/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0705.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E9%9B%86%E5%90%88.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0706 | [设计哈希映射](https://leetcode.cn/problems/design-hashmap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0706.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E6%98%A0%E5%B0%84.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0217 | [存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 数组、哈希表、排序 | 简单 | -| 0219 | [存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0219.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 数组、哈希表、滑动窗口 | 简单 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 1941 | [检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1941.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E7%9B%B8%E5%90%8C.md) | 哈希表、字符串、计数 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0383 | [赎金信](https://leetcode.cn/problems/ransom-note/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0383.%20%E8%B5%8E%E9%87%91%E4%BF%A1.md) | 哈希表、字符串、计数 | 简单 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0036 | [有效的数独](https://leetcode.cn/problems/valid-sudoku/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0036.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、矩阵 | 中等 | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0018 | [四数之和](https://leetcode.cn/problems/4sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0454 | [四数相加 II](https://leetcode.cn/problems/4sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0454.%20%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 数组、哈希表 | 中等 | -| 0041 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md) | 数组、哈希表 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 0202 | [快乐数](https://leetcode.cn/problems/happy-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0202.%20%E5%BF%AB%E4%B9%90%E6%95%B0.md) | 哈希表、数学、双指针 | 简单 | -| 0242 | [有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0242.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、排序 | 简单 | -| 0205 | [同构字符串](https://leetcode.cn/problems/isomorphic-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0205.%20%E5%90%8C%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串 | 简单 | -| 0442 | [数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/) | | 数组、哈希表 | 中等 | -| 剑指 Offer 61 | [扑克牌中的顺子](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2061.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md) | 数组、排序 | 简单 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 剑指 Offer 03 | [数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2003.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、哈希表、排序 | 简单 | -| 0451 | [根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | -| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 0599 | [两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0599.%20%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B4%A2%E5%BC%95%E6%80%BB%E5%92%8C.md) | 数组、哈希表、字符串 | 简单 | -| 0387 | [字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0387.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%94%AF%E4%B8%80%E5%AD%97%E7%AC%A6.md) | 队列、哈希表、字符串、计数 | 简单 | -| 0447 | [回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0447.%20%E5%9B%9E%E6%97%8B%E9%95%96%E7%9A%84%E6%95%B0%E9%87%8F.md) | 数组、哈希表、数学 | 中等 | -| 0149 | [直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0149.%20%E7%9B%B4%E7%BA%BF%E4%B8%8A%E6%9C%80%E5%A4%9A%E7%9A%84%E7%82%B9%E6%95%B0.md) | 几何、数组、哈希表、数学 | 困难 | -| 0359 | [日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0359.%20%E6%97%A5%E5%BF%97%E9%80%9F%E7%8E%87%E9%99%90%E5%88%B6%E5%99%A8.md) | 设计、哈希表 | 简单 | -| 0811 | [子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0811.%20%E5%AD%90%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E8%AE%A1%E6%95%B0.md) | 数组、哈希表、字符串、计数 | 中等 | - -## 06. 字符串 - -### 字符串基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0557 | [反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0557.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20III.md) | 双指针、字符串 | 简单 | -| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | -| 0151 | [反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 双指针、字符串 | 中等 | -| 0043 | [字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md) | 数学、字符串、模拟 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | - -### 单模式串匹配题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0028 | [找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0028.%20%E6%89%BE%E5%87%BA%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8C%B9%E9%85%8D%E9%A1%B9%E7%9A%84%E4%B8%8B%E6%A0%87.md) | 双指针、字符串、字符串匹配 | 中等 | -| 0459 | [重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0459.%20%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 0686 | [重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0686.%20%E9%87%8D%E5%A4%8D%E5%8F%A0%E5%8A%A0%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 字符串、字符串匹配 | 中等 | -| 1668 | [最大重复子字符串](https://leetcode.cn/problems/maximum-repeating-substring/) | | 字符串、字符串匹配 | 简单 | -| 0796 | [旋转字符串](https://leetcode.cn/problems/rotate-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0796.%20%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 1408 | [数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1408.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 数组、字符串、字符串匹配 | 简单 | -| 2156 | [查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2156.%20%E6%9F%A5%E6%89%BE%E7%BB%99%E5%AE%9A%E5%93%88%E5%B8%8C%E5%80%BC%E7%9A%84%E5%AD%90%E4%B8%B2.md) | 字符串、滑动窗口、哈希函数、滚动哈希 | 困难 | - -### 字典树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0208 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0208.%20%E5%AE%9E%E7%8E%B0%20Trie%20%28%E5%89%8D%E7%BC%80%E6%A0%91%29.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0677 | [键值映射](https://leetcode.cn/problems/map-sum-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0677.%20%E9%94%AE%E5%80%BC%E6%98%A0%E5%B0%84.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0648 | [单词替换](https://leetcode.cn/problems/replace-words/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0648.%20%E5%8D%95%E8%AF%8D%E6%9B%BF%E6%8D%A2.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 0642 | [设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0642.%20%E8%AE%BE%E8%AE%A1%E6%90%9C%E7%B4%A2%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E7%B3%BB%E7%BB%9F.md) | 设计、字典树、字符串、数据流 | 困难 | -| 0211 | [添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0211.%20%E6%B7%BB%E5%8A%A0%E4%B8%8E%E6%90%9C%E7%B4%A2%E5%8D%95%E8%AF%8D%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md) | 深度优先搜索、设计、字典树、字符串 | 中等 | -| 0421 | [数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0421.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md) | 位运算、字典树、数组、哈希表 | 中等 | -| 0212 | [单词搜索 II](https://leetcode.cn/problems/word-search-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0212.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%20II.md) | 字典树、数组、字符串、回溯、矩阵 | 困难 | -| 0425 | [单词方块](https://leetcode.cn/problems/word-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0425.%20%E5%8D%95%E8%AF%8D%E6%96%B9%E5%9D%97.md) | 字典树、数组、字符串、回溯 | 困难 | -| 0336 | [回文对](https://leetcode.cn/problems/palindrome-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0336.%20%E5%9B%9E%E6%96%87%E5%AF%B9.md) | 字典树、数组、哈希表、字符串 | 困难 | -| 1023 | [驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1023.%20%E9%A9%BC%E5%B3%B0%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 字典树、双指针、字符串、字符串匹配 | 中等 | -| 0676 | [实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0676.%20%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E9%AD%94%E6%B3%95%E5%AD%97%E5%85%B8.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0440 | [字典序的第K小数字](https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/) | | 字典树 | 困难 | - -## 07. 树 - -### 二叉树的遍历题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0102 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0103 | [二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0107 | [二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0107.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%20II.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0101 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0112 | [路径总和](https://leetcode.cn/problems/path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0113 | [路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 0236 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0116 | [填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0116.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0117 | [填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0117.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88%20II.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0297 | [二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0297.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 0114 | [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | | 栈、树、深度优先搜索、链表、二叉树 | 中等 | - -### 二叉树的还原题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0105 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0106 | [从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0106.%20%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0889 | [根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0889.%20%E6%A0%B9%E6%8D%AE%E5%89%8D%E5%BA%8F%E5%92%8C%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | - -### 二叉搜索树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0098 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0173 | [二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0173.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | -| 0700 | [二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0700.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%90%9C%E7%B4%A2.md) | 树、二叉搜索树、二叉树 | 简单 | -| 0701 | [二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0701.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%8F%92%E5%85%A5%E6%93%8D%E4%BD%9C.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0450 | [删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0450.%20%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0703 | [数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 剑指 Offer 54 | [二叉搜索树的第k大节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2054.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0230 | [二叉搜索树中第K小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0235 | [二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0235.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0426 | [将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0426.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%8E%92%E5%BA%8F%E7%9A%84%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | -| 0108 | [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0108.%20%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | -| 0110 | [平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | - -### 线段树题目 - -#### 单点更新题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0303 | [区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、前缀和 | 简单 | -| 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | - -#### 区间更新题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0370 | [区间加法](https://leetcode.cn/problems/range-addition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0370.%20%E5%8C%BA%E9%97%B4%E5%8A%A0%E6%B3%95.md) | 数组、前缀和 | 中等 | -| 1109 | [航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md) | 数组、前缀和 | 中等 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 1310 | [子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md) | 位运算、数组、前缀和 | 中等 | -| 1851 | [包含每个查询的最小区间](https://leetcode.cn/problems/minimum-interval-to-include-each-query/) | | 数组、二分查找、排序、扫描线、堆(优先队列) | 困难 | - -#### 区间合并题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0729 | [我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0729.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20I.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0731 | [我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0731.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20II.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0732 | [我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0732.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20III.md) | 设计、线段树、二分查找、有序集合 | 困难 | - -#### 扫描线问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | -| 0391 | [完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md) | 数组、扫描线 | 困难 | -| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | - -### 树状数组题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0303 | [区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、前缀和 | 简单 | -| 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | -| 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 1310 | [子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md) | 位运算、数组、前缀和 | 中等 | -| 1893 | [检查是否区域内所有整数都被覆盖](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) | | 数组、哈希表、前缀和 | 简单 | - -### 并查集题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0990 | [等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0990.%20%E7%AD%89%E5%BC%8F%E6%96%B9%E7%A8%8B%E7%9A%84%E5%8F%AF%E6%BB%A1%E8%B6%B3%E6%80%A7.md) | 并查集、图、数组、字符串 | 中等 | -| 0547 | [省份数量](https://leetcode.cn/problems/number-of-provinces/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0547.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0684 | [冗余连接](https://leetcode.cn/problems/redundant-connection/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 1319 | [连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1319.%20%E8%BF%9E%E9%80%9A%E7%BD%91%E7%BB%9C%E7%9A%84%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0765 | [情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0765.%20%E6%83%85%E4%BE%A3%E7%89%B5%E6%89%8B.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | -| 0399 | [除法求值](https://leetcode.cn/problems/evaluate-division/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0399.%20%E9%99%A4%E6%B3%95%E6%B1%82%E5%80%BC.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 | -| 0959 | [由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0959.%20%E7%94%B1%E6%96%9C%E6%9D%A0%E5%88%92%E5%88%86%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | -| 1202 | [交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1202.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md) | 深度优先搜索、广度优先搜索、并查集、哈希表、字符串 | 中等 | -| 0947 | [移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0947.%20%E7%A7%BB%E9%99%A4%E6%9C%80%E5%A4%9A%E7%9A%84%E5%90%8C%E8%A1%8C%E6%88%96%E5%90%8C%E5%88%97%E7%9F%B3%E5%A4%B4.md) | 深度优先搜索、并查集、图 | 中等 | -| 0803 | [打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0803.%20%E6%89%93%E7%A0%96%E5%9D%97.md) | 并查集、数组、矩阵 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | - -## 08. 图论 - -### 图的深度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0797 | [所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0695 | [岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0133 | [克隆图](https://leetcode.cn/problems/clone-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0589 | [N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0589.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0590 | [N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0590.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0841 | [钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0841.%20%E9%92%A5%E5%8C%99%E5%92%8C%E6%88%BF%E9%97%B4.md) | 深度优先搜索、广度优先搜索、图 | 中等 | -| 0129 | [求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0684 | [冗余连接](https://leetcode.cn/problems/redundant-connection/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0785 | [判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0886 | [可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0886.%20%E5%8F%AF%E8%83%BD%E7%9A%84%E4%BA%8C%E5%88%86%E6%B3%95.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0130 | [被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0130.%20%E8%A2%AB%E5%9B%B4%E7%BB%95%E7%9A%84%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0417 | [太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0417.%20%E5%A4%AA%E5%B9%B3%E6%B4%8B%E5%A4%A7%E8%A5%BF%E6%B4%8B%E6%B0%B4%E6%B5%81%E9%97%AE%E9%A2%98.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 1020 | [飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1020.%20%E9%A3%9E%E5%9C%B0%E7%9A%84%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1254 | [统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1254.%20%E7%BB%9F%E8%AE%A1%E5%B0%81%E9%97%AD%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1034 | [边界着色](https://leetcode.cn/problems/coloring-a-border/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1034.%20%E8%BE%B9%E7%95%8C%E7%9D%80%E8%89%B2.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 剑指 Offer 13 | [机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | -| 0529 | [扫雷游戏](https://leetcode.cn/problems/minesweeper/) | | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | - -### 图的广度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0797 | [所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | -| 0286 | [墙与门](https://leetcode.cn/problems/walls-and-gates/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0286.%20%E5%A2%99%E4%B8%8E%E9%97%A8.md) | 广度优先搜索、数组、矩阵 | 中等 | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0752 | [打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0752.%20%E6%89%93%E5%BC%80%E8%BD%AC%E7%9B%98%E9%94%81.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0133 | [克隆图](https://leetcode.cn/problems/clone-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 0733 | [图像渲染](https://leetcode.cn/problems/flood-fill/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0733.%20%E5%9B%BE%E5%83%8F%E6%B8%B2%E6%9F%93.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | -| 0542 | [01 矩阵](https://leetcode.cn/problems/01-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0542.%2001%20%E7%9F%A9%E9%98%B5.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 剑指 Offer 13 | [机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 32 - III | [从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md) | 树、广度优先搜索、二叉树 | 中等 | - -### 图的拓扑排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 1136 | [并行课程](https://leetcode.cn/problems/parallel-courses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1136.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B.md) | 图、拓扑排序 | 中等 | -| 2050 | [并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2050.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B%20III.md) | 图、拓扑排序、数组、动态规划 | 困难 | -| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | - -### 图的最小生成树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1584 | [连接所有点的最小费用](https://leetcode.cn/problems/min-cost-to-connect-all-points/) | | 并查集、图、数组、最小生成树 | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | - -### 单源最短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0407 | [接雨水 II](https://leetcode.cn/problems/trapping-rain-water-ii/) | | 广度优先搜索、数组、矩阵、堆(优先队列) | 困难 | -| 0743 | [网络延迟时间](https://leetcode.cn/problems/network-delay-time/) | | 深度优先搜索、广度优先搜索、图、最短路、堆(优先队列) | 中等 | -| 0787 | [K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops/) | | 深度优先搜索、广度优先搜索、图、动态规划、最短路、堆(优先队列) | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 1786 | [从第一个节点出发到最后一个节点的受限路径数](https://leetcode.cn/problems/number-of-restricted-paths-from-first-to-last-node/) | | 图、拓扑排序、动态规划、最短路、堆(优先队列) | 中等 | - -### 多源最短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0815 | [公交路线](https://leetcode.cn/problems/bus-routes/) | | 广度优先搜索、数组、哈希表 | 困难 | -| 1162 | [地图分析](https://leetcode.cn/problems/as-far-from-land-as-possible/) | | 广度优先搜索、数组、动态规划、矩阵 | 中等 | - -### 次短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 2045 | [到达目的地的第二短时间](https://leetcode.cn/problems/second-minimum-time-to-reach-destination/) | | 广度优先搜索、图、最短路 | 困难 | - -### 差分约束系统 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | -| 1109 | [航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md) | 数组、前缀和 | 中等 | - -### 二分图基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0785 | [判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | - -### 二分图最大匹配题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| LCP 04 | [覆盖](https://leetcode.cn/problems/broken-board-dominoes/) | | 位运算、图、数组、动态规划、状态压缩 | 困难 | -| 1947 | [最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1595 | [连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | - -## 09. 基础算法 - -### 枚举算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0204 | [计数质数](https://leetcode.cn/problems/count-primes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0204.%20%E8%AE%A1%E6%95%B0%E8%B4%A8%E6%95%B0.md) | 数组、数学、枚举、数论 | 中等 | -| 1925 | [统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1925.%20%E7%BB%9F%E8%AE%A1%E5%B9%B3%E6%96%B9%E5%92%8C%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数学、枚举 | 简单 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 1620 | [网络信号最好的坐标](https://leetcode.cn/problems/coordinate-with-maximum-network-quality/) | | 数组、枚举 | 中等 | -| 剑指 Offer 57 - II | [和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057%20-%20II.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%AD%A3%E6%95%B0%E5%BA%8F%E5%88%97.md) | 数学、双指针、枚举 | 简单 | -| 0800 | [相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0800.%20%E7%9B%B8%E4%BC%BC%20RGB%20%E9%A2%9C%E8%89%B2.md) | 数学、字符串、枚举 | 简单 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0560 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | - -### 递归算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0024 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 递归、链表 | 中等 | -| 0118 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md) | 数组、动态规划 | 简单 | -| 0119 | [杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md) | 数组、动态规划 | 简单 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0779 | [第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0779.%20%E7%AC%ACK%E4%B8%AA%E8%AF%AD%E6%B3%95%E7%AC%A6%E5%8F%B7.md) | 位运算、递归、数学 | 中等 | -| 0095 | [不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0095.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%20II.md) | 树、二叉搜索树、动态规划、回溯、二叉树 | 中等 | -| 剑指 Offer 62 | [圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 递归、数学 | 简单 | - -### 分治算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0241 | [为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0241.%20%E4%B8%BA%E8%BF%90%E7%AE%97%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%BE%E8%AE%A1%E4%BC%98%E5%85%88%E7%BA%A7.md) | 递归、记忆化搜索、数学、字符串、动态规划 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | -| 剑指 Offer 33 | [二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2033.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 栈、树、二叉搜索树、递归、二叉树、单调栈 | 中等 | - -### 回溯算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0046 | [全排列](https://leetcode.cn/problems/permutations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 0047 | [全排列 II](https://leetcode.cn/problems/permutations-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0047.%20%E5%85%A8%E6%8E%92%E5%88%97%20II.md) | 数组、回溯 | 中等 | -| 0037 | [解数独](https://leetcode.cn/problems/sudoku-solver/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0037.%20%E8%A7%A3%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、回溯、矩阵 | 困难 | -| 0022 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md) | 字符串、动态规划、回溯 | 中等 | -| 0017 | [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0017.%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.md) | 哈希表、字符串、回溯 | 中等 | -| 0784 | [字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0784.%20%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%B0%8F%E5%86%99%E5%85%A8%E6%8E%92%E5%88%97.md) | 位运算、字符串、回溯 | 中等 | -| 0039 | [组合总和](https://leetcode.cn/problems/combination-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md) | 数组、回溯 | 中等 | -| 0040 | [组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0040.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20II.md) | 数组、回溯 | 中等 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0090 | [子集 II](https://leetcode.cn/problems/subsets-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md) | 位运算、数组、回溯 | 中等 | -| 0473 | [火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0473.%20%E7%81%AB%E6%9F%B4%E6%8B%BC%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1593 | [拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1593.%20%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E5%94%AF%E4%B8%80%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE%E6%9C%80%E5%A4%A7.md) | 哈希表、字符串、回溯 | 中等 | -| 1079 | [活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1079.%20%E6%B4%BB%E5%AD%97%E5%8D%B0%E5%88%B7.md) | 哈希表、字符串、回溯、计数 | 中等 | -| 0093 | [复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md) | 字符串、回溯 | 中等 | -| 0079 | [单词搜索](https://leetcode.cn/problems/word-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0079.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2.md) | 数组、回溯、矩阵 | 中等 | -| 0679 | [24 点游戏](https://leetcode.cn/problems/24-game/) | | 数组、数学、回溯 | 困难 | - -### 贪心算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0455 | [分发饼干](https://leetcode.cn/problems/assign-cookies/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0455.%20%E5%88%86%E5%8F%91%E9%A5%BC%E5%B9%B2.md) | 贪心、数组、双指针、排序 | 简单 | -| 0860 | [柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0860.%20%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.md) | 贪心、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0435 | [无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0435.%20%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.md) | 贪心、数组、动态规划、排序 | 中等 | -| 0452 | [用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0452.%20%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88%86%E6%B0%94%E7%90%83.md) | 贪心、数组、排序 | 中等 | -| 0055 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md) | 贪心、数组、动态规划 | 中等 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0392 | [判断子序列](https://leetcode.cn/problems/is-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0392.%20%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.md) | 双指针、字符串、动态规划 | 简单 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0561 | [数组拆分](https://leetcode.cn/problems/array-partition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md) | 贪心、数组、计数排序、排序 | 简单 | -| 1710 | [卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1710.%20%E5%8D%A1%E8%BD%A6%E4%B8%8A%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%95%E5%85%83%E6%95%B0.md) | 贪心、数组、排序 | 简单 | -| 1217 | [玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1217.%20%E7%8E%A9%E7%AD%B9%E7%A0%81.md) | 贪心、数组、数学 | 简单 | -| 1247 | [交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1247.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%BD%BF%E5%BE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%90%8C.md) | 贪心、数学、字符串 | 中等 | -| 1400 | [构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1400.%20%E6%9E%84%E9%80%A0%20K%20%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 贪心、哈希表、字符串、计数 | 中等 | -| 0921 | [使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0921.%20%E4%BD%BF%E6%8B%AC%E5%8F%B7%E6%9C%89%E6%95%88%E7%9A%84%E6%9C%80%E5%B0%91%E6%B7%BB%E5%8A%A0.md) | 栈、贪心、字符串 | 中等 | -| 1029 | [两地调度](https://leetcode.cn/problems/two-city-scheduling/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1029.%20%E4%B8%A4%E5%9C%B0%E8%B0%83%E5%BA%A6.md) | 贪心、数组、排序 | 中等 | -| 1605 | [给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1605.%20%E7%BB%99%E5%AE%9A%E8%A1%8C%E5%92%8C%E5%88%97%E7%9A%84%E5%92%8C%E6%B1%82%E5%8F%AF%E8%A1%8C%E7%9F%A9%E9%98%B5.md) | 贪心、数组、矩阵 | 中等 | -| 0135 | [分发糖果](https://leetcode.cn/problems/candy/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0135.%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.md) | 贪心、数组 | 困难 | -| 0134 | [加油站](https://leetcode.cn/problems/gas-station/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0134.%20%E5%8A%A0%E6%B2%B9%E7%AB%99.md) | 贪心、数组 | 中等 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0376 | [摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0376.%20%E6%91%86%E5%8A%A8%E5%BA%8F%E5%88%97.md) | 贪心、数组、动态规划 | 中等 | -| 0738 | [单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0738.%20%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E%E7%9A%84%E6%95%B0%E5%AD%97.md) | 贪心、数学 | 中等 | -| 0402 | [移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | | 栈、贪心、字符串、单调栈 | 中等 | -| 0861 | [翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0861.%20%E7%BF%BB%E8%BD%AC%E7%9F%A9%E9%98%B5%E5%90%8E%E7%9A%84%E5%BE%97%E5%88%86.md) | 贪心、位运算、数组、矩阵 | 中等 | -| 0670 | [最大交换](https://leetcode.cn/problems/maximum-swap/) | | 贪心、数学 | 中等 | - -### 位运算题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0504 | [七进制数](https://leetcode.cn/problems/base-7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0504.%20%E4%B8%83%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 数学 | 简单 | -| 0405 | [数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0405.%20%E6%95%B0%E5%AD%97%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 位运算、数学 | 简单 | -| 0190 | [颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0190.%20%E9%A2%A0%E5%80%92%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BD%8D.md) | 位运算、分治 | 简单 | -| 1009 | [十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1009.%20%E5%8D%81%E8%BF%9B%E5%88%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%8F%8D%E7%A0%81.md) | 位运算 | 简单 | -| 0191 | [位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0191.%20%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算、分治 | 简单 | -| 0371 | [两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0371.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 位运算、数学 | 中等 | -| 0089 | [格雷编码](https://leetcode.cn/problems/gray-code/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0089.%20%E6%A0%BC%E9%9B%B7%E7%BC%96%E7%A0%81.md) | 位运算、数学、回溯 | 中等 | -| 0201 | [数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0201.%20%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4%E6%8C%89%E4%BD%8D%E4%B8%8E.md) | 位运算 | 中等 | -| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0137 | [只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0137.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20II.md) | 位运算、数组 | 中等 | -| 0260 | [只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0260.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20III.md) | 位运算、数组 | 中等 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 1349 | [参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 0645 | [错误的集合](https://leetcode.cn/problems/set-mismatch/) | | 位运算、数组、哈希表、排序 | 简单 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0090 | [子集 II](https://leetcode.cn/problems/subsets-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md) | 位运算、数组、回溯 | 中等 | - -## 10. 动态规划 - -### 动态规划基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | - -### 记忆化搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0375 | [猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md) | 数学、动态规划、博弈 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 0576 | [出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md) | 动态规划 | 中等 | -| 0087 | [扰乱字符串](https://leetcode.cn/problems/scramble-string/) | | 字符串、动态规划 | 困难 | -| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 | -| 0552 | [学生出勤记录 II](https://leetcode.cn/problems/student-attendance-record-ii/) | | 动态规划 | 困难 | -| 0913 | [猫和老鼠](https://leetcode.cn/problems/cat-and-mouse/) | | 图、拓扑排序、记忆化搜索、数学、动态规划、博弈 | 困难 | -| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | - -### 线性 DP 题目 - -#### 单串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 数组、二分查找、动态规划 | 中等 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0152 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划 | 中等 | -| 0918 | [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0918.%20%E7%8E%AF%E5%BD%A2%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 队列、数组、分治、动态规划、单调队列 | 中等 | -| 0198 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md) | 数组、动态规划 | 中等 | -| 0213 | [打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0213.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20II.md) | 数组、动态规划 | 中等 | -| 0740 | [删除并获得点数](https://leetcode.cn/problems/delete-and-earn/) | | 数组、哈希表、动态规划 | 中等 | -| 1388 | [3n 块披萨](https://leetcode.cn/problems/pizza-with-3n-slices/) | | 贪心、数组、动态规划、堆(优先队列) | 困难 | -| 0873 | [最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0873.%20%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6.md) | 数组、哈希表、动态规划 | 中等 | -| 1027 | [最长等差数列](https://leetcode.cn/problems/longest-arithmetic-subsequence/) | | 数组、哈希表、二分查找、动态规划 | 中等 | -| 1055 | [形成字符串的最短路径](https://leetcode.cn/problems/shortest-way-to-form-string/) | | 贪心、双指针、字符串 | 中等 | -| 0368 | [最大整除子集](https://leetcode.cn/problems/largest-divisible-subset/) | | 数组、数学、动态规划、排序 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0413 | [等差数列划分](https://leetcode.cn/problems/arithmetic-slices/) | | 数组、动态规划 | 中等 | -| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 | -| 0639 | [解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0639.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95%20II.md) | 字符串、动态规划 | 困难 | -| 0132 | [分割回文串 II](https://leetcode.cn/problems/palindrome-partitioning-ii/) | | 字符串、动态规划 | 困难 | -| 1220 | [统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1220.%20%E7%BB%9F%E8%AE%A1%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 动态规划 | 困难 | -| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 0801 | [使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0801.%20%E4%BD%BF%E5%BA%8F%E5%88%97%E9%80%92%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md) | 数组、动态规划 | 困难 | -| 0871 | [最低加油次数](https://leetcode.cn/problems/minimum-number-of-refueling-stops/) | | 贪心、数组、动态规划、堆(优先队列) | 困难 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0813 | [最大平均值和的分组](https://leetcode.cn/problems/largest-sum-of-averages/) | | 数组、动态规划、前缀和 | 中等 | -| 0887 | [鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0887.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD.md) | 数学、二分查找、动态规划 | 困难 | -| 0256 | [粉刷房子](https://leetcode.cn/problems/paint-house/) | | 数组、动态规划 | 中等 | -| 0265 | [粉刷房子 II](https://leetcode.cn/problems/paint-house-ii/) | | 数组、动态规划 | 困难 | -| 1473 | [粉刷房子 III](https://leetcode.cn/problems/paint-house-iii/) | | 数组、动态规划 | 困难 | -| 0975 | [奇偶跳](https://leetcode.cn/problems/odd-even-jump/) | | 栈、数组、动态规划、有序集合、单调栈 | 困难 | -| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 | -| 1478 | [安排邮筒](https://leetcode.cn/problems/allocate-mailboxes/) | | 数组、数学、动态规划、排序 | 困难 | -| 1230 | [抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | | 数学、动态规划、概率与统计 | 中等 | -| 0410 | [分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | -| 1751 | [最多可以参加的会议数目 II](https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/) | | 数组、二分查找、动态规划、排序 | 困难 | -| 1787 | [使所有区间的异或结果为零](https://leetcode.cn/problems/make-the-xor-of-all-segments-equal-to-zero/) | | 位运算、数组、动态规划 | 困难 | -| 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0123 | [买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0123.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20III.md) | 数组、动态规划 | 困难 | -| 0188 | [买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0188.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20IV.md) | 数组、动态规划 | 困难 | -| 0309 | [最佳买卖股票时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0309.%20%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.md) | 数组、动态规划 | 中等 | -| 0714 | [买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0714.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%E5%90%AB%E6%89%8B%E7%BB%AD%E8%B4%B9.md) | 贪心、数组 | 中等 | - -#### 双串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0712 | [两个字符串的最小ASCII删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) | | 字符串、动态规划 | 中等 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0583 | [两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0583.%20%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.md) | 字符串、动态规划 | 中等 | -| 0072 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md) | 字符串、动态规划 | 困难 | -| 0044 | [通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0044.%20%E9%80%9A%E9%85%8D%E7%AC%A6%E5%8C%B9%E9%85%8D.md) | 贪心、递归、字符串、动态规划 | 困难 | -| 0010 | [正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0010.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 递归、字符串、动态规划 | 困难 | -| 0097 | [交错字符串](https://leetcode.cn/problems/interleaving-string/) | | 字符串、动态规划 | 中等 | -| 0115 | [不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 困难 | -| 0087 | [扰乱字符串](https://leetcode.cn/problems/scramble-string/) | | 字符串、动态规划 | 困难 | - -#### 矩阵线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0118 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md) | 数组、动态规划 | 简单 | -| 0119 | [杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md) | 数组、动态规划 | 简单 | -| 0120 | [三角形最小路径和](https://leetcode.cn/problems/triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0120.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划 | 中等 | -| 0064 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划、矩阵 | 中等 | -| 0174 | [地下城游戏](https://leetcode.cn/problems/dungeon-game/) | | 数组、动态规划、矩阵 | 困难 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0931 | [下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum/) | | 数组、动态规划、矩阵 | 中等 | -| 0576 | [出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md) | 动态规划 | 中等 | -| 0085 | [最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | | 栈、数组、动态规划、矩阵、单调栈 | 困难 | -| 0363 | [矩形区域不超过 K 的最大数值和](https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/) | | 数组、二分查找、矩阵、有序集合、前缀和 | 困难 | -| 面试题 17.24 | [最大子矩阵](https://leetcode.cn/problems/max-submatrix-lcci/) | | 数组、动态规划、矩阵、前缀和 | 困难 | -| 1444 | [切披萨的方案数](https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/) | | 记忆化搜索、数组、动态规划、矩阵 | 困难 | - -#### 无串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0650 | [只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0650.%20%E5%8F%AA%E6%9C%89%E4%B8%A4%E4%B8%AA%E9%94%AE%E7%9A%84%E9%94%AE%E7%9B%98.md) | 数学、动态规划 | 中等 | -| 0264 | [丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0264.%20%E4%B8%91%E6%95%B0%20II.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0343 | [整数拆分](https://leetcode.cn/problems/integer-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md) | 数学、动态规划 | 中等 | - -### 背包问题题目 - -#### 0-1 背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0416 | [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0416.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md) | 数组、动态规划 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 1049 | [最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1049.%20%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F%20II.md) | 数组、动态规划 | 中等 | - -#### 完全背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | -| 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | -| 0377 | [组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md) | 数组、动态规划 | 中等 | -| 0638 | [大礼包](https://leetcode.cn/problems/shopping-offers/) | | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | - -#### 多重背包问题 - -#### 分组背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1155 | [掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1155.%20%E6%8E%B7%E9%AA%B0%E5%AD%90%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%92%8C%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 动态规划 | 中等 | -| 2585 | [获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2585.%20%E8%8E%B7%E5%BE%97%E5%88%86%E6%95%B0%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 数组、动态规划 | 困难 | - -#### 多维背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0474 | [一和零](https://leetcode.cn/problems/ones-and-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0474.%20%E4%B8%80%E5%92%8C%E9%9B%B6.md) | 数组、字符串、动态规划 | 中等 | -| 0879 | [盈利计划](https://leetcode.cn/problems/profitable-schemes/) | | 数组、动态规划 | 困难 | -| 1995 | [统计特殊四元组](https://leetcode.cn/problems/count-special-quadruplets/) | | 数组、枚举 | 简单 | - -### 区间 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0486 | [预测赢家](https://leetcode.cn/problems/predict-the-winner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0486.%20%E9%A2%84%E6%B5%8B%E8%B5%A2%E5%AE%B6.md) | 递归、数组、数学、动态规划、博弈 | 中等 | -| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | -| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | -| 1000 | [合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1000.%20%E5%90%88%E5%B9%B6%E7%9F%B3%E5%A4%B4%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md) | 数组、动态规划、前缀和 | 困难 | -| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划、排序 | 困难 | -| 0664 | [奇怪的打印机](https://leetcode.cn/problems/strange-printer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0664.%20%E5%A5%87%E6%80%AA%E7%9A%84%E6%89%93%E5%8D%B0%E6%9C%BA.md) | 字符串、动态规划 | 困难 | -| 1039 | [多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1039.%20%E5%A4%9A%E8%BE%B9%E5%BD%A2%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%9A%84%E6%9C%80%E4%BD%8E%E5%BE%97%E5%88%86.md) | 数组、动态规划 | 中等 | -| 0546 | [移除盒子](https://leetcode.cn/problems/remove-boxes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0546.%20%E7%A7%BB%E9%99%A4%E7%9B%92%E5%AD%90.md) | 记忆化搜索、数组、动态规划 | 困难 | -| 0375 | [猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md) | 数学、动态规划、博弈 | 中等 | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0730 | [统计不同回文子序列](https://leetcode.cn/problems/count-different-palindromic-subsequences/) | | 字符串、动态规划 | 困难 | -| 2104 | [子数组范围和](https://leetcode.cn/problems/sum-of-subarray-ranges/) | | 栈、数组、单调栈 | 中等 | - -### 树形 DP 题目 - -#### 固定根的树形 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 1245 | [树的直径](https://leetcode.cn/problems/tree-diameter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1245.%20%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 2246 | [相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2246.%20%E7%9B%B8%E9%82%BB%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%80%E9%95%BF%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、图、拓扑排序、数组、字符串 | 困难 | -| 0687 | [最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0687.%20%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0337 | [打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0337.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20III.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | -| 0333 | [最大 BST 子树](https://leetcode.cn/problems/largest-bst-subtree/) | | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 中等 | -| 1617 | [统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1617.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E6%A0%91%E4%B8%AD%E5%9F%8E%E5%B8%82%E4%B9%8B%E9%97%B4%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md) | 位运算、树、动态规划、状态压缩、枚举 | 困难 | -| 2538 | [最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2538.%20%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%E5%92%8C%E4%B8%8E%E6%9C%80%E5%B0%8F%E4%BB%B7%E5%80%BC%E5%92%8C%E7%9A%84%E5%B7%AE%E5%80%BC.md) | 树、深度优先搜索、数组、动态规划 | 困难 | -| 1569 | [将子数组重新排序得到同一个二叉搜索树的方案数](https://leetcode.cn/problems/number-of-ways-to-reorder-array-to-get-same-bst/) | | 树、并查集、二叉搜索树、记忆化搜索、数组、数学、分治、动态规划、二叉树、组合数学 | 困难 | -| 1372 | [二叉树中的最长交错路径](https://leetcode.cn/problems/longest-zigzag-path-in-a-binary-tree/) | | 树、深度优先搜索、动态规划、二叉树 | 中等 | -| 1373 | [二叉搜索子树的最大键值和](https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/) | | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 困难 | -| 0968 | [监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0968.%20%E7%9B%91%E6%8E%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 1273 | [删除树节点](https://leetcode.cn/problems/delete-tree-nodes/) | | 树、深度优先搜索、广度优先搜索 | 中等 | -| 1519 | [子树中标签相同的节点数](https://leetcode.cn/problems/number-of-nodes-in-the-sub-tree-with-the-same-label/) | | 树、深度优先搜索、广度优先搜索、哈希表、计数 | 中等 | - -#### 不定根的树形 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0310 | [最小高度树](https://leetcode.cn/problems/minimum-height-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0310.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0834 | [树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0834.%20%E6%A0%91%E4%B8%AD%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、图、动态规划 | 困难 | -| 2581 | [统计可能的树根数目](https://leetcode.cn/problems/count-number-of-possible-root-nodes/) | | 树、深度优先搜索、哈希表、动态规划 | 困难 | - -### 状态压缩 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1879 | [两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1879.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E7%9A%84%E5%BC%82%E6%88%96%E5%80%BC%E4%B9%8B%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 2172 | [数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2172.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B8%8E%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 1947 | [最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1595 | [连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 1494 | [并行课程 II](https://leetcode.cn/problems/parallel-courses-ii/) | | 位运算、图、动态规划、状态压缩 | 困难 | -| 1655 | [分配重复整数](https://leetcode.cn/problems/distribute-repeating-integers/) | | 位运算、数组、动态规划、回溯、状态压缩 | 困难 | -| 1986 | [完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1986.%20%E5%AE%8C%E6%88%90%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%B7%A5%E4%BD%9C%E6%97%B6%E9%97%B4%E6%AE%B5.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1434 | [每个人戴不同帽子的方案数](https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other/) | | 位运算、数组、动态规划、状态压缩 | 困难 | -| 1799 | [N 次操作后的最大分数和](https://leetcode.cn/problems/maximize-score-after-n-operations/) | | 位运算、数组、数学、动态规划、回溯、状态压缩、数论 | 困难 | -| 1681 | [最小不兼容性](https://leetcode.cn/problems/minimum-incompatibility/) | | 位运算、数组、动态规划、状态压缩 | 困难 | -| 0526 | [优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0526.%20%E4%BC%98%E7%BE%8E%E7%9A%84%E6%8E%92%E5%88%97.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 0351 | [安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md) | 动态规划、回溯 | 中等 | -| 0464 | [我能赢吗](https://leetcode.cn/problems/can-i-win/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0464.%20%E6%88%91%E8%83%BD%E8%B5%A2%E5%90%97.md) | 位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 | 中等 | -| 0847 | [访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0847.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md) | 位运算、广度优先搜索、图、动态规划、状态压缩 | 困难 | -| 0638 | [大礼包](https://leetcode.cn/problems/shopping-offers/) | | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 1994 | [好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1994.%20%E5%A5%BD%E5%AD%90%E9%9B%86%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 位运算、数组、数学、动态规划、状态压缩 | 困难 | -| 1349 | [参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 0698 | [划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0698.%20%E5%88%92%E5%88%86%E4%B8%BAk%E4%B8%AA%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E9%9B%86.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 0943 | [最短超级串](https://leetcode.cn/problems/find-the-shortest-superstring/) | | 位运算、数组、字符串、动态规划、状态压缩 | 困难 | -| 0691 | [贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0691.%20%E8%B4%B4%E7%BA%B8%E6%8B%BC%E8%AF%8D.md) | 位运算、数组、字符串、动态规划、回溯、状态压缩 | 困难 | -| 0982 | [按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0982.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E4%B8%BA%E9%9B%B6%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84.md) | 位运算、数组、哈希表 | 困难 | - -### 计数 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | -| 0063 | [不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0063.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II.md) | 数组、动态规划、矩阵 | 中等 | -| 0343 | [整数拆分](https://leetcode.cn/problems/integer-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md) | 数学、动态规划 | 中等 | -| 0096 | [不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0096.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | -| 1259 | [不相交的握手](https://leetcode.cn/problems/handshakes-that-dont-cross/) | | 数学、动态规划 | 困难 | -| 0790 | [多米诺和托米诺平铺](https://leetcode.cn/problems/domino-and-tromino-tiling/) | | 动态规划 | 中等 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0746 | [使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0746.%20%E4%BD%BF%E7%94%A8%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 数组、动态规划 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | - -### 数位 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 2376 | [统计特殊整数](https://leetcode.cn/problems/count-special-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2376.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E6%95%B4%E6%95%B0.md) | 数学、动态规划 | 困难 | -| 0357 | [统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0357.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E9%83%BD%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97%E4%B8%AA%E6%95%B0.md) | 数学、动态规划、回溯 | 中等 | -| 1012 | [至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1012.%20%E8%87%B3%E5%B0%91%E6%9C%89%201%20%E4%BD%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 困难 | -| 0902 | [最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0902.%20%E6%9C%80%E5%A4%A7%E4%B8%BA%20N%20%E7%9A%84%E6%95%B0%E5%AD%97%E7%BB%84%E5%90%88.md) | 数组、数学、字符串、二分查找、动态规划 | 困难 | -| 0788 | [旋转数字](https://leetcode.cn/problems/rotated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0788.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 中等 | -| 0600 | [不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0600.%20%E4%B8%8D%E5%90%AB%E8%BF%9E%E7%BB%AD1%E7%9A%84%E9%9D%9E%E8%B4%9F%E6%95%B4%E6%95%B0.md) | 动态规划 | 困难 | -| 0233 | [数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0233.%20%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | -| 2719 | [统计整数数目](https://leetcode.cn/problems/count-of-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2719.%20%E7%BB%9F%E8%AE%A1%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 数学、字符串、动态规划 | 困难 | -| 0248 | [中心对称数 III](https://leetcode.cn/problems/strobogrammatic-number-iii/) | | 递归、数组、字符串 | 困难 | -| 1088 | [易混淆数 II](https://leetcode.cn/problems/confusing-number-ii/) | | 数学、回溯 | 困难 | -| 1067 | [范围内的数字计数](https://leetcode.cn/problems/digit-count-in-range/) | | 数学、动态规划 | 困难 | -| 1742 | [盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1742.%20%E7%9B%92%E5%AD%90%E4%B8%AD%E5%B0%8F%E7%90%83%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md) | 哈希表、数学、计数 | 简单 | -| 面试题 17.06 | [2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.06.%202%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | - -### 概率 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0688 | [骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0688.%20%E9%AA%91%E5%A3%AB%E5%9C%A8%E6%A3%8B%E7%9B%98%E4%B8%8A%E7%9A%84%E6%A6%82%E7%8E%87.md) | 动态规划 | 中等 | -| 0808 | [分汤](https://leetcode.cn/problems/soup-servings/) | | 数学、动态规划、概率与统计 | 中等 | -| 0837 | [新 21 点](https://leetcode.cn/problems/new-21-game/) | | 数学、动态规划、滑动窗口、概率与统计 | 中等 | -| 1230 | [抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | | 数学、动态规划、概率与统计 | 中等 | -| 1467 | [两个盒子中球的颜色数相同的概率](https://leetcode.cn/problems/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls/) | | 数组、数学、动态规划、回溯、组合数学、概率与统计 | 困难 | -| 1227 | [飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1227.%20%E9%A3%9E%E6%9C%BA%E5%BA%A7%E4%BD%8D%E5%88%86%E9%85%8D%E6%A6%82%E7%8E%87.md) | 脑筋急转弯、数学、动态规划、概率与统计 | 中等 | -| 1377 | [T 秒后青蛙的位置](https://leetcode.cn/problems/frog-position-after-t-seconds/) | | 树、深度优先搜索、广度优先搜索、图 | 困难 | -| 剑指 Offer 60 | [n个骰子的点数](https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/) | | 数学、动态规划、概率与统计 | 中等 | - -### 动态规划优化题目 - diff --git a/Contents/00.Introduction/06.Interview-100-List.md b/Contents/00.Introduction/06.Interview-100-List.md deleted file mode 100644 index df1c1d8b..00000000 --- a/Contents/00.Introduction/06.Interview-100-List.md +++ /dev/null @@ -1,382 +0,0 @@ -# LeetCode 面试最常考 100 题(按分类排序) - -## 01. 数组 - -### 数组基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 中等 | -| 0048 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、数学、矩阵 | 中等 | - -### 排序算法题目 - -#### 选择排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | - -#### 希尔排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 归并排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | - -#### 快速排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | - -#### 堆排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | - -#### 计数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 桶排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 其他排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0179 | [最大数](https://leetcode.cn/problems/largest-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md) | 贪心、数组、字符串、排序 | 中等 | - -### 二分查找题目 - -#### 二分下标题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0704 | [二分查找](https://leetcode.cn/problems/binary-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md) | 数组、二分查找 | 简单 | -| 0034 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 中等 | -| 0153 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0033 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找 | 中等 | -| 0162 | [寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0240 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md) | 数组、二分查找、分治、矩阵 | 中等 | - -#### 二分答案题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0069 | [x 的平方根](https://leetcode.cn/problems/sqrtx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | - -### 双指针题目 - -#### 对撞指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | - -#### 快慢指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | - -#### 分离双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | - -### 滑动窗口题目 - -#### 固定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | - -#### 不定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0076 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | - -## 02. 链表 - -### 链表经典题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0083 | [删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 链表 | 简单 | -| 0082 | [删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 链表、双指针 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0025 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 困难 | -| 0234 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | - -### 链表排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0148 | [排序链表](https://leetcode.cn/problems/sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | - -### 链表双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0141 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0142 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md) | 哈希表、链表、双指针 | 中等 | -| 0160 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0019 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 剑指 Offer 22 | [链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 0143 | [重排链表](https://leetcode.cn/problems/reorder-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 0002 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 递归、链表、数学 | 中等 | - -## 03. 堆栈 - -### 堆栈基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0155 | [最小栈](https://leetcode.cn/problems/min-stack/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md) | 栈、设计 | 中等 | -| 0020 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 栈、字符串 | 简单 | -| 0227 | [基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md) | 栈、数学、字符串 | 中等 | -| 0232 | [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 0394 | [字符串解码](https://leetcode.cn/problems/decode-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md) | 栈、递归、字符串 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | - -### 单调栈 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | - -## 04. 队列 - -### 队列基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0225 | [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md) | 栈、设计、队列 | 简单 | - -### 优先队列题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | - -## 05. 哈希表 - -### 哈希表题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0041 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md) | 数组、哈希表 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | - -## 06. 字符串 - -### 字符串基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | -| 0151 | [反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 双指针、字符串 | 中等 | -| 0043 | [字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md) | 数学、字符串、模拟 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | - -## 07. 树 - -### 二叉树的遍历题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0102 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0103 | [二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0236 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0112 | [路径总和](https://leetcode.cn/problems/path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0113 | [路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 0101 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | - -### 二叉树的还原题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0105 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | - -### 二叉搜索树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0098 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0110 | [平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | - -### 并查集题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | - -## 08. 图论 - -### 图的深度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0695 | [岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0129 | [求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | - -### 图的广度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | - -## 09. 基础算法 - -### 枚举算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | - -### 递归算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0024 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 递归、链表 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | - -### 分治算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | - -### 回溯算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0046 | [全排列](https://leetcode.cn/problems/permutations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 0022 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md) | 字符串、动态规划、回溯 | 中等 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0039 | [组合总和](https://leetcode.cn/problems/combination-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md) | 数组、回溯 | 中等 | -| 0093 | [复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md) | 字符串、回溯 | 中等 | - -### 贪心算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | - -### 位运算题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | - -## 10. 动态规划 - -### 动态规划题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 数组、二分查找、动态规划 | 中等 | -| 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0064 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划、矩阵 | 中等 | -| 0072 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md) | 字符串、动态规划 | 困难 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | -| 0152 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划 | 中等 | -| 0198 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md) | 数组、动态规划 | 中等 | - -## 11. 补充题目 - -#### 设计数据结构题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0146 | [LRU 缓存](https://leetcode.cn/problems/lru-cache/) | | 设计、哈希表、链表、双向链表 | 中等 | - -#### 模拟题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0008 | [字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0008.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0%20%28atoi%29.md) | 字符串 | 中等 | -| 0165 | [比较版本号](https://leetcode.cn/problems/compare-version-numbers/) | | 双指针、字符串 | 中等 | -| 0468 | [验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0468.%20%E9%AA%8C%E8%AF%81IP%E5%9C%B0%E5%9D%80.md) | 字符串 | 中等 | - -#### 思维锻炼题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0031 | [下一个排列](https://leetcode.cn/problems/next-permutation/) | | 数组、双指针 | 中等 | -| 0470 | [用 Rand7() 实现 Rand10()](https://leetcode.cn/problems/implement-rand10-using-rand7/) | | 数学、拒绝采样、概率与统计、随机化 | 中等 | - - -## 参考资料 - -- 【清单】[CodeTop 企业题库](https://codetop.cc/home) diff --git a/Contents/00.Introduction/07.Interview-200-List.md b/Contents/00.Introduction/07.Interview-200-List.md deleted file mode 100644 index 2c2f0798..00000000 --- a/Contents/00.Introduction/07.Interview-200-List.md +++ /dev/null @@ -1,551 +0,0 @@ -# LeetCode 面试最常考 200 题(按分类排序) - -## 01. 数组 - -### 数组基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0189 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0189.%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针 | 中等 | -| 0498 | [对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0498.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86.md) | 数组、矩阵、模拟 | 中等 | -| 0048 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、数学、矩阵 | 中等 | -| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 中等 | -| 0059 | [螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0059.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20II.md) | 数组、矩阵、模拟 | 中等 | - -### 排序算法题目 - -#### 冒泡排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | - -#### 选择排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | - -#### 插入排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | - -#### 希尔排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 归并排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 剑指 Offer 51 | [数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2051.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | - -#### 快速排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | - -#### 堆排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 剑指 Offer 40 | [最小的k个数](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2040.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | - -#### 计数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 桶排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | - -#### 基数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | - -#### 其他排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0179 | [最大数](https://leetcode.cn/problems/largest-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md) | 贪心、数组、字符串、排序 | 中等 | -| 0384 | [打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0384.%20%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84.md) | 数组、数学、随机化 | 中等 | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | - -### 二分查找题目 - -#### 二分下标题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0704 | [二分查找](https://leetcode.cn/problems/binary-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md) | 数组、二分查找 | 简单 | -| 0034 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 中等 | -| 0153 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0154 | [寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0154.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%20II.md) | 数组、二分查找 | 困难 | -| 0033 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找 | 中等 | -| 0162 | [寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0074 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0074.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5.md) | 数组、二分查找、矩阵 | 中等 | -| 0240 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md) | 数组、二分查找、分治、矩阵 | 中等 | - -#### 二分答案题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0069 | [x 的平方根](https://leetcode.cn/problems/sqrtx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | - -#### 复杂的二分查找问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | - -### 双指针题目 - -#### 对撞指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0611 | [有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0611.%20%E6%9C%89%E6%95%88%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0016 | [最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0016.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0011 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0011.%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8.md) | 贪心、数组、双指针 | 中等 | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | -| 剑指 Offer 21 | [调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2021.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md) | 数组、双指针、排序 | 简单 | -| 0443 | [压缩字符串](https://leetcode.cn/problems/string-compression/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0443.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 中等 | - -#### 快慢指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0026 | [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0026.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 数组、双指针 | 简单 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | - -#### 分离双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | - -### 滑动窗口题目 - -#### 固定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | - -#### 不定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0076 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0862 | [和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 1004 | [最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1004.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | - -## 02. 链表 - -### 链表经典题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0083 | [删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 链表 | 简单 | -| 0082 | [删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 链表、双指针 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0025 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 困难 | -| 0328 | [奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0328.%20%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md) | 链表 | 中等 | -| 0234 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0138 | [复制带随机指针的链表](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0138.%20%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 哈希表、链表 | 中等 | -| 0061 | [旋转链表](https://leetcode.cn/problems/rotate-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0061.%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 链表、双指针 | 中等 | - -### 链表排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0148 | [排序链表](https://leetcode.cn/problems/sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | - -### 链表双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0141 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0142 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md) | 哈希表、链表、双指针 | 中等 | -| 0160 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0019 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 剑指 Offer 22 | [链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 0143 | [重排链表](https://leetcode.cn/problems/reorder-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 0002 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 递归、链表、数学 | 中等 | -| 0445 | [两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0445.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 栈、链表、数学 | 中等 | - -## 03. 堆栈 - -### 堆栈基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1047 | [删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1047.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 栈、字符串 | 简单 | -| 0155 | [最小栈](https://leetcode.cn/problems/min-stack/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md) | 栈、设计 | 中等 | -| 0020 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 栈、字符串 | 简单 | -| 0224 | [基本计算器](https://leetcode.cn/problems/basic-calculator/) | | 栈、递归、数学、字符串 | 困难 | -| 0227 | [基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md) | 栈、数学、字符串 | 中等 | -| 0232 | [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 剑指 Offer 09 | [用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2009.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 0394 | [字符串解码](https://leetcode.cn/problems/decode-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md) | 栈、递归、字符串 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0071 | [简化路径](https://leetcode.cn/problems/simplify-path/) | | 栈、字符串 | 中等 | - -### 单调栈 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0503 | [下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II.md) | 栈、数组、单调栈 | 中等 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0085 | [最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | | 栈、数组、动态规划、矩阵、单调栈 | 困难 | - -## 04. 队列 - -### 队列基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0225 | [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md) | 栈、设计、队列 | 简单 | - -### 优先队列题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0347 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0347.%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0295 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0295.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | - -## 05. 哈希表 - -### 哈希表题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0041 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md) | 数组、哈希表 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0242 | [有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0242.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、排序 | 简单 | -| 0442 | [数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/) | | 数组、哈希表 | 中等 | -| 剑指 Offer 61 | [扑克牌中的顺子](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2061.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md) | 数组、排序 | 简单 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 剑指 Offer 03 | [数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2003.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、哈希表、排序 | 简单 | - -## 06. 字符串 - -### 字符串基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0557 | [反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0557.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20III.md) | 双指针、字符串 | 简单 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | -| 0151 | [反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 双指针、字符串 | 中等 | -| 0043 | [字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md) | 数学、字符串、模拟 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | - -### 单模式串匹配题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0459 | [重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0459.%20%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | - -### 字典树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0208 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0208.%20%E5%AE%9E%E7%8E%B0%20Trie%20%28%E5%89%8D%E7%BC%80%E6%A0%91%29.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0440 | [字典序的第K小数字](https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/) | | 字典树 | 困难 | - -## 07. 树 - -### 二叉树的遍历题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0102 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0103 | [二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0101 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0112 | [路径总和](https://leetcode.cn/problems/path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0113 | [路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 0236 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0297 | [二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0297.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 0114 | [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | | 栈、树、深度优先搜索、链表、二叉树 | 中等 | - -### 二叉树的还原题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0105 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0106 | [从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0106.%20%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | - -### 二叉搜索树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0098 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0450 | [删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0450.%20%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 树、二叉搜索树、二叉树 | 中等 | -| 剑指 Offer 54 | [二叉搜索树的第k大节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2054.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0230 | [二叉搜索树中第K小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0426 | [将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0426.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%8E%92%E5%BA%8F%E7%9A%84%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | -| 0110 | [平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | - -### 并查集题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | - -## 08. 图论 - -### 图的深度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0695 | [岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0129 | [求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | - -### 图的广度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 32 - III | [从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md) | 树、广度优先搜索、二叉树 | 中等 | - -### 图的拓扑排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | - -## 09. 基础算法 - -### 枚举算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0560 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | - -### 递归算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0024 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 递归、链表 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 62 | [圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 递归、数学 | 简单 | - -### 分治算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | -| 剑指 Offer 33 | [二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2033.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 栈、树、二叉搜索树、递归、二叉树、单调栈 | 中等 | - -### 回溯算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0046 | [全排列](https://leetcode.cn/problems/permutations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 0047 | [全排列 II](https://leetcode.cn/problems/permutations-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0047.%20%E5%85%A8%E6%8E%92%E5%88%97%20II.md) | 数组、回溯 | 中等 | -| 0037 | [解数独](https://leetcode.cn/problems/sudoku-solver/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0037.%20%E8%A7%A3%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、回溯、矩阵 | 困难 | -| 0022 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md) | 字符串、动态规划、回溯 | 中等 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0039 | [组合总和](https://leetcode.cn/problems/combination-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md) | 数组、回溯 | 中等 | -| 0040 | [组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0040.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20II.md) | 数组、回溯 | 中等 | -| 0093 | [复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md) | 字符串、回溯 | 中等 | -| 0079 | [单词搜索](https://leetcode.cn/problems/word-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0079.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2.md) | 数组、回溯、矩阵 | 中等 | -| 0679 | [24 点游戏](https://leetcode.cn/problems/24-game/) | | 数组、数学、回溯 | 困难 | - -### 贪心算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0055 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md) | 贪心、数组、动态规划 | 中等 | -| 0402 | [移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | | 栈、贪心、字符串、单调栈 | 中等 | -| 0135 | [分发糖果](https://leetcode.cn/problems/candy/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0135.%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.md) | 贪心、数组 | 困难 | -| 0134 | [加油站](https://leetcode.cn/problems/gas-station/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0134.%20%E5%8A%A0%E6%B2%B9%E7%AB%99.md) | 贪心、数组 | 中等 | -| 0670 | [最大交换](https://leetcode.cn/problems/maximum-swap/) | | 贪心、数学 | 中等 | - -### 位运算题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0191 | [位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0191.%20%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算、分治 | 简单 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | - -## 10. 动态规划 - -### 动态规划题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | -| 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 数组、二分查找、动态规划 | 中等 | -| 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0064 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划、矩阵 | 中等 | -| 0072 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md) | 字符串、动态规划 | 困难 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | -| 0063 | [不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0063.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II.md) | 数组、动态规划、矩阵 | 中等 | -| 0152 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划 | 中等 | -| 0198 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md) | 数组、动态规划 | 中等 | -| 0213 | [打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0213.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20II.md) | 数组、动态规划 | 中等 | -| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 | -| 0010 | [正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0010.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 递归、字符串、动态规划 | 困难 | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | -| 0044 | [通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0044.%20%E9%80%9A%E9%85%8D%E7%AC%A6%E5%8C%B9%E9%85%8D.md) | 贪心、递归、字符串、动态规划 | 困难 | -| 0120 | [三角形最小路径和](https://leetcode.cn/problems/triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0120.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划 | 中等 | -| 0096 | [不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0096.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | -| 0887 | [鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0887.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD.md) | 数学、二分查找、动态规划 | 困难 | -| 0097 | [交错字符串](https://leetcode.cn/problems/interleaving-string/) | | 字符串、动态规划 | 中等 | -| 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | - -### 记忆化搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | - -## 11. 补充题目 - -#### 设计数据结构题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0146 | [LRU 缓存](https://leetcode.cn/problems/lru-cache/) | | 设计、哈希表、链表、双向链表 | 中等 | -| 0460 | [LFU 缓存](https://leetcode.cn/problems/lfu-cache/) | | 设计、哈希表、链表、双向链表 | 困难 | - -#### 数学题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0007 | [整数反转](https://leetcode.cn/problems/reverse-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0007.%20%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC.md) | 数学 | 中等 | -| 0009 | [回文数](https://leetcode.cn/problems/palindrome-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0009.%20%E5%9B%9E%E6%96%87%E6%95%B0.md) | 数学 | 简单 | -| 剑指 Offer 62 | [圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 递归、数学 | 简单 | -| 0168 | [Excel表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0168.%20Excel%E8%A1%A8%E5%88%97%E5%90%8D%E7%A7%B0.md) | 数学、字符串 | 简单 | -| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | - -#### 模拟题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0008 | [字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0008.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0%20%28atoi%29.md) | 字符串 | 中等 | -| 0165 | [比较版本号](https://leetcode.cn/problems/compare-version-numbers/) | | 双指针、字符串 | 中等 | -| 0468 | [验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0468.%20%E9%AA%8C%E8%AF%81IP%E5%9C%B0%E5%9D%80.md) | 字符串 | 中等 | -| 0086 | [分隔链表](https://leetcode.cn/problems/partition-list/) | | 链表、双指针 | 中等 | - -#### 前缀和 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0560 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | - -#### 思维锻炼题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0031 | [下一个排列](https://leetcode.cn/problems/next-permutation/) | | 数组、双指针 | 中等 | -| 0556 | [下一个更大元素 III](https://leetcode.cn/problems/next-greater-element-iii/) | | 数学、双指针、字符串 | 中等 | -| 0470 | [用 Rand7() 实现 Rand10()](https://leetcode.cn/problems/implement-rand10-using-rand7/) | | 数学、拒绝采样、概率与统计、随机化 | 中等 | - - -## 参考资料 - -- 【清单】[CodeTop 企业题库](https://codetop.cc/home) diff --git a/Contents/00.Introduction/08.Algorithms-Overview.md b/Contents/00.Introduction/08.Algorithms-Overview.md deleted file mode 100644 index 3bb31bac..00000000 --- a/Contents/00.Introduction/08.Algorithms-Overview.md +++ /dev/null @@ -1,3 +0,0 @@ -## 1. 数据结构和算法分类 - -## 2. 时间复杂度与数据规模 \ No newline at end of file diff --git a/Contents/00.Introduction/index.md b/Contents/00.Introduction/index.md deleted file mode 100644 index 7acc1ae5..00000000 --- a/Contents/00.Introduction/index.md +++ /dev/null @@ -1,9 +0,0 @@ -## 本章内容 - -- [算法与数据结构](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/01.Data-Structures-Algorithms.md) -- [算法复杂度](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/02.Algorithm-Complexity.md) -- [LeetCode 入门与攻略](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/03.LeetCode-Guide.md) -- [LeetCode 题解(字典序排序,700+ 道题解)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/04.Solutions-List.md) -- [LeetCode 题解(按分类排序,推荐刷题列表 ★★★)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/05.Categories-List.md) -- [LeetCode 面试最常考 100 题(按分类排序)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/06.Interview-100-List.md) -- [LeetCode 面试最常考 200 题(按分类排序)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/00.Introduction/07.Interview-200-List.md) \ No newline at end of file diff --git a/Contents/01.Array/01.Array-Basic/01.Array-Basic.md b/Contents/01.Array/01.Array-Basic/01.Array-Basic.md deleted file mode 100644 index 1c5f4011..00000000 --- a/Contents/01.Array/01.Array-Basic/01.Array-Basic.md +++ /dev/null @@ -1,251 +0,0 @@ -## 1. 数组简介 - -### 1.1 数组定义 - -> **数组(Array)**:一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。 - -简单来说,**「数组」** 是实现线性表的顺序结构存储的基础。 - -以整数数组为例,数组的存储方式如下图所示。 - -![数组](https://qcdn.itcharge.cn/images/20210913163542.png) - -如上图所示,假设数据元素的个数为 $n$,则数组中的每一个数据元素都有自己的下标索引,下标索引从 $0$ 开始,到 $n - 1$ 结束。数组中的每一个「下标索引」,都有一个与之相对应的「数据元素」。 - -从上图还可以看出,数组在计算机中的表示,就是一片连续的存储单元。数组中的每一个数据元素都占有一定的存储单元,每个存储单元都有自己的内存地址,并且元素之间是紧密排列的。 - -我们还可以从两个方面来解释一下数组的定义。 - -> 1. **线性表**:线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前、后两个方向。数组就是一种线性表结构,此外,栈、队列、链表都是线性表结构。 -> 2. **连续的内存空间**:线性表有两种存储结构:「顺序存储结构」和「链式存储结构」。其中,「顺序存储结构」是指占用的内存空间是连续的,相邻数据元素之间,物理内存上的存储位置也相邻。数组也是采用了顺序存储结构,并且存储的数据都是相同类型的。 - -综合这两个角度,数组就可以看做是:使用了「顺序存储结构」的「线性表」的一种实现方式。 - -### 1.2 如何随机访问数据元素 - -数组的一个最大特点是:**可以进行随机访问**。即数组可以根据下标,直接定位到某一个元素存放的位置。 - -那么,计算机是如何实现根据下标随机访问数组元素的? - -计算机给一个数组分配了一组连续的存储空间,其中第一个元素开始的地址被称为 **「首地址」**。每个数据元素都有对应的下标索引和内存地址,计算机通过地址来访问数据元素。当计算机需要访问数组的某个元素时,会通过 **「寻址公式」** 计算出对应元素的内存地址,然后访问地址对应的数据元素。 - -寻址公式如下:**下标 $i$ 对应的数据元素地址 = 数据首地址 + $i$ × 单个数据元素所占内存大小**。 - -### 1.3 多维数组 - -上面介绍的数组只有一个维度,称为一维数组,其数据元素也是单下标变量。但是在实际问题中,很多信息是二维或者是多维的,一维数组已经满足不了我们的需求,所以就有了多维数组。 - -以二维数组为例,数组的形式如下图所示。 - -![二维数组](https://qcdn.itcharge.cn/images/20210916222435.png) - -二维数组是一个由 $m$ 行 $n$ 列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即 **「数组的数组」**。二维数组的第一维度表示行,第二维度表示列。 - -我们可以将二维数组看做是一个矩阵,并处理矩阵的相关问题,比如转置矩阵、矩阵相加、矩阵相乘等等。 - -### 1.4 不同编程语言中数组的实现 - -在具体的编程语言中,数组这个数据结构的实现方式具有一定差别。 - -C / C++ 语言中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如: - -```C++ -int arr[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; -``` - -Java 中的数组跟数据结构定义中的数组不太一样。Java 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如: - -```Java -int[][] arr = new int[3][]{ {1,2,3}, {4,5}, {6,7,8,9}}; -``` - -原生 Python 中其实没有数组的概念,而是使用了类似 Java 中的 ArrayList 容器类数据结构,叫做列表。通常我们把列表来作为 Python 中的数组使用。Python 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如: - -```python -arr = ['python', 'java', ['asp', 'php'], 'c'] -``` - -## 2. 数组的基本操作 - -数据结构的操作一般涉及到增、删、改、查共 $4$ 种情况,下面我们一起来看一下数组的这 $4$ 种基本操作。 - -### 2.1 访问元素 - -> **访问数组中第 $i$ 个元素**: -> -> 1. 只需要检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。超出范围的访问为非法访问。 -> 2. 当位置合法时,由给定下标得到元素的值。 - -```python -# 从数组 nums 中读取下标为 i 的数据元素值 -def value(nums, i): - if 0 <= i <= len(nums) - 1: - print(nums[i]) - -arr = [0, 5, 2, 3, 7, 1, 6] -value(arr, 3) -``` - -「访问数组元素」的操作不依赖于数组中元素个数,因此,「访问数组元素」的时间复杂度为 $O(1)$。 - -### 2.2 查找元素 - -> **查找数组中元素值为 $val$ 的位置**: -> -> 1. 建立一个基于下标的循环,每次将 $val$ 与当前数据元素 $nums[i]$ 进行比较。 -> 2. 在找到元素的时候返回元素下标。 -> 3. 遍历完找不到时可以返回一个特殊值(例如 $-1$)。 - -```python -# 从数组 nums 中查找元素值为 val 的数据元素第一次出现的位置 -def find(nums, val): - for i in range(len(nums)): - if nums[i] == val: - return i - return -1 - -arr = [0, 5, 2, 3, 7, 1, 6] -print(find(arr, 5)) -``` - -在「查找元素」的操作中,如果数组无序,那么我们只能通过将 $val$ 与数组中的数据元素逐一对比的方式进行查找,也称为线性查找。而线性查找操作依赖于数组中元素个数,因此,「查找元素」的时间复杂度为 $O(n)$。 - -### 2.3 插入元素 - -插入元素操作分为两种:「在数组尾部插入值为 $val$ 的元素」和「在数组第 $i$ 个位置上插入值为 $val$ 的元素」。 - -> **在数组尾部插入值为 $val$ 的元素**: -> -> 1. 如果数组尾部容量不满,则直接把 $val$ 放在数组尾部的空闲位置,并更新数组的元素计数值。 -> 2. 如果数组容量满了,则插入失败。不过,Python 中的 list 列表做了其他处理,当数组容量满了,则会开辟新的空间进行插入。 - -Python 中的 list 列表直接封装了尾部插入操作,直接调用 `append` 方法即可。 - -![插入元素](https://qcdn.itcharge.cn/images/20210916222517.png) - -```python -arr = [0, 5, 2, 3, 7, 1, 6] -val = 4 -arr.append(val) -print(arr) -``` - -「在数组尾部插入元素」的操作不依赖数组个数,因此,「在数组尾部插入元素」的时间复杂度为 $O(1)$。 - -> **在数组第 $i$ 个位置上插入值为 $val$ 的元素**: -> -> 1. 先检查插入下标 $i$ 是否合法,即 $0 \le i \le len(nums)$。 -> 2. 确定合法位置后,通常情况下第 $i$ 个位置上已经有数据了(除非 $i == len(nums)$),要把第 $i \sim len(nums) - 1$ 位置上的元素依次向后移动。 -> 3. 然后再在第 $i$ 个元素位置赋值为 $val$,并更新数组的元素计数值。 - -Python 中的 list 列表直接封装了中间插入操作,直接调用 `insert` 方法即可。 - -![插入中间元素](https://qcdn.itcharge.cn/images/20210916224032.png) - -```python -arr = [0, 5, 2, 3, 7, 1, 6] -i, val = 2, 4 -arr.insert(i, val) -print(arr) -``` - -「在数组中间位置插入元素」的操作中,由于移动元素的操作次数跟元素个数有关,因此,「在数组中间位置插入元素」的最坏和平均时间复杂度都是 $O(n)$。 - -### 2.4 改变元素 - -> **将数组中第 $i$ 个元素值改为 $val$**: -> -> 1. 需要先检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。 -> 2. 然后将第 $i$ 个元素值赋值为 $val$。 - -![改变元素](https://qcdn.itcharge.cn/images/20210916224722.png) - -```python -def change(nums, i, val): - if 0 <= i <= len(nums) - 1: - nums[i] = val - -arr = [0, 5, 2, 3, 7, 1, 6] -i, val = 2, 4 -change(arr, i, val) -print(arr) -``` - -「改变元素」的操作跟访问元素操作类似,访问操作不依赖于数组中元素个数,因此,「改变元素」的时间复杂度为 $O(1)$。 - -### 2.5 删除元素 - -删除元素分为三种情况:「删除数组尾部元素」、「删除数组第 $i$ 个位置上的元素」、「基于条件删除元素」。 - -> **删除数组尾部元素**: -> -> 1. 只需将元素计数值减一即可。 - -Python 中的 list 列表直接封装了删除数组尾部元素的操作,只需要调用 `pop` 方法即可。 - -![删除尾部元素](https://qcdn.itcharge.cn/images/20210916233914.png) - -```python -arr = [0, 5, 2, 3, 7, 1, 6] -arr.pop() -print(arr) -``` - -「删除数组尾部元素」的操作,不依赖于数组中的元素个数,因此,「删除数组尾部元素」的时间复杂度为 $O(1)$。 - -> **删除数组第 $i$ 个位置上的元素**: -> -> 1. 先检查下标 $i$ 是否合法,即 $0 \le i \le len(nums) - 1$。 -> 2. 如果下标合法,则将第 $i + 1$ 个位置到第 $len(nums) - 1$ 位置上的元素依次向左移动。 -> 3. 删除后修改数组的元素计数值。 - -Python 中的 list 列表直接封装了删除数组中间元素的操作,只需要以下标作为参数调用 `pop` 方法即可。 - -![删除中间元素](https://qcdn.itcharge.cn/images/20210916234013.png) - -``` -arr = [0, 5, 2, 3, 7, 1, 6] -i = 3 -arr.pop(i) -print(arr) -``` - -「删除数组中间位置元素」的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此,「删除数组中间位置元素」的最坏和平均时间复杂度都是 $O(n)$。 - -> **基于条件删除元素**:这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。 - -```python -arr = [0, 5, 2, 3, 7, 1, 6] -arr.remove(5) -print(arr) -``` - -「基于条件删除元素」的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此,「基于条件删除元素」的最坏和平均时间复杂度都是 $O(n)$。 - ---- - -到这里,有关数组的基础知识就介绍完了。下面进行一下总结。 - -## 3. 数组的基础知识总结 - -数组是最基础、最简单的数据结构。数组是实现线性表的顺序结构存储的基础。它使用一组连续的内存空间,来存储一组具有相同类型的数据。 - -数组的最大特点的支持随机访问。访问数组元素、改变数组元素的时间复杂度为 $O(1)$,在数组尾部插入、删除元素的时间复杂度也是 $O(1)$,普通情况下插入、删除元素的时间复杂度为 $O(n)$。 - -## 参考资料 - -- 【文章】[数据结构中的数组和不同语言中数组的区别 - CSDN 博客](https://blog.csdn.net/sinat_14913533/article/details/102763573) - -- 【文章】[数组理论基础 - 代码随想录](https://programmercarl.com/数组理论基础.html#数组理论基础) - -- 【文章】[Python 与 Java 中容器对比:List - 知乎](https://zhuanlan.zhihu.com/p/120312437) - -- 【文章】[什么是数组 - 漫画算法 - 小灰的算法之旅 - 力扣](https://leetcode.cn/leetbook/read/journey-of-algorithm/5ozchs/) - -- 【文章】[数组 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/intro/100017301) - -- 【书籍】数据结构教程 第 2 版 - 唐发根 著 - -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 - - diff --git a/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md b/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md deleted file mode 100644 index eb1b656c..00000000 --- a/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md +++ /dev/null @@ -1,23 +0,0 @@ -### 数组基础题目 - -#### 数组操作题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0189 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0189.%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针 | 中等 | -| 0066 | [加一](https://leetcode.cn/problems/plus-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0066.%20%E5%8A%A0%E4%B8%80.md) | 数组、数学 | 简单 | -| 0724 | [寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0724.%20%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E5%BF%83%E4%B8%8B%E6%A0%87.md) | 数组、前缀和 | 简单 | -| 0485 | [最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 数组 | 简单 | -| 0238 | [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0238.%20%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF.md) | 数组、前缀和 | 中等 | - -#### 二维数组题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0498 | [对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0498.%20%E5%AF%B9%E8%A7%92%E7%BA%BF%E9%81%8D%E5%8E%86.md) | 数组、矩阵、模拟 | 中等 | -| 0048 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0048.%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F.md) | 数组、数学、矩阵 | 中等 | -| 0073 | [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0073.%20%E7%9F%A9%E9%98%B5%E7%BD%AE%E9%9B%B6.md) | 数组、哈希表、矩阵 | 中等 | -| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组、矩阵、模拟 | 中等 | -| 0059 | [螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0059.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5%20II.md) | 数组、矩阵、模拟 | 中等 | -| 0289 | [生命游戏](https://leetcode.cn/problems/game-of-life/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0289.%20%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F.md) | 数组、矩阵、模拟 | 中等 | - diff --git a/Contents/01.Array/01.Array-Basic/index.md b/Contents/01.Array/01.Array-Basic/index.md deleted file mode 100644 index 6dad3efd..00000000 --- a/Contents/01.Array/01.Array-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [数组基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/01.Array-Basic.md) -- [数组基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md b/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md deleted file mode 100644 index 6dc7a253..00000000 --- a/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md +++ /dev/null @@ -1,102 +0,0 @@ -## 1. 冒泡排序算法思想 - -> **冒泡排序(Bubble Sort)基本思想**: -> -> 经过多次迭代,通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 - -这个过程就像水底的气泡一样从底部向上「冒泡」到水面,这也是冒泡排序法名字的由来。 - -接下来,我们使用「冒泡」的方式来模拟一下这个过程。 - -1. 首先将数组想象是一排「泡泡」,元素值的大小与泡泡的大小成正比。 -2. 然后从左到右依次比较相邻的两个「泡泡」: - 1. 如果左侧泡泡大于右侧泡泡,则交换两个泡泡的位置。 - 2. 如果左侧泡泡小于等于右侧泡泡,则两个泡泡保持不变。 -3. 这 $1$ 趟遍历完成之后,最大的泡泡就会放置到所有泡泡的最右侧,就像是「泡泡」从水底向上浮到了水面。 - -::: tabs#bubble - -@tab <1> - -![冒泡排序 1](https://qcdn.itcharge.cn/images/202308152226863.png) - -@tab <2> - -![冒泡排序 2](https://qcdn.itcharge.cn/images/202308152227763.png) - -@tab <3> - -![冒泡排序 3](https://qcdn.itcharge.cn/images/202308152227002.png) - -@tab <4> - -![冒泡排序 4](https://qcdn.itcharge.cn/images/202308152227621.png) - -@tab <5> - -![冒泡排序 5](https://qcdn.itcharge.cn/images/202308152227175.png) - -@tab <6> - -![冒泡排序 6](https://qcdn.itcharge.cn/images/202308152227578.png) - -@tab <7> - -![冒泡排序 7](https://qcdn.itcharge.cn/images/202308152228488.png) - -::: - -## 2. 冒泡排序算法步骤 - -假设数组的元素个数为 $n$ 个,则冒泡排序的算法步骤如下: - -1. 第 $1$ 趟「冒泡」:对前 $n$ 个元素执行「冒泡」,从而使第 $1$ 个值最大的元素放置在正确位置上。 - 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换。 - 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,如果前者大于后者,则两者交换位置,否则不交换。 - 3. 依次类推,直到第 $n - 1$ 个元素与第 $n$ 个元素比较(或交换)为止。 - 4. 经过第 $1$ 趟排序,使得 $n$ 个元素中第 $i$ 个值最大元素被安置在第 $n$ 个位置上。 -2. 第 $2$ 趟「冒泡」:对前 $n - 1$ 个元素执行「冒泡」,从而使第 $2$ 个值最大的元素放置在正确位置上。 - 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换。 - 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,若前者大于后者,则两者交换位置,否则不交换。 - 3. 依次类推,直到对 $n - 2$ 个元素与第 $n - 1$ 个元素比较(或交换)为止。 - 4. 经过第 $2$ 趟排序,使得数组中第 $2$ 个值最大元素被安置在第 $n$ 个位置上。 -3. 依次类推,重复上述「冒泡」过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 - -我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下冒泡排序的整个过程。 - -![冒泡排序](http://qcdn.itcharge.cn/images/20230816154510.png) - -## 3. 冒泡排序代码实现 - -```python -class Solution: - def bubbleSort(self, nums: [int]) -> [int]: - # 第 i 趟「冒泡」 - for i in range(len(nums) - 1): - flag = False # 是否发生交换的标志位 - # 从数组中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较 - for j in range(len(nums) - i - 1): - # 相邻两个元素进行比较,如果前者大于后者,则交换位置 - if nums[j] > nums[j + 1]: - nums[j], nums[j + 1] = nums[j + 1], nums[j] - flag = True - if not flag: # 此趟遍历未交换任何元素,直接跳出 - break - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.bubbleSort(nums) -``` - -## 4. 冒泡排序算法分析 - -- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),只需经过 $1$ 趟排序,总共经过 $n$ 次元素之间的比较,并且不移动元素,算法就可以结束排序。因此,冒泡排序算法的最佳时间复杂度为 $O(n)$。 -- **最坏时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列,或者最小值元素处在序列的最后),则需要进行 $n$ 趟排序,总共进行 $∑^n_{i=2}(i−1) = \frac{n(n−1)}{2}$ 次元素之间的比较,因此,冒泡排序算法的最坏时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(1)$。冒泡排序为原地排序算法,只用到指针变量 $i$、$j$ 以及标志位 $flag$ 等常数项的变量。 -- **冒泡排序适用情况**:冒泡排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,冒泡排序方法比较适合于参加排序序列的数据量较小的情况,尤其是当序列的初始状态为基本有序的情况。 -- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变相等元素的相对顺序,因此,冒泡排序法是一种 **稳定排序算法**。 - -## 参考资料 - -- 【文章】[11.3. 冒泡排序 - Hello 算法](https://www.hello-algo.com/chapter_sorting/bubble_sort/) \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md b/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md deleted file mode 100644 index ed31b9a6..00000000 --- a/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md +++ /dev/null @@ -1,88 +0,0 @@ -## 1. 选择排序算法思想 - -> **选择排序(Selection Sort)基本思想**: -> -> 将数组分为两个区间:左侧为已排序区间,右侧为未排序区间。每趟从未排序区间中选择一个值最小的元素,放到已排序区间的末尾,从而将该元素划分到已排序区间。 - -选择排序是一种简单直观的排序算法,其思想简单,代码也相对容易。 - -## 2. 选择排序算法步骤 - -假设数组的元素个数为 $n$ 个,则选择排序的算法步骤如下: - -1. 初始状态下,无已排序区间,未排序区间为 $[0, n - 1]$。 -2. 第 $1$ 趟选择: - 1. 遍历未排序区间 $[0, n - 1]$,使用变量 $min\underline{}i$ 记录区间中值最小的元素位置。 - 2. 将 $min\underline{}i$ 与下标为 $0$ 处的元素交换位置。如果下标为 $0$ 处元素就是值最小的元素位置,则不用交换。 - 3. 此时,$[0, 0]$ 为已排序区间,$[1, n - 1]$(总共 $n - 1$ 个元素)为未排序区间。 -3. 第 $2$ 趟选择: - 1. 遍历未排序区间 $[1, n - 1]$,使用变量 $min\underline{}i$ 记录区间中值最小的元素位置。 - 2. 将 $min\underline{}i$ 与下标为 $1$ 处的元素交换位置。如果下标为 $1$ 处元素就是值最小的元素位置,则不用交换。 - 3. 此时,$[0, 1]$ 为已排序区间,$[2, n - 1]$(总共 $n - 2$ 个元素)为未排序区间。 -4. 依次类推,对剩余未排序区间重复上述选择过程,直到所有元素都划分到已排序区间,排序结束。 - -我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下选择排序的整个过程。 - -::: tabs#selectionSort - -@tab <1> - -![选择排序 1](http://qcdn.itcharge.cn/images/20230816155042.png) - -@tab <2> - -![选择排序 2](http://qcdn.itcharge.cn/images/20230816155017.png) - -@tab <3> - -![选择排序 3](http://qcdn.itcharge.cn/images/20230816154955.png) - -@tab <4> - -![选择排序 4](http://qcdn.itcharge.cn/images/20230816154924.png) - -@tab <5> - -![选择排序 5](http://qcdn.itcharge.cn/images/20230816154859.png) - -@tab <6> - -![选择排序 6](http://qcdn.itcharge.cn/images/20230816154836.png) - -@tab <7> - -![选择排序 7](http://qcdn.itcharge.cn/images/20230816153324.png) - -::: - -## 3. 选择排序代码实现 - -```python -class Solution: - def selectionSort(self, nums: [int]) -> [int]: - for i in range(len(nums) - 1): - # 记录未排序区间中最小值的位置 - min_i = i - for j in range(i + 1, len(nums)): - if nums[j] < nums[min_i]: - min_i = j - # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 - if i != min_i: - nums[i], nums[min_i] = nums[min_i], nums[i] - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.selectionSort(nums) -``` - -## 4. 选择排序算法分析 - -- **时间复杂度**:$O(n^2)$。排序法所进行的元素之间的比较次数与序列的原始状态无关,时间复杂度总是 $O(n^2)$。 - - 这是因为无论序列中元素的初始排列状态如何,第 $i$ 趟排序要找出值最小元素都需要进行 $n − i$ 次元素之间的比较。因此,整个排序过程需要进行的元素之间的比较次数都相同,为 $∑^n_{i=2}(i - 1) = \frac{n(n−1)}{2}$ 次。 -- **空间复杂度**:$O(1)$。选择排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及最小值位置 $min\underline{}i$ 等常数项的变量。 -- **选择排序适用情况**:选择排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序,因此在空间复杂度要求较高时,可以考虑选择排序。 - -- **排序稳定性**:由于值最小元素与未排序区间第 $1$ 个元素的交换动作是在不相邻的元素之间进行的,因此很有可能会改变相等元素的相对顺序,因此,选择排序法是一种 **不稳定排序算法**。 - - - diff --git a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md b/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md deleted file mode 100644 index 07881db9..00000000 --- a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md +++ /dev/null @@ -1,60 +0,0 @@ -## 1. 插入排序算法思想 - -> **插入排序(Insertion Sort)基本思想**: -> -> 将数组分为两个区间:左侧为有序区间,右侧为无序区间。每趟从无序区间取出一个元素,然后将其插入到有序区间的适当位置。 -> - -插入排序在每次插入一个元素时,该元素会在有序区间找到合适的位置,因此每次插入后,有序区间都会保持有序。 - -## 2. 插入排序算法步骤 - -假设数组的元素个数为 $n$ 个,则插入排序的算法步骤如下: - -1. 初始状态下,有序区间为 $[0, 0]$,无序区间为 $[1, n - 1]$。 -2. 第 $1$ 趟插入: - 1. 取出无序区间 $[1, n - 1]$ 中的第 $1$ 个元素,即 $nums[1]$。 - 2. 从右到左遍历有序区间中的元素,将比 $nums[1]$ 小的元素向后移动 $1$ 位。 - 3. 如果遇到大于或等于 $nums[1]$ 的元素时,说明找到了插入位置,将 $nums[1]$ 插入到该位置。 - 4. 插入元素后有序区间变为 $[0, 1]$,无序区间变为 $[2, n - 1]$。 -3. 第 $2$ 趟插入: - 1. 取出无序区间 $[2, n - 1]$ 中的第 $1$ 个元素,即 $nums[2]$。 - 2. 从右到左遍历有序区间中的元素,将比 $nums[2]$ 小的元素向后移动 $1$ 位。 - 3. 如果遇到大于或等于 $nums[2]$ 的元素时,说明找到了插入位置,将 $nums[2]$ 插入到该位置。 - 4. 插入元素后有序区间变为 $[0, 2]$,无序区间变为 $[3, n - 1]$。 -4. 依次类推,对剩余无序区间中的元素重复上述插入过程,直到所有元素都插入到有序区间中,排序结束。 - -我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下插入排序的整个过程。 - -![插入排序](http://qcdn.itcharge.cn/images/20230816175619.png) - -## 3. 插入排序代码实现 - -```python -class Solution: - def insertionSort(self, nums: [int]) -> [int]: - # 遍历无序区间 - for i in range(1, len(nums)): - temp = nums[i] - j = i - # 从右至左遍历有序区间 - while j > 0 and nums[j - 1] > temp: - # 将有序区间中插入位置右侧的元素依次右移一位 - nums[j] = nums[j - 1] - j -= 1 - # 将该元素插入到适当位置 - nums[j] = temp - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.insertionSort(nums) -``` - -## 4. 插入排序算法分析 - -- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时区间已经是升序排列),每个元素只进行一次元素之间的比较,因而总的比较次数最少,为 $∑^n_{i = 2}1 = n − 1$,并不需要移动元素(记录),这是最好的情况。 -- **最差时间复杂度**:$O(n^2)$。最差的情况下(初始时区间已经是降序排列),每个元素 $nums[i]$ 都要进行 $i - 1$ 次元素之间的比较,元素之间总的比较次数达到最大值,为 $∑^n_{i=2}(i − 1) = \frac{n(n−1)}{2}$。 -- **平均时间复杂度**:$O(n^2)$。如果区间的初始情况是随机的,即参加排序的区间中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $\frac{n^2}{4}$。由此得知,插入排序算法的平均时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(1)$。插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量等常数项的变量。 -- **排序稳定性**:在插入操作过程中,每次都讲元素插入到相等元素的右侧,并不会改变相等元素的相对顺序。因此,插入排序方法是一种 **稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md b/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md deleted file mode 100644 index e0e9aa93..00000000 --- a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md +++ /dev/null @@ -1,89 +0,0 @@ -## 1. 希尔排序算法思想 - -> **希尔排序(Shell Sort)基本思想**: -> -> 将整个数组切按照一定的间隔取值划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 $1$,对整个数组进行插入排序。 -> - -## 2. 希尔排序算法步骤 - -假设数组的元素个数为 $n$ 个,则希尔排序的算法步骤如下: - -1. 确定一个元素间隔数 $gap$。 -2. 将参加排序的数组按此间隔数从第 $1$ 个元素开始一次分成若干个子数组,即分别将所有位置相隔为 $gap$ 的元素视为一个子数组。 -3. 在各个子数组中采用某种排序算法(例如插入排序算法)进行排序。 -4. 减少间隔数,并重新将整个数组按新的间隔数分成若干个子数组,再分别对各个子数组进行排序。 -5. 依次类推,直到间隔数 $gap$ 值为 $1$,最后进行一次排序,排序结束。 - -我们以 $[7, 2, 6, 8, 0, 4, 1, 5, 9, 3]$ 为例,演示一下希尔排序的整个过程。 - -::: tabs#shellSort - -@tab <1> - -![希尔排序 1](https://qcdn.itcharge.cn/images/202308162132060.png) - -@tab <2> - -![希尔排序 2](https://qcdn.itcharge.cn/images/202308162132189.png) - -@tab <3> - -![希尔排序 3](https://qcdn.itcharge.cn/images/202308162132870.png) - -@tab <4> - -![希尔排序 4](https://qcdn.itcharge.cn/images/202308162132322.png) - -@tab <5> - -![希尔排序 5](https://qcdn.itcharge.cn/images/202308162132881.png) - -@tab <6> - -![希尔排序 6](https://qcdn.itcharge.cn/images/202308162132386.png) - -@tab <7> - -![希尔排序 7](https://qcdn.itcharge.cn/images/202308162132898.png) - -::: - -## 3. 希尔排序代码实现 - -```python -class Solution: - def shellSort(self, nums: [int]) -> [int]: - size = len(nums) - gap = size // 2 - # 按照 gap 分组 - while gap > 0: - # 对每组元素进行插入排序 - for i in range(gap, size): - # temp 为每组中无序数组第 1 个元素 - temp = nums[i] - j = i - # 从右至左遍历每组中的有序数组元素 - while j >= gap and nums[j - gap] > temp: - # 将每组有序数组中插入位置右侧的元素依次在组中右移一位 - nums[j] = nums[j - gap] - j -= gap - # 将该元素插入到适当位置 - nums[j] = temp - # 缩小 gap 间隔 - gap = gap // 2 - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.shellSort(nums) -``` - -## 4. 希尔排序算法分析 - -- **时间复杂度**:介于 $O(n \times \log^2 n)$ 与 $O(n^2)$ 之间。 - - 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,而比较次数与 $gap_i$ 之间的依赖关系比较复杂,不太容易给出完整的数学分析。 - - 本文采用 $gap_i = \lfloor gap_{i-1}/2 \rfloor$ 的方法缩小间隔数,对于具有 $n$ 个元素的数组,如果 $gap_1 = \lfloor n/2 \rfloor$,则经过 $p = \lfloor \log_2 n \rfloor$ 趟排序后就有 $gap_p = 1$,因此,希尔排序方法的排序总躺数为 $\lfloor \log_2 n \rfloor$。 - - 从算法中也可以看到,外层 `while gap > 0` 的循环次数为 $\log n$ 数量级,内层插入排序算法循环次数为 $n$ 数量级。当子数组分得越多时,子数组内的元素就越少,内层循环的次数也就越少;反之,当所分的子数组个数减少时,子数组内的元素也随之增多,但整个数组也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n \times \log^2 n)$ 与 $O(n^2)$ 之间。 - -- **空间复杂度**:$O(1)$。希尔排序中用到的插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量、间隔数 $gap$ 等常数项的变量。 -- **排序稳定性**:在一次插入排序是稳定的,不会改变相等元素的相对顺序,但是在不同的插入排序中,相等元素可能在各自的插入排序中移动。因此,希尔排序方法是一种 **不稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md b/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md deleted file mode 100644 index b9f989c1..00000000 --- a/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md +++ /dev/null @@ -1,76 +0,0 @@ -## 1. 归并排序算法思想 - -> **归并排序(Merge Sort)基本思想**: -> -> 采用经典的分治策略,先递归地将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。 - -## 2. 归并排序算法步骤 - -假设数组的元素个数为 $n$ 个,则归并排序的算法步骤如下: - -1. **分解过程**:先递归地将当前数组平均分成两半,直到子数组长度为 $1$。 - 1. 找到数组中心位置 $mid$,从中心位置将数组分成左右两个子数组 $left\underline{}nums$、$right\underline{}nums$。 - 2. 对左右两个子数组 $left\underline{}nums$、$right\underline{}nums$ 分别进行递归分解。 - 3. 最终将数组分解为 $n$ 个长度均为 $1$ 的有序子数组。 -2. **归并过程**:从长度为 $1$ 的有序子数组开始,依次将有序数组两两合并,直到合并成一个长度为 $n$ 的有序数组。 - 1. 使用数组变量 $nums$ 存放合并后的有序数组。 - 2. 使用两个指针 $left\underline{}i$、$right\underline{}i$ 分别指向两个有序子数组 $left\underline{}nums$、$right\underline{}nums$ 的开始位置。 - 3. 比较两个指针指向的元素,将两个有序子数组中较小元素依次存入到结果数组 $nums$ 中,并将指针移动到下一位置。 - 4. 重复步骤 $3$,直到某一指针到达子数组末尾。 - 5. 将另一个子数组中的剩余元素存入到结果数组 $nums$ 中。 - 6. 返回合并后的有序数组 $nums$。 - -我们以 $[0, 5, 7, 3, 1, 6, 8, 4]$ 为例,演示一下归并排序的整个过程。 - -![归并排序](http://qcdn.itcharge.cn/images/20230817103814.png) - -## 3. 归并排序代码实现 - -```python -class Solution: - # 合并过程 - def merge(self, left_nums: [int], right_nums: [int]): - nums = [] - left_i, right_i = 0, 0 - while left_i < len(left_nums) and right_i < len(right_nums): - # 将两个有序子数组中较小元素依次插入到结果数组中 - if left_nums[left_i] < right_nums[right_i]: - nums.append(left_nums[left_i]) - left_i += 1 - else: - nums.append(right_nums[right_i]) - right_i += 1 - - # 如果左子数组有剩余元素,则将其插入到结果数组中 - while left_i < len(left_nums): - nums.append(left_nums[left_i]) - left_i += 1 - - # 如果右子数组有剩余元素,则将其插入到结果数组中 - while right_i < len(right_nums): - nums.append(right_nums[right_i]) - right_i += 1 - - # 返回合并后的结果数组 - return nums - - # 分解过程 - def mergeSort(self, nums: [int]) -> [int]: - # 数组元素个数小于等于 1 时,直接返回原数组 - if len(nums) <= 1: - return nums - - mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 - left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 - right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 - return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 - - def sortArray(self, nums: [int]) -> [int]: - return self.mergeSort(nums) -``` - -## 4. 归并排序算法分析 - -- **时间复杂度**:$O(n \times \log n)$。归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度乘积。子算法 `merge(left_nums, right_nums):` 的时间复杂度是 $O(n)$,因此,归并排序算法总的时间复杂度为 $O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。归并排序方法需要用到与参加排序的数组同样大小的辅助空间。因此,算法的空间复杂度为 $O(n)$。 -- **排序稳定性**:因为在两个有序子数组的归并过程中,如果两个有序数组中出现相等元素,`merge(left_nums, right_nums):` 算法能够使前一个数组中那个相等元素先被复制,从而确保这两个元素的相对顺序不发生改变。因此,归并排序算法是一种 **稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md b/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md deleted file mode 100644 index 297f03fe..00000000 --- a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md +++ /dev/null @@ -1,139 +0,0 @@ -## 1. 快速排序算法思想 - -> **快速排序(Quick Sort)基本思想**: -> -> 采用经典的分治策略,选择数组中某个元素作为基准数,通过一趟排序将数组分为独立的两个子数组,一个子数组中所有元素值都比基准数小,另一个子数组中所有元素值都比基准数大。然后再按照同样的方式递归的对两个子数组分别进行快速排序,以达到整个数组有序。 -> - -## 2. 快速排序算法步骤 - -假设数组的元素个数为 $n$ 个,则快速排序的算法步骤如下: - -1. **哨兵划分**:选取一个基准数,将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。 - 1. 从当前数组中找到一个基准数 $pivot$(这里以当前数组第 $1$ 个元素作为基准数,即 $pivot = nums[low]$)。 - 2. 使用指针 $i$ 指向数组开始位置,指针 $j$ 指向数组末尾位置。 - 3. 从右向左移动指针 $j$,找到第 $1$ 个小于基准值的元素。 - 4. 从左向右移动指针 $i$,找到第 $1$ 个大于基准数的元素。 - 5. 交换指针 $i$、指针 $j$ 指向的两个元素位置。 - 6. 重复第 $3 \sim 5$ 步,直到指针 $i$ 和指针 $j$ 相遇时停止,最后将基准数放到两个子数组交界的位置上。 -2. **递归分解**:完成哨兵划分之后,对划分好的左右子数组分别进行递归排序。 - 1. 按照基准数的位置将数组拆分为左右两个子数组。 - 2. 对每个子数组分别重复「哨兵划分」和「递归分解」,直到各个子数组只有 $1$ 个元素,排序结束。 - -我们以 $[4, 7, 5, 2, 6, 1, 3]$ 为例,演示一下快速排序的整个步骤。 - -我们先来看一下单次「哨兵划分」的过程。 - -::: tabs#partition - -@tab <1> - -![哨兵划分 1](http://qcdn.itcharge.cn/images/20230818175908.png) - -@tab <2> - -![哨兵划分 2](http://qcdn.itcharge.cn/images/20230818175922.png) - -@tab <3> - -![哨兵划分 3](http://qcdn.itcharge.cn/images/20230818175952.png) - -@tab <4> - -![哨兵划分 4](http://qcdn.itcharge.cn/images/20230818180001.png) - -@tab <5> - -![哨兵划分 5](http://qcdn.itcharge.cn/images/20230818180009.png) - -@tab <6> - -![哨兵划分 6](http://qcdn.itcharge.cn/images/20230818180019.png) - -@tab <7> - -![哨兵划分 7](http://qcdn.itcharge.cn/images/20230818180027.png) - -::: - -在经过一次「哨兵划分」过程之后,数组就被划分为左子数组、基准数、右子树组三个独立部分。接下来只要对划分好的左右子数组分别进行递归排序即可完成排序。整个步骤如下: - -![快速排序](http://qcdn.itcharge.cn/images/20230818153642.png) - -## 3. 快速排序代码实现 - -```python -import random - -class Solution: - # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 - def randomPartition(self, nums: [int], low: int, high: int) -> int: - # 随机挑选一个基准数 - i = random.randint(low, high) - # 将基准数与最低位互换 - nums[i], nums[low] = nums[low], nums[i] - # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 - return self.partition(nums, low, high) - - # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 - def partition(self, nums: [int], low: int, high: int) -> int: - # 以第 1 位元素为基准数 - pivot = nums[low] - - i, j = low, high - while i < j: - # 从右向左找到第 1 个小于基准数的元素 - while i < j and nums[j] >= pivot: - j -= 1 - # 从左向右找到第 1 个大于基准数的元素 - while i < j and nums[i] <= pivot: - i += 1 - # 交换元素 - nums[i], nums[j] = nums[j], nums[i] - - # 将基准节点放到正确位置上 - nums[i], nums[low] = nums[low], nums[i] - # 返回基准数的索引 - return i - - def quickSort(self, nums: [int], low: int, high: int) -> [int]: - if low < high: - # 按照基准数的位置,将数组划分为左右两个子数组 - pivot_i = self.randomPartition(nums, low, high) - # 对左右两个子数组分别进行递归快速排序 - self.quickSort(nums, low, pivot_i - 1) - self.quickSort(nums, pivot_i + 1, high) - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.quickSort(nums, 0, len(nums) - 1) -``` - -## 4. 快速排序算法分析 - -快速排序算法的时间复杂度主要跟基准数的选择有关。本文中是将当前数组中第 $1$ 个元素作为基准值。 - -在这种选择下,如果参加排序的元素初始时已经有序的情况下,快速排序方法花费的时间最长。也就是会得到最坏时间复杂度。 - -在这种情况下,第 $1$ 趟排序经过 $n - 1$ 次比较以后,将第 $1$ 个元素仍然确定在原来的位置上,并得到 $1$ 个长度为 $n - 1$ 的子数组。第 $2$ 趟排序进过 $n - 2$ 次比较以后,将第 $2$ 个元素确定在它原来的位置上,又得到 $1$ 个长度为 $n - 2$ 的子数组。 - -最终总的比较次数为 $(n − 1) + (n − 2) + … + 1 = \frac{n(n − 1)}{2}$。因此这种情况下的时间复杂度为 $O(n^2)$,也是最坏时间复杂度。 - -我们可以改进一下基准数的选择。如果每次我们选中的基准数恰好能将当前数组平分为两份,也就是刚好取到当前数组的中位数。 - -在这种选择下,每一次都将数组从 $n$ 个元素变为 $\frac{n}{2}$ 个元素。此时的时间复杂度公式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n)$。根据主定理可以得出 $T(n) = O(n \times \log n)$,也是最佳时间复杂度。 - -而在平均情况下,我们可以从当前数组中随机选择一个元素作为基准数。这样,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log n)$,也就是平均时间复杂度。 - -下面来总结一下: - -- **最佳时间复杂度**:$O(n \times \log n)$。每一次选择的基准数都是当前数组的中位数,此时算法时间复杂度满足的递推式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n)$,由主定理可得 $T(n) = O(n \times \log n)$。 -- **最坏时间复杂度**:$O(n^2)$。每一次选择的基准数都是数组的最终位置上的值,此时算法时间复杂度满足的递推式为 $T(n) = T(n - 1) + \Theta(n)$,累加可得 $T(n) = O(n^2)$。 -- **平均时间复杂度**:$O(n \times \log n)$。在平均情况下,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序数组的首、尾位置。最坏的情况下,空间复杂度为 $O(n)$。如果对算法进行一些改写,在一趟排序之后比较被划分所得到的两个子数组的长度,并且首先对长度较短的子数组进行快速排序,这时候需要的空间复杂度可以达到 $O(log_2 n)$。 -- **排序稳定性**:在进行哨兵划分时,基准数可能会被交换至相等元素的右侧。因此,快速排序是一种 **不稳定排序算法**。 - -## 参考资料 - -- 【文章】[快速排序 - OI Wiki](https://oi-wiki.org/basic/quick-sort/) diff --git a/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md b/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md deleted file mode 100644 index 4cde705e..00000000 --- a/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md +++ /dev/null @@ -1,365 +0,0 @@ -## 1. 堆结构 - -「堆排序(Heap sort)」是一种基于「堆结构」实现的高效排序算法。在介绍「堆排序」之前,我们先来了解一下什么是「堆结构」。 - -### 1.1 堆的定义 - -> **堆(Heap)**:一种满足以下两个条件之一的完全二叉树: -> -> - **大顶堆(Max Heap)**:任意节点值 ≥ 其子节点值。 -> - **小顶堆(Min Heap)**:任意节点值 ≤ 其子节点值。 - -![堆结构](https://qcdn.itcharge.cn/images/20230823133321.png) - -### 1.2 堆的存储结构 - -堆的逻辑结构就是一颗完全二叉树。而我们在「07.树 - 01.二叉树 - 01.树与二叉树的基础知识」章节中学过,对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构(数组)的形式来表示完全二叉树,能够充分利用存储空间。 - -当我们使用顺序存储结构(即数组)来表示堆时,堆中元素的节点编号与数组的索引关系为: - -- 如果某二叉树节点(非叶子节点)的下标为 $i$,那么其左孩子节点下标为 $2 \times i + 1$,右孩子节点下标为 $2 \times i + 2$。 -- 如果某二叉树节点(非根结点)的下标为 $i$,那么其根节点下标为 $\lfloor \frac{i - 1}{2} \rfloor$(向下取整)。 - -```python -class MaxHeap: - def __init__(self): - self.max_heap = [] -``` - -![堆的存储结构](https://qcdn.itcharge.cn/images/20230824154601.png) - -### 1.3 访问堆顶元素 - -> **访问堆顶元素**:指的是从堆结构中获取位于堆顶的元素。 - -在堆中,堆顶元素位于根节点,当我们使用顺序存储结构(即数组)来表示堆时,堆顶元素就是数组的首个元素。 - -```python -class MaxHeap: - ...... - def peek(self) -> int: - # 大顶堆为空 - if not self.max_heap: - return None - # 返回堆顶元素 - return self.max_heap[0] -``` - -访问堆顶元素不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 - -### 1.4 向堆中插入元素 - -> **向堆中插入元素**:指的将一个新的元素添加到堆中,调整堆结构,以保持堆的特性不变。 - -向堆中插入元素的步骤如下: - -1. 将新元素添加到堆的末尾,保持完全二叉树的结构。 -2. 从新插入的元素节点开始,将该节点与其父节点进行比较。 - 1. 如果新节点的值大于其父节点的值,则交换它们,以保持最大堆的特性。 - 2. 如果新节点的值小于等于其父节点的值,说明已满足最大堆的特性,此时结束。 -3. 重复上述比较和交换步骤,直到新节点不再大于其父节点,或者达到了堆的根节点。 - -这个过程称为「上移调整(Shift Up)」。因为新插入的元素会逐步向堆的上方移动,直到找到了合适的位置,保持堆的有序性。 - -::: tabs#heapPush - -@tab <1> - -![向堆中插入元素1](https://qcdn.itcharge.cn/images/20230831111022.png) - -@tab <2> - -![向堆中插入元素2](https://qcdn.itcharge.cn/images/20230831111036.png) - -@tab <3> - -![向堆中插入元素3](https://qcdn.itcharge.cn/images/20230831111052.png) - -@tab <4> - -![向堆中插入元素4](https://qcdn.itcharge.cn/images/20230831111103.png) - -@tab <5> - -![向堆中插入元素5](https://qcdn.itcharge.cn/images/20230831112321.png) - -@tab <6> - -![向堆中插入元素6](https://qcdn.itcharge.cn/images/20230831112328.png) - -@tab <7> - -![向堆中插入元素7](https://qcdn.itcharge.cn/images/20230831134124.png) - -::: - -```python -class MaxHeap: - ...... - def push(self, val: int): - # 将新元素添加到堆的末尾 - self.max_heap.append(val) - - size = len(self.max_heap) - # 从新插入的元素节点开始,进行上移调整 - self.__shift_up(size - 1) - - def __shift_up(self, i: int): - while (i - 1) // 2 >= 0 and self.max_heap[i] > self.max_heap[(i - 1) // 2]: - self.max_heap[i], self.max_heap[(i - 1) // 2] = self.max_heap[(i - 1) // 2], self.max_heap[i] - i = (i - 1) // 2 -``` - -在最坏情况下,「向堆中插入元素」的时间复杂度为 $O(\log n)$,其中 $n$ 是堆中元素的数量,这是因为堆的高度是 $\log n$。 - -### 1.5 删除堆顶元素 - -> **删除堆顶元素**:指的是从堆中移除位于堆顶的元素,并重新调整对结果,以保持堆的特性不变。 - -删除堆顶元素的步骤如下: - -1. 将堆顶元素(即根节点)与堆的末尾元素交换。 -2. 移除堆末尾的元素(之前的堆顶),即将其从堆中剔除。 -3. 从新的堆顶元素开始,将其与其较大的子节点进行比较。 - 1. 如果当前节点的值小于其较大的子节点,则将它们交换。这一步是为了将新的堆顶元素「下沉」到适当的位置,以保持最大堆的特性。 - 2. 如果当前节点的值大于等于其较大的子节点,说明已满足最大堆的特性,此时结束。 -4. 重复上述比较和交换步骤,直到新的堆顶元素不再小于其子节点,或者达到了堆的底部。 - -这个过程称为「下移调整(Shift Down)」。因为新的堆顶元素会逐步向堆的下方移动,直到找到了合适的位置,保持堆的有序性。 - -::: tabs#heapPop - -@tab <1> - -![删除堆顶元素 1](https://qcdn.itcharge.cn/images/20230831134148.png) - -@tab <2> - -![删除堆顶元素 2](https://qcdn.itcharge.cn/images/20230831134156.png) - -@tab <3> - -![删除堆顶元素 3](https://qcdn.itcharge.cn/images/20230831134205.png) - -@tab <4> - -![删除堆顶元素 4](https://qcdn.itcharge.cn/images/20230831134214.png) - -@tab <5> - -![删除堆顶元素 5](https://qcdn.itcharge.cn/images/20230831134221.png) - -@tab <6> - -![删除堆顶元素 6](https://qcdn.itcharge.cn/images/20230831134229.png) - -@tab <7> - -![删除堆顶元素 7](https://qcdn.itcharge.cn/images/20230831134237.png) - -::: - -```python -class MaxHeap: - ...... - def pop(self) -> int: - # 堆为空 - if not self.max_heap: - raise IndexError("堆为空") - - size = len(self.max_heap) - self.max_heap[0], self.max_heap[size - 1] = self.max_heap[size - 1], self.max_heap[0] - # 删除堆顶元素 - val = self.max_heap.pop() - # 节点数减 1 - size -= 1 - - # 下移调整 - self.__shift_down(0, size) - - # 返回堆顶元素 - return val - - - def __shift_down(self, i: int, n: int): - while 2 * i + 1 < n: - # 左右子节点编号 - left, right = 2 * i + 1, 2 * i + 2 - - # 找出左右子节点中的较大值节点编号 - if 2 * i + 2 >= n: - # 右子节点编号超出范围(只有左子节点 - larger = left - else: - # 左子节点、右子节点都存在 - if self.max_heap[left] >= self.max_heap[right]: - larger = left - else: - larger = right - - # 将当前节点值与其较大的子节点进行比较 - if self.max_heap[i] < self.max_heap[larger]: - # 如果当前节点值小于其较大的子节点,则将它们交换 - self.max_heap[i], self.max_heap[larger] = self.max_heap[larger], self.max_heap[i] - i = larger - else: - # 如果当前节点值大于等于于其较大的子节点,此时结束 - break -``` - -「删除堆顶元素」的时间复杂度通常为$O(\log n)$,其中 $n$ 是堆中元素的数量,因为堆的高度是 $\log n$。 - -## 2. 堆排序 - -### 2.1 堆排序算法思想 - -> **堆排序(Heap sort)基本思想**: -> -> 借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 - -### 2.2 堆排序算法步骤 - -1. **构建初始大顶堆**: - 1. 定义一个数组实现的堆结构,将原始数组的元素依次存入堆结构的数组中(初始顺序不变)。 - 2. 从数组的中间位置开始,从右至左,依次通过「下移调整」将数组转换为一个大顶堆。 - -2. **交换元素,调整堆**: - 1. 交换堆顶元素(第 $1$ 个元素)与末尾(最后 $1$ 个元素)的位置,交换完成后,堆的长度减 $1$。 - 2. 交换元素之后,由于堆顶元素发生了改变,需要从根节点开始,对当前堆进行「下移调整」,使其保持堆的特性。 - -3. **重复交换和调整堆**: - 1. 重复第 $2$ 步,直到堆的大小为 $1$ 时,此时大顶堆的数组已经完全有序。 - -::: tabs#heapSortBuildMaxHeap - -@tab <1> - -![1. 构建初始大顶堆 1](https://qcdn.itcharge.cn/images/20230831151620.png) - -@tab <2> - -![1. 构建初始大顶堆 2](https://qcdn.itcharge.cn/images/20230831151641.png) - -@tab <3> - -![1. 构建初始大顶堆 3](https://qcdn.itcharge.cn/images/20230831151703.png) - -@tab <4> - -![1. 构建初始大顶堆 4](https://qcdn.itcharge.cn/images/20230831151715.png) - -@tab <5> - -![1. 构建初始大顶堆 5](https://qcdn.itcharge.cn/images/20230831151725.png) - -@tab <6> - -![1. 构建初始大顶堆 6](https://qcdn.itcharge.cn/images/20230831151735.png) - -@tab <7> - -![1. 构建初始大顶堆 7](https://qcdn.itcharge.cn/images/20230831151749.png) - -::: - -::: tabs#heapSortExchangeVal - -@tab <1> - -![2. 交换元素,调整堆 1](https://qcdn.itcharge.cn/images/20230831162335.png) - -@tab <2> - -![2. 交换元素,调整堆 2](https://qcdn.itcharge.cn/images/20230831162346.png) - -@tab <3> - -![2. 交换元素,调整堆 3](https://qcdn.itcharge.cn/images/20230831162359.png) - -@tab <4> - -![2. 交换元素,调整堆 4](https://qcdn.itcharge.cn/images/20230831162408.png) - -@tab <5> - -![2. 交换元素,调整堆 5](https://qcdn.itcharge.cn/images/20230831162416.png) - -@tab <6> - -![2. 交换元素,调整堆 6](https://qcdn.itcharge.cn/images/20230831162424.png) - -@tab <7> - -![2. 交换元素,调整堆 7](https://qcdn.itcharge.cn/images/20230831162431.png) - -@tab <8> - -![2. 交换元素,调整堆 8](https://qcdn.itcharge.cn/images/20230831162440.png) - -@tab <9> - -![2. 交换元素,调整堆 9](https://qcdn.itcharge.cn/images/20230831162449.png) - -@tab <10> - -![2. 交换元素,调整堆 10](https://qcdn.itcharge.cn/images/20230831162457.png) - -@tab <11> - -![https://qcdn.](https://qcdn.itcharge.cn/images/20230831162505.png) - -@tab <12> - -![2. 交换元素,调整堆 12](https://qcdn.itcharge.cn/images/20230831162512.png) - -::: - -### 2.3 堆排序代码实现 - -```python -class MaxHeap: - ...... - def __buildMaxHeap(self, nums: [int]): - size = len(nums) - # 先将数组 nums 的元素按顺序添加到 max_heap 中 - for i in range(size): - self.max_heap.append(nums[i]) - - # 从最后一个非叶子节点开始,进行下移调整 - for i in range((size - 2) // 2, -1, -1): - self.__shift_down(i, size) - - def maxHeapSort(self, nums: [int]) -> [int]: - # 根据数组 nums 建立初始堆 - self.__buildMaxHeap(nums) - - size = len(self.max_heap) - for i in range(size - 1, -1, -1): - # 交换根节点与当前堆的最后一个节点 - self.max_heap[0], self.max_heap[i] = self.max_heap[i], self.max_heap[0] - # 从根节点开始,对当前堆进行下移调整 - self.__shift_down(0, i) - - # 返回排序后的数组 - return self.max_heap - -class Solution: - def maxHeapSort(self, nums: [int]) -> [int]: - return MaxHeap().maxHeapSort(nums) - - def sortArray(self, nums: [int]) -> [int]: - return self.maxHeapSort(nums) - -print(Solution().sortArray([10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14])) -``` - -### 2.4 堆排序算法分析 - -- **时间复杂度**:$O(n \times \log n)$。 - - 堆积排序的时间主要花费在两个方面:「建立初始堆」和「下移调整」。 - - 设原始数组所对应的完全二叉树深度为 $d$,算法由两个独立的循环组成: - 1. 在第 $1$ 个循环构造初始堆积时,从 $i = d - 1$ 层开始,到 $i = 1$ 层为止,对每个分支节点都要调用一次调整堆算法,而一次调整堆算法,对于第 $i$ 层一个节点到第 $d$ 层上建立的子堆积,所有节点可能移动的最大距离为该子堆积根节点移动到最后一层(第 $d$ 层) 的距离,即 $d - i$。而第 $i$ 层上节点最多有 $2^{i-1}$ 个,所以每一次调用调整堆算法的最大移动距离为 $2^{i-1} * (d-i)$。因此,堆积排序算法的第 $1$ 个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和,即:$\sum_{i = d - 1}^1 2^{i-1} (d-i) = \sum_{j = 1}^{d-1} 2^{d-j-1} \times j = \sum_{j = 1}^{d-1} 2^{d-1} \times {j \over 2^j} \le n \times \sum_{j = 1}^{d-1} {j \over 2^j} < 2 \times n$。这一部分的时间花费为 $O(n)$。 - 2. 在第 $2$ 个循环中,每次调用调整堆算法一次,节点移动的最大距离为这棵完全二叉树的深度 $d = \lfloor \log_2(n) \rfloor + 1$,一共调用了 $n - 1$ 次调整堆算法,所以,第 $2$ 个循环的时间花费为 $(n-1)(\lfloor \log_2 (n)\rfloor + 1) = O(n \times \log n)$。 - - 因此,堆积排序的时间复杂度为 $O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。由于在堆积排序中只需要一个记录大小的辅助空间,因此,堆积排序的空间复杂度为:$O(1)$。 -- **排序稳定性**:在进行「下移调整」时,相等元素的相对位置可能会发生变化。因此,堆排序是一种 **不稳定排序算法**。 diff --git a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md b/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md deleted file mode 100644 index 29c05587..00000000 --- a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md +++ /dev/null @@ -1,63 +0,0 @@ -## 1. 计数排序算法思想 - -> **计数排序(Counting Sort)基本思想**: -> -> 通过统计数组中每个元素在数组中出现的次数,根据这些统计信息将数组元素有序的放置到正确位置,从而达到排序的目的。 - -## 2. 计数排序算法步骤 - -1. **计算排序范围**:遍历数组,找出待排序序列中最大值元素 $nums\underline{}max$ 和最小值元素 $nums\underline{}min$,计算出排序范围为 $nums\underline{}max - nums\underline{}min + 1$。 -2. **定义计数数组**:定义一个大小为排序范围的计数数组 $counts$,用于统计每个元素的出现次数。其中: - 1. 数组的索引值 $num - nums\underline{}min$ 表示元素的值为 $num$。 - 2. 数组的值 $counts[num - nums\underline{}min]$ 表示元素 $num$ 的出现次数。 - -3. **对数组元素进行计数统计**:遍历待排序数组 $nums$,对每个元素在计数数组中进行计数,即将待排序数组中「每个元素值减去最小值」作为索引,将「对计数数组中的值」加 $1$,即令 $counts[num - nums\underline{}min]$ 加 $1$。 -4. **生成累积计数数组**:从 $counts$ 中的第 $1$ 个元素开始,每一项累家前一项和。此时 $counts[num - nums\underline{}min]$ 表示值为 $num$ 的元素在排序数组中最后一次出现的位置。 -5. **逆序填充目标数组**:逆序遍历数组 $nums$,将每个元素 $num$ 填入正确位置。 - 1. 将其填充到结果数组 $res$ 的索引 $counts[num - nums\underline{}min]$ 处。 - 2. 放入后,令累积计数数组中对应索引减 $1$,从而得到下个元素 $num$ 的放置位置。 - -我们以 $[3, 0, 4, 2, 5, 1, 3, 1, 4, 5]$ 为例,演示一下计数排序的整个步骤。 - -![计数排序](https://qcdn.itcharge.cn/images/20230822135634.png) - -## 3. 计数排序代码实现 - -```python -class Solution: - def countingSort(self, nums: [int]) -> [int]: - # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min - nums_min, nums_max = min(nums), max(nums) - # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 - size = nums_max - nums_min + 1 - counts = [0 for _ in range(size)] - - # 统计值为 num 的元素出现的次数 - for num in nums: - counts[num - nums_min] += 1 - - # 生成累积计数数组 - for i in range(1, size): - counts[i] += counts[i - 1] - - # 反向填充目标数组 - res = [0 for _ in range(len(nums))] - for i in range(len(nums) - 1, -1, -1): - num = nums[i] - # 根据累积计数数组,将 num 放在数组对应位置 - res[counts[num - nums_min] - 1] = num - # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 - counts[nums[i] - nums_min] -= 1 - - return res - - def sortArray(self, nums: [int]) -> [int]: - return self.countingSort(nums) -``` - -## 4. 计数排序算法分析 - -- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序数组的值域。 -- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。由于用于计数的数组 $counts$ 的长度取决于待排序数组中数据的范围(大小等于待排序数组最大值减去最小值再加 $1$)。所以计数排序算法对于数据范围很大的数组,需要大量的内存。 -- **计数排序适用情况**:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。 -- **排序稳定性**:由于向结果数组中填充元素时使用的是逆序遍历,可以避免改变相等元素之间的相对顺序。因此,计数排序是一种 **稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md b/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md deleted file mode 100644 index 7e42d6f7..00000000 --- a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md +++ /dev/null @@ -1,66 +0,0 @@ -## 1. 桶排序算法思想 - -> **桶排序(Bucket Sort)基本思想**: -> -> 将待排序数组中的元素分散到若干个「桶」中,然后对每个桶中的元素再进行单独排序。 - -## 2. 桶排序算法步骤 - -1. **确定桶的数量**:根据待排序数组的值域范围,将数组划分为 $k$ 个桶,每个桶可以看做是一个范围区间。 -2. **分配元素**:遍历待排序数组元素,将每个元素根据大小分配到对应的桶中。 -3. **对每个桶进行排序**:对每个非空桶内的元素单独排序(使用插入排序、归并排序、快排排序等算法)。 -4. **合并桶内元素**:将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。 - -我们以 $[39, 49, 8, 13, 22, 15, 10, 30, 5, 44]$ 为例,演示一下桶排序的整个步骤。 - -![桶排序](http://qcdn.itcharge.cn/images/20230822153701.png) - -## 3. 桶排序代码实现 - -```python -class Solution: - def insertionSort(self, nums: [int]) -> [int]: - # 遍历无序区间 - for i in range(1, len(nums)): - temp = nums[i] - j = i - # 从右至左遍历有序区间 - while j > 0 and nums[j - 1] > temp: - # 将有序区间中插入位置右侧的元素依次右移一位 - nums[j] = nums[j - 1] - j -= 1 - # 将该元素插入到适当位置 - nums[j] = temp - - return nums - - def bucketSort(self, nums: [int], bucket_size=5) -> [int]: - # 计算待排序序列中最大值元素 nums_max、最小值元素 nums_min - nums_min, nums_max = min(nums), max(nums) - # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 - bucket_count = (nums_max - nums_min) // bucket_size + 1 - # 定义桶数组 buckets - buckets = [[] for _ in range(bucket_count)] - - # 遍历待排序数组元素,将每个元素根据大小分配到对应的桶中 - for num in nums: - buckets[(num - nums_min) // bucket_size].append(num) - - # 对每个非空桶内的元素单独排序,排序之后,按照区间顺序依次合并到 res 数组中 - res = [] - for bucket in buckets: - self.insertionSort(bucket) - res.extend(bucket) - - # 返回结果数组 - return res - - def sortArray(self, nums: [int]) -> [int]: - return self.bucketSort(nums) -``` - -## 4. 桶排序算法分析 - -- **时间复杂度**:$O(n)$。当输入元素个数为 $n$,桶的个数是 $m$ 时,每个桶里的数据就是 $k = \frac{n}{m}$ 个。每个桶内排序的时间复杂度为 $O(k \times \log k)$。$m$ 个桶就是 $m \times O(k \times \log k) = m \times O(\frac{n}{m} \times \log \frac{n}{m}) = O(n \times \log \frac{n}{m})$。当桶的个数 $m$ 接近于数据个数 $n$ 时,$\log \frac{n}{m}$ 就是一个较小的常数,所以排序桶排序时间复杂度接近于 $O(n)$。 -- **空间复杂度**:$O(n + m)$。由于桶排序使用了辅助空间,所以桶排序的空间复杂度是 $O(n + m)$。 -- **排序稳定性**:桶排序的稳定性取决于桶内使用的排序算法。如果桶内使用稳定的排序算法(比如插入排序算法),并且在合并桶的过程中保持相等元素的相对顺序不变,则桶排序是一种 **稳定排序算法**。反之,则桶排序是一种 **不稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md deleted file mode 100644 index e19f8651..00000000 --- a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md +++ /dev/null @@ -1,57 +0,0 @@ -## 1. 基数排序算法思想 - -> **基数排序(Radix Sort)基本思想**: -> -> 将整数按位数切割成不同的数字,然后从低位开始,依次到高位,逐位进行排序,从而达到排序的目的。 - -## 2. 基数排序算法步骤 - -基数排序算法可以采用「最低位优先法(Least Significant Digit First)」或者「最高位优先法(Most Significant Digit first)」。最常用的是「最低位优先法」。 - -下面我们以最低位优先法为例,讲解一下算法步骤。 - -1. **确定排序的最大位数**:遍历数组元素,获取数组最大值元素,并取得对应位数。 -2. **从最低位(个位)开始,到最高位为止,逐位对每一位进行排序**: - 1. 定义一个长度为 $10$ 的桶数组 $buckets$,每个桶分别代表 $0 \sim 9$ 中的 $1$ 个数字。 - 2. 按照每个元素当前位上的数字,将元素放入对应数字的桶中。 - 3. 清空原始数组,然后按照桶的顺序依次取出对应元素,重新加入到原始数组中。 - - -我们以 $[692, 924, 969, 503, 871, 704, 542, 436]$ 为例,演示一下基数排序的整个步骤。 - -![基数排序](http://qcdn.itcharge.cn/images/20230822171758.png) - -## 3. 基数排序代码实现 - -```python -class Solution: - def radixSort(self, nums: [int]) -> [int]: - # 桶的大小为所有元素的最大位数 - size = len(str(max(nums))) - - # 从最低位(个位)开始,逐位遍历每一位 - for i in range(size): - # 定义长度为 10 的桶数组 buckets,每个桶分别代表 0 ~ 9 中的 1 个数字。 - buckets = [[] for _ in range(10)] - # 遍历数组元素,按照每个元素当前位上的数字,将元素放入对应数字的桶中。 - for num in nums: - buckets[num // (10 ** i) % 10].append(num) - # 清空原始数组 - nums.clear() - # 按照桶的顺序依次取出对应元素,重新加入到原始数组中。 - for bucket in buckets: - for num in bucket: - nums.append(num) - - # 完成排序,返回结果数组 - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.radixSort(nums) -``` - -## 4. 基数排序算法分析 - -- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 -- **空间复杂度**:$O(n + k)$。 -- **排序稳定性**:基数排序采用的桶排序是稳定的。基数排序是一种 **稳定排序算法**。 \ No newline at end of file diff --git a/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md b/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md deleted file mode 100644 index c8b4dbcd..00000000 --- a/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md +++ /dev/null @@ -1,85 +0,0 @@ -### 排序算法题目 - -#### 冒泡排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | - -#### 选择排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | - -#### 插入排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | - -#### 希尔排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0506 | [相对名次](https://leetcode.cn/problems/relative-ranks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0506.%20%E7%9B%B8%E5%AF%B9%E5%90%8D%E6%AC%A1.md) | 数组、排序、堆(优先队列) | 简单 | - -#### 归并排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 剑指 Offer 51 | [数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2051.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | - -#### 快速排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | - -#### 堆排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0215 | [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0215.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | -| 剑指 Offer 40 | [最小的k个数](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2040.%20%E6%9C%80%E5%B0%8F%E7%9A%84k%E4%B8%AA%E6%95%B0.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | - -#### 计数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 1122 | [数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1122.%20%E6%95%B0%E7%BB%84%E7%9A%84%E7%9B%B8%E5%AF%B9%E6%8E%92%E5%BA%8F.md) | 数组、哈希表、计数排序、排序 | 简单 | - -#### 桶排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0912 | [排序数组](https://leetcode.cn/problems/sort-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0912.%20%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | - -#### 基数排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0164 | [最大间距](https://leetcode.cn/problems/maximum-gap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0164.%20%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D.md) | 数组、桶排序、基数排序、排序 | 困难 | -| 0561 | [数组拆分](https://leetcode.cn/problems/array-partition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md) | 贪心、数组、计数排序、排序 | 简单 | - -#### 其他排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0217 | [存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 数组、哈希表、排序 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0179 | [最大数](https://leetcode.cn/problems/largest-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0179.%20%E6%9C%80%E5%A4%A7%E6%95%B0.md) | 贪心、数组、字符串、排序 | 中等 | -| 0384 | [打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0384.%20%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84.md) | 数组、数学、随机化 | 中等 | -| 剑指 Offer 45 | [把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2045.%20%E6%8A%8A%E6%95%B0%E7%BB%84%E6%8E%92%E6%88%90%E6%9C%80%E5%B0%8F%E7%9A%84%E6%95%B0.md) | 贪心、字符串、排序 | 中等 | - diff --git a/Contents/01.Array/02.Array-Sort/index.md b/Contents/01.Array/02.Array-Sort/index.md deleted file mode 100644 index 5042bb9a..00000000 --- a/Contents/01.Array/02.Array-Sort/index.md +++ /dev/null @@ -1,13 +0,0 @@ -## 本章内容 - -- [冒泡排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md) -- [选择排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md) -- [插入排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md) -- [希尔排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md) -- [归并排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md) -- [快速排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md) -- [堆排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md) -- [计数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md) -- [桶排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md) -- [基数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md) -- [数组排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) \ No newline at end of file diff --git a/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md b/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md deleted file mode 100644 index 31430917..00000000 --- a/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md +++ /dev/null @@ -1,148 +0,0 @@ -## 1. 二分查找算法介绍 - -### 1.1 二分查找算法简介 - -> **二分查找算法(Binary Search Algorithm)**:也叫做折半查找算法、对数查找算法,是一种用于在有序数组中查找特定元素的高效搜索算法。 - -二分查找的基本算法思想为:通过确定目标元素所在的区间范围,反复将查找范围减半,直到找到元素或找不到该元素为止。 - -### 1.2 二分查找算法步骤 - -以下是二分查找算法的基本步骤: - -1. **初始化**:首先,确定要查找的有序数据集合。可以是一个数组或列表,确保其中的元素按照升序或者降序排列。 -2. **确定查找范围**:将整个有序数组集合的查找范围确定为整个数组范围区间,即左边界 $left$ 和右边界 $right$。 -3. **计算中间元素**:根据 $mid = \lfloor (left + right) / 2 \rfloor$ 计算出中间元素下标位置 $mid$。 -4. **比较中间元素**:将目标元素 $target$ 与中间元素 $nums[mid]$ 进行比较: - 1. 如果 $target == nums[mid]$,说明找到 $target$,因此返回中间元素的下标位置 $mid$。 - 2. 如果 $target < nums[mid]$,说明目标元素在左半部分($[left, mid - 1]$),更新右边界为中间元素的前一个位置,即 $right = mid - 1$。 - 3. 如果 $target > nums[mid]$,说明目标元素在右半部分($[mid + 1, right]$),更新左边界为中间元素的后一个位置,即 $left = mid + 1$。 - -5. 重复步骤 $3 \sim 4$,直到找到目标元素时返回中间元素下标位置,或者查找范围缩小为空(左边界大于右边界),表示目标元素不存在,此时返回 $-1$。 - -举个例子来说,以在有序数组 $[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]$ 中查找目标元素 $6$ 来说,使用二分查找算法的步骤如下: - -1. **确定查找范围**:初始时左边界 $left$ 为 $0$(数组的起始位置),$right$ 为 $10$(数组的末尾位置)。此时查找范围为 $[0, 10]$。 -2. **计算中间元素**:中间元素下标位置为 $5$,对应元素为 $nums[5] == 5$。 -3. **比较中间元素**:因为 $6 > nums[5]$,所以目标元素可能在右半部分,更新左边界为中间元素的后一个位置,即 $left = 5$。此时查找范围为 $[5, 10]$。 -4. **计算中间元素**:中间元素下标位置为 $7$,对应元素为 $nums[7] == 7$。 -5. **比较中间元素**:因为 $6 < nums[7]$,所以目标元素可能在左半部分,更新右边界为中间元素的前一个位置,即 $right = 6$。此时查找范围为 $[5, 6]$。 -6. **计算中间元素**:中间元素下标位置为 $5$,对应元素为 $nums[5] == 5$。 -7. **比较中间元素**:因为 $5 == nums[5]$,正好是我们正在查找的目标元素,此时返回中间元素的下标位置,算法结束。 - -于是我们发现,对于一个长度为 $10$ 的有序数组,我们只进行了 $3$ 次查找就找到了目标元素。而如果是按照顺序依次遍历数组,则在最坏情况下,我们可能需要查找 $10$ 次才能找到目标元素。 - -::: tabs#BinarySearch - -@tab <1> - -![二分查找算法 1](https://qcdn.itcharge.cn/images/20230907144632.png) - -@tab <2> - -![二分查找算法 2](https://qcdn.itcharge.cn/images/20230906133742.png) - -@tab <3> - -![二分查找算法 3](https://qcdn.itcharge.cn/images/20230906133758.png) - -@tab <4> - -![二分查找算法 4](https://qcdn.itcharge.cn/images/20230906133809.png) - -@tab <5> - -![二分查找算法 5](https://qcdn.itcharge.cn/images/20230906133820.png) - -@tab <6> - -![二分查找算法 6](https://qcdn.itcharge.cn/images/20230906133830.png) - -@tab <7> - -![二分查找算法 7](https://qcdn.itcharge.cn/images/20230906133839.png) - -@tab <8> - -![二分查找算法 8](https://qcdn.itcharge.cn/images/20230906133848.png) - -::: - -### 1.2 二分查找算法思想 - -二分查找算法是经典的 **「减而治之」** 的思想。 - -这里的 **「减」** 是减少问题规模的意思,**「治」** 是解决问题的意思。**「减」** 和 **「治」** 结合起来的意思就是 **「排除法解决问题」**。即:**每一次查找,排除掉一定不存在目标元素的区间,在剩下可能存在目标元素的区间中继续查找。** - -每一次通过一些条件判断,将待搜索的区间逐渐缩小,以达到「减少问题规模」的目的。而于问题的规模是有限的,经过有限次的查找,最终会查找到目标元素或者查找失败。 - -## 2. 简单二分查找 - -下面通过一个简单的例子来讲解下二分查找的思路和代码。 - -- 题目链接:[704. 二分查找](https://leetcode.cn/problems/binary-search/) - -### 2.1 题目大意 - -**描述**:给定一个升序的数组 $nums$,和一个目标值 $target$。 - -**要求**:返回 $target$ 在数组中的位置,如果找不到,则返回 $-1$。 - -**说明**: - -- 你可以假设 $nums$ 中的所有元素是不重复的。 -- $n$ 将在 $[1, 10000]$ 之间。 -- $nums$ 的每个元素都将在 $[-9999, 9999]$之间。 - -**示例**: - -```python -输入: nums = [-1,0,3,5,9,12], target = 9 -输出: 4 -解释: 9 出现在 nums 中并且下标为 4 - - -输入: nums = [-1,0,3,5,9,12], target = 2 -输出: -1 -解释: 2 不存在 nums 中因此返回 -1 -``` - -### 2.2 解题思路 - -#### 思路 1:二分查找 - -1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 -2. 取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 - 1. 如果 $target == nums[mid]$,则返回中心位置。 - 2. 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 - 3. 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 -3. 如果左边界大于右边界,查找范围缩小为空,说明目标元素不存在,此时返回 $-1$。 - -#### 思路 1:代码 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - # 在区间 [left, right] 内查找 target - while left <= right: - # 取区间中间节点 - mid = (left + right) // 2 - # 如果找到目标值,则直接返回中心位置 - if nums[mid] == target: - return mid - # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 - elif nums[mid] < target: - left = mid + 1 - # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 - else: - right = mid - 1 - # 未搜索到元素,返回 -1 - return -1 -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 diff --git a/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md b/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md deleted file mode 100644 index 35dbd9b8..00000000 --- a/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md +++ /dev/null @@ -1,242 +0,0 @@ -## 3. 二分查找细节 - -从上篇文章的例子中我们了解了二分查找的思路和具体代码。但是真正在解决二分查找题目的时候还需要考虑更多细节。比如说以下几个问题: - -1. **区间的开闭问题**:区间应该是左闭右闭区间 $[left, right]$,还是左闭右开区间 $[left, right)$? -2. **$mid$ 的取值问题**:$mid = \lfloor \frac{left + right}{2} \rfloor$,还是 $mid = \lfloor \frac{left + right + 1}{2} \rfloor$? -3. **出界条件的判断**:$left \le right$,还是 $left < right$? -4. **搜索区间范围的选择**:$left = mid + 1$、$right = mid - 1$、 $left = mid$、$right = mid$ 应该怎么写? - -下面依次进行讲解。 - -### 3.1 区间的开闭问题 - -左闭右闭区间、左闭右开区间指的是初始待查找区间的范围。 - -- **左闭右闭区间**:初始化时,$left = 0$,$right = len(nums) - 1$。 - - $left$ 为数组第一个元素位置,$right$ 为数组最后一个元素位置。 - - 区间 $[left, right]$ 左右边界上的点都能取到。 - -- **左闭右开区间**:初始化时,$left = 0$,$right = len(nums)$。 - - $left$ 为数组第一个元素位置,$right$ 为数组最后一个元素的下一个位置。 - - 区间 $[left, right)$ 左边界点能取到,而右边界上的点不能取到。 - - -关于二分查找算法的左闭右闭区间、左闭右开区间,其实在网上都有对应的代码。但是相对来说,左闭右开区间这种写法在解决问题的过程中,会使得问题变得复杂,需要考虑的情况更多,所以不建议使用左闭右开区间这种写法,而是建议:**全部使用「左闭右闭区间」这种写法**。 - -### 3.2 $mid$ 的取值问题 - -在二分查找的实际问题中,最常见的 $mid$ 取值公式有两个: - -1. `mid = (left + right) // 2`。 -2. `mid = (left + right + 1) // 2 `。 - -式子中 `//` 所代表的含义是「中间数向下取整」。当待查找区间中的元素个数为奇数个,使用这两种取值公式都能取到中间元素的下标位置。 - -而当待查找区间中的元素个数为偶数时,使用 `mid = (left + right) // 2` 式子我们能取到中间靠左边元素的下标位置,使用 `mid = (left + right + 1) // 2` 式子我们能取到中间靠右边元素的下标位置。 - -::: tabs#mid - -@tab <1> - -![mid 取值问题 1](https://qcdn.itcharge.cn/images/20230906153359.png) - -@tab <2> - -![mid 取值问题 2](https://qcdn.itcharge.cn/images/20230906153409.png) - -::: - -把这两个公式分别代入到 [704. 二分查找](https://leetcode.cn/problems/binary-search/) 的代码中试一试,发现都能通过题目评测。这是为什么呢? - -因为二分查找算法的思路是:根据每次选择中间位置上的数值来决定下一次在哪个区间查找元素。每一次选择的元素位置可以是中间位置,但并不是一定非得是区间中间位置元素,靠左一些、靠右一些、甚至区间三分之一、五分之一处等等,都是可以的。比如说 `mid = (left + right) * 1 // 5` 也是可以的。 - -但一般来说,取区间中间位置在平均意义下所达到的效果最好。同时这样写最简单。而对于这两个取值公式,大多数时候是选择第一个公式。不过,有些情况下,是需要考虑第二个公式的,我们会在「4.2 排除法」中进行讲解。 - -除了上面提到的这两种写法,我们还经常能看到下面两个公式: - -1. `mid = left + (right - left) // 2`。 -2. `mid = left + (right - left + 1) // 2`。 - -这两个公式其实分别等同于之前两个公式,可以看做是之前两个公式的另一种写法。这种写法能够防止整型溢出问题(Python 语言中整型不会溢出,其他语言可能会有整型溢出问题)。 - -在 $left + right$ 的数据量不会超过整型变量最大值时,这两种写法都没有问题。在 $left + right$ 的数据量可能会超过整型变量最大值时,最好使用第二种写法。所以,为了统一和简化二分查找算法的写法,建议统一写成第二种写法: - -1. `mid = left + (right - left) // 2`。 -2. `mid = left + (right - left + 1) // 2`。 - -### 3.3 出界条件的判断 - -二分查找算法的写法中,`while` 语句出界判断条件通常有两种: - -1. `left <= right`。 -2. `left < right`。 - -我们究竟应该使用哪一种写法呢? - -我们先来判断一下导致 `while` 语句出界的条件是什么。 - -1. 如果判断语句为 `left <= right`,并且查找的元素不在有序数组中,则 `while` 语句的出界条件是 `left > right`,也就是 `left == right + 1`,写成区间形式就是 $[right + 1, right]$,此时待查找区间为空,待查找区间中没有元素存在,此时终止循环时,可以直接返回 $-1$。 - - 比如说区间 $[3, 2]$, 此时左边界大于右边界,直接终止循环,返回 $-1$ 即可。 -2. 如果判断语句为`left < right`,并且查找的元素不在有序数组中,则 `while` 语句出界条件是 `left == right`,写成区间形式就是 $[right, right]$。此时区间不为空,待查找区间还有一个元素存在,我们并不能确定查找的元素不在这个区间中,此时终止循环时,如果直接返回 $-1$ 就是错误的。 - - 比如说区间 $[2, 2]$,如果元素 $nums[2]$ 刚好就是目标元素 $target$,此时终止循环,返回 $-1$ 就漏掉了这个元素。 - -但是如果我们还是想要使用 `left < right` 的话,怎么办? - -可以在出界之后增加一层判断,判断 $left$ 所指向位置是否等于目标元素,如果是的话就返回 $left$,如果不是的话返回 $-1$。即: - -```python -# ... - while left < right: - # ... - return left if nums[left] == target else -1 -``` - -此外,`while` 判断语句用 `left < right` 有一个好处,就是在跳出循环的时候,一定是 `left == right`,我们就不用判断此时应该返回 $left$ 还是 $right$ 了。 - -### 3.4 搜索区间范围的选择 - -在进行区间范围选择的时候,通常有三种写法: - -1. `left = mid + 1`,`right = mid - 1`。 -2. `left = mid + 1 `,`right = mid`。 -3. `left = mid`,`right = mid - 1`。 - -我们到底应该如何确定搜索区间范围呢? - -这是二分查找的一个难点,写错了很容易造成死循环,或者得不到正确结果。 - -这其实跟二分查找算法的两种不同思路和三种写法有关。 - -- 思路 1:「直接法」—— 在循环体中找到元素后直接返回结果。 -- 思路 2:「排除法」—— 在循环体中排除目标元素一定不存在区间。 - -接下来我们具体讲解下这两种思路。 - -## 4. 二分查找两种思路 - -### 4.1 直接法 - -> **直接法思想**:一旦我们在循环体中找到元素就直接返回结果。 - -这种思路比较简单,其实我们在上篇 「2. 简单二分查找 - [704. 二分查找](https://leetcode.cn/problems/binary-search/)」 中就已经用过了。这里再看一下思路和代码: - -#### 思路 1:直接法 - -1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 -2. 取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 - 1. 如果 $target == nums[mid]$,则返回中心位置。 - 2. 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 - 3. 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 -3. 如果左边界大于右边界,查找范围缩小为空,说明目标元素不存在,此时返回 $-1$。 - -#### 思路 1:代码 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - # 在区间 [left, right] 内查找 target - while left <= right: - # 取区间中间节点 - mid = left + (right - left) // 2 - # 如果找到目标值,则直接范围中心位置 - if nums[mid] == target: - return mid - # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 - elif nums[mid] < target: - left = mid + 1 - # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 - else: - right = mid - 1 - # 未搜索到元素,返回 -1 - return -1 -``` - -#### 思路 1:细节 - -- 这种思路是在一旦循环体中找到元素就直接返回。 -- 循环可以继续的条件是 `left <= right`。 -- 如果一旦退出循环,则说明这个区间内一定不存在目标元素。 - -### 4.2 排除法 - -> **排除法思想**:在循环体中排除目标元素一定不存在区间。 - -#### 思路 2:排除法 - -1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 -2. 取两个节点中心位置 $mid$,比较目标元素和中间元素的大小,先将目标元素一定不存在的区间排除。 -3. 然后在剩余区间继续查找元素,继续根据条件排除目标元素一定不存在的区间。 -4. 直到区间中只剩下最后一个元素,然后再判断这个元素是否是目标元素。 - -根据排除法的思路,我们可以写出来两种代码。 - -#### 思路 2:代码 1 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - # 在区间 [left, right] 内查找 target - while left < right: - # 取区间中间节点 - mid = left + (right - left) // 2 - # nums[mid] 小于目标值,排除掉不可能区间 [left, mid],在 [mid + 1, right] 中继续搜索 - if nums[mid] < target: - left = mid + 1 - # nums[mid] 大于等于目标值,目标元素可能在 [left, mid] 中,在 [left, mid] 中继续搜索 - else: - right = mid - # 判断区间剩余元素是否为目标元素,不是则返回 -1 - return left if nums[left] == target else -1 -``` - -#### 思路 2:代码 2 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - # 在区间 [left, right] 内查找 target - while left < right: - # 取区间中间节点 - mid = left + (right - left + 1) // 2 - # nums[mid] 大于目标值,排除掉不可能区间 [mid, right],在 [left, mid - 1] 中继续搜索 - if nums[mid] > target: - right = mid - 1 - # nums[mid] 小于等于目标值,目标元素可能在 [mid, right] 中,在 [mid, right] 中继续搜索 - else: - left = mid - # 判断区间剩余元素是否为目标元素,不是则返回 -1 - return left if nums[left] == target else -1 -``` - -#### 思路 2:细节 - -- 判断语句是 `left < right`。这样在退出循环时,一定有`left == right` 成立,就不用判断应该返回 $left$ 还是 $right$ 了。此时只需要判断 $nums[left]$ 是否为目标元素即可。 -- 在循环体中,比较目标元素和中间元素的大小之后,优先将目标元素一定不存在的区间排除,然后再从剩余区间中确定下一次查找区间的范围。 -- 在将目标元素一定不存在的区间排除之后,它的对立面(即 `else` 部分)一般就不需要再考虑区间范围了,直接取上一个区间的相反区间。如果上一个区间是 $[mid + 1, right]$,那么相反区间就是 $[left, mid]$。如果上一个区间是 $[left, mid - 1]$,那么相反区间就是 $[mid, right]$。 -- 为了避免陷入死循环,当区分被划分为 $[left, mid - 1]$ 与 $[mid, right]$ 两部分时,**$mid$ 取值要向上取整**。即 `mid = left + (right - left + 1) // 2`。因为如果当区间中只剩下两个元素时(此时 `right = left + 1`),一旦进入 `left = mid` 分支,区间就不会再缩小了,下一次循环的查找区间还是 $[left, right]$,就陷入了死循环。 - - 比如左边界 $left = 5$,右边界 $right = 6$,此时查找区间为 $[5, 6]$,$mid = 5 + (6 - 5) // 2 = 5$,如果进入 $left = mid$ 分支,那么下次查找区间仍为 $[5, 6]$,区间不再缩小,陷入死循环。 - - 这种情况下,$mid$ 应该向上取整,$mid = 5 + (6 - 5 + 1) // 2 = 6$,如果进入 $left = mid$ 分支,则下次查找区间为 $[6, 6]$。 - - -- 关于边界设置可以记忆为:只要看到 `left = mid` 就向上取整。或者记为: - - `left = mid + 1`、`right = mid` 和 `mid = left + (right - left) // 2` 一定是配对出现的。 - - `right = mid - 1`、`left = mid` 和 `mid = left + (right - left + 1) // 2` 一定是配对出现的。 - -### 4.3 两种思路适用范围 - -- **直接法**:因为判断语句是 `left <= right`,有时候要考虑返回是 $left$ 还是 $right$。循环体内有 3 个分支,并且一定有一个分支用于退出循环或者直接返回。这种思路适合解决简单题目。即要查找的元素性质简单,数组中都是非重复元素,且 `==`、`>`、`<` 的情况非常好写的时候。 -- **排除法**:更加符合二分查找算法的减治思想。每次排除目标元素一定不存在的区间,达到减少问题规模的效果。然后在可能存在的区间内继续查找目标元素。这种思路适合解决复杂题目。比如查找一个数组里可能不存在的元素,找边界问题,可以使用这种思路。 - -## 参考资料 - -- 【博文】[Learning-Algorithms-with-Leetcode - 第 3.1 节 二分查找算法](https://www.yuque.com/liweiwei1419/algo/wkmtx4) -- 【博文】[二分法的细节加细节 你真的应该搞懂!!!_小马的博客](https://blog.csdn.net/xiao_jj_jj/article/details/106018702) -- 【课程】[零起步学算法 - LeetBook - 二分查找的基本思想:减而治之](https://leetcode.cn/leetbook/read/learning-algorithms-with-leetcode/xsz9zc/) -- 【题解】[二分查找算法细节详解,顺便写了首诗 - LeetCode](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/) \ No newline at end of file diff --git a/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md b/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md deleted file mode 100644 index 451e6ee3..00000000 --- a/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md +++ /dev/null @@ -1,53 +0,0 @@ -### 二分查找题目 - -#### 二分下标题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0704 | [二分查找](https://leetcode.cn/problems/binary-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0704.%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md) | 数组、二分查找 | 简单 | -| 0374 | [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0374.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F.md) | 二分查找、交互 | 简单 | -| 0035 | [搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0035.%20%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 简单 | -| 0034 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0034.%20%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md) | 数组、二分查找 | 中等 | -| 0167 | [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、二分查找 | 中等 | -| 0153 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0153.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0154 | [寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0154.%20%E5%AF%BB%E6%89%BE%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC%20II.md) | 数组、二分查找 | 困难 | -| 0033 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0033.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找 | 中等 | -| 0081 | [搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0081.%20%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%20II.md) | 数组、二分查找 | 中等 | -| 0278 | [第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0278.%20%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E7%9A%84%E7%89%88%E6%9C%AC.md) | 二分查找、交互 | 简单 | -| 0162 | [寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0162.%20%E5%AF%BB%E6%89%BE%E5%B3%B0%E5%80%BC.md) | 数组、二分查找 | 中等 | -| 0852 | [山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0852.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E7%9A%84%E5%B3%B0%E9%A1%B6%E7%B4%A2%E5%BC%95.md) | 数组、二分查找 | 中等 | -| 1095 | [山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1095.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%80%BC.md) | 数组、二分查找、交互 | 困难 | -| 0744 | [寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0744.%20%E5%AF%BB%E6%89%BE%E6%AF%94%E7%9B%AE%E6%A0%87%E5%AD%97%E6%AF%8D%E5%A4%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%97%E6%AF%8D.md) | 数组、二分查找 | 简单 | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0074 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0074.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5.md) | 数组、二分查找、矩阵 | 中等 | -| 0240 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0240.%20%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II.md) | 数组、二分查找、分治、矩阵 | 中等 | - -#### 二分答案题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0069 | [x 的平方根](https://leetcode.cn/problems/sqrtx/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0069.%20x%20%E7%9A%84%E5%B9%B3%E6%96%B9%E6%A0%B9.md) | 数学、二分查找 | 简单 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0367 | [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0367.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 数学、二分查找 | 简单 | -| 1300 | [转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1300.%20%E8%BD%AC%E5%8F%98%E6%95%B0%E7%BB%84%E5%90%8E%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、二分查找、排序 | 中等 | -| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 | - -#### 复杂的二分查找问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0875 | [爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0875.%20%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82.md) | 数组、二分查找 | 中等 | -| 0410 | [分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0658 | [找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | -| 0270 | [最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0270.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%80%BC.md) | 树、深度优先搜索、二叉搜索树、二分查找、二叉树 | 简单 | -| 0702 | [搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0702.%20%E6%90%9C%E7%B4%A2%E9%95%BF%E5%BA%A6%E6%9C%AA%E7%9F%A5%E7%9A%84%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、二分查找、交互 | 中等 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0287 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0287.%20%E5%AF%BB%E6%89%BE%E9%87%8D%E5%A4%8D%E6%95%B0.md) | 位运算、数组、双指针、二分查找 | 中等 | -| 0719 | [找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、二分查找、排序 | 困难 | -| 0259 | [较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 中等 | -| 1011 | [在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1011.%20%E5%9C%A8%20D%20%E5%A4%A9%E5%86%85%E9%80%81%E8%BE%BE%E5%8C%85%E8%A3%B9%E7%9A%84%E8%83%BD%E5%8A%9B.md) | 数组、二分查找 | 中等 | -| 1482 | [制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1482.%20%E5%88%B6%E4%BD%9C%20m%20%E6%9D%9F%E8%8A%B1%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E5%A4%A9%E6%95%B0.md) | 数组、二分查找 | 中等 | - diff --git a/Contents/01.Array/03.Array-Binary-Search/index.md b/Contents/01.Array/03.Array-Binary-Search/index.md deleted file mode 100644 index 5b261ffb..00000000 --- a/Contents/01.Array/03.Array-Binary-Search/index.md +++ /dev/null @@ -1,5 +0,0 @@ -## 本章内容 - -- [二分查找知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md) -- [二分查找知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md) -- [二分查找题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) \ No newline at end of file diff --git a/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md b/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md deleted file mode 100644 index d8068ee6..00000000 --- a/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md +++ /dev/null @@ -1,500 +0,0 @@ -## 1. 双指针简介 - -> **双指针(Two Pointers)**:指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 - -在数组的区间问题上,暴力算法的时间复杂度往往是 $O(n^2)$。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 $O(n)$。 - -## 2. 对撞指针 - -> **对撞指针**:指的是两个指针 $left$、$right$ 分别指向序列第一个元素和最后一个元素,然后 $left$ 指针不断递增,$right$ 不断递减,直到两个指针的值相撞(即 $left == right$),或者满足其他要求的特殊条件为止。 - -![对撞指针](https://qcdn.itcharge.cn/images/20230906165407.png) - -### 2.1 对撞指针求解步骤 - -1. 使用两个指针 $left$,$right$。$left$ 指向序列第一个元素,即:$left = 0$,$right$ 指向序列最后一个元素,即:$right = len(nums) - 1$。 -2. 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,$left += 1$。当满足另外一定条件时,将右指针左移,$right -= 1$。 -3. 直到两指针相撞(即 $left == right$),或者满足其他要求的特殊条件时,跳出循环体。 - -### 2.2 对撞指针伪代码模板 - -```python -left, right = 0, len(nums) - 1 - -while left < right: - if 满足要求的特殊条件: - return 符合条件的值 - elif 一定条件 1: - left += 1 - elif 一定条件 2: - right -= 1 - -return 没找到 或 找到对应值 -``` - -### 2.3 对撞指针适用范围 - -对撞指针一般用来解决有序数组或者字符串问题: - -- 查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题。 -- 字符串反转问题:反转字符串、回文数、颠倒二进制等问题。 - -下面我们根据具体例子来讲解如何使用对撞指针来解决问题。 - -### 2.4 两数之和 II - 输入有序数组 - -#### 2.4.1 题目链接 - -- [167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) - -#### 2.4.2 题目大意 - -**描述**:给定一个下标从 $1$ 开始计数、升序排列的整数数组:$numbers$ 和一个目标值 $target$。 - -**要求**:从数组中找出满足相加之和等于 $target$ 的两个数,并返回两个数在数组中下的标值。 - -**说明**: - -- $2 \le numbers.length \le 3 * 10^4$。 -- $-1000 \le numbers[i] \le 1000$。 -- $numbers$ 按非递减顺序排列。 -- $-1000 \le target \le 1000$。 -- 仅存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:numbers = [2,7,11,15], target = 9 -输出:[1,2] -解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 -``` - -- 示例 2: - -```python -输入:numbers = [2,3,4], target = 6 -输出:[1,3] -解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。 -``` - -#### 2.4.3 解题思路 - -这道题如果暴力遍历数组,从中找到相加之和等于 $target$ 的两个数,时间复杂度为 $O(n^2)$,可以尝试一下。 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - size = len(numbers) - for i in range(size): - for j in range(i + 1, size): - if numbers[i] + numbers[j] == target: - return [i + 1, j + 1] - return [-1, -1] -``` - -结果不出意外的超时了。所以我们要想办法减少时间复杂度。 - -##### 思路 1:对撞指针 - -可以考虑使用对撞指针来减少时间复杂度。具体做法如下: - -1. 使用两个指针 $left$,$right$。$left$ 指向数组第一个值最小的元素位置,$right$ 指向数组值最大元素位置。 -2. 判断两个位置上的元素的和与目标值的关系。 - 1. 如果元素和等于目标值,则返回两个元素位置。 - 2. 如果元素和大于目标值,则让 $right$ 左移,继续检测。 - 3. 如果元素和小于目标值,则让 $left$ 右移,继续检测。 -3. 直到 $left$ 和 $right$ 移动到相同位置停止检测。 -4. 如果最终仍没找到,则返回 $[-1, -1]$。 - -##### 思路 1:代码 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - left = 0 - right = len(numbers) - 1 - while left < right: - total = numbers[left] + numbers[right] - if total == target: - return [left + 1, right + 1] - elif total < target: - left += 1 - else: - right -= 1 - return [-1, -1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - -### 2.5 验证回文串 - -#### 2.5.1 题目链接 - -- [125. 验证回文串 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-palindrome/) - -#### 2.5.2 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 - -**说明**: - -- 回文串:正着读和反着读都一样的字符串。 -- $1 \le s.length \le 2 * 10^5$。 -- $s$ 仅由可打印的 ASCII 字符组成。 - -**示例**: - -```python -输入: "A man, a plan, a canal: Panama" -输出:true -解释:"amanaplanacanalpanama" 是回文串。 - - -输入:"race a car" -输出:false -解释:"raceacar" 不是回文串。 -``` - -#### 2.5.3 解题思路 - -##### 思路 1:对撞指针 - -1. 使用两个指针 $left$,$right$。$left$ 指向字符串开始位置,$right$ 指向字符串结束位置。 -2. 判断两个指针对应字符是否是字母或数字。 通过 $left$ 右移、$right$ 左移的方式过滤掉字母和数字以外的字符。 -3. 然后判断 $s[start]$ 是否和 $s[end]$ 相等(注意大小写)。 - 1. 如果相等,则将 $left$ 右移、$right$ 左移,继续进行下一次过滤和判断。 - 2. 如果不相等,则说明不是回文串,直接返回 $False$。 -4. 如果遇到 $left == right$,跳出循环,则说明该字符串是回文串,返回 $True$。 - -##### 思路 1:代码 - -```python -class Solution: - def isPalindrome(self, s: str) -> bool: - left = 0 - right = len(s) - 1 - - while left < right: - if not s[left].isalnum(): - left += 1 - continue - if not s[right].isalnum(): - right -= 1 - continue - - if s[left].lower() == s[right].lower(): - left += 1 - right -= 1 - else: - return False - return True -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(len(s))$。 -- **空间复杂度**:$O(len(s))$。 - -### 2.6 盛最多水的容器 - -#### 2.6.1 题目链接 - -- [11. 盛最多水的容器 - 力扣(LeetCode)](https://leetcode.cn/problems/container-with-most-water/) - -#### 2.6.2 题目大意 - -**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n$,每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 - -**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 - -**说明**: - -- $n == height.length$。 -- $2 \le n \le 10^5$。 -- $0 \le height[i] \le 10^4$。 - -**示例**: - -![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) - -```python -输入:[1,8,6,2,5,4,8,3,7] -输出:49 -解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 -``` - -#### 2.6.3 解题思路 - -##### 思路 1:对撞指针 - -从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由「左右两端直线中较低直线的高度 * 两端直线之间的距离」所决定的。所以我们应该使得「」,这样才能使盛水面积尽可能的大。 - -可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: - -1. 使用两个指针 $left$,$right$。$left$ 指向数组开始位置,$right$ 指向数组结束位置。 -2. 计算 $left$ 和 $right$ 所构成的面积值,同时维护更新最大面积值。 -3. 判断 $left$ 和 $right$ 的高度值大小。 - 1. 如果 $left$ 指向的直线高度比较低,则将 $left$ 指针右移。 - 2. 如果 $right$ 指向的直线高度比较低,则将 $right$ 指针左移。 -4. 如果遇到 $left == right$,跳出循环,最后返回最大的面积。 - -##### 思路 1:代码 - -```python -class Solution: - def maxArea(self, height: List[int]) -> int: - left = 0 - right = len(height) - 1 - ans = 0 - while left < right: - area = min(height[left], height[right]) * (right-left) - ans = max(ans, area) - if height[left] < height[right]: - left += 1 - else: - right -= 1 - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 3. 快慢指针 - -> **快慢指针**:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。 - -![快慢指针](https://qcdn.itcharge.cn/images/20230906173808.png) - -### 3.1 快慢指针求解步骤 - -1. 使用两个指针 $slow$、$fast$。$slow$ 一般指向序列第一个元素,即:$slow = 0$,$fast$ 一般指向序列第二个元素,即:$fast = 1$。 -2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 $slow += 1$。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 $fast += 1$。 -3. 到快指针移动到数组尾端(即 $fast == len(nums) - 1$),或者两指针相交,或者满足其他特殊条件时跳出循环体。 - -### 3.2 快慢指针伪代码模板 - -```python -slow = 0 -fast = 1 -while 没有遍历完: - if 满足要求的特殊条件: - slow += 1 - fast += 1 -return 合适的值 -``` - -### 3.3 快慢指针适用范围 - -快慢指针一般用于处理数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。关于链表相关的双指针做法我们到链表章节再详细讲解。 - -下面我们根据具体例子来讲解如何使用快慢指针来解决问题。 - -### 3.4 删除有序数组中的重复项 - -#### 3.4.1 题目链接 - -- [26. 删除有序数组中的重复项 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) - -#### 3.4.2 题目大意 - -**描述**:给定一个有序数组 $nums$。 - -**要求**:删除数组 $nums$ 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 - -**说明**: - -- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,2] -输出:2, nums = [1,2,_] -解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 -``` - -- 示例 2: - -```python -输入:nums = [0,0,1,1,1,2,2,3,3,4] -输出:5, nums = [0,1,2,3,4] -解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 -``` - -#### 3.4.3 解题思路 - -##### 思路 1:快慢指针 - -因为数组是有序的,那么重复的元素一定会相邻。 - -删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: - -1. 定义两个快慢指针 $slow$,$fast$。其中 $slow$ 指向去除重复元素后的数组的末尾位置。$fast$ 指向当前元素。 -2. 令 $slow$ 在后, $fast$ 在前。令 $slow = 0$,$fast = 1$。 -3. 比较 $slow$ 位置上元素值和 $fast$ 位置上元素值是否相等。 - - 如果不相等,则将 $slow$ 右移一位,将 $fast$ 指向位置的元素复制到 $slow$ 位置上。 -4. 将 $fast$ 右移 $1$ 位。 -5. 重复上述 $3 \sim 4$ 步,直到 $fast$ 等于数组长度。 -6. 返回 $slow + 1$ 即为新数组长度。 - -##### 思路 1:代码 - -```python -class Solution: - def removeDuplicates(self, nums: List[int]) -> int: - if len(nums) <= 1: - return len(nums) - - slow, fast = 0, 1 - - while (fast < len(nums)): - if nums[slow] != nums[fast]: - slow += 1 - nums[slow] = nums[fast] - fast += 1 - - return slow + 1 -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 4. 分离双指针 - -> **分离双指针**:两个指针分别属于不同的数组,两个指针分别在两个数组中移动。 - -![分离双指针](https://qcdn.itcharge.cn/images/20230906180852.png) - -### 4.1 分离双指针求解步骤 - -1. 使用两个指针 $left\underline{}1$、$left\underline{}2$。$left\underline{}1$ 指向第一个数组的第一个元素,即:$left\underline{}1 = 0$,$left\underline{}2$ 指向第二个数组的第一个元素,即:$left\underline{}2 = 0$。 -2. 当满足一定条件时,两个指针同时右移,即 $left\underline{}1 += 1$、$left\underline{}2 += 1$。 -3. 当满足另外一定条件时,将 $left\underline{}1$ 指针右移,即 $left\underline{}1 += 1$。 -4. 当满足其他一定条件时,将 $left\underline{}2$ 指针右移,即 $left\underline{}2 += 1$。 -5. 当其中一个数组遍历完时或者满足其他特殊条件时跳出循环体。 - -### 4.2 分离双指针伪代码模板 - -```python -left_1 = 0 -left_2 = 0 - -while left_1 < len(nums1) and left_2 < len(nums2): - if 一定条件 1: - left_1 += 1 - left_2 += 1 - elif 一定条件 2: - left_1 += 1 - elif 一定条件 3: - left_2 += 1 -``` - -### 4.3 分离双指针使用范围 - -分离双指针一般用于处理有序数组合并,求交集、并集问题。 - -下面我们根据具体例子来讲解如何使用分离双指针来解决问题。 - -### 4.4 两个数组的交集 - -#### 4.4.1 题目链接 - -- [349. 两个数组的交集 - 力扣(LeetCode)](https://leetcode.cn/problems/intersection-of-two-arrays/) - -#### 4.4.2 题目大意 - -**描述**:给定两个数组 $nums1$ 和 $nums2$。 - -**要求**:返回两个数组的交集。重复元素只计算一次。 - -**说明**: - -- $1 \le nums1.length, nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2,2,1], nums2 = [2,2] -输出:[2] -示例 2: -``` - -- 示例 2: - -```python -输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] -输出:[9,4] -解释:[4,9] 也是可通过的 -``` - -#### 4.4.3 解题思路 - -##### 思路 1:分离双指针 - -1. 对数组 $nums1$、$nums2$ 先排序。 -2. 使用两个指针 $left\underline{}1$、$left\underline{}2$。$left\underline{}1$ 指向第一个数组的第一个元素,即:$left\underline{}1 = 0$,$left\underline{}2$ 指向第二个数组的第一个元素,即:$left\underline{}2 = 0$。 -3. 如果 $nums1[left\underline{}1] == nums2[left\underline{}2]$,则将其加入答案数组(注意去重),并将 $left\underline{}1$ 和 $left\underline{}2$ 右移。 -4. 如果 $nums1[left\underline{}1] < nums2[left\underline{}2]$,则将 $left\underline{}1$ 右移。 -5. 如果 $nums1[left\underline{}1] > nums2[left\underline{}2]$,则将 $left\underline{}2$ 右移。 -6. 最后返回答案数组。 - -##### 思路 1:代码 - -```python -class Solution: - def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: - nums1.sort() - nums2.sort() - - left_1 = 0 - left_2 = 0 - res = [] - while left_1 < len(nums1) and left_2 < len(nums2): - if nums1[left_1] == nums2[left_2]: - if nums1[left_1] not in res: - res.append(nums1[left_1]) - left_1 += 1 - left_2 += 1 - elif nums1[left_1] < nums2[left_2]: - left_1 += 1 - elif nums1[left_1] > nums2[left_2]: - left_2 += 1 - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 5. 双指针总结 - -双指针分为「对撞指针」、「快慢指针」、「分离双指针」。 - -- **对撞指针**:两个指针方向相反。适合解决查找有序数组中满足某些约束条件的一组元素问题、字符串反转问题。 -- **快慢指针**:两个指针方向相同。适合解决数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。 -- **分离双指针**:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。 - -## 参考资料 - -- 【博文】[双指针算法之快慢指针 (yanyusoul.com)](https://yanyusoul.com/blog/cs/algorithms_fast-slow-points/) -- 【博文】[双指针算法各类基础题型总结 - 掘金](https://juejin.cn/post/6855129006451687431) -- 【博文】[双指针 - 力扣加加 - 努力做西湖区最好的算法题解](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/91/two-pointers#zuo-you-duan-dian-zhi-zhen) -- 【博文】[LeetCode分类专题(四)——双指针和滑动窗口1 - iwehdio - 博客园](https://www.cnblogs.com/iwehdio/p/14434988.html) -- 【博文】[双指针算法各类基础题型总结 - 掘金](https://juejin.cn/post/6855129006451687431) diff --git a/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md b/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md deleted file mode 100644 index 5626dc60..00000000 --- a/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md +++ /dev/null @@ -1,50 +0,0 @@ -### 双指针题目 - -#### 对撞指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0167 | [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0167.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C%20II%20-%20%E8%BE%93%E5%85%A5%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、二分查找 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0345 | [反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0345.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D.md) | 双指针、字符串 | 简单 | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0011 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0011.%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8.md) | 贪心、数组、双指针 | 中等 | -| 0611 | [有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0611.%20%E6%9C%89%E6%95%88%E4%B8%89%E8%A7%92%E5%BD%A2%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0016 | [最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0016.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0018 | [四数之和](https://leetcode.cn/problems/4sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0259 | [较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0259.%20%E8%BE%83%E5%B0%8F%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 中等 | -| 0658 | [找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0658.%20%E6%89%BE%E5%88%B0%20K%20%E4%B8%AA%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E5%85%83%E7%B4%A0.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | -| 1099 | [小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1099.%20%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、二分查找、排序 | 简单 | -| 0075 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0075.%20%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md) | 数组、双指针、排序 | 中等 | -| 0360 | [有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0360.%20%E6%9C%89%E5%BA%8F%E8%BD%AC%E5%8C%96%E6%95%B0%E7%BB%84.md) | 数组、数学、双指针、排序 | 中等 | -| 0977 | [有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0977.%20%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.md) | 数组、双指针、排序 | 简单 | -| 0881 | [救生艇](https://leetcode.cn/problems/boats-to-save-people/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0881.%20%E6%95%91%E7%94%9F%E8%89%87.md) | 贪心、数组、双指针、排序 | 中等 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0443 | [压缩字符串](https://leetcode.cn/problems/string-compression/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0443.%20%E5%8E%8B%E7%BC%A9%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 中等 | - -#### 快慢指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0026 | [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0026.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 数组、双指针 | 简单 | -| 0080 | [删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0080.%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9%20II.md) | 数组、双指针 | 中等 | -| 0027 | [移除元素](https://leetcode.cn/problems/remove-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0027.%20%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md) | 数组、双指针 | 简单 | -| 0283 | [移动零](https://leetcode.cn/problems/move-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0283.%20%E7%A7%BB%E5%8A%A8%E9%9B%B6.md) | 数组、双指针 | 简单 | -| 0845 | [数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0845.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E5%B1%B1%E8%84%89.md) | 数组、双指针、动态规划、枚举 | 中等 | -| 0088 | [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0088.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84.md) | 数组、双指针、排序 | 简单 | -| 0719 | [找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0719.%20%E6%89%BE%E5%87%BA%E7%AC%AC%20K%20%E5%B0%8F%E7%9A%84%E6%95%B0%E5%AF%B9%E8%B7%9D%E7%A6%BB.md) | 数组、双指针、二分查找、排序 | 困难 | -| 0334 | [递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0334.%20%E9%80%92%E5%A2%9E%E7%9A%84%E4%B8%89%E5%85%83%E5%AD%90%E5%BA%8F%E5%88%97.md) | 贪心、数组 | 中等 | -| 0978 | [最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0978.%20%E6%9C%80%E9%95%BF%E6%B9%8D%E6%B5%81%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 剑指 Offer 21 | [调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2021.%20%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E5%89%8D%E9%9D%A2.md) | 数组、双指针、排序 | 简单 | - -#### 分离双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0925 | [长按键入](https://leetcode.cn/problems/long-pressed-name/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0925.%20%E9%95%BF%E6%8C%89%E9%94%AE%E5%85%A5.md) | 双指针、字符串 | 简单 | -| 0844 | [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0844.%20%E6%AF%94%E8%BE%83%E5%90%AB%E9%80%80%E6%A0%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、双指针、字符串、模拟 | 简单 | -| 1229 | [安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1229.%20%E5%AE%89%E6%8E%92%E4%BC%9A%E8%AE%AE%E6%97%A5%E7%A8%8B.md) | 数组、双指针、排序 | 中等 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | - diff --git a/Contents/01.Array/04.Array-Two-Pointers/index.md b/Contents/01.Array/04.Array-Two-Pointers/index.md deleted file mode 100644 index a0f281c8..00000000 --- a/Contents/01.Array/04.Array-Two-Pointers/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [数组双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md) -- [数组双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) \ No newline at end of file diff --git a/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md b/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md deleted file mode 100644 index 5b2ec775..00000000 --- a/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md +++ /dev/null @@ -1,422 +0,0 @@ -## 1. 滑动窗口算法介绍 - -在计算机网络中,滑动窗口协议(Sliding Window Protocol)是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。我们所要讲解的滑动窗口算法也是利用了同样的特性。 - -> **滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 - -- **滑动操作**:窗口可按照一定方向进行移动。最常见的是向右侧移动。 -- **缩放操作**:对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度。 - -滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以将滑动窗口看做是快慢指针的一种特殊形式。 - -![滑动窗口](https://qcdn.itcharge.cn/images/20230907105115.png) - -## 2. 滑动窗口适用范围 - -滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。 - -按照窗口长度的固定情况,我们可以将滑动窗口题目分为以下两种: - -- **固定长度窗口**:窗口大小是固定的。 -- **不定长度窗口**:窗口大小是不固定的。 - - 求解最大的满足条件的窗口。 - - 求解最小的满足条件的窗口。 - - -下面来分别讲解一下这两种类型题目。 - -## 3. 固定长度滑动窗口 - -> **固定长度滑动窗口算法(Fixed Length Sliding Window)**:在给定数组 / 字符串上维护一个固定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 - -![固定长度滑动窗口](https://qcdn.itcharge.cn/images/20230907110356.png) - -### 3.1 固定长度滑动窗口算法步骤 - -假设窗口的固定大小为 $window\underline{}size$。 - -1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。 -2. 当窗口未达到 $window\underline{}size$ 大小时,不断移动 $right$,先将数组前 $window\underline{}size$ 个元素填入窗口中,即 `window.append(nums[right])`。 -2. 当窗口达到 $window\underline{}size$ 大小时,即满足 `right - left + 1 >= window_size` 时,判断窗口内的连续元素是否满足题目限定的条件。 - 1. 如果满足,再根据要求更新最优解。 - 2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $window\underline{}size$。 -3. 向右移动 $right$,将元素填入窗口中,即 `window.append(nums[right])`。 -4. 重复 $2 \sim 4$ 步,直到 $right$ 到达数组末尾。 - -### 3.2 固定长度滑动窗口代码模板 - -```python -left = 0 -right = 0 - -while right < len(nums): - window.append(nums[right]) - - # 超过窗口大小时,缩小窗口,维护窗口中始终为 window_size 的长度 - if right - left + 1 >= window_size: - # ... 维护答案 - window.popleft() - left += 1 - - # 向右侧增大窗口 - right += 1 -``` - -下面我们根据具体例子来讲解一下如何使用固定窗口大小的滑动窗口来解决问题。 - -### 3.3 大小为 K 且平均值大于等于阈值的子数组数目 - -#### 3.3.1 题目链接 - -- [1343. 大小为 K 且平均值大于等于阈值的子数组数目 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) - -#### 3.3.2 题目大意 - -**描述**:给定一个整数数组 $arr$ 和两个整数 $k$ 和 $threshold$ 。 - -**要求**:返回长度为 $k$ 且平均值大于等于 $threshold$ 的子数组数目。 - -**说明**: - -- $1 \le arr.length \le 10^5$。 -- $1 \le arr[i] \le 10^4$。 -- $1 \le k \le arr.length$。 -- $0 \le threshold \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 -输出:3 -解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。 -``` - -- 示例 2: - -```python -输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 -输出:6 -解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 -``` - -#### 3.3.3 解题思路 - -##### 思路 1:滑动窗口(固定长度) - -这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 $k$。具体做法如下: - -1. $ans$ 用来维护答案数目。$window\underline{}sum$ 用来维护窗口中元素的和。 -2. $left$ 、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$。 -3. 向右移动 $right$,先将 $k$ 个元素填入窗口中,即 `window_sum += arr[right]`。 -4. 当窗口元素个数为 $k$ 时,即满足 `right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 $threshold$。 - 1. 如果满足,则答案数目加 $1$。 - 2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 -5. 重复 $3 \sim 4$ 步,直到 $right$ 到达数组末尾。 -6. 最后输出答案数目。 - -##### 思路 1:代码 - -```python -class Solution: - def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int: - left = 0 - right = 0 - window_sum = 0 - ans = 0 - - while right < len(arr): - window_sum += arr[right] - - if right - left + 1 >= k: - if window_sum >= k * threshold: - ans += 1 - window_sum -= arr[left] - left += 1 - - right += 1 - - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 4. 不定长度滑动窗口 - -> **不定长度滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 - -![不定长度滑动窗口](https://qcdn.itcharge.cn/images/20230907132630.png) - -### 4.1 不定长度滑动窗口算法步骤 - -1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素。即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。 -2. 将区间最右侧元素添加入窗口中,即 `window.add(s[right])`。 -3. 然后向右移动 $right$,从而增大窗口长度,即 `right += 1`。直到窗口中的连续元素满足要求。 -4. 此时,停止增加窗口大小。转向不断将左侧元素移出窗口,即 `window.popleft(s[left])`。 -5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`。直到窗口中的连续元素不再满足要求。 -6. 重复 2 ~ 5 步,直到 $right$ 到达序列末尾。 - -### 4.2 不定长度滑动窗口代码模板 - -```python -left = 0 -right = 0 - -while right < len(nums): - window.append(nums[right]) - - while 窗口需要缩小: - # ... 可维护答案 - window.popleft() - left += 1 - - # 向右侧增大窗口 - right += 1 -``` - -### 4.3 [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) - -#### 4.3.1 题目链接 - -- [3. 无重复字符的最长子串 - 力扣(LeetCode)](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) - -#### 4.3.2 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:找出其中不含有重复字符的最长子串的长度。 - -**说明**: - -- $0 \le s.length \le 5 * 10^4$。 -- $s$ 由英文字母、数字、符号和空格组成。 - -**示例**: - -- 示例 1: - -```python -输入: s = "abcabcbb" -输出: 3 -解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 -``` - -- 示例 2: - -```python -输入: s = "bbbbb" -输出: 1 -解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 -``` - -#### 4.3.3 解题思路 - -##### 思路 1:滑动窗口(不定长度) - -用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。 - -1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 -2. 一开始,$left$、$right$ 都指向 $0$。 -3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。 -4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$。 -5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -6. 输出无重复字符的最长子串长度。 - -##### 思路 1:代码 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - left = 0 - right = 0 - window = dict() - ans = 0 - - while right < len(s): - if s[right] not in window: - window[s[right]] = 1 - else: - window[s[right]] += 1 - - while window[s[right]] > 1: - window[s[left]] -= 1 - left += 1 - - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 - -### 4.4 长度最小的子数组 - -#### 4.4.1 题目链接 - -- [209. 长度最小的子数组 - 力扣(LeetCode)](https://leetcode.cn/problems/minimum-size-subarray-sum/) - -#### 4.4.2 题目大意 - -**描述**:给定一个只包含正整数的数组 $nums$ 和一个正整数 $target$。 - -**要求**:找出数组中满足和大于等于 $target$ 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 $0$。 - -**说明**: - -- $1 \le target \le 10^9$。 -- $1 \le nums.length \le 10^5$。 -- $1 \le nums[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:target = 7, nums = [2,3,1,2,4,3] -输出:2 -解释:子数组 [4,3] 是该条件下的长度最小的子数组。 -``` - -- 示例 2: - -```python -输入:target = 4, nums = [1,4,4] -输出:1 -``` - -#### 4.4.3 解题思路 - -##### 思路 1:滑动窗口(不定长度) - -最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 - -用滑动窗口来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 $target$。 - -1. 一开始,$left$、$right$ 都指向 $0$。 -2. 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{}sum$ 中。 -3. 如果 $window\underline{}sum \ge target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{}sum < target$。 -4. 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -5. 输出窗口和的最小值作为答案。 - -##### 思路 1:代码 - -```python -class Solution: - def minSubArrayLen(self, target: int, nums: List[int]) -> int: - size = len(nums) - ans = size + 1 - left = 0 - right = 0 - window_sum = 0 - - while right < size: - window_sum += nums[right] - - while window_sum >= target: - ans = min(ans, right - left + 1) - window_sum -= nums[left] - left += 1 - - right += 1 - - return ans if ans != size + 1 else 0 -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 4.5 乘积小于K的子数组 - -#### 4.5.1 题目链接 - -- [713. 乘积小于K的子数组 - 力扣(LeetCode)](https://leetcode.cn/problems/subarray-product-less-than-k/) - -#### 4.5.2 题目大意 - -**描述**:给定一个正整数数组 $nums$ 和整数 $k$。 - -**要求**:找出该数组内乘积小于 $k$ 的连续的子数组的个数。 - -**说明**: - -- $1 \le nums.length \le 3 * 10^4$。 -- $1 \le nums[i] \le 1000$。 -- $0 \le k \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [10,5,2,6], k = 100 -输出:8 -解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,3], k = 0 -输出:0 -``` - -#### 4.5.3 解题思路 - -##### 思路 1:滑动窗口(不定长度) - -1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 $window\underline{}product$ 都小于 $k$。使用 $window\underline{}product$ 记录窗口中的乘积值,使用 $count$ 记录符合要求的子数组个数。 -2. 一开始,$left$、$right$ 都指向 $0$。 -3. 向右移动 $right$,将最右侧元素加入当前子数组乘积 $window\underline{}product$ 中。 -4. 如果 $window\underline{}product \ge k$,则不断右移 $left$,缩小滑动窗口长度,并更新当前乘积值 $window\underline{}product$ 直到 $window\underline{}product < k$。 -5. 记录累积答案个数加 $1$,继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -6. 输出累积答案个数。 - -##### 思路 1:代码 - -```python -class Solution: - def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int: - if k <= 1: - return 0 - - size = len(nums) - left = 0 - right = 0 - window_product = 1 - - count = 0 - - while right < size: - window_product *= nums[right] - - while window_product >= k: - window_product /= nums[left] - left += 1 - - count += (right - left + 1) - right += 1 - - return count -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- 【答案】[TCP 协议的滑动窗口具体是怎样控制流量的? - 知乎](https://www.zhihu.com/question/32255109/answer/68558623) -- 【博文】[滑动窗口算法基本原理与实践 - huansky - 博客园](https://www.cnblogs.com/huansky/p/13488234.html) -- 【博文】[滑动窗口(Sliding Window)- lucifer.ren](https://lucifer.ren/leetcode/thinkings/slide-window.html) - diff --git a/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md b/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md deleted file mode 100644 index 12e27653..00000000 --- a/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md +++ /dev/null @@ -1,51 +0,0 @@ -### 滑动窗口题目 - -#### 固定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1343 | [大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1343.%20%E5%A4%A7%E5%B0%8F%E4%B8%BA%20K%20%E4%B8%94%E5%B9%B3%E5%9D%87%E5%80%BC%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E%E9%98%88%E5%80%BC%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E6%95%B0%E7%9B%AE.md) | 数组、滑动窗口 | 中等 | -| 0643 | [子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0643.%20%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%B9%B3%E5%9D%87%E6%95%B0%20I.md) | 数组、滑动窗口 | 简单 | -| 1052 | [爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1052.%20%E7%88%B1%E7%94%9F%E6%B0%94%E7%9A%84%E4%B9%A6%E5%BA%97%E8%80%81%E6%9D%BF.md) | 数组、滑动窗口 | 中等 | -| 1423 | [可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1423.%20%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E6%95%B0.md) | 数组、前缀和、滑动窗口 | 中等 | -| 1456 | [定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1456.%20%E5%AE%9A%E9%95%BF%E5%AD%90%E4%B8%B2%E4%B8%AD%E5%85%83%E9%9F%B3%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md) | 字符串、滑动窗口 | 中等 | -| 0567 | [字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0567.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 哈希表、双指针、字符串、滑动窗口 | 中等 | -| 1100 | [长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1100.%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20K%20%E7%9A%84%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1151 | [最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1151.%20%E6%9C%80%E5%B0%91%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0%E6%9D%A5%E7%BB%84%E5%90%88%E6%89%80%E6%9C%89%E7%9A%84%201.md) | 数组、滑动窗口 | 中等 | -| 1176 | [健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1176.%20%E5%81%A5%E8%BA%AB%E8%AE%A1%E5%88%92%E8%AF%84%E4%BC%B0.md) | 数组、滑动窗口 | 简单 | -| 0438 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | -| 0683 | [K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md) | 树状数组、数组、有序集合、滑动窗口 | 困难 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0480 | [滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | - -#### 不定长度窗口题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0674 | [最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0674.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.md) | 数组 | 简单 | -| 0485 | [最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0485.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 数组 | 简单 | -| 0487 | [最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0487.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20II.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0076 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0076.%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 困难 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0209 | [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0209.%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 0862 | [和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 1004 | [最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1004.%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | -| 1658 | [将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1658.%20%E5%B0%86%20x%20%E5%87%8F%E5%88%B0%200%20%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0.md) | 数组、哈希表、二分查找、前缀和、滑动窗口 | 中等 | -| 0424 | [替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0424.%20%E6%9B%BF%E6%8D%A2%E5%90%8E%E7%9A%84%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 1695 | [删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1695.%20%E5%88%A0%E9%99%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md) | 数组、哈希表、滑动窗口 | 中等 | -| 1208 | [尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1208.%20%E5%B0%BD%E5%8F%AF%E8%83%BD%E4%BD%BF%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E7%AD%89.md) | 字符串、二分查找、前缀和、滑动窗口 | 中等 | -| 1493 | [删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1493.%20%E5%88%A0%E6%8E%89%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BB%A5%E5%90%8E%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | -| 0727 | [最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0727.%20%E6%9C%80%E5%B0%8F%E7%AA%97%E5%8F%A3%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划、滑动窗口 | 困难 | -| 0159 | [至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0159.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0340 | [至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0340.%20%E8%87%B3%E5%A4%9A%E5%8C%85%E5%90%AB%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0795 | [区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0795.%20%E5%8C%BA%E9%97%B4%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AA%E6%95%B0.md) | 数组、双指针 | 中等 | -| 0992 | [K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0992.%20K%20%E4%B8%AA%E4%B8%8D%E5%90%8C%E6%95%B4%E6%95%B0%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、计数、滑动窗口 | 困难 | -| 0713 | [乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0713.%20%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、滑动窗口 | 中等 | -| 0904 | [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md) | 数组、哈希表、滑动窗口 | 中等 | -| 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0467 | [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | - diff --git a/Contents/01.Array/05.Array-Sliding-Window/index.md b/Contents/01.Array/05.Array-Sliding-Window/index.md deleted file mode 100644 index 2d29fdfb..00000000 --- a/Contents/01.Array/05.Array-Sliding-Window/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [数组滑动窗口知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md) -- [数组滑动窗口题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) \ No newline at end of file diff --git a/Contents/01.Array/index.md b/Contents/01.Array/index.md deleted file mode 100644 index 35f35190..00000000 --- a/Contents/01.Array/index.md +++ /dev/null @@ -1,36 +0,0 @@ -## 本章内容 - -### 数组基础知识 - -- [数组基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/01.Array-Basic.md) -- [数组基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) - -### 数组排序算法 - -- [冒泡排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md) -- [选择排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md) -- [插入排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md) -- [希尔排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md) -- [归并排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md) -- [快速排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md) -- [堆排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md) -- [计数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md) -- [桶排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md) -- [基数排序](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md) -- [数组排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) - -### 二分查找 - -- [二分查找知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md) -- [二分查找知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md) -- [二分查找题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) - -### 数组双指针 - -- [数组双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md) -- [数组双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) - -### 数组滑动窗口 - -- [数组滑动窗口知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md) -- [数组滑动窗口题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) \ No newline at end of file diff --git a/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md b/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md deleted file mode 100644 index c0fef36e..00000000 --- a/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md +++ /dev/null @@ -1,366 +0,0 @@ -## 1. 链表简介 - -### 1.1 链表定义 - -> **链表(Linked List)**:一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。 - -简单来说,**「链表」** 是实现线性表链式存储结构的基础。 - -以单链表为例,链表的存储方式如下图所示。 - -![](https://qcdn.itcharge.cn/images/20211208180037.png) - -如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 $next$」。 - -在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。 - -我们先来简单介绍一下链表结构的优缺点: - -- **优点**:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。 - -- **缺点**:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。 - -接下来我们来介绍一下除了单链表之外,链表的其他几种类型。 - -### 1.2 双向链表 - -> **双向链表(Doubly Linked List)**:链表的一种,也叫做双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。 - -- **双向链表特点**:从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。 - -![](https://qcdn.itcharge.cn/images/20211208103220.png) - -### 1.3 循环链表 - -> **循环链表(Circular linked list)**:链表的一种。它的最后一个链节点指向头节点,形成一个环。 - -- **循环链表特点**:从循环链表的任何一个节点出发都能找到任何其他节点。 - -![](https://qcdn.itcharge.cn/images/20211208180048.png) - -接下来我们以最基本的「单链表」为例,介绍一下链表的基本操作。 - -## 2. 链表的基本操作 - -数据结构的操作一般涉及到增、删、改、查 4 种情况,链表的操作也基本上是这 4 种情况。我们一起来看一下链表的基本操作。 - -### 2.1 链表的结构定义 - -链表是由链节点通过 $next$ 链接而构成的,我们可以先定义一个简单的「链节点类」,再来定义完整的「链表类」。 - -- **链节点类(即 ListNode 类)**:使用成员变量 $val$ 表示数据元素的值,使用指针变量 $next$ 表示后继指针。 - -- **链表类(即 LinkedList 类)**:使用一个链节点变量 $head$ 来表示链表的头节点。 - -我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 Python 里可以将其设置为 $None$,其他语言也有类似的惯用值,比如 $NULL$、$nil$、$0$ 等。 - -**「链节点以及链表结构定义」** 的代码如下: - -```python -# 链节点类 -class ListNode: - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -# 链表类 -class LinkedList: - def __init__(self): - self.head = None -``` - -### 2.2 建立一个线性链表 - -> **建立一个线性链表**:根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。 -> -> 1. 从所给线性表的第 $1$ 个数据元素开始依次获取表中的数据元素。 -> 2. 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。 -> 3. 插入完毕之后返回第 $1$ 个链节点的地址。 - -**「建立一个线性链表」** 的代码如下: - -```python -# 根据 data 初始化一个新链表 -def create(self, data): - self.head = ListNode(0) - cur = self.head - for i in range(len(data)): - node = ListNode(data[i]) - cur.next = node - cur = cur.next -``` - -「建立一个线性链表」的操作依赖于线性表的数据元素个数,因此,「建立一个线性链表」的时间复杂度为 $O(n)$,$n$ 为线性表长度。 - -### 2.3 求线性链表的长度 - -> **求线性链表长度**:使用指针变量 $cur$ 顺着链表 $next$ 指针进行移动,并使用计数器 $count$ 记录元素个数。 -> -> 1. 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。 -> 2. 顺着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 -> 3. 等 $cur$ 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。 - -**「求线性链表长度」** 的代码如下: - -```python -# 获取线性链表长度 -def length(self): - count = 0 - cur = self.head - while cur: - count += 1 - cur = cur.next - return count -``` - -「求线性链表长度」的操作依赖于链表的链节点个数,操作的次数为 $n$,因此,「求线性链表长度」的时间复杂度为 $O(n)$,$n$ 为链表长度。 - -### 2.4 查找元素 - -> **在链表中查找值为 $val$ 的元素**:从头节点 $head$ 开始,沿着链表节点逐一进行查找。如果查找成功,返回被查找节点的地址;否则返回 $None$。 -> -> 1. 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。 -> 2. 顺着链节点的 $next$ 指针遍历链表,如果遇到 $cur.val == val$,则返回当前指针变量 $cur$。 -> 3. 如果 $cur$ 指向为空时也未找到,则该链表中没有值为 $val$ 的元素,则返回 $None$。 - -**「在链表中查找值为 $val$ 的元素」** 的代码如下: - -```python -# 查找元素:在链表中查找值为 val 的元素 -def find(self, val): - cur = self.head - while cur: - if val == cur.val: - return cur - cur = cur.next - - return None -``` - -「在链表中查找值为 $val$ 的元素」的操作依赖于链表的链节点个数,因此,「在链表中查找值为 $val$ 的元素」的时间复杂度为 $O(n)$,$n$ 为链表长度。 - -### 2.5 插入元素 - -链表中插入元素操作分为三种: - -- **链表头部插入元素**:在链表第 $1$ 个链节点之前插入值为 $val$ 的链节点。 -- **链表尾部插入元素**:在链表最后 $1$ 个链节点之后插入值为 $val$ 的链节点。 -- **链表中间插入元素**:在链表第 $i$ 个链节点之前插入值为 $val$ 的链节点。 - -接下来我们分别讲解一下。 - -#### 2.5.1 链表头部插入元素 - -> **链表头部插入元素**:在链表第 $1$ 个链节点之前插入值为 $val$ 的链节点。 -> -> 1. 先创建一个值为 $val$ 的链节点 $node$。 -> 2. 然后将 $node$ 的 $next$ 指针指向链表的头节点 $head$。 -> 3. 再将链表的头节点 $head$ 指向 $node$。 - -![](https://qcdn.itcharge.cn/images/20211208180101.png) - -**「链表头部插入元素」** 的代码如下: - -```python -# 链表头部插入元素 -def insertFront(self, val): - node = ListNode(val) - node.next = self.head - self.head = node -``` - -「链表头部插入元素」的操作与链表的长度无关,因此,「链表头部插入元素」的时间复杂度为 $O(1)$。 - -#### 2.5.2 链表尾部插入元素 - -> **链表尾部插入元素**:在链表最后 $1$ 个链节点之后插入值为 $val$ 的链节点。 -> -> 1. 先创建一个值为 $val$ 的链节点 $node$。 -> 2. 使用指针 $cur$ 指向链表的头节点 $head$。 -> 3. 通过链节点的 $next$ 指针移动 $cur$ 指针,从而遍历链表,直到 $cur.next$ 为 $None$。 -> 4. 令 $cur.next$ 指向将新的链节点 $node$。 - -![](https://qcdn.itcharge.cn/images/20211208180111.png) - -**「链表尾部插入元素」** 的代码如下: - -```python -# 链表尾部插入元素 -def insertRear(self, val): - node = ListNode(val) - cur = self.head - while cur.next: - cur = cur.next - cur.next = node -``` - -「链表尾部插入元素」的操作需要将 $cur$ 从链表头部移动到尾部,操作次数是 $n$ 次,因此,「链表尾部插入元素」的时间复杂度是 $O(n)$。 - -#### 2.5.3 链表中间插入元素 - -> **链表中间插入元素**:在链表第 $i$ 个链节点之前插入值为 $val$ 的链节点。 -> -> 1. 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。 -> 2. 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 -> 3. 当遍历到第 $index - 1$ 个链节点时停止遍历。 -> 4. 创建一个值为 $val$ 的链节点 $node$。 -> 5. 将 $node.next$ 指向 $cur.next$。 -> 6. 然后令 $cur.next$ 指向 $node$。 - -![](https://qcdn.itcharge.cn/images/20211208180121.png) - -**「链表中间插入元素」** 的代码如下: - -```python -# 链表中间插入元素 -def insertInside(self, index, val): - count = 0 - cur = self.head - while cur and count < index - 1: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - node = ListNode(val) - node.next = cur.next - cur.next = node -``` - -「链表中间插入元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间插入元素」的时间复杂度是 $O(n)$。 - -### 2.6 改变元素 - -> **将链表中第 $i$ 个元素值改为 $val$**:首先要先遍历到第 $i$ 个链节点,然后直接更改第 $i$ 个链节点的元素值。具体做法如下: -> -> 1. 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。 -> 2. 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 -> 3. 当遍历到第 $index$ 个链节点时停止遍历。 -> 4. 直接更改 $cur$ 的值 $val$。 - -**「将链表中第 $i$ 个元素值改为 $val$」** 的代码如下: - -```python -# 改变元素:将链表中第 i 个元素值改为 val -def change(self, index, val): - count = 0 - cur = self.head - while cur and count < index: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - cur.val = val -``` - -「将链表中第 $i$ 个元素值改为 $val$」需要将 $cur$ 从链表头部移动到第 $i$ 个链节点,操作的平均时间复杂度是 $O(n)$,因此,「将链表中第 $i$ 个元素值改为 $val$」的时间复杂度是 $O(n)$。 - -### 2.7 删除元素 - -链表的删除元素操作与链表的查找元素操作一样,同样分为三种情况: - -- **链表头部删除元素**:删除链表的第 $1$ 个链节点。 -- **链表尾部删除元素**:删除链表末尾最后 $1$ 个链节点。 -- **链表中间删除元素**:删除链表第 $i$ 个链节点。 - -接下来我们分别讲解一下。 - -#### 2.7.1 链表头部删除元素 - -> **链表头部删除元素**:删除链表的第 $1$ 个链节点。 -> -> 1. 直接将 $self.head$ 沿着 $next$ 指针向右移动一步即可。 - -![](https://qcdn.itcharge.cn/images/20211208180131.png) - -**「链表头部删除元素」** 的代码如下: - -```python -# 链表头部删除元素 -def removeFront(self): - if self.head: - self.head = self.head.next -``` - -「链表头部删除元」只涉及到 $1$ 步移动操作,因此,「链表头部删除元素」的时间复杂度为 $O(1)$。 - -#### 2.7.2 链表尾部删除元素 - -> **链表尾部删除元素**:删除链表末尾最后 $1$ 个链节点。 -> -> 1. 先使用指针变量 $cur$ 沿着 $next$ 指针移动到倒数第 $2$ 个链节点。 -> 2. 然后将此节点的 $next$ 指针指向 $None$ 即可。 - -![](https://qcdn.itcharge.cn/images/20211208180138.png) - -**「链表尾部删除元素」** 的代码如下: - -```python -# 链表尾部删除元素 -def removeRear(self): - if not self.head or not self.head.next: - return 'Error' - - cur = self.head - while cur.next.next: - cur = cur.next - cur.next = None -``` - -「链表尾部删除元素」的操作涉及到移动到链表尾部,操作次数为 $n - 2$ 次,因此,「链表尾部删除元素」的时间复杂度为 $O(n)$。 - -#### 2.7.3 链表中间删除元素 - -> **链表中间删除元素**:删除链表第 $i$ 个链节点。 -> -> 1. 先使用指针变量 $cur$ 移动到第 $i - 1$ 个位置的链节点。 -> 2. 然后将 $cur$ 的 $next$ 指针,指向要第 $i$ 个元素的下一个节点即可。 - -![](https://qcdn.itcharge.cn/images/20211208180144.png) - -**「链表中间删除元素」** 的代码如下: - -```python -# 链表中间删除元素 -def removeInside(self, index): - count = 0 - cur = self.head - - while cur.next and count < index - 1: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - del_node = cur.next - cur.next = del_node.next -``` - -「链表中间删除元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间删除元素」的时间复杂度是 $O(n)$。 - ---- - -到这里,有关链表的基础知识就介绍完了。下面进行一下总结。 - -## 3. 链表总结 - -链表是最基础、最简单的数据结构。**「链表」** 是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。 - -链表最大的优点在于可以灵活的添加和删除元素。 - -- 链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$。 -- 链表进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$。 -- 链表进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。 -- 链表在普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。 - -## 参考资料 - -- 【文章】[链表理论基础 - 代码随想录](https://programmercarl.com/链表理论基础.html#链表理论基础) -- 【文章】[什么是链表 - 漫画算法 - 小灰的算法之旅 - 力扣](https://leetcode.cn/leetbook/read/journey-of-algorithm/5ozchs/) -- 【文章】[链表 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41013) -- 【书籍】数据结构教程 第 2 版 - 唐发根 著 -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 diff --git a/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md b/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md deleted file mode 100644 index b7018994..00000000 --- a/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md +++ /dev/null @@ -1,17 +0,0 @@ -### 链表经典题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0707 | [设计链表](https://leetcode.cn/problems/design-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0707.%20%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.md) | 设计、链表 | 中等 | -| 0083 | [删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0083.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 链表 | 简单 | -| 0082 | [删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0082.%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 链表、双指针 | 中等 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0025 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0025.%20K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 困难 | -| 0203 | [移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0203.%20%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.md) | 递归、链表 | 简单 | -| 0328 | [奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0328.%20%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md) | 链表 | 中等 | -| 0234 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0430 | [扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0430.%20%E6%89%81%E5%B9%B3%E5%8C%96%E5%A4%9A%E7%BA%A7%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 深度优先搜索、链表、双向链表 | 中等 | -| 0138 | [复制带随机指针的链表](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0138.%20%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8.md) | 哈希表、链表 | 中等 | -| 0061 | [旋转链表](https://leetcode.cn/problems/rotate-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0061.%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 链表、双指针 | 中等 | - diff --git a/Contents/02.Linked-List/01.Linked-List-Basic/index.md b/Contents/02.Linked-List/01.Linked-List-Basic/index.md deleted file mode 100644 index c8f1269c..00000000 --- a/Contents/02.Linked-List/01.Linked-List-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [链表基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md) -- [链表经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) \ No newline at end of file diff --git a/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md b/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md deleted file mode 100644 index 169dcfd9..00000000 --- a/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md +++ /dev/null @@ -1,523 +0,0 @@ -## 1. 链表排序简介 - -在数组排序中,常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 - -而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠 `next` 指针从头部顺序遍历,所以相对于数组排序问题来说,链表排序问题会更加复杂一点。 - -下面先来总结一下适合链表排序与不适合链表排序的算法: - -- 适合链表的排序算法:**冒泡排序**、**选择排序**、**插入排序**、**归并排序**、**快速排序**、**计数排序**、**桶排序**、**基数排序**。 -- 不适合链表的排序算法:**希尔排序**。 -- 可以用于链表排序但不建议使用的排序算法:**堆排序**。 - -> 希尔排序为什么不适合链表排序? - -**希尔排序**:希尔排序中经常涉及到对序列中第 `i + gap` 的元素进行操作,其中 `gap` 是希尔排序中当前的步长。而链表不支持随机访问的特性,导致这种操作不适合链表,因而希尔排序算法不适合进行链表排序。 - -> 为什么不建议使用堆排序? - -**堆排序**:堆排序所使用的最大堆 / 最小堆结构本质上是一棵完全二叉树。而完全二叉树适合采用顺序存储结构(数组)。因为数组存储的完全二叉树可以很方便的通过下标序号来确定父亲节点和孩子节点,并且可以极大限度的节省存储空间。 - -而链表用在存储完全二叉树的时候,因为不支持随机访问的特性,导致其寻找子节点和父亲节点会比较耗时,如果增加指向父亲节点的变量,又会浪费大量存储空间。所以堆排序算法不适合进行链表排序。 - -如果一定要对链表进行堆排序,则可以使用额外的数组空间表示堆结构。然后将链表中各个节点的值依次添加入堆结构中,对数组进行堆排序。排序后,再按照堆中元素顺序,依次建立链表节点,构建新的链表并返回新链表头节点。 - -> 需要用到额外的辅助空间进行排序的算法 - -刚才我们说到如果一定要对链表进行堆排序,则需要使用额外的数组空间。除此之外,计数排序、桶排序、基数排序都需要用到额外的数组空间。 - -接下来,我们将对适合链表排序的 8 种算法进行一一讲解。当然,这些排序算法不用完全掌握,重点是掌握 **「链表插入排序」**、**「链表归并排序」** 这两种排序算法。 - -## 2. 链表冒泡排序 - -### 2.1 链表冒泡排序算法描述 - -1. 使用三个指针 `node_i`、`node_j` 和 `tail`。其中 `node_i` 用于控制外循环次数,循环次数为链节点个数(链表长度)。`node_j` 和 `tail` 用于控制内循环次数和循环结束位置。 - -2. 排序开始前,将 `node_i` 、`node_j` 置于头节点位置。`tail` 指向链表末尾,即 `None`。 - -3. 比较链表中相邻两个元素 `node_j.val` 与 `node_j.next.val` 的值大小,如果 `node_j.val > node_j.next.val`,则值相互交换。否则不发生交换。然后向右移动 `node_j` 指针,直到 `node_j.next == tail` 时停止。 - -4. 一次循环之后,将 `tail` 移动到 `node_j` 所在位置。相当于 `tail` 向左移动了一位。此时 `tail` 节点右侧为链表中最大的链节点。 - -5. 然后移动 `node_i` 节点,并将 `node_j` 置于头节点位置。然后重复第 3、4 步操作。 -6. 直到 `node_i` 节点移动到链表末尾停止,排序结束。 -7. 返回链表的头节点 `head`。 - -### 2.2 链表冒泡排序算法实现代码 - -```python -class Solution: - def bubbleSort(self, head: ListNode): - node_i = head - tail = None - # 外层循环次数为 链表节点个数 - while node_i: - node_j = head - while node_j and node_j.next != tail: - if node_j.val > node_j.next.val: - # 交换两个节点的值 - node_j.val, node_j.next.val = node_j.next.val, node_j.val - node_j = node_j.next - # 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表 - tail = node_j - node_i = node_i.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.bubbleSort(head) -``` - -### 2.3 链表冒泡排序算法复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -## 3. 链表选择排序 - -### 3.1 链表选择排序算法描述 - -1. 使用两个指针 `node_i`、`node_j`。`node_i` 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。 -2. 使用 `min_node` 记录当前未排序链表中值最小的链节点。 -3. 每一趟排序开始时,先令 `min_node = node_i`(即暂时假设链表中 `node_i` 节点为值最小的节点,经过比较后再确定最小值节点位置)。 -4. 然后依次比较未排序链表中 `node_j.val` 与 `min_node.val` 的值大小。如果 `node_j.val < min_node.val`,则更新 `min_node` 为 `node_j`。 -5. 这一趟排序结束时,未排序链表中最小值节点为 `min_node`,如果 `node_i != min_node`,则将 `node_i` 与 `min_node` 值进行交换。如果 `node_i == min_node`,则不用交换。 -6. 排序结束后,继续向右移动 `node_i`,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 `node_i` 进行比较和交换,直到 `node_i == None` 或者 `node_i.next == None` 时,停止排序。 -7. 返回链表的头节点 `head`。 - -### 3.2 链表选择排序实现代码 - -```python -class Solution: - def sectionSort(self, head: ListNode): - node_i = head - # node_i 为当前未排序链表的第一个链节点 - while node_i and node_i.next: - # min_node 为未排序链表中的值最小节点 - min_node = node_i - node_j = node_i.next - while node_j: - if node_j.val < min_node.val: - min_node = node_j - node_j = node_j.next - # 交换值最小节点与未排序链表中第一个节点的值 - if node_i != min_node: - node_i.val, min_node.val = min_node.val, node_i.val - node_i = node_i.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.sectionSort(head) -``` - -### 3.3 链表选择排序算法复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -## 4. 链表插入排序 - -### 4.1 链表插入排序算法描述 - -1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 -2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 -3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 -4. 比较 `sorted_list` 和 `cur` 的节点值。 - - - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 - - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 - -5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 -6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 - -### 4.2 链表插入排序实现代码 - -```python -class Solution: - def insertionSort(self, head: ListNode): - if not head or not head.next: - return head - - dummy_head = ListNode(-1) - dummy_head.next = head - sorted_list = head - cur = head.next - - while cur: - if sorted_list.val <= cur.val: - # 将 cur 插入到 sorted_list 之后 - sorted_list = sorted_list.next - else: - prev = dummy_head - while prev.next.val <= cur.val: - prev = prev.next - # 将 cur 到链表中间 - sorted_list.next = cur.next - cur.next = prev.next - prev.next = cur - cur = sorted_list.next - - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.insertionSort(head) -``` - -### 4.3 链表插入排序算法复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -## 5. 链表归并排序 - -### 5.1 链表归并排序算法描述 - -1. **分割环节**:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。 - 1. 使用快慢指针 `fast = head.next`、`slow = head`,让 `fast` 每次移动 `2` 步,`slow` 移动 `1` 步,移动到链表末尾,从而找到链表中心链节点,即 `slow`。 - 2. 从中心位置将链表从中心位置分为左右两个链表 `left_head` 和 `right_head`,并从中心位置将其断开,即 `slow.next = None`。 - 3. 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。 -2. **归并环节**:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。 - 1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `cur` 指向 `dummy_head` 用于遍历。 - 2. 比较两个链表头节点 `left` 和 `right` 的值大小。将较小的头节点加入到合并后的链表中,并向后移动该链表的头节点指针。 - 3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 - 4. 将剩余链表插入到合并后的链表中。 - 5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后的头节点返回。 - -### 5.2 链表归并排序实现代码 - -```python -class Solution: - def merge(self, left, right): - # 归并环节 - dummy_head = ListNode(-1) - cur = dummy_head - while left and right: - if left.val <= right.val: - cur.next = left - left = left.next - else: - cur.next = right - right = right.next - cur = cur.next - - if left: - cur.next = left - elif right: - cur.next = right - - return dummy_head.next - - def mergeSort(self, head: ListNode): - # 分割环节 - if not head or not head.next: - return head - - # 快慢指针找到中心链节点 - slow, fast = head, head.next - while fast and fast.next: - slow = slow.next - fast = fast.next.next - - # 断开左右链节点 - left_head, right_head = head, slow.next - slow.next = None - - # 归并操作 - return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.mergeSort(head) -``` - -### 5.3 链表归并排序算法复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(1)$。 - -## 6. 链表快速排序 - -### 6.1 链表快速排序算法描述 - -1. 从链表中找到一个基准值 `pivot`,这里以头节点为基准值。 -2. 然后通过快慢指针 `node_i`、`node_j` 在链表中移动,使得 `node_i` 之前的节点值都小于基准值,`node_i` 之后的节点值都大于基准值。从而把数组拆分为左右两个部分。 -3. 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。 - -### 6.2 链表快速排序实现代码 - -```python -class Solution: - def partition(self, left: ListNode, right: ListNode): - # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 - if left == right or left.next == right: - return left - # 选择头节点为基准节点 - pivot = left.val - # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 - node_i, node_j = left, left.next - - while node_j != right: - # 发现一个小与基准值的元素 - if node_j.val < pivot: - # 因为 node_i 之前节点都小于基准值,所以先将 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) - node_i = node_i.next - # 将小于基准值的元素 node_j 与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 - node_i.val, node_j.val = node_j.val, node_i.val - node_j = node_j.next - # 将基准节点放到正确位置上 - node_i.val, left.val = left.val, node_i.val - return node_i - - def quickSort(self, left: ListNode, right: ListNode): - if left == right or left.next == right: - return left - pi = self.partition(left, right) - self.quickSort(left, pi) - self.quickSort(pi.next, right) - return left - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - if not head or not head.next: - return head - return self.quickSort(head, None) -``` - -### 6.3 链表快速排序算法复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(1)$。 - -## 7. 链表计数排序 - -### 7.1 链表计数排序算法描述 - -1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 -2. 使用数组 `counts` 存储节点出现次数。 -3. 再次使用 `cur` 指针遍历一遍链表。将链表中每个值为 `cur.val` 的节点出现次数,存入数组对应第 `cur.val - list_min` 项中。 -4. 反向填充目标链表: - 1. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 - 2. 从小到大遍历一遍数组 `counts`。对于每个 `counts[i] != 0` 的元素建立一个链节点,值为 `i + list_min`,将其插入到 `cur.next` 上。并向右移动 `cur`。同时 `counts[i] -= 1`。直到 `counts[i] == 0` 后继续向后遍历数组 `counts`。 -5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 - -### 7.2 链表计数排序代码实现 - -```python -class Solution: - def countingSort(self, head: ListNode): - if not head: - return head - - # 找出链表中最大值 list_max 和最小值 list_min - list_min, list_max = float('inf'), float('-inf') - cur = head - while cur: - if cur.val < list_min: - list_min = cur.val - if cur.val > list_max: - list_max = cur.val - cur = cur.next - - size = list_max - list_min + 1 - counts = [0 for _ in range(size)] - - cur = head - while cur: - counts[cur.val - list_min] += 1 - cur = cur.next - - dummy_head = ListNode(-1) - cur = dummy_head - for i in range(size): - while counts[i]: - cur.next = ListNode(i + list_min) - counts[i] -= 1 - cur = cur.next - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.countingSort(head) -``` - -### 7.3 链表计数排序算法复杂度分析 - -- **时间复杂度**:$O(n + k)$,其中 $k$ 代表待排序链表中所有元素的值域。 -- **空间复杂度**:$O(k)$。 - -## 8. 链表桶排序 - -### 8.1 链表桶排序算法描述 - -1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 -2. 通过 `(最大值 - 最小值) / 每个桶的大小` 计算出桶的个数,即 `bucket_count = (list_max - list_min) // bucket_size + 1` 个桶。 -3. 定义数组 `buckets` 为桶,桶的个数为 `bucket_count` 个。 -4. 使用 `cur` 指针再次遍历一遍链表,将每个元素装入对应的桶中。 -5. 对每个桶内的元素单独排序,可以使用链表插入排序、链表归并排序、链表快速排序等算法。 -6. 最后按照顺序将桶内的元素拼成新的链表,并返回。 - -### 8.2 链表桶排序代码实现 - -```python -class ListNode: - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -class Solution: - # 将链表节点值 val 添加到对应桶 buckets[index] 中 - def insertion(self, buckets, index, val): - if not buckets[index]: - buckets[index] = ListNode(val) - return - - node = ListNode(val) - node.next = buckets[index] - buckets[index] = node - - # 归并环节 - def merge(self, left, right): - dummy_head = ListNode(-1) - cur = dummy_head - while left and right: - if left.val <= right.val: - cur.next = left - left = left.next - else: - cur.next = right - right = right.next - cur = cur.next - - if left: - cur.next = left - elif right: - cur.next = right - - return dummy_head.next - - def mergeSort(self, head: ListNode): - # 分割环节 - if not head or not head.next: - return head - - # 快慢指针找到中心链节点 - slow, fast = head, head.next - while fast and fast.next: - slow = slow.next - fast = fast.next.next - - # 断开左右链节点 - left_head, right_head = head, slow.next - slow.next = None - - # 归并操作 - return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) - - def bucketSort(self, head: ListNode, bucket_size=5): - if not head: - return head - - # 找出链表中最大值 list_max 和最小值 list_min - list_min, list_max = float('inf'), float('-inf') - cur = head - while cur: - if cur.val < list_min: - list_min = cur.val - if cur.val > list_max: - list_max = cur.val - cur = cur.next - - # 计算桶的个数,并定义桶 - bucket_count = (list_max - list_min) // bucket_size + 1 - buckets = [None for _ in range(bucket_count)] - - # 将链表节点值依次添加到对应桶中 - cur = head - while cur: - index = (cur.val - list_min) // bucket_size - self.insertion(buckets, index, cur.val) - cur = cur.next - - dummy_head = ListNode(-1) - cur = dummy_head - # 将元素依次出桶,并拼接成有序链表 - for bucket_head in buckets: - bucket_cur = self.mergeSort(bucket_head) - while bucket_cur: - cur.next = bucket_cur - cur = cur.next - bucket_cur = bucket_cur.next - - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.bucketSort(head) -``` - -### 8.3 链表桶排序算法复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 - -## 9. 链表基数排序 - -### 9.1 链表基数排序算法描述 - -1. 使用 `cur` 指针遍历链表,获取节点值位数最长的位数 `size`。 -2. 从个位到高位遍历位数。因为 `0` ~ `9` 共有 `10` 位数字,所以建立 `10` 个桶。 -3. 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。 -4. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 -5. 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。 -6. 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。 -7. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 - -### 9.2 链表基数排序代码实现 - -```python -class Solution: - def radixSort(self, head: ListNode): - # 计算位数最长的位数 - size = 0 - cur = head - while cur: - val_len = len(str(cur.val)) - if val_len > size: - size = val_len - cur = cur.next - - # 从个位到高位遍历位数 - for i in range(size): - buckets = [[] for _ in range(10)] - cur = head - while cur: - # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中 - buckets[cur.val // (10 ** i) % 10].append(cur.val) - cur = cur.next - - # 生成新的链表 - dummy_head = ListNode(-1) - cur = dummy_head - for bucket in buckets: - for num in bucket: - cur.next = ListNode(num) - cur = cur.next - head = dummy_head.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.radixSort(head) -``` - -### 9.3 链表基数排序算法复杂度分析 - -- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 -- **空间复杂度**:$O(n + k)$。 - -## 参考资料 - -- 【文章】[单链表的冒泡排序_zhao_miao的博客 - CSDN博客](https://blog.csdn.net/zhao_miao/article/details/81708454) -- 【文章】[链表排序总结(全)(C++)- 阿祭儿 - CSDN博客](https://blog.csdn.net/qq_32523711/article/details/107402873) -- 【题解】[快排、冒泡、选择排序实现列表排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/kuai-pai-mou-pao-xuan-ze-pai-xu-shi-xian-ula7/) -- 【题解】[归并排序+快速排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/gui-bing-pai-xu-kuai-su-pai-xu-by-datacruiser/) -- 【题解】[排序链表(递归+迭代)详解 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/pai-xu-lian-biao-di-gui-die-dai-xiang-jie-by-cherr/) -- 【题解】[Sort List (归并排序链表) - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) \ No newline at end of file diff --git a/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md b/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md deleted file mode 100644 index bde4212f..00000000 --- a/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md +++ /dev/null @@ -1,9 +0,0 @@ -### 链表排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0148 | [排序链表](https://leetcode.cn/problems/sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0148.%20%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、双指针、分治、排序、归并排序 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0147 | [对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0147.%20%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md) | 链表、排序 | 中等 | - diff --git a/Contents/02.Linked-List/02.Linked-List-Sort/index.md b/Contents/02.Linked-List/02.Linked-List-Sort/index.md deleted file mode 100644 index 772df309..00000000 --- a/Contents/02.Linked-List/02.Linked-List-Sort/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [链表排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md) -- [链表排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) \ No newline at end of file diff --git a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md b/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md deleted file mode 100644 index 95028f79..00000000 --- a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md +++ /dev/null @@ -1,409 +0,0 @@ -## 1. 双指针简介 - -在数组双指针中我们已经学习过了双指针的概念。这里再来复习一下。 - -> **双指针(Two Pointers)**:指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞时针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 - -而在单链表中,因为遍历节点只能顺着 `next` 指针方向进行,所以对于链表而言,一般只会用到「快慢指针」和「分离双指针」。其中链表的「快慢指针」又分为「起点不一致的快慢指针」和「步长不一致的快慢指针」。这几种类型的双指针所解决的问题也各不相同,下面我们一一进行讲解。 - -## 2. 起点不一致的快慢指针 - ->**起点不一致的快慢指针**:指的是两个指针从同一侧开始遍历链表,但是两个指针的起点不一样。 快指针 `fast` 比慢指针 `slow` 先走 `n` 步,直到快指针移动到链表尾端时为止。 - -### 2.1 起点不一致的快慢指针求解步骤 - -1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点,即:`slow = head`,`fast = head`。 -2. 先将快指针向右移动 `n` 步。然后再同时向右移动快、慢指针。 -3. 等到快指针移动到链表尾部(即 `fast == None`)时跳出循环体。 - -### 2.2 起点不一致的快慢指针伪代码模板 - -```python -slow = head -fast = head - -while n: - fast = fast.next - n -= 1 -while fast: - fast = fast.next - slow = slow.next -``` - -### 2.3 起点不一致的快慢指针适用范围 - -起点不一致的快慢指针主要用于找到链表中倒数第 k 个节点、删除链表倒数第 N 个节点等。 - -### 2.4 删除链表的倒数第 N 个结点 - -#### 2.4.1 题目链接 - -- [19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) - -#### 2.4.2 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。 - -**说明**: - -- 要求使用一次遍历实现。 -- 链表中结点的数目为 `sz`。 -- $1 \le sz \le 30$。 -- $0 \le Node.val \le 100$。 -- $1 \le n \le sz$。 - -**示例**: - -![](https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg) - -```python -输入:head = [1,2,3,4,5], n = 2 -输出:[1,2,3,5] - - -输入:head = [1], n = 1 -输出:[] -``` - -#### 2.4.3 解题思路 - -##### 思路 1:快慢指针 - -常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 - -如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `n` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `n` 个节点位置。将该位置上的节点删除即可。 - -需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 - -##### 思路 1:代码 - -```python -class Solution: - def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: - newHead = ListNode(0, head) - fast = head - slow = newHead - while n: - fast = fast.next - n -= 1 - while fast: - fast = fast.next - slow = slow.next - slow.next = slow.next.next - return newHead.next -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 3. 步长不一致的快慢指针 - -> **步长不一致的快慢指针**:指的是两个指针从同一侧开始遍历链表,两个指针的起点一样,但是步长不一致。例如,慢指针 `slow` 每次走 `1` 步,快指针 `fast` 每次走两步。直到快指针移动到链表尾端时为止。 - -### 3.1 步长不一致的快慢指针求解步骤 - -1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 -2. 在循环体中将快、慢指针同时向右移动,但是快、慢指针的移动步长不一致。比如将慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 -3. 等到快指针移动到链表尾部(即 `fast == None`)时跳出循环体。 - -### 3.2 步长不一致的快慢指针伪代码模板 - -```python -fast = head -slow = head - -while fast and fast.next: - slow = slow.next - fast = fast.next.next -``` - -### 3.3 步长不一致的快慢指针适用范围 - -步长不一致的快慢指针适合寻找链表的中点、判断和检测链表是否有环、找到两个链表的交点等问题。 - -### 3.4 链表的中间结点 - -#### 3.4.1 题目链接 - -- [876. 链表的中间结点 - 力扣(LeetCode)](https://leetcode.cn/problems/middle-of-the-linked-list/) - -#### 3.4.2 题目大意 - -**描述**:给定一个单链表的头节点 `head`。 - -**要求**:返回链表的中间节点。如果有两个中间节点,则返回第二个中间节点。 - -**说明**: - -- 给定链表的结点数介于 `1` 和 `100` 之间。 - -**示例**: - -```python -输入:[1,2,3,4,5] -输出:此列表中的结点 3 (序列化形式:[3,4,5]) -解释:返回的结点值为 3 。 -注意,我们返回了一个 ListNode 类型的对象 ans,这样: -ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. - - -输入:[1,2,3,4,5,6] -输出:此列表中的结点 4 (序列化形式:[4,5,6]) -解释:由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 -``` - -#### 3.4.3 解题思路 - -##### 思路 1:单指针 - -先遍历一遍链表,统计一下节点个数为 `n`,再遍历到 `n / 2` 的位置,返回中间节点。 - -##### 思路 1:代码 - -```python -class Solution: - def middleNode(self, head: ListNode) -> ListNode: - n = 0 - curr = head - while curr: - n += 1 - curr = curr.next - k = 0 - curr = head - while k < n // 2: - k += 1 - curr = curr.next - return curr -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -##### 思路 2:快慢指针 - -使用步长不一致的快慢指针进行一次遍历找到链表的中间节点。具体做法如下: - -1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 -2. 在循环体中将快、慢指针同时向右移动。其中慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 -3. 等到快指针移动到链表尾部(即 `fast == Node`)时跳出循环体,此时 `slow` 指向链表中间位置。 -4. 返回 `slow` 指针。 - -##### 思路 2:代码 - -```python -class Solution: - def middleNode(self, head: ListNode) -> ListNode: - fast = head - slow = head - while fast and fast.next: - slow = slow.next - fast = fast.next.next - return slow -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 3.5 判断链表中是否含有环 - -#### 3.5.1 题目链接 - -- [141. 环形链表 - 力扣(LeetCode)](https://leetcode.cn/problems/linked-list-cycle/) - -#### 3.5.2 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:判断链表中是否有环。如果有环则返回 `True`,否则返回 `False`。 - -**说明**: - -- 链表中节点的数目范围是 $[0, 10^4]$。 -- $-10^5 \le Node.val \le 10^5$。 -- `pos` 为 `-1` 或者链表中的一个有效索引。 - -**示例**: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) - -```python -输入:head = [3,2,0,-4], pos = 1 -输出:True -解释:链表中有一个环,其尾部连接到第二个节点。 -``` - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) - -```python -输入:head = [1,2], pos = 0 -输出:True -解释:链表中有一个环,其尾部连接到第一个节点。 -``` - -#### 3.5.3 解题思路 - -##### 思路 1:哈希表 - -最简单的思路是遍历所有节点,每次遍历节点之前,使用哈希表判断该节点是否被访问过。如果访问过就说明存在环,如果没访问过则将该节点添加到哈希表中,继续遍历判断。 - -##### 思路 1:代码 - -```python -class Solution: - def hasCycle(self, head: ListNode) -> bool: - nodeset = set() - - while head: - if head in nodeset: - return True - nodeset.add(head) - head = head.next - return False -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -##### 思路 2:快慢指针(Floyd 判圈算法) - -这种方法类似于在操场跑道跑步。两个人从同一位置同时出发,如果跑道有环(环形跑道),那么快的一方总能追上慢的一方。 - -基于上边的想法,Floyd 用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 - -##### 思路 2:代码 - -```python -class Solution: - def hasCycle(self, head: ListNode) -> bool: - if head == None or head.next == None: - return False - - slow = head - fast = head.next - - while slow != fast: - if fast == None or fast.next == None: - return False - slow = slow.next - fast = fast.next.next - - return True -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -## 4. 分离双指针 - -> **分离双指针**:两个指针分别属于不同的链表,两个指针分别在两个链表中移动。 - -### 4.1 分离双指针求解步骤 - -1. 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个链表头节点,即:`left_1 = list1`,`left_2` 指向第二个链表头节点,即:`left_2 = list2`。 -2. 当满足一定条件时,两个指针同时右移,即 `left_1 = left_1.next`、`left_2 = left_2.next`。 -3. 当满足另外一定条件时,将 `left_1` 指针右移,即 `left_1 = left_1.next`。 -4. 当满足其他一定条件时,将 `left_2` 指针右移,即 `left_2 = left_2.next`。 -5. 当其中一个链表遍历完时或者满足其他特殊条件时跳出循环体。 - -### 4.2 分离双指针伪代码模板 - -```python -left_1 = list1 -left_2 = list2 - -while left_1 and left_2: - if 一定条件 1: - left_1 = left_1.next - left_2 = left_2.next - elif 一定条件 2: - left_1 = left_1.next - elif 一定条件 3: - left_2 = left_2.next -``` - -### 4.3 分离双指针适用范围 - -分离双指针一般用于有序链表合并等问题。 - -### 4.4 合并两个有序链表 - -#### 4.4.1 题目链接 - -- [21. 合并两个有序链表 - 力扣(LeetCode)](https://leetcode.cn/problems/merge-two-sorted-lists/) - -#### 4.4.2 题目大意 - -**描述**:给定两个升序链表的头节点 `list1` 和 `list2`。 - -**要求**:将其合并为一个升序链表。 - -**说明**: - -- 两个链表的节点数目范围是 $[0, 50]$。 -- $-100 \le Node.val \le 100$。 -- `list1` 和 `list2` 均按 **非递减顺序** 排列 - -**示例**: - -![](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) - -```python -输入:list1 = [1,2,4], list2 = [1,3,4] -输出:[1,1,2,3,4,4] - - -输入:list1 = [], list2 = [] -输出:[] -``` - -#### 4.4.3 解题思路 - -##### 思路 1:归并排序 - -利用归并排序的思想,具体步骤如下: - -1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `curr` 指向 `dummy_head` 用于遍历。 -2. 然后判断 `list1` 和 `list2` 头节点的值,将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 -3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 -4. 将剩余链表链接到合并后的链表中。 -5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后有序链表的头节点返回。 - -##### 思路 1:代码 - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - dummy_head = ListNode(-1) - - curr = dummy_head - while list1 and list2: - if list1.val <= list2.val: - curr.next = list1 - list1 = list1.next - else: - curr.next = list2 - list2 = list2.next - curr = curr.next - - curr.next = list1 if list1 is not None else list2 - - return dummy_head.next -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md b/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md deleted file mode 100644 index f35368d5..00000000 --- a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md +++ /dev/null @@ -1,14 +0,0 @@ -### 链表双指针题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0141 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0141.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0142 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0142.%20%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%20II.md) | 哈希表、链表、双指针 | 中等 | -| 0160 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0160.%20%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8.md) | 哈希表、链表、双指针 | 简单 | -| 0019 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0019.%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 中等 | -| 0876 | [链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0876.%20%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E7%BB%93%E7%82%B9.md) | 链表、双指针 | 简单 | -| 剑指 Offer 22 | [链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2022.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md) | 链表、双指针 | 简单 | -| 0143 | [重排链表](https://leetcode.cn/problems/reorder-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0143.%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 中等 | -| 0002 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0002.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0.md) | 递归、链表、数学 | 中等 | -| 0445 | [两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0445.%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 栈、链表、数学 | 中等 | - diff --git a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/index.md b/Contents/02.Linked-List/03.Linked-List-Two-Pointers/index.md deleted file mode 100644 index df50efdc..00000000 --- a/Contents/02.Linked-List/03.Linked-List-Two-Pointers/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [链表双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md) -- [链表双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) \ No newline at end of file diff --git a/Contents/02.Linked-List/index.md b/Contents/02.Linked-List/index.md deleted file mode 100644 index 880bf9a0..00000000 --- a/Contents/02.Linked-List/index.md +++ /dev/null @@ -1,17 +0,0 @@ -## 本章内容 - -### 链表基础知识 - -- [链表基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md) -- [链表经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) - -### 链表排序 - -- [链表排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md) -- [链表排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) - -### 链表双指针 - -- [链表双指针知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md) -- [链表双指针题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - diff --git a/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md b/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md deleted file mode 100644 index f7b7f95e..00000000 --- a/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md +++ /dev/null @@ -1,356 +0,0 @@ -## 1. 堆栈简介 - -> **堆栈(Stack)**:简称为栈。一种线性表数据结构,是一种只允许在表的一端进行插入和删除操作的线性表。 - -我们把栈中允许插入和删除的一端称为 **「栈顶(top)」**;另一端则称为 **「栈底(bottom)」**。当表中没有任何数据元素时,称之为 **「空栈」**。 - -堆栈有两种基本操作:**「插入操作」** 和 **「删除操作」**。 - -- 栈的插入操作又称为「入栈」或者「进栈」。 -- 栈的删除操作又称为「出栈」或者「退栈」。 - -![](https://qcdn.itcharge.cn/images/20211202095938.png) - -简单来说,栈是一种 **「后进先出(Last In First Out)」** 的线性表,简称为 **「LIFO 结构」**。 - -我们可以从两个方面来解释一下栈的定义: - -- 第一个方面是 **「线性表」**。 - -栈首先是一个线性表,栈中元素具有前驱后继的线性关系。栈中元素按照 $a_1, a_2, ... , a_n$ 的次序依次进栈。栈顶元素为 $a_n$。 - -- 第二个方面是 **「后进先出原则」**。 - -根据堆栈的定义,每次删除的总是堆栈中当前的栈顶元素,即最后进入堆栈的元素。而在进栈时,最先进入堆栈的元素一定在栈底,最后进入堆栈的元素一定在栈顶。也就是说,元素进入堆栈或者退出退栈是按照「后进先出(Last In First Out)」的原则进行的。 - -## 2. 堆栈的顺序存储与链式存储 - -和线性表类似,栈有两种存储表示方法:**「顺序栈」** 和 **「链式栈」**。 - -- **「顺序栈」**:即堆栈的顺序存储结构。利用一组地址连续的存储单元依次存放自栈底到栈顶的元素,同时使用指针 `top` 指示栈顶元素在顺序栈中的位置。 -- **「链式栈」**:即堆栈的链式存储结构。利用单链表的方式来实现堆栈。栈中元素按照插入顺序依次插入到链表的第一个节点之前,并使用栈顶指针 `top` 指示栈顶元素,`top` 永远指向链表的头节点位置。 - -在描述堆栈的顺序存储与链式存储具体实现之前,我们先来看看堆栈具有哪些基本操作。 - -### 2.1 堆栈的基本操作 - -栈作为一种线性表来说,理论上应该具备线性表所有的操作特性,但由于「后进先出」的特殊性,所以针对栈的操作进行了一些变化。尤其是插入操作和删除操作,改为了入栈(push)和出栈(pop)。 - -堆栈的基本操作如下: - -- **初始化空栈**:创建一个空栈,定义栈的大小 `size`,以及栈顶元素指针 `top`。 - -- **判断栈是否为空**:当堆栈为空时,返回 `True`。当堆栈不为空时,返回 `False`。一般只用于栈中删除操作和获取当前栈顶元素操作中。 - -- **判断栈是否已满**:当堆栈已满时,返回 `True`,当堆栈未满时,返回 `False`。一般只用于顺序栈中插入元素和获取当前栈顶元素操作中。 - -- **插入元素(进栈、入栈)**:相当于在线性表最后元素后面插入一个新的数据元素。并改变栈顶指针 `top` 的指向位置。 - -- **删除元素(出栈、退栈)**:相当于在线性表最后元素后面删除最后一个数据元素。并改变栈顶指针 `top` 的指向位置。 -- **获取栈顶元素**:相当于获取线性表中最后一个数据元素。与插入元素、删除元素不同的是,该操作并不改变栈顶指针 `top` 的指向位置。 - -接下来我们来看一下栈的顺序存储与链式存储两种不同的实现方式。 - -### 2.2 堆栈的顺序存储实现 - -堆栈最简单的实现方式就是借助于一个数组来描述堆栈的顺序存储结构。在 `Python` 中我们可以借助列表 `list` 来实现。这种采用顺序存储结构的堆栈也被称为 **「顺序栈」**。 - -#### 2.2.1 堆栈的顺序存储基本描述 - -![](https://qcdn.itcharge.cn/images/20211202101936.png) - -我们约定 `self.top` 指向栈顶元素所在位置。 - -- **初始化空栈**:使用列表创建一个空栈,定义栈的大小 `self.size`,并令栈顶元素指针 `self.top` 指向 `-1`,即 `self.top = -1`。 -- **判断栈是否为空**:当 `self.top == -1` 时,说明堆栈为空,返回 `True`,否则返回 `False`。 -- **判断栈是否已满**:当 `self.top == self.size - 1`,说明堆栈已满,返回 `True`,否则返回返回 `False`。 -- **插入元素(进栈、入栈)**:先判断堆栈是否已满,已满直接抛出异常。如果堆栈未满,则在 `self.stack` 末尾插入新的数据元素,并令 `self.top` 向右移动 `1` 位。 -- **删除元素(出栈、退栈)**:先判断堆栈是否为空,为空直接抛出异常。如果堆栈不为空,则删除 `self.stack` 末尾的数据元素,并令 `self.top` 向左移动 `1` 位。 -- **获取栈顶元素**:先判断堆栈是否为空,为空直接抛出异常。不为空则返回 `self.top` 指向的栈顶元素,即 `self.stack[self.top]`。 - -#### 2.2.2 堆栈的顺序存储实现代码 - -```python -class Stack: - # 初始化空栈 - def __init__(self, size=100): - self.stack = [] - self.size = size - self.top = -1 - - # 判断栈是否为空 - def is_empty(self): - return self.top == -1 - - # 判断栈是否已满 - def is_full(self): - return self.top + 1 == self.size - - # 入栈操作 - def push(self, value): - if self.is_full(): - raise Exception('Stack is full') - else: - self.stack.append(value) - self.top += 1 - - # 出栈操作 - def pop(self): - if self.is_empty(): - raise Exception('Stack is empty') - else: - self.stack.pop() - self.top -= 1 - - # 获取栈顶元素 - def peek(self): - if self.is_empty(): - raise Exception('Stack is empty') - else: - return self.stack[self.top] -``` - -### 2.3 堆栈的链式存储实现 - -堆栈的顺序存储结构保留着顺序存储分配空间的固有缺陷,即在栈满或者其他需要重新调整存储空间时需要移动大量元素。为此,堆栈可以采用链式存储方式来实现。在 `Python` 中我们通过构造链表节点 `Node` 的方式来实现。这种采用链式存储结构的堆栈也被称为 **「链式栈」**。 - -![](https://qcdn.itcharge.cn/images/20211202103327.png) - -#### 2.3.1 堆栈的链式存储基本描述 - -我们约定 `self.top` 指向栈顶元素所在位置。 - -- **初始化空栈**:使用列表创建一个空栈,并令栈顶元素指针 `self.top` 指向 `None`,即 `self.top = None`。 -- **判断栈是否为空**:当 `self.top == None` 时,说明堆栈为空,返回 `True`,否则返回 `False`。 -- **插入元素(进栈、入栈)**:创建值为 `value` 的链表节点,插入到链表头节点之前,并令栈顶指针 `self.top` 指向新的头节点。 -- **删除元素(出栈、退栈)**:先判断堆栈是否为空,为空直接抛出异常。如果堆栈不为空,则先使用变量 `cur` 存储当前栈顶指针 `self.top` 指向的头节点,然后令 `self.top` 沿着链表移动 `1` 位,然后再删除之前保存的 `cur` 节点。 -- **获取栈顶元素**:先判断堆栈是否为空,为空直接抛出异常。不为空则返回 `self.top` 指向的栈顶节点的值,即 `self.top.value`。 - -#### 2.3.2 堆栈的链式存储实现代码 - -```python -class Node: - def __init__(self, value): - self.value = value - self.next = None - -class Stack: - # 初始化空栈 - def __init__(self): - self.top = None - - # 判断栈是否为空 - def is_empty(self): - return self.top == None - - # 入栈操作 - def push(self, value): - cur = Node(value) - cur.next = self.top - self.top = cur - - # 出栈操作 - def pop(self): - if self.is_empty(): - raise Exception('Stack is empty') - else: - cur = self.top - self.top = self.top.next - del cur - - # 获取栈顶元素 - def peek(self): - if self.is_empty(): - raise Exception('Stack is empty') - else: - return self.top.value -``` - -## 3. 堆栈的应用 - -堆栈是算法和程序中最常用的辅助结构,其的应用十分广泛。堆栈基本应用于两个方面: - -- 使用堆栈可以很方便的保存和取用信息,因此长被用作算法和程序中的辅助存储结构,临时保存信息,供后面操作中使用。 - - 例如:操作系统中的函数调用栈,浏览器中的前进、后退功能。 -- 堆栈的后进先出规则,可以保证特定的存取顺序。 - - 例如:翻转一组元素的顺序、铁路列车车辆调度。 - -下面我们来讲解一下栈应用的典型例子。 - -### 3.1 括号匹配问题 - -#### 3.1.1 题目链接 - -- [20. 有效的括号 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-parentheses/) - -#### 3.1.2 题目大意 - -**描述**:给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串 `s` 。 - -**要求**:判断字符串 `s` 是否有效(即括号是否匹配)。 - -**说明**: - -- 有效字符串需满足: - 1. 左括号必须用相同类型的右括号闭合。 - 2. 左括号必须以正确的顺序闭合。 - -**示例**: - -```python -输入:s = "()" -输出:True - - -输入:s = "()[]{}" -输出:True -``` - -#### 3.2.3 解题思路 - -##### 思路 1:栈 - -括号匹配是「栈」的经典应用。我们可以用栈来解决这道题。具体做法如下: - -1. 先判断一下字符串的长度是否为偶数。因为括号是成对出现的,所以字符串的长度应为偶数,可以直接判断长度为奇数的字符串不匹配。如果字符串长度为奇数,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 -2. 使用栈 `stack` 来保存未匹配的左括号。然后依次遍历字符串 `s` 中的每一个字符。 - 1. 如果遍历到左括号时,将其入栈。 - 2. 如果遍历到右括号时,先看栈顶元素是否是与当前右括号相同类型的左括号。 - 1. 如果是与当前右括号相同类型的左括号,则令其出栈,继续向前遍历。 - 2. 如果不是与当前右括号相同类型的左括号,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 -3. 遍历完,还要再判断一下栈是否为空。 - 1. 如果栈为空,则说明字符串 `s` 中的括号匹配,返回 `True`。 - 2. 如果栈不为空,则说明字符串 `s` 中的括号不匹配,返回 `False`。 - -##### 思路 1:代码 - -```python -class Solution: - def isValid(self, s: str) -> bool: - if len(s) % 2 == 1: - return False - stack = list() - for ch in s: - if ch == '(' or ch == '[' or ch == '{': - stack.append(ch) - elif ch == ')': - if len(stack) !=0 and stack[-1] == '(': - stack.pop() - else: - return False - elif ch == ']': - if len(stack) !=0 and stack[-1] == '[': - stack.pop() - else: - return False - elif ch == '}': - if len(stack) !=0 and stack[-1] == '{': - stack.pop() - else: - return False - if len(stack) == 0: - return True - else: - return False -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 3.2 表达式求值问题 - -#### 3.2.1 题目链接 - -- [227. 基本计算器 II - 力扣(LeetCode)](https://leetcode.cn/problems/basic-calculator-ii/) - -#### 3.2.2 题目大意 - -**描述**:给定一个字符串表达式 `s`,表达式中所有整数为非负整数,运算符只有 `+`、`-`、`*`、`/`,没有括号。 - -**要求**:实现一个基本计算器来计算并返回它的值。 - -**说明**: - -- $1 \le s.length \le 3 * 10^5$。 -- `s` 由整数和算符(`+`、`-`、`*`、`/`)组成,中间由一些空格隔开。 -- `s` 表示一个有效表达式。 -- 表达式中的所有整数都是非负整数,且在范围 $[0, 2^{31} - 1]$ 内。 -- 题目数据保证答案是一个 32-bit 整数。 - -**示例**: - -```python -输入:s = "3+2*2" -输出:7 - - -输入:s = " 3/2 " -输出:1 -``` - -#### 3.2.3 解题思路 - -##### 思路 1:栈 - -计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 - -可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 - -具体做法: - -1. 遍历字符串 `s`,使用变量 `op` 来标记数字之前的运算符,默认为 `+`。 -2. 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 - 1. 如果 `op` 为 `+`,则将 `num` 压入栈中。 - 2. 如果 `op` 为 `-`,则将 `-num` 压入栈中。 - 3. 如果 `op` 为 `*`,则将栈顶元素 `top` 取出,计算 `top * num`,并将计算结果压入栈中。 - 4. 如果 `op` 为 `/`,则将栈顶元素 `top` 取出,计算 `int(top / num)`,并将计算结果压入栈中。 -3. 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 `op`。 -4. 最后将栈中整数进行累加,并返回结果。 - -##### 思路 1:代码 - -```python -class Solution: - def calculate(self, s: str) -> int: - size = len(s) - stack = [] - op = '+' - index = 0 - while index < size: - if s[index] == ' ': - index += 1 - continue - if s[index].isdigit(): - num = ord(s[index]) - ord('0') - while index + 1 < size and s[index+1].isdigit(): - index += 1 - num = 10 * num + ord(s[index]) - ord('0') - if op == '+': - stack.append(num) - elif op == '-': - stack.append(-num) - elif op == '*': - top = stack.pop() - stack.append(top * num) - elif op == '/': - top = stack.pop() - stack.append(int(top / num)) - elif s[index] in "+-*/": - op = s[index] - index += 1 - return sum(stack) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 -- 【书籍】数据结构教程 第 3 版 - 唐发根 著 -- 【书籍】大话数据结构 程杰 著 -- 【文章】[栈 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41222) diff --git a/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md b/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md deleted file mode 100644 index 369efe75..00000000 --- a/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md +++ /dev/null @@ -1,18 +0,0 @@ -### 堆栈基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1047 | [删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1047.%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md) | 栈、字符串 | 简单 | -| 0155 | [最小栈](https://leetcode.cn/problems/min-stack/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0155.%20%E6%9C%80%E5%B0%8F%E6%A0%88.md) | 栈、设计 | 中等 | -| 0020 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0020.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md) | 栈、字符串 | 简单 | -| 0227 | [基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0227.%20%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II.md) | 栈、数学、字符串 | 中等 | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0150 | [逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0150.%20%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.md) | 栈、数组、数学 | 中等 | -| 0232 | [用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0232.%20%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 剑指 Offer 09 | [用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2009.%20%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 栈、设计、队列 | 简单 | -| 0394 | [字符串解码](https://leetcode.cn/problems/decode-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0394.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%A7%A3%E7%A0%81.md) | 栈、递归、字符串 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0946 | [验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0946.%20%E9%AA%8C%E8%AF%81%E6%A0%88%E5%BA%8F%E5%88%97.md) | 栈、数组、模拟 | 中等 | -| 剑指 Offer 06 | [从尾到头打印链表](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2006.%20%E4%BB%8E%E5%B0%BE%E5%88%B0%E5%A4%B4%E6%89%93%E5%8D%B0%E9%93%BE%E8%A1%A8.md) | 栈、递归、链表、双指针 | 简单 | -| 0071 | [简化路径](https://leetcode.cn/problems/simplify-path/) | | 栈、字符串 | 中等 | - diff --git a/Contents/03.Stack/01.Stack-Basic/index.md b/Contents/03.Stack/01.Stack-Basic/index.md deleted file mode 100644 index e5e04e07..00000000 --- a/Contents/03.Stack/01.Stack-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [堆栈基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md) -- [堆栈基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) \ No newline at end of file diff --git a/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md b/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md deleted file mode 100644 index fbc70c07..00000000 --- a/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md +++ /dev/null @@ -1,276 +0,0 @@ -## 1. 单调栈简介 - -> **单调栈(Monotone Stack)**:一种特殊的栈。在栈的「先进后出」规则基础上,要求「从 **栈顶** 到 **栈底** 的元素是单调递增(或者单调递减)」。其中满足从栈顶到栈底的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」。 - -注意:这里定义的顺序是从「栈顶」到「栈底」。有的文章里是反过来的。本文全文以「栈顶」到「栈底」的顺序为基准来描述单调栈。 - -### 1.1 单调递增栈 - -> **单调递增栈**:只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。 -> -> 这样就保证了:栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的。 - -单调递增栈的入栈、出栈过程如下: - -- 假设当前进栈元素为 `x`,如果 `x` 比栈顶元素小,则直接入栈。 -- 否则从栈顶开始遍历栈中元素,把小于 `x` 或者等于 `x` 的元素弹出栈,直到遇到一个大于 `x` 的元素为止,然后再把 `x` 压入栈中。 - -下面我们以数组 `[2, 7, 5, 4, 6, 3, 4, 2]` 为例,模拟一下「单调递增栈」的进栈、出栈过程。具体过程如下: - -- 数组元素:`[2, 7, 5, 4, 6, 3, 4, 2]`,遍历顺序为从左到右。 - -| 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作 用 | -| :-----: | :--------: | ---------------------- | ------------------- | ------------------------------------- | -| 1 | 2 | 2 入栈 | [2] | 元素 2 的左侧无比 2 大的元素 | -| 2 | 7 | 2 出栈,7 入栈 | [7] | 元素 7 的左侧无比 7 大的元素 | -| 3 | 5 | 5 入栈 | [7, 5] | 元素 5 的左侧第一个比 5 大的元素为:7 | -| 4 | 4 | 4 入栈 | [7, 5, 4] | 元素 4 的左侧第一个比 4 大的元素为:5 | -| 5 | 6 | 4 出栈,5 出栈,6 入栈 | [7, 6] | 元素 6 的左侧第一个比 6 大的元素为:7 | -| 6 | 3 | 3 入栈 | [7, 6, 3] | 元素 3 的左侧第一个比 3 大的元素为:6 | -| 7 | 4 | 3 出栈,4 入栈 | [7, 6, 4] | 元素 4 的左侧第一个比 4 大的元素为:6 | -| 8 | 2 | 2 入栈 | [7, 6, 4, 2] | 元素 2 的左侧第一个比 2 大的元素为:4 | - -最终栈中元素为 `[7, 6, 4, 2]`。因为从栈顶(右端)到栈底(左侧)元素的顺序为 `2, 4, 6, 7`,满足递增关系,所以这是一个单调递增栈。 - -我们以上述过程第 5 步为例,所对应的图示过程为: - -![](https://qcdn.itcharge.cn/images/20220107101219.png) - - -### 1.2 单调递减栈 - -> **单调递减栈**:只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈。 -> -> 这样就保证了:栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递减的。 - -单调递减栈的入栈、出栈过程如下: - -- 假设当前进栈元素为 `x`,如果 `x` 比栈顶元素大,则直接入栈。 -- 否则从栈顶开始遍历栈中元素,把大于 `x` 或者等于 `x` 的元素弹出栈,直到遇到一个小于 `x` 的元素为止,然后再把 `x` 压入栈中。 - -下面我们以数组 `[4, 3, 2, 5, 7, 4, 6, 8]` 为例,模拟一下「单调递减栈」的进栈、出栈过程。具体过程如下: - -- 数组元素:`[4, 3, 2, 5, 7, 4, 6, 8]`,遍历顺序为从左到右。 - -| 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作用 | -| :-----: | :--------: | ---------------------- | ------------------- | ------------------------------------- | -| 1 | 4 | 4 入栈 | [4] | 元素 4 的左侧无比 4 小的元素 | -| 2 | 3 | 4 出栈,3 入栈 | [3] | 元素 3 的左侧无比 3 小的元素 | -| 3 | 2 | 3 出栈,2 入栈 | [2] | 元素 2 的左侧无比 2 小的元素 | -| 4 | 5 | 5 入栈 | [2, 5] | 元素 5 的左侧第一个比 5 小的元素是:2 | -| 5 | 7 | 7 入栈 | [2, 5, 7] | 元素 7 的左侧第一个比 7 小的元素是:5 | -| 6 | 4 | 7 出栈,5 出栈,4 入栈 | [2, 4] | 元素 4 的左侧第一个比 4 小的元素是:2 | -| 7 | 6 | 6 入栈 | [2, 4, 6] | 元素 6 的左侧第一个比 6 小的元素是:4 | -| 8 | 8 | 8 入栈 | [2, 4, 6, 8] | 元素 8 的左侧第一个比 8 小的元素是:6 | - -最终栈中元素为 `[2, 4, 6, 8]`。因为从栈顶(右端)到栈底(左侧)元素的顺序为 `8, 6, 4, 2`,满足递减关系,所以这是一个单调递减栈。 - -我们以上述过程第 6 步为例,所对应的图示过程为: - -![](https://qcdn.itcharge.cn/images/20220107102446.png) - -## 2. 单调栈适用场景 - -单调栈可以在时间复杂度为 $O(n)$ 的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。 - -所以单调栈一般用于解决一下几种问题: - -- 寻找左侧第一个比当前元素大的元素。 -- 寻找左侧第一个比当前元素小的元素。 -- 寻找右侧第一个比当前元素大的元素。 -- 寻找右侧第一个比当前元素小的元素。 - -下面分别说一下这几种问题的求解方法。 - -### 2.1 寻找左侧第一个比当前元素大的元素 - -- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增): - - 一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。 - - 如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。 - - -### 2.2 寻找左侧第一个比当前元素小的元素 - -- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减): - - 一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。 - - 如果插入时的栈为空,则说明左侧不存在比当前元素小的元素。 - - -### 2.3 寻找右侧第一个比当前元素大的元素 - -- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增): - - 一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。 - - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素。 - -- 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增): - - 一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。 - - 如果插入时的栈为空,则说明右侧不存在比当前元素大的元素。 - - -### 2.4 寻找右侧第一个比当前元素小的元素 - -- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减): - - 一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。 - - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。 - -- 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减): - - 一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。 - - 如果插入时的栈为空,则说明右侧不存在比当前元素小的元素。 - - -上边的分类解法有点绕口,可以简单记为以下条规则: - -- 无论哪种题型,都建议从左到右遍历元素。 - -- 查找 **「比当前元素大的元素」** 就用 **单调递增栈**,查找 **「比当前元素小的元素」** 就用 **单调递减栈**。 -- 从 **「左侧」** 查找就看 **「插入栈」** 时的栈顶元素,从 **「右侧」** 查找就看 **「弹出栈」** 时即将插入的元素。 - -## 3. 单调栈模板 - -以从左到右遍历元素为例,介绍一下构造单调递增栈和单调递减栈的模板。 - -### 3.1 单调递增栈模板 - -```python -def monotoneIncreasingStack(nums): - stack = [] - for num in nums: - while stack and num >= stack[-1]: - stack.pop() - stack.append(num) -``` - -### 3.2 单调递减栈模板 - -```python -def monotoneDecreasingStack(nums): - stack = [] - for num in nums: - while stack and num <= stack[-1]: - stack.pop() - stack.append(num) -``` - -## 4. 单调栈的应用 - -### 4.1 下一个更大元素 I - -#### 4.1.1 题目链接 - -- [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) - -#### 4.1.2 题目大意 - -给定两个没有重复元素的数组 `nums1` 和 `nums2` ,其中 `nums1` 是 `nums2` 的子集。 - -要求:找出 `nums1` 中每个元素在 `nums2` 中的下一个比其大的值。 - -- `nums1` 中数字 `x` 的下一个更大元素是指: `x` 在 `nums2` 中对应位置的右边的第一个比 `x` 大的元素。如果不存在,对应位置输出 `-1`。 - -#### 4.1.3 解题思路 - -第一种思路是根据题意直接暴力求解。遍历 `nums1` 中的每一个元素。对于 `nums1` 的每一个元素 `nums1[i]`,再遍历一遍 `nums2`,查找 `nums2` 中对应位置右边第一个比 `nums1[i]` 大的元素。这种解法的时间复杂度是 $O(n^2)$。 - -第二种思路是使用单调递增栈。因为 `nums1` 是 `nums2` 的子集,所以我们可以先遍历一遍 `nums2`,并构造单调递增栈,求出 `nums2` 中每个元素右侧下一个更大的元素。然后将其存储到哈希表中。然后再遍历一遍 `nums1`,从哈希表中取出对应结果,存放到答案数组中。这种解法的时间复杂度是 $O(n)$。具体做法如下: - -- 使用数组 `res` 存放答案。使用 `stack` 表示单调递增栈。使用哈希表 `num_map` 用于存储 `nums2` 中下一个比当前元素大的数值,映射关系为 `当前元素值:下一个比当前元素大的数值`。 -- 遍历数组 `nums2`,对于当前元素: - - 如果当前元素值较小,则直接让当前元素值入栈。 - - 如果当前元素值较大,则一直出栈,直到当前元素值小于栈顶元素。 - - 出栈时,出栈元素是第一个大于当前元素值的元素。则将其映射到 `num_map` 中。 - -- 遍历完数组 `nums2`,建立好所有元素下一个更大元素的映射关系之后,再遍历数组 `nums1`。 -- 从 `num_map` 中取出对应的值,将其加入到答案数组中。 -- 最终输出答案数组 `res`。 - -#### 4.1.4 代码 - -```python -class Solution: - def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: - res = [] - stack = [] - num_map = dict() - for num in nums2: - while stack and num > stack[-1]: - num_map[stack[-1]] = num - stack.pop() - stack.append(num) - - for num in nums1: - res.append(num_map.get(num, -1)) - return res -``` - -### 4.2 每日温度 - -#### 4.2.1 题目链接 - -- [739. 每日温度 - 力扣(LeetCode)](https://leetcode.cn/problems/daily-temperatures/) - -#### 4.2.2 题目大意 - -**描述**:给定一个列表 `temperatures`,`temperatures[i]` 表示第 `i` 天的气温。 - -**要求**:输出一个列表,列表上每个位置代表「如果要观测到更高的气温,至少需要等待的天数」。如果之后的气温不再升高,则用 `0` 来代替。 - -**说明**: - -- $1 \le temperatures.length \le 10^5$。 -- $30 \le temperatures[i] \le 100$。 - -**示例**: - -```python -输入: temperatures = [73,74,75,71,69,72,76,73] -输出: [1,1,4,2,1,1,0,0] - - -输入: temperatures = [30,40,50,60] -输出: [1,1,1,0] -``` - -#### 4.2.3 解题思路 - -题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置右侧找到第一个比当前元素更大的元素。求「该元素」与「右侧第一个比当前元素更大的元素」之间的距离,将所有距离保存为数组返回结果。 - -最简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 - -更好的方式使用「单调递增栈」,栈中保存元素的下标。 - -##### 思路 1:单调栈 - -1. 首先,将答案数组 `ans` 全部赋值为 0。然后遍历数组每个位置元素。 -2. 如果栈为空,则将当前元素的下标入栈。 -3. 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 -4. 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组 `ans` 中保存起来,判断栈顶元素。 -5. 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 -6. 最后输出答案数组 `ans`。 - -##### 思路 1:代码 - -```python -class Solution: - def dailyTemperatures(self, T: List[int]) -> List[int]: - n = len(T) - stack = [] - ans = [0 for _ in range(n)] - for i in range(n): - while stack and T[i] > T[stack[-1]]: - index = stack.pop() - ans[index] = (i-index) - stack.append(i) - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【博文】[动画:什么是单调栈?_- 吴师兄学编程](https://www.cxyxiaowu.com/450.html) -- 【博文】[单调栈 - OI Wiki](https://oi-wiki.org/ds/monotonous-stack/) -- 【博文】[单调栈解题模板秒杀八道题 - lucifer 的网络博客](https://lucifer.ren/blog/2020/11/03/monotone-stack/) -- 【博文】[理解单调栈与单调队列 - Hopefully Sky 的博客](https://blog.csdn.net/fuzhongmin05/article/details/118090554) \ No newline at end of file diff --git a/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md b/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md deleted file mode 100644 index bbed1109..00000000 --- a/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md +++ /dev/null @@ -1,13 +0,0 @@ -### 单调栈 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0739 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md) | 栈、数组、单调栈 | 中等 | -| 0496 | [下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0496.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20I.md) | 栈、数组、哈希表、单调栈 | 简单 | -| 0503 | [下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II.md) | 栈、数组、单调栈 | 中等 | -| 0901 | [股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0901.%20%E8%82%A1%E7%A5%A8%E4%BB%B7%E6%A0%BC%E8%B7%A8%E5%BA%A6.md) | 栈、设计、数据流、单调栈 | 中等 | -| 0084 | [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0084.%20%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.md) | 栈、数组、单调栈 | 困难 | -| 0316 | [去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0316.%20%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D.md) | 栈、贪心、字符串、单调栈 | 中等 | -| 0042 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0042.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | -| 0085 | [最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | | 栈、数组、动态规划、矩阵、单调栈 | 困难 | - diff --git a/Contents/03.Stack/02.Monotone-Stack/index.md b/Contents/03.Stack/02.Monotone-Stack/index.md deleted file mode 100644 index 58d7f5f6..00000000 --- a/Contents/03.Stack/02.Monotone-Stack/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [单调栈知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md) -- [单调栈题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) \ No newline at end of file diff --git a/Contents/03.Stack/index.md b/Contents/03.Stack/index.md deleted file mode 100644 index f5426b4d..00000000 --- a/Contents/03.Stack/index.md +++ /dev/null @@ -1,11 +0,0 @@ -## 本章内容 - -### 堆栈基础知识 - -- [堆栈基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md) -- [堆栈基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) - -### 单调栈 - -- [单调栈知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md) -- [单调栈题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) \ No newline at end of file diff --git a/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md b/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md deleted file mode 100644 index 13eb3d55..00000000 --- a/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md +++ /dev/null @@ -1,337 +0,0 @@ -## 1. 队列简介 - -> **队列(Queue)**:一种线性表数据结构,是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表。 - -我们把队列中允许插入的一端称为 **「队尾(rear)」**;把允许删除的另一端称为 **「队头(front)」**。当表中没有任何数据元素时,称之为 **「空队」**。 - -队列有两种基本操作:**「插入操作」** 和 **「删除操作」**。 - -- 队列的插入操作又称为「入队」。 -- 队列的删除操作又称为「出队」。 - -![](https://qcdn.itcharge.cn/images/20211204211538.png) - -简单来说,队列是一种 **「先进先出(First In First Out)」** 的线性表,简称为 **「FIFO 结构」**。 - -我们可以从两个方面来解释一下队列的定义: - -- 第一个方面是 **「线性表」**。 - -队列首先是一个线性表,队列中元素具有前驱后继的线性关系。队列中元素按照 $a_1, a_2, ... , a_n$ 的次序依次入队。队头元素为 $a_1$,队尾元素为 $a_n$。 - -- 第二个方面是 **「先进先出原则」**。 - -根据队列的定义,最先进入队列的元素在队头,最后进入队列的元素在队尾。每次从队列中删除的总是队头元素,即最先进入队列的元素。也就是说,元素进入队列或者退出队列是按照「先进先出(First In First Out)」的原则进行的。 - -## 2. 队列的顺序存储与链式存储 - -和线性表类似,队列有两种存储表示方法:**「顺序存储的队列」** 和 **「链式存储的队列」**。 - -- **「顺序存储的队列」**:利用一组地址连续的存储单元依次存放队列中从队头到队尾的元素,同时使用指针 `front` 指向队头元素在队列中的位置,使用指针 `rear` 指示队尾元素在队列中的位置。 -- **「链式存储的队列」**:利用单链表的方式来实现队列。队列中元素按照插入顺序依次插入到链表的第一个节点之后,并使用队头指针 `front` 指向链表头节点位置,也就是队头元素,`rear` 指向链表尾部位置,也就是队尾元素。 - -注意:`front` 和 `rear` 的指向位置并不完全固定。有时候算法设计上的方便以及代码简洁,也会使 `front` 指向队头元素所在位置的前一个位置。`rear` 也可能指向队尾元素在队列位置的下一个位置。具体还是要看算法是如何实现的。 - -在描述队列的顺序存储与链式存储具体实现之前,我们先来看看队列具有哪些基本操作。 - -### 2.1 队列的基本操作 - -- **初始化空队列**:创建一个空队列,定义队列的大小 `size`,以及队头元素指针 `front`,队尾指针 `rear`。 - -- **判断队列是否为空**:当队列为空时,返回 `True`。当队列不为空时,返回 `False`。一般只用于「出队操作」和「获取队头元素操作」中。 - -- **判断队列是否已满**:当队列已满时,返回 `True`,当队列未满时,返回 `False`。一般只用于顺序队列中插入元素操作中。 - -- **插入元素(入队)**:相当于在线性表最后一个数据元素后面插入一个新的数据元素。并改变队尾指针 `rear` 的指向位置。 - -- **删除元素(出队)**:相当于在线性表中删除第一个数据元素。并改变队头指针 `front` 的指向位置。 -- **获取队头元素**:相当于获取线性表中第一个数据元素。与插入元素(入队)、删除元素(出队)不同的是,该操作并不改变队头指针 `front` 的指向位置。 -- **获取队尾元素**:相当于获取线性表中最后一个数据元素。与插入元素(入队)、删除元素(出队)不同的是,该操作并不改变队尾指针 `rear` 的指向位置。 - -接下来我们来看一下队列的顺序存储与链式存储两种不同的实现方式。 - -### 2.2 队列的顺序存储实现 - -队列最简单的实现方式就是借助于一个数组来描述队列的顺序存储结构。在 `Python` 中我们可以借助列表 `list` 来实现。 - -#### 2.2.1 队列的顺序存储基本描述 - -![](https://qcdn.itcharge.cn/images/20211204211607.png) - -为了算法设计上的方便以及算法本身的简单,我们约定:队头指针 `self.front` 指向队头元素所在位置的前一个位置,而队尾指针 `self.rear` 指向队尾元素所在位置。 - -- **初始化空队列**:创建一个空队列 `self.queue`,定义队列大小 `self.size`。令队头指针 `self.front` 和队尾指针 `self.rear` 都指向 `-1`。即 `self.front = self.rear = -1`。 -- **判断队列是否为空**:根据 `self.front` 和 `self.rear` 的指向位置关系进行判断。如果队头指针 `self.front` 和队尾指针 `self.rear` 相等,则说明队列为空。否则,队列不为空。 -- **判断队列是否已满**:如果 `self.rear` 指向队列最后一个位置,即 `self.rear == self.size - 1`,则说明队列已满。否则,队列未满。 -- **插入元素(入队)**:先判断队列是否已满,已满直接抛出异常。如果队列不满,则将队尾指针 `self.rear` 向右移动一位,并进行赋值操作。此时 `self.rear` 指向队尾元素。 -- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果队列不为空,则将队头指针 `self.front` 指向元素赋值为 `None`,并将 `self.front` 向右移动一位。 -- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果队列不为空,因为 `self.front` 指向队头元素所在位置的前一个位置,所以队头元素在 `self.front` 后面一个位置上,返回 `self.queue[self.front + 1]`。 -- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 `self.rear` 指向队尾元素所在位置,所以直接返回 `self.queue[self.rear]`。 - -#### 2.2.2 队列的顺序存储实现代码 - -```python -class Queue: - # 初始化空队列 - def __init__(self, size=100): - self.size = size - self.queue = [None for _ in range(size)] - self.front = -1 - self.rear = -1 - - # 判断队列是否为空 - def is_empty(self): - return self.front == self.rear - - # 判断队列是否已满 - def is_full(self): - return self.rear + 1 == self.size - - # 入队操作 - def enqueue(self, value): - if self.is_full(): - raise Exception('Queue is full') - else: - self.rear += 1 - self.queue[self.rear] = value - - # 出队操作 - def dequeue(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - self.front += 1 - return self.queue[self.front] - - # 获取队头元素 - def front_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - return self.queue[self.front + 1] - - # 获取队尾元素 - def rear_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - return self.queue[self.rear] -``` - -### 2.3 循环队列的顺序存储实现 - -在「2.2 队列的顺序存储实现」中,如果队列中第 `0` ~ `size - 1` 位置均被队列元素占用时,此时队列已满(即 `self.rear == self.size - 1`),再进行入队操作就会抛出队列已满的异常。 - -而由于出队操作总是删除当前的队头元素,将 `self.front` 进行右移,而插入操作又总是在队尾进行。经过不断的出队、入队操作,队列的变化就像是使队列整体向右移动。 - -当队尾指针满足 `self.rear == self.size - 1` 条件时,此时再进行入队操作就会抛出队列已满的异常。而之前因为出队操作而产生空余位置也没有利用上,这就造成了「假溢出」问题。 - -为了解决「假溢出」问题,有两种做法: - -- 第一种:每一次删除队头元素之后,就将整个队列往前移动 `1` 个位置。其代码如下所示: - -```python -# 出队操作 -def dequeue(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - value = self.queue[0] - for i in range(self.rear): - self.queue[i] = self.queue[i + 1] - return value -``` - -这种情况下,队头指针似乎用不到了。因为队头指针总是在队列的第 `0` 个位置。但是因为删除操作涉及到整个队列元素的移动,所以每次删除操作的时间复杂度就从 $O(1)$ 变为了 $O(n)$。所以这种方式不太可取。 - -- 第二种:将队列想象成为头尾相连的循环表,利用数学中的求模运算,使得空间得以重复利用,这样就解决了问题。 - -在进行插入操作时,如果队列的第 `self.size - 1` 个位置被占用之后,只要队列前面还有可用空间,新的元素加入队列时就可以从第 `0` 个位置开始继续插入。 - -我们约定:`self.size` 为循环队列的最大元素个数。队头指针 `self.front` 指向队头元素所在位置的前一个位置,而队尾指针 `self.rear` 指向队尾元素所在位置。则: - -1. **插入元素(入队)时**:队尾指针循环前进 `1` 个位置,即 `self.rear = (self.rear + 1) % self.size`。 -2. **删除元素(出队)时**:队头指针循环前进 `1` 个位置,即 `self.front = (self.front + 1) % self.size`。 - -> **注意**: -> -> - 循环队列在一开始初始化,队列为空时,满足条件`self.front == self.rear`。 -> - 而当充满队列后,仍满足条件 `self.front == self.rear`。 -> -> 这种情况下就无法判断「队列为空」还是「队列为满」了。 - -为了区分循环队列中「队列为空」还是「队列已满」的情况,有多种处理方式: - -- **方式 1**:增加表示队列中元素个数的变量 `self.count`,用来以区分队列已满还是队列为空。在入队、出队过程中不断更新元素个数 `self.count` 的值。 - - 队列已满条件为:队列中元素个数等于队列整体容量,即 `self.count == self.size`。 - - 队空为空条件为:队列中元素个数等于 `0`,即 `self.count == 0`。 -- **方式 2**:增加标记变量 `self.tag`,用来以区分队列已满还是队列为空。 - - 队列已满条件为:`self.tag == 1` 的情况下,因插入导致 `self.front == self.rear`。 - - 队列为空条件为:在 `self.tag == 0` 的情况下,因删除导致 `self.front == self.rear`。 -- **方式 3**:特意空出来一个位置用于区分队列已满还是队列为空。入队时少用一个队列单元,即约定以「队头指针在队尾指针的下一位置」作为队满的标志。 - - 队列已满条件为:队头指针在队尾指针的下一位置,即 `(self.rear + 1) % self.size == self.front`。 - - 队列为空条件为:队头指针等于队尾指针,即 `self.front == self.rear`。 - -#### 2.3.1 循环队列的顺序存储基本描述 - -下面我们以「方式 3」中特意空出来一个位置的处理方式为例,对循环队列的顺序存储做一下基本描述。 - -![](https://qcdn.itcharge.cn/images/20220109164459.png) - -我们约定:`self.size` 为循环队列的最大元素个数。队头指针 `self.front` 指向队头元素所在位置的前一个位置,而队尾指针 `self.rear` 指向队尾元素所在位置。 - -- **初始化空队列**:创建一个空队列,定义队列大小为 `self.size + 1`。令队头指针 `self.front` 和队尾指针 `self.rear` 都指向 `0`。即 `self.front = self.rear = 0`。 -- **判断队列是否为空**:根据 `self.front` 和 `self.rear` 的指向位置进行判断。根据约定,如果队头指针 `self.front` 和队尾指针 `self.rear` 相等,则说明队列为空。否则,队列不为空。 -- **判断队列是否已满**:队头指针在队尾指针的下一位置,即 `(self.rear + 1) % self.size == self.front`,则说明队列已满。否则,队列未满。 -- **插入元素(入队)**:先判断队列是否已满,已满直接抛出异常。如果不满,则将队尾指针 `self.rear` 向右循环移动一位,并进行赋值操作。此时 `self.rear` 指向队尾元素。 -- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果不为空,则将队头指针 `self.front` 指向元素赋值为 `None`,并将 `self.front` 向右循环移动一位。 -- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 `self.front` 指向队头元素所在位置的前一个位置,所以队头元素在 `self.front` 后一个位置上,返回 `self.queue[(self.front + 1) % self.size]`。 -- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 `self.rear` 指向队尾元素所在位置,所以直接返回 `self.queue[self.rear]`。 - -#### 2.3.2 循环队列的顺序存储实现代码 - -```python -class Queue: - # 初始化空队列 - def __init__(self, size=100): - self.size = size + 1 - self.queue = [None for _ in range(size + 1)] - self.front = 0 - self.rear = 0 - - # 判断队列是否为空 - def is_empty(self): - return self.front == self.rear - - # 判断队列是否已满 - def is_full(self): - return (self.rear + 1) % self.size == self.front - - # 入队操作 - def enqueue(self, value): - if self.is_full(): - raise Exception('Queue is full') - else: - self.rear = (self.rear + 1) % self.size - self.queue[self.rear] = value - - # 出队操作 - def dequeue(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - self.queue[self.front] = None - self.front = (self.front + 1) % self.size - return self.queue[self.front] - - # 获取队头元素 - def front_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - value = self.queue[(self.front + 1) % self.size] - return value - - # 获取队尾元素 - def rear_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - value = self.queue[self.rear] - return value -``` - -### 2.3 队列的链式存储实现 - -对于在使用过程中数据元素变动较大,或者说频繁进行插入和删除操作的数据结构来说,采用链式存储结构比顺序存储结构更加合适。 - -所以我们可以采用链式存储结构来实现队列。 - -1. 我们用一个线性链表来表示队列,队列中的每一个元素对应链表中的一个链节点。 -2. 再把线性链表的第 `1` 个节点定义为队头指针 `front`,在链表最后的链节点建立指针 `rear` 作为队尾指针。 -3. 最后限定只能在链表队头进行删除操作,在链表队尾进行插入操作,这样整个线性链表就构成了一个队列。 - -#### 2.3.1 队列的链式存储基本描述 - -![](https://qcdn.itcharge.cn/images/20211204211644.png) - -我们约定:队头指针 `self.front` 指向队头元素所在位置的前一个位置,而队尾指针 `self.rear` 指向队尾元素所在位置。 - -- **初始化空队列**:建立一个链表头节点 `self.head`,令队头指针 `self.front` 和队尾指针 `self.rear` 都指向 `head`。即 `self.front = self.rear = head`。 -- **判断队列是否为空**:根据 `self.front` 和 `self.rear` 的指向位置进行判断。根据约定,如果队头指针 `self.front` 等于队尾指针 `self.rear`,则说明队列为空。否则,队列不为空。 -- **插入元素(入队)**:创建值为 `value` 的链表节点,插入到链表末尾,并令队尾指针 `self.rear` 沿着链表移动 `1` 位到链表末尾。此时 `self.rear` 指向队尾元素。 -- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果不为空,则获取队头指针 `self.front` 下一个位置节点上的值,并将 `self.front` 沿着链表移动 `1` 位。如果 `self.front` 下一个位置是 `self.rear`,则说明队列为空,此时,将 `self.rear` 赋值为 `self.front`,令其相等。 -- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 `self.front` 指向队头元素所在位置的前一个位置,所以队头元素在 `self.front` 后一个位置上,返回 `self.front.next.value`。 -- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 `self.rear` 指向队尾元素所在位置,所以直接返回 `self.rear.value`。 - -#### 2.3.2 队列的链式存储实现代码 - -```python -class Node: - def __init__(self, value): - self.value = value - self.next = None - -class Queue: - # 初始化空队列 - def __init__(self): - head = Node(0) - self.front = head - self.rear = head - - # 判断队列是否为空 - def is_empty(self): - return self.front == self.rear - - # 入队操作 - def enqueue(self, value): - node = Node(value) - self.rear.next = node - self.rear = node - - # 出队操作 - def dequeue(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - node = self.front.next - self.front.next = node.next - if self.rear == node: - self.rear = self.front - value = node.value - del node - return value - - # 获取队头元素 - def front_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - return self.front.next.value - - # 获取队尾元素 - def rear_value(self): - if self.is_empty(): - raise Exception('Queue is empty') - else: - return self.rear.value -``` - -## 3. 队列的应用 - -队列是算法和程序中最常用的辅助结构,其应用十分广泛。比如现实生活中的排队买票、银行办理业务挂号等等。队列在计算机科学领域的应用主要提现在以下两个方面: - -1. 解决计算机的主机与外部设备之间速度不匹配的问题。 - - 比如解决主机与打印机之间速度不匹配问题。主机输出数据给计算机打印,输出数据的速度比打印数据的速度要快很多,如果直接把数据送给打印机进行打印,由于速度不匹配,显然行不通。为此,可以设置一个打印数据缓存队列,将要打印的数据依次写入缓存队列中。然后打印机从缓冲区中按照先进先出的原则依次取出数据并且打印。这样即保证了打印数据的正确,又提高了主机的效率。 -2. 解决由于多用户引起的系统资源竞争的问题。 - - 比如说一个带有多终端的计算机系统,当有多个用户需要各自运行各自的程序时,就分别通过终端向操作系统提出占用 CPU 的请求。操作系统通常按照每个请求在时间上的先后顺序将它们排成一个队列,每次把 CPU 分配给队头请求的用户使用;当相应的程序运行结束或用完规定的时间间隔之后,将其退出队列,再把 CPU 分配给新的队头请求的用户使用。这样既能满足多用户的请求,又能使 CPU 正常运行。 - - 再比如 Linux 中的环形缓存、高性能队列 Disruptor,都用到了循环并发队列。iOS 多线程中的 GCD、NSOperationQueue 都用到了队列结构。 - -## 参考资料 - -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 -- 【书籍】数据结构教程 第 3 版 - 唐发根 著 -- 【书籍】大话数据结构 程杰 著 -- 【文章】[数据结构之 python 实现队列的链式存储 - 不服输的南瓜的博客](https://blog.csdn.net/weixin_40283816/article/details/87952682) -- 【文章】[顺序存储的循环队列判空判满判长_- ccxcuixia](https://blog.csdn.net/baidu_41304382/article/details/108091899) -- 【文章】[队列 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41330) \ No newline at end of file diff --git a/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md b/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md deleted file mode 100644 index 72b53f41..00000000 --- a/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md +++ /dev/null @@ -1,8 +0,0 @@ -### 队列基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0622 | [设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0622.%20%E8%AE%BE%E8%AE%A1%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97.md) | 设计、队列、数组、链表 | 中等 | -| 0346 | [数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0346.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%A7%BB%E5%8A%A8%E5%B9%B3%E5%9D%87%E5%80%BC.md) | 设计、队列、数组、数据流 | 简单 | -| 0225 | [用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0225.%20%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md) | 栈、设计、队列 | 简单 | - diff --git a/Contents/04.Queue/01.Queue-Basic/index.md b/Contents/04.Queue/01.Queue-Basic/index.md deleted file mode 100644 index c6c1d365..00000000 --- a/Contents/04.Queue/01.Queue-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [队列基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md) -- [队列基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) \ No newline at end of file diff --git a/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md b/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md deleted file mode 100644 index 79470557..00000000 --- a/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md +++ /dev/null @@ -1,404 +0,0 @@ -## 1. 优先队列简介 - -> **优先队列(Priority Queue)**:一种特殊的队列。在优先队列中,元素被赋予优先级,当访问队列元素时,具有最高优先级的元素最先删除。 - -优先队列与普通队列最大的不同点在于 **出队顺序**。 - -- 普通队列的出队顺序跟入队顺序相关,符合「先进先出(First in, First out)」的规则。 -- 优先队列的出队顺序跟入队顺序无关,优先队列是按照元素的优先级来决定出队顺序的。优先级高的元素优先出队,优先级低的元素后出队。优先队列符合 **「最高级先出(First in, Largest out)」** 的规则。 - -优先队列的示例图如下所示。 - -![](https://qcdn.itcharge.cn/images/20220109170709.png) - -## 2. 优先队列的适用场景 - -优先队列的应用场景非常多,比如: - -- **数据压缩**:赫夫曼编码算法; -- **最短路径算法**:Dijkstra 算法; -- **最小生成树算法**:Prim 算法; -- **任务调度器**:根据优先级执行系统任务; -- **事件驱动仿真**:顾客排队算法; -- **排序问题**:查找第 k 个最小元素。 - -很多语言都提供了优先级队列的实现。比如,Java 的 `PriorityQueue`,C++ 的 `priority_queue` 等。Python 中也可以通过 `heapq` 来实现优先队列。下面我们来讲解一下优先队列的实现。 - -## 3. 优先队列的实现方式 - -优先队列所涉及的基本操作跟普通队列差不多,主要是 **「入队操作」** 和 **「出队操作」**。 - -而优先队列的实现方式也有很多种,除了使用「数组(顺序存储)实现」与「链表(链式存储)实现」之外,我们最常用的是使用 **「二叉堆结构实现」**优先队列。以下是三种方案的介绍和总结。 - -- **数组(顺序存储)实现优先队列**:入队操作直接插入到数组队尾,时间复杂度为 $O(1)$。出队操作需要遍历整个数组,找到优先级最高的元素,返回并删除该元素,时间复杂度为 $O(n)$。 -- **链表(链式存储)实现优先队列**:链表中的元素按照优先级排序,入队操作需要为待插入元素创建节点,并在链表中找到合适的插入位置,时间复杂度为 $O(n)$。出队操作直接返回链表队头元素,并删除队头元素,时间复杂度为 $O(1)$。 -- **二叉堆结构实现优先队列**:构建一个二叉堆结构,二叉堆按照优先级进行排序。入队操作就是将元素插入到二叉堆中合适位置,时间复杂度为 $O(\log_2n)$。吹对操作则返回二叉堆中优先级最大节点并删除,时间复杂度也是 $O(\log n)$。 - -下面是三种结构实现的优先队列入队操作和出队操作的时间复杂度总结。 - -| | 入队操作时间复杂度 | 出队操作(取出优先级最高的元素)时间复杂度 | -| ---- | ------------------ | ------------------------------------------ | -| 堆 | $O(\log n)$ | $O(\log n)$ | -| 数组 | $O(1)$ | $O(n)$ | -| 链表 | $O(n)$ | $O(1)$ | - -从上面的表格可以看出,使用「二叉堆」这种数据结构来实现优先队列是比较高效的。下面我们来讲解一下二叉堆实现的优先队列。 - -## 4. 二叉堆实现的优先队列 - -我们曾经在「01. 数组 - 02. 数组排序 - 07. 堆排序」中介绍过二叉堆,这里再简单介绍一下。 - -### 4.1 二叉堆的定义 - -二叉堆:符合以下两个条件之一的完全二叉树: - -- 大顶堆:根节点值 ≥ 子节点值。 -- 小顶堆:根节点值 ≤ 子节点值。 - -### 4.2 二叉堆的基本操作 - -二叉树主要涉及两个基本操作:「堆调整方法」和「将数组构建为二叉堆方法」。 - -- **堆调整方法 `heapAdjust`**:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下: - - - 从根节点开始,自上而下地调整节点的位置,使其成为堆积。即把序号为 `i` 的节点与其左子树节点(序号为 `2 * i`)、右子树节点(序号为 `2 * i + 1`)中值最大的节点交换位置。 - - - 因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是,从当前节点的左右子树节点开始,自上而下继续进行类似的调整。 - - - 如此下去直到整棵完全二叉树成为一个大顶堆。 - -- **将数组构建为二叉堆方法(初始堆建立方法) `heapify`**: - - - 如果原始序列对应的完全二叉树(不一定是堆)的深度为 `d`,则从 `d - 1` 层最右侧分支节点(序号为 ⌊n/2⌋)开始,初始时令 `i = ⌊n/2⌋`,调用堆调整算法。 - - - 每调用一次堆调整算法,执行一次 `i = i - 1`,直到 `i == 1` 时,再调用一次,就把原始数组构建为了一个二叉堆。 - -### 4.3 优先队列的基本操作 - -在「3. 优先队列的实现方式」中我们已经提到过,优先队列所涉及的基本操作主要是 **「入队操作」** 和 **「出队操作」**。 - -- **入队操作 `heappush`**: - - 先将待插入元素 `value` 插入到数组 `nums` 末尾。 - - 如果完全二叉树的深度为 `d`,则从 `d - 1` 层开始最右侧分支节点(序号为 ⌊n/2⌋)开始,初始时令 `i = ⌊n/2⌋`,从下向上依次查找插入位置。 - - 遇到 `value` 小于当前根节点时,将其插入到当前位置。否则继续向上寻找插入位置。 - - 如果找到插入位置或者到达根位置,将 `value` 插入该位置。 -- **出队操作 `heappop`**: - - 交换数组 `nums` 首尾元素,此时 `nums` 尾部就是值最大(优先级最高)的元素,将其从 `nums` 中弹出,并保存起来。 - - 弹出后,对 `nums` 剩余元素调用堆调整算法,将其调整为大顶堆。 - -### 4.4 手写二叉堆实现优先队列 - -通过手写二叉堆的方式实现优先队列。主要实现了以下五种方法: - -- `heapAdjust`:将完全二叉树调整为二叉堆。 -- `heapify`: 将数组构建为二叉堆方法(初始堆建立方法)。 -- `heappush`:向堆中添加元素,也是优先队列的入队操作。 -- `heappop`:删除堆顶元素,也是优先队列的出队操作,弹出优先队列中优先级最高的元素。 -- `heapSort`:堆排序。 - -```python -class Heapq: - # 堆调整方法:调整为大顶堆 - def heapAdjust(self, nums: [int], index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子结点 - max_index = index - if nums[left] > nums[max_index]: - max_index = left - if right <= end and nums[right] > nums[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 将数组构建为二叉堆 - def heapify(self, nums: [int]): - size = len(nums) - # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - # 调用调整堆函数 - self.heapAdjust(nums, i, size - 1) - - # 入队操作 - def heappush(self, nums: list, value): - nums.append(value) - size = len(nums) - i = size - 1 - # 寻找插入位置 - while (i - 1) // 2 >= 0: - cur_root = (i - 1) // 2 - # value 小于当前根节点,则插入到当前位置 - if nums[cur_root] > value: - break - # 继续向上查找 - nums[i] = nums[cur_root] - i = cur_root - # 找到插入位置或者到达根位置,将其插入 - nums[i] = value - - # 出队操作 - def heappop(self, nums: list) -> int: - size = len(nums) - nums[0], nums[-1] = nums[-1], nums[0] - # 得到最大值(堆顶元素)然后调整堆 - top = nums.pop() - if size > 0: - self.heapAdjust(nums, 0, size - 2) - - return top - - # 升序堆排序 - def heapSort(self, nums: [int]): - self.heapify(nums) - size = len(nums) - for i in range(size): - nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] - self.heapAdjust(nums, 0, size - i - 2) - return nums -``` - -### 4.5 使用 heapq 模块实现优先队列 - -Python 中的 `heapq` 模块提供了优先队列算法。函数 `heapq.heappush()` 用于在队列 `queue` 上插入一个元素。`heapq.heappop()` 用于在队列 `queue` 上删除一个元素。 - -需要注意的是:`heapq.heappop()` 函数总是返回「最小的」的元素。所以我们在使用 `heapq.heappush()` 时,将优先级设置为负数,这样就使得元素可以按照优先级从高到低排序, 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。这样做的目的是为了 `heapq.heappop()` 每次弹出的元素都是优先级最高的元素。 - -```python -import heapq - -class PriorityQueue: - def __init__(self): - self.queue = [] - self.index = 0 - - def push(self, item, priority): - heapq.heappush(self.queue, (-priority, self.index, item)) - self.index += 1 - - def pop(self): - return heapq.heappop(self.queue)[-1] -``` - -## 5. 优先队列的应用 - -### 5.1 滑动窗口最大值 - -#### 5.1.1 题目链接 - -- [239. 滑动窗口最大值 - 力扣(LeetCode)](https://leetcode.cn/problems/sliding-window-maximum/) - -#### 5.1.2 题目大意 - -**描述**:给定一个整数数组 `nums`,再给定一个整数 `k`,表示为大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 `k` 个数字,滑动窗口每次只能向右移动一位。 - -**要求**:返回滑动窗口中的最大值。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 -- $1 \le k \le nums.length$。 - -**示例**: - -```python -输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 -输出:[3,3,5,5,6,7] -解释: -滑动窗口的位置 最大值 ---------------- ----- -[1 3 -1] -3 5 3 6 7 3 - 1 [3 -1 -3] 5 3 6 7 3 - 1 3 [-1 -3 5] 3 6 7 5 - 1 3 -1 [-3 5 3] 6 7 5 - 1 3 -1 -3 [5 3 6] 7 6 - 1 3 -1 -3 5 [3 6 7] 7 - - -输入:nums = [1], k = 1 -输出:[1] -``` - -#### 5.1.3 解题思路 - -暴力求解的话,需要使用二重循环遍历,其时间复杂度为 $O(n * k)$。根据题目给定的数据范围,肯定会超时。 - -我们可以使用优先队列来做。 - -##### 思路 1:优先队列 - -1. 初始的时候将前 `k` 个元素加入优先队列的二叉堆中。存入优先队列的是数组值与索引构成的元组。优先队列将数组值作为优先级。 -2. 然后滑动窗口从第 `k` 个元素开始遍历,将当前数组值和索引的元组插入到二叉堆中。 -3. 当二叉堆堆顶元素的索引已经不在滑动窗口的范围中时,即 `q[0][1] <= i - k` 时,不断删除堆顶元素,直到最大值元素的索引在滑动窗口的范围中。 -4. 将最大值加入到答案数组中,继续向右滑动。 -5. 滑动结束时,输出答案数组。 - -##### 思路 1:代码 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - size = len(nums) - q = [(-nums[i], i) for i in range(k)] - heapq.heapify(q) - res = [-q[0][0]] - - for i in range(k, size): - heapq.heappush(q, (-nums[i], i)) - while q[0][1] <= i - k: - heapq.heappop(q) - res.append(-q[0][0]) - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(k)$。 - -### 5.2 前 K 个高频元素 - -#### 5.2.1 题目链接 - -- [347. 前 K 个高频元素 - 力扣(LeetCode)](https://leetcode.cn/problems/top-k-frequent-elements/) - -#### 5.2.2 题目大意 - -**描述**:给定一个整数数组 `nums` 和一个整数 `k`。 - -**要求**:返回出现频率前 `k` 高的元素。可以按任意顺序返回答案。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $k$ 的取值范围是 $[1, \text{ 数组中不相同的元素的个数}]$。 -- 题目数据保证答案唯一,换句话说,数组中前 $k$ 个高频元素的集合是唯一的。 - -**示例**: - -```python -输入: nums = [1,1,1,2,2,3], k = 2 -输出: [1,2] - - -输入: nums = [1], k = 1 -输出: [1] -``` - -#### 5.2.3 解题思路 - -##### 思路 1:哈希表 + 优先队列 - -1. 使用哈希表记录下数组中各个元素的频数。 -2. 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -3. 使用二叉堆构建优先队列,优先级为元素频数。此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -4. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(\log_2 n)$。 - - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 -5. 不断重复第 4 步,直到 `k` 次结束。调整 `k` 次的时间复杂度 $O(n\log_2 n)$。 - -##### 思路 1:代码 - -```python -class Heapq: - # 堆调整方法:调整为大顶堆 - def heapAdjust(self, nums: [int], nums_dict, index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子结点 - max_index = index - if nums_dict[nums[left]] > nums_dict[nums[max_index]]: - max_index = left - if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 将数组构建为二叉堆 - def heapify(self, nums: [int], nums_dict): - size = len(nums) - # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - # 调用调整堆函数 - self.heapAdjust(nums, nums_dict, i, size - 1) - - # 入队操作 - def heappush(self, nums: list, nums_dict, value): - nums.append(value) - size = len(nums) - i = size - 1 - # 寻找插入位置 - while (i - 1) // 2 >= 0: - cur_root = (i - 1) // 2 - # value 小于当前根节点,则插入到当前位置 - if nums_dict[nums[cur_root]] > nums_dict[value]: - break - # 继续向上查找 - nums[i] = nums[cur_root] - i = cur_root - # 找到插入位置或者到达根位置,将其插入 - nums[i] = value - - # 出队操作 - def heappop(self, nums: list, nums_dict) -> int: - size = len(nums) - nums[0], nums[-1] = nums[-1], nums[0] - # 得到最大值(堆顶元素)然后调整堆 - top = nums.pop() - if size > 0: - self.heapAdjust(nums, nums_dict, 0, size - 2) - - return top - -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - # 统计元素频数 - nums_dict = dict() - for num in nums: - if num in nums_dict: - nums_dict[num] += 1 - else: - nums_dict[num] = 1 - - # 使用 set 方法去重,得到新数组 - new_nums = list(set(nums)) - size = len(new_nums) - - heap = Heapq() - queue = [] - for num in new_nums: - heap.heappush(queue, nums_dict, num) - - res = [] - for i in range(k): - res.append(heap.heappop(queue, nums_dict)) - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【博文】[浅入浅出数据结构(15)—— 优先队列(堆) - NSpt - 博客园](https://www.cnblogs.com/mm93/p/7481782.html) -- 【博文】[堆(Heap)和优先队列(Priority Queue) - 简书](https://www.jianshu.com/p/859e5fb89eb7) -- 【博文】[漫画:什么是优先队列?- 吴师兄学编程](https://www.cxyxiaowu.com/5417.html) -- 【博文】[Python3,手写一个堆及其简易功能,并实现优先队列,最小堆任务调度等 - pythonstrat 的博客](https://blog.csdn.net/pythonstrat/article/details/119378788) -- 【文档】[实现一个优先级队列 - python3-cookbook 3.0.0 文档](https://python3-cookbook.readthedocs.io/zh_CN/latest/c01/p05_implement_a_priority_queue.html) -- 【文档】[heapq - 堆队列算法 - Python 3.10.1 文档](https://docs.python.org/zh-cn/3/library/heapq.html) -- 【题解】[239. 滑动窗口最大值 (优先队列&单调栈) - 滑动窗口最大值 - 力扣](https://leetcode.cn/problems/sliding-window-maximum/solution/239-hua-dong-chuang-kou-zui-da-zhi-you-x-9qur/) \ No newline at end of file diff --git a/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md b/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md deleted file mode 100644 index 5a7753f2..00000000 --- a/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md +++ /dev/null @@ -1,14 +0,0 @@ -### 优先队列题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0703 | [数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 0347 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0347.%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | -| 0451 | [根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | -| 0973 | [最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0973.%20%E6%9C%80%E6%8E%A5%E8%BF%91%E5%8E%9F%E7%82%B9%E7%9A%84%20K%20%E4%B8%AA%E7%82%B9.md) | 几何、数组、数学、分治、快速选择、排序、堆(优先队列) | 中等 | -| 1296 | [划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1296.%20%E5%88%92%E5%88%86%E6%95%B0%E7%BB%84%E4%B8%BA%E8%BF%9E%E7%BB%AD%E6%95%B0%E5%AD%97%E7%9A%84%E9%9B%86%E5%90%88.md) | 贪心、数组、哈希表、排序 | 中等 | -| 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | -| 0295 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0295.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | - diff --git a/Contents/04.Queue/02.Priority-Queue/index.md b/Contents/04.Queue/02.Priority-Queue/index.md deleted file mode 100644 index 9563cfb4..00000000 --- a/Contents/04.Queue/02.Priority-Queue/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [优先队列知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md) -- [优先队列题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) \ No newline at end of file diff --git a/Contents/04.Queue/index.md b/Contents/04.Queue/index.md deleted file mode 100644 index 1f37c2e9..00000000 --- a/Contents/04.Queue/index.md +++ /dev/null @@ -1,11 +0,0 @@ -## 本章内容 - -### 队列基础知识 - -- [队列基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md) -- [队列基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) - -### 优先队列 - -- [优先队列知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md) -- [优先队列题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) \ No newline at end of file diff --git a/Contents/05.Hash-Table/01.Hash-Table.md b/Contents/05.Hash-Table/01.Hash-Table.md deleted file mode 100644 index 90103e98..00000000 --- a/Contents/05.Hash-Table/01.Hash-Table.md +++ /dev/null @@ -1,167 +0,0 @@ -## 1. 哈希表简介 - -> **哈希表(Hash Table)**:也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。 -> -> 哈希表通过「键 `key` 」和「映射函数 `Hash(key)` 」计算出对应的「值 `value`」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。 - -哈希表的关键思想是使用哈希函数,将键 `key` 映射到对应表的某个区块中。我们可以将算法思想分为两个部分: - -- **向哈希表中插入一个关键码值**:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。 -- **在哈希表中搜索一个关键码值**:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。 - -哈希表的原理示例图如下所示: - -![](https://qcdn.itcharge.cn/images/20220114120000.png) - -在上图例子中,我们使用 `value = Hash(key) = key // 1000` 作为哈希函数。`//` 符号代表整除。我们以这个例子来说明一下哈希表的插入和查找策略。 - -- **向哈希表中插入一个关键码值**:通过哈希函数解析关键字,并将对应值存放到该区块中。 - - 比如:`0138` 通过哈希函数 `Hash(key) = 0138 // 100 = 0`,得出应将 `0138` 分配到`0` 所在的区块中。 -- **在哈希表中搜索一个关键码值**:通过哈希函数解析关键字,并在特定的区块搜索该关键字对应的值。 - - 比如:查找 `2321`,通过哈希函数,得出 `2321` 应该在 `2` 所对应的区块中。然后我们从 `2` 对应的区块中继续搜索,并在 `2` 对应的区块中成功找到了 `2321`。 - - 比如:查找 `3214`,通过哈希函数,得出 `3214` 应该在 `3` 所对应的区块中。然后我们从 `3` 对应的区块中继续搜索,但并没有找到对应值,则说明 `3214` 不在哈希表中。 - -哈希表在生活中的应用也很广泛,其中一个常见例子就是「查字典」。 - -比如为了查找 `赞` 这个字的具体意思,我们在字典中根据这个字的拼音索引 `zan`,查找到对应的页码为 `599`。然后我们就可以翻到字典的第 `599` 页查看 `赞` 字相关的解释了。 - -![](https://qcdn.itcharge.cn/images/20220111174223.png) - -在这个例子中: - -- 存放所有拼音和对应地址的表可以看做是 **「哈希表」**。 -- `赞` 字的拼音索引 `zan` 可以看做是哈希表中的 **「关键字 `key`」**。 -- 根据拼音索引 `zan` 来确定字对应页码的过程可以看做是哈希表中的 **「哈希函数 `Hash(key)`」**。 -- 查找到的对应页码 `599` 可以看做是哈希表中的 **「哈希地址 `value`」**。 - -## 2. 哈希函数 - -> **哈希函数(Hash Function)**:将哈希表中元素的关键键值映射为元素存储位置的函数。 - -哈希函数是哈希表中最重要的部分。一般来说,哈希函数会满足以下几个条件: - -- 哈希函数应该易于计算,并且尽量使计算出来的索引值均匀分布。 -- 哈希函数计算得到的哈希值是一个固定长度的输出值。 -- 如果 `Hash(key1)` 不等于 `Hash(key2)`,那么 `key1`、`key2` 一定不相等。 -- 如果 `Hash(key1)` 等于 `Hash(key2)`,那么 `key1`、`key2` 可能相等,也可能不相等(会发生哈希碰撞)。 - -在哈希表的实际应用中,关键字的类型除了数字类,还有可能是字符串类型、浮点数类型、大整数类型,甚至还有可能是几种类型的组合。一般我们会将各种类型的关键字先转换为整数类型,再通过哈希函数,将其映射到哈希表中。 - -而关于整数类型的关键字,通常用到的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。下面我们介绍几个常用的哈希函数方法。 - -### 2.1 直接定址法 - -- **直接定址法**:取关键字本身 / 关键字的某个线性函数值 作为哈希地址。即:`Hash(key) = key` 或者 `Hash(key) = a * key + b`,其中 `a` 和 `b` 为常数。 - -这种方法计算最简单,且不会产生冲突。适合于关键字分布基本连续的情况,如果关键字分布不连续,空位较多,则会造成存储空间的浪费。 - -举一个例子,假设我们有一个记录了从 `1` 岁到 `100` 岁的人口数字统计表。其中年龄为关键字,哈希函数取关键字自身,如下表所示。 - -| 年龄 | 1 | 2 | 3 | ... | 25 | 26 | 27 | ... | 100 | -| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | -| 人数 | 3000 | 2000 | 5000 | ... | 1050 | ... | ... | ... | ... | - -比如我们想要查询 `25` 岁的人有多少,则只要查询表中第 `25` 项即可。 - -### 2.2 除留余数法 - -- **除留余数法**:假设哈希表的表长为 `m`,取一个不大于 `m` 但接近或等于 `m` 的质数 `p`,利用取模运算,将关键字转换为哈希地址。即:`Hash(key) = key % p`,其中 `p` 为不大于 `m` 的质数。 - -这也是一种简单且常用的哈希函数方法。其关键点在于 `p` 的选择。根据经验而言,一般 `p` 取素数或者 `m`,这样可以尽可能的减少冲突。 - -比如我们需要将 `7` 个数 `[432, 5, 128, 193, 92, 111, 88]` 存储在 `11` 个区块中(长度为 `11` 的数组),通过除留余数法将这 `7` 个数应分别位于如下地址: - -| 索引 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | -| :--: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | -| 数据 | 88 | 111 | | 432 | 92 | 5 | 193 | | 128 | | | - -### 2.3 平方取中法 - -- **平方取中法**:先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。 - - 比如:`Hash(key) = (key * key) // 100 % 1000`,先计算平方,去除末尾的 2 位数,再取中间 3 位数作为哈希地址。 - -这种方法因为关键字平方值的中间几位数和原关键字的每一位数都相关,所以产生的哈希地址也比较均匀,有利于减少冲突的发生。 - -### 2.4 基数转换法 - -- **基数转换法**:将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。 - - 比如,将关键字看做是 `13` 进制的数,再将其转变为 `10` 进制的数,将其作为哈希地址。 - -以 `343246` 为例,哈希地址计算方式如下: - -$343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4 \times 13^1 + 6 \times 13^0 = 1235110_{10}$ - -## 3. 哈希冲突 - -> **哈希冲突(Hash Collision)**:不同的关键字通过同一个哈希函数可能得到同一哈希地址,即 `key1 ≠ key2`,而 `Hash(key1) = Hash(key2)`,这种现象称为哈希冲突。 - -理想状态下,我们的哈希函数是完美的一对一映射,即一个关键字(key)对应一个值(value),不需要处理冲突。但是一般情况下,不同的关键字 `key` 可能对应了同一个值 `value`,这就发生了哈希冲突。 - -设计再好的哈希函数也无法完全避免哈希冲突。所以就需要通过一定的方法来解决哈希冲突问题。常用的哈希冲突解决方法主要是两类:**「开放地址法(Open Addressing)」** 和 **「链地址法(Chaining)」**。 - -### 3.1 开放地址法 - -> **开放地址法(Open Addressing)**:指的是将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的单元,直到找到空的单元为止。 - -当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:`H(i) = (Hash(key) + F(i)) % m`,`i = 1, 2, 3, ..., n (n ≤ m - 1)`。 -- `H(i)` 是在处理冲突中得到的地址序列。即在第 1 次冲突(`i = 1`)时经过处理得到一个新地址 `H(1)`,如果在 `H(1)` 处仍然发生冲突(`i = 2`)时经过处理时得到另一个新地址 `H(2)` …… 如此下去,直到求得的 `H(n)` 不再发生冲突。 -- `Hash(key)` 是哈希函数,`m` 是哈希表表长,对哈希表长取余的目的是为了使得到的下一个地址一定落在哈希表中。 -- `F(i)` 是冲突解决方法,取法可以有以下几种: - - 线性探测法:$F(i) = 1, 2, 3, ..., m - 1$。 - - 二次探测法:$F(i) = 1^2, -1^2, 2^2, -2^2, ..., \pm n^2(n \le m / 2)$。 - - 伪随机数序列:$F(i) = \text{伪随机数序列}$。 - - -举个例子说说明一下如何用以上三种冲突解决方法处理冲突,并得到新地址 `H(i)`。例如,在长度为 `11` 的哈希表中已经填有关键字分别为 `28`、`49`、`18` 的记录(哈希函数为 `Hash(key) = key % 11`)。现在将插入关键字为 `38` 的新纪录。根据哈希函数得到的哈希地址为 `5`,产生冲突。接下来分别使用这三种冲突解决方法处理冲突。 - -- 使用线性探测法:得到下一个地址 `H(1) = (5 + 1) % 11 = 6`,仍然冲突;继续求出 `H(2) = (5 + 2) % 11 = 7`,仍然冲突;继续求出 `H(3) = (5 + 3) % 11 = 8`,`8` 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 `8` 的位置。 -- 使用二次探测法:得到下一个地址 `H(1) = (5 + 1*1) % 11 = 6`,仍然冲突;继续求出 `H(2) = (5 - 1*1) % 11 = 4`,`4` 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 `4` 的位置。 -- 使用伪随机数序列:假设伪随机数为 `9`,则得到下一个地址 `H(1) = (9 + 5) % 11 = 3`,`3` 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 `3` 的位置。 - -使用这三种方法处理冲突的结果如下图所示: - -![](https://qcdn.itcharge.cn/images/20220115162728.png) - -### 3.2 链地址法 - -> **链地址法(Chaining)**:将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。 - -链地址法是一种更加常用的哈希冲突解决方法。相比于开放地址法,链地址法更加简单。 - -我们假设哈希函数产生的哈希地址区间为 `[0, m - 1]`,哈希表的表长为 `m`。则可以将哈希表定义为一个有 `m` 个头节点组成的链表指针数组 `T`。 - -- 这样在插入关键字的时候,我们只需要通过哈希函数 `Hash(key)` 计算出对应的哈希地址 `i`,然后将其以链表节点的形式插入到以 `T[i]` 为头节点的单链表中。在链表中插入位置可以在表头或表尾,也可以在中间。如果每次插入位置为表头,则插入操作的时间复杂度为 $O(1)$。 - -- 而在在查询关键字的时候,我们只需要通过哈希函数 `Hash(key)` 计算出对应的哈希地址 `i`,然后将对应位置上的链表整个扫描一遍,比较链表中每个链节点的键值与查询的键值是否一致。查询操作的时间复杂度跟链表的长度 `k` 成正比,也就是 $O(k)$。对于哈希地址比较均匀的哈希函数来说,理论上讲,`k = n // m`,其中 `n` 为关键字的个数,`m` 为哈希表的表长。 - -举个例子来说明如何使用链地址法处理冲突。假设现在要存入的关键字集合 `keys = [88, 60, 65, 69, 90, 39, 07, 06, 14, 44, 52, 70, 21, 45, 19, 32]`。再假定哈希函数为 `Hash(key) = key % 13`,哈希表的表长 `m = 13`,哈希地址范围为 `[0, m - 1]`。将这些关键字使用链地址法处理冲突,并按顺序加入哈希表中(图示为插入链表表尾位置),最终得到的哈希表如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220115182535.png) - -相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间(主要是链节点占用空间)。但它可以减少在进行插入和查找具有相同哈希地址的关键字的操作过程中的平均查找长度。这是因为在链地址法中,待比较的关键字都是具有相同哈希地址的元素,而在开放地址法中,待比较的关键字不仅包含具有相同哈希地址的元素,而且还包含哈希地址不相同的元素。 - -## 4. 哈希表总结 - -本文讲解了一些比较基础、偏理论的哈希表知识。包含哈希表的定义,哈希函数、哈希冲突以及哈希冲突的解决方法。 - -- **哈希表(Hash Table)**:通过键 `key` 和一个映射函数 `Hash(key)` 计算出对应的值 `value`,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。 -- **哈希函数(Hash Function)**:将哈希表中元素的关键键值映射为元素存储位置的函数。 -- **哈希冲突(Hash Collision)**:不同的关键字通过同一个哈希函数可能得到同一哈希地址。 - -哈希表的两个核心问题是:**「哈希函数的构建」** 和 **「哈希冲突的解决方法」**。 - -- 常用的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。 -- 常用的哈希冲突的解决方法有两种:开放地址法和链地址法。 - -## 参考资料 - -- 【博文】[哈希算法及 python 字典的实现 – Tim's Path](https://xiaoxubeii.github.io/articles/hash/) -- 【文章】[哈希表 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/hash-table/xh8uld/) -- 【文章】[漫画算法 - 小灰的算法之旅 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/journey-of-algorithm/5o13c3/) -- 【博文】[面试官:哈希表都不知道,你是怎么看懂 HashMap 的? - 掘金](https://juejin.cn/post/6876105622274703368) -- 【博文】[散列表 | Frank's Blog](https://frankfang.cn/article/202104852) -- 【博文】[哈希表 - OI Wiki](https://oi-wiki.org/ds/hash/) -- 【博文】[散列表(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/64233) -- 【书籍】数据结构(C 语言版)- 严蔚敏 著 -- 【书籍】数据结构教程(第 3 版)- 唐发根 著 -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 \ No newline at end of file diff --git a/Contents/05.Hash-Table/02.Hash-Table-List.md b/Contents/05.Hash-Table/02.Hash-Table-List.md deleted file mode 100644 index a035c3e7..00000000 --- a/Contents/05.Hash-Table/02.Hash-Table-List.md +++ /dev/null @@ -1,37 +0,0 @@ -### 哈希表题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0705 | [设计哈希集合](https://leetcode.cn/problems/design-hashset/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0705.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E9%9B%86%E5%90%88.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0706 | [设计哈希映射](https://leetcode.cn/problems/design-hashmap/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0706.%20%E8%AE%BE%E8%AE%A1%E5%93%88%E5%B8%8C%E6%98%A0%E5%B0%84.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | -| 0217 | [存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0217.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0.md) | 数组、哈希表、排序 | 简单 | -| 0219 | [存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0219.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20II.md) | 数组、哈希表、滑动窗口 | 简单 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | -| 1941 | [检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1941.%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%89%80%E6%9C%89%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E7%9B%B8%E5%90%8C.md) | 哈希表、字符串、计数 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0383 | [赎金信](https://leetcode.cn/problems/ransom-note/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0383.%20%E8%B5%8E%E9%87%91%E4%BF%A1.md) | 哈希表、字符串、计数 | 简单 | -| 0349 | [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0349.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0350 | [两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0350.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86%20II.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | -| 0036 | [有效的数独](https://leetcode.cn/problems/valid-sudoku/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0036.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、矩阵 | 中等 | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0015 | [三数之和](https://leetcode.cn/problems/3sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0015.%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0018 | [四数之和](https://leetcode.cn/problems/4sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0018.%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、双指针、排序 | 中等 | -| 0454 | [四数相加 II](https://leetcode.cn/problems/4sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0454.%20%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0%20II.md) | 数组、哈希表 | 中等 | -| 0041 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0041.%20%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md) | 数组、哈希表 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | -| 0202 | [快乐数](https://leetcode.cn/problems/happy-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0202.%20%E5%BF%AB%E4%B9%90%E6%95%B0.md) | 哈希表、数学、双指针 | 简单 | -| 0242 | [有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0242.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、排序 | 简单 | -| 0205 | [同构字符串](https://leetcode.cn/problems/isomorphic-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0205.%20%E5%90%8C%E6%9E%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 哈希表、字符串 | 简单 | -| 0442 | [数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/) | | 数组、哈希表 | 中等 | -| 剑指 Offer 61 | [扑克牌中的顺子](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2061.%20%E6%89%91%E5%85%8B%E7%89%8C%E4%B8%AD%E7%9A%84%E9%A1%BA%E5%AD%90.md) | 数组、排序 | 简单 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 剑指 Offer 03 | [数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2003.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数组、哈希表、排序 | 简单 | -| 0451 | [根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0451.%20%E6%A0%B9%E6%8D%AE%E5%AD%97%E7%AC%A6%E5%87%BA%E7%8E%B0%E9%A2%91%E7%8E%87%E6%8E%92%E5%BA%8F.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | -| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 0599 | [两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0599.%20%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B4%A2%E5%BC%95%E6%80%BB%E5%92%8C.md) | 数组、哈希表、字符串 | 简单 | -| 0387 | [字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0387.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%94%AF%E4%B8%80%E5%AD%97%E7%AC%A6.md) | 队列、哈希表、字符串、计数 | 简单 | -| 0447 | [回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0447.%20%E5%9B%9E%E6%97%8B%E9%95%96%E7%9A%84%E6%95%B0%E9%87%8F.md) | 数组、哈希表、数学 | 中等 | -| 0149 | [直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0149.%20%E7%9B%B4%E7%BA%BF%E4%B8%8A%E6%9C%80%E5%A4%9A%E7%9A%84%E7%82%B9%E6%95%B0.md) | 几何、数组、哈希表、数学 | 困难 | -| 0359 | [日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0359.%20%E6%97%A5%E5%BF%97%E9%80%9F%E7%8E%87%E9%99%90%E5%88%B6%E5%99%A8.md) | 设计、哈希表 | 简单 | -| 0811 | [子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0811.%20%E5%AD%90%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E8%AE%A1%E6%95%B0.md) | 数组、哈希表、字符串、计数 | 中等 | - diff --git a/Contents/05.Hash-Table/index.md b/Contents/05.Hash-Table/index.md deleted file mode 100644 index 16278edc..00000000 --- a/Contents/05.Hash-Table/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [哈希表知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/05.Hash-Table/01.Hash-Table.md) -- [哈希表题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/05.Hash-Table/02.Hash-Table-List.md) \ No newline at end of file diff --git a/Contents/06.String/01.String-Basic/01.String-Basic.md b/Contents/06.String/01.String-Basic/01.String-Basic.md deleted file mode 100644 index e6b41e75..00000000 --- a/Contents/06.String/01.String-Basic/01.String-Basic.md +++ /dev/null @@ -1,193 +0,0 @@ -## 1. 字符串简介 - -> **字符串(String)**:简称为串,是由零个或多个字符组成的有限序列。一般记为 $s = a_1a_2…a_n (0 \le n ⪇ \infty)$。 - -下面介绍一下字符串相关的一些重要概念。 - -- **字符串名称**:字符串定义中的 `s` 就是字符串的名称。 -- **字符串的值**:$a_1a_2…a_n$ 组成的字符序列就是字符串的值,一般用双引号括起来。 -- **字符变量**:字符串每一个位置上的元素都是一个字符变量。字符 $a_i$ 可以是字母、数字或者其他字符。$i$ 是该字符在字符串中的位置。 -- **字符串的长度**:字符串中字符的数目 $n$ 称为字符串的长度。 -- **空串**:零个字符构成的串也成为 **「空字符串(Null String)」**,它的长度为 $0$,可以表示为 `""`。 -- **子串**:字符串中任意个连续的字符组成的子序列称为该字符串的 **「子串(Substring)」**。并且有两种特殊子串,起始于位置为 `0`、长度为 `k` 的子串称为 **「前缀(Prefix)」**。而终止于位置 `n - 1`、长度为 `k` 的子串称为 **「后缀(Suffix)」**。 -- **主串**:包含子串的字符串相应的称为 **「主串」**。 - -举个例子来说明一下: - -```python -str = "Hello World" -``` - -在示例代码中,`str` 是一个字符串的变量名称,`Hello World` 则是该字符串的值,字符串的长度为 `11`。该字符串的表示如下图所示: - -![](https://qcdn.itcharge.cn/images/20220117141211.png) - -可以看出来,字符串和数组有很多相似之处。比如同样使用 `名称[下标]` 的方式来访问一个字符。 - -之所以单独讨论字符串是因为: - -- 字符串中的数据元素都是字符,结构相对简单,但规模可能比较庞大。 -- 经常需要把字符串作为一个整体来使用和处理。操作对象一般不是某个数据元素,而是一组数据元素(整个字符串或子串)。 -- 经常需要考虑多个字符串之间的操作。比如:字符串之间的连接、比较操作。 - -根据字符串的特点,我们可以将字符串问题分为以下几种: - -- 字符串匹配问题。 -- 子串相关问题。 -- 前缀 / 后缀相关问题; -- 回文串相关问题。 -- 子序列相关问题。 - -## 2. 字符串的比较 - -### 2.1 字符串的比较操作 - -两个数字之间很容易比较大小,例如 `1 < 2`。而字符串之间的比较相对来说复杂一点。字符串之间的大小取决于它们按顺序排列字符的前后顺序。 - -比如字符串 `str1 = "abc"` 和 `str2 = "acc"`,它们的第一个字母都是 `a`,而第二个字母,由于字母 `b` 比字母 `c` 要靠前,所以 `b < c`,于是我们可以说 `"abc" < "acd" `,也可以说 `str1 < str2`。 - -字符串之间的比较是通过组成字符串的字符之间的「字符编码」来决定的。而字符编码指的是字符在对应字符集中的序号。 - -我们先来考虑一下如何判断两个字符串是否相等。 - -如果说两个字符串 `str1` 和 `str2` 相等,则必须满足两个条件: - -1. 字符串 `str1` 和字符串 `str2` 的长度相等。 -2. 字符串 `str1` 和字符串 `str2` 对应位置上的各个字符都相同。 - -下面我们再来考虑一下如何判断两个字符串的大小。 - -而对于两个不相等的字符串,我们可以以下面的规则定义两个字符串的大小: - -- 从两个字符串的第 `0` 个位置开始,依次比较对应位置上的字符编码大小。 - - 如果 `str1[i]` 对应的字符编码等于 `str2[i]` 对应的字符编码,则比较下一位字符。 - - 如果 `str1[i]` 对应的字符编码小于 `str2[i]` 对应的字符编码,则说明 `str1 < str2`。比如:`"abc" < "acc"`。 - - 如果 `str1[i]` 对应的字符编码大于 `str2[i]` 对应的字符编码,则说明 `str1 > str2`。比如:`"bcd" > "bad"`。 -- 如果比较到某一个字符串末尾,另一个字符串仍有剩余: - - 如果字符串 `str1` 的长度小于字符串 `str2`,即 `len(str1) < len(str2)`。则 `str1 < str2`。比如:`"abc" < "abcde"`。 - - 如果字符串 `str1` 的长度大于字符串 `str2`,即 `len(str1) > len(str2)`。则 `str1 > str2`。比如:`"abcde" > "abc"`。 -- 如果两个字符串每一个位置上的字符对应的字符编码都相等,且长度相同,则说明 `str1 == str2`,比如:`"abcd" == "abcd"`。 - -按照上面的规则,我们可以定义一个 `strcmp` 方法,并且规定: - -- 当 `str1 < str2` 时,`strcmp` 方法返回 `-1`。 -- 当 `str1 == str2` 时,`strcmp` 方法返回 `0`。 -- 当 `str1 > str2` 时,`strcmp` 方法返回 `1`。 - -`strcmp` 方法对应的具体代码如下: - -```python -def strcmp(str1, str2): - index1, index2 = 0, 0 - while index1 < len(str1) and index2 < len(str2): - if ord(str1[index1]) == ord(str2[index2]): - index1 += 1 - index2 += 1 - elif ord(str1[index1]) < ord(str2[index2]): - return -1 - else: - return 1 - - if len(str1) < len(str2): - return -1 - elif len(str1) > len(str2): - return 1 - else: - return 0 -``` - -上面关于字符串大小的定义有点复杂,其实字符串比较大小最简单的例子就是「查英语词典」。在英语词典的目录中,前面的单词要比后面的单词小。我们将英语词典中的每个英语单词都当做是一个字符串,那么查找单词的过程,其实就是比较字符串大小的过程。 - -### 2.2 字符串的字符编码 - -刚才我们提到了字符编码,这里我们也稍微介绍一下字符串中常用的字符编码标准。 - -以计算机中常用字符使用的 `ASCII` 编码为例。最早的时候,人们制定了一个包含 `127` 个字符的编码表 `ASCII` 到计算机系统中。`ASCII` 编码表中的字符包含了大小写的英文字母、数字和一些符号。每个字符对应一个编码,比如大写字母 `A` 的编码是 `65`,小写字母 `a` 的编码是 `97`。 - -`ASCII` 编码可以解决以英语为主的语言,可是无法满足中文编码。为了解决中文编码,我国制定了 `GB2312`、`GBK`、`GB18030` 等中文编码标准,将中文编译进去。但是世界上有上百种语言和文字,各国有各国的标准,就会不可避免的产生冲突,于是就有了 `Unicode` 编码。`Unicode` 编码最常用的就是 `UTF-8` 编码,`UTF-8` 编码把一个 `Unicode` 字符根据不同的数字大小编码成 `1` ~ `6` 个字节,常用的英文字母被编码成 `1` 个字节,汉字通常是 `3` 个字节。 - -## 3. 字符串的存储结构 - -字符串的存储结构跟线性表相同,分为「顺序存储结构」和「链式存储结构」。 - -### 3.1 字符串的顺序存储结构 - -与线性表的顺序存储结构相似,字符串的顺序存储结构也是使用一组地址连续的存储单元依次存放串中的各个字符。按照预定义的大小,为每个定义的字符串变量分配一个固定长度的存储区域。一般是用定长数组来定义。 - -字符串的顺序存储结构如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220118151100.png) - -如上图所示,字符串的顺序存储中每一个字符元素都有自己的下标索引,下标所以从 `0` 开始,到 `字符串长度 - 1` 结束。字符串中每一个「下标索引」,都有一个与之对应的「字符元素」。 - -跟数组类似,字符串也支持随机访问。即字符串可以根据下标,直接定位到某一个字符元素存放的位置。 - -### 3.2 字符串的链式存储结构 - -字符串的存储也可以采用链式存储结构,即采用一个线性链表来存储一个字符串。字符串的链节点包含一个用于存放字符的 `data` 变量,和指向下一个链节点的指针变量 `next`。这样,一个字符串就可以用一个线性链表来表示。 - -在字符串的链式存储结构中,每个链节点可以仅存放一个字符,也可以存放多个字符。通常情况下,链节点的字符长度为 `1` 或者 `4`,这是为了避免浪费空间。当链节点的字符长度为 `4` 时,由于字符串的长度不一定是 `4` 的倍数,因此字符串所占用的链节点中最后那个链节点的 `data` 变量可能没有占满,我们可以用 `#` 或其他不属于字符集的特殊字符将其补全。 - -字符串的链式存储结构图下图所示。 - -![](https://qcdn.itcharge.cn/images/20220118152105.png) - -如上图所示,字符串的链式存储将一组任意的存储单元串联在一起。链节点之间的逻辑关系是通过指针来间接反映的。 - -### 3.3 不同语言中的字符串 - -- C 语言中的字符串是使用空字符 `\0` 结尾的字符数组。`\0` 符号用于标记字符串的结束。C 语言的标准库 `string.h` 头文件中提供了各种操作字符串的函数。 -- C++ 语言中除了提供 C 风格的字符串,还引入了 `string` 类类型。`string` 类处理起字符串来会方便很多,完全可以代替 C 语言中的字符数组或字符串指针。 -- Java 语言的标准库中也提供了 `String` 类作为字符串库。 -- Python 语言中使用 `str` 对象来代表字符串。`str` 对象一种不可变类型对象。即 `str` 类型创建的字符串对象在定义之后,无法更改字符串的长度,也无法改变或删除字符串中的字符。 - -## 4. 字符串匹配问题 - -> **字符串匹配(String Matching)**:又称模式匹配(Pattern Matching)。可以简单理解为,给定字符串 `T` 和 `p`,在主串 `T` 中寻找子串 `p`。主串 `T` 又被称为文本串,子串 `p` 又被称为模式串(`Pattern`)。 - -在字符串问题中,最重要的问题之一就是字符串匹配问题。而按照模式串的个数,我们可以将字符串匹配问题分为:「单模式串匹配问题」和「多模式串匹配问题」。 - -### 4.1 单模式串匹配问题 - -> **单模式匹配问题(Single Pattern Matching)**:给定一个文本串 $T = t_1t_2...t_n$,再给定一个特定模式串 $p = p_1p_2...p_n$。要求从文本串 $T$ 找出特定模式串 $p$ 的所有出现位置。 - -有很多算法可以解决单模式匹配问题。而根据在文本中搜索模式串方式的不同,我们可以将单模式匹配算法分为以下几种: - -- **基于前缀搜索方法**:在搜索窗口内从前向后(沿着文本的正向)逐个读入文本字符,搜索窗口中文本和模式串的最长公共前缀。 - - 著名的 `Knuth-Morris-Pratt (KMP)` 算法和更快的 `Shift-Or` 算法使用的就是这种方法。 -- **基于后缀搜索方法**:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索窗口中文本和模式串的最长公共后缀。使用这种搜索算法可以跳过一些文本字符,从而具有亚线性的平均时间复杂度。 - - 最著名的 `Boyer-Moore` 算法,以及 `Horspool` 算法、`Sunday (Boyer-Moore 算法的简化)` 算法都使用了这种方法。 -- **基于子串搜索方法**:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索满足「既是窗口中文本的后缀,也是模式串的子串」的最长字符串。与后缀搜索方法一样,使用这种搜索方法也具有亚线性的平均时间复杂度。这种方法的主要缺点在于需要识别模式串的所有子串,这是一个非常复杂的问题。 - - `Rabin-Karp` 算法、`Backward Dawg Matching (BDM)` 算法、`Backward Nondeterministtic Dawg Matching (BNDM)` 算法和 `Backward Oracle Matching (BOM)` 算法使用的就是这种思想。其中,`Rabin-Karp` 算法使用了基于散列的子串搜索算法。 - -### 4.2 多模式串匹配问题 - -> **多模式匹配问题(Multi Pattern Matching)**:给定一个文本串 $T = t_1t_2...t_n$,再给定一组模式串 $P = {p^1, p^2, ... ,p^r}$,其中每个模式串 $p^i$ 是定义在有限字母表上的字符串 $p^i = p^i_1p^i_2...p^i_n$。要求从文本串 $T$ 中找到模式串集合 $P$ 中所有模式串 $p^i$ 的所有出现位置。 - -模式串集合 $P$ 中的一些字符串可能是集合中其他字符串的子串、前缀、后缀,或者完全相等。解决多模式串匹配问题最简单的方法是利用「单模式串匹配算法」搜索 `r` 遍。这将导致预处理阶段的最坏时间复杂度为 $O(|P|)$,搜索阶段的最坏时间复杂度为 $O(r * n)$。 - -如果使用「单模式串匹配算法」解决多模式匹配问题,那么根据在文本中搜索模式串方式的不同,我们也可以将多模式串匹配算法分为以下三种: - -- **基于前缀搜索方法**:搜索从前向后(沿着文本的正向)进行,逐个读入文本字符,使用在 $P$ 上构建的自动机进行识别。对于每个文本位置,计算既是已读入文本的后缀,同时也是 $P$ 中某个模式串的前缀的最长字符串。 - - 著名的 `Aho-Corasick Automaton (AC 自动机)` 算法、`Multiple Shift-And` 算法使用的这种方法。 -- **基于后缀搜索方法**:搜索从后向前(沿着文本的反向)进行,搜索模式串的后缀。根据后缀的下一次出现位置来移动当前文本位置。这种方法可以避免读入所有的文本字符。 - - `Commentz-Walter` 算法(`Boyer-Moore` 算法的扩展算法)、`Set Horspool` 算法(`Commentz-Walter` 算法的简化算法)、`Wu-Manber` 算法都使用了这种方法。 -- **基于子串搜索方法**:搜索从后向前(沿着文本的反向)进行,在模式串的长度为 $min(len(p^i))$ 的前缀中搜索子串,以此决定当前文本位置的移动。这种方法也可以避免读入所有的文本字符。 - - `Multiple BNDM` 算法、`Set Backward Dawg Matching (SBDM)` 算法、`Set Backwrad Oracle Matching (SBOM)` 算法都使用了这种方法。 - -需要注意的是,以上所介绍的多模式串匹配算法大多使用了一种基本的数据结构:**「字典树(Trie Tree)」**。著名的 **「Aho-Corasick Automaton (AC 自动机) 算法」** 就是在 `KMP` 算法的基础上,与「字典树」结构相结合而诞生的。而「AC 自动机算法」也是多模式串匹配算法中最有效的算法之一。 - -所以学习多模式匹配算法,重点是要掌握 **「字典树」** 和 **「AC 自动机算法」** 。 - -## 参考资料 - -- 【书籍】数据结构(C 语言版)- 严蔚敏 著 -- 【书籍】大话数据结构 - 程杰 著 -- 【书籍】数据结构教程(第 3 版)唐发根 著 -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 -- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 -- 【博文】[字符串和编码 - 廖雪峰的官方网站 ](https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896) -- 【文章】[数组和字符串 - LeetBook - 力扣](https://leetcode.cn/leetbook/read/array-and-string/c9lnm/) -- 【文章】[字符串部分简介 - OI Wiki](https://oi-wiki.org/string/) -- 【文章】[解密 Python 中字符串的底层实现,以及相关操作 - 古明地盆 - 博客园](https://www.cnblogs.com/traditional/p/13455962.html) - diff --git a/Contents/06.String/01.String-Basic/02.String-Basic-List.md b/Contents/06.String/01.String-Basic/02.String-Basic-List.md deleted file mode 100644 index 44679425..00000000 --- a/Contents/06.String/01.String-Basic/02.String-Basic-List.md +++ /dev/null @@ -1,15 +0,0 @@ -### 字符串基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0125 | [验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0125.%20%E9%AA%8C%E8%AF%81%E5%9B%9E%E6%96%87%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0003 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0003.%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、滑动窗口 | 中等 | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0557 | [反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0557.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%20III.md) | 双指针、字符串 | 简单 | -| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 数组、哈希表、字符串、排序 | 中等 | -| 0415 | [字符串相加](https://leetcode.cn/problems/add-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0415.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%8A%A0.md) | 数学、字符串、模拟 | 简单 | -| 0151 | [反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D.md) | 双指针、字符串 | 中等 | -| 0043 | [字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0043.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E4%B9%98.md) | 数学、字符串、模拟 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | - diff --git a/Contents/06.String/01.String-Basic/index.md b/Contents/06.String/01.String-Basic/index.md deleted file mode 100644 index 784ecfdf..00000000 --- a/Contents/06.String/01.String-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [字符串基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/01.String-Basic.md) -- [字符串经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/02.String-Basic-List.md) \ No newline at end of file diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md b/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md deleted file mode 100644 index 8e97897b..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md +++ /dev/null @@ -1,53 +0,0 @@ -## 1. Brute Force 算法介绍 - -> **Brute Force 算法**:简称为 BF 算法。中文意思是暴力匹配算法,也可以叫做朴素匹配算法。 -> -> - **BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 - -![](https://qcdn.itcharge.cn/images/20220205003716.png) - -## 2. Brute Force 算法步骤 - -1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 - 1. 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 - 2. 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 -3. 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 - -## 3. Brute Force 算法代码实现 - -```python -def bruteForce(T: str, p: str) -> int: - n, m = len(T), len(p) - - i, j = 0, 0 # i 表示文本串 T 的当前位置,j 表示模式串 p 的当前位置 - while i < n and j < m: # i 或 j 其中一个到达尾部时停止搜索 - if T[i] == p[j]: # 如果相等,则继续进行下一个字符匹配 - i += 1 - j += 1 - else: - i = i - (j - 1) # 如果匹配失败则将 i 移动到上次匹配开始位置的下一个位置 - j = 0 # 匹配失败 j 回退到模式串开始位置 - - if j == m: - return i - j # 匹配成功,返回匹配的开始位置 - else: - return -1 # 匹配失败,返回 -1 -``` - -## 4. Brute Force 算法分析 - -BF 算法非常简单,容易理解,但其效率很低。主要是因为在匹配过程中可能会出现回溯:当遇到一对字符不同时,模式串 `p` 直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开始比较。 - -在回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致 BF 算法的效率很低。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 `m` 次字符对比,总共需要进行 `n - m + 1` 轮比较,总的比较次数为 `m * (n - m + 1) `。所以 BF 算法的最坏时间复杂度为 $O(m \times n)$。 - -在最理想的情况下(第一次匹配直接匹配成功),BF 算法的最佳时间复杂度是 $O(m)$。 - -在一般情况下,根据等概率原则,平均搜索次数为 $\frac{(n + m)}{2}$,所以 Brute Force 算法的平均时间复杂度为 $O(n + m)$。 - -## 参考资料 - -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 -- 【文章】[动画:什么是 BF 算法 ?- 吴师兄学编程](https://www.cxyxiaowu.com/560.html) -- 【文章】[BF 算法(普通模式匹配算法)及 C 语言实现 - 数据结构与算法教程](http://data.biancheng.net/view/12.html) -- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187) diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md b/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md deleted file mode 100644 index 3f602667..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md +++ /dev/null @@ -1,104 +0,0 @@ -## 1. Rabin Karp 算法介绍 - -> **Rabin Karp 算法**:简称为 RK 算法。是由它的两位发明者 Michael Oser Rabin 和 Richard Manning Karp 的名字来命名的。RK 算法是他们在 1987 年提出的、使用哈希函数以在文本中搜寻单个模式串的字符串搜索算法。 -> -> - **Rabin Karp 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 - -## 2. Rabin Karp 算法步骤 - -### 2.1 Rabin Karp 算法整体步骤 - -1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 -3. 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 -4. 然后逐个与模式串的哈希值比较大小。 - 1. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 - 2. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 - 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 - 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 -5. 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 - -### 2.2 滚动哈希算法 - -实现 RK 算法中一个重要步骤是 **「滚动哈希算法」**,通过滚动哈希算法,将每次计算子串哈希值的复杂度从 $O(m)$ 降到了 $O(1)$,从而提升了整个算法效率。 - -RK 算法中的滚动哈希算法主要是利用了 **「Rabin fingerprint 思想」**。这种算法思想利用了子串中每一位字符的哈希值,并且还可以根据上一个子串的哈希值,快速计算相邻子串的哈希值,从而使得每次计算子串哈希值的时间复杂度降为了 $O(1)$。 - -下面我们用一个例子来解释一下这种算法思想。 - -假设给定的字符串的字符集中只包含 `d` 种字符,那么我们就可以用一个 `d` 进制数表示子串的哈希值。 - -举个例子,假如字符串只包含 `a` ~ `z` 这 `26` 个小写字母,那么我们就可以用 `26` 进制数来表示一个字符串,`a` 表示为 `0`,`b` 表示为 `1`,以此类推,`z` 就用 `25` 表示。 - -比如 `cat` 的哈希值就可以表示为: - -$\begin{align} Hash(cat) &= c \times 26 \times 26 + a \times 26 + t \times 1 \cr &= 2 \times 26 \times 26 + 0 \times 26 + 19 \times 1 \cr &= 1371 \end{align}$ - -这种按位计算哈希值的哈希函数有一个特点:在计算相邻子串时,可以利用上一个子串的哈希值。 - -比如说 `cat` 的相邻子串为 `ate`。按照刚才哈希函数计算,可以得出 `ate` 的哈希值为: - -$\begin{align} Hash(ate) &= a \times 26 \times 26 + t \times 26 + e \times 1 \cr &= 0 \times 26 \times 26 + 19 \times 26 + 4 \times 1 \cr &= 498 \end{align}$ - -如果利用上一个子串 `cat` 的哈希值计算 `ate`,则 `ate` 的哈希值为: - -$\begin{align} Hash(ate) &= (Hash(cat) - c \times 26 \times 26) * 26 + e \times 26 \cr &= (1371 - 2 \times 26 \times 26) \times 26 + 4 \times 1 \cr &= 498 \end{align}$ - -可以看出,这两种方式计算出的哈希值是相同的。但是第二种计算方式不需要再遍历子串,只需要进行一位字符的计算即可得出整个子串的哈希值。这样每次计算子串哈希值的时间复杂度就降到了 $O(1)$。然后我们就可以通过滚动哈希算法快速计算出子串的哈希值了。 - -我们将上面的规律扩展总结一下。 - -给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。字符串字符种类数为 `d`,则: - -- 模式串 `p` 的哈希值计算方式为:$Hash(p) = p_0 \times d^{m - 1} + p_1 \times d^{m - 2} + … + p_{m-1} \times d^{0}$。 -- 文本串中起始于位置 `0`,长度为 `m` 的子串 $T_{[0,m-1]}$ 对应哈希值计算方法为:$Hash(T_{[0, m - 1]}) = T_0 \times d^{m - 1} + T_1 \times d^{m - 2} + ... + T_{m - 1} \times d^0$。 -- 已知子串的哈希值 $Hash(T_{[i,i + m - 1]})$,将子串向右移动一位的子串对应哈希值计算方法为:$Hash(T_{[i + 1, i + m]}) = [Hash(T_{[i, i + m - 1]}) - T_i \times d^{m - 1}] \times d + T_{i + m} \times d^{0}$。 - -因为哈希值过大会造成溢出,所以我们在计算过程中还要对结果取模。取模的值应该尽可能大,并且应该是质数,这样才能减少哈希碰撞的概率。 - -## 3. Rabin Karp 算法代码实现 - -```python -# T 为文本串,p 为模式串,d 为字符集的字符种类数,q 为质数 -def rabinKarp(T: str, p: str, d, q) -> int: - n, m = len(T), len(p) - if n < m: - return -1 - - hash_p, hash_t = 0, 0 - - for i in range(m): - hash_p = (hash_p * d + ord(p[i])) % q # 计算模式串 p 的哈希值 - hash_t = (hash_t * d + ord(T[i])) % q # 计算文本串 T 中第一个子串的哈希值 - - power = pow(d, m - 1) % q # power 用于移除字符哈希时 - - for i in range(n - m + 1): - if hash_p == hash_t: # 检查模式串 p 的哈希值和子串的哈希值 - match = True # 如果哈希值相等,验证模式串和子串每个字符是否完全相同(避免哈希冲突) - for j in range(m): - if T[i + j] != p[j]: - match = False # 模式串和子串某个字符不相等,验证失败,跳出循环 - break - if match: # 如果模式串和子串每个字符是否完全相同,返回匹配开始位置 - return i - if i < n - m: # 计算下一个相邻子串的哈希值 - hash_t = (hash_t - power * ord(T[i])) % q # 移除字符 T[i] - hash_t = (hash_t * d + ord(T[i + m])) % q # 增加字符 T[i + m] - hash_t = (hash_t + q) % q # 确保 hash_t >= 0 - - return -1 -``` - -## 4. RK 算法分析 - -RK 算法可以看做是 BF 算法的一种改进。在 BF 算法中,每一个字符都需要进行比较。而在 RK 算法中,判断模式串的哈希值与每个子串的哈希值之间是否相等的时间复杂度为 $O(1)$。总共需要比较 `n - m + 1` 个子串的哈希值,所以 RK 算法的整体时间复杂度为 $O(n)$。跟 BF 算法相比,RK 算法的效率提高了很多。 - -但是如果存在冲突的情况下,算法的效率会降低。最坏情况是每一次比较模式串的哈希值和子串的哈希值时都相等,但是每一次都会出现冲突,那么每一次都需要验证模式串和子串每个字符是否完全相同,那么总的比较次数就是 `m * (n - m + 1) `,时间复杂度就会退化为 $O(m * n)$。 - -## 参考资料 - -- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 -- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187) -- 【文章】[字符串匹配算法 - Rabin Karp 算法 - coolcao 的小站](https://coolcao.com/2020/08/20/rabin-karp/) -- 【问答】[string - Python: Rabin-Karp algorithm hashing - Stack Overflow](https://stackoverflow.com/questions/22216948/python-rabin-karp-algorithm-hashing) diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md b/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md deleted file mode 100644 index 7e4b1a14..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md +++ /dev/null @@ -1,154 +0,0 @@ -## 1. KMP 算法介绍 - -> **KMP 算法**:全称叫做 **「Knuth Morris Pratt 算法」**,是由它的三位发明者 Donald Knuth、James H. Morris、 Vaughan Pratt 的名字来命名的。KMP 算法是他们三人在 1977 年联合发表的。 -> -> - **KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 - -### 1.1 朴素匹配算法的缺陷 - -在朴素匹配算法的匹配过程中,我们分别用指针 `i` 和指针 `j` 指示文本串 `T` 和模式串 `p` 中当前正在对比的字符。当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,`j` 回退到开始位置,`i` 回退到之前匹配开始位置的下一个位置上,然后开启新一轮的匹配,如图所示。 - -![](https://qcdn.itcharge.cn/images/20220205003716.png) - -这样,在 Brute Force 算法中,如果从文本串 `T[i]` 开始的这一趟字符串比较失败了,算法会直接开始尝试从 `T[i + 1]` 开始比较。如果 `i` 已经比较到了后边位置,则该操作相当于将指针 `i` 进行了回退操作。 - -那么有没有哪种算法,可以让 `i` 不发生回退,一直向右移动呢? - -### 1.2 KMP 算法的改进 - -如果我们可以通过每一次的失配而得到一些「信息」,并且这些「信息」可以帮助我们跳过那些不可能匹配成功的位置,那么我们就能大大减少模式串与文本串的匹配次数,从而达到快速匹配的目的。 - -每一次失配所告诉我们的信息是:**主串的某一个子串等于模式串的某一个前缀**。 - -这个信息的意思是:如果文本串 `T[i: i + m]` 与模式串 `p` 的失配是下标位置 `j` 上发生的,那么文本串 `T` 从下标位置 `i` 开始连续的 `j - 1` 个字符,一定与模式串 `p` 的前 `j - 1` 个字符一模一样,即:`T[i: i + j] == p[0: j]`。 - -但是知道这个信息有什么用呢? - -以刚才图中的例子来说,文本串的子串 `T[i: i + m]` 与模式串 `p` 的失配是在第 `5` 个位置发生的,那么: - -- 文本串 `T` 从下标位置 `i` 开始连续的 `5` 个字符,一定与模式串 `p` 的前 `5` 个字符一模一样,即:`"ABCAB" == "ABCAB"`。 -- 而模式串的前 `5` 个字符中,前 `2` 位前缀和后 `2` 位后缀又是相同的,即 `"AB" == "AB"`。 - -所以根据上面的信息,我们可以推出:文本串子串的后 `2` 位后缀和模式串子串的前 `2` 位是相同的,即 `T[i + 3: i + 5] == p[0: 2]`,而这部分(即下图中的蓝色部分)是之前已经比较过的,不需要再比较了,可以直接跳过。 - -那么我们就可以将文本串中的 `T[i + 5]` 对准模式串中的 `p[2]`,继续进行对比。这样 `i` 就不再需要回退了,可以一直向右移动匹配下去。在这个过程中,我们只需要将模式串 `j` 进行回退操作即可。 - -![](https://qcdn.itcharge.cn/images/20220205003701.png) - -KMP 算法就是使用了这样的思路,对模式串 `p` 进行了预处理,计算出一个 **「部分匹配表」**,用一个数组 `next` 来记录。然后在每次失配发生时,不回退文本串的指针 `i`,而是根据「部分匹配表」中模式串失配位置 `j` 的前一个位置的值,即 `next[j - 1]` 的值来决定模式串可以向右移动的位数。 - -比如上述示例中模式串 `p` 是在 `j = 5` 的位置上发生失配的,则说明文本串的子串 `T[i: i + 5]` 和模式串 `p[0: 5]` 的字符是一致的,即 `"ABCAB" == "ABCAB"`。而根据「部分匹配表」中 `next[4] == 2`,所以不用回退 `i`,而是将 `j` 移动到下标为 `2` 的位置,让 `T[i + 5]` 直接对准 `p[2]`,然后继续进行比对。 - -### 1.3 next 数组 - -上文提到的「部分匹配表」,也叫做「前缀表」,在 KMP 算法中使用 `next` 数组存储。`next[j]` 表示的含义是:**记录下标 j 之前(包括 j)的模式串 `p` 中,最长相等前后缀的长度。** - -简单而言,就是求:**模式串 `p` 的子串 `p[0: j + 1]` 中,使得「前 k 个字符」恰好等于「后 k 个字符」的「最长的 `k`」**。当然子串 `p[0: j + 1]` 本身不参与比较。 - -举个例子来说明一下,以 `p = "ABCABCD"` 为例。 - -- `next[0] = 0`,因为 `"A"` 中无有相同前缀后缀,最大长度为 `0`。 -- `next[1] = 0`,因为 `"AB"` 中无相同前缀后缀,最大长度为 `0`。 -- `next[2] = 0`,因为 `"ABC"` 中无相同前缀后缀,最大长度为 `0`。 -- `next[3] = 1`,因为 `"ABCA"` 中有相同的前缀后缀 `"a"`,最大长度为 `1`。 -- `next[4] = 2`,因为 `"ABCAB"` 中有相同的前缀后缀 `"AB"`,最大长度为 `2`。 -- `next[5] = 3`,因为 `"ABCABC"` 中有相同的前缀后缀 `"ABC"`,最大长度为 `3`。 -- `next[6] = 0`,因为 `"ABCABCD"` 中无相同前缀后缀,最大长度为 `0`。 - -同理也可以计算出 `"ABCABDEF"` 的前缀表为 `[0, 0, 0, 1, 2, 0, 0, 0]`。`"AABAAAB"` 的前缀表为 `[0, 1, 0, 1, 2, 2, 3]`。`"ABCDABD"` 的前缀表为 `[0, 0, 0, 0, 1, 2, 0]`。 - -在之前的例子中,当 `p[5]` 和 `T[i + 5]` 匹配失败后,根据模式串失配位置 `j` 的前一个位置的值,即 `next[4] = 2`,我们直接让 `T[i + 5]` 直接对准了 `p[2]`,然后继续进行比对,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220205003647.png) - -**但是这样移动的原理是什么?** - -其实在上文 **「1.2 KMP 算法的改进」** 中的例子中我们提到过了。现在我们将其延伸总结一下,其实这个过程就是利用了前缀表进行模式串移动的原理,具体推论如下。 - -如果文本串 `T[i: i + m]` 与模式串 `p` 的失配是在第 `j` 个下标位置发生的,那么: - -- 文本串 `T` 从下标位置 `i` 开始连续的 `j` 个字符,一定与模式串 `p` 的前 `j` 个字符一模一样,即:`T[i: i + j] == p[0: j]`。 -- 而如果模式串 `p` 的前 `j ` 个字符中,前 `k` 位前缀和后 `k` 位后缀相同,即 `p[0: k] == p[j - k: j]`,并且要保证 `k` 要尽可能长。 - -可以推出:文本串子串的后 `k` 位后缀和模式串子串的前 `k` 位是相同的,即 `T[i + m - k: i + m] == p[0: k]`(这部分是已经比较过的),不需要再比较了,可以直接跳过。 - -那么我们就可以将文本串中的 `T[i + m]` 对准模式串中的 `p[k]`,继续进行对比。这里的 `k` 其实就是 `next[j - 1]`。 - -## 2. KMP 算法步骤 - -### 3.1 next 数组的构造 - -我们可以通过递推的方式构造 `next` 数组。 - -- 我们把模式串 `p` 拆分成 `left`、`right` 两部分。`left` 表示前缀串开始所在的下标位置,`right` 表示后缀串开始所在的下标位置,起始时 `left = 0`,`right = 1`。 -- 比较一下前缀串和后缀串是否相等。通过比较 `p[left]` 和 `p[right]` 来进行判断。 -- 如果 `p[left] != p[right]`,说明当前的前后缀不相同。则让后缀开始位置 `k` 不动,前缀串开始位置 `left` 不断回退到 `next[left - 1]` 位置,直到 `p[left] == p[right]` 为止。 -- 如果 `p[left] == p[right]`,说明当前的前后缀相同,则可以先让 `left += 1`,此时 `left` 既是前缀下一次进行比较的下标位置,又是当前最长前后缀的长度。 -- 记录下标 `right` 之前的模式串 `p` 中,最长相等前后缀的长度为 `left`,即 `next[right] = left`。 - -### 3.2 KMP 算法整体步骤 - -1. 根据 `next` 数组的构造步骤生成「前缀表」`next`。 -2. 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 -3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 -4. 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 -5. 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 -6. 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 -7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 - -## 3. KMP 算法代码实现 - -```python -# 生成 next 数组 -# next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度 -def generateNext(p: str): - m = len(p) - next = [0 for _ in range(m)] # 初始化数组元素全部为 0 - - left = 0 # left 表示前缀串开始所在的下标位置 - for right in range(1, m): # right 表示后缀串开始所在的下标位置 - while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退 - left = next[left - 1] # left 进行回退操作 - if p[left] == p[right]: # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度 - left += 1 - next[right] = left # 记录前缀长度,更新 next[right], 结束本次循环, right += 1 - - return next - -# KMP 匹配算法,T 为文本串,p 为模式串 -def kmp(T: str, p: str) -> int: - n, m = len(T), len(p) - - next = generateNext(p) # 生成 next 数组 - - j = 0 # j 为模式串中当前匹配的位置 - for i in range(n): # i 为文本串中当前匹配的位置 - while j > 0 and T[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退 - j = next[j - 1] - if T[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配 - j += 1 - if j == m: # 当前模式串完全匹配成功,返回匹配开始位置 - return i - j + 1 - return -1 # 匹配失败,返回 -1 - -print(kmp("abbcfdddbddcaddebc", "ABCABCD")) -print(kmp("abbcfdddbddcaddebc", "bcf")) -print(kmp("aaaaa", "bba")) -print(kmp("mississippi", "issi")) -print(kmp("ababbbbaaabbbaaa", "bbbb")) -``` - -## 4. KMP 算法分析 - -- KMP 算法在构造前缀表阶段的时间复杂度为 $O(m)$,其中 $m$ 是模式串 `p` 的长度。 -- KMP 算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标 `i` 并没有进行回退,可以看出匹配阶段的时间复杂度是 $O(n)$,其中 $n$ 是文本串 `T` 的长度。 -- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n * m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。 - -## 参考资料 - -- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 -- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 -- 【博文】[从头到尾彻底理解 KMP - 结构之法 算法之道 - CSDN博客](https://blog.csdn.net/v_JULY_v/article/details/7041827?spm=1001.2014.3001.5502) -- 【博文】[字符串匹配的 KMP 算法 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html) -- 【题解】[多图预警👊🏻详解 KMP 算法 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/duo-tu-yu-jing-xiang-jie-kmp-suan-fa-by-w3c9c/) -- 【题解】[「代码随想录」KMP算法详解 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/dai-ma-sui-xiang-lu-kmpsuan-fa-xiang-jie-mfbs/) \ No newline at end of file diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md b/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md deleted file mode 100644 index dc54e5f2..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md +++ /dev/null @@ -1,309 +0,0 @@ -## 1. Boyer Moore 算法介绍 - -> **Boyer Moore 算法**:简称为 BM 算法,是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中,比 KMP 算法要快 3~5 倍。 -> -> - **BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -BM 算法的精髓在于使用了两种不同的启发策略来计算后移位数:**「坏字符规则(The Bad Character Rule)」** 和 **「好后缀规则(The Good Suffix Shift Rule)」**。 - -这两种启发策略的计算过程只与模式串 `p` 相关,而与文本串 `T` 无关。因此在对模式串 `p` 进行预处理时,可以预先生成「坏字符规则后移表」和「好后缀规则后移表」,然后在匹配的过程中,只需要比较一下两种策略下最大的后移位数进行后移即可。 - -同时,还需要注意一点。BM 算法在移动模式串的时候和常规匹配算法一样是从左到右进行,但是在进行比较的时候是从右到左,即基于后缀进行比较。 - -下面我们来讲解一下 BF 算法中的两种不同启发策略:「坏字符规则」和「好后缀规则」。 - -## 2. Boyer Moore 算法启发策略 - -### 2.1 坏字符规则 - -> **坏字符规则(The Bad Character Rule)**:当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,则称文本串 `T` 中这个失配字符为 **「坏字符」**,此时模式串 `p` 可以快速向右移动。 - -「坏字符规则」的移动位数分为两种情况: - -- **情况 1:坏字符出现在模式串 `p` 中**。 - - 这种情况下,可将模式串中最后一次出现的坏字符与文本串中的坏字符对齐,如下图所示。 - - **向右移动位数 = 坏字符在模式串中的失配位置 - 坏字符在模式串中最后一次出现的位置**。 - -![](https://qcdn.itcharge.cn/images/20220128162720.png) - -- **情况 2:坏字符没有出现在模式串 `p` 中**。 - - 这种情况下,可将模式串向右移动一位,如下图所示。 - - **向右移动位数 = 坏字符在模式串中的失配位置 + 1**。 - -![](https://qcdn.itcharge.cn/images/20220128162735.png) - -### 2.2 好后缀规则 - -> **好后缀规则(The Good Suffix Shift Rule)**:当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,则称文本串 `T` 中已经匹配好的字符串为 **「好后缀」**,此时模式串 `p` 可以快速向右移动。 - -「好后缀规则」的移动方式分为三种情况: - -- **情况 1:模式串中有子串匹配上好后缀**。 - - 这种情况下,移动模式串,让该子串和好后缀对齐即可。如果超过一个子串匹配上好后缀,则选择最右侧的子串对齐,如下图所示。 - - **向右移动位数 = 好后缀的最后一个字符在模式串中的位置 - 匹配的子串最后一个字符出现的位置**。 - -![](https://qcdn.itcharge.cn/images/20220128162537.png) - -- **情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀**。 - - 这种情况下,我们需要在模式串的前缀中寻找一个最长前缀,该前缀等于好后缀的后缀。找到该前缀后,让该前缀和好后缀的后缀对齐。 - - **向右移动位数 = 好后缀的后缀的最后一个字符在模式串中的位置 - 最长前缀的最后一个字符出现的位置**。 - -![](https://qcdn.itcharge.cn/images/20220128162600.png) - -- **情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配**。 - - 可将模式串整个右移。 - - **向右移动位数 = 模式串的长度**。 - -![](https://qcdn.itcharge.cn/images/20220128162651.png) - -## 3. Boyer Moore 算法匹配过程示例 - -下面我们根据 J Strother Moore 教授给出的例子,先来介绍一下 BF 算法的匹配过程,顺便加深对 **「坏字符规则」** 和 **「好后缀规则」** 的理解。 - -1. 假设文本串为 `"HERE IS A SIMPLE EXAMPLE"`,模式串为 `"EXAMPLE"`,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164130.png) - -2. 首先,令模式串与文本串的头部对齐,然后从模式串的尾部开始逐位比较,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164140.png) - -可以看出来,`'S'` 与 `'E'` 不匹配。这时候,不匹配的字符 `'S'` 就被称为「坏字符(Bad Character)」,对应着模式串的第 `6` 位。并且 `'S'` 并不包含在模式串 `"EXAMPLE"` 中(相当于`'S'` 在模式串中最后一次出现的位置是 `-1`)。根据「坏字符规则」,可以把模式串直接向右移动 `6 - (-1) = 7` 位,即将文本串中 `'S'` 的后一位上。 - -3. 将模式串向右移动 `7` 位。然后依然从模式串尾部开始比较,发现 `'P'` 和 `'E'` 不匹配,则 `'P'` 是坏字符,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164151.png) - -但是 `'P'` 包含在模式串 `"EXAMPLE"` 中,`'P'` 这个坏字符在模式串中的失配位置是第 `6` 位,并且在模式串中最后一次出现的位置是 `4`(编号从 `0` 开始)。 - -4. 根据「坏字符规则」,可以将模式串直接向右移动 `6 - 4 = 2` 位,将文本串的 `'P'` 和模式串中的 `'P'` 对齐,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164202.png) - -5. 我们继续从尾部开始逐位比较。先比较文本串的 `'E'` 和模式串的 `'E'`,如下图所示。可以看出文本串的 `'E'` 和模式串的 `'E'` 匹配,则 `"E"` 为好后缀,`"E"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 - -![](https://qcdn.itcharge.cn/images/20220127164212.png) - -6. 继续比较前面一位,即文本串的 `'L'` 和模式串的 `'L'`,如下图所示。可以看出文本串的 `'L'` 和模式串的 `'L'` 匹配。则 `"LE"` 为好后缀,`"LE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 - -![](https://qcdn.itcharge.cn/images/20220127164222.png) - -7. 继续比较前面一位,即文本串中的 `'P'` 和模式串中的 `'P'`,如下图所示。可以看出文本串中的 `'P'` 和模式串中的 `'P'` 匹配,则 `"PLE"` 为好后缀,`"PLE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 - -![](https://qcdn.itcharge.cn/images/20220127164232.png) - -8. 继续比较前面一位,即文本串中的 `'M'` 和模式串中的 `'M'`,如下图所示。可以看出文本串中的 `'M'` 和模式串中的 `'M'` 匹配,则 `"MPLE"` 为好后缀。`"MPLE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 - -![](https://qcdn.itcharge.cn/images/20220127164241.png) - -9. 继续比较前面一位,即文本串中的 `'I'` 和模式串中的 `'A'`,如下图所示。可以看出文本串中的 `'I'` 和模式串中的 `'A'` 不匹配。 - -![](https://qcdn.itcharge.cn/images/20220127164251.png) - -此时,如果按照「坏字符规则」,模式串应该向右移动 `2 - (-1) = 3` 位。但是根据「好后缀规则」,我们还有更好的移动方法。 - -在好后缀 `"MPLE"` 和好后缀的后缀 `"PLE"`、`"LE"`、`"E"` 中,只有好后缀的后缀 `"E"` 和模式串中的前缀 `"E"` 相匹配,符合好规则的第二种情况。好后缀的后缀 `"E"` 的最后一个字符在模式串中的位置为 `6`,最长前缀 `"E"`的最后一个字符出现的位置为 `0`,则根据「好后缀规则」,可以将模式串直接向右移动 `6 - 0 = 6` 位。如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164301.png) - -10. 继续从模式串的尾部开始逐位比较,如下图所示。 - -可以看出,`'P'` 与`'E'` 不匹配,`'P'` 是坏字符。根据「坏字符规则」,可以将模式串直接向右移动 `6 - 4 = 2` 位,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220127164312.png) - -11. 继续从模式串的尾部开始逐位比较,发现模式串全部匹配,于是搜索结束,返回模式串在文本串中的位置。 - -## 4. Boyer Moore 算法步骤 - -整个 BM 算法步骤描述如下: - -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 - 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: - 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 - 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 - 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -## 5. Boyer Moore 算法代码实现 - -BM 算法的匹配过程实现起来并不是很难,而整个算法实现的难点在于预处理阶段的「生成坏字符位置表」和「生成好后缀规则后移位数表」这两步上。尤其是「生成好后缀规则后移位数表」,实现起来十分复杂。下面我们一一进行讲解。 - -### 5.1 生成坏字符位置表代码实现 - -生成坏字符位置表的代码实现比较简单。具体步骤如下: - -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示坏字符 `bad_char` 在模式串中出现的最右位置。 - -- 遍历模式串,以当前字符 `p[i]` 为键,所在位置下标为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现的最右侧位置。 - -这样如果在 BM 算法的匹配过程中,如果 `bad_char` 不在 `bc_table` 中时,可令 `bad_char` 在模式串中出现的最右侧位置为 `-1`。如果 `bad_char` 在 `bc_table` 中时,`bad_char` 在模式串中出现的最右侧位置就是 `bc_table[bad_char]`。这样就可以根据公式计算出可以向右移动的位数了。 - -生成坏字符位置表的代码如下: - -```python -# 生成坏字符位置表 -# bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 -def generateBadCharTable(p: str): - bc_table = dict() - - for i in range(len(p)): - bc_table[p[i]] = i # 更新坏字符在模式串中最后一次出现的位置 - return bc_table -``` - -### 5.2 生成好后缀规则后移位数表代码实现 - -为了生成好后缀规则后移位数表,我们需要先定义一个后缀数组 `suffix`,其中 `suffix[i] = s` 表示为以下标 `i` 为结尾的子串与模式串后缀匹配的最大长度为 `s`。即满足 `p[i-s...i] == p[m-1-s, m-1]` 的最大长度为 `s`。 - -构建 `suffix` 数组的代码如下: - -```python -# 生成 suffix 数组 -# suffix[i] 表示为以下标 i 为结尾的子串与模式串后缀匹配的最大长度 -def generageSuffixArray(p: str): - m = len(p) - suffix = [m for _ in range(m)] # 初始化时假设匹配的最大长度为 m - for i in range(m - 2, -1, -1): # 子串末尾从 m - 2 开始 - start = i # start 为子串开始位置 - while start >= 0 and p[start] == p[m - 1 - i + start]: - start -= 1 # 进行后缀匹配,start 为匹配到的子串开始位置 - suffix[i] = i - start # 更新以下标 i 为结尾的子串与模式串后缀匹配的最大长度 - return suffix -``` - -有了 `suffix` 数组,我们就可以在此基础上定义好后缀规则后移位数表 `gs_list`。我们使用一个数组来表示好后缀规则后移位数表。其中 `gs_list[j]` 表示在 `j` 下标处遇到坏字符时,可根据好规则向右移动的距离。 - -由 `2.2 好后缀规则` 中可知,好后缀规则的移动方式可以分为三种情况。 - -- 情况 1:模式串中有子串匹配上好后缀。 -- 情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀。 -- 情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配。 - -这 3 种情况中,情况 2 和情况 3 可以合并,因为情况 3 可以看做是匹配到的最长前缀长度为 `0`。而如果遇到一个坏字符同时满足多种情况,则我们应该选择满足情况中最小的移动距离才不会漏掉可能匹配的情况,比如说当模式串中既有子串可以匹配上好后缀,又有前缀可以匹配上好后缀的后缀,则应该按照前者的方式移动模式串。 - -- 为了得到精确的 `gs_list[j]`,我们可以先假定所有情况都为情况 3,即 `gs_list[i] = m`。 -- 然后通过后缀和前缀匹配的方法,更新情况 2 下 `gs_list` 中坏字符位置处的值,即 `gs_list[j] = m - 1 - i`,其中 `j` 是好后缀前的坏字符位置,`i` 是最长前缀的末尾位置,`m - 1 - i` 是可向右移动的距离。 -- 最后再计算情况 1 下 `gs_list` 中坏字符位置处的值,更新在好后缀的左端点处(`m - 1 - suffix[i]` 处)遇到坏字符可向后移动位数,即 `gs_list[m - 1 - suffix[i]] = m - 1 - i`。 - -生成好后缀规则后移位数表 `gs_list` 代码如下: - -```python -# 生成好后缀规则后移位数表 -# gs_list[j] 表示在 j 下标处遇到坏字符时,可根据好规则向右移动的距离 -def generageGoodSuffixList(p: str): - # 好后缀规则后移位数表 - # 情况 1: 模式串中有子串匹配上好后缀 - # 情况 2: 模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀 - # 情况 3: 模式串中无子串匹配上好后缀,也找不到前缀匹配 - - m = len(p) - gs_list = [m for _ in range(m)] # 情况 3:初始化时假设全部为情况 3 - suffix = generageSuffixArray(p) # 生成 suffix 数组 - - j = 0 # j 为好后缀前的坏字符位置 - for i in range(m - 1, -1, -1): # 情况 2:从最长的前缀开始检索 - if suffix[i] == i + 1: # 匹配到前缀,即 p[0...i] == p[m-1-i...m-1] - while j < m - 1 - i: - if gs_list[j] == m: - gs_list[j] = m - 1 - i # 更新在 j 处遇到坏字符可向后移动位数 - j += 1 - - for i in range(m - 1): # 情况 1:匹配到子串, p[i-s...i] == p[m-1-s, m-1] - gs_list[m - 1 - suffix[i]] = m - 1 - i # 更新在好后缀的左端点处遇到坏字符可向后移动位数 - return gs_list -``` - -### 5.3 Boyer Moore 算法整体代码实现 - -```python -# BM 匹配算法 -def boyerMoore(T: str, p: str) -> int: - n, m = len(T), len(p) - - bc_table = generateBadCharTable(p) # 生成坏字符位置表 - gs_list = generageGoodSuffixList(p) # 生成好后缀规则后移位数表 - - i = 0 - while i <= n - m: - j = m - 1 - while j > -1 and T[i + j] == p[j]: # 进行后缀匹配,跳出循环说明出现坏字符 - j -= 1 - if j < 0: - return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 - bad_move = j - bc_table.get(T[i + j], -1) # 坏字符规则下的后移位数 - good_move = gs_list[j] # 好后缀规则下的后移位数 - i += max(bad_move, good_move) # 取两种规则下后移位数的最大值进行移动 - return -1 - - -# 生成坏字符位置表 -# bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 -def generateBadCharTable(p: str): - bc_table = dict() - - for i in range(len(p)): - bc_table[p[i]] = i # 更新坏字符在模式串中最后一次出现的位置 - return bc_table - -# 生成好后缀规则后移位数表 -# gs_list[j] 表示在 j 下标处遇到坏字符时,可根据好规则向右移动的距离 -def generageGoodSuffixList(p: str): - # 好后缀规则后移位数表 - # 情况 1: 模式串中有子串匹配上好后缀 - # 情况 2: 模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀 - # 情况 3: 模式串中无子串匹配上好后缀,也找不到前缀匹配 - - m = len(p) - gs_list = [m for _ in range(m)] # 情况 3:初始化时假设全部为情况 3 - suffix = generageSuffixArray(p) # 生成 suffix 数组 - - j = 0 # j 为好后缀前的坏字符位置 - for i in range(m - 1, -1, -1): # 情况 2:从最长的前缀开始检索 - if suffix[i] == i + 1: # 匹配到前缀,即 p[0...i] == p[m-1-i...m-1] - while j < m - 1 - i: - if gs_list[j] == m: - gs_list[j] = m - 1 - i # 更新在 j 处遇到坏字符可向后移动位数 - j += 1 - - for i in range(m - 1): # 情况 1:匹配到子串 p[i-s...i] == p[m-1-s, m-1] - gs_list[m - 1 - suffix[i]] = m - 1 - i # 更新在好后缀的左端点处遇到坏字符可向后移动位数 - return gs_list - -# 生成 suffix 数组 -# suffix[i] 表示为以下标 i 为结尾的子串与模式串后缀匹配的最大长度 -def generageSuffixArray(p: str): - m = len(p) - suffix = [m for _ in range(m)] # 初始化时假设匹配的最大长度为 m - for i in range(m - 2, -1, -1): # 子串末尾从 m - 2 开始 - start = i # start 为子串开始位置 - while start >= 0 and p[start] == p[m - 1 - i + start]: - start -= 1 # 进行后缀匹配,start 为匹配到的子串开始位置 - suffix[i] = i - start # 更新以下标 i 为结尾的子串与模式串后缀匹配的最大长度 - return suffix - -print(boyerMoore("abbcfdddbddcaddebc", "aaaaa")) -print(boyerMoore("", "")) -``` - -## 6. Boyer Moore 算法分析 - -- BM 算法在预处理阶段的时间复杂度为 $O(n + \sigma)$,其中 $\sigma$ 是字符集的大小。 -- BM 算法在搜索阶段最好情况是每次匹配时,模式串 `p` 中不存在与文本串 `T` 中第一个匹配的字符。这时的时间复杂度为 $O(n / m)$。 -- BM 算法在搜索阶段最差情况是文本串 `T` 中有多个重复的字符,并且模式串 `p` 中有 `m - 1` 个相同字符前加一个不同的字符组成。这时的时间复杂度为 $O(m * n)$。 -- 当模式串 `p` 是非周期性的,在最坏情况下,BM 算法最多需要进行 $3 * n$ 次字符比较操作。 - -## 参考资料 - -- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 -- 【文章】[不用找了,学习 BM 算法,这篇就够了(思路+详注代码)- BoCong-Deng 的博客](https://blog.csdn.net/DBC_121/article/details/105569440) -- 【文章】[字符串匹配的 Boyer-Moore 算法 - 阮一峰的网络日志](https://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html) -- 【文章】[ bm 算法好后缀 java 实现 - 长笛小号的博客 - CSDN博客](https://blog.csdn.net/weixin_29217235/article/details/114488027) -- 【文章】[BM算法详解 - 简单爱_wxg - 博客园](https://www.cnblogs.com/wxgblogs/p/5701101.html) -- 【文章】[grep 之字符串搜索算法 Boyer-Moore 由浅入深 - Alexia(minmin) - 博客园](https://www.cnblogs.com/lanxuezaipiao/p/3452579.html) -- 【文章】[字符串匹配基础(中)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71525) -- 【代码】[BM算法 附有解释 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/bmsuan-fa-fu-you-jie-shi-by-wen-198/) \ No newline at end of file diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md b/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md deleted file mode 100644 index be831fc1..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md +++ /dev/null @@ -1,103 +0,0 @@ -## 1.1 Horspool 算法介绍 - -> **Horspool 算法**:是一种在字符串中查找子串的算法,它是由 Nigel Horspool 教授于 1980 年出版的,是首个对 Boyer Moore 算法进行简化的算法。 -> -> - **Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -可以看出,Horspool 算法思想和 Boyer Moore 算法思想是一致的。Horspool 算法是在 Boyer Moore 算法思想基础上改进了「坏字符规则」。当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,可以模式串 `p` 快速向右移动。 - -遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: - -- **情况 1:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符 `T[i + m - 1]` 出现在模式串 `p` 中**。 - - 这种情况下,可将 `T[i + m - 1]` 与模式串中最后一次出现的该字符对齐,如下图所示。 - - **向右移动位数 = 模式串最后一个字符的位置 - T[i + m - 1] 在模式串中最后一次出现的位置**。 - - 注意:模式串最后一个字符的位置其实就是「模式串长度 - 1」。 - -![](https://qcdn.itcharge.cn/images/20220128164320.png) - -- **情况 2:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符 `T[i + m - 1]` 没有出现在模式串 `p` 中**。 - - 这种情况下,可将模式串整个右移,如下图所示。 - - **向右移动位数 = 整个模式串长度**。 - -![](https://qcdn.itcharge.cn/images/20220128164333.png) - -## 2. Horspool 算法步骤 - -整个 Horspool 算法步骤描述如下: - -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 - 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -## 3. Horspool 算法代码实现 - -### 3.1 后移位数表代码实现 - -生成后移位数表的代码实现比较简单,跟 Boyer Moore 算法中生成坏字符位置表的代码差不多。具体步骤如下: - -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示表示遇到坏字符可以向右移动的距离。 -- 遍历模式串,以当前字符 `p[i]` 为键,可以向右移动的距离(`m - 1 - i`)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 - -如果在 Horspool 算法的匹配过程中,如果 `T[i + m - 1]` 不在 `bc_table` 中时,可令其为 `m`,表示可以将模式串整个右移。如果 `T[i + m - 1]` 在 `bc_table` 中时,可移动距离就是 `bc_table[T[i + m - 1]]` 。这样就能计算出可以向右移动的位数了。 - -生成后移位数表的代码如下: - -```python -# 生成后移位数表 -# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 -def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m - 1): # 迭代到 m - 2 - bc_table[p[i]] = m - 1 - i # 更新遇到坏字符可向右移动的距离 - return bc_table -``` - -### 3.2 Horspool 算法整体代码实现 - -```python -# horspool 算法,T 为文本串,p 为模式串 -def horspool(T: str, p: str) -> int: - n, m = len(T), len(p) - - bc_table = generateBadCharTable(p) # 生成后移位数表 - - i = 0 - while i <= n - m: - j = m - 1 - while j > -1 and T[i + j] == p[j]: # 进行后缀匹配,跳出循环说明出现坏字符 - j -= 1 - if j < 0: - return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 - i += bc_table.get(T[i + m - 1], m) # 通过后移位数表,向右进行进行快速移动 - return -1 # 匹配失败 - -# 生成后移位数表 -# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 -def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m - 1): # 迭代到 m - 2 - bc_table[p[i]] = m - 1 - i # 更新遇到坏字符可向右移动的距离 - return bc_table - -print(horspool("abbcfdddbddcaddebc", "aaaaa")) -print(horspool("abbcfdddbddcaddebc", "bcf")) -``` - -## 4. Horspool 算法分析 - -- Horspool 算法在平均情况下的时间复杂度为 $O(n)$,但是在最坏情况下时间复杂度会退化为 $O(n * m)$。 - -## 参考资料 - -- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 -- 【博文】[字符串模式匹配算法:BM、Horspool、Sunday、KMP、KR、AC算法 - schips - 博客园](https://www.cnblogs.com/schips/p/11098041.html) - diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md b/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md deleted file mode 100644 index 1b37ef55..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md +++ /dev/null @@ -1,103 +0,0 @@ -## 1. Sunday 算法介绍 - -**「Sunday 算法」** 是一种在字符串中查找子串的算法,是 Daniel M.Sunday 于1990年提出的字符串模式匹配算法。 - -> **Sunday 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -Sunday 算法思想跟 Boyer Moore 算法思想类似。不同的是,Sunday 算法匹配顺序是从左向右,并且在模式串 `p` 匹配失败时关注的是文本串 `T` 中参加匹配的末尾字符的下一位字符。当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,可以将模式串 `p` 快速向右移动。 - -遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: - -- **情况 1:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符下一个位置的字符 `T[i + m]` 出现在模式串 `p` 中**。 - - 这种情况下,可将`T[i + m]` 与模式串中最后一次出现的该字符对齐,如下图所示。 - - **向右移动位数 = 文本串 `T` 中与模式串 `p` 尾部位置的下一个位置 - T[i + m] 在模式串中最后一次出现的位置**。 - - 注意:文本串 `T` 中与模式串 `p` 尾部位置的下一个位置其实就是「模式串长度」。 - -![](https://qcdn.itcharge.cn/images/20220128165756.png) - -- **情况 2:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符下一个位置的字符 `T[i + m]` 没有出现在模式串 `p` 中**。 - - 这种情况下,可将模式串整个右移,如下图所示。 - - **向右移动位数 = 整个模式串长度 + 1**。 - -![](https://qcdn.itcharge.cn/images/20220128165811.png) - -## 2. Sunday 算法步骤 - -整个 Horspool 算法步骤描述如下: - -- 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -- 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -- 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串开始,即 `j = 0`,然后从模式串开始位置开始比较。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较后一位字符。 - - 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - - 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m]` ,计算出可移动距离 `bc_table[T[i + m]]`,然后将模式串进行后移。 -- 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -## 3. Sunday 算法代码实现 - -### 3.1 后移位数表代码实现 - -生成后移位数表的代码实现比较简单,跟 Horspool 算法中生成后移位数表的代码差不多。具体步骤如下: - -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示表示遇到坏字符可以向右移动的距离。 -- 遍历模式串,以当前字符 `p[i]` 为键,可以向右移动的距离(`m - i`)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 - -如果在 Sunday 算法的匹配过程中,如果 `T[i + m]` 不在 `bc_table` 中时,可令其为 `m + 1`,表示可以将模式串整个右移到上一次匹配末尾后边两个位置上。如果 `T[i + m]` 在 `bc_table` 中时,可移动距离就是 `bc_table[T[i + m]]` 。这样就能计算出可以向右移动的位数了。 - -生成后移位数表的代码如下: - -```python -# 生成后移位数表 -# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 -def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m): # 迭代到最后一个位置 m - 1 - bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 - return bc_table -``` - -### 3.2 Sunday 算法整体代码实现 - -```python -# sunday 算法,T 为文本串,p 为模式串 -def sunday(T: str, p: str) -> int: - n, m = len(T), len(p) - - bc_table = generateBadCharTable(p) # 生成后移位数表 - - i = 0 - while i <= n - m: - j = 0 - if T[i: i + m] == p: - return i # 匹配完成,返回模式串 p 在文本串 T 的位置 - if i + m >= n: - return -1 - i += bc_table.get(T[i + m], m + 1) # 通过后移位数表,向右进行进行快速移动 - return -1 # 匹配失败 - -# 生成后移位数表 -# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 -def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m): # 迭代到最后一个位置 m - 1 - bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 - return bc_table - -print(sunday("abbcfdddbddcaddebc", "aaaaa")) -print(sunday("abbcfdddbddcaddebc", "bcf")) -``` - -## 4. Sunday 算法分析 - -- Sunday 算法在平均情况下的时间复杂度为 $O(n)$,但是在最坏情况下时间复杂度会退化为 $O(n * m)$。 - -## 参考资料 - -- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 -- 【博文】[字符串模式匹配算法:BM、Horspool、Sunday、KMP、KR、AC算法 - schips - 博客园](https://www.cnblogs.com/schips/p/11098041.html) -- 【博文】[字符串匹配——Sunday 算法 - Switch 的博客 - CSDN 博客](https://blog.csdn.net/q547550831/article/details/51860017) diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md b/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md deleted file mode 100644 index 966cb4b5..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md +++ /dev/null @@ -1,12 +0,0 @@ -### 单模式串匹配题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0028 | [找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0028.%20%E6%89%BE%E5%87%BA%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8C%B9%E9%85%8D%E9%A1%B9%E7%9A%84%E4%B8%8B%E6%A0%87.md) | 双指针、字符串、字符串匹配 | 中等 | -| 0459 | [重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0459.%20%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 0686 | [重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0686.%20%E9%87%8D%E5%A4%8D%E5%8F%A0%E5%8A%A0%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 字符串、字符串匹配 | 中等 | -| 1668 | [最大重复子字符串](https://leetcode.cn/problems/maximum-repeating-substring/) | | 字符串、字符串匹配 | 简单 | -| 0796 | [旋转字符串](https://leetcode.cn/problems/rotate-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0796.%20%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、字符串匹配 | 简单 | -| 1408 | [数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1408.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 数组、字符串、字符串匹配 | 简单 | -| 2156 | [查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2156.%20%E6%9F%A5%E6%89%BE%E7%BB%99%E5%AE%9A%E5%93%88%E5%B8%8C%E5%80%BC%E7%9A%84%E5%AD%90%E4%B8%B2.md) | 字符串、滑动窗口、哈希函数、滚动哈希 | 困难 | - diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/index.md b/Contents/06.String/02.String-Single-Pattern-Matching/index.md deleted file mode 100644 index bc823dec..00000000 --- a/Contents/06.String/02.String-Single-Pattern-Matching/index.md +++ /dev/null @@ -1,9 +0,0 @@ -## 本章内容 - -- [Brute Force 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md) -- [Rabin Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md) -- [KMP 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md) -- [Boyer Moore 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md) -- [Horspool 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md) -- [Sunday 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md) -- [单模式串匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) \ No newline at end of file diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md b/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md deleted file mode 100644 index 33abd869..00000000 --- a/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md +++ /dev/null @@ -1,221 +0,0 @@ -## 1. 字典树简介 - -> **字典树(Trie)**:又称为前缀树、单词查找树,是一种树形结构。顾名思义,就是一个像字典一样的树。它是字典的一种存储方式。字典中的每个单词在字典树中表现为一条从根节点出发的路径,路径相连的边上的字母连起来就形成对应的字符串。 - -例如下图就是一棵字典树,其中包含有 `a`、`abc`、`acb`、`acc`、`ach`、`b`、`chb` 这 7 个单词。 - -![](https://qcdn.itcharge.cn/images/20220210142321.png) - -从图中可以发现,这棵字典树用边来表示字母,从根节点到树上某一节点的路径就代表了一个单词。比如 1 → 2 → 6 → 10 表示的就是单词 `acc`。为了清楚地标记单词,我们可以在每个单词的结束节点位置增加一个 `end` 标记(图中红色节点),表示从根节点到这里有一个单词。 - -字典树的结构比较简单,其本质上就是一个用于字符串快速检索的多叉树,树上每个节点都包含多字符指针。将从根节点到某一节点路径上经过的字符连接起来,就是该节点对应的字符串。 - -**字典树设计的核心思想 **:利用空间换时间,利用字符串的公共前缀来降低查询时间的开销,最大限度的减少无谓的字符串比较,以达到提高效率的目的。 - -下面我们来归纳一下 **字典树的基本性质**: - -- 根节点不包含字符,除根节点外,每个节点都只包含一个字符。 -- 从根节点到某一节点,路径航经过的字符串连接起来,就是该节点对应的字符串。 -- 每个节点的所有子节点包含的字符串都不相同。 - -## 2. 字典树的基本操作 - -字典树的基本操作有 **创建**、**插入**、**查找** 和 **删除**。其中删除操作是最不常用,我们这里主要介绍字典树的创建、插入和查找。 - -### 2.1 字典树的结构 - -#### 2.1.1 字典树的节点结构 - -首先我们先来定义一下字典树的节点结构。 - -上面说到字典树是一棵多叉树,这个 **「多叉」** 的意思是一个节点可以有多个子节点。而多叉的实现方式可以使用数组实现,也可以使用哈希表实现。接下来我们来介绍一下这两种节点结构。 - -- 如果字符串所涉及的字符集合只包含小写英文字母的话,我们可以使用一个长度为 `26` 的数组来表示当前节点的多个子节点,如下面代码所示。 - -```python -class Node: # 字符节点 - def __init__(self): # 初始化字符节点 - self.children = [None for _ in range(26)] # 初始化子节点 - self.isEnd = False # isEnd 用于标记单词结束 -``` - -代码中,`self.children` 使用数组实现,表示该节点的所有子节点。`isEnd` 则用于标记单词是否结束。 - -这样,如果我们在插入单词时,需要先将单词中的字符转换为数字,再创建对应的字符节点,并将其映射到长度为 `26` 数组中。 - -- 如果所涉及的字符集合不仅包含小写字母,还包含大写字母和其他字符,我们可以使用哈希表来表示当前节点的多个子节点,如下面代码所示。 - -```python -class Node: # 字符节点 - def __init__(self): # 初始化字符节点 - self.children = dict() # 初始化子节点 - self.isEnd = False # isEnd 用于标记单词结束 -``` - -代码中,`self.children` 使用哈希表实现,表示该节点的所有子节点。`isEnd` 则用于标记单词是否结束。这样,如果我们在插入单词时,直接根据单词中的字符创建对应的字符节点,并将其插入到对应的哈希表中。 - -下面为了统一代码和编写方便,本文代码全部以哈希表的形式来表示当前节点的多个子节点。 - -#### 2.1.2 字典树的基本结构 - -定义完了字典树的字符结构,下面我们定义下字典树的基本结构。在字典树的初始化操作时,定义一个根节点。并且这个根节点不用保存字符。在后续进行插入操作、查找操作都是从字典树的根节点开始的。字典树的基本结构代码如下。 - -```python -class Trie: # 字典树 - - # 初始化字典树 - def __init__(self): # 初始化字典树 - self.root = Node() # 初始化根节点(根节点不保存字符) -``` - -### 2.2 字典树的创建和插入操作 - -字典树的创建指的是将字符串数组中的所有字符串都插⼊字典树中。而插⼊操作指的是将⼀个字符串插⼊字典树中。 - -#### 2.2.1 字典树的插入操作 - -在讲解字典树的创建之前,我们先来看一下如何在字典树中插入一个单词。具体步骤如下: - -- 依次遍历单词中的字符 `ch`,并从字典树的根节点的子节点位置开始进行插入操作(根节点不包含字符)。 -- 如果当前节点的子节点中,不存在键为 `ch` 的节点,则建立一个节点,并将其保存到当前节点的子节点中,即 `cur.children[ch] = Node()`,然后令当前节点指向新建立的节点,然后继续处理下一个字符。 -- 如果当前节点的子节点中,存在键为 `ch` 的节点,则直接令当前节点指向键为 `ch` 的节点,继续处理下一个字符。 -- 在单词处理完成时,将当前节点标记为单词结束。 - -```python -# 向字典树中插入一个单词 -def insert(self, word: str) -> None: - cur = self.root - for ch in word: # 遍历单词中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - cur.children[ch] = Node() # 建立一个节点,并将其保存到当前节点的子节点 - cur = cur.children[ch] # 令当前节点指向新建立的节点,继续处理下一个字符 - cur.isEnd = True # 单词处理完成时,将当前节点标记为单词结束 -``` - -#### 2.2.2 字典树的创建操作 - -字典树的创建比较简单,具体步骤如下: - -- 首先初始化一个字典树,即 `trie = Trie()`。 -- 然后依次遍历字符串中的所有单词,将其一一插入到字典树中。 - -```python -trie = Trie() -for word in words: - trie.insert(word) -``` - -### 2.3 字典树的查找操作 - -#### 2.3.1 字典树的查找单词操作 - -在字典树中查找某个单词是否存在,其实和字典树的插入操作差不多。具体操作如下: - -- 依次遍历单词中的字符,并从字典树的根节点位置开始进行查找操作。 -- 如果当前节点的子节点中,不存在键为 `ch` 的节点,则说明不存在该单词,直接返回 `False`。 -- 如果当前节点的子节点中,存在键为 `ch` 的节点,则令当前节点指向新建立的节点,然后继续查找下一个字符。 -- 在单词处理完成时,判断当前节点是否有单词结束标记,如果有,则说明字典树中存在该单词,返回 `True`。否则,则说明字典树中不存在该单词,返回 `False`。 - -```python -# 查找字典树中是否存在一个单词 -def search(self, word: str) -> bool: - cur = self.root - for ch in word: # 遍历单词中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - return False # 直接返回 False - cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 - - return cur is not None and cur.isEnd # 判断当前节点是否为空,并且是否有单词结束标记 -``` - -#### 2.3.2 字典树的查找前缀操作 - -在字典树中查找某个前缀是否存在,和字典树的查找单词操作一样,不同点在于最后不需要判断是否有单词结束标记。 - -```python -# 查找字典树中是否存在一个前缀 -def startsWith(self, prefix: str) -> bool: - cur = self.root - for ch in prefix: # 遍历前缀中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - return False # 直接返回 False - cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 - return cur is not None # 判断当前节点是否为空,不为空则查找成功 -``` - -## 3. 字典树的实现代码 - -```python -class Node: # 字符节点 - def __init__(self): # 初始化字符节点 - self.children = dict() # 初始化子节点 - self.isEnd = False # isEnd 用于标记单词结束 - - -class Trie: # 字典树 - - # 初始化字典树 - def __init__(self): # 初始化字典树 - self.root = Node() # 初始化根节点(根节点不保存字符) - - # 向字典树中插入一个单词 - def insert(self, word: str) -> None: - cur = self.root - for ch in word: # 遍历单词中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - cur.children[ch] = Node() # 建立一个节点,并将其保存到当前节点的子节点 - cur = cur.children[ch] # 令当前节点指向新建立的节点,继续处理下一个字符 - cur.isEnd = True # 单词处理完成时,将当前节点标记为单词结束 - - # 查找字典树中是否存在一个单词 - def search(self, word: str) -> bool: - cur = self.root - for ch in word: # 遍历单词中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - return False # 直接返回 False - cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 - - return cur is not None and cur.isEnd # 判断当前节点是否为空,并且是否有单词结束标记 - - # 查找字典树中是否存在一个前缀 - def startsWith(self, prefix: str) -> bool: - cur = self.root - for ch in prefix: # 遍历前缀中的字符 - if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 - return False # 直接返回 False - cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 - return cur is not None # 判断当前节点是否为空,不为空则查找成功 -``` - -## 4. 字典树的算法分析 - -假设单词的长度为 `n`,前缀的长度为 `m`,字符集合的维度为 `d`,则: - -- **插入一个单词**:时间复杂度为 $O(n)$;如果使用数组,则空间复杂度为 $O(d^n)$,如果使用哈希表实现,则空间复杂度为 $O(n)$。 -- **查找一个单词**:时间复杂度为 $O(n)$;空间复杂度为 $O(1)$。 -- **查找一个前缀**:时间复杂度为 $O(m)$;空间复杂度为 $O(1)$。 - -## 5. 字典树的应用 - -字典树一个典型的应用场景就是:在搜索引擎中输入部分内容之后,搜索引擎就会自动弹出一些关联的相关搜索内容。我们可以从中直接选择自己想要搜索的内容,而不用将所有内容都输入进去。这个功能从一定程度上节省了我们的搜索时间。 - -例如下图,当我们输入「字典树」后,底下会出现一些以「字典树」为前缀的相关搜索内容。 - -![](https://qcdn.itcharge.cn/images/20220210134829.png) - -这个功能实现的基本原理就是字典树。当然,像 Google、必应、百度这样的搜索引擎,在这个功能能的背后肯定做了大量的改进和优化,但它的底层最基本的原理就是「字典树」这种数据结构。 - -除此之外,我们可以把字典树的应用分为以下几种: - -- **字符串检索**:事先将已知的⼀些字符串(字典)的有关信息存储到字典树⾥, 查找⼀些字符串是否出现过、出现的频率。 -- **前缀统计**:统计⼀个串所有前缀单词的个数,只需统计从根节点到叶子节点路径上单词出现的个数,也可以判断⼀个单词是否为另⼀个单词的前缀。 -- **最长公共前缀问题**:利用字典树求解多个字符串的最长公共前缀问题。将⼤量字符串都存储到⼀棵字典树上时, 可以快速得到某些字符串的公共前缀。对所有字符串都建⽴字典树,两个串的最长公共前缀的长度就是它们所在节点最近公共祖先的长度,于是转变为最近公共祖先问题。 -- **字符串排序**:利⽤字典树进⾏串排序。例如,给定多个互不相同的仅由⼀个单词构成的英⽂名,将它们按字典序从⼩到⼤输出。采⽤数组⽅式创建字典树,字典树中每个节点的所有⼦节点都是按照其字母⼤⼩排序的。然后对字典树进⾏先序遍历,输出的相应字符串就是按字典序排序的结果。 - -## 参考资料 - -- 【书籍】算法训练营 陈小玉 著 -- 【书籍】ACM-ICPC 程序设计系列 算法设计与实现 陈宇 吴昊 主编 -- 【博文】[Trie 树 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/72414) -- 【博文】[一文搞懂字典树](https://segmentfault.com/a/1190000040801084) -- 【博文】[字典树 (Trie) - OI Wiki](https://oi-wiki.org/string/trie/) diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md b/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md deleted file mode 100644 index 9cecd882..00000000 --- a/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md +++ /dev/null @@ -1,17 +0,0 @@ -### 字典树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0208 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0208.%20%E5%AE%9E%E7%8E%B0%20Trie%20%28%E5%89%8D%E7%BC%80%E6%A0%91%29.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0677 | [键值映射](https://leetcode.cn/problems/map-sum-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0677.%20%E9%94%AE%E5%80%BC%E6%98%A0%E5%B0%84.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0648 | [单词替换](https://leetcode.cn/problems/replace-words/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0648.%20%E5%8D%95%E8%AF%8D%E6%9B%BF%E6%8D%A2.md) | 字典树、数组、哈希表、字符串 | 中等 | -| 0642 | [设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0642.%20%E8%AE%BE%E8%AE%A1%E6%90%9C%E7%B4%A2%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E7%B3%BB%E7%BB%9F.md) | 设计、字典树、字符串、数据流 | 困难 | -| 0211 | [添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0211.%20%E6%B7%BB%E5%8A%A0%E4%B8%8E%E6%90%9C%E7%B4%A2%E5%8D%95%E8%AF%8D%20-%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1.md) | 深度优先搜索、设计、字典树、字符串 | 中等 | -| 0421 | [数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0421.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BC%82%E6%88%96%E5%80%BC.md) | 位运算、字典树、数组、哈希表 | 中等 | -| 0212 | [单词搜索 II](https://leetcode.cn/problems/word-search-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0212.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%20II.md) | 字典树、数组、字符串、回溯、矩阵 | 困难 | -| 0425 | [单词方块](https://leetcode.cn/problems/word-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0425.%20%E5%8D%95%E8%AF%8D%E6%96%B9%E5%9D%97.md) | 字典树、数组、字符串、回溯 | 困难 | -| 0336 | [回文对](https://leetcode.cn/problems/palindrome-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0336.%20%E5%9B%9E%E6%96%87%E5%AF%B9.md) | 字典树、数组、哈希表、字符串 | 困难 | -| 1023 | [驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1023.%20%E9%A9%BC%E5%B3%B0%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 字典树、双指针、字符串、字符串匹配 | 中等 | -| 0676 | [实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0676.%20%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E9%AD%94%E6%B3%95%E5%AD%97%E5%85%B8.md) | 设计、字典树、哈希表、字符串 | 中等 | -| 0440 | [字典序的第K小数字](https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/) | | 字典树 | 困难 | - diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md b/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md deleted file mode 100644 index 73663069..00000000 --- a/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md +++ /dev/null @@ -1,27 +0,0 @@ -## 1. AC 自动机简介 - -> **AC 自动机(Aho-Corasick Automaton)**:该算法在 1975 年产生于贝尔实验室,是最著名的多模式匹配算法之一。简单来说,AC 自动机是以 **字典树(Trie)** 的结构为基础,结合 **KMP 算法思想** 建立的。 - -AC 自动机的构造有 3 个步骤: - -1. 构造一棵字典树(Trie),作为 AC 自动机的搜索数据结构。 -2. 利用 KMP 算法思想,构造失配指针。使得当前字符失配时可以通过失配指针跳转到具有最长公共前后缀的字符位置上继续匹配。 -3. 扫描文本串进行匹配。 - -## 2. AC 自动机原理 - -接下来我们以一个例子来说明一下 AC 自动机的原理。 - -> 描述:给定 5 个单词,分别是 `say`、`she`、`shr`、`he`、`her`,再给定一个文本串 `yasherhs`。 -> -> 要求:计算出有多少个单词在文本串中出现过。 - -### 2.1 构造一棵字典树(Trie) - -首先我们需要建立一棵字典树。 - -### 2.2 构造失配指针 - -### 2.3 扫描文本串 - -## 3. AC 自动机的应用 \ No newline at end of file diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md b/Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md b/Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/index.md b/Contents/06.String/03.String-Multi-Pattern-Matching/index.md deleted file mode 100644 index 60064500..00000000 --- a/Contents/06.String/03.String-Multi-Pattern-Matching/index.md +++ /dev/null @@ -1,8 +0,0 @@ -## 本章内容 - -- [字典树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md) -- [字典树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) -- [AC 自动机知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md) -- [AC 自动机题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md) -- [后缀数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md) -- [后缀数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md) \ No newline at end of file diff --git a/Contents/06.String/index.md b/Contents/06.String/index.md deleted file mode 100644 index d931547d..00000000 --- a/Contents/06.String/index.md +++ /dev/null @@ -1,25 +0,0 @@ -## 本章内容 - -### 字符串基础知识 - -- [字符串基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/01.String-Basic.md) -- [字符串经典题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/01.String-Basic/02.String-Basic-List.md) - -### 单模式串匹配 - -- [Brute Force 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md) -- [Rabin Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md) -- [KMP 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md) -- [Boyer Moore 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md) -- [Horspool 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md) -- [Sunday 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md) -- [单模式串匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) - -### 多模式串匹配 - -- [字典树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md) -- [字典树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) -- [AC 自动机知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md) -- [AC 自动机题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md) -- [后缀数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md) -- [后缀数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md) \ No newline at end of file diff --git a/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md b/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md deleted file mode 100644 index df1710de..00000000 --- a/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md +++ /dev/null @@ -1,212 +0,0 @@ -## 1. 树简介 - -### 1.1 树的定义 - -> **树(Tree)**:由 $n \ge 0$ 个节点与节点之间的关系组成的有限集合。当 $n = 0$ 时称为空树,当 $n > 0$ 时称为非空树。 - -之所以把这种数据结构称为「树」是因为这种数据结构看起来就像是一棵倒挂的树,也就是说数据结构中的「树」是根朝上,而叶朝下的。如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220221091603.png) - -「树」具有以下的特点: - -- 有且仅有一个节点没有前驱节点,该节点被称为树的 **「根节点(Root)」** 。 -- 除了根节点以之,每个节点有且仅有一个直接前驱节点。 -- 包括根节点在内,每个节点可以有多个后继节点。 -- 当 $n > 1$ 时,除了根节点之外的其他节点,可分为 $m(m > 0)$ 个互不相交的有限集合 $T_1, T_2, ..., T_m$,其中每一个集合本身又是一棵树,并且被称为根的 **「子树(SubTree)」**。 - -如下图所示,红色节点 $A$ 是根节点,除了根节点之外,还有 `3` 棵互不相交的子树 $T_1(B, E, H, I, G)$、$T_2(C)$、$T_3(D, F, G, K)$。 - -![](https://qcdn.itcharge.cn/images/20220218104556.png) - - - -### 1.2 树的相关术语 - -下面我们来介绍一下树结构中的一些基本术语。 - -#### 1.2.1 节点分类 - -**「树的节点」** 由一个数据元素和若干个指向其子树的树的分支组成。而节点所含有的子树个数称为 **「节点的度」**。度为 $0$ 的节点称为 **「叶子节点」** 或者 **「终端节点」**,度不为 $0$ 的节点称为 **「分支节点」** 或者 **「非终端节点」**。树中各节点的最大度数称为 **「树的度」**。 - -![](https://qcdn.itcharge.cn/images/20220218134918.png) - -- **树的节点**:由一个数据元素和若干个指向其子树的树的分支组成。 -- **节点的度**:一个节点所含有的子树个数。 -- **叶子节点(终端节点)**:度为 $0$ 的节点。例如图中叶子节点为 `C`、`H`、`I`、`G`、`F`、`K`。 -- **分支节点(非终端节点)**:度不为 $0$ 的节点。例如图中分支节点为 `A`、`B`、`D`、`E`、`G`。 -- **树的度**:树中节点的最大度数。例如图中树的度为 $3$。 - -#### 1.2.2 节点间关系 - -一个节点的子树的根节点称为该节点的 **「孩子节点」**,相应的,该节点称为孩子的 **「父亲节点」**。同一个父亲节点的孩子节点之间互称为 **「兄弟节点」**。 - -![](https://qcdn.itcharge.cn/images/20220218142604.png) - -- **孩子节点(子节点)**:一个节点含有的子树的根节点称为该节点的子节点。例如图中 `B` 是 `A` 的孩子节点。 -- **父亲节点(父节点)**:如果一个节点含有子节点,则这个节点称为其子节点的父节点。例如图中 `B` 是 `E` 的父亲节点。 -- **兄弟节点**:具有相同父节点的节点互称为兄弟节点。例如图中 `F`、`G` 互为兄弟节点。 - -#### 1.2.3 树的其他术语 - -**「节点的层次」** 是从根节点开始定义,将根节点作为第 1 层,根的孩子节点作为第 2 层,以此类推,如果某个节点在第 `i` 层,则其孩子节点在第 `i + 1` 层。而父亲节点在同一层的节点互为 **「堂兄弟节点」**。树中所有节点最大的层数称为 **「树的深度」** 或 **「树的高度」**。树中,两个节点之间所经过节点序列称为 **「路径」**,两个节点之间路径上经过的边数称为 **「路径长度」**。 - -![](https://qcdn.itcharge.cn/images/20220218144813.png) - -- **节点的层次**:从根节点开始定义,根为第 $1$ 层,根的子节点为第 $2$ 层,以此类推。 -- **树的深度(高度)**:所有节点中最大的层数。例如图中树的深度为 $4$。 -- **堂兄弟节点**:父节点在同一层的节点互为堂兄弟。例如图中 `G`、`K` 互为堂兄弟节点。 -- **路径**:树中两个节点之间所经过的节点序列。例如图中 `E` 到 `G` 的路径为 `E - B - A - D - G`。 -- **路径长度**:两个节点之间路径上经过的边数。例如图中 `E` 到 `G` 的路径长度为 $4$。 -- **节点的祖先**:从该节点到根节点所经过的所有节点,被称为该节点的祖先。例如图中 `H` 的祖先为 `E`、`B`、`A`。 -- **节点的子孙**:节点的子树中所有节点被称为该节点的子孙。例如图中 `D` 的子孙为 `F`、`G`、`K`。 - -### 1.3 树的分类 - -根据节点的子树是否可以互换位置,我们可以将树分为两种类型:**「有序树」** 和 **「无序树」**。 - -如果将树中节点的各个子树看做是从左到右是依次有序的(即不能互换),则称该树为 **「有序树」**。反之,如果节点的各个子树可以互换位置,则成该树为 **「无序树」**。 - -- **有序树**:节点的各个⼦树从左⾄右有序, 不能互换位置。 -- **无序树**:节点的各个⼦树可互换位置。 - -## 2. 二叉树简介 - -### 2.1 二叉树的定义 - -> **二叉树(Binary Tree)**:树中各个节点的度不大于 `2` 个的有序树,称为二叉树。通常树中的分支节点被称为 **「左子树」** 或 **「右子树」**。二叉树的分支具有左右次序,不能随意互换位置。 - -下图就是一棵二叉树。 - -![](https://qcdn.itcharge.cn/images/20220221094909.png) - -二叉树也可以使用递归方式来定义,即二叉树满足以下两个要求之一: - -- **空树**:二叉树是一棵空树。 -- **非空树**:二叉树是由一个根节点和两棵互不相交的子树 $T_1$、$T_2$,分别称为根节点的左子树、右子树组成的非空树;并且 $T_1$、$T_2$ 本身都是二叉树。 - -⼆叉树是种特殊的树,它最多有两个⼦树,分别为左⼦树和右⼦树,并且两个子树是有序的,不可以互换。也就是说,在⼆叉树中不存在度⼤于 $2$ 的节点。 - -二叉树在逻辑上可以分为 $5$ 种基本形态,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220218164839.png) - -### 2.2 特殊的二叉树 - -下面我们来介绍一些特殊的二叉树。 - -#### 2.2.1 满二叉树 - -> **满二叉树(Full Binary Tree)**:如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,则称该二叉树为满二叉树。 - -满二叉树满足以下特点: - -- 叶子节点只出现在最下面一层。 -- 非叶子节点的度一定为 $2$。 -- 在同等深度的二叉树中,满二叉树的节点个数最多,叶子节点个数最多。 - -如果我们对满二叉树的节点进行编号,根结点编号为 $1$,然后按照层次依次向下,每一层从左至右的顺序进行编号。则深度为 $k$ 的满二叉树最后一个节点的编号为 $2^k - 1$。 - -我们可以来看几个例子。 - -![](https://qcdn.itcharge.cn/images/20220218173007.png) - -#### 2.2.2 完全二叉树 - -> **完全二叉树(Complete Binary Tree)**:如果叶子节点只能出现在最下面两层,并且最下层的叶子节点都依次排列在该层最左边的位置上,具有这种特点的二叉树称为完全二叉树。 - -完全二叉树满足以下特点: - -- 叶子节点只能出现在最下面两层。 -- 最下层的叶子节点一定集中在该层最左边的位置上。 -- 倒数第二层如果有叶子节点,则该层的叶子节点一定集中在右边的位置上。 -- 如果节点的度为 `1`,则该节点只偶遇左孩子节点,即不存在只有右子树的情况。 -- 同等节点数的二叉树中,完全二叉树的深度最小。 - -完全二叉树也可以使用类似满二叉树的节点编号的方式来定义。即从根节点编号为 $1$ 开始,按照层次从上至下,每一层从左至右进行编号。对于深度为 $i$ 且有 $n$ 个节点的二叉树,当且仅当每一个节点都与深度为 $k$ 的满二叉树中编号从 $1$ 至 $n$ 的节点意义对应时,该二叉树为完全二叉树。 - -我们可以来看几个例子。 - -![](https://qcdn.itcharge.cn/images/20220218174000.png) - -#### 2.2.3 二叉搜索树 - -> **二叉搜索树(Binary Search Tree)**:也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树: -> -> - 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。 -> - 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。 -> - 任意节点的左子树、右子树均为二叉搜索树。 - -如图所示,这 $3$ 棵树都是二叉搜索树。 - -![](https://qcdn.itcharge.cn/images/20220218175944.png) - -#### 2.2.4 平衡二叉搜索树 - -> **平衡二叉搜索树(Balanced Binary Tree)**:一种结构平衡的二叉搜索树。即叶节点高度差的绝对值不超过 $1$,并且左右两个子树都是一棵平衡二叉搜索树。平衡二叉树可以在 $O(logn)$ 内完成插入、查找和删除操作。最早被发明的平衡二叉搜索树为 **「AVL 树(Adelson-Velsky and Landis Tree))」**。 -> -> AVL 树满足以下性质: -> -> - 空二叉树是一棵 AVL 树。 -> - 如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且 $|h(ls) - h(rs)| \le 1$,$h(ls)$ 是左子树的高度,$h(rs)$ 是右子树的高度。 -> - AVL 树的高度为 $O(log n)$。 - -如图所示,前 $2$ 棵树是平衡二叉搜索树,最后一棵树不是平衡二叉搜索树,因为这棵树的左右子树的高度差的绝对值超过了 $1$。 - -![](https://qcdn.itcharge.cn/images/20220221103552.png) - -### 2.3 二叉树的存储结构 - -二叉树的存储结构分为两种:「顺序存储结构」和「链式存储结构」,下面进行一一讲解。 - -#### 2.3.1 二叉树的顺序存储结构 - -其实,堆排序、优先队列中的二叉堆结构,采用的就是二叉树的顺序存储结构。 - -二叉树的顺序存储结构使用一维数组来存储二叉树中的节点,节点存储位置则采用完全二叉树的节点层次编号,按照层次从上至下,每一层从左至右的顺序依次存放二叉树的数据元素。在进行顺序存储时,如果对应的二叉树节点不存在,则设置为「空节点」。 - -下图为二叉树的顺序存储结构。 - -![](https://qcdn.itcharge.cn/images/20220221144552.png) - -从图中我们也可以看出节点之间的逻辑关系。 - -- 如果某二叉树节点(非叶子节点)的下标为 $i$,那么其左孩子节点下标为 $2 * i + 1$,右孩子节点下标为 $2 * i + 2$。 -- 如果某二叉树节点(非根结点)的下标为 $i$,那么其根节点下标为 $(i - 1) // 2$。$//$ 表示整除。 - -对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构比较合适,它能充分利用存储空间;而对于一般二叉树,如果需要设置很多的「空节点」,则采用顺序存储结构就会浪费很多存储空间。并且,由于顺序存储结构固有的一些缺陷,会使得二叉树的插入、删除等操作不方便,效率也比较低。对于二叉树来说,当树的形态和大小经常发生动态变化时,更适合采用链式存储结构。 - -#### 2.3.2 二叉树的链式存储结构 - -二叉树采用链式存储结构时,每个链节点包含一个用于数据域 `val`,存储节点信息;还包含两个指针域 `left` 和 `right`,分别指向左右两个孩子节点,当左孩子或者右孩子不存在时,相应指针域值为空。二叉链节点结构如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220221151412.png) - -二叉链节点结构的对应代码为: - -```python -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right -``` - -下面我们将值为 `1、2、3、4、5、6、7` 的二叉树使用链式存储结构进行存储,即为下图所示。 - -![](https://qcdn.itcharge.cn/images/20220221153539.png) - -二叉树的链表存储结构具有灵活、方便的特点。节点的最大数目只受系统最大可存储空间的限制。一般情况下,二叉树的链表存储结构比顺序存储结构更省空间(用于存储指针域的空间开销只是二叉树中节点数的线性函数),而且对于二叉树实施相关操作也很方便,因此,一般我们使用链式存储结构来存储二叉树。 - -## 参考链接 - -- 【书籍】数据结构教程 第 3 版 - 唐发根 著 -- 【书籍】大话数据结构 程杰 著 -- 【书籍】算法训练营 陈小玉 著 -- 【博文】[二叉树理论基础 - 代码随想录](https://programmercarl.com/二叉树理论基础.html) -- 【博文】[二叉树基础 - 袁厨的算法小屋](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/二叉树/二叉树基础.md) - - - - - diff --git a/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md b/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md deleted file mode 100644 index 2917f01d..00000000 --- a/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md +++ /dev/null @@ -1,317 +0,0 @@ -## 1. 二叉树的遍历简介 - -> **二叉树的遍历**:指的是从根节点出发,按照某种次序依次访问二叉树中所有节点,使得每个节点被访问一次且仅被访问一次。 - -在二叉树的一些实际问题中,经常需要按照一定顺序对二叉树中每个节点逐个进行访问一次,用以查找具有某一特点的节点或者全部节点,然后对这些满足要求的节点进行处理。这里所说的「访问」就是指对该节点进行某种操作,例如:依次输出节点的数据信息、统计满足某条件的节点总数等等。 - -回顾二叉树的递归定义可以知道,二叉树是由根节点和左子树、右子树构成的。因此,如果能依次遍历这 `3` 个部分,就可以遍历整个二叉树。 - -如果利用深度优先搜索的方式,并且根据访问顺序次序的不同,我们可以分为 `6` 种遍历方式,而如果限制先左子树后右子树的遍历顺序,则总共有 `3` 种遍历方式:分别为 **「二叉树的前序遍历」**、**「二叉树的中序遍历」** 和 **「二叉树的后续遍历」**。 - -而如果使用广度优先搜索的方式,则可以按照层序方式(按照层次从上至下,每一层从左至右)对二叉树进行遍历,这种方式叫做 **「二叉树的层序遍历」**。 - -## 2. 二叉树的前序遍历 - -> 二叉树的前序遍历规则为: -> -> - 如果二叉树为空,则返回。 -> - 如果二叉树不为空,则: -> 1. 访问根节点。 -> 2. 以前序遍历的方式遍历根节点的左子树。 -> 3. 以前序遍历的方式遍历根节点的右子树。 - -从二叉树的前序遍历规则可以看出:前序遍历过程是一个递归过程。在遍历任何一棵子树时仍然是按照先访问根节点,然后遍历子树根节点的左子树,最后再遍历子树根节点的右子树的顺序进行遍历。 - -如下图所示,该二叉树的前序遍历顺序为:`A - B - D - H - I - E - C - F - J - G - K`。 - -![](https://qcdn.itcharge.cn/images/20220222165249.png) - -### 2.1 二叉树的前序遍历递归实现 - -二叉树的前序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先访问根节点。 -3. 然后递归遍历左子树。 -4. 最后递归遍历右子树。 - -二叉树的前序遍历递归实现代码如下: - -```python -class Solution: - def preorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - - def preorder(root): - if not root: - return - res.append(root.val) - preorder(root.left) - preorder(root.right) - - preorder(root) - return res -``` - -### 2.2 二叉树的前序遍历显式栈实现 - -二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 `stack` 来模拟递归的过程。 - -前序遍历的顺序为:根节点 - 左子树 - 右子树,而根据栈的「先入后出」特点,所以入栈的顺序应该为:先放入右子树,再放入左子树。这样可以保证最终遍历顺序为前序遍历顺序。 - -二叉树的前序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个栈,将根节点入栈。 -3. 当栈不为空时: - 1. 弹出栈顶元素 `node`,并访问该元素。 - 2. 如果 `node` 的右子树不为空,则将 `node` 的右子树入栈。 - 3. 如果 `node` 的左子树不为空,则将 `node` 的左子树入栈。 - - 二叉树的前序遍历显式栈实现代码如下: - -```python -class Solution: - def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - if not root: # 二叉树为空直接返回 - return [] - - res = [] - stack = [root] - - while stack: # 栈不为空 - node = stack.pop() # 弹出根节点 - res.append(node.val) # 访问根节点 - if node.right: - stack.append(node.right) # 右子树入栈 - if node.left: - stack.append(node.left) # 左子树入栈 - - return res -``` - -## 3. 二叉树的中序遍历 - -> 二叉树的中序遍历规则为: -> -> - 如果二叉树为空,则返回。 -> - 如果二叉树不为空,则: -> 1. 以中序遍历的方式遍历根节点的左子树。 -> 2. 访问根节点。 -> 3. 以中序遍历的方式遍历根节点的右子树。 - -从二叉树的中序遍历规则可以看出:中序遍历过程也是一个递归过程。在遍历任何一棵子树时仍然是按照先遍历子树根节点的左子树,然后访问根节点,最后再遍历子树根节点的右子树的顺序进行遍历。 - -如下图所示,该二叉树的中序遍历顺序为:`H - D - I - B - E - A - F - J - C - K - G`。 - -![](https://qcdn.itcharge.cn/images/20220222165231.png) - -### 3.1 二叉树的中序遍历递归实现 - -二叉树的中序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先递归遍历左子树。 -3. 然后访问根节点。 -4. 最后递归遍历右子树。 - -二叉树的中序遍历递归实现代码如下: - -```python -class Solution: - def inorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - def inorder(root): - if not root: - return - inorder(root.left) - res.append(root.val) - inorder(root.right) - - inorder(root) - return res -``` - -### 3.2 二叉树的中序遍历显式栈实现 - -我们可以使用一个显式栈 `stack` 来模拟二叉树的中序遍历递归的过程。 - -与前序遍历不同,访问根节点要放在左子树遍历完之后。因此我们需要保证:**在左子树访问之前,当前节点不能提前出栈**。 - -我们应该从根节点开始,循环遍历左子树,不断将当前子树的根节点放入栈中,直到当前节点无左子树时,从栈中弹出该节点并进行处理。 - -然后再访问该元素的右子树,并进行上述循环遍历左子树的操作。这样可以保证最终遍历顺序为中序遍历顺序。 - -二叉树的中序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个空栈。 -3. 当根节点或者栈不为空时: - 1. 如果当前节点不为空,则循环遍历左子树,并不断将当前子树的根节点入栈。 - 1. 如果当前节点为空,说明当前节点无左子树,则弹出栈顶元素 `node`,并访问该元素,然后尝试访问该节点的右子树。 - - 二叉树的中序遍历显式栈实现代码如下: - -```python -class Solution: - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - if not root: # 二叉树为空直接返回 - return [] - - res = [] - stack = [] - - while root or stack: # 根节点或栈不为空 - while root: - stack.append(root) # 将当前树的根节点入栈 - root = root.left # 找到最左侧节点 - - node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 - res.append(node.val) # 访问该节点 - root = node.right # 尝试访问该节点的右子树 - return res -``` - -## 4. 二叉树的后序遍历 - -> 二叉树的后序遍历规则为: -> -> - 如果二叉树为空,则返回。 -> - 如果二叉树不为空,则: -> 1. 以后序遍历的方式遍历根节点的左子树。 -> 2. 以后序遍历的方式遍历根节点的右子树。 -> 3. 访问根节点。 - -从二叉树的后序遍历规则可以看出:后序遍历过程也是一个递归过程。在遍历任何一棵子树时仍然是按照先遍历子树根节点的左子树,然后遍历子树根节点的右子树,最后再访问根节点的顺序进行遍历。 - -如下图所示,该二叉树的后序遍历顺序为:`H - I - D - E - B - J - F - K - G - C - A`。 - -![](https://qcdn.itcharge.cn/images/20220222165218.png) - -### 4.1 二叉树的后序遍历递归实现 - -二叉树的后序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先递归遍历左子树。 -3. 然后递归遍历右子树。 -4. 最后访问根节点。 - -二叉树的后序遍历递归实现代码如下: - -```python -class Solution: - def postorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - def postorder(root): - if not root: - return - postorder(root.left) - postorder(root.right) - res.append(root.val) - - postorder(root) - return res -``` - -### 4.2 二叉树的后序遍历显式栈实现 - -我们可以使用一个显式栈 `stack` 来模拟二叉树的后序遍历递归的过程。 - -与前序、中序遍历不同,在后序遍历中,根节点的访问要放在左右子树访问之后。因此,我们要保证:**在左右孩子节点访问结束之前,当前节点不能提前出栈**。 - -我们应该从根节点开始,先将根节点放入栈中,然后依次遍历左子树,不断将当前子树的根节点放入栈中,直到遍历到左子树最左侧的那个节点,从栈中弹出该元素,并判断该元素的右子树是否已经访问完毕,如果访问完毕,则访问该元素。如果未访问完毕,则访问该元素的右子树。 - -二叉树的后序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个空栈,使用 `prev` 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕。 -3. 当根节点或者栈不为空时,从当前节点开始: - 1. 如果当前节点有左子树,则不断遍历左子树,并将当前根节点压入栈中。 - 2. 如果当前节点无左子树,则弹出栈顶元素 `node`。 - 2. 如果栈顶元素 `node` 无右子树(即 `not node.right`)或者右子树已经访问完毕(即 `node.right == prev`),则访问该元素,然后记录前一节点,并将当前节点标记为空节点。 - 2. 如果栈顶元素有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子树。 - -```python -class Solution: - def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - res = [] - stack = [] - prev = None # 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕 - - while root or stack: # 根节点或栈不为空 - while root: - stack.append(root) # 将当前树的根节点入栈 - root = root.left # 继续访问左子树,找到最左侧节点 - - node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 - - # 如果当前节点无右子树或者右子树访问完毕 - if not node.right or node.right == prev: - res.append(node.val)# 访问该节点 - prev = node # 记录前一节点 - root = None # 将当前根节点标记为空 - else: - stack.append(node) # 右子树尚未访问完毕,将当前节点重新压回栈中 - root = node.right # 继续访问右子树 - - return res -``` - -## 5. 二叉树的层序遍历 - -> 二叉树的层序遍历规则为: -> -> - 如果二叉树为空,则返回。 -> - 如果二叉树不为空,则: -> 1. 先依次访问二叉树第 `1` 层的节点。 -> 2. 然后依次访问二叉树第 `2` 层的节点。 -> 3. …… -> 4. 依次下去,最后依次访问二叉树最下面一层的节点。 - -从二叉树的层序遍历规则可以看出:遍历过程是一个广度优先搜索过程。在遍历的时候是按照第 `1` 层、第 `2` 层、…… 最后一层依次遍历的,而同一层节点则是按照从左至右的顺序依次访问的。 - -如下图所示,该二叉树的后序遍历顺序为:`A - B - C - D - E - F - G - H - I - J - K`。 - -![](https://qcdn.itcharge.cn/images/20220222165158.png) - -二叉树的层序遍历是通过队列来实现的。具体步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 令根节点入队。 -3. 当队列不为空时,求出当前队列长度 $s_i$。 -4. 依次从队列中取出这 $s_i$ 个元素,并对这 $s_i$ 个元素依次进行访问。然后将其左右孩子节点入队,然后继续遍历下一层节点。 -5. 当队列为空时,结束遍历。 - -二叉树的层序遍历代码实现如下: - -```python -class Solution: - def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - while queue: - level = [] - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - level.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(level) - return order -``` - -## 参考资料 - -1. 【书籍】数据结构教程 第 3 版 - 唐发根 著 -2. 【书籍】大话数据结构 程杰 著 -3. 【书籍】算法训练营 陈小玉 著 -3. 【题解】[LeetCode 二叉树前序遍历(递归法 + 非递归法)- 二叉树的前序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-preorder-traversal/solution/acm-xuan-shou-tu-jie-leetcode-er-cha-shu-pqpz/) -3. 【题解】[二叉树遍历通解(递归和迭代解法)- 完全模拟递归 - 二叉树的后序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-postorder-traversal/solution/bian-li-tong-jie-by-long_wotu/) -3. 【题解】[迭代后序遍历 - 二叉树的后序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-postorder-traversal/solution/die-dai-hou-xu-bian-li-by-wang-mo-ji-98ob/) diff --git a/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md b/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md deleted file mode 100644 index a1bbc360..00000000 --- a/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md +++ /dev/null @@ -1,27 +0,0 @@ -### 二叉树的遍历题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0102 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0102.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0103 | [二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0103.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0107 | [二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0107.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%20II.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0101 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0101.%20%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0112 | [路径总和](https://leetcode.cn/problems/path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0112.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0113 | [路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0113.%20%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C%20II.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | -| 0236 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0236.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0116 | [填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0116.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0117 | [填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0117.%20%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88%20II.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | -| 0297 | [二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0297.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | -| 0114 | [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | | 栈、树、深度优先搜索、链表、二叉树 | 中等 | - diff --git a/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md b/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md deleted file mode 100644 index 36f8b1df..00000000 --- a/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md +++ /dev/null @@ -1,167 +0,0 @@ -## 1. 二叉树的还原简介 - -> **二叉树的还原**:指的是通过二叉树的遍历序列,还原出对应的二叉树。 - -从二叉树的遍历过程可以看出,给定一棵非空二叉树,它的前序、中序、后续遍历所得到的遍历序列都是唯一的。那么反过来,如果已知节点的某种遍历序列,能否确定这棵二叉树呢?并且确定的二叉树是否是唯一的呢? - -我们先来回顾一下二叉树的前序遍历、中序遍历、后序遍历规则。 - -- 非空二叉树的前序遍历规则: - 1. 访问根节点。 - 2. 以前序遍历的方式遍历根节点的左子树。 - 3. 以前序遍历的方式遍历根节点的右子树。 -- 非空二叉树的中序遍历规则: - 1. 以中序遍历的方式遍历根节点的左子树。 - 2. 访问根节点。 - 3. 以中序遍历的方式遍历根节点的右子树。 -- 非空二叉树的后序遍历规则: - 1. 以后序遍历的方式遍历根节点的左子树。 - 2. 以后序遍历的方式遍历根节点的右子树。 - 3. 访问根节点。 - -先来看二叉树的前序遍历,前序遍历过程中首先访问的是根节点,所以通过前序遍历序列,我们可以确定序列的第 $1$ 个节点肯定是根节点。但是从第 $2$ 个节点开始就不确定它是根节点的左子树还是根节点的右子树了。所以单凭前序遍历序列是无法恢复一棵二叉树的。 - -再来看二叉树的后序遍历,后序遍历也是只能确定序列的最后一个节点为根节点,而无法确定其他节点在二叉树中的位置。所以单凭后序遍历序列也是无法恢复一棵二叉树的。 - -最后我们来看二叉树的中序遍历,中序遍历是先遍历根节点的左子树,然后访问根节点,最后遍历根节点的右子树。这样,根节点在中序遍历序列中必然将中序序列分割成前后两个子序列,其中前一个子序列是根节点的左子树的中序遍历序列,后一个子序列是根节点的右子树的中序遍历序列。当然单凭中序遍历序列也是无法恢复一棵二叉树的。 - -但是如果我们可以将「前序遍历序列」和「中序遍历序列」相结合,那么我们就可以通过上面中序遍历序列中的两个子序列,在前序遍历序列中找到对应的左子序列和右子序列。在前序遍历序列中,左子序列的第 `1` 个节点是左子树的根节点,右子序列的第 `1` 个节点是右子树的根节点。这样,就确定了二叉树的 `3` 个节点。 - -同时,左子树和右子树的根节点在中序遍历序列中又可以将左子序列和右子序列分别划分成两个子序列。如此递归下去,当确定了前序遍历序列中的所有节点时,我们就得到了一棵二叉树。 - -还有一个问题,通过前序序列和中序序列还原的二叉树是唯一的吗? - -这个唯一性可以利用归纳法加以证明。感兴趣的读者可以试试自己证明或者参考有关资料。 - -通过上述过程说明:**如果已知一棵二叉树的前序序列和中序序列,可以唯一地确定这棵二叉树。** - -同理,**如果已知一棵二叉树的中序序列和后序序列,也可以唯一地确定这棵二叉树。** 方法和通过二叉树的前序序列和中序序列构造二叉树类似,唯一不同点在于二叉树的根节点是根据后序遍历序列的最后一个元素确定的。 - -类似的,**已知二叉树的「中序遍历序列」和「层序遍历序列」,也可以唯一地确定一棵二叉树。** - -需要注意的是:**如果已知二叉树的「前序遍历序列」和「后序遍历序列」,是不能唯一地确定一棵二叉树的。** 这是因为没有中序遍历序列无法确定左右部分,也就无法进行子序列的分割。 - -只有二叉树中每个节点度为 $2$ 或者 $0$ 的时候,已知前序遍历序列和后序遍历序列,才能唯一地确定一颗二叉树,如果二叉树中存在度为 $1$ 的节点时是无法唯一地确定一棵二叉树的,这是因为我们无法判断该节点是左子树还是右子树。 - -## 2. 从前序与中序遍历序列构造二叉树 - -- **描述**:已知一棵二叉树的前序遍历序列和中序遍历序列。 -- **要求**:构造出该二叉树。 -- **注意**:假设树中没有重复的元素。 - -### 2.1 从前序与中序遍历序列构造二叉树实现过程 - -前序遍历的顺序是:根节点 - 左子树 - 右子树。中序遍历的顺序是:左子树 - 根节点 - 右子树。 - -根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。 - -此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: - -1. 从前序遍历顺序中得到当前根节点的位置在 `postorder[0]`。 -2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 -3. 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 2.2 从前序与中序遍历序列构造二叉树实现代码 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def createTree(preorder, inorder, n): - if n == 0: - return None - k = 0 - while preorder[0] != inorder[k]: - k += 1 - node = TreeNode(inorder[k]) - node.left = createTree(preorder[1: k + 1], inorder[0: k], k) - node.right = createTree(preorder[k + 1:], inorder[k + 1:], n - k - 1) - return node - return createTree(preorder, inorder, len(inorder)) -``` - -## 3. 从中序与后序遍历序列构造二叉树 - -- **描述**:已知一棵二叉树的中序遍历序列和后序遍历序列。 -- **要求**:构造出该二叉树。 -- **注意**:假设树中没有重复的元素。 - -### 3.1 从中序与后序遍历序列构造二叉树实现过程 - -中序遍历的顺序是:左子树 - 根节点 - 右子树。后序遍历的顺序是:左子树 - 右子树 - 根节点。 - -根据后序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。 - -此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: - -1. 从后序遍历顺序中当前根节点的位置在 `postorder[n-1]`。 -2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 -3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 3.2 从中序与后序遍历序列构造二叉树实现代码 - -```python -class Solution: - def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: - def createTree(inorder, postorder, n): - if n == 0: - return None - k = 0 - while postorder[n - 1] != inorder[k]: - k += 1 - node = TreeNode(inorder[k]) - node.right = createTree(inorder[k + 1: n], postorder[k: n - 1], n - k - 1) - node.left = createTree(inorder[0: k], postorder[0: k], k) - return node - return createTree(inorder, postorder, len(postorder)) -``` - -## 4. 从前序与后序遍历序列构造二叉树 - -前边我们说过:**已知二叉树的前序遍历序列和后序遍历序列,是不能唯一地确定一棵二叉树的。** 而如果不要求构造的二叉树是唯一的,只要求构造出一棵二叉树,还是可以进行构造的。 - -- **描述**:已知一棵二叉树的前序遍历序列和后序遍历序列。 - -- **要求**:重构并返回该二叉树。 - -- **注意**:假设树中没有重复的元素。如果存在多个答案,则可以返回其中任意一个。 - -### 4.1 从前序与后序遍历序列构造二叉树实现过程 - -我们可以默认指定前序遍历序列的第 `2` 个值为左子树的根节点,由此递归划分左右子序列。具体操作步骤如下: - -1. 从前序遍历序列中可知当前根节点的位置在 `preorder[0]`。 - -2. 前序遍历序列的第 `2` 个值为左子树的根节点,即 `preorder[1]`。通过在后序遍历中查找上一步根节点对应的位置 `postorder[k]`(该节点右侧为右子树序列),从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 - -3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 - -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 4.2 从前序与后序遍历序列构造二叉树实现代码 - -```python -class Solution: - def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode: - def createTree(preorder, postorder, n): - if n == 0: - return None - node = TreeNode(preorder[0]) - if n == 1: - return node - k = 0 - while postorder[k] != preorder[1]: - k += 1 - node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1) - node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2) - return node - return createTree(preorder, postorder, len(preorder)) -``` - -## 参考资料 - -1. 【书籍】数据结构教程 第 3 版 - 唐发根 著 -2. 【书籍】算法训练营 陈小玉 著 -3. 【博文】[二叉树的构造系列 - 知乎](https://zhuanlan.zhihu.com/p/346336665) -4. 【评论】[889. 根据前序和后序遍历构造二叉树 - 力扣)](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/comments/) \ No newline at end of file diff --git a/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md b/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md deleted file mode 100644 index fff819ec..00000000 --- a/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md +++ /dev/null @@ -1,8 +0,0 @@ -### 二叉树的还原题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0105 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0105.%20%E4%BB%8E%E5%89%8D%E5%BA%8F%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0106 | [从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0106.%20%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | -| 0889 | [根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0889.%20%E6%A0%B9%E6%8D%AE%E5%89%8D%E5%BA%8F%E5%92%8C%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、数组、哈希表、分治、二叉树 | 中等 | - diff --git a/Contents/07.Tree/01.Binary-Tree/index.md b/Contents/07.Tree/01.Binary-Tree/index.md deleted file mode 100644 index d962991b..00000000 --- a/Contents/07.Tree/01.Binary-Tree/index.md +++ /dev/null @@ -1,7 +0,0 @@ -## 本章内容 - -- [树与二叉树基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md) -- [二叉树的遍历知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md) -- [二叉树的遍历题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) -- [二叉树的还原知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md) -- [二叉树的还原题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) \ No newline at end of file diff --git a/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md b/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md deleted file mode 100644 index 3aa7a3ec..00000000 --- a/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md +++ /dev/null @@ -1,195 +0,0 @@ -## 1. 二叉搜索树简介 - -> **二叉搜索树(Binary Search Tree)**:也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树: -> -> - 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。 -> - 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。 -> - 任意节点的左子树、右子树均为二叉搜索树。 - -如图所示,这 $3$ 棵树都是二叉搜索树。 - -![img](https://qcdn.itcharge.cn/images/20220218175944.png) - -二叉树具有一个特性,即:**左子树的节点值 < 根节点值 < 右子树的节点值**。 - -根据这个特性,如果我们以中序遍历的方式遍历整个二叉搜索树时,会得到一个递增序列。例如,一棵二叉搜索树的中序遍历序列如下图所示。 - -## 2. 二叉搜索树的查找 - -> **二叉搜索树的查找**:在二叉搜索树中查找值为 `val` 的节点。 - -### 2.1 二叉搜索树的查找算法步骤 - -按照二叉搜索树的定义,在进行元素查找时,我们只需要根据情况判断需要往左还是往右走。这样,每次根据情况判断都会缩小查找范围,从而提高查找效率。二叉树的查找步骤如下: - -1. 如果二叉搜索树为空,则查找失败,结束查找,并返回空指针节点 `None`。 -2. 如果二叉搜索树不为空,则将要查找的值 `val` 与二叉搜索树根节点的值 `root.val` 进行比较: - 1. 如果 `val == root.val`,则查找成功,结束查找,返回被查找到的节点。 - 2. 如果 `val < root.val`,则递归查找左子树。 - 3. 如果 `val > root.val`,则递归查找右子树。 - -### 2.2 二叉搜索树的查找代码实现 - -```python -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right - -class Solution: - def searchBST(self, root: TreeNode, val: int) -> TreeNode: - if not root: - return None - - if val == root.val: - return root - elif val < root.val: - return self.searchBST(root.left, val) - else: - return self.searchBST(root.right, val) -``` - -### 2.3 二叉搜索树的查找算法分析 - -- 二叉搜索树的查找时间复杂度和树的形态有关。 -- 在最好情况下,二叉搜索树的形态与二分查找的判定树相似。每次查找都可以所辖一半搜索范围。查找路径最多从根节点到叶子节点,比较次数最多为树的高度 $\log_2 n$。在最好情况下查找的时间复杂度为 $O(\log_2 n)$。 -- 在最坏情况下,二叉搜索树的形态为单支树,即只有左子树或者只有右子树。每次查找的搜索范围都缩小为 $n - 1$,退化为顺序查找,在最坏情况下时间复杂度为 $O(n)$。 -- 在平均情况下,二叉搜索树的平均查找长度为 $ASL = [(n + 1) / n] * /log_2(n+1) - 1$。所以二分搜索树的查找平均时间复杂度为 $O(log_2 n)$。 - -## 3. 二叉搜索树的插入 - -> **二叉搜索树的插入**:在二叉搜索树中插入一个值为 `val` 的节点(假设当前二叉搜索树中不存在值为 `val` 的节点)。 - -### 3.1 二叉搜索树的插入算法步骤 - -二叉搜索树的插入操作与二叉树的查找操作过程类似,具体步骤如下: - -1. 如果二叉搜索树为空,则创建一个值为 `val` 的节点,并将其作为二叉搜索树的根节点。 -2. 如果二叉搜索树不为空,则将待插入的值 `val` 与二叉搜索树根节点的值 `root.val` 进行比较: - 1. 如果 `val < root.val`,则递归将值为 `val` 的节点插入到左子树中。 - 2. 如果 `val > root.val`,则递归将值为 `val` 的节点插入到右子树中。 - -### 3.2 二叉搜索树的插入代码实现 - -```python -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right - -class Solution: - def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: - if root == None: - return TreeNode(val) - - if val < root.val: - root.left = self.insertIntoBST(root.left, val) - if val > root.val: - root.right = self.insertIntoBST(root.right, val) - return root -``` - -## 4. 二叉搜索树的创建 - -> **二叉搜索树的创建**:根据数组序列中的元素值,建立一棵二叉搜索树。 - -### 4.1 二叉搜索树的创建算法步骤 - -二叉搜索树的创建操作是从空树开始,按照给定数组元素的值,依次进行二叉搜索树的插入操作,最终得到一棵二叉搜索树。具体算法步骤如下: - -1. 初始化二叉搜索树为空树。 -2. 遍历数组元素,将数组元素值 `nums[i]` 依次插入到二叉搜索树中。 -3. 将数组中全部元素值插入到二叉搜索树中之后,返回二叉搜索树的根节点。 - -### 4.2 二叉搜索树的创建代码实现 - -```python -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right - -class Solution: - def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: - if root == None: - return TreeNode(val) - - if val < root.val: - root.left = self.insertIntoBST(root.left, val) - if val > root.val: - root.right = self.insertIntoBST(root.right, val) - return root - def buildBST(self, nums) -> TreeNode: - root = TreeNode(val) - for num in nums: - self.insertIntoBST(root, num) - return root -``` - -## 5. 二叉搜索树的删除 - -> **二叉搜索树的删除**:在二叉搜索树中删除值为 `val` 的节点。 - -### 5.1 二叉搜索树的删除算法步骤 - -在二叉搜索树中删除元素,首先要找到待删除节点,然后执行删除操作。根据待删除节点所在位置的不同,可以分为 $3$ 种情况: - -1. 被删除节点的左子树为空。则令其右子树代替被删除节点的位置。 -2. 被删除节点的右子树为空。则令其左子树代替被删除节点的位置。 -3. 被删除节点的左右子树均不为空,则根据二叉搜索树的中序遍历有序性,删除该节点时,可以使用其直接前驱(或直接后继)代替被删除节点的位置。 - -- **直接前驱**:在中序遍历中,节点 `p` 的直接前驱为其左子树的最右侧的叶子节点。 -- **直接后继**:在中序遍历中,节点 `p` 的直接后继为其右子树的最左侧的叶子节点。 - -二叉搜索树的删除算法步骤如下: - -1. 如果当前节点为空,则返回当前节点。 -2. 如果当前节点值大于 `val`,则递归去左子树中搜索并删除,此时 `root.left` 也要跟着递归更新。 -3. 如果当前节点值小于 `val`,则递归去右子树中搜索并删除,此时 `root.right` 也要跟着递归更新。 -4. 如果当前节点值等于 `val`,则该节点就是待删除节点。 - 1. 如果当前节点的左子树为空,则删除该节点之后,则右子树代替当前节点位置,返回右子树。 - 2. 如果当前节点的右子树为空,则删除该节点之后,则左子树代替当前节点位置,返回左子树。 - 3. 如果当前节点的左右子树都有,则将左子树转移到右子树最左侧的叶子节点位置上,然后右子树代替当前节点位置。 - -### 5.2 二叉搜索树的删除代码实现 - -```python -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right - -class Solution: - def deleteNode(self, root: TreeNode, val: int) -> TreeNode: - if not root: - return root - - if root.val > val: - root.left = self.deleteNode(root.left, val) - return root - elif root.val < val: - root.right = self.deleteNode(root.right, val) - return root - else: - if not root.left: - return root.right - elif not root.right: - return root.left - else: - curr = root.right - while curr.left: - curr = curr.left - curr.left = root.left - return root.right -``` - -## 参考资料 - -- 【书籍】算法训练营 陈小玉 著 -- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 -- 【书籍】算法竞赛进阶指南 - 李煜东 著 - diff --git a/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md b/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md deleted file mode 100644 index 6d06541d..00000000 --- a/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md +++ /dev/null @@ -1,17 +0,0 @@ -### 二叉搜索树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0098 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0098.%20%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0173 | [二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0173.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BF%AD%E4%BB%A3%E5%99%A8.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | -| 0700 | [二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0700.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%90%9C%E7%B4%A2.md) | 树、二叉搜索树、二叉树 | 简单 | -| 0701 | [二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0701.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E6%8F%92%E5%85%A5%E6%93%8D%E4%BD%9C.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0450 | [删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0450.%20%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 树、二叉搜索树、二叉树 | 中等 | -| 0703 | [数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0703.%20%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E7%AC%AC%20K%20%E5%A4%A7%E5%85%83%E7%B4%A0.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | -| 剑指 Offer 54 | [二叉搜索树的第k大节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2054.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | -| 0230 | [二叉搜索树中第K小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0235 | [二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0235.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | -| 0426 | [将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0426.%20%E5%B0%86%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%8E%92%E5%BA%8F%E7%9A%84%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | -| 0108 | [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0108.%20%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | -| 0110 | [平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0110.%20%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、二叉树 | 简单 | - diff --git a/Contents/07.Tree/02.Binary-Search-Tree/index.md b/Contents/07.Tree/02.Binary-Search-Tree/index.md deleted file mode 100644 index 2f1c88f7..00000000 --- a/Contents/07.Tree/02.Binary-Search-Tree/index.md +++ /dev/null @@ -1,5 +0,0 @@ -## 本章内容 - -- [二叉搜索树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md) -- [二叉搜索树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) - diff --git a/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md b/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md deleted file mode 100644 index 368f40bb..00000000 --- a/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md +++ /dev/null @@ -1,526 +0,0 @@ -## 1. 线段树简介 - -### 1.1 线段树的定义 - -> **线段树(Segment Tree)**:一种基于分治思想的二叉树,用于在区间上进行信息统计。它的每一个节点都对应一个区间 `[left, right]` ,`left`、`right` 通常是整数。每一个叶子节点表示了一个单位区间(长度为 `1`),叶子节点对应区间上 `left == right`。每一个非叶子节点 `[left, right]` 的左子节点表示的区间都为 `[left, (left + right) / 2]`,右子节点表示的的区间都为 `[(left + right) / 2 + 1, right]`。 - -线段树是一棵平衡二叉树,树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父亲节点的区间二等分之后的其中一个子区间。当有 `n` 个元素时,对区间的操作(单点更新、区间更新、区间查询等)可以在 $O(log_2n)$ 的时间复杂度内完成。 - -如下图所示,这是一棵区间为 `[0, 7]` 的线段树。 - -![](https://qcdn.itcharge.cn/images/20220302103338.png) - -### 1.2 线段树的特点 - -根据上述描述,我们可以总结一下线段树的特点: - -1. 线段树的每个节点都代表一个区间。 -2. 线段树具有唯一的根节点,代表的区间是整个统计范围,比如 `[1, n]`。 -3. 线段树的每个叶子节点都代表一个长度为 `1` 的单位区间 `[x, x]`。 -4. 对于每个内部节点 `[left, right]`,它的左子节点是 `[left, mid]`,右子节点是 `[mid + 1, right]`。其中 `mid = (left + right) / 2`(向下取整)。 - -## 2. 线段树的构建 - -### 2.1 线段树的存储结构 - -之前我们学习过二叉树的两种存储结构,一种是「链式存储结构」,另一种是「顺序存储结构」。线段树也可以使用这两种存储结构来实现。 - -由于线段树近乎是完全二叉树,所以很适合用「顺序存储结构」来实现。 - -我们可以采用与完全二叉树类似的编号方法来对线段树进行编号,方法如下: - -- 根节点的编号为 `0`。 -- 如果某二叉树节点(非叶子节点)的下标为 `i`,那么其左孩子节点下标为 `2 * i + 1`,右孩子节点下标为 `2 * i + 2`。 -- 如果某二叉树节点(非根节点)的下标为 `i`,那么其父节点下标为 `(i - 1) // 2`,`//` 表示整除。 - -这样我们就能使用一个数组来保存线段树。那么这个数组的大小应该设置为多少才合适? - -- 在理想情况下,`n` 个单位区间构成的线段树是一棵满二叉树,节点数为 $n + n/2 + n/4 + ... + 2 + 1 = 2 * n - 1$ 个。 因为 $2 * n - 1 < 2 * n$,所以在理想情况下,只需要使用一个大小为 $2 * n$ 的数组来存储线段树就足够了。 -- 但是在一般情况下,有些区间元素需要开辟新的一层来存储元素。线段树的深度为 $\lceil log_2n \rceil$,最坏情况下叶子节点(包括无用的节点)的数量为 $2^{\lceil log_2n \rceil}$ 个,总节点数为 $2^{\lceil log_2n \rceil + 1} - 1$ 个,可以近似看做是 $4 * n$,所以我们可以使用一个大小为 $4 * n$ 的数组来存储线段树。 - -### 2.2 线段树的构建方法 - -![线段树父子节点下标关系](https://qcdn.itcharge.cn/images/20220303131328.png) - -通过上图可知:下标为 `i` 的节点的孩子节点下标为 `2 * i + 1` 和 `2 * i + 2`。所以线段树十分适合采用递归的方法来创建。具体步骤如下: - -1. 如果是叶子节点(`left == right`),则节点的值就是对应位置的元素值。 -2. 如果是非叶子节点,则递归创建左子树和右子树。 -3. 节点的区间值(区间和、区间最大值、区间最小值)等于该节点左右子节点元素值的对应计算结果。 - -线段树的构建实现代码如下: - -```python -# 线段树的节点类 -class TreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [TreeNode() for _ in range(4 * self.size)] # 维护 TreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 构建线段树,节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - # 向上更新下标为 index 的节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) -``` - -这里的 `function` 指的是线段树区间合并的聚合方法。可以根据题意进行变化,常见的操作有求和、取最大值、取最小值等等。 - -## 3. 线段树的基本操作 - -线段树的基本操作主要涉及到单点更新、区间查询和区间更新操作。下面我们来进行一一讲解。 - -### 3.1 线段树的单点更新 - -> **线段树的单点更新**:修改一个元素的值,例如将 `nums[i]` 修改为 `val`。 - -我们可以采用递归的方式进行单点更新,具体步骤如下: - -1. 如果是叶子节点,满足 `left == right`,则更新该节点的值。 -2. 如果是非叶子节点,则判断应该在左子树中更新,还是应该在右子树中更新。 -3. 在对应的左子树或右子树中更新节点值。 -4. 左右子树更新返回之后,向上更新节点的区间值(区间和、区间最大值、区间最小值等),区间值等于该节点左右子节点元素值的聚合计算结果。 - -线段树的单点更新实现代码如下: - -```python - # 单点更新,将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0, 0, self.size - 1) - - # 单点更新,将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] - def __update_point(self, i, val, index, left, right): - if self.tree[index].left == self.tree[index].right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index, left, mid) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index, mid + 1, right) - self.__pushup(index) # 向上更新节点的区间值 -``` - -### 3.2 线段树的区间查询 - -> **线段树的区间查询**:查询一个区间为 `[q_left, q_right]` 的区间值。 - -我们可以采用递归的方式进行区间查询,具体步骤如下: - -1. 如果区间 `[q_left, q_right]` 完全覆盖了当前节点所在区间 `[left, right]` ,即 `left >= q_left` 并且 `right <= q_right`,则返回该节点的区间值。 -2. 如果区间 `[q_left, q_right]` 与当前节点所在区间 `[left, right]` 毫无关系,即 `right < q_left` 或者 `left > q_right`,则返回 `0`。 -3. 如果区间 `[q_left, q_right]` 与当前节点所在区间有交集,则: - 1. 如果区间 `[q_left, q_right]` 与左子节点所在区间 `[left, mid]` 有交集,即 `q_left <= mid`,则在当前节点的左子树中进行查询并保存查询结果 `res_left`。 - 2. 如果区间 `[q_left, q_right]` 与右子节点所在区间 `[mid + 1, right]` 有交集,即 `q_right > mid`,则在当前节点的右子树中进行查询并保存查询结果 `res_right`。 - 3. 最后返回左右子树元素区间值的聚合计算结果。 - -线段树的区间查询代码如下: - -```python - # 区间查询,查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0, 0, self.size - 1) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index, left, right): - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index, left, mid) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index, mid + 1, right) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 -``` - -### 3.3 线段树的区间更新 - -> **线段树的区间更新**:对 `[q_left, q_right]` 区间进行更新,例如将 `[q_left, q_right]` 区间内所有元素都更新为 `val`。 - -#### 3.3.1 延迟标记 - -线段树在进行单点更新、区间查询时,区间 `[q_left, q_right]` 在线段树上会被分成 $O(log_2n)$ 个小区间(节点),从而在 $O(log_2n)$ 的时间复杂度内完成操作。 - -而在「区间更新」操作中,如果某个节点区间 `[left, right]` 被修改区间 `[q_left, q_right]` 完全覆盖,则以该节点为根的整棵子树中所有节点的区间值都要发生变化,如果逐一进行更新的话,将使得一次区间更新操作的时间复杂度增加到 $O(n)$。 - -设想这一种情况:如果我们在一次执行更新操作时,发现当前节点区间 `[left, right]` 被修改区间 `[q_left, q_right]` 完全覆盖,然后逐一更新了区间 `[left, right]` 对应子树中的所有节点,但是在后续的区间查询操作中却根本没有用到 `[left, right]` 作为候选答案,则更新 `[left, right]` 对应子树的工作就是徒劳的。 - -如果我们减少更新的次数和时间复杂度,应该怎么办? - -我们可以向线段树的节点类中增加一个 **「延迟标记」**,标识为 **「该区间曾经被修改为 `val`,但其子节点区间值尚未更新」**。也就是说除了在进行区间更新时,将区间子节点的更新操作延迟到 **「在后续操作中递归进入子节点时」** 再执行。这样一来,每次区间更新和区间查询的时间复杂度都降低到了 $O(log_2n)$。 - -使用「延迟标记」的区间更新步骤为: - -1. 如果区间 `[q_left, q_right]` 完全覆盖了当前节点所在区间 `[left, right]` ,即 `left >= q_left` 并且 `right <= q_right`,则更新当前节点所在区间的值,并将当前节点的延迟标记为区间值。 -2. 如果区间 `[q_left, q_right]` 与当前节点所在区间 `[left, right]` 毫无关系,即 `right < q_left` 或者 `left > q_right`,则直接返回。 -3. 如果区间 `[q_left, q_right]` 与当前节点所在区间有交集,则: - 1. 如果当前节点使用了「延迟标记」,即延迟标记不为 `None`,则将当前区间的更新操作应用到该节点的子节点上(即向下更新)。 - 2. 如果区间 `[q_left, q_right]` 与左子节点所在区间 `[left, mid]` 有交集,即 `q_left <= mid`,则在当前节点的左子树中更新区间值。 - 3. 如果区间 `[q_left, q_right]` 与右子节点所在区间 `[mid + 1, right]` 有交集,即 `q_right > mid`,则在当前节点的右子树中更新区间值。 - 4. 左右子树更新返回之后,向上更新节点的区间值(区间和、区间最大值、区间最小值),区间值等于该节点左右子节点元素值的对应计算结果。 - -#### 3.3.2 向下更新 - -上面提到了如果当前节点使用了「延迟标记」,即延迟标记不为 `None`,则将当前区间的更新操作应用到该节点的子节点上(即向下更新)。这里描述一下向下更新的具体步骤: - -1. 更新左子节点值和左子节点懒惰标记为 `val`。 -2. 更新右子节点值和右子节点懒惰标记为 `val`。 -3. 将当前节点的懒惰标记更新为 `None`。 - -使用「延迟标记」的区间更新实现代码如下: - -```python - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1) - - # 区间更新 - def __update_interval(self, q_left, q_right, val, index, left, right): - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - interval_size = (right - left + 1) # 当前节点所在区间大小 - self.tree[index].val = interval_size * val # 当前节点所在区间每个元素值改为 val - self.tree[index].lazy_tag = val # 将当前节点的延迟标记为区间值 - return - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index, left, mid) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index, mid + 1, right) - - self.__pushup(index) - - # 向下更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if not lazy_tag: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - self.tree[left_index].lazy_tag = lazy_tag # 更新左子节点懒惰标记 - left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) - self.tree[left_index].val = lazy_tag * left_size # 更新左子节点值 - - self.tree[right_index].lazy_tag = lazy_tag # 更新右子节点懒惰标记 - right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) - self.tree[right_index].val = lazy_tag * right_size # 更新右子节点值 - - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 -``` - -> **注意**:有些题目中不是将 `[q_left, q_right]` 区间更新为 `val`,而是将 `[q_left, q_right]` 区间中每一个元素值在原值基础增加或减去 `val`。 -> -> 对于这种情况,我们可以更改一下「延迟标记」的定义。改变为: **「该区间曾经变化了 `val`,但其子节点区间值尚未更新」**。并更改对应的代码逻辑。 - -使用「延迟标记」的区间增减更新实现代码如下: - -```python - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1) - - # 区间更新 - def __update_interval(self, q_left, q_right, val, index, left, right): - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 -# interval_size = (right - left + 1) # 当前节点所在区间大小 -# self.tree[index].val = interval_size * val # 当前节点所在区间每个元素值改为 val -# self.tree[index].lazy_tag = val # 将当前节点的延迟标记为区间值 - - if self.tree[index].lazy_tag: - self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val - else: - self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - interval_size = (right - left + 1) # 当前节点所在区间大小 - self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val - return - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index, left, mid) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index, mid + 1, right) - - self.__pushup(index) - - # 向下更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if not lazy_tag: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - if self.tree[left_index].lazy_tag: - self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - self.tree[left_index].lazy_tag = lazy_tag - left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) - self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag - - if self.tree[right_index].lazy_tag: - self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - self.tree[right_index].lazy_tag = lazy_tag - right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) - self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag - - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 -``` - -## 4. 线段树的常见题型 - -### 4.1 RMQ 问题 - -> **RMQ 问题**:Range Maximum / Minimum Query 的缩写,指的是对于长度为 `n` 的数组序列 `nums`,回答若干个询问问题 `RMQ(nums, q_left, q_right)`,要求返回数组序列 `nums` 在区间 `[q_left, q_right]` 中的最大(最小)值。也就是求区间最大(最小)值问题。 - -假设查询次数为 `q`,则使用朴素算法解决 RMQ 问题的时间复杂度为 $O(q * n)$。而使用线段树解决 RMQ 问题的时间复杂度为 $O(q * n)$ ~ $Q(q * log_2n)$ 之间。 - -### 4.2 单点更新,区间查询问题 - -> **单点更新,区间查询问题**: -> -> 1. 修改某一个元素的值。 -> 2. 查询区间为 `[q_left, q_right]` 的区间值。 - -这类问题直接使用「3.1 线段树的单点更新」和「3.2 线段树的区间查询」即可解决。 - -### 4.3 区间更新,区间查询问题 - -> **区间更新,区间查询问题**: -> -> 1. 修改某一个区间的值。 -> 2. 查询区间为 `[q_left, q_right]` 的区间值。 - -这类问题直接使用「3.3 线段树的区间更新」和「3.2 线段树的区间查询」即可解决。 - -### 4.4 区间合并问题 - -> **区间合并,区间查询问题**: -> -> 1. 修改某一个区间的值。 -> 2. 查询区间为 `[q_left, q_right]` 中满足条件的连续最长区间值。 - -这类问题需要在「3.3 线段树的区间更新」和「3.2 线段树的区间查询」的基础上增加变动,在进行向上更新时需要对左右子节点的区间进行合并。 - -### 4.5 扫描线问题 - -> **扫描线问题**:虚拟扫描线或扫描面来解决欧几里德空间中的各种问题,一般被用来解决图形面积,周长等问题。 -> -> 主要思想为:想象一条线(通常是一条垂直线)在平面上扫过或移动,在某些点停止。几何操作仅限于几何对象,无论何时停止,它们都与扫描线相交或紧邻扫描线,并且一旦线穿过所有对象,就可以获得完整的解。 - -这类问题通常坐标跨度很大,需要先对每条扫描线的坐标进行离散化处理,将 `y` 坐标映射到 `0, 1, 2, ...` 中。然后将每条竖线的端点作为区间范围,使用线段树存储每条竖线的信息(`x` 坐标、是左竖线还是右竖线等),然后再进行区间合并,并统计相关信息。 - -## 5. 线段树的拓展 - -### 5.1 动态开点线段树 - -在有些情况下,线段树需要维护的区间很大(例如 $[1, 10^9]$),在实际中用到的节点却很少。 - -如果使用之前数组形式实现线段树,则需要 $4 * n$ 大小的空间,空间消耗有点过大了。 - -这时候我们就可以使用动态开点的思想来构建线段树。 - -动态开点线段树的算法思想如下: - -- 开始时只建立一个根节点,代表整个区间。 -- 当需要访问线段树的某棵子树(某个子区间)时,再建立代表这个子区间的节点。 - -动态开点线段树实现代码如下: - -```python -# 线段树的节点类 -class TreeNode: - def __init__(self, left=-1, right=-1, val=0): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = None # 区间左节点 - self.rightNode = None # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - def __init__(self, function): - self.tree = TreeNode(0, int(1e9)) - self.function = function # function 是一个函数,左右区间的聚合方法 - - # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - leftNode = node.leftNode - rightNode = node.rightNode - if leftNode and rightNode: - node.val = self.function(leftNode.val, rightNode.val) - - # 单点更新,将 nums[i] 更改为 val - def update_point(self, i, val): - self.__update_point(i, val, self.tree) - - # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] - def __update_point(self, i, val, node): - if node.left == node.right: - node.val = val # 叶子节点,节点值修改为 val - return - - if i <= node.mid: # 在左子树中更新节点值 - if not node.leftNode: - node.leftNode = TreeNode(node.left, node.mid) - self.__update_point(i, val, node.leftNode) - else: # 在右子树中更新节点值 - if not node.rightNode: - node.rightNode = TreeNode(node.mid + 1, node.right) - self.__update_point(i, val, node.rightNode) - self.__pushup(node) # 向上更新节点的区间值 - - # 区间查询,查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - if not node.leftNode: - node.leftNode = TreeNode(node.left, node.mid) - res_left = self.__query_interval(q_left, q_right, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - if not node.rightNode: - node.rightNode = TreeNode(node.mid + 1, node.right) - res_right = self.__query_interval(q_left, q_right, node.rightNode) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间更新 - def __update_interval(self, q_left, q_right, val, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if node.lazy_tag: - node.lazy_tag += val # 将当前节点的延迟标记增加 val - else: - node.lazy_tag = val # 将当前节点的延迟标记增加 val - interval_size = (node.right - node.left + 1) # 当前节点所在区间大小 - node.val += val * interval_size # 当前节点所在区间每个元素值增加 val - return - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - if q_left <= node.mid: # 在左子树中更新区间值 - if not node.leftNode: - node.leftNode = TreeNode(node.left, node.mid) - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - if not node.rightNode: - node.rightNode = TreeNode(node.mid + 1, node.right) - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - lazy_tag = node.lazy_tag - if not node.lazy_tag: - return - - if not node.leftNode: - node.leftNode = TreeNode(node.left, node.mid) - if not node.rightNode: - node.rightNode = TreeNode(node.mid + 1, node.right) - - if node.leftNode.lazy_tag: - node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 - left_size = (node.leftNode.right - node.leftNode.left + 1) - node.leftNode.val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag - - if node.rightNode.lazy_tag: - node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 - right_size = (node.rightNode.right - node.rightNode.left + 1) - node.rightNode.val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag - - node.lazy_tag = None # 更新当前节点的懒惰标记 -``` - -## 参考资料 - -- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 -- 【书籍】算法训练营 陈小玉 著 -- 【博文】[史上最详细的线段树教程 - 知乎](https://zhuanlan.zhihu.com/p/34150142) -- 【博文】[线段树 Segment Tree 实战 - halfrost](https://halfrost.com/segment_tree/) -- 【博文】[线段树 - OI Wiki](https://oi-wiki.org/ds/seg/) -- 【博文】[线段树的 python 实现 - 年糕的博客 - CSDN博客](https://blog.csdn.net/qq_33935895/article/details/102806357) -- 【博文】[线段树 从入门到进阶 - Dijkstra·Liu - 博客园](https://www.cnblogs.com/dijkstra2003/p/9676729.html) - diff --git a/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md b/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md deleted file mode 100644 index a9a1dc92..00000000 --- a/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md +++ /dev/null @@ -1,37 +0,0 @@ -### 线段树题目 - -#### 单点更新题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0303 | [区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、前缀和 | 简单 | -| 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | - -#### 区间更新题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0370 | [区间加法](https://leetcode.cn/problems/range-addition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0370.%20%E5%8C%BA%E9%97%B4%E5%8A%A0%E6%B3%95.md) | 数组、前缀和 | 中等 | -| 1109 | [航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md) | 数组、前缀和 | 中等 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 1310 | [子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md) | 位运算、数组、前缀和 | 中等 | -| 1851 | [包含每个查询的最小区间](https://leetcode.cn/problems/minimum-interval-to-include-each-query/) | | 数组、二分查找、排序、扫描线、堆(优先队列) | 困难 | - -#### 区间合并题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0729 | [我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0729.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20I.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0731 | [我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0731.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20II.md) | 设计、线段树、二分查找、有序集合 | 中等 | -| 0732 | [我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0732.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20III.md) | 设计、线段树、二分查找、有序集合 | 困难 | - -#### 扫描线问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | -| 0391 | [完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md) | 数组、扫描线 | 困难 | -| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | - diff --git a/Contents/07.Tree/03.Segment-Tree/index.md b/Contents/07.Tree/03.Segment-Tree/index.md deleted file mode 100644 index c0fe57d3..00000000 --- a/Contents/07.Tree/03.Segment-Tree/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [线段树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md) -- [线段树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) \ No newline at end of file diff --git a/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md b/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md deleted file mode 100644 index fa4ae282..00000000 --- a/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md +++ /dev/null @@ -1,31 +0,0 @@ -## 1. 树状数组简介 - -### 1.1 树状数组的定义 - -> **树状数组(Binary Indexed Tree)**:也因其发明者命名为 Fenwick 树,最早 Peter M. Fenwick 于 1994 年以 A New Data Structure for Cumulative Frequency Tables 为题发表在 SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和,区间和。它可以以 $O(\log n)$ 的时间得到任意前缀 $\sum_{i=1}^{j}A[i], 1 \le j \le n$,并同时支持在 $O(\log n)$ 时间内支持动态单点值的修改。空间复杂度为 $O(n)$。 - -### 1.2 树状数组的原理 - -## 2. 树状数组的基本操作 - -### 2.1 树状数组的建立 - -### 2.2 树状数组的修改 - -### 2.3 树状数组的求和 - -## 4. 树状数组的常见题型 - -### 4.1 单点更新 + 区间求值 - -### 4.2 区间更新 + 单点求值 - -### 4.3 求逆序对数 - -## 参考资料 - -- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 -- 【书籍】算法训练营 陈小玉 著 -- 【博文】[聊聊树状数组 Binary Indexed Tree - halfrost](https://halfrost.com/binary_indexed_tree/) -- 【博文】[树状数组学习笔记 - AcWing](https://www.acwing.com/blog/content/80/) - diff --git a/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md b/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md deleted file mode 100644 index c67d720a..00000000 --- a/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md +++ /dev/null @@ -1,13 +0,0 @@ -### 树状数组题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0303 | [区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0303.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、前缀和 | 简单 | -| 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | -| 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 1310 | [子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1310.%20%E5%AD%90%E6%95%B0%E7%BB%84%E5%BC%82%E6%88%96%E6%9F%A5%E8%AF%A2.md) | 位运算、数组、前缀和 | 中等 | -| 1893 | [检查是否区域内所有整数都被覆盖](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) | | 数组、哈希表、前缀和 | 简单 | - diff --git a/Contents/07.Tree/04.Binary-Indexed-Tree/index.md b/Contents/07.Tree/04.Binary-Indexed-Tree/index.md deleted file mode 100644 index f80203d7..00000000 --- a/Contents/07.Tree/04.Binary-Indexed-Tree/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [树状数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md) -- [树状数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) \ No newline at end of file diff --git a/Contents/07.Tree/05.Union-Find/01.Union-Find.md b/Contents/07.Tree/05.Union-Find/01.Union-Find.md deleted file mode 100644 index 1939baa0..00000000 --- a/Contents/07.Tree/05.Union-Find/01.Union-Find.md +++ /dev/null @@ -1,547 +0,0 @@ -## 1. 并查集简介 - -### 1.1 并查集的定义 - -> **并查集(Union Find)**:一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。不交集指的是一系列没有重复元素的集合。 -> -> 并查集主要支持两种操作: -> -> - **合并(Union)**:将两个集合合并成一个集合。 -> - **查找(Find)**:确定某个元素属于哪个集合。通常是返回集合内的一个「代表元素」。 - -简单来说,并查集就是用来处理集合的合并和集合的查询。 - -- 并查集中的「集」指的就是我们初中所学的集合概念,在这里指的是不相交的集合,即一系列没有重复元素的集合。 -- 并查集中的「并」指的就是集合的并集操作,将两个集合合并之后就变成一个集合。合并操作如下所示: - -```python -{1, 3, 5, 7} ∪ {2, 4, 6, 8} = {1, 2, 3, 4, 5, 6, 7, 8} -``` - -- 并查集中的「查」是对于集合中存放的元素来说的,通常我们需要查询两个元素是否属于同一个集合。 - -如果我们只是想知道一个元素是否在集合中,可以通过 `Python` 或其他语言中的 `set` 集合来解决。而如果我们想知道两个元素是否属于同一个集合,则仅用一个 `set` 集合就很难做到了。这就需要用到我们接下来要讲解的「并查集」结构。 - -根据上文描述,我们就可以定义一下「并查集」结构所支持的操作接口: - -- **合并 `union(x, y)`**:将集合 `x` 和集合 `y` 合并成一个集合。 -- 查找 `find(x)`:查找元素 `x` 属于哪个集合。 -- **查找 `is_connected(x, y)`**:查询元素 `x` 和 `y` 是否在同一个集合中。 - -### 1.2 并查集的两种实现思路 - -下面我们来讲解一下并查集的两种实现思路:一种是使用「快速查询」思路、基于数组结构实现的并查集;另一种是使用「快速合并」思路、基于森林实现的并查集。 - -#### 1.2.1 快速查询:基于数组实现 - -如果我们希望并查集的查询效率高一些,那么我们就可以侧重于查询操作。 - -在使用「快速查询」思路实现并查集时,我们可以使用一个「数组结构」来表示集合中的元素。数组元素和集合元素是一一对应的,我们可以将数组的索引值作为每个元素的集合编号,称为 `id`。然后可以对数组进行以下操作来实现并查集: - -- **当初始化时**:将每个元素的集合编号初始化为数组下标索引。则所有元素的 `id` 都是唯一的,代表着每个元素单独属于一个集合。 -- **合并操作时**:需要将其中一个集合中的所有元素 `id` 更改为另一个集合中的 `id`,这样能够保证在合并后一个集合中所有元素的 `id` 均相同。 -- **查找操作时**:如果两个元素的 `id` 一样,则说明它们属于同一个集合;如果两个元素的 `id` 不一样,则说明它们不属于同一个集合。 - -举个例子来说明一下,我们使用数组来表示一系列集合元素 `{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}`,初始化时如下图所示。从下图中可以看出:元素的集合编号就是数组的索引值,代表着每个元素属于一个集合。 - -![](https://qcdn.itcharge.cn/images/20220505145234.png) - -当我们进行一系列的合并操作后,比如合并后变为 `{0}, {1, 2, 3}, {4}, {5, 6}, {7}`,合并操作的结果如下图所示。从图中可以看出,在进行一系列合并操作后,下标为 `1`、`2`、`3` 的元素集合编号是一致的,说明这 `3` 个 元素同属于一个集合。同理下标为 `5` 和 `6` 的元素则同属于另一个集合。 - -![](https://qcdn.itcharge.cn/images/20220505145302.png) - -在快速查询的实现思路中,单次查询操作的时间复杂度是 $O(1)$,而单次合并操作的时间复杂度为 $O(n)$(每次合并操作需要遍历数组)。两者的时间复杂度相差得比较大,完全牺牲了合并操作的性能。因此,这种并查集的实现思路并不常用。 - -- 使用「快速查询」思路实现并查集代码如下所示: - -```python -class UnionFind: - def __init__(self, n): # 初始化:将每个元素的集合编号初始化为数组下标索引 - self.ids = [i for i in range(n)] - - def find(self, x): # 查找元素所属集合编号内部实现方法 - return self.ids[x] - - def union(self, x, y): # 合并操作:将集合 x 和集合 y 合并成一个集合 - x_id = self.find(x) - y_id = self.find(y) - - if x_id == y_id: # x 和 y 已经同属于一个集合 - return False - - for i in range(len(self.ids)): # 将两个集合的集合编号改为一致 - if self.ids[i] == y_id: - self.ids[i] = x_id - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -#### 1.2.2 快速合并:基于森林实现 - -因为快速查询的实现思路中,合并操作的效率比较低。所以我们现在的重点是提高合并操作的效率。 - -在使用「快速合并」思路实现并查集时,我们可以使用「一个森林(若干棵树)」来存储所有集合。每一棵树代表一个集合,树上的每个节点都是一个元素,树根节点为这个集合的代表元素。 - -> **注意**:与普通的树形结构(父节点指向子节点)不同的是,基于森林实现的并查集中,树中的子节点是指向父节点的。 - -此时,我们仍然可以使用一个数组 `fa` 来记录这个森林。我们用 `fa[x]` 来保存 `x` 的父节点的集合编号,代表着元素节点 `x` 指向父节点 `fa[x]`。 - -当初始化时,`fa[x]` 值赋值为下标索引 `x`。在进行合并操作时,只需要将两个元素的树根节点相连接(`fa[root1] = root2`)即可。而在进行查询操作时,只需要查看两个元素的树根节点是否一致,就能知道两个元素是否属于同一个集合。 - -总结一下,我们可以对数组 `fa` 进行以下操作来实现并查集: - -- **当初始化时**:将每个元素的集合编号初始化为数组 `fa` 的下标索引。所有元素的根节点的集合编号不一样,代表着每个元素单独属于一个集合。 -- **合并操作时**:需要将两个集合的树根节点相连接。即令其中一个集合的树根节点指向另一个集合的树根节点(`fa[root1] = root2`),这样合并后当前集合中的所有元素的树根节点均为同一个。 -- **查找操作时**:分别从两个元素开始,通过数组 `fa` 存储的值,不断递归访问元素的父节点,直到到达树根节点。如果两个元素的树根节点一样,则说明它们属于同一个集合;如果两个元素的树根节点不一样,则说明它们不属于同一个集合。 - -举个例子来说明一下,我们使用数组来表示一系列集合元素 `{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}`,初始化时如下图所示。从下图中可以看出:元素的集合编号就是数组 `fa` 的索引值,代表着每个元素属于一个集合。 - -![](https://qcdn.itcharge.cn/images/20220507112934.png) - -当我们进行一系列的合并操作后,比如 `union(4, 5)`、`union(6, 7)`、`union(4, 7)` 操作后变为 `{0}, {1}, {2}, {3}, {4, 5, 6, 7}`,合并操作的步骤及结果如下图所示。从图中可以看出,在进行一系列合并操作后,`fa[4] == fa[5] == fa[6] == fa[fa[7]]`,即 `4`、`5`、`6`、`7` 的元素根节点编号都是 `4`,说明这 `4` 个 元素同属于一个集合。 - -![](https://qcdn.itcharge.cn/images/20220507142647.png) - -- 使用「快速合并」思路实现并查集代码如下所示: - -```python -class UnionFind: - def __init__(self, n): # 初始化:将每个元素的集合编号初始化为数组 fa 的下标索引 - self.fa = [i for i in range(n)] - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -## 2. 路径压缩 - -在集合很大或者树很不平衡时,使用上述「快速合并」思路实现并查集的代码效率很差,最坏情况下,树会退化成一条链,单次查询的时间复杂度高达 $O(n)$。并查集的最坏情况如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220507172300.png) - -为了避免出现最坏情况,一个常见的优化方式是「路径压缩」。 - -> **路径压缩(Path Compression)**:在从底向上查找根节点过程中,如果此时访问的节点不是根节点,则我们可以把这个节点尽量向上移动一下,从而减少树的层树。这个过程就叫做路径压缩。 - -路径压缩有两种方式:一种叫做「隔代压缩」;另一种叫做「完全压缩」。 - -### 2.1 隔代压缩 - -> **隔代压缩**:在查询时,两步一压缩,一直循环执行「把当前节点指向它的父亲节点的父亲节点」这样的操作,从而减小树的深度。 - -下面是一个「隔代压缩」的例子。 - -![](https://qcdn.itcharge.cn/images/20220509113954.png) - -- 隔代压缩的查找代码如下: - -```python -def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩 - x = self.fa[x] - return x # 返回元素根节点的集合编号 -``` - -### 2.2 完全压缩 - -> **完全压缩**:在查询时,把被查询的节点到根节点的路径上的所有节点的父节点设置为根节点,从而减小树的深度。也就是说,在向上查询的同时,把在路径上的每个节点都直接连接到根上,以后查询时就能直接查询到根节点。 - -相比较于「隔代压缩」,「完全压缩」压缩的更加彻底。下面是一个「完全压缩」的例子。 - -![](https://qcdn.itcharge.cn/images/20220507174723.png) - -- 完全压缩的查找代码如下: - -```python -def find(self, x): # 查找元素根节点的集合编号内部实现方法 - if self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.find(self.fa[x]) # 完全压缩优化 - return self.fa[x] -``` - -## 3. 按秩合并 - -因为路径压缩只在查询时进行,并且只压缩一棵树上的路径,所以并查集最终的结构仍然可能是比较复杂的。为了避免这种情况,另一个优化方式是「按秩合并」。 - -> **按秩合并(Union By Rank)**:指的是在每次合并操作时,都把「秩」较小的树根节点指向「秩」较大的树根节点。 - -这里的「秩」有两种定义,一种定义指的是树的深度;另一种定义指的是树的大小(即集合节点个数)。无论采用哪种定义,集合的秩都记录在树的根节点上。 - -按秩合并也有两种方式:一种叫做「按深度合并」;另一种叫做「按大小合并」。 - -### 3.1 按深度合并 - -> **按深度合并(Unoin By Rank)**:在每次合并操作时,都把「深度」较小的树根节点指向「深度」较大的树根节点。 - -我们用一个数组 `rank` 记录每个根节点对应的树的深度(如果不是根节点,其 `rank` 值相当于以它作为根节点的子树的深度)。 - -初始化时,将所有元素的 `rank` 值设为 `1`。在合并操作时,比较两个根节点,把 `rank` 值较小的根节点指向 `rank` 值较大的根节点上合并。 - -下面是一个「按深度合并」的例子。 - -![](https://qcdn.itcharge.cn/images/20220509094655.png) - -- 按深度合并的实现代码如下: - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 - self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 - else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # 向任意一方合并即可 - rank[y] += 1 # 因为层数相同,被合并的树必然层数会 +1 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -### 3.2 按大小合并 - -> **按大小合并(Unoin By Size)**:这里的大小指的是集合节点个数。在每次合并操作时,都把「集合节点个数」较少的树根节点指向「集合节点个数」较大的树根节点。 - -我们用一个数组 `size` 记录每个根节点对应的集合节点个数(如果不是根节点,其 `size` 值相当于以它作为根节点的子树的集合节点个数)。 - -初始化时,将所有元素的 `size` 值设为 `1`。在合并操作时,比较两个根节点,把 `size` 值较小的根节点指向 `size` 值较大的根节点上合并。 - -下面是一个「按大小合并」的例子。 - -![](https://qcdn.itcharge.cn/images/20220509094634.png) - -- 按大小合并的实现代码如下: - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - self.size = [1 for i in range(n)] # 每个元素的集合个数初始化为 1 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - if self.size[root_x] < self.size[root_y]: # x 对应的集合元素个数 小于 y 对应的集合元素个数 - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - self.size[root_y] += self.size[root_x] # y 的根节点对应的集合元素个数 累加上 x 的根节点对应的集合元素个数 - elif self.size[root_x] > self.size[root_y]: # x 对应的集合元素个数 大于 y 对应的集合元素个数 - self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 - self.size[root_x] += self.size[root_y] # x 的根节点对应的集合元素个数 累加上 y 的根节点对应的集合元素个数 - else: # x 对应的集合元素个数 小于 y 对应的集合元素个数 - self.fa[root_x] = root_y # 向任意一方合并即可 - self.size[root_y] += self.size[root_x] - - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -### 3.3 按秩合并的注意点 - -看过「按深度合并」和「按大小合并」的实现代码后,大家可能会产生一个疑问:为什么在路径压缩的过程中不用更新 `rank` 值或者 `size` 值呢? - -其实,代码中的 `rank` 值或者 `size` 值并不完全是树中真实的深度或者集合元素个数。 - -这是因为当我们在代码中引入路径压缩之后,维护真实的深度或者集合元素个数就会变得比较难。此时我们使用的 `rank` 值或者 `size` 值更像是用于当前节点排名的一个标志数字,只在合并操作的过程中,用于比较两棵树的权值大小。 - -换句话说,我们完全可以不知道每个节点的具体深度或者集合元素个数,只要能够保证每两个节点之间的深度或者集合元素个数关系可以通过 `rank` 值或者 `size` 值正确的表达即可。 - -而根据路径压缩的过程,`rank` 值或者 `size` 值只会不断的升高,而不可能降低到比原先深度更小的节点或者集合元素个数更少的节点还要小。所以,`rank` 值或者 `size` 值足够用于比较两个节点的权值,进而选择合适的方式进行合并操作。 - -## 4. 并查集的算法分析 - -首先我们来分析一下并查集的空间复杂度。在代码中,我们主要使用了数组 `fa` 来存储集合中的元素。如果使用了「按秩合并」的优化方式,还会使用数组 `rank` 或者数组 `size` 来存放权值。因为空间复杂度取决于元素个数,不难得出空间复杂度为 $O(n)$。 - -在同时使用了「路径压缩」和「按秩合并」的情况下,并查集的合并操作和查找操作的时间复杂度可以接近于 $O(1)$。最坏情况下的时间复杂度是 $O(m * \alpha(n))$。这里的 $m$ 是合并操作和查找操作的次数,$\alpha(n)$ 是 Ackerman 函数的某个反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。 - -总结一下: - -- 并查集的空间复杂度:$O(n)$。 -- 并查集的时间复杂度:$O(m * \alpha(n))$。 - -## 5. 并查集的最终实现代码 - -根据我自己的做题经验和网上大佬的经验,我使用并查集的策略(仅供参考)是这样:使用「隔代压缩」,一般不使用「按秩合并」。 - -这样选择的原因是既能保证代码简单易写,又能得到不错的性能。如果这样写的性能还不够好的话,再考虑使用「按秩合并」。 - -在有些题目中,还会遇到需要查询集合的个数或者集合中元素个数的情况,可以根据题目具体要求再做相应的更改。 - -- 使用「隔代压缩」,不使用「按秩合并」的并查集最终实现代码: - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -- 使用「隔代压缩」,使用「按秩合并」的并查集最终实现代码: - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 - self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 - else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # 向任意一方合并即可 - self.rank[y] += 1 # 因为层数相同,被合并的树必然层数会 +1 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) -``` - -## 6. 并查集的应用 - -并查集通常用来求解不同元素之间的关系问题,比如判断两个人是否是亲戚关系、两个点之间时候存在至少一条路径连接。或者用来求解集合的个数、集合中元素的个数等等。 - -### 6.1 等式方程的可满足性 - -#### 6.1.1 题目链接 - -- [990. 等式方程的可满足性 - 力扣(LeetCode)](https://leetcode.cn/problems/satisfiability-of-equality-equations/) - -#### 6.1.2 题目大意 - -**描述**:给定一个由字符串方程组成的数组 `equations`,每个字符串方程 `equations[i]` 的长度为 `4`,有以下两种形式组成:`a==b` 或 `a!=b`。`a` 和 `b` 是小写字母,表示单字母变量名。 - -**要求**:判断所有的字符串方程是否能同时满足,如果能同时满足,返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le equations.length \le 500$。 -- $equations[i].length == 4$。 -- $equations[i][0]$ 和 $equations[i][3]$ 是小写字母。 -- $equations[i][1]$ 要么是 `'='`,要么是 `'!'`。 -- `equations[i][2]` 是 `'='`。 - -**示例**: - -```python -输入 ["a==b","b!=a"] -输出 False -解释 如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。 -``` - -#### 6.1.3 解题思路 - -字符串方程只有 `==` 或者 `!=`,可以考虑将相等的遍历划分到相同集合中,然后再遍历所有不等式方程,看方程的两个变量是否在之前划分的相同集合中,如果在则说明不满足。 - -这就需要用到并查集,具体操作如下: - -- 遍历所有等式方程,将等式两边的单字母变量顶点进行合并。 -- 遍历所有不等式方程,检查不等式两边的单字母遍历是不是在一个连通分量中,如果在则返回 `False`,否则继续扫描。如果所有不等式检查都没有矛盾,则返回 `True`。 - -#### 6.1.4 代码 - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) - -class Solution: - def equationsPossible(self, equations: List[str]) -> bool: - union_find = UnionFind(26) - for eqation in equations: - if eqation[1] == "=": - index1 = ord(eqation[0]) - 97 - index2 = ord(eqation[3]) - 97 - union_find.union(index1, index2) - - for eqation in equations: - if eqation[1] == "!": - index1 = ord(eqation[0]) - 97 - index2 = ord(eqation[3]) - 97 - if union_find.is_connected(index1, index2): - return False - return True -``` - -### 6.2 省份数量 - -#### 6.2.1 题目链接 - -- [547. 省份数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-provinces/) - -#### 6.2.2 题目大意 - -**描述**:有 `n` 个城市,其中一些彼此相连,另一些没有相连。如果城市 `a` 与城市 `b` 直接相连,且城市 `b` 与城市 `c` 直接相连,那么城市 `a` 与城市 `c` 间接相连。 - -「省份」是由一组直接或间接链接的城市组成,组内不含有其他没有相连的城市。 - -现在给定一个 `n * n` 的矩阵 `isConnected` 表示城市的链接关系。其中 `isConnected[i][j] = 1` 表示第 `i` 个城市和第 `j` 个城市直接相连,`isConnected[i][j] = 0` 表示第 `i` 个城市和第 `j` 个城市没有相连。 - -**要求**:根据给定的城市关系,返回「省份」的数量。 - -**说明**: - -- $1 \le n \le 200$。 -- $n == isConnected.length$。 -- $n == isConnected[i].length$。 -- $isConnected[i][j]$ 为 $1$ 或 $0$。 -- $isConnected[i][i] == 1$。 -- $isConnected[i][j] == isConnected[j][i]$。 - -**示例**: - -- 如图所示: - -![](https://assets.leetcode.com/uploads/2020/12/24/graph1.jpg) - -```python -输入 isConnected = [[1,1,0],[1,1,0],[0,0,1]] -输出 2 -``` - -#### 6.2.3 解题思路 - -具体做法如下: -- 遍历矩阵 `isConnected`。如果 `isConnected[i][j] = 1`,将 `i` 节点和 `j` 节点相连。 -- 然后判断每个城市节点的根节点,然后统计不重复的根节点有多少个,也就是集合个数,即为「省份」的数量。 - -#### 6.2.4 代码 - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) - -class Solution: - def findCircleNum(self, isConnected: List[List[int]]) -> int: - size = len(isConnected) - union_find = UnionFind(size) - for i in range(size): - for j in range(i + 1, size): - if isConnected[i][j] == 1: - union_find.union(i, j) - - res = set() - for i in range(size): - res.add(union_find.find(i)) - return len(res) -``` - -## 参考资料 - -- 【博文】[并查集 - OI Wiki](https://oi-wiki.org/ds/dsu/) -- 【博文】[并查集 - LeetBook - 力扣](https://leetcode.cn/leetbook/detail/disjoint-set/) -- 【博文】[并查集概念及用法分析 - 掘金](https://juejin.cn/post/6844903954774491149) -- 【博文】[数据结构之并查集 - 端碗吹水的技术博客](https://blog.51cto.com/zero01/2609695) -- 【博文】[并查集复杂度 - OI Wiki](https://oi-wiki.org/ds/dsu-complexity/) -- 【题解】[使用并查集处理不相交集合问题(Java、Python) - 等式方程的可满足性 - 力扣](https://leetcode.cn/problems/satisfiability-of-equality-equations/solution/shi-yong-bing-cha-ji-chu-li-bu-xiang-jiao-ji-he-we/) -- 【书籍】算法训练营 - 陈小玉 著 -- 【书籍】算法 第 4 版 - 谢路云 译 -- 【书籍】算法竞赛进阶指南 - 李煜东 著 -- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 \ No newline at end of file diff --git a/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md b/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md deleted file mode 100644 index 0d2d1b75..00000000 --- a/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md +++ /dev/null @@ -1,18 +0,0 @@ -### 并查集题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0990 | [等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0990.%20%E7%AD%89%E5%BC%8F%E6%96%B9%E7%A8%8B%E7%9A%84%E5%8F%AF%E6%BB%A1%E8%B6%B3%E6%80%A7.md) | 并查集、图、数组、字符串 | 中等 | -| 0547 | [省份数量](https://leetcode.cn/problems/number-of-provinces/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0547.%20%E7%9C%81%E4%BB%BD%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0684 | [冗余连接](https://leetcode.cn/problems/redundant-connection/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 1319 | [连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1319.%20%E8%BF%9E%E9%80%9A%E7%BD%91%E7%BB%9C%E7%9A%84%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0765 | [情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0765.%20%E6%83%85%E4%BE%A3%E7%89%B5%E6%89%8B.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | -| 0399 | [除法求值](https://leetcode.cn/problems/evaluate-division/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0399.%20%E9%99%A4%E6%B3%95%E6%B1%82%E5%80%BC.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 | -| 0959 | [由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0959.%20%E7%94%B1%E6%96%9C%E6%9D%A0%E5%88%92%E5%88%86%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | -| 1202 | [交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1202.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0.md) | 深度优先搜索、广度优先搜索、并查集、哈希表、字符串 | 中等 | -| 0947 | [移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0947.%20%E7%A7%BB%E9%99%A4%E6%9C%80%E5%A4%9A%E7%9A%84%E5%90%8C%E8%A1%8C%E6%88%96%E5%90%8C%E5%88%97%E7%9F%B3%E5%A4%B4.md) | 深度优先搜索、并查集、图 | 中等 | -| 0803 | [打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0803.%20%E6%89%93%E7%A0%96%E5%9D%97.md) | 并查集、数组、矩阵 | 困难 | -| 0128 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0128.%20%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97.md) | 并查集、数组、哈希表 | 中等 | - diff --git a/Contents/07.Tree/05.Union-Find/index.md b/Contents/07.Tree/05.Union-Find/index.md deleted file mode 100644 index 9eba76ed..00000000 --- a/Contents/07.Tree/05.Union-Find/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [并查集知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/01.Union-Find.md) -- [并查集题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) \ No newline at end of file diff --git a/Contents/07.Tree/index.md b/Contents/07.Tree/index.md deleted file mode 100644 index ae371747..00000000 --- a/Contents/07.Tree/index.md +++ /dev/null @@ -1,29 +0,0 @@ -## 本章内容 - -### 二叉树 - -- [树与二叉树基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md) -- [二叉树的遍历知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md) -- [二叉树的遍历题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) -- [二叉树的还原知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md) -- [二叉树的还原题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) - -### 二叉搜索树 - -- [二叉搜索树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md) -- [二叉搜索树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) - -### 线段树 - -- [线段树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md) -- [线段树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) - -### 树状数组 - -- [树状数组知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md) -- [树状数组题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) - -### 并查集 - -- [并查集知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/01.Union-Find.md) -- [并查集题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) \ No newline at end of file diff --git a/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md b/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md deleted file mode 100644 index 7cd2bae7..00000000 --- a/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md +++ /dev/null @@ -1,139 +0,0 @@ -## 1. 图的定义 - -> **图(Graph)**:由顶点的非空有限集合 $V$ (由 $n > 0$ 个顶点组成)与边的集合 $E$(顶点之间的关系)构成的结构。其形式化定义为 $G = (V, E)$。 - -- **顶点(Vertex)**:图中的数据元素通常称为顶点,在下面的示意图中我们使用圆圈来表示顶点。 -- **边(Edge)**:图中两个数据元素之间的关联关系通常称为边,在下面的示意图中我们使用连接两个顶点之间的线段来表示边。边的形式化定义为:$e = \langle u, v \rangle$,表示从 $u$ 到 $v$ 的一条边,其中 $u$ 称为起始点,$v$ 称为终止点。 - -![](https://qcdn.itcharge.cn/images/20220307145142.png) - -- **子图(Sub Graph)**:对于图 $G = (V, E)$ 与 $G^{'} = (V^{'}, E^{'})$,如果存在 $V^{'} \subseteq V$,$E^{'} \subseteq E$,则称图 $G^{'}$ 是图 $G$ 的一个子图。在下面的示意图中我们给出了一个图 $G$ 及其一个子图 $G^{'}$。特别的,根据定义,$G$ 也是其自身的子图。 - -![](https://qcdn.itcharge.cn/images/20220317163120.png) - -## 2. 图的分类 - -### 2.1 无向图和有向图 - -按照边是否有方向,我们可以将图分为两种类型:「无向图」和「有向图」。 - -- **无向图(Undirected Graph)**:如果图中的每条边都没有指向性,则称为无向图。例如朋友关系图、路线图都是无向图。 -- **有向图(Directed Graph)**:如果图中的每条边都具有指向性,则称为有向图。例如流程图是有向图。 - -在无向图中,每条边都是由两个顶点组成的无序对。例如下图左侧中的顶点 $v_1$ 和顶点 $v_2$ 之间的边记为 $(v_1, v_2)$ 或 $(v_2, v_1)$。 - -在有向图中,有向边也被称为弧,每条弧是由两个顶点组成的有序对,例如下图右侧中从顶点 $v_1$ 到顶点 $v_2$ 的弧,记为 $\langle v_1, v_2 \rangle$,$v_1$ 被称为弧尾,$v_2$ 被称为弧头,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220307160017.png) - -如果无向图中有 $n$ 个顶点,则无向图中最多有 $n \times (n - 1) / 2$ 条边。而具有 $n \times (n - 1) / 2$ 条边的无向图称为 **「完全无向图(Completed Undirected Graph)」**。 - -如果有向图中有 $n$ 个顶点,则有向图中最多有 $n \times (n - 1)$ 条弧。而具有 $n \times (n - 1)$ 条弧的有向图称为 **「完全有向图(Completed Directed Graph)」**。 - -如下图所示,左侧为包含 $4$ 个顶点的完全无向图,右侧为包含 $4$ 个顶点的完全有向图。 - -![](https://qcdn.itcharge.cn/images/20220308151436.png) - -下面介绍一下无向图和有向图中一个重要概念 **「顶点的度」**。 - -- **顶点的度**:与该顶点 $v_i$ 相关联的边的条数,记为 $TD(v_i)$。 - -例如上图左侧的完全无向图中,顶点 $v_3$ 的度为 $3$。 - -而对于有向图,我们可以将顶点的度分为 **「顶点的出度」** 和 **「顶点的入度」**。 - -- **顶点的出度**:以该顶点 $v_i$ 为出发点的边的条数,记为 $OD(v_i)$。 -- **顶点的入度**:以该顶点 $v_i$ 为终止点的边的条数,记为 $ID(v_i)$。 -- 有向图中某顶点的度 = 该顶点的出度 + 该顶点的入度,即 $TD(v_i) = OD(v_i) + ID(v_i)$。 - -例如上图右侧的完全有向图中,顶点 $v_3$ 的出度为 $3$,入度为 $3$,顶点 $v_3$ 的度为 $3 + 3 = 6$。 - -### 2.2 环形图和无环图 - - **「路径」** 是图中的一个重要概念,对于图 $G = (V, E)$,如果存在顶点序列 $v_{i_0}, v_{i_1}, v_{i_2},… , v_{i_m}$,使得 $(v_{i_0}, v_{i_1}), (v_{i_1}, v_{i_2}), …, (v_{i_{m-1}}, v_{i_m}) \in E$(即他们都是图 G 的边,对于有向图则是 $\langle v_{i_0}, v_{i_1} \rangle, \langle v_{i_1}, v_{i_2} \rangle, …, \langle v_{i_{m-1}}, v_{i_m} \rangle \in E$),则称该顶点序列为顶点 $v_{i_0}$ 和顶点 $v_{i_m}$ 之间的一条路径,其中 $v_{i_0}$ 是这条路径的起始点,$v_{i_m}$ 是这条路径的终止点。 - -简单来说,如果顶点 $v_{i_0}$ 可以通过一系列的顶点和边,到达顶点 $v_{i_m}$,则称顶点 $v_{i_0}$ 和顶点 $v_{i_m}$ 之间有一条路径,其中经过的顶点序列则称为两个顶点之间的路径。 - -- **环(Circle)**:如果一条路径的起始点和终止点相同(即 $v_{i_0} == v_{i_m}$ ),则称这条路径为「回路」或者「环」。 - -- **简单路径**:顶点序列中顶点不重复出现的路径称为「简单路径」。 - -而根据图中是否有环,我们可以将图分为「环形图」和「无环图」。 - -- **环形图(Circular Graph)**:如果图中存在至少一条环路,则该图称为「环形图」。 -- **无环图(Acyclic Graph)**:如果图中不存在环路,则该图称为「无环图」。 - -特别的,在有向图中,如果不存在环路,则将该图称为「有向无环图(Directed Acyclic Graph)」,缩写为 DAG。因为有向无环图拥有为独特的拓扑结构,经常被用于处理动态规划、导航中寻求最短路径、数据压缩等多种算法场景。 - -如下图所示,分别为:无向无环图、无向环形图、有向无环图和有向环形图。其中有向环形图中的顶点 $v_1$、$v_2$、$v_3$ 与相连的边构成了一个环。 - -![环形图和无环图](https://qcdn.itcharge.cn/images/20220317115641.png) - -### 2.3 连通图和非连通图 - -#### 2.3.1 连通无向图和连通分量 - -在无向图中,如果从顶点 $v_i$ 到顶点 $v_j$ 有路径,则称顶点 $v_i$ 和 $v_j$ 是连通的。 - -- **连通无向图**:在无向图中,如果图中任意两个顶点之间都是连通的,则称该图为连通无向图。 -- **非连通无向图**:在无向图中,如果图中至少存在一对顶点之间不存在任何路径,则该图称为非连通无向图。 - -如下图所示,左侧图中 $v_1$ 与 $v_2$、$v_3$、$v_4$、$v_5$、$v_6$ 都是连通的,所以该图为连通无向图。右侧图中 $v_1$ 与 $v_2$、$v_3$、$v_4$ 都是连通的,但是 $v_1$ 和 $v_5$、$v_6$ 之间不存在任何路径,则该图为非连通无向图。 - -![](https://qcdn.itcharge.cn/images/20220317163249.png) - -下面介绍一下无向图的「连通分量」概念。有些无向图可能不是连通无向图,但是其子图可能是连通的。这些子图称为原图的连通子图。而无向图的一个极大连通子图(不存在包含它的更大的连通子图)则称为该图的「连通分量」。 - -- **连通子图**:如果无向图的子图是连通无向图,则该子图称为原图的连通子图。 -- **连通分量**:无向图中的一个极大连通子图(不存在包含它的更大的连通子图)称为该图的连通分量。 -- **极⼤连通⼦图**:无向图中的一个连通子图,并且不存在包含它的更大的连通子图。 - -例如上图中右侧的非连通无向图,其本身是非连通的。但顶点 $v_1$、$v_2$、$v_3$、$v_4$ 与其相连的边构成的子图是连通的,并且不存在包含它的更大的连通子图了,所以该子图是原图的一个连通分量。同理,顶点 $v_5$、$v_6$ 与其相连的边构成的子图也是原图的一个连通分量。 - -#### 2.3.2 强连通有向图和强连通分量 - -在有向图中,如果从顶点 $v_i$ 到 $v_j$ 有路径,并且从顶点 $v_j$ 到 $v_i$ 也有路径,则称顶点 $v_i$ 与 $v_j$ 是连通的。 - -- **强连通有向图**:如果图中任意两个顶点 $v_i$ 和 $v_j$,从 $v_i$ 到 $v_j$ 和从 $v_j$ 到 $v_i$ 都有路径,则称该图为强连通有向图。 -- **非强连通有向图**:如果图中至少存在一对顶点之间不存在任何路径,则该图称为非强连通有向图。 - -如下图所示,左侧图中任意两个顶点之间都有路径,则左侧图为强连通有向图。右侧图中顶点 $v_7$ 无法通过路径到达其他顶点,则右侧图为非强连通有向图。 - -![](https://qcdn.itcharge.cn/images/20220317133500.png) - -与无向图类似,有向图的一个极大强连通子图称为该图的 **强连通分量**。 - -- **强连通子图**:如果有向图的子图是连通有向图,则该子图称为原图的强连通子图。 -- **强连通分量**:有向图中的一个极⼤强连通⼦图,称为该图的强连通分量。 -- **极⼤强连通⼦图**:有向图中的一个强连通子图,并且不存在包含它的更大的强连通子图。 - -例如上图中,右侧的非强连通有向图,其本身不是强连通的(顶点 $v_7$ 无法通过路径到达其他顶点)。但顶点 $v_1$、$v_2$、$v_3$、$v_4$、$v_5$、$v_6$ 与其相连的边构成的子图(即上图的左侧图)是强连通的,并且不存在包含它的更大的强连通子图了,所以该子图是原图的一个强连通分量(即上图中的左侧图是右侧图的强连通分量)。同理,顶点 $v_7$ 构成的子图也是原图的一个强连通分量。 - -### 2.4 带权图 - -有时,图不仅需要表示顶点之间是否存在某种关系,还需要表示这一关系的具体细节。这时候我们需要在边上带一些数据信息,这些数据信息被称为 **权**。在具体应用中,权值可以具有某种具体意义,比如权值可以代表距离、时间以及价格等不同属性。 - -- **带权图**:如果图的每条边都被赋以⼀个权值,这种图称为带权图。 -- **网络**:带权的连通⽆向图称为⽹络。 - -在下面的示意图中,我们给出了一个带权图的例子。 - -![](https://qcdn.itcharge.cn/images/20220317135207.png) - -### 2.5 稠密图和稀疏图 - -根据图中边的稀疏程度,我们可以将图分为「稠密图」和「稀疏图」。这是一个模糊的概念,目前为止还没有给出一个量化的定义。 - -- **稠密图(Dense Graph)**:有很多条边或弧(边的条数 $e$ 接近于完全图的边数)的图称为稠密图。 -- **稀疏图(Sparse Graph)**:有很少条边或弧(边的条数 $e$ 远小于完全图的边数,如 $e < n \times \log_2n$)的图称为稀疏图。 - -## 参考资料 - -- 【书籍】ACM-ICPC 程序设计系列 - 图论及应用 \- 陈宇 吴昊 主编 -- 【书籍】数据结构教程 第 3 版 - 唐发根 著 -- 【书籍】大话数据结构 - 程杰 著 -- 【书籍】算法训练营 - 陈小玉 著 -- 【书籍】Python 数据结构与算法分析 第 2 版 - 布拉德利·米勒 戴维·拉努姆 著 -- 【博文】[图的基础知识 | 小浩算法](https://www.geekxh.com/1.99.其他补充题目/50.html) -- 【博文】[链式前向星及其简单应用 | Malash's Blog](https://malash.me/200910/linked-forward-star/) - diff --git a/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md b/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md deleted file mode 100644 index 48271f9b..00000000 --- a/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md +++ /dev/null @@ -1,513 +0,0 @@ -## 1. 图的存储结构 - -图的结构比较复杂,我们需要表示顶点和边。一个图可能有任意多个(有限个)顶点,而且任何两个顶点之间都可能存在边。我们在实现图的存储时,重点需要关注边与顶点之间的关联关系,这是图的存储的关键。 - -图的存储可以通过「顺序存储结构」和「链式存储结构」来实现。其中顺序存储结构包括邻接矩阵和边集数组。链式存储结构包括邻接表、链式前向星、十字链表和邻接多重表。 - -接下来我们来介绍几个常用的图的存储结构。在下文中,我们约定用 $n$ 代表顶点数目,$m$ 代表边数目,$TD(v_i)$ 表示顶点 $v_i$ 的度。 - -### 1.1 邻接矩阵 - -#### 1.1.1 邻接矩阵的原理描述 - -> **邻接矩阵(Adjacency Matrix)**:使用一个二维数组 $adj\underline{}matrix$ 来存储顶点之间的邻接关系。 -> -> - 对于无权图来说,如果 $adj\underline{}matrix[i][j]$ 为 $1$,则说明顶点 $v_i$ 到 $v_j$ 存在边,如果 $adj\underline{}matrix[i][j]$ 为 $0$,则说明顶点 $v_i$ 到 $v_j$ 不存在边。 -> - 对于带权图来说,如果 $adj\underline{}matrix[i][j]$ 为 $w$,并且 $w \ne \infty$(即 `w != float('inf')`),则说明顶点 $v_i$ 到 $v_j$ 的权值为 $w$。如果 $adj\underline{}matrix[i][j]$ 为 $\infty$(即 `float('inf')`),则说明顶点 $v_i$ 到 $v_j$ 不存在边。 - -在下面的示意图中,左侧是一个无向图,右侧则是该无向图对应的邻接矩阵结构。 - -![](https://qcdn.itcharge.cn/images/20220317144826.png) - -邻接矩阵的特点: - -- 优点:实现简单,并且可以直接查询顶点 $v_i$ 与 $v_j$ 之间是否有边存在,还可以直接查询边的权值。 -- 缺点:初始化效率和遍历效率较低,空间开销大,空间利用率低,并且不能存储重复边,也不便于增删节点。如果当顶点数目过大(比如当 $n > 10^5$)时,使用邻接矩阵建立一个 $n \times n$ 的二维数组不太现实。 - -#### 1.1.2 邻接矩阵的算法分析 - -- **时间复杂度**: - - **初始化操作**:$O(n^2)$。 - - **查询、添加或删除边操作**:$O(1)$。 - - **获取某个点的所有边操作**:$O(n)$。 - - **图的遍历操作** :$O(n^2)$。 - -- **空间复杂度**:$O(n^2)$。 - -#### 1.1.3 邻接矩阵的代码实现 - -```python -class Graph: # 基本图类,采用邻接矩阵表示 - # 图的初始化操作,ver_count 为顶点个数 - def __init__(self, ver_count): - self.ver_count = ver_count # 顶点个数 - self.adj_matrix = [[None for _ in range(ver_count)] for _ in range(ver_count)] # 邻接矩阵 - - # 判断顶点 v 是否有效 - def __valid(self, v): - return 0 <= v <= self.ver_count - - # 图的创建操作,edges 为边信息 - def creatGraph(self, edges=[]): - for vi, vj, val in edges: - self.add_edge(vi, vj, val) - - # 向图的邻接矩阵中添加边:vi - vj,权值为 val - def add_edge(self, vi, vj, val): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - self.adj_matrix[vi][vj] = val - - # 获取 vi - vj 边的权值 - def get_edge(self, vi, vj): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - return self.adj_matrix[vi][vj] - - # 根据邻接矩阵打印图的边 - def printGraph(self): - for vi in range(self.ver_count): - for vj in range(self.ver_count): - val = self.get_edge(vi, vj) - if val: - print(str(vi) + ' - ' + str(vj) + ' : ' + str(val)) - - -graph = Graph(5) -edges = [[1, 2, 5],[2, 1, 5],[1, 3, 30],[3, 1, 30],[2, 3, 14],[3, 2, 14],[2, 4, 26], [4, 2, 26]] -graph.creatGraph(edges) -print(graph.get_edge(3, 4)) -graph.printGraph() -``` - -### 1.2 边集数组 - -#### 1.2.1 边集数组的原理描述 - -> **边集数组(Edgeset Array)**:使用一个数组来存储存储顶点之间的邻接关系。数组中每个元素都包含一条边的起点 $v_i$、终点 $v_j$ 和边的权值 $val$(如果是带权图)。 - -在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的边集数组结构。 - -![](https://qcdn.itcharge.cn/images/20220317161454.png) - -#### 1.2.2 边集数组的算法分析 - -边集数组的时间复杂度: - -- 图的初始化和创建操作:$O(m)$。 -- 查询是否存在某条边:$O(m)$。 -- 遍历某个点的所有边:$O(m)$。 -- 遍历整张图:$O(nm)$。 - -边集数组的空间复杂度: - -- 空间复杂度:$O(m)$。 - -采用边集数组计算节点的度或者查找某条边时,需要遍历整个边集数组,时间复杂度为 $O(m)$,`m` 是边的数量。除非特殊必要,很少用使用边集数组来存储图。 - -一般来说,边集数组适合那些对边依次进行处理的运算,不适合对顶点的运算和对任何一条边的运算。 - -#### 1.2.3 边集数组的代码实现 - -```python -class EdgeNode: # 边信息类 - def __init__(self, vi, vj, val): - self.vi = vi # 边的起点 - self.vj = vj # 边的终点 - self.val = val # 边的权值 - -class Graph: # 基本图类,采用边集数组表示 - def __init__(self): - self.edges = [] # 边数组 - - # 图的创建操作,edges 为边信息 - def creatGraph(self, edges=[]): - for vi, vj, val in edges: - self.add_edge(vi, vj, val) - - # 向图的边数组中添加边:vi - vj,权值为 val - def add_edge(self, vi, vj, val): - edge = EdgeNode(vi, vj, val) # 创建边节点 - self.edges.append(edge) # 将边节点添加到边数组中 - - # 获取 vi - vj 边的权值 - def get_edge(self, vi, vj): - for edge in self.edges: - if vi == edge.vi and vj == edge.vj: - val = edge.val - return val - return None - - # 根据边数组打印图 - def printGraph(self): - for edge in self.edges: - print(str(edge.vi) + ' - ' + str(edge.vj) + ' : ' + str(edge.val)) - -graph = Graph() -edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] -graph.creatGraph(edges) -print(graph.get_edge(3, 4)) -graph.printGraph() -``` - -### 1.3 邻接表 - -#### 1.3.1 邻接表的原理描述 - -> **邻接表(Adjacency List)**:使用顺序存储和链式存储相结合的存储结构来存储图的顶点和边。其数据结构包括两个部分,其中一个部分是数组,主要用来存放顶点的数据信息,另一个部分是链表,用来存放边信息。 - -在邻接表的存储方法中,对于对图中每个顶点 $v_i$ 建立一个线性链表,把所有邻接于 $v_i$ 的顶点链接到单链表上。这样对于具有 `n` 个顶点的图而言,其邻接表结构由 `n` 个线性链表组成。 - -然后我们在每个顶点前边设置一个表头节点,称之为「顶点节点」。每个顶点节点由「顶点域」和「指针域」组成。其中顶点域用于存放某个顶点的数据信息,指针域用于指出该顶点第 `1` 条边所对应的链节点。 - -为了方便随机访问任意顶点的链表,通常我们会使用一组顺序存储结构(数组)存储所有「顶点节点」部分,顺序存储结构(数组)的下标表示该顶点在图中的位置。 - -在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的邻接表结构。 - -![](https://qcdn.itcharge.cn/images/20220317154531.png) - -#### 1.3.2 邻接表的算法分析 - -邻接表的时间复杂度: - -- 图的初始化和创建操作:$O(n + m)$。 -- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(TD(v_i))$。 -- 遍历某个点的所有边:$O(TD(v_i))$。 -- 遍历整张图:$O(n + m)$。 - -邻接表的空间复杂度: - -- 空间复杂度:$O(n + m)$。 - -#### 1.3.3 邻接表的代码实现 - -```python -class EdgeNode: # 边信息类 - def __init__(self, vj, val): - self.vj = vj # 边的终点 - self.val = val # 边的权值 - self.next = None # 下一条边 - -class VertexNode: # 顶点信息类 - def __init__(self, vi): - self.vi = vi # 边的起点 - self.head = None # 下一个邻接点 - -class Graph: - def __init__(self, ver_count): - self.ver_count = ver_count - self.vertices = [] - for vi in range(ver_count): - vertex = VertexNode(vi) - self.vertices.append(vertex) - - # 判断顶点 v 是否有效 - def __valid(self, v): - return 0 <= v <= self.ver_count - - # 图的创建操作,edges 为边信息 - def creatGraph(self, edges=[]): - for vi, vj, val in edges: - self.add_edge(vi, vj, val) - - # 向图的邻接表中添加边:vi - vj,权值为 val - def add_edge(self, vi, vj, val): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - vertex = self.vertices[vi] - edge = EdgeNode(vj, val) - edge.next = vertex.head - vertex.head = edge - - # 获取 vi - vj 边的权值 - def get_edge(self, vi, vj): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - vertex = self.vertices[vi] - cur_edge = vertex.head - while cur_edge: - if cur_edge.vj == vj: - return cur_edge.val - cur_edge = cur_edge.next - return None - - # 根据邻接表打印图的边 - def printGraph(self): - for vertex in self.vertices: - cur_edge = vertex.head - while cur_edge: - print(str(vertex.vi) + ' - ' + str(cur_edge.vj) + ' : ' + str(cur_edge.val)) - cur_edge = cur_edge.next - -graph = Graph(7) -edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] -graph.creatGraph(edges) -print(graph.get_edge(3, 4)) -graph.printGraph() -``` - -### 1.4 链式前向星 - -#### 1.4.1 链式前向星的原理描述 - -> **链式前向星(Linked Forward Star)**:也叫做静态邻接表,实质上就是使用静态链表实现的邻接表。链式前向星将边集数组和邻接表相结合,可以快速访问一个节点所有的邻接点,并且使用很少的额外空间。 - -链式前向星采用了一种静态链表的存储方式,可以说是目前建图和遍历效率最高的存储方式。 - -链式前向星由两种数据结构组成: - -- **特殊的边集数组**:`edges`,其中 `edges[i]` 表示第 `i` 条边。`edges[i].vj` 表示第 `i` 条边的终止点,`edges[i].val` 表示第 `i` 条边的权值,`edges[i].next` 表示与第 `i` 条边同起始点的下一条边的存储位置。 -- **头节点数组**:`head`,其中 `head[i]` 存储以顶点 `i` 为起始点的第 `1` 条边在数组 `edges` 中的下标。 - -链式前向星其实并没有改变边集数组原来的存储数学,只是利用 `head` 数组构成静态链表,建立了顶点 $v_i$ 和顶点 $v_i$ 所连第 `1` 条边的关系。 - -在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的链式前向星结构。 - -如果需要在该图中遍历顶点 $v_1$ 的所有边,则步骤如下: - -- 找到以顶点 $v_1$ 为起始点的的 `1` 条边在数组 `edges` 中的下标,即 `index = head[1] = 1 `。则在 `edges` 数组中找到与顶点 $v_1$ 相连的第 `1` 条边为 `edges[1]`,即 $\langle v_1, v_5 \rangle$,权值为 6。 -- 查找 `index = self.edges[1].next = 0 `,则在 `edges` 数组中找到与顶点 $v_1$ 相连的第 `2` 条边 `edges[0]`,即 $\langle v_1, v_2 \rangle$,权值为 5。 -- 继续查找 `index = self.edges[0].next = -1`,则不存在其余边,查找结束。 - -![](https://qcdn.itcharge.cn/images/20220317161217.png) - -#### 1.4.2 链式前向星的算法分析 - -链式前向星的时间复杂度: - -- 图的初始化和创建操作:$O(n + m)$。 -- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(TD(v_i))$。 -- 遍历某个点的所有边:$O(TD(v_i))$。 -- 遍历整张图:$O(n + m)$。 - -链式前向星的空间复杂度: - -- 空间复杂度:$O(n + m)$。 - -#### 1.4.3 链式前向星的代码实现 - -```python -class EdgeNode: # 边信息类 - def __init__(self, vj, val): - self.vj = vj # 边的终点 - self.val = val # 边的权值 - self.next = None # 下一条边 - -class Graph: - def __init__(self, ver_count, edge_count): - self.ver_count = ver_count # 顶点个数 - self.edge_count = edge_count # 边个数 - self.head = [-1 for _ in range(ver_count)] # 头节点数组 - self.edges = [] # 边集数组 - - # 判断顶点 v 是否有效 - def __valid(self, v): - return 0 <= v <= self.ver_count - - # 图的创建操作,edges 为边信息 - def creatGraph(self, edges=[]): - for i in range(len(edges)): - vi, vj, val = edges[i] - self.add_edge(i, vi, vj, val) - - # 向图的边集数组中添加边:vi - vj,权值为 val - def add_edge(self, index, vi, vj, val): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - edge = EdgeNode(vj, val) # 构造边节点 - edge.next = self.head[vi] # 边节点的 next 指向原来首指针 - self.edges.append(edge) # 边集数组添加该边 - self.head[vi] = index # 首指针指向新加边所在边集数组的下标 - - # 获取 vi - vj 边的权值 - def get_edge(self, vi, vj): - if not self.__valid(vi) or not self.__valid(vj): - raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") - - index = self.head[vi] # 得到顶点 vi 相连的第一条边在边集数组的下标 - while index != -1: # index == -1 时说明 vi 相连的边遍历完了 - if vj == self.edges[index].vj: # 找到了 vi - vj 边 - return self.edges[index].val # 返回 vi - vj 边的权值 - index = self.edges[index].next # 取顶点 vi 相连的下一条边在边集数组的下标 - return None # 没有找到 vi - vj 边 - - # 根据链式前向星打印图的边 - def printGraph(self): - for vi in range(self.ver_count): # 遍历顶点 vi - index = self.head[vi] # 得到顶点 vi 相连的第一条边在边集数组的下标 - while index != -1: # index == -1 时说明 vi 相连的边遍历完了 - print(str(vi) + ' - ' + str(self.edges[index].vj) + ' : ' + str(self.edges[index].val)) - index = self.edges[index].next # 取顶点 vi 相连的下一条边在边集数组的下标 - - -graph = Graph(7, 7) -edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] -graph.creatGraph(edges) -print(graph.get_edge(4, 3)) -print(graph.get_edge(4, 5)) -graph.printGraph() -``` - -### 1.5 哈希表实现邻接表 - -#### 1.5.1 哈希表实现邻接表的原理描述 - -在 Python 中,通过哈希表(字典)可以轻松的实现邻接表。哈希表实现邻接表包含两个哈希表:第一个哈希表主要用来存放顶点的数据信息,哈希表的键是顶点,值是该点所有邻接边构成的另一个哈希表。另一个哈希表用来存放顶点相连的边信息,哈希表的键是边的终点,值是边的权重。 - -#### 1.5.2 哈希表实现邻接表的算法分析 - -哈希表实现邻接表的时间复杂度: - -- 图的初始化和创建操作:$O(n + m)$。 -- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(1)$。 -- 遍历某个点的所有边:$O(TD(v_i))$。 -- 遍历整张图:$O(n + m)$。 - -哈希表实现邻接表的空间复杂度: - -- 空间复杂度:$O(n + m)$。 - -#### 1.5.3 哈希表实现邻接表的代码实现 - -```python -class VertexNode: # 顶点信息类 - def __init__(self, vi): - self.vi = vi # 顶点 - self.adj_edges = dict() # 顶点的邻接边 - -class Graph: - def __init__(self): - self.vertices = dict() # 顶点 - - # 图的创建操作,edges 为边信息 - def creatGraph(self, edges=[]): - for vi, vj, val in edges: - self.add_edge(vi, vj, val) - - # 向图中添加节点 - def add_vertex(self, vi): - vertex = VertexNode(vi) - self.vertices[vi] = vertex - - # 向图的邻接表中添加边:vi - vj,权值为 val - def add_edge(self, vi, vj, val): - if vi not in self.vertices: - self.add_vertex(vi) - if vj not in self.vertices: - self.add_vertex(vj) - - self.vertices[vi].adj_edges[vj] = val - - # 获取 vi - vj 边的权值 - def get_edge(self, vi, vj): - if vi in self.vertices and vj in self.vertices[vi].adj_edges: - return self.vertices[vi].adj_edges[vj] - return None - - # 根据邻接表打印图的边 - def printGraph(self): - for vi in self.vertices: - for vj in self.vertices[vi].adj_edges: - print(str(vi) + ' - ' + str(vj) + ' : ' + str(self.vertices[vi].adj_edges[vj])) - - -graph = Graph() -edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] -graph.creatGraph(edges) -print(graph.get_edge(3, 4)) -graph.printGraph() -``` - -## 2. 图论问题应用 - -图论和图论算法在计算机科学中扮演这很重要的角色,它提供了对很多问题都有效的一种简单而系统的建模方式。很多实际问题都可以转化为图论问题,然后使用图论的景点算法加以解决。例如: - -- 集成电路的设计和布线。 -- 互联网和路由移动电话网的路由设计。 -- 工程项目的计划安排问题。 - -常见的图论问题应用大概可以分为以下几类:**图的遍历问题**、**图的连通性问题**、**图的生成树问题**、**图的最短路径问题**、**图的网络流问题**、**二分图问题** 等等。 - -### 2.1 图的遍历问题 - -> **图的遍历**:与树的遍历类似,图的遍历指的是从图的某一个顶点出发,按照某种搜索方式对图中的所有节点都仅访问一次。 - -图的遍历是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。 - -根据搜索方式的不同,可以将图的遍历分为「深度优先搜索」和「广度优先搜索」。 - -- **深度优先搜索**:从某一顶点出发,沿着⼀条路径⼀直搜索下去,在⽆法搜索时,回退到刚刚访问过的节点。 -- **广度优先搜索**:从某个顶点出发,⼀次性访问所有未被访问的邻接点,再依次从这些已访问过的邻接点出发,⼀层⼀层地访问。 - -### 2.2 图的连通性问题 - -我们在「2.3 连通图和非连通图」中提到过「2.3.1 连通无向图和连通分量」和「2.3.2 强连通有向图和强连通分量」。 - -在无向图中,图的连通性问题主要包括:**求无向图的连通分量**、**求点双连通分量(找割点)**、**求边双连通分量(找桥)**、**全局最小割问题** 等等。 - -在有向图中,图的连通性问题主要包括:**求有向图的强连通分量**、**最小点基**、**最小权点基**、**2-SAT 问题** 等等。 - -### 2.3 图的生成树问题 - -> **图的生成树(Spanning Tree)**:如果连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。 - -图的生成树问题主要包括:**最小生成树问题**、**次小生成树问题** 和 **有向图的最小树形图问题** 等等。 - -- **无向图的最小生成树**:如果连通图 $G$ 是一个带权无向图,则生成树的边也带权,则称该带权图中所有带权生成树中权值总和最小的生成树为最小生成树(也称为最小代价生成树)。 -- **无向图的次小生成树**:如果连通图 $G$ 是一个带权无向图,生成树 $T$ 是图 $G$ 的一个最小生成树,如果有另一棵生成树 $T_1$,$T_1 \ne T$,满足不存在树 $T^{'}$,$T^{'} \ne T$,且 $w(T^{'}) < W(T_1)$,则称 $T_1$ 是图 $G$ 的次小生成树。 -- **有向图的最小树形图**:如果连通图 $G$ 是一个带权有向图,以顶点 $v_i$ 为根节点的生成树 $T$ 中,顶点 $v_i$ 到任意非 $v_i$ 顶点的路径存在且唯一,并且生成树 $T$ 中权值总和最小,则该生成树被称为有向图 $G$ 的最小树形图。 - -### 2.4 图的最短路径问题 - -> **图的最短路径问题**:如果用带权图来表示真实的交通、物流或社交网络,则边的权重可能代表交通运输费、距离或者熟悉程度。此时我们会考虑两个不同顶点之间的最短路径有多长,这一类问题统称为最短路径。并且我们称路径上的第一个顶点为源点,最后一个顶点为终点。 - -按照源点数目的不同,可以将图的最短路径问题分为 **单源最短路径问题** 和 **多源最短路径问题**。 - -- **单源最短路径问题**:从一个顶点出发到图中其余各个顶点之间的最短路径问题。 -- **多源最短路径问题**:图中任意两点之间的最短路径问题。 - -**单元最短路径问题** 的求解还是 **差分约束系统问题** 的基础。 - -除此之外,在实际应用中,有时候除了需要知道最短路径外,还需要知道次最短路径或者第三最短路径。这样的多条最短路径问题称为 **`k` 最短路径问题**。 - -### 2.5 图的网络流问题 - -> **图的网络流**:这里的「网络」指的是:带权的连通有向图。该有向图中的每条边都有一个权值(也称为容量值),当顶点之间不存在边时,两点之间的容量为 0。并且该有向图中有两个特殊的顶点:源点 $s$ 和汇点 $t$。 -> -> 这里的「流」指的是:网络上的流。如果把网络想象成一个自来水管道网络,那么流就是其中流动的水。每条边的方向表示允许的流向,边上的权值表示这条边允许通过的最大流量,也就是说每条边上的流都不能超过它的容量。并且对于除了源点 $s$ 和汇点 $t$ 外的所有点(即中继点),流入的流量都等于流出的流量。 - -图的网络流中最常见的问题就是 **网络最大流问题**。其次还有 **网络最小费用最大流问题**、**网络最小割问题**。 - -- **网络最大流**:给定一个网络,要求计算从源点流向汇点的最大流量(可以有很多条路到达汇点)。 -- **网络最小费用最大流**:给定一个网络,并且每条边都有一个费用,代表单位流量流过这条边的开销。要求计算出最大流的同时,要求花费的费用最小。 -- **网络最小割**:割是删边的意思。给定一个网络,删掉其中 $x$ 条边,从而使原本连通的网络变得不连通,要求计算出 $x$ 条边加起来最小的流量总和是多少。 - -### 2.6 二分图问题 - -> **二分图**:设 $G = (V, E)$ 是一个无向图,如果顶点 $V$ 可以分为两个互不相交的子集 $(A, B)$,并且图中每条边 $(u, v)$ 所关联的两个顶点 $u$ 和 $v$ 分别属于这两个不同的顶点集(即 $u \in A, v \in B$),则称图 $G$ 是一个二分图。 - -二分图中的常见问题有:**二分图最大匹配问题**、**二分图最大权匹配问题**、**二分图多重匹配问题**。 - -先来介绍一下匹配的概念:在二分图中,一个匹配就是一个边的集合,其中任意两条边之间都没有公共节点。 - -- **二分图最大匹配**:在一个二分图的所有匹配中,边数最多的匹配叫做该二分图的最大匹配。 -- **二分图最大权匹配**:在一个二分图的所有匹配中,边的权值和最大的匹配叫做该二分图的最大权匹配。 -- **二分图多重匹配**:在二分图最大匹配问题中,每个点最多只能和一条匹配边相关联。但是在二分图多重匹配中,每个点可以多次匹配但是有匹配上限。 - - -## 参考资料 - -- 【书籍】ACM-ICPC 程序设计系列 - 图论及应用 \- 陈宇 吴昊 主编 -- 【书籍】数据结构教程 第 3 版 - 唐发根 著 -- 【书籍】大话数据结构 - 程杰 著 -- 【书籍】算法训练营 - 陈小玉 著 -- 【书籍】Python 数据结构与算法分析 第 2 版 - 布拉德利·米勒 戴维·拉努姆 著 -- 【博文】[图的基础知识 | 小浩算法](https://www.geekxh.com/1.99.其他补充题目/50.html) -- 【博文】[链式前向星及其简单应用 | Malash's Blog](https://malash.me/200910/linked-forward-star/) -- 【博文】[图论部分简介 - OI Wiki](https://oi-wiki.org/graph/) - diff --git a/Contents/08.Graph/01.Graph-Basic/index.md b/Contents/08.Graph/01.Graph-Basic/index.md deleted file mode 100644 index 03b14c94..00000000 --- a/Contents/08.Graph/01.Graph-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [图的定义和分类](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md) -- [图的存储结构和问题应用](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md) \ No newline at end of file diff --git a/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md b/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md deleted file mode 100644 index 19e365f0..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md +++ /dev/null @@ -1,333 +0,0 @@ -## 1. 深度优先搜索简介 - -> **深度优先搜索算法(Depth First Search)**:英文缩写为 DFS,是一种用于搜索树或图结构的算法。深度优先搜索算法采用了回溯思想,从起始节点开始,沿着一条路径尽可能深入地访问节点,直到无法继续前进时为止,然后回溯到上一个未访问的节点,继续深入搜索,直到完成整个搜索过程。 - -深度优先搜索算法中所谓的深度优先,就是说优先沿着一条路径走到底,直到无法继续深入时再回头。 - -在深度优先遍历的过程中,我们需要将当前遍历节点 $u$ 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。 - -## 2. 深度优先搜索算法步骤 - -接下来我们以一个无向图为例,介绍一下深度优先搜索的算法步骤。 - -1. 选择起始节点 $u$,并将其标记为已访问。 -2. 检查当前节点是否为目标节点(看具体题目要求)。 -3. 如果当前节点 $u$ 是目标节点,则直接返回结果。 -4. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点。 -5. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归)。 -6. 如果节点 $u$ 没有未访问的相邻节点,回溯到上一个节点,继续搜索其他路径。 -7. 重复 $2 \sim 6$ 步骤,直到遍历完整个图或找到目标节点为止。 - -::: tabs#DFS - -@tab <1> - -![深度优先搜索 1](https://qcdn.itcharge.cn/images/202309042321406.png) - -@tab <2> - -![深度优先搜索 2](https://qcdn.itcharge.cn/images/202309042323911.png) - -@tab <3> - -![深度优先搜索 3](https://qcdn.itcharge.cn/images/202309042324370.png) - -@tab <4> - -![深度优先搜索 4](https://qcdn.itcharge.cn/images/202309042325587.png) - -@tab <5> - -![深度优先搜索 5](https://qcdn.itcharge.cn/images/202309042325689.png) - -@tab <6> - -![深度优先搜索 6](https://qcdn.itcharge.cn/images/202309042325770.png) - -::: - -## 3. 基于递归实现的深度优先搜索 - -### 3.1 基于递归实现的深度优先搜索算法步骤 - -深度优先搜索算法可以通过递归来实现,以下是基于递归实现的深度优先搜索算法步骤: - -1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$u$ 为当前遍历边的开始节点。定义 `def dfs_recursive(graph, u, visited):` 为递归实现的深度优先搜索方法。 -2. 选择起始节点 $u$,并将其标记为已访问,即将节点 $u$ 放入 $visited$ 中(`visited.add(u)`)。 -3. 检查当前节点 $u$ 是否为目标节点(看具体题目要求)。 -4. 如果当前节点 $u$ 是目标节点,则直接返回结果。 -5. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点。 -6. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归),即调用 `dfs_recursive(graph, v, visited)`。 -7. 如果节点 $u$ 没有未访问的相邻节点,则回溯到最近访问的节点,继续搜索其他路径。 -8. 重复 $3 \sim 7$ 步骤,直到遍历完整个图或找到目标节点为止。 - -### 3.2 基于递归实现的深度优先搜索实现代码 - -```python -class Solution: - def dfs_recursive(self, graph, u, visited): - print(u) # 访问节点 - visited.add(u) # 节点 u 标记其已访问 - - for v in graph[u]: - if v not in visited: # 节点 v 未访问过 - # 深度优先搜索遍历节点 - self.dfs_recursive(graph, v, visited) - - -graph = { - "A": ["B", "C"], - "B": ["A", "C", "D"], - "C": ["A", "B", "D", "E"], - "D": ["B", "C", "E", "F"], - "E": ["C", "D"], - "F": ["D", "G"], - "G": [] -} - -# 基于递归实现的深度优先搜索 -visited = set() -Solution().dfs_recursive(graph, "A", visited) -``` - -## 4. 基于堆栈实现的深度优先搜索 - -### 4.1 基于堆栈实现的深度优先搜索算法步骤 - -深度优先搜索算法除了基于递归实现之外,还可以基于堆栈来实现。同时,为了防止多次遍历同一节点,在使用栈存放节点访问记录时,我们将「当前节点」以及「下一个将要访问的邻接节点下标」一同存入栈中,从而在出栈时,可以通过下标直接找到下一个邻接节点,而不用遍历所有邻接节点。 - -以下是基于堆栈实现的深度优先搜索的算法步骤: - -1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$start$ 为当前遍历边的开始节点。定义 $stack$ 用于存放节点访问记录的栈结构。 -2. 选择起始节点 $u$,检查当前节点 $u$ 是否为目标节点(看具体题目要求)。 -3. 如果当前节点 $u$ 是目标节点,则直接返回结果。 -4. 如果当前节点 $u$ 不是目标节点,则将节点 $u$ 以及节点 $u$ 下一个将要访问的邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([u, 0])`,`visited.add(u)`。 -5. 如果栈不为空,取出 $stack$ 栈顶元素节点 $u$,以及节点 $u$ 下一个将要访问的邻接节点下标 $i$。 -6. 根据节点 $u$ 和下标 $i$,取出将要遍历的未访问过的邻接节点 $v$。 -7. 将节点 $u$ 以及节点 u 的下一个邻接节点下标 $i + 1$ 放入栈中。 -8. 访问节点 $v$,并对节点进行相关操作(看具体题目要求)。 -9. 将节点 $v$ 以及节点 $v$ 下一个邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([v, 0])`,`visited.add(v)`。 -10. 重复步骤 $5 \sim 9$,直到 $stack$ 栈为空或找到目标节点为止。 - -### 4.2 基于堆栈实现的深度优先搜索实现代码 - -```python -class Solution: - def dfs_stack(self, graph, u): - print(u) # 访问节点 u - visited, stack = set(), [] # 使用 visited 标记访问过的节点, 使用栈 stack 存放临时节点 - - stack.append([u, 0]) # 将节点 u,节点 u 的下一个邻接节点下标放入栈中,下次将遍历 graph[u][0] - visited.add(u) # 将起始节点 u 标记为已访问 - - - while stack: - u, i = stack.pop() # 取出节点 u,以及节点 u 下一个将要访问的邻接节点下标 i - - if i < len(graph[u]): - v = graph[u][i] # 取出邻接节点 v - stack.append([u, i + 1]) # 下一次将遍历 graph[u][i + 1] - if v not in visited: # 节点 v 未访问过 - print(v) # 访问节点 v - stack.append([v, 0]) # 下一次将遍历 graph[v][0] - visited.add(v) # 将节点 v 标记为已访问 - - -graph = { - "A": ["B", "C"], - "B": ["A", "C", "D"], - "C": ["A", "B", "D", "E"], - "D": ["B", "C", "E", "F"], - "E": ["C", "D"], - "F": ["D", "G"], - "G": [] -} - -# 基于堆栈实现的深度优先搜索 -Solution().dfs_stack(graph, "A") -``` - -## 5. 深度优先搜索应用 - -### 5.1 岛屿数量 - -#### 5.1.1 题目链接 - -- [200. 岛屿数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-islands/) - -#### 5.1.2 题目大意 - -**描述**:给定一个由字符 `'1'`(陆地)和字符 `'0'`(水)组成的的二维网格 `grid`。 - -**要求**:计算网格中岛屿的数量。 - -**说明**: - -- 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 -- 此外,你可以假设该网格的四条边均被水包围。 -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 300$。 -- $grid[i][j]$ 的值为 `'0'` 或 `'1'`。 - -**示例**: - -- 示例 1: - -```python -输入:grid = [ - ["1","1","1","1","0"], - ["1","1","0","1","0"], - ["1","1","0","0","0"], - ["0","0","0","0","0"] -] -输出:1 -``` - -- 示例 2: - -```python -输入:grid = [ - ["1","1","0","0","0"], - ["1","1","0","0","0"], - ["0","0","1","0","0"], - ["0","0","0","1","1"] -] -输出:3 -``` - -#### 5.1.3 解题思路 - -如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。 - -使用深度优先搜索或者广度优先搜索都可以。 - -##### 思路 1:深度优先搜索 - -1. 遍历 $grid$。 -2. 对于每一个字符为 `'1'` 的元素,遍历其上下左右四个方向,并将该字符置为 `'0'`,保证下次不会被重复遍历。 -3. 如果超出边界,则返回 $0$。 -4. 对于 $(i, j)$ 位置的元素来说,递归遍历的位置就是 $(i - 1, j)$、$(i, j - 1)$、$(i + 1, j)$、$(i, j + 1)$ 四个方向。每次遍历到底,统计数记录一次。 -5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。 - -##### 思路 1:代码 - -```python -class Solution: - def dfs(self, grid, i, j): - n = len(grid) - m = len(grid[0]) - if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0': - return 0 - grid[i][j] = '0' - self.dfs(grid, i + 1, j) - self.dfs(grid, i, j + 1) - self.dfs(grid, i - 1, j) - self.dfs(grid, i, j - 1) - - def numIslands(self, grid: List[List[str]]) -> int: - count = 0 - for i in range(len(grid)): - for j in range(len(grid[0])): - if grid[i][j] == '1': - self.dfs(grid, i, j) - count += 1 - return count -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(m \times n)$。 - -### 5.2 克隆图 - -#### 5.2.1 题目链接 - -- [133. 克隆图 - 力扣(LeetCode)](https://leetcode.cn/problems/clone-graph/) - -#### 5.2.2 题目大意 - -**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 - -**要求**:返回该图的深拷贝。 - -**说明**: - -- 节点数不超过 $100$。 -- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 -- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 -- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 -- 图是连通图,你可以从给定节点访问到所有节点。 - -**示例**: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) - -```python -输入:adjList = [[2,4],[1,3],[2,4],[1,3]] -输出:[[2,4],[1,3],[2,4],[1,3]] -解释: -图中有 4 个节点。 -节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 -节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 -节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 -节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 -``` - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) - -```python -输入:adjList = [[2],[1]] -输出:[[2],[1]] -``` - -#### 5.2.3 解题思路 - -所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。 - -可以用深度优先搜索或者广度优先搜索来做。 - -##### 思路 1:深度优先搜索 - -1. 使用哈希表 $visitedDict$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。 -2. 从给定节点开始,以深度优先搜索的方式遍历原图。 - 1. 如果当前节点被访问过,则返回隆图中对应节点。 - 2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。 - 3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。 -3. 递归结束,返回克隆节点。 - -##### 思路 1:代码 - -```python -class Solution: - def cloneGraph(self, node: 'Node') -> 'Node': - if not node: - return node - visitedDict = dict() - - def dfs(node: 'Node') -> 'Node': - if node in visitedDict: - return visitedDict[node] - - clone_node = Node(node.val, []) - visitedDict[node] = clone_node - for neighbor in node.neighbors: - clone_node.neighbors.append(dfs(neighbor)) - return clone_node - - return dfs(node) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【文章】[深度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/dfs/egx6xc/) -- 【文章】[算法数据结构:深度优先搜索(DFS) - 掘金](https://juejin.cn/post/6864348493721387021) -- 【文章】[Python 图的 BFS 与 DFS - 黄蜜桃的博客 - CSDN 博客](https://blog.csdn.net/qq_37738656/article/details/83027943) -- 【文章】[图的深度优先遍历(递归、非递归;邻接表,邻接矩阵)_zjq_smile 的博客 - CSDN博客](https://blog.csdn.net/zscfa/article/details/75947816) -- 【题解】[200. 岛屿数量(DFS / BFS) - 岛屿数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-islands/solution/number-of-islands-shen-du-you-xian-bian-li-dfs-or-/) \ No newline at end of file diff --git a/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md b/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md deleted file mode 100644 index 32734567..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md +++ /dev/null @@ -1,38 +0,0 @@ -### 图的深度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0797 | [所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0695 | [岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0695.%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0133 | [克隆图](https://leetcode.cn/problems/clone-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 0144 | [二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0144.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0094 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0094.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0145 | [二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0145.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索、二叉树 | 简单 | -| 0589 | [N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0589.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0590 | [N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0590.%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86.md) | 栈、树、深度优先搜索 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0841 | [钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0841.%20%E9%92%A5%E5%8C%99%E5%92%8C%E6%88%BF%E9%97%B4.md) | 深度优先搜索、广度优先搜索、图 | 中等 | -| 0129 | [求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0129.%20%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0684 | [冗余连接](https://leetcode.cn/problems/redundant-connection/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0684.%20%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0785 | [判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0886 | [可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0886.%20%E5%8F%AF%E8%83%BD%E7%9A%84%E4%BA%8C%E5%88%86%E6%B3%95.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 0130 | [被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0130.%20%E8%A2%AB%E5%9B%B4%E7%BB%95%E7%9A%84%E5%8C%BA%E5%9F%9F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0417 | [太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0417.%20%E5%A4%AA%E5%B9%B3%E6%B4%8B%E5%A4%A7%E8%A5%BF%E6%B4%8B%E6%B0%B4%E6%B5%81%E9%97%AE%E9%A2%98.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 1020 | [飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1020.%20%E9%A3%9E%E5%9C%B0%E7%9A%84%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1254 | [统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1254.%20%E7%BB%9F%E8%AE%A1%E5%B0%81%E9%97%AD%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 1034 | [边界着色](https://leetcode.cn/problems/coloring-a-border/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1034.%20%E8%BE%B9%E7%95%8C%E7%9D%80%E8%89%B2.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | -| 剑指 Offer 13 | [机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | -| 0529 | [扫雷游戏](https://leetcode.cn/problems/minesweeper/) | | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | - diff --git a/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md b/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md deleted file mode 100644 index af18e135..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md +++ /dev/null @@ -1,275 +0,0 @@ -## 1. 广度优先搜索简介 - -> **广度优先搜索算法(Breadth First Search)**:英文缩写为 BFS,又译作宽度优先搜索 / 横向优先搜索,是一种用于搜索树或图结构的算法。广度优先搜索算法从起始节点开始,逐层扩展,先访问离起始节点最近的节点,后访问离起始节点稍远的节点。以此类推,直到完成整个搜索过程。 - -因为遍历到的节点顺序符合「先进先出」的特点,所以广度优先搜索可以通过「队列」来实现。 - -## 2. 广度优先搜索算法步骤 - -接下来我们以一个无向图为例,介绍一下广度优先搜索的算法步骤。 - -1. 将起始节点 $u$ 放入队列中,并标记为已访问。 -2. 从队列中取出一个节点,访问它并将其所有的未访问邻接节点 $v$ 放入队列中。 -3. 标记已访问的节点 $v$,以避免重复访问。 -4. 重复步骤 $2 \sim 3$,直到队列为空或找到目标节点。 - -::: tabs#BFS - -@tab <1> - -![广度优先搜索 1](https://qcdn.itcharge.cn/images/20230905152316.png) - -@tab <2> - -![广度优先搜索 2](https://qcdn.itcharge.cn/images/20230905152327.png) - -@tab <3> - -![广度优先搜索 3](http://qcdn.itcharge.cn/images/20231009141628.png) - -@tab <4> - -![广度优先搜索 4](https://qcdn.itcharge.cn/images/20230905152401.png) - -@tab <5> - -![广度优先搜索 5](https://qcdn.itcharge.cn/images/20230905152420.png) - -@tab <6> - -![广度优先搜索 6](https://qcdn.itcharge.cn/images/20230905152433.png) - -@tab <7> - -![广度优先搜索 7](https://qcdn.itcharge.cn/images/20230905152445.png) - -::: - -## 3. 基于队列实现的广度优先搜索 - -### 3.1 基于队列实现的广度优先搜索算法步骤 - -1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量,$queue$ 为存放节点的队列,$u$ 为开始节点,定义 `def bfs(graph, u):` 为队列实现的广度优先搜索方法。 -2. 首先将起始节点 $u$ 标记为已访问,并将其加入队列中,即 `visited.add(u)`,`queue.append(u)`。 -3. 从队列中取出队头节点 $u$。访问节点 $u$,并对节点进行相关操作(看具体题目要求)。 -4. 遍历节点 $u$ 的所有未访问邻接节点 $v$(节点 $v$ 不在 $visited$ 中)。 -5. 将节点 $v$ 标记已访问,并加入队列中,即 `visited.add(v)`,`queue.append(v)`。 -6. 重复步骤 $3 \sim 5$,直到队列 $queue$ 为空。 - -### 3.2 基于队列实现的广度优先搜索实现代码 - -```python -import collections - -class Solution: - def bfs(self, graph, u): - visited = set() # 使用 visited 标记访问过的节点 - queue = collections.deque([]) # 使用 queue 存放临时节点 - - visited.add(u) # 将起始节点 u 标记为已访问 - queue.append(u) # 将起始节点 u 加入队列中 - - while queue: # 队列不为空 - u = queue.popleft() # 取出队头节点 u - print(u) # 访问节点 u - for v in graph[u]: # 遍历节点 u 的所有未访问邻接节点 v - if v not in visited: # 节点 v 未被访问 - visited.add(v) # 将节点 v 标记为已访问 - queue.append(v) # 将节点 v 加入队列中 - - -graph = { - "0": ["1", "2"], - "1": ["0", "2", "3"], - "2": ["0", "1", "3", "4"], - "3": ["1", "2", "4", "5"], - "4": ["2", "3"], - "5": ["3", "6"], - "6": [] -} - -# 基于队列实现的广度优先搜索 -Solution().bfs(graph, "0") -``` - -## 4. 广度优先搜索应用 - -### 4.1 克隆图 - -#### 4.1.1 题目链接 - -- [133. 克隆图 - 力扣(LeetCode)](https://leetcode.cn/problems/clone-graph/) - -#### 4.1.2 题目大意 - -**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 - -**要求**:返回该图的深拷贝。 - -**说明**: - -- 节点数不超过 $100$。 -- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 -- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 -- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 -- 图是连通图,你可以从给定节点访问到所有节点。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) - -```python -输入:adjList = [[2,4],[1,3],[2,4],[1,3]] -输出:[[2,4],[1,3],[2,4],[1,3]] -解释: -图中有 4 个节点。 -节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 -节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 -节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 -节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) - -```python -输入:adjList = [[2],[1]] -输出:[[2],[1]] -``` - -#### 4.1.3 解题思路 - -##### 思路 1:广度优先搜索 - -1. 使用哈希表 $visited$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。使用队列 $queue$ 存放节点。 -2. 根据起始节点 $node$,创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列中,即 `queue.append(node)`。 -3. 从队列中取出第一个节点 $node\underline{}u$。访问节点 $node\underline{}u$。 -4. 遍历节点 $node\underline{}u$ 的所有未访问邻接节点 $node\underline{}v$(节点 $node\underline{}v$ 不在 $visited$ 中)。 -5. 根据节点 $node\underline{}v$ 创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node_v] = Node(node_v.val, [])`。 -6. 然后将节点 $node\underline{}v$ 放入队列 $queue$ 中,即 `queue.append(node_v)`。 -7. 重复步骤 $3 \sim 6$,直到队列 $queue$ 为空。 -8. 广度优先搜索结束,返回起始节点的克隆节点(即 $visited[node]$)。 - -##### 思路 1:代码 - -```python -class Solution: - def cloneGraph(self, node: 'Node') -> 'Node': - if not node: - return node - - visited = dict() - queue = collections.deque() - - visited[node] = Node(node.val, []) - queue.append(node) - - while queue: - node_u = queue.popleft() - for node_v in node_u.neighbors: - if node_v not in visited: - visited[node_v] = Node(node_v.val, []) - queue.append(node_v) - visited[node_u].neighbors.append(visited[node_v]) - - return visited[node] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 -- **空间复杂度**:$O(n)$。 - -### 4.2 岛屿的最大面积 - -#### 4.2.1 题目链接 - -- [695. 岛屿的最大面积 - 力扣(LeetCode)](https://leetcode.cn/problems/max-area-of-island/) - -#### 4.2.2 题目大意 - -**描述**:给定一个只包含 $0$、$1$ 元素的二维数组,$1$ 代表岛屿,$0$ 代表水。一座岛的面积就是上下左右相邻的 $1$ 所组成的连通块的数目。 - -**要求**:计算出最大的岛屿面积。 - -**说明**: - -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 50$。 -- $grid[i][j]$ 为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg) - -```python -输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] -输出:6 -解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。 -``` - -- 示例 2: - -```python -输入:grid = [[0,0,0,0,0,0,0,0]] -输出:0 -``` - -#### 4.2.3 解题思路 - -##### 思路 1:广度优先搜索 - -1. 使用 $ans$ 记录最大岛屿面积。 -2. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: - 1. 将该元素置为 $0$。并使用队列 $queue$ 存储该节点位置。使用 $temp\underline{}ans$ 记录当前岛屿面积。 - 2. 然后从队列 $queue$ 中取出第一个节点位置 $(i, j)$。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 $0$(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`。 - 3. 不断重复上一步骤,直到队列 $queue$ 为空。 - 4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`。 -3. 将 $ans$ 作为答案返回。 - -##### 思路 1:代码 - -```python -import collections - -class Solution: - def maxAreaOfIsland(self, grid: List[List[int]]) -> int: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - rows, cols = len(grid), len(grid[0]) - ans = 0 - for i in range(rows): - for j in range(cols): - if grid[i][j] == 1: - grid[i][j] = 0 - temp_ans = 1 - queue = collections.deque([(i, j)]) - while queue: - i, j = queue.popleft() - for direct in directs: - new_i = i + direct[0] - new_j = j + direct[1] - if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols or grid[new_i][new_j] == 0: - continue - grid[new_i][new_j] = 0 - queue.append((new_i, new_j)) - temp_ans += 1 - - ans = max(ans, temp_ans) - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(n \times m)$。 - -## 参考资料 - -- 【文章】[广度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/bfs/e69rh1/) - diff --git a/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md b/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md deleted file mode 100644 index 388f15cd..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md +++ /dev/null @@ -1,23 +0,0 @@ -### 图的广度优先搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0797 | [所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0797.%20%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | -| 0286 | [墙与门](https://leetcode.cn/problems/walls-and-gates/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0286.%20%E5%A2%99%E4%B8%8E%E9%97%A8.md) | 广度优先搜索、数组、矩阵 | 中等 | -| 0200 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0200.%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | -| 0752 | [打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0752.%20%E6%89%93%E5%BC%80%E8%BD%AC%E7%9B%98%E9%94%81.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0133 | [克隆图](https://leetcode.cn/problems/clone-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0133.%20%E5%85%8B%E9%9A%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | -| 0733 | [图像渲染](https://leetcode.cn/problems/flood-fill/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0733.%20%E5%9B%BE%E5%83%8F%E6%B8%B2%E6%9F%93.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | -| 0542 | [01 矩阵](https://leetcode.cn/problems/01-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0542.%2001%20%E7%9F%A9%E9%98%B5.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0323 | [无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0323.%20%E6%97%A0%E5%90%91%E5%9B%BE%E4%B8%AD%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | -| 剑指 Offer 13 | [机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2013.%20%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | -| 0199 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0199.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0662 | [二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0662.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | -| 0958 | [二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0958.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%8C%E5%85%A8%E6%80%A7%E6%A3%80%E9%AA%8C.md) | 树、广度优先搜索、二叉树 | 中等 | -| 0572 | [另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | -| 0100 | [相同的树](https://leetcode.cn/problems/same-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0100.%20%E7%9B%B8%E5%90%8C%E7%9A%84%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0111 | [二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0111.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 剑指 Offer 32 - III | [从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2032%20-%20III.%20%E4%BB%8E%E4%B8%8A%E5%88%B0%E4%B8%8B%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91%20III.md) | 树、广度优先搜索、二叉树 | 中等 | - diff --git a/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md b/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md deleted file mode 100644 index 7248f982..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md +++ /dev/null @@ -1,346 +0,0 @@ -## 1. 拓扑排序简介 - -> **拓扑排序(**Topological Sorting**)**:一种对有向无环图(DAG)的所有顶点进行线性排序的方法,使得图中任意一点 $u$ 和 $v$,如果存在有向边 $$,则 $u$ 必须在 $v$ 之前出现。对有向图进行拓扑排序产生的线性序列称为满足拓扑次序的序列,简称拓扑排序。 - -图的拓扑排序是针对有向无环图(DAG)来说的,无向图和有向有环图没有拓扑排序,或者说不存在拓扑排序。 - -![](https://qcdn.itcharge.cn/images/20230504153551.png) - -如上图中的有向无环图(DAG)所示,$v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_5 \rightarrow v_6$ 是该图的一个拓扑序列。与此同时,$v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_6 \rightarrow v_5$ 也是该图的一个拓扑序列。也就是说,对于一个有向无环图来说,拓扑序列可能不止一个。 - -## 2. 拓扑排序的实现方法 - -拓扑排序有两种实现方法,分别是「Kahn 算法」和「DFS 深度优先搜索算法」。接下来我们依次来看下它们是如何实现的。 - -### 2.1 Kahn 算法 - -> **Kahn 算法的基本思想**: -> -> 1. 不断找寻有向图中入度为 $0$ 的顶点,将其输出。 -> 2. 然后删除入度为 $0$ 的顶点和从该顶点出发的有向边。 -> 3. 重复上述操作直到图为空,或者找不到入度为 $0$ 的节点为止。 - -#### 2.1.1 Kahn 算法的实现步骤 - -1. 使用数组 $indegrees$ 用于记录图中各个顶点的入度。 -2. 维护一个入度为 $0$ 的顶点集合 $S$(可使用栈、队列、优先队列)。 -3. 每次从集合中选择任何一个没有前驱(即入度为 $0$)的顶点 $u$,将其输出到拓扑序列 $order$ 中。 -4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将顶点 $v$ 放入集合 $S$ 中。 -5. 重复上述过程,直到集合 $S$ 为空,或者图中还有顶点未被访问(说明一定存在环路,无法形成拓扑序列)。 -6. 如果不存在环路,则 $order$ 中顶点的顺序就是拓扑排序的结果。 - -#### 2.1.2 Kahn 算法的实现代码 - -```python -import collections - -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingKahn(self, graph: dict): - indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 - for u in graph: - for v in graph[u]: - indegrees[v] += 1 # 统计所有顶点入度 - - # 将入度为 0 的顶点存入集合 S 中 - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - order = [] # order 用于存储拓扑序列 - - while S: - u = S.pop() # 从集合中选择一个没有前驱的顶点 0 - order.append(u) # 将其输出到拓扑序列 order 中 - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 - if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 - S.append(v) # 将其放入集合 S 中 - - if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 - return [] - return order # 返回拓扑序列 - - - def findOrder(self, n: int, edges): - # 构建图 - graph = dict() - for i in range(n): - graph[i] = [] - - for u, v in edges: - graph[u].append(v) - - return self.topologicalSortingKahn(graph) -``` - -### 2.2 基于 DFS 实现拓扑排序算法 - -> **基于 DFS 实现拓扑排序算法的基本思想**: -> -> 1. 对于一个顶点 $u$,深度优先遍历从该顶点出发的有向边 $$。如果从该顶点 $u$ 出发的所有相邻顶点 $v$ 都已经搜索完毕,则回溯到顶点 $u$ 时,该顶点 $u$ 应该位于其所有相邻顶点 $v$ 的前面(拓扑序列中)。 -> 2. 这样一来,当我们对每个顶点进行深度优先搜索,在回溯到该顶点时将其放入栈中,则最终从栈顶到栈底的序列就是一种拓扑排序。 - -#### 2.2.1 基于 DFS 实现拓扑排序算法实现步骤 - -1. 使用集合 $visited$ 用于记录当前顶点是否被访问过,避免重复访问。 -2. 使用集合 $onStack$ 用于记录同一次深度优先搜索时,当前顶点是否被访问过。如果当前顶点被访问过,则说明图中存在环路,无法构成拓扑序列。 -3. 使用布尔变量 $hasCycle$ 用于判断图中是否存在环。 -4. 从任意一个未被访问的顶点 $u$ 出发。 - 1. 如果顶点 $u$ 在同一次深度优先搜索时被访问过,则说明存在环。 - 2. 如果当前顶点被访问或者有环时,则无需再继续遍历,直接返回。 - -5. 将顶点 $u$ 标记为被访问过,并在本次深度优先搜索中标记为访问过。然后深度优先遍历从顶点 $u$ 出发的有向边 $$。 -6. 当顶点 $u$ 的所有相邻顶点 $v$ 都被访问后,回溯前记录当前节点 $u$(将当前节点 $u$ 输出到拓扑序列 $order$ 中)。 -7. 取消本次深度优先搜索时,顶点 $u$ 的访问标记。 -8. 对其他未被访问的顶点重复 $4 \sim 7$ 步过程,直到所有节点都遍历完,或者出现环。 -9. 如果不存在环路,则将 $order$ 逆序排序后,顶点的顺序就是拓扑排序的结果。 - -#### 2.2.2 DFS 深度优先搜索算法实现代码 - -```python -import collections - -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingDFS(self, graph: dict): - visited = set() # 记录当前顶点是否被访问过 - onStack = set() # 记录同一次深搜时,当前顶点是否被访问过 - order = [] # 用于存储拓扑序列 - hasCycle = False # 用于判断是否存在环 - - def dfs(u): - nonlocal hasCycle - if u in onStack: # 同一次深度优先搜索时,当前顶点被访问过,说明存在环 - hasCycle = True - if u in visited or hasCycle: # 当前节点被访问或者有环时直接返回 - return - - visited.add(u) # 标记节点被访问 - onStack.add(u) # 标记本次深搜时,当前顶点被访问 - - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - dfs(v) # 递归访问节点 v - - order.append(u) # 后序遍历顺序访问节点 u - onStack.remove(u) # 取消本次深搜时的 顶点访问标记 - - for u in graph: - if u not in visited: - dfs(u) # 递归遍历未访问节点 u - - if hasCycle: # 判断是否存在环 - return [] # 存在环,无法构成拓扑序列 - order.reverse() # 将后序遍历转为拓扑排序顺序 - return order # 返回拓扑序列 - - def findOrder(self, n: int, edges): - # 构建图 - graph = dict() - for i in range(n): - graph[i] = [] - for v, u in edges: - graph[u].append(v) - - return self.topologicalSortingDFS(graph) -``` - -## 3. 拓扑排序的应用 - -拓扑排序可以用来解决一些依赖关系的问题,比如项目的执行顺序,课程的选修顺序等。 - -### 3.1 课程表 II - -#### 3.1.1 题目链接 - -- [210. 课程表 II - 力扣](https://leetcode.cn/problems/course-schedule-ii/) - -#### 3.1.2 题目大意 - -**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 - -**要求**:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 - -**说明**: - -- $1 \le numCourses \le 2000$。 -- $0 \le prerequisites.length \le numCourses \times (numCourses - 1)$。 -- $prerequisites[i].length == 2$。 -- $0 \le ai, bi < numCourses$。 -- $ai \ne bi$。 -- 所有$[ai, bi]$ 互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:numCourses = 2, prerequisites = [[1,0]] -输出:[0,1] -解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]。 -``` - -- 示例 2: - -```python -输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] -输出:[0,2,1,3] -解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 -因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]。 -``` - -#### 3.1.3 解题思路 - -##### 思路 1:拓扑排序 - -这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组 $order$ 即可。 - -1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 -2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 -3. 从队列中选择一个节点 $u$,并将其加入到答案数组 $order$ 中。 -4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 -5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 -6. 最后判断总的顶点数和拓扑序列中的顶点数是否相等,如果相等,则返回答案数组 $order$,否则,返回空数组。 - -##### 思路 1:代码 - -```python -import collections - -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingKahn(self, graph: dict): - indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 - for u in graph: - for v in graph[u]: - indegrees[v] += 1 # 统计所有顶点入度 - - # 将入度为 0 的顶点存入集合 S 中 - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - order = [] # order 用于存储拓扑序列 - - while S: - u = S.pop() # 从集合中选择一个没有前驱的顶点 0 - order.append(u) # 将其输出到拓扑序列 order 中 - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 - if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 - S.append(v) # 将其放入集合 S 中 - - if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 - return [] - return order # 返回拓扑序列 - - - def findOrder(self, numCourses: int, prerequisites): - graph = dict() - for i in range(numCourses): - graph[i] = [] - - for v, u in prerequisites: - graph[u].append(v) - - return self.topologicalSortingKahn(graph) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 -- **空间复杂度**:$O(n + m)$。 - -### 3.2 找到最终的安全状态 - -#### 3.2.1 题目链接 - -- [802. 找到最终的安全状态 - 力扣](https://leetcode.cn/problems/find-eventual-safe-states/) - -#### 3.2.2 题目大意 - -**描述**:给定一个有向图 $graph$,其中 $graph[i]$ 是与节点 $i$ 相邻的节点列表,意味着从节点 $i$ 到节点 $graph[i]$ 中的每个节点都有一条有向边。 - -**要求**:找出图中所有的安全节点,将其存入数组作为答案返回,答案数组中的元素应当按升序排列。 - -**说明**: - -- **终端节点**:如果一个节点没有连出的有向边,则它是终端节点。或者说,如果没有出边,则节点为终端节点。 -- **安全节点**:如果从该节点开始的所有可能路径都通向终端节点,则该节点为安全节点。 -- $n == graph.length$。 -- $1 \le n \le 10^4$。 -- $0 \le graph[i].length \le n$。 -- $0 \le graph[i][j] \le n - 1$。 -- $graph[i]$ 按严格递增顺序排列。 -- 图中可能包含自环。 -- 图中边的数目在范围 $[1, 4 \times 10^4]$ 内。 - -**示例**: - -- 示例 1: - -![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/03/17/picture1.png) - -```python -输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]] -输出:[2,4,5,6] -解释:示意图如上。 -节点 5 和节点 6 是终端节点,因为它们都没有出边。 -从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6。 -``` - -- 示例 2: - -```python -输入:graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]] -输出:[4] -解释: -只有节点 4 是终端节点,从节点 4 开始的所有路径都通向节点 4。 -``` - -#### 3.2.3 解题思路 - -##### 思路 1:拓扑排序 - -1. 根据题意可知,安全节点所对应的终点,一定是出度为 $0$ 的节点。而安全节点一定能在有限步内到达终点,则说明安全节点一定不在「环」内。 -2. 我们可以利用拓扑排序来判断顶点是否在环中。 -3. 为了找出安全节点,可以采取逆序建图的方式,将所有边进行反向。这样出度为 $0$ 的终点就变为了入度为 $0$ 的点。 -4. 然后通过拓扑排序不断移除入度为 $0$ 的点之后,如果不在「环」中的点,最后入度一定为 $0$,这些点也就是安全节点。而在「环」中的点,最后入度一定不为 $0$。 -5. 最后将所有安全的起始节点存入数组作为答案返回。 - -##### 思路 1:代码 - -```python -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingKahn(self, graph: dict): - indegrees = {u: 0 for u in graph} # indegrees 用于记录所有节点入度 - for u in graph: - for v in graph[u]: - indegrees[v] += 1 # 统计所有节点入度 - - # 将入度为 0 的顶点存入集合 S 中 - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - - while S: - u = S.pop() # 从集合中选择一个没有前驱的顶点 0 - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 - if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 - S.append(v) # 将其放入集合 S 中 - - res = [] - for u in indegrees: - if indegrees[u] == 0: - res.append(u) - - return res - - def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]: - graph_dict = {u: [] for u in range(len(graph))} - - for u in range(len(graph)): - for v in graph[u]: - graph_dict[v].append(u) # 逆序建图 - - return self.topologicalSortingKahn(graph_dict) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 是图中节点数目,$m$ 是图中边数目。 -- **空间复杂度**:$O(n + m)$。 diff --git a/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md b/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md deleted file mode 100644 index ff061023..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md +++ /dev/null @@ -1,11 +0,0 @@ -### 图的拓扑排序题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 1136 | [并行课程](https://leetcode.cn/problems/parallel-courses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1136.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B.md) | 图、拓扑排序 | 中等 | -| 2050 | [并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2050.%20%E5%B9%B6%E8%A1%8C%E8%AF%BE%E7%A8%8B%20III.md) | 图、拓扑排序、数组、动态规划 | 困难 | -| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | - diff --git a/Contents/08.Graph/02.Graph-Traversal/index.md b/Contents/08.Graph/02.Graph-Traversal/index.md deleted file mode 100644 index c404d4b6..00000000 --- a/Contents/08.Graph/02.Graph-Traversal/index.md +++ /dev/null @@ -1,8 +0,0 @@ -## 本章内容 - -- [图的深度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md) -- [图的深度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) -- [图的广度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md) -- [图的广度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) -- [图的拓扑排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md) -- [图的拓扑排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) \ No newline at end of file diff --git a/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md b/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md deleted file mode 100644 index 10cf4435..00000000 --- a/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md +++ /dev/null @@ -1,73 +0,0 @@ -## 1. 最小生成树的定义 - -在了解「最小生成树」之前,我们需要要先理解 「生成树」的概念。 - -> **图的生成树(Spanning Tree)**:如果无向连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。 - -换句话说,生成树是原图 G 的一个子图,它包含了原图 G 的所有顶点,并且通过选择图中一部分边连接这些顶点,使得子图中没有环。 - -生成树有以下特点: - -1. **包含所有顶点**:生成树中包含了原图的所有顶点。 -2. **连通性**:生成树是原图的一个连通子图,意味着任意两个顶点之间都存在一条路径。 -3. **无环图**:生成树一个无环图。 -4. **边数最少**:在包含所有顶点的情况下,生成树的边数最少,其边数为顶点数减 $1$。 - -> **最小生成树(Minimum Spanning Tree)**:无向连通图 G 的所有生成树中,边的权值之和最小的生成树,被称为最小生成树。 - -最小生成树除了包含生成树的特点之外,还具有一个特点。 - -1. **边的权值之和最小**:在包含所有顶点的情况下,最小生成树的边的权重之和是所有可能的生成树中最小的。 - -为了找到无向图的最小生成树,常用的算法有「Prim 算法」和「Kruskal 算法」。 - -- **Prim 算法**:从一个起始顶点出发,逐步选择与已经构建的树连接的最短边,直到包含所有顶点为止。 -- **Kruskal 算法**:基于边的排序和并查集数据结构,逐步添加边,并保证所选边不会构成环路,直到构建出最小生成树。 - -这两个算法都可以帮助我们找到图中的最小生成树,以满足连接所有顶点的要求同时使得总权重最小。 - -## 2. Prim 算法 - -### 2.1 Prim 算法的算法思想 - -> **Prim 算法的算法思想**:每次选择最短边来扩展最小生成树,从而保证生成树的总权重最小。算法通过不断扩展小生成树的顶点集合 $MST$,逐步构建出最小生成树。 - -### 2.2 Prim 算法的实现步骤 - -1. 维护两个集合,一个是已经加入到最小生成树的顶点集合 $MST$,另一个是还未加入生成树的顶点集合。 -2. 选择起始顶点,将其加入到最小生成树的顶点集合 $MST$ 中。 -3. 从 $MST$ 的顶点集合中选择一个顶点,然后找到连接这个顶点与 $MST$ 之间的边中权重最小的边。 -4. 让上一步中找到的顶点和边加入到 $MST$ 中,更新 $MST$ 的顶点集合和边集合。 -5. 重复第 $3 \sim 4$ 步,直到 $MST$ 的顶点集合中包含了图中的所有顶点为止。 - -### 2.3 Prim 算法的实现代码 - -```python - -``` - -### 2.3 Prim 算法 - -## 03. Kruskal 算法 - -### 3.1 Kruskal 算法的算法思想 - -> **Kruskal 算法的算法思想**:通过依次选择权重最小的边并判断其两个端点是否连接在同一集合中,从而逐步构建最小生成树。这个过程保证了最终生成的树是无环的,并且总权重最小。 - -在实际实现中,我们通常使用并查集数据结构来管理顶点的集合信息,以便高效地判断两个顶点是否在同一个集合中,以及合并集合。 - -### 3.2 Kruskal 算法的实现步骤 - -1. 将图中所有边按照权重从小到大进行排序。 -2. 将每个顶点看做是一个单独集合,即初始时每个顶点自成一个集合。 -3. 按照排好序的边顺序,按照权重从小到大,依次遍历每一条边。 -4. 对于每条边,检查其连接的两个顶点所属的集合: - 1. 如果两个顶点属于同一个集合,则跳过这条边,以免形成环路。 - 2. 如果两个顶点不属于同一个集合,则将这条边加入到最小生成树中,同时合并这两个顶点所属的集合。 -5. 重复第 $3 \sim 4$ 步,直到最小生成树中的变数等于所有节点数减 $1$ 为止。 - -### 3.3 Kruskal 算法的实现代码 - -```python - -``` \ No newline at end of file diff --git a/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md b/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md deleted file mode 100644 index e3e7ab31..00000000 --- a/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md +++ /dev/null @@ -1,8 +0,0 @@ -### 图的最小生成树题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1584 | [连接所有点的最小费用](https://leetcode.cn/problems/min-cost-to-connect-all-points/) | | 并查集、图、数组、最小生成树 | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | - diff --git a/Contents/08.Graph/03.Graph-Spanning-Tree/index.md b/Contents/08.Graph/03.Graph-Spanning-Tree/index.md deleted file mode 100644 index 7de35b34..00000000 --- a/Contents/08.Graph/03.Graph-Spanning-Tree/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [图的最小生成树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md) -- [图的最小生成树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) \ No newline at end of file diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md b/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md deleted file mode 100644 index 6152ecb6..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md +++ /dev/null @@ -1,53 +0,0 @@ -## 1. 单源最短路径的定义 - -> **单源最短路径(Single Source Shortest Path)**:对于一个带权图 $G = (V, E)$,其中每条边的权重是一个实数。另外,给定 $v$ 中的一个顶点,称之为源点。则源点到其他所有各个顶点之间的最短路径长度,称为单源最短路径。 - -这里的路径长度,指的是路径上各边权之和。 - -单源最短路径问题的核心是找到从源点到其他各个顶点的路径,使得路径上边的权重之和最小。这个问题在许多实际应用中都非常重要,比如网络路由、地图导航、通信网络优化等。 - -常见的解决单源最短路径问题的算法包括: - -1. **Dijkstra 算法**:一种贪心算法,用于解决无负权边的情况。它逐步扩展当前已知最短路径的范围,选择当前距离起始节点最近的节点,并更新与该节点相邻的节点的距离。 -2. **Bellman-Ford 算法**:适用于有负权边的情况。它通过多次迭代来逐步逼近最短路径,每次迭代都尝试通过更新边的权重来缩短路径。 -3. **SPFA 算法**:优化的 Bellman-Ford 算法,它在每次迭代中不遍历所有的边,而是选择性地更新与当前节点相关的边,从而提高了算法的效率。 - -这些算法根据图的特点和问题的需求有所不同,选择适合的算法可以在不同情况下有效地解决单源最短路径问题。 - -## 2. Dijkstra 算法 - -### 2.1 Dijkstra 算法的算法思想 - -> **Dijkstra 算法的算法思想**:通过逐步选择距离起始节点最近的节点,并根据这些节点的路径更新其他节点的距离,从而逐步找到最短路径。 - -### 2.2 Dijkstra 算法的实现步骤 - -### 2.3 Dijkstra 算法的实现代码 - -```python - -``` - -## 3. Bellman-Ford 算法 - -### 3.1 Bellman-Ford 算法的算法思想 - -### 3.2 Bellman-Ford 算法的实现步骤 - -### 3.3 Bellman-Ford 算法的实现代码 - -```python - -``` - -## 4. SPFA 算法 - -### 4.1 SPFA 算法的算法思想 - -### 4.2 SPFA 算法的实现步骤 - -### 4.3 SPFA 算法的实现代码 - -```python -``` - diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md b/Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md b/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md deleted file mode 100644 index 686c2ea2..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md +++ /dev/null @@ -1,10 +0,0 @@ -### 单源最短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0407 | [接雨水 II](https://leetcode.cn/problems/trapping-rain-water-ii/) | | 广度优先搜索、数组、矩阵、堆(优先队列) | 困难 | -| 0743 | [网络延迟时间](https://leetcode.cn/problems/network-delay-time/) | | 深度优先搜索、广度优先搜索、图、最短路、堆(优先队列) | 中等 | -| 0787 | [K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops/) | | 深度优先搜索、广度优先搜索、图、动态规划、最短路、堆(优先队列) | 中等 | -| 1631 | [最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1631.%20%E6%9C%80%E5%B0%8F%E4%BD%93%E5%8A%9B%E6%B6%88%E8%80%97%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | -| 1786 | [从第一个节点出发到最后一个节点的受限路径数](https://leetcode.cn/problems/number-of-restricted-paths-from-first-to-last-node/) | | 图、拓扑排序、动态规划、最短路、堆(优先队列) | 中等 | - diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md b/Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md b/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md deleted file mode 100644 index 7a66c8e1..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md +++ /dev/null @@ -1,7 +0,0 @@ -### 多源最短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0815 | [公交路线](https://leetcode.cn/problems/bus-routes/) | | 广度优先搜索、数组、哈希表 | 困难 | -| 1162 | [地图分析](https://leetcode.cn/problems/as-far-from-land-as-possible/) | | 广度优先搜索、数组、动态规划、矩阵 | 中等 | - diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md b/Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md b/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md deleted file mode 100644 index b174a222..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md +++ /dev/null @@ -1,6 +0,0 @@ -### 次短路径题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 2045 | [到达目的地的第二短时间](https://leetcode.cn/problems/second-minimum-time-to-reach-destination/) | | 广度优先搜索、图、最短路 | 困难 | - diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md b/Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md b/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md deleted file mode 100644 index a0ad57fa..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md +++ /dev/null @@ -1,7 +0,0 @@ -### 差分约束系统 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | -| 1109 | [航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1109.%20%E8%88%AA%E7%8F%AD%E9%A2%84%E8%AE%A2%E7%BB%9F%E8%AE%A1.md) | 数组、前缀和 | 中等 | - diff --git a/Contents/08.Graph/04.Graph-Shortest-Path/index.md b/Contents/08.Graph/04.Graph-Shortest-Path/index.md deleted file mode 100644 index ed11ed6b..00000000 --- a/Contents/08.Graph/04.Graph-Shortest-Path/index.md +++ /dev/null @@ -1,11 +0,0 @@ -## 本章内容 - -- [单源最短路径知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md) -- [单源最短路径知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md) -- [单源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) -- [多源最短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md) -- [多源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) -- [次短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md) -- [次短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) -- [差分约束系统知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md) -- [差分约束系统题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) \ No newline at end of file diff --git a/Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md b/Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md b/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md deleted file mode 100644 index 2dae199f..00000000 --- a/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md +++ /dev/null @@ -1,6 +0,0 @@ -### 二分图基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0785 | [判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0785.%20%E5%88%A4%E6%96%AD%E4%BA%8C%E5%88%86%E5%9B%BE.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | - diff --git a/Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md b/Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md b/Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md b/Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md b/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md deleted file mode 100644 index 2b0a9816..00000000 --- a/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md +++ /dev/null @@ -1,8 +0,0 @@ -### 二分图最大匹配题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| LCP 04 | [覆盖](https://leetcode.cn/problems/broken-board-dominoes/) | | 位运算、图、数组、动态规划、状态压缩 | 困难 | -| 1947 | [最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1595 | [连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | - diff --git a/Contents/08.Graph/05.Graph-Bipartite/index.md b/Contents/08.Graph/05.Graph-Bipartite/index.md deleted file mode 100644 index 0cebef1e..00000000 --- a/Contents/08.Graph/05.Graph-Bipartite/index.md +++ /dev/null @@ -1,8 +0,0 @@ -## 本章内容 - -- [二分图基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md) -- [二分图基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) -- [二分图最大匹配知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md) -- [匈牙利算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md) -- [Hopcroft-Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md) -- [二分图最大匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) \ No newline at end of file diff --git a/Contents/08.Graph/index.md b/Contents/08.Graph/index.md deleted file mode 100644 index 96dff5e7..00000000 --- a/Contents/08.Graph/index.md +++ /dev/null @@ -1,41 +0,0 @@ -## 本章内容 - -### 图的基础知识 - -- [图的定义和分类](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md) -- [图的存储结构和问题应用](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md) - -### 图的遍历 - -- [图的深度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md) -- [图的深度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) -- [图的广度优先搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md) -- [图的广度优先搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) -- [图的拓扑排序知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md) -- [图的拓扑排序题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) - -### 图的生成树 - -- [图的最小生成树知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md) -- [图的最小生成树题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) - -### 最短路径 - -- [单源最短路径知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md) -- [单源最短路径知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md) -- [单源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) -- [多源最短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md) -- [多源最短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) -- [次短路径知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md) -- [次短路径题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) -- [差分约束系统知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md) -- [差分约束系统题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) - -### 二分图 - -- [二分图基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md) -- [二分图基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) -- [二分图最大匹配知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md) -- [匈牙利算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md) -- [Hopcroft-Karp 算法](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md) -- [二分图最大匹配题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md b/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md deleted file mode 100644 index 35b9bb3b..00000000 --- a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md +++ /dev/null @@ -1,280 +0,0 @@ -## 1. 枚举算法简介 - -> **枚举算法(Enumeration Algorithm)**:也称为穷举算法,指的是按照问题本身的性质,一一列举出该问题所有可能的解,并在逐一列举的过程中,将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中,既不能遗漏也不能重复。 - -枚举算法的核心思想是:通过列举问题的所有状态,将它们逐一与目标状态进行比较,从而得到满足条件的解。 - -由于枚举算法要通过列举问题的所有状态来得到满足条件的解,因此,在问题规模变大时,其效率一般是比较低的。但是枚举算法也有自己特有的优点: - -1. 多数情况下容易编程实现,也容易调试。 -2. 建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。 - -所以,枚举算法通常用于求解问题规模比较小的问题,或者作为求解问题的一个子算法出现,通过枚举一些信息并进行保存,而这些消息的有无对主算法效率的高低有着较大影响。 - -## 2. 枚举算法的解题思路 - -### 2.1 枚举算法的解题思路 - -枚举算法是设计最简单、最基本的搜索算法。是我们在遇到问题时,最应该优先考虑的算法。 - -因为其实现足够简单,所以在遇到问题时,我们往往可以先通过枚举算法尝试解决问题,然后在此基础上,再去考虑其他优化方法和解题思路。 - -采用枚举算法解题的一般思路如下: - -1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 -2. 一一枚举可能的情况,并验证是否是问题的解。 -3. 考虑提高枚举算法的效率。 - -我们可以从下面几个方面考虑提高算法的效率: - -1. 抓住问题状态的本质,尽可能缩小问题状态空间的大小。 -2. 加强约束条件,缩小枚举范围。 -3. 根据某些问题特有的性质,例如对称性等,避免对本质相同的状态重复求解。 - -### 2.2 枚举算法的简单应用 - -下面举个著名的例子:「百钱买百鸡问题」。这个问题是我国古代数学家张丘在「算经」一书中提出的。该问题叙述如下: - -> **百钱买百鸡问题**:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则鸡翁、鸡母、鸡雏各几何? - -翻译一下,意思就是:公鸡一只五块钱,母鸡一只三块钱,小鸡三只一块钱。现在我们用 $100$ 块钱买了 $100$ 只鸡,问公鸡、母鸡、小鸡各买了多少只? - -下面我们根据算法的一般思路来解决一下这道题。 - -1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 - - 1. 确定枚举对象:枚举对象为公鸡、母鸡、小鸡的只数,那么我们可以用变量 $x$、$y$、$z$ 分别来代表公鸡、母鸡、小鸡的只数。 - 2. 确定枚举范围:因为总共买了 $100$ 只鸡,所以 $0 \le x, y, z \le 100$,则 $x$、$y$、$z$ 的枚举范围为 $[0, 100]$。 - 3. 确定判断条件:根据题意,我们可以列出两个方程式:$5 \times x + 3 \times y + \frac{z}{3} = 100$,$x + y + z = 100$。在枚举 $x$、$y$、$z$ 的过程中,我们可以根据这两个方程式来判断是否当前状态是否满足题意。 - -2. 一一枚举可能的情况,并验证是否是问题的解。 - - 1. 根据枚举对象、枚举范围和判断条件,我们可以顺利写出对应的代码。 - - ```python - class Solution: - def buyChicken(self): - for x in range(101): - for y in range(101): - for z in range(101): - if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100 and x + y + z == 100: - print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) - ``` - -3. 考虑提高枚举算法的效率。 - - 1. 在上面的代码中,我们枚举了 $x$、$y$、$z$,但其实根据方程式 $x + y + z = 100$,得知:$z$ 可以通过 $z = 100 - x - y$ 而得到,这样我们就不用再枚举 $z$ 了。 - 2. 在上面的代码中,对 $x$、$y$ 的枚举范围是 $[0, 100]$,但其实如果所有钱用来买公鸡,最多只能买 $20$ 只,同理,全用来买母鸡,最多只能买 $33$ 只。所以对 $x$ 的枚举范围可改为 $[0, 20]$,$y$ 的枚举范围可改为 $[0, 33]$。 - - ```python - class Solution: - def buyChicken(self): - for x in range(21): - for y in range(34): - z = 100 - x - y - if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100: - print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) - ``` - - -## 3. 枚举算法的应用 - -### 3.1 两数之和 - -#### 3.1.1 题目链接 - -- [1. 两数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum/) - -#### 3.1.2 题目大意 - -**描述**:给定一个整数数组 `nums` 和一个整数目标值 `target`。 - -**要求**:在该数组中找出和为 `target` 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。 - -**说明**: - -- $2 \le nums.length \le 10^4$。 -- $-10^9 \le nums[i] \le 10^9$。 -- $-10^9 \le target \le 10^9$。 -- 只会存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,7,11,15], target = 9 -输出:[0,1] -解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 -``` - -- 示例 2: - -```python -输入:nums = [3,2,4], target = 6 -输出:[1,2] -``` - -#### 3.1.3 解题思路 - -这里说下枚举算法的解题思路。 - -##### 思路 1:枚举算法 - -1. 使用两重循环枚举数组中每一个数 `nums[i]`、`nums[j]`,判断所有的 `nums[i] + nums[j]` 是否等于 `target`。 -2. 如果出现 `nums[i] + nums[j] == target`,则说明数组中存在和为 `target` 的两个整数,将两个整数的下标 `i`、`j` 输出即可。 - -##### 思路 1:代码 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - for i in range(len(nums)): - for j in range(i + 1, len(nums)): - if i != j and nums[i] + nums[j] == target: - return [i, j] - return [] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$ -- **空间复杂度**:$O(1)$。 - -### 3.2 计数质数 - -#### 3.2.1 题目链接 - -- [204. 计数质数 - 力扣(LeetCode)](https://leetcode.cn/problems/count-primes/) - -#### 3.2.2 题目大意 - -**描述**:给定 一个非负整数 $n$。 - -**要求**:统计小于 $n$ 的质数数量。 - -**说明**: - -- $0 \le n \le 5 * 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入 n = 10 -输出 4 -解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:0 -``` - -#### 3.2.3 解题思路 - -这里说下枚举算法的解题思路(注意:提交会超时,只是讲解一下枚举算法的思路)。 - -##### 思路 1:枚举算法(超时) - -对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整数的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 - -这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。 - -在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终返回该数目作为答案。 - -考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。 - -利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n})$,检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。 - -##### 思路 1:代码 - -```python -class Solution: - def isPrime(self, x): - for i in range(2, int(pow(x, 0.5)) + 1): - if x % i == 0: - return False - return True - - def countPrimes(self, n: int) -> int: - cnt = 0 - for x in range(2, n): - if self.isPrime(x): - cnt += 1 - return cnt -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \sqrt{n})$。 -- **空间复杂度**:$O(1)$。 - -### 3.3 统计平方和三元组的数目 - -#### 3.3.1 题目链接 - -- [1925. 统计平方和三元组的数目 - 力扣(LeetCode)](https://leetcode.cn/problems/count-square-sum-triples/) - -#### 3.3.2 题目大意 - -**描述**:给你一个整数 $n$。 - -**要求**:请你返回满足 $1 \le a, b, c \le n$ 的平方和三元组的数目。 - -**说明**: - -- **平方和三元组**:指的是满足 $a^2 + b^2 = c^2$ 的整数三元组 $(a, b, c)$。 -- $1 \le n \le 250$。 - -**示例**: - -- 示例 1: - -```python -输入 n = 5 -输出 2 -解释 平方和三元组为 (3,4,5) 和 (4,3,5)。 -``` - -- 示例 2: - -```python -输入:n = 10 -输出:4 -解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。 -``` - -#### 3.3.3 解题思路 - -##### 思路 1:枚举算法 - -我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n$,并且是完全平方数。 - -在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终,我们返回该数目作为答案。 - -利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。 - -- 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 $1$,所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。 - -##### 思路 1:代码 - -```python -class Solution: - def countTriples(self, n: int) -> int: - cnt = 0 - for a in range(1, n + 1): - for b in range(1, n + 1): - c = int(sqrt(a * a + b * b + 1)) - if c <= n and a * a + b * b == c * c: - cnt += 1 - return cnt -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md b/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md deleted file mode 100644 index ab965a29..00000000 --- a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md +++ /dev/null @@ -1,14 +0,0 @@ -### 枚举算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0001 | [两数之和](https://leetcode.cn/problems/two-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0001.%20%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 数组、哈希表 | 简单 | -| 0204 | [计数质数](https://leetcode.cn/problems/count-primes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0204.%20%E8%AE%A1%E6%95%B0%E8%B4%A8%E6%95%B0.md) | 数组、数学、枚举、数论 | 中等 | -| 1925 | [统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1925.%20%E7%BB%9F%E8%AE%A1%E5%B9%B3%E6%96%B9%E5%92%8C%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 数学、枚举 | 简单 | -| 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | -| 1620 | [网络信号最好的坐标](https://leetcode.cn/problems/coordinate-with-maximum-network-quality/) | | 数组、枚举 | 中等 | -| 剑指 Offer 57 - II | [和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2057%20-%20II.%20%E5%92%8C%E4%B8%BAs%E7%9A%84%E8%BF%9E%E7%BB%AD%E6%AD%A3%E6%95%B0%E5%BA%8F%E5%88%97.md) | 数学、双指针、枚举 | 简单 | -| 0800 | [相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0800.%20%E7%9B%B8%E4%BC%BC%20RGB%20%E9%A2%9C%E8%89%B2.md) | 数学、字符串、枚举 | 简单 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0560 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0560.%20%E5%92%8C%E4%B8%BA%20K%20%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | - diff --git a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/index.md b/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/index.md deleted file mode 100644 index a6650a96..00000000 --- a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [枚举算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md) -- [枚举算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md b/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md deleted file mode 100644 index 81823e2d..00000000 --- a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md +++ /dev/null @@ -1,328 +0,0 @@ -## 1. 递归简介 - -> **递归(Recursion)**:指的是一种通过重复将原问题分解为同类的子问题而解决的方法。在绝大数编程语言中,可以通过在函数中再次调用函数自身的方式来实现递归。 - -举个简单的例子来了解一下递归算法。比如阶乘的计算方法在数学上的定义为: - -$fact(n) = \begin{cases} 1 & \text{n = 0} \cr n * fact(n - 1) & \text{n > 0} \end{cases}$ - -根据阶乘计算方法的数学定义,我们可以使用调用函数自身的方式来实现阶乘函数 `fact(n)` ,其实现代码可以写作: - -```python -def fact(n): - if n == 0: - return 1 - return n * fact(n - 1) -``` - -以 `n = 6` 为例,上述代码中阶乘函数 `fact(6):` 的计算过程如下: - -```python -fact(6) -= 6 * fact(5) -= 6 * (5 * fact(4)) -= 6 * (5 * (4 * fact(3))) -= 6 * (5 * (4 * (3 * fact(2)))) -= 6 * (5 * (4 * (3 * (2 * fact(1))))) -= 6 * (5 * (4 * (3 * (2 * (1 * fact(0)))))) -= 6 * (5 * (4 * (3 * (2 * (1 * 1))))) -= 6 * (5 * (4 * (3 * (2 * 1)))) -= 6 * (5 * (4 * (3 * 2))) -= 6 * (5 * (4 * 6)) -= 6 * (5 * 24) -= 6 * 120 -= 720 -``` - -上面的例子也可以用语言描述为: - -1. 函数从 `fact(6)` 开始,一层层地调用 `fact(5)`、`fact(4)`、…… 一直调用到最底层的 `fact(0)`。 -2. 当 `n == 0` 时,`fact(0)` 不再继续调用自身,而是直接向上一层返回结果 `1`。 -3. `fact(1)` 通过下一层 `fact(0)` 的计算结果得出 `fact(1) = 1 * 1 = 1`,从而向上一层返回结果 `1`。 -4. `fact(2)` 通过下一层 `fact(1)` 的计算结果得出 `fact(2) = 2 * 1 = 2 `,从而向上一层返回结果 `2`。 -5. `fact(3)` 通过下一层 `fact(2)` 的计算结果得出 `fact(3) = 3 * 2 = 6 `,从而向上一层返回结果 `6`。 -6. `fact(4)` 通过下一层 `fact(3)` 的计算结果得出 `fact(4) = 4 * 6 = 24`,从而向上一层返回结果 `24`。 -7. `fact(5)` 通过下一层 `fact(4)` 的计算结果得出 `fact(5) = 5 * 24 = 120`,从而向上一层返回结果 `120`。 -8. `fact(6)` 通过下一层 `fact(5)` 的计算结果得出 `fact(6) = 6 * 120 = 720`,从而返回函数的最终结果 `720`。 - -这就是阶乘函数的递归计算过程。 - -根据上面的描述,我们可以把阶乘函数的递归计算过程分为两个部分: - -1. 先逐层向下调用自身,直到达到结束条件(即 `n == 0`)。 -2. 然后再向上逐层返回结果,直到返回原问题的解(即返回 `fact(6) == 720`)。 - -这两个部分也可以叫做「递推过程」和「回归过程」,如下面两幅图所示: - -![](https://qcdn.itcharge.cn/images/20220407160648.png) - -![](https://qcdn.itcharge.cn/images/20220407160659.png) - -如上面所说,我们可以把「递归」分为两个部分:「递推过程」和「回归过程」。 - -- **递推过程**:指的是将原问题一层一层地分解为与原问题形式相同、规模更小的子问题,直到达到结束条件时停止,此时返回最底层子问题的解。 -- **回归过程**:指的是从最底层子问题的解开始,逆向逐一回归,最终达到递推开始时的原问题,返回原问题的解。 - -「递推过程」和「回归过程」是递归算法的精髓。从这个角度来理解递归,递归的基本思想就是: **把规模大的问题不断分解为子问题来解决。** - -同时,因为解决原问题和不同规模的小问题往往使用的是相同的方法,所以就产生了函数调用函数自身的情况,这也是递归的定义所在。 - -## 2. 递归和数学归纳法 - -递归的数学模型其实就是「数学归纳法」。这里简单复习一下数学归纳法的证明步骤: - -1. 证明当 $n = b$ ($b$ 为基本情况,通常为 $0$ 或者 $1$)时,命题成立。 -2. 证明当 $n > b$ 时,假设 $n = k$ 时命题成立,那么可以推导出 $n = k + 1$ 时命题成立。这一步不是直接证明的,而是先假设 $n = k$ 时命题成立,利用这个条件,可以推论出 $n = k + 1$ 时命题成立。 - -通过以上两步证明,就可以说:当 $n >= b$ 时,命题都成立。 - -我们可以从「数学归纳法」的角度来解释递归: - -- **递归终止条件**:数学归纳法第一步中的 $n = b$,可以直接得出结果。 -- **递推过程**:数学归纳法第二步中的假设部分(假设 $n = k$ 时命题成立),也就是假设我们当前已经知道了 $n = k$ 时的计算结果。 -- **回归过程**:数学归纳法第二步中的推论部分(根据 $n = k$ 推论出 $n = k + 1$),也就是根据下一层的结果,计算出上一层的结果。 - -事实上,数学归纳法的思考过程也正是在解决某些数列问题时,可以使用递归算法的原因。比如阶乘、数组前 $n$ 项和、斐波那契数列等等。 - -## 3. 递归三步走 - -上面我们提到,递归的基本思想就是: **把规模大的问题不断分解为子问题来解决。** 那么,在写递归的时候,我们可以按照这个思想来书写递归,具体步骤如下: - -1. **写出递推公式**:找到将原问题分解为子问题的规律,并且根据规律写出递推公式。 -2. **明确终止条件**:推敲出递归的终止条件,以及递归终止时的处理方法。 -3. **将递推公式和终止条件翻译成代码**: - 1. 定义递归函数(明确函数意义、传入参数、返回结果等)。 - 2. 书写递归主体(提取重复的逻辑,缩小问题规模)。 - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - -### 3.1 写出递推公式 - -写出递推公式的关键在于:**找到将原问题分解为子问题的规律,并将其抽象成递推公式**。 - -我们在思考递归的逻辑时,没有必要在大脑中将整个递推过程和回归过程一层层地想透彻。很可能还没有递推到栈底呢,脑子就已经先绕晕了。 - -之前讲解的阶乘例子中,一个问题只需要分解为一个子问题,我们很容易能够想清楚「递推过程」和「回归过程」的每一个步骤,所以写起来和理解起来都不难。 - -但是当我们面对的是一个问题需要分解为多个子问题的情况时,就没有那么容易想清楚「递推过程」和「回归过程」的每一个步骤了。 - -那么我们应该如何思考「递推过程」和「回归过程」呢,又该如何写出递归中的递推公式呢? - -如果一个问题 $A$,可以分解为若干个规模较小、与原问题形式相同的子问题 $B$、$C$、$D$,那么这些子问题就可以用相同的解题思路来解决。我们可以假设 $B$、$C$、$D$ 已经解决了,然后只需要考虑在这个基础上去思考如何解决问题 $A$ 即可。不需要再一层层往下思考子问题与子子问题、子子问题与子子子问题之间的关系。这样理解起来就简单多了。 - -从问题 $A$ 到分解为子问题 $B$、$C$、$D$ 的思考过程其实就是递归的「递推过程」。而从子问题 $B$、$C$、$D$ 的解回到问题 $A$ 的解的思考过程其实就是递归的「回归过程」。想清楚了「如何划分子问题」和「如何通过子问题来解决原问题」这两个过程,也就想清楚了递归的「递推过程」和「回归过程」。 - -然后,我们只需要考虑原问题与子问题之间的关系,就能够在此基础上,写出递推公式了。 - -### 3.2 明确终止条件 - -递归的终止条件也叫做递归出口。在写出了递推公式之后,就要考虑递归的终止条件是什么。如果没有递归的终止条件,函数就会无限地递归下去,程序就会失控崩溃了。通常情况下,递归的终止条件是问题的边界值。 - -在找到递归的终止条件时,我们应该直接给出该条件下的处理方法。一般地,在这种情境下,问题的解决方案是直观的、容易的。例如阶乘中 `fact(0) = 1`。斐波那契数列中 `f(1) = 1, f(2) = 2`。 - -### 3.3 将递推公式和终止条件翻译成代码 - -在写出递推公式和明确终止条件之后,我们就可以将其翻译成代码了。这一步也可以分为 3 步来做: - -1. **定义递归函数**:明确函数意义、传入参数、返回结果等。 -2. **书写递归主体**:提取重复的逻辑,缩小问题规模。 -3. **明确递归终止条件**:给出递归终止条件,以及递归终止时的处理方法。 - -#### 3.3.1 定义递归函数 - -在定义递归函数时,一定要明确递归函数的意义,也就是要明白这个问题传入的参数是什么,最终返回的结果是要解决的什么问题。 - -比如说阶乘函数 `fact(n)`,这个函数的传入参数是问题的规模 `n`,最终返回的结果是 `n` 的阶乘值。 - -#### 3.3.2 书写递归主体 - -在将原问题划分为子问题,并根据原问题和子问题的关系,我们就可以推论出对应的递推公式。然后根据递推公式,就可以将其转换为递归的主体代码。 - -#### 3.3.3 明确递归终止条件 - -这一步其实就是将「3.2 明确终止条件」章节中的递归终止条件和终止条件下的处理方法转换为代码中的条件语句和对应的执行语句。 - -#### 3.3.4 递归伪代码 - -根据上述递归书写的步骤,我们就可以写出递归算法的代码了。递归算法的伪代码如下: - -```python -def recursion(大规模问题): - if 递归终止条件: - 递归终止时的处理方法 - - return recursion(小规模问题) -``` - -## 4. 递归的注意点 - -### 4.1 避免栈溢出 - -在程序执行中,递归是利用堆栈来实现的。每一次递推都需要一个栈空间来保存调用记录,每当进入一次函数调用,栈空间就会加一层栈帧。每一次回归,栈空间就会减一层栈帧。由于系统中的栈空间大小不是无限的,所以,如果递归调用的次数过多,会导致栈空间溢出。 - -为了避免栈溢出,我们可以在代码中限制递归调用的最大深度来解决问题。当递归调用超过一定深度时(比如 100)之后,不再进行递归,而是直接返回报错。 - -当然这种做法并不能完全避免栈溢出,也无法完全解决问题,因为系统允许的最大递归深度跟当前剩余的占空间有关,事先无法计算。 - -如果使用递归算法实在无法解决问题,我们可以考虑将递归算法变为非递归算法(即递推算法)来解决栈溢出的问题。 - -### 4.2 避免重复运算 - -在使用递归算法时,还可能会出现重复运算的问题。 - -比如斐波那契数列的定义是: - -$f(n) = \begin{cases} 0 & n = 0 \cr 1 & n = 1 \cr f(n - 2) + f(n - 1) & n > 1 \end{cases}$ - -其对应的递归过程如下图所示: - -![](https://qcdn.itcharge.cn/images/20230307164107.png) - -从图中可以看出:想要计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$,这样 $f(3)$ 就进行了多次计算。同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,就导致了重复计算问题。 - -为了避免重复计算,我们可以使用一个缓存(哈希表、集合或数组)来保存已经求解过的 $f(k)$ 的结果,这也是动态规划算法中的做法。当递归调用用到 $f(k)$ 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。 - -## 5. 递归的应用 - -### 5.1 斐波那契数 - -#### 5.1.1 题目链接 - -- [509. 斐波那契数 - 力扣(LeetCode)](https://leetcode.cn/problems/fibonacci-number/) - -#### 5.1.2 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:计算第 `n` 个斐波那契数。 - -**说明**: - -- 斐波那契数列的定义如下: - - `f(0) = 0, f(1) = 1`。 - - `f(n) = f(n - 1) + f(n - 2)`,其中 `n > 1`。 - - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:1 -解释:F(2) = F(1) + F(0) = 1 + 0 = 1 -``` - -- 示例 2: - -```python -输入:n = 3 -输出:2 -解释:F(3) = F(2) + F(1) = 1 + 1 = 2 -``` - -#### 5.1.3 解题思路 - -##### 思路 1:递归算法 - -根据我们的递推三步走策略,写出对应的递归代码。 - -1. 写出递推公式:`f(n) = f(n - 1) + f(n - 2)`。 -2. 明确终止条件:`f(0) = 0, f(1) = 1`。 -3. 翻译为递归代码: - 1. 定义递归函数:`fib(self, n)` 表示输入参数为问题的规模 `n`,返回结果为第 `n` 个斐波那契数。 - 2. 书写递归主体:`return self.fib(n - 1) + self.fib(n - 2)`。 - 3. 明确递归终止条件: - 1. `if n == 0: return 0` - 2. `if n == 1: return 1` - -##### 思路 1:代码 - -```python -class Solution: - def fib(self, n: int) -> int: - if n == 0: - return 0 - if n == 1: - return 1 - return self.fib(n - 1) + self.fib(n - 2) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。具体证明方法参考 [递归求斐波那契数列的时间复杂度,不要被网上的答案误导了 - 知乎](https://zhuanlan.zhihu.com/p/256344121)。 -- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 - -### 5.2 二叉树的最大深度 - -#### 5.2.1 题目链接 - -- [104. 二叉树的最大深度 - 力扣(LeetCode)](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) - -#### 5.2.2 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:找出该二叉树的最大深度。 - -**说明**: - -- **二叉树的深度**:根节点到最远叶子节点的最长路径上的节点数。 -- **叶子节点**:没有子节点的节点。 - -**示例**: - -- 示例 1: - -```python -输入:[3,9,20,null,null,15,7] -对应二叉树 - 3 - / \ - 9 20 - / \ - 15 7 -输出:3 -解释:该二叉树的最大深度为 3 -``` - -#### 5.2.3 解题思路 - -##### 思路 1: 递归算法 - -根据递归三步走策略,写出对应的递归代码。 - -1. 写出递推公式:`当前二叉树的最大深度 = max(当前二叉树左子树的最大深度, 当前二叉树右子树的最大深度) + 1`。 - - 即:先得到左右子树的高度,在计算当前节点的高度。 -2. 明确终止条件:当前二叉树为空。 -3. 翻译为递归代码: - 1. 定义递归函数:`maxDepth(self, root)` 表示输入参数为二叉树的根节点 `root`,返回结果为该二叉树的最大深度。 - 2. 书写递归主体:`return max(self.maxDepth(root.left) + self.maxDepth(root.right))`。 - 3. 明确递归终止条件:`if not root: return 0` - -##### 思路 1:代码 - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - if not root: - return 0 - - return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - -## 参考资料 - -- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 -- 【书籍】算法训练营 陈小玉 著 -- 【书籍】挑战程序设计竞赛 第 2 版 - 秋叶拓哉,岩田阳一,北川宜稔 著,巫泽俊,庄俊元,李津羽 译 -- 【问答】[对于递归有没有什么好的理解方法? - 知乎 - 方应杭](https://www.zhihu.com/question/31412436/answer/738989709) -- 【问答】[对于递归有没有什么好的理解方法? - 知乎 - 老刘](https://www.zhihu.com/question/31412436/answer/724915708) -- 【博文】[递归 & 分治 - OI Wiki](https://oi-wiki.org/basic/divide-and-conquer/) -- 【博文】[递归详解 - labuladong](https://github.com/labuladong/fucking-algorithm/blob/master/算法思维系列/递归详解.md) -- 【博文】[递归 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41440) -- 【视频】[清华学长带你从宏观角度看递归](https://mp.weixin.qq.com/s/BHY7ZBxIr3UCpIvY4-IVOQ) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md b/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md deleted file mode 100644 index 6aa36c26..00000000 --- a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md +++ /dev/null @@ -1,21 +0,0 @@ -### 递归算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0344 | [反转字符串](https://leetcode.cn/problems/reverse-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 双指针、字符串 | 简单 | -| 0024 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0024.%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.md) | 递归、链表 | 中等 | -| 0118 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md) | 数组、动态规划 | 简单 | -| 0119 | [杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md) | 数组、动态规划 | 简单 | -| 0206 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0092 | [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0092.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%20II.md) | 链表 | 中等 | -| 0021 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0021.%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 递归、链表 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0104 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0104.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 0226 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0226.%20%E7%BF%BB%E8%BD%AC%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0779 | [第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0779.%20%E7%AC%ACK%E4%B8%AA%E8%AF%AD%E6%B3%95%E7%AC%A6%E5%8F%B7.md) | 位运算、递归、数学 | 中等 | -| 0095 | [不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0095.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%20II.md) | 树、二叉搜索树、动态规划、回溯、二叉树 | 中等 | -| 剑指 Offer 62 | [圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2062.%20%E5%9C%86%E5%9C%88%E4%B8%AD%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84%E6%95%B0%E5%AD%97.md) | 递归、数学 | 简单 | - diff --git a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/index.md b/Contents/09.Algorithm-Base/02.Recursive-Algorithm/index.md deleted file mode 100644 index 3500d867..00000000 --- a/Contents/09.Algorithm-Base/02.Recursive-Algorithm/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [递归算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md) -- [递归算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md b/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md deleted file mode 100644 index 4cf0124b..00000000 --- a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md +++ /dev/null @@ -1,256 +0,0 @@ -## 1. 分治算法简介 - -### 1.1 分治算法的定义 - -> **分治算法(Divide and Conquer)**:字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。 - -简单来说,分治算法的基本思想就是: **把规模大的问题不断分解为子问题,使得问题规模减小到可以直接求解为止。** - -![](https://qcdn.itcharge.cn/images/20220413153059.png) - -### 1.2 分治算法和递归算法的异同 - -从定义上来看,分治算法的思想和递归算法的思想是一样的,都是把规模大的问题不断分解为子问题。 - -其实,分治算法和递归算法的关系是包含与被包含的关系,可以看做: **递归算法 ∈ 分治算法**。 - -分治算法从实现方式上来划分,可以分为两种:「递归算法」和「迭代算法」。 - -![](https://qcdn.itcharge.cn/images/20220414093828.png) - -分治算法一般都比较适合使用递归算法来实现。但除了递归算法之外,分治算法还可以通过迭代算法来实现。比较常见的例子有:快速傅里叶变换算法、二分查找算法、非递归实现的归并排序算法等等。 - -### 1.3 分治算法的适用条件 - -分治算法能够解决的问题,一般需要满足以下 $4$ 个条件: - -1. 原问题可以分解为若干个规模较小的相同子问题。 -2. 分解出来的子问题可以独立求解,即子问题之间不包含公共的子子问题。 -3. 具有分解的终止条件,也就是说当问题的规模足够小时,能够用较简单的方法解决。 -4. 子问题的解可以合并为原问题的解,并且合并操作的复杂度不能太高,否则就无法起到减少算法总体复杂度的效果了。 - -## 2. 分治算法的基本步骤 - -使用分治算法解决问题主要分为 $3$ 个步骤: - -1. **分解**:把要解决的问题分解为成若干个规模较小、相对独立、与原问题形式相同的子问题。 -2. **求解**:递归求解各个子问题。 -3. **合并**:按照原问题的要求,将子问题的解逐层合并构成原问题的解。 - -其中第 $1$ 步中将问题分解为若干个子问题时,最好使子问题的规模大致相同。换句话说,将一个问题分成大小相等的 $k$ 个子问题的处理方法是行之有效的。在许多问题中,可以取 $k = 2$。这种使子问题规模大致相等的做法是出自一种平衡子问题的思想,它几乎总是比子问题规模不等的做法要好。 - -其中第 $2$ 步的「递归求解各个子问题」指的是按照同样的分治策略进行求解,即通过将这些子问题分解为更小的子子问题来进行求解。就这样一直分解下去,直到分解出来的子问题简单到只用常数操作时间即可解决为止。 - -在完成第 $2$ 步之后,最小子问题的解可用常数时间求得。然后我们再按照递归算法中回归过程的顺序,由底至上地将子问题的解合并起来,逐级上推就构成了原问题的解。 - -按照分而治之的策略,在编写分治算法的代码时,也是按照上面的 $3$ 个步骤来编写的,其对应的伪代码如下: - -```python -def divide_and_conquer(problem): # problem 为问题规模 - if problem < d: # 当问题规模足够小时,直接解决该问题 - return solove(); # 直接求解 - - k_problems = divide(problem) # 将问题分解为 k 个相同形式的子问题 - - res = [0 for _ in range(k)] # res 用来保存 k 个子问题的解 - for k_problem in k_problems: - res[i] = divide_and_conquer(k_problem) # 递归的求解 k 个子问题 - - ans = merge(res) # 合并 k 个子问题的解 - return ans # 返回原问题的解 -``` - -## 3. 分治算法的复杂度分析 - - 分治算法中,在不断递归后,最后的子问题将变得极为简单,可在常数操作时间内予以解决,其带来的时间复杂度在整个分治算法中的比重微乎其微,可以忽略不计。所以,分治算法的时间复杂度实际上是由「分解」和「合并」两个部分构成的。 - -一般来讲,分治算法将一个问题划分为 $a$ 个形式相同的子问题,每个子问题的规模为 $n/b$,则总的时间复杂度的递归表达式可以表示为: - -$T(n) = \begin{cases} \begin{array} \ \Theta{(1)} & n = 1 \cr a * T(n/b) + f(n) & n > 1 \end{array} \end{cases}$ - -其中,每次分解时产生的子问题个数是 $a$ ,每个子问题的规模是原问题规模的 $1 / b$,分解和合并 $a$ 个子问题的时间复杂度是 $f(n)$。 - -这样,求解一个分治算法的时间复杂度,就是求解上述递归表达式。关于递归表达式的求解有多种方法,这里我们介绍一下比较常用的「递推求解法」和「递归树法」。 - -### 3.1 递推求解法 - -根据问题的递归表达式,通过一步步递推分解推导,从而得到最终结果。 - -以「归并排序算法」为例,接下来我们通过递推求解法计算一下归并排序算法的时间复杂度。 - -我们得出归并排序算法的递归表达式如下: - -$T(n) = \begin{cases} \begin{array} \ O{(1)} & n = 1 \cr 2T(n/2) + O(n) & n > 1 \end{array} \end{cases}$ - -根据归并排序的递归表达式,当 $n > 1$ 时,可以递推求解: - -$\begin{align} T(n) & = 2T(n/2) + O(n) \cr & = 2(2T(n / 4) + O(n/2)) + O(n) \cr & = 4T(n/4) + 2O(n) \cr & = 8T(n/8) + 3O(n) \cr & = …… \cr & = 2^x \times T(n/2^x) + x \times O(n) \end{align}$ - -递推最终规模为 $1$,令 $n = 2^x$,则 $x = \log_2n$,则: - -$\begin{align} T(n) & = n \times T(1) + \log_2n \times O(n) \cr & = n + \log_2n \times O(n) \cr & = O(n \times \log_2n) \end{align}$ - -则归并排序的时间复杂度为 $O(n \times \log_2n)$。 - -### 3.2 递归树法 - -递归树求解方式其实和递推求解一样,只不过递归树能够更清楚直观的显示出来,更能够形象地表达每层分解的节点和每层产生的时间成本。 - -使用递归树法计算时间复杂度的公式为: - -$\text{时间复杂度} = \text{叶子数} \times T(1) + \text{成本和} = 2^x \times T(1) + x \times O(n)$。 - -我们还是以「归并排序算法」为例,通过递归树法计算一下归并排序算法的时间复杂度。 - -归并排序算法的递归表达式如下: - -$T(n) = \begin{cases} \begin{array} \ O{(1)} & n = 1 \cr 2T(n/2) + O(n) & n > 1 \end{array} \end{cases}$ - -其对应的递归树如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220414171458.png) - -因为 $n = 2^x$,则 $x = \log_2n$,则归并排序算法的时间复杂度为:$2^x \times T(1) + x \times O(n) = n + \log_2n \times O(n) = O(n \times log_2n)$。 - -## 4. 分治算法的应用 - -### 4.1 归并排序 - -#### 4.1.1 题目链接 - -- [912. 排序数组 - 力扣(LeetCode) ](https://leetcode.cn/problems/sort-an-array/) - -#### 4.1.2 题目大意 - -**描述**:给定一个整数数组 `nums`。 - -**要求**:对该数组升序排列。 - -**说明**: - -- $1 \le nums.length \le 5 * 10^4$。 -- $-5 * 10^4 \le nums[i] \le 5 * 10^4$。 - -**示例**: - -```python -输入 nums = [5,2,3,1] -输出 [1,2,3,5] -``` - -#### 4.1.3 解题思路 - -我们使用归并排序算法来解决这道题。 - -1. **分解**:将待排序序列中的 $n$ 个元素分解为左右两个各包含 $\frac{n}{2}$ 个元素的子序列。 -2. **求解**:递归将子序列进行分解和排序,直到所有子序列长度为 $1$。 -3. **合并**:把当前序列组中有序子序列逐层向上,进行两两合并。 - -使用归并排序算法对数组排序的过程如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220414204405.png) - -#### 4.1.4 代码 - -```python -class Solution: - def merge(self, left_arr, right_arr): # 合并 - arr = [] - while left_arr and right_arr: # 将两个排序数组中较小元素依次插入到结果数组中 - if left_arr[0] <= right_arr[0]: - arr.append(left_arr.pop(0)) - else: - arr.append(right_arr.pop(0)) - - while left_arr: # 如果左子序列有剩余元素,则将其插入到结果数组中 - arr.append(left_arr.pop(0)) - while right_arr: # 如果右子序列有剩余元素,则将其插入到结果数组中 - arr.append(right_arr.pop(0)) - return arr # 返回排好序的结果数组 - - def mergeSort(self, arr): # 分解 - if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 - return arr - - mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 - left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分解和排序 - right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分解和排序 - return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 - - def sortArray(self, nums: List[int]) -> List[int]: - return self.mergeSort(nums) -``` - -### 4.2 二分查找 - -#### 4.2.1 题目链接 - -- [704. 二分查找 - 力扣(LeetCode)](https://leetcode.cn/problems/binary-search/) - -#### 4.2.2 题目大意 - -**描述**:给定一个含有 $n$ 个元素有序的(升序)整型数组 `nums` 和一个目标值 `target`。 - -**要求**:返回 `target` 在数组 `nums` 中的位置,如果找不到,则返回 $-1$。 - -**说明**: - -- 假设 `nums` 中的所有元素是不重复的。 -- $n$ 将在 $[1, 10000]$ 之间。 -- $-9999 \le nums[i] \le 9999$。 - -**示例**: - -```python -输入 nums = [-1,0,3,5,9,12], target = 9 -输出 4 -解释 9 出现在 nums 中并且下标为 4 -``` - -#### 4.2.3 解题思路 - -我们使用分治算法来解决这道题。与其他分治题目不一样的地方是二分查找不用进行合并过程,最小子问题的解就是原问题的解。 - -1. **分解**:将数组的 $n$ 个元素分解为左右两个各包含 $\frac{n}{2}$ 个元素的子序列。 -2. **求解**:取中间元素 `nums[mid]` 与 `target` 相比。 - 1. 如果相等,则找到该元素; - 2. 如果 `nums[mid] < target`,则递归在左子序列中进行二分查找。 - 3. 如果 `nums[mid] > target`,则递归在右子序列中进行二分查找。 - -二分查找的的分治算法过程如下图所示。 - -![](https://qcdn.itcharge.cn/images/20211223115032.png) - -#### 4.2.4 代码 - -二分查找问题的非递归实现的分治算法代码如下: - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left = 0 - right = len(nums) - 1 - # 在区间 [left, right] 内查找 target - while left < right: - # 取区间中间节点 - mid = left + (right - left) // 2 - # nums[mid] 小于目标值,排除掉不可能区间 [left, mid],在 [mid + 1, right] 中继续搜索 - if nums[mid] < target: - left = mid + 1 - # nums[mid] 大于等于目标值,目标元素可能在 [left, mid] 中,在 [left, mid] 中继续搜索 - else: - right = mid - # 判断区间剩余元素是否为目标元素,不是则返回 -1 - return left if nums[left] == target else -1 -``` - -## 参考资料 - -- 【书籍】趣学算法 - 陈小玉 著 -- 【书籍】算法之道 - 邹恒铭 著 -- 【书籍】算法图解 - 袁国忠 译 -- 【书籍】算法训练营 陈小玉 著 -- 【博文】[从合并排序算法看“分治法” - 船长&CAP - 博客园](https://www.cnblogs.com/liuning8023/archive/2012/06/25/2562747.html) -- 【博文】[递归、迭代、分治、回溯、动态规划、贪心算法 - 力扣](https://leetcode.cn/circle/article/yXFal5/) -- 【博文】[递归 & 分治 - OI Wiki](https://oi-wiki.org/basic/divide-and-conquer/) -- 【博文】[漫画:5分钟弄懂分治算法!它和递归算法的关系!](https://mp.weixin.qq.com/s/0Z1tiqWTO410jYTJ4K0Ihg) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md b/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md deleted file mode 100644 index 2c383b83..00000000 --- a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md +++ /dev/null @@ -1,13 +0,0 @@ -### 分治算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0004 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0004.%20%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、二分查找、分治 | 困难 | -| 0023 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0023.%20%E5%90%88%E5%B9%B6%20K%20%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0241 | [为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0241.%20%E4%B8%BA%E8%BF%90%E7%AE%97%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%BE%E8%AE%A1%E4%BC%98%E5%85%88%E7%BA%A7.md) | 递归、记忆化搜索、数学、字符串、动态规划 | 中等 | -| 0169 | [多数元素](https://leetcode.cn/problems/majority-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0169.%20%E5%A4%9A%E6%95%B0%E5%85%83%E7%B4%A0.md) | 数组、哈希表、分治、计数、排序 | 简单 | -| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 递归、数学 | 中等 | -| 0014 | [最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0014.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80.md) | 字典树、字符串 | 简单 | -| 剑指 Offer 33 | [二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E5%89%91%E6%8C%87%20Offer%2033.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 栈、树、二叉搜索树、递归、二叉树、单调栈 | 中等 | - diff --git a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/index.md b/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/index.md deleted file mode 100644 index 0cb593e7..00000000 --- a/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [分治算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md) -- [分治算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md b/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md deleted file mode 100644 index 0db8402f..00000000 --- a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md +++ /dev/null @@ -1,419 +0,0 @@ -## 1. 回溯算法简介 - -> **回溯算法(Backtracking)**:一种能避免不必要搜索的穷举式的搜索算法。采用试错的思想,在搜索尝试过程中寻找问题的解,当探索到某一步时,发现原先的选择并不满足求解条件,或者还需要满足更多求解条件时,就退回一步(回溯)重新选择,这种走不通就退回再走的技术称为「回溯法」,而满足回溯条件的某个状态的点称为「回溯点」。 - -简单来说,回溯算法采用了一种 **「走不通就回退」** 的算法思想。 - -回溯算法通常用简单的递归方法来实现,在进行回溯过程中更可能会出现两种情况: - -1. 找到一个可能存在的正确答案; -2. 在尝试了所有可能的分布方法之后宣布该问题没有答案。 - -## 2. 从全排列问题开始理解回溯算法 - -以求解 `[1, 2, 3]` 的全排列为例,我们来讲解一下回溯算法的过程。 - -1. 选择以 `1` 为开头的全排列。 - 1. 选择以 `2` 为中间数字的全排列,则最后数字只能选择 `3`。即排列为:`[1, 2, 3]`。 - 2. 撤销选择以 `3` 为最后数字的全排列,再撤销选择以 `2` 为中间数字的全排列。然后选择以 `3` 为中间数字的全排列,则最后数字只能选择 `2`,即排列为:`[1, 3, 2]`。 -2. 撤销选择以 `2` 为最后数字的全排列,再撤销选择以 `3` 为中间数字的全排列,再撤销选择以 `1` 为开头的全排列。然后选择以 `2` 开头的全排列。 - 1. 选择以 `1` 为中间数字的全排列,则最后数字只能选择 `3`。即排列为:`[2, 1, 3]`。 - 2. 撤销选择以 `3` 为最后数字的全排列,再撤销选择以 `1` 为中间数字的全排列。然后选择以 `3` 为中间数字的全排列,则最后数字只能选择 `1`,即排列为:`[2, 3, 1]`。 -3. 撤销选择以 `1` 为最后数字的全排列,再撤销选择以 `3` 为中间数字的全排列,再撤销选择以 `2` 为开头的全排列,选择以 `3` 开头的全排列。 - 1. 选择以 `1` 为中间数字的全排列,则最后数字只能选择 `2`。即排列为:`[3, 1, 2]`。 - 2. 撤销选择以 `2` 为最后数字的全排列,再撤销选择以 `1` 为中间数字的全排列。然后选择以 `2` 为中间数字的全排列,则最后数字只能选择 `1`,即排列为:`[3, 2, 1]`。 - -总结一下全排列的回溯过程: - -- **按顺序枚举每一位上可能出现的数字,之前已经出现的数字在接下来要选择的数字中不能再次出现。** -- 对于每一位,进行如下几步: - 1. **选择元素**:从可选元素列表中选择一个之前没有出现过的元素。 - 2. **递归搜索**:从选择的元素出发,一层层地递归搜索剩下位数,直到遇到边界条件时,不再向下搜索。 - 3. **撤销选择**:一层层地撤销之前选择的元素,转而进行另一个分支的搜索。直到完全遍历完所有可能的路径。 - -对于上述决策过程,我们也可以用一棵决策树来表示: - -![](https://qcdn.itcharge.cn/images/20220425102048.png) - -从全排列的决策树中我们可以看出: - -- 每一层中有一个或多个不同的节点,这些节点以及节点所连接的分支代表了「不同的选择」。 -- 每一个节点代表了求解全排列问题的一个「状态」,这些状态是通过「不同的值」来表现的。 -- 每向下递推一层就是在「可选元素列表」中选择一个「元素」加入到「当前状态」。 -- 当一个决策分支探索完成之后,会逐层向上进行回溯。 -- 每向上回溯一层,就是把所选择的「元素」从「当前状态」中移除,回退到没有选择该元素时的状态(或者说重置状态),从而进行其他分支的探索。 - -根据上文的思路和决策树,我们来写一下全排列的回溯算法代码(假设给定数组 `nums` 中不存在重复元素)。则代码如下所示: - -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - res = [] # 存放所有符合条件结果的集合 - path = [] # 存放当前符合条件的结果 - def backtracking(nums): # nums 为选择元素列表 - if len(path) == len(nums): # 说明找到了一组符合条件的结果 - res.append(path[:]) # 将当前符合条件的结果放入集合中 - return - - for i in range(len(nums)): # 枚举可选元素列表 - if nums[i] not in path: # 从当前路径中没有出现的数字中选择 - path.append(nums[i]) # 选择元素 - backtracking(nums) # 递归搜索 - path.pop() # 撤销选择 - - backtracking(nums) - return res -``` - -## 3. 回溯算法的通用模板 - -根据上文全排列的回溯算法代码,我们可以提炼出回溯算法的通用模板,回溯算法的通用模板代码如下所示: - -```python -res = [] # 存放所欲符合条件结果的集合 -path = [] # 存放当前符合条件的结果 -def backtracking(nums): # nums 为选择元素列表 - if 遇到边界条件: # 说明找到了一组符合条件的结果 - res.append(path[:]) # 将当前符合条件的结果放入集合中 - return - - for i in range(len(nums)): # 枚举可选元素列表 - path.append(nums[i]) # 选择元素 - backtracking(nums) # 递归搜索 - path.pop() # 撤销选择 - -backtracking(nums) -``` - -## 4. 回溯算法三步走 - -网络上给定的回溯算法解题步骤比较抽象,这里只做一下简单介绍。 - -1. **根据所给问题,定义问题的解空间**:要定义合适的解空间,包括解的组织形式和显约束。 - - **解的组织形式**:将解的组织形式都规范为⼀个 `n` 元组 ${x_1, x_2 …, x_n}$。 - - **显约束**:对解分量的取值范围的限定,可以控制解空间的大小。 -2. **确定解空间的组织结构**:解空间的组织结构通常以解空间树的方式形象地表达,根据解空间树的不同,解空间分为⼦集树、排列树、`m` 叉树等。 -3. **搜索解空间**:按照深度优先搜索策略,根据隐约束(约束函数和限界函数),在解空间中搜索问题的可⾏解或最优解。当发现当 前节点不满⾜求解条件时,就回溯,尝试其他路径。 - - 如果问题只是求可⾏解,则只需设定约束函数即可,如果要求最优解,则需要设定约束函数和限界函数。 - -这种回溯算法的解题步骤太过于抽象,不利于我们在日常做题时进行思考。其实在递归算法知识的相关章节中,我们根据递归的基本思想总结了递归三步走的书写步骤。同样,根据回溯算法的基本思想,我们也来总结一下回溯算法三步走的书写步骤。 - -回溯算法的基本思想是:**以深度优先搜索的方式,根据产生子节点的条件约束,搜索问题的解。当发现当前节点已不满足求解条件时,就「回溯」返回,尝试其他的路径。** - -那么,在写回溯算法时,我们可以按照这个思想来书写回溯算法,具体步骤如下: - -1. **明确所有选择**:画出搜索过程的决策树,根据决策树来确定搜索路径。 -2. **明确终止条件**:推敲出递归的终止条件,以及递归终止时的要执行的处理方法。 -3. **将决策树和终止条件翻译成代码:** - 1. 定义回溯函数(明确函数意义、传入参数、返回结果等)。 - 2. 书写回溯函数主体(给出约束条件、选择元素、递归搜索、撤销选择部分)。 - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - -### 4.1 明确所有选择 - -决策树是帮助我们理清搜索过程的一个很好的工具。我们可以画出搜索过程的决策树,根据决策树来帮助我们确定搜索范围和对应的搜索路径。 - -### 4.2 明确终止条件 - -回溯算法的终止条件也就是决策树的底层,即达到无法再做选择的条件。 - -回溯函数的终止条件一般为给定深度、叶子节点、非叶子节点(包括根节点)、所有节点等。并且还要给出在终止条件下的处理方法,比如输出答案,将当前符合条件的结果放入集合中等等。 - -### 4.3 将决策树和终止条件翻译成代码 - -在明确所有选择和明确终止条件之后,我们就可以将其翻译成代码了。这一步也可以分为 `3` 步来做: - -1. 定义回溯函数(明确函数意义、传入参数、返回结果等)。 -2. 书写回溯函数主体(给出约束条件、选择元素、递归搜索、撤销选择部分)。 -3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - -#### 4.3.1 定义回溯函数 - -在定义回溯函数时,一定要明确递归函数的意义,也就是要明白这个问题的传入参数和全局变量是什么,最终返回的结果是要解决的什么问题。 - -- **传入参数和全局变量**:是由递归搜索阶段时的「当前状态」来决定的。最好是能通过传入参数和全局变量直接记录「当前状态」。 - -比如全排列中,`backtracking(nums)` 这个函数的传入参数是 `nums`(可选择的元素列表),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。`nums` 表示当前可选的元素,`path` 用于记录递归搜索阶段的「当前状态」。`res` 则用来保存递归搜索阶段的「所有状态」。 - -- **返回结果**:返回结果是在遇到递归终止条件时,需要向上一层函数返回的信息。 - -一般回溯函数的返回结果都是单个节点或单个数值,告诉上一层函数我们当前的搜索结果是什么即可。 - -当然,如果使用全局变量来保存「当前状态」的话,也可以不需要向上一层函数返回结果,即返回空结果。比如上文中的全排列。 - -#### 4.3.2 书写回溯函数主体 - -根据当前可选择的元素列表、给定的约束条件(例如之前已经出现的数字在接下来要选择的数字中不能再次出现)、存放当前状态的变量,我们就可以写出回溯函数的主体部分了。即: - -```python -for i in range(len(nums)): # 枚举可选元素列表 - if 满足约束条件: # 约束条件 - path.append(nums[i]) # 选择元素 - backtracking(nums) # 递归搜索 - path.pop() # 撤销选择 -``` - -#### 4.3.3 明确递归终止条件 - -这一步其实就是将「4.2 明确终止条件」章节中的递归终止条件和终止条件下的处理方法转换为代码中的条件语句和对应的执行语句。 - -## 5. 回溯算法的应用 - -### 5.1 子集 - -#### 5.1.1 题目链接 - -- [78. 子集 - 力扣(LeetCode)](https://leetcode.cn/problems/subsets/) - -#### 5.1.2 题目大意 - -**描述**:给定一个整数数组 `nums`,数组中的元素互不相同。 - -**要求**:返回该数组所有可能的不重复子集。可以按任意顺序返回解集。 - -**说明**: - -- $1 \le nums.length \le 10$。 -- $-10 \le nums[i] \le 10$。 -- `nums` 中的所有元素互不相同。 - -**示例**: - -- 示例 1: - -```python -输入 nums = [1,2,3] -输出 [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] -``` - -- 示例 2: - -```python -输入:nums = [0] -输出:[[],[0]] -``` - -#### 5.1.3 解题思路 - -##### 思路 1:回溯算法 - -数组的每个元素都有两个选择:选与不选。 - -我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:根据数组中每个位置上的元素选与不选两种选择,画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220425210640.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - 1. 定义回溯函数: - - - `backtracking(nums, index):` 函数的传入参数是 `nums`(可选数组列表)和 `index`(代表当前正在考虑元素是 `nums[i]` ),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 - - `backtracking(nums, index):` 函数代表的含义是:在选择 `nums[index]` 的情况下,递归选择剩下的元素。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: - - 约束条件:之前选过的元素不再重复选用。每次从 `index` 位置开始遍历而不是从 `0` 位置开始遍历就是为了避免重复。集合跟全排列不一样,子集中 `{1, 2}` 和 `{2, 1}` 是等价的。为了避免重复,我们之前考虑过的元素,就不再重复考虑了。 - - 选择元素:将其添加到当前子集数组 `path` 中。 - - 递归搜索:在选择该元素的情况下,继续递归考虑下一个位置上的元素。 - - 撤销选择:将该元素从当前子集数组 `path` 中移除。 - ```python - for i in range(index, len(nums)): # 枚举可选元素列表 - path.append(nums[i]) # 选择元素 - backtracking(nums, i + 1) # 递归搜索 - path.pop() # 撤销选择 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是当正在考虑的元素位置到达数组末尾(即 `start >= len(nums)`)时,递归停止。 - - 从决策树中也可以看出,子集需要存储的答案集合应该包含决策树上所有的节点,应该需要保存递归搜索的所有状态。所以无论是否达到终止条件,我们都应该将当前符合条件的结果放入到集合中。 - -##### 思路 1:代码 - -```python -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - res = [] # 存放所有符合条件结果的集合 - path = [] # 存放当前符合条件的结果 - def backtracking(nums, index): # 正在考虑可选元素列表中第 index 个元素 - res.append(path[:]) # 将当前符合条件的结果放入集合中 - if index >= len(nums): # 遇到终止条件(本题) - return - - for i in range(index, len(nums)): # 枚举可选元素列表 - path.append(nums[i]) # 选择元素 - backtracking(nums, i + 1) # 递归搜索 - path.pop() # 撤销选择 - - backtracking(nums, 0) - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 -- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 - -### 5.2 N 皇后 - -#### 5.2.1 题目链接 - -- [51. N 皇后 - 力扣(LeetCode)](https://leetcode.cn/problems/n-queens/) - -#### 5.2.2 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:返回所有不同的「`n` 皇后问题」的解决方案。每一种解法包含一个不同的「`n` 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 - -**说明**: - -- **n 皇后问题**:将 `n` 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 -- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 -- $1 \le n \le 9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] -解释:如下图所示,4 皇后问题存在 2 个不同的解法。 -``` - -![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) - -#### 5.2.3 解题思路 - -##### 思路 1:回溯算法 - -这道题是经典的回溯问题。我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 - -对于 `n * n` 的棋盘来说,每一行有 `n` 列,也就有 `n` 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 - -并且在放置完之后,通过回溯的方式尝试其他可能的分支。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220426095225.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - 首先我们先使用一个 `n * n` 大小的二维矩阵 `chessboard` 来表示当前棋盘,`chessboard` 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 - - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 `chessboard`(棋盘数组)和 `row`(代表当前正在考虑放置第 `row` 行皇后),全局变量是 `res`(存放所有符合条件结果的集合数组)。 - - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 `row` 行皇后的情况下,递归放置剩下行的皇后。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 枚举出当前行所有的列。对于每一列位置: - - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 - - 选择元素:选择 `row, col` 位置放置皇后,将其棋盘对应位置设置为 `Q`。 - - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 - - 撤销选择:将棋盘上 `row, col` 位置设置为 `.`。 - - ```python - # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - ``` - - ```python - for col in range(n): # 枚举可放置皇后的列 - if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 - chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 - backtrack(row + 1, chessboard) # 递归放置 row + 1 行之后的皇后 - chessboard[row][col] = '.' # 撤销选择 row, col 位置 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后(即 `row == n`)时,递归停止。 - - 递归停止时,将当前符合条件的棋盘转换为答案需要的形式,然后将其存入答案数组 `res` 中即可。 - -##### 思路 1:代码 - -```python -class Solution: - res = [] - def backtrack(self, n: int, row: int, chessboard: List[List[str]]): - if row == n: - temp_res = [] - for temp in chessboard: - temp_str = ''.join(temp) - temp_res.append(temp_str) - self.res.append(temp_res) - return - for col in range(n): - if self.isValid(n, row, col, chessboard): - chessboard[row][col] = 'Q' - self.backtrack(n, row + 1, chessboard) - chessboard[row][col] = '.' - - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - - def solveNQueens(self, n: int) -> List[List[str]]: - self.res.clear() - chessboard = [['.' for _ in range(n)] for _ in range(n)] - self.backtrack(n, 0, chessboard) - return self.res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 -- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 - - -## 参考资料 - -- 【题解】[回溯算法入门级详解 + 练习(持续更新) - 全排列 - 力扣](https://leetcode.cn/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/) -- 【题解】[「代码随想录」带你学透回溯算法!51. N-Queens - N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/solution/dai-ma-sui-xiang-lu-51-n-queenshui-su-fa-2k32/) -- 【文章】[回溯算法详解](https://mp.weixin.qq.com/s/trILKSiN9EoS58pXmvUtUQ) -- 【文章】[回溯算法详解修订版 - labuladong](https://github.com/labuladong/fucking-algorithm/blob/master/算法思维系列/回溯算法详解修订版.md) -- 【文章】[【算法】回溯法四步走 - Nemo& - 博客园](https://www.cnblogs.com/blknemo/p/12431911.html) diff --git a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md b/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md deleted file mode 100644 index 3446cfa9..00000000 --- a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md +++ /dev/null @@ -1,21 +0,0 @@ -### 回溯算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0046 | [全排列](https://leetcode.cn/problems/permutations/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0046.%20%E5%85%A8%E6%8E%92%E5%88%97.md) | 数组、回溯 | 中等 | -| 0047 | [全排列 II](https://leetcode.cn/problems/permutations-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0047.%20%E5%85%A8%E6%8E%92%E5%88%97%20II.md) | 数组、回溯 | 中等 | -| 0037 | [解数独](https://leetcode.cn/problems/sudoku-solver/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0037.%20%E8%A7%A3%E6%95%B0%E7%8B%AC.md) | 数组、哈希表、回溯、矩阵 | 困难 | -| 0022 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0022.%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90.md) | 字符串、动态规划、回溯 | 中等 | -| 0017 | [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0017.%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.md) | 哈希表、字符串、回溯 | 中等 | -| 0784 | [字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0784.%20%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%B0%8F%E5%86%99%E5%85%A8%E6%8E%92%E5%88%97.md) | 位运算、字符串、回溯 | 中等 | -| 0039 | [组合总和](https://leetcode.cn/problems/combination-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0039.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md) | 数组、回溯 | 中等 | -| 0040 | [组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0040.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20II.md) | 数组、回溯 | 中等 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0090 | [子集 II](https://leetcode.cn/problems/subsets-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md) | 位运算、数组、回溯 | 中等 | -| 0473 | [火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0473.%20%E7%81%AB%E6%9F%B4%E6%8B%BC%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1593 | [拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1593.%20%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E5%94%AF%E4%B8%80%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE%E6%9C%80%E5%A4%A7.md) | 哈希表、字符串、回溯 | 中等 | -| 1079 | [活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1079.%20%E6%B4%BB%E5%AD%97%E5%8D%B0%E5%88%B7.md) | 哈希表、字符串、回溯、计数 | 中等 | -| 0093 | [复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0093.%20%E5%A4%8D%E5%8E%9F%20IP%20%E5%9C%B0%E5%9D%80.md) | 字符串、回溯 | 中等 | -| 0079 | [单词搜索](https://leetcode.cn/problems/word-search/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0079.%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2.md) | 数组、回溯、矩阵 | 中等 | -| 0679 | [24 点游戏](https://leetcode.cn/problems/24-game/) | | 数组、数学、回溯 | 困难 | - diff --git a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/index.md b/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/index.md deleted file mode 100644 index 6044c40a..00000000 --- a/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [回溯算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md) -- [回溯算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md b/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md deleted file mode 100644 index 91c32e7a..00000000 --- a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md +++ /dev/null @@ -1,241 +0,0 @@ -## 1. 贪心算法简介 - -### 1.1 贪心算法的定义 - -> **贪心算法(Greedy Algorithm)**:一种在每次决策时,总是采取在当前状态下的最好选择,从而希望导致结果是最好或最优的算法。 - -贪心算法是一种改进的「分步解决算法」,其核心思想是:将求解过程分成「若干个步骤」,然后根据题意选择一种「度量标准」,每个步骤都应用「贪心原则」,选取当前状态下「最好 / 最优选择(局部最优解)」,并以此希望最后得出的结果也是「最好 / 最优结果(全局最优解)」。 - -换句话说,贪心算法不从整体最优上加以考虑,而是一步一步进行,每一步只以当前情况为基础,根据某个优化测度做出局部最优选择,从而省去了为找到最优解要穷举所有可能所必须耗费的大量时间。 - -### 1.2 贪心算法的特征 - -对许多问题来说,可以使用贪心算法,通过局部最优解而得到整体最优解或者是整体最优解的近似解。但并不是所有问题,都可以使用贪心算法的。 - -一般来说,这些能够使用贪心算法解决的问题必须满足下面的两个特征: - -1. **贪⼼选择性质** -2. **最优子结构** - -#### 1.2.1 贪心选择性质 - -> **贪心选择**:指的是一个问题的全局最优解可以通过一系列局部最优解(贪心选择)来得到。 - -换句话说,当进行选择时,我们直接做出在当前问题中看来最优的选择,而不用去考虑子问题的解。在做出选择之后,才会去求解剩下的子问题,如下图所示。 - -![](https://qcdn.itcharge.cn/images/20220511174939.png) - -贪心算法在进行选择时,可能会依赖之前做出的选择,但不会依赖任何将来的选择或是子问题的解。运用贪心算法解决的问题在程序的运行过程中无回溯过程。 - -#### 1.2.2 最优子结构性质 - -> **最优子结构**:指的是一个问题的最优解包含其子问题的最优解。 - -问题的最优子结构性质是该问题能否用贪心算法求解的关键。 - -举个例子,如下图所示,原问题 $S = \lbrace a_1, a_2, a_3, a_4 \rbrace$,在 $a_1$ 步我们通过贪心选择选出一个当前最优解之后,问题就转换为求解子问题 $S_{\text{子问题}} = \lbrace a_2, a_3, a_4 \rbrace$。如果原问题 $S$ 的最优解可以由「第 $a_1$ 步通过贪心选择的局部最优解」和「 $S_{\text{子问题}}$ 的最优解」构成,则说明该问题满足最优子结构性质。 - -也就是说,如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。 - -![](https://qcdn.itcharge.cn/images/20220511175042.png) - -在做了贪心选择后,满足最优子结构性质的原问题可以分解成规模更小的类似子问题来解决,并且可以通过贪心选择和子问题的最优解推导出问题的最优解。 - -反之,如果不能利用子问题的最优解推导出整个问题的最优解,那么这种问题就不具有最优子结构。 - -### 1.3 贪心算法正确性的证明 - -贪心算法最难的部分不在于问题的求解,而在于是正确性的证明。我们常用的证明方法有「数学归纳法」和「交换论证法」。 - -> - **数学归纳法**:先计算出边界情况(例如 $n = 1$)的最优解,然后再证明对于每个 $n$,$F_{n + 1}$ 都可以由 $F_n$ 推导出。 -> -> - **交换论证法**:从最优解出发,在保证全局最优不变的前提下,如果交换方案中任意两个元素 / 相邻的两个元素后,答案不会变得更好,则可以推定目前的解是最优解。 - -判断一个问题是否通过贪心算法求解,是需要进行严格的数学证明的。但是在日常写题或者算法面试中,不太会要求大家去证明贪心算法的正确性。 - -所以,当我们想要判断一个问题是否通过贪心算法求解时,我们可以: - -1. **凭直觉**:如果感觉这道题可以通过「贪心算法」去做,就尝试找到局部最优解,再推导出全局最优解。 -2. **举反例**:尝试一下,举出反例。也就是说找出一个局部最优解推不出全局最优解的例子,或者找出一个替换当前子问题的最优解,可以得到更优解的例子。如果举不出反例,大概率这道题是可以通过贪心算法求解的。 - -## 3. 贪心算法三步走 - -1. **转换问题**:将优化问题转换为具有贪心选择性质的问题,即先做出选择,再解决剩下的一个子问题。 -2. **贪心选择性质**:根据题意选择一种度量标准,制定贪心策略,选取当前状态下「最好 / 最优选择」,从而得到局部最优解。 -3. **最优子结构性质**:根据上一步制定的贪心策略,将贪心选择的局部最优解和子问题的最优解合并起来,得到原问题的最优解。 - -## 4. 贪心算法的应用 - -### 4.1 分发饼干 - -#### 4.1.1 题目链接 - -- [455. 分发饼干 - 力扣](https://leetcode.cn/problems/assign-cookies/) - -#### 4.1.2 题目大意 - -**描述**:一位很棒的家长为孩子们分发饼干。对于每个孩子 `i`,都有一个胃口值 `g[i]`,即每个小孩希望得到饼干的最小尺寸值。对于每块饼干 `j`,都有一个尺寸值 `s[j]`。只有当 `s[j] > g[i]` 时,我们才能将饼干 `j` 分配给孩子 `i`。每个孩子最多只能给一块饼干。 - -现在给定代表所有孩子胃口值的数组 `g` 和代表所有饼干尺寸的数组 `j`。 - -**要求**:尽可能满足越多数量的孩子,并求出这个最大数值。 - -**说明**: - -- $1 \le g.length \le 3 * 10^4$。 -- $0 \le s.length \le 3 * 10^4$。 -- $1 \le g[i], s[j] \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:g = [1,2,3], s = [1,1] -输出:1 -解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1, 2, 3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以应该输出 1。 -``` - -- 示例 2: - -```python -输入: g = [1,2], s = [1,2,3] -输出: 2 -解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1, 2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2。 -``` - -#### 4.1.3 解题思路 - -##### 思路 1:贪心算法 - -为了尽可能的满⾜更多的⼩孩,而且一块饼干不能掰成两半,所以我们应该尽量让胃口小的孩子吃小块饼干,这样胃口大的孩子才有大块饼干吃。 - -所以,从贪心算法的角度来考虑,我们应该按照孩子的胃口从小到大对数组 `g` 进行排序,然后按照饼干的尺寸大小从小到大对数组 `s` 进行排序,并且对于每个孩子,应该选择满足这个孩子的胃口且尺寸最小的饼干。 - -下面我们使用贪心算法三步走的方法解决这道题。 - -1. **转换问题**:将原问题转变为,当胃口最小的孩子选择完满足这个孩子的胃口且尺寸最小的饼干之后,再解决剩下孩子的选择问题(子问题)。 -2. **贪心选择性质**:对于当前孩子,用尺寸尽可能小的饼干满足这个孩子的胃口。 -3. **最优子结构性质**:在上面的贪心策略下,当前孩子的贪心选择 + 剩下孩子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得满足胃口的孩子数量达到最大。 - -使用贪心算法的代码解决步骤描述如下: - -1. 对数组 `g`、`s` 进行从小到大排序,使用变量 `index_g` 和 `index_s` 分别指向 `g`、`s` 初始位置,使用变量 `res` 保存结果,初始化为 `0`。 -2. 对比每个元素 `g[index_g]` 和 `s[index_s]`: - 1. 如果 `g[index_g] <= s[index_s]`,说明当前饼干满足当前孩子胃口,则答案数量加 `1`,并且向右移动 `index_g` 和 `index_s`。 - 2. 如果 `g[index_g] > s[index_s]`,说明当前饼干无法满足当前孩子胃口,则向右移动 `index_s`,判断下一块饼干是否可以满足当前孩子胃口。 -3. 遍历完输出答案 `res`。 - -##### 思路 1:代码 - -```python -class Solution: - def findContentChildren(self, g: List[int], s: List[int]) -> int: - g.sort() - s.sort() - index_g, index_s = 0, 0 - res = 0 - while index_g < len(g) and index_s < len(s): - if g[index_g] <= s[index_s]: - res += 1 - index_g += 1 - index_s += 1 - else: - index_s += 1 - - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times \log m + n \times \log n)$,其中 $m$ 和 $n$ 分别是数组 $g$ 和 $s$ 的长度。 -- **空间复杂度**:$O(\log m + \log n)$。 - -### 4.2 无重叠区间 - -#### 4.2.1 题目链接 - -- [435. 无重叠区间 - 力扣](https://leetcode.cn/problems/non-overlapping-intervals/) - -#### 4.2.2 题目大意 - -**描述**:给定一个区间的集合 `intervals`,其中 `intervals[i] = [starti, endi]`。从集合中移除部分区间,使得剩下的区间互不重叠。 - -**要求**:返回需要移除区间的最小数量。 - -**说明**: - -- $1 \le intervals.length \le 10^5$。 -- $intervals[i].length == 2$。 -- $-5 * 10^4 \le starti < endi \le 5 * 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:intervals = [[1,2],[2,3],[3,4],[1,3]] -输出:1 -解释:移除 [1,3] 后,剩下的区间没有重叠。 -``` - -- 示例 2: - -```python -输入: intervals = [ [1,2], [1,2], [1,2] ] -输出: 2 -解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 -``` - -#### 4.2.3 解题思路 - -##### 思路 1:贪心算法 - -这道题我们可以转换一下思路。原题要求保证移除区间最少,使得剩下的区间互不重叠。换个角度就是:「如何使得剩下互不重叠区间的数目最多」。那么答案就变为了:「总区间个数 - 不重叠区间的最多个数」。我们的问题也变成了求所有区间中不重叠区间的最多个数。 - -从贪心算法的角度来考虑,我们应该将区间按照结束时间排序。每次选择结束时间最早的区间,然后再在剩下的时间内选出最多的区间。 - -我们用贪心三部曲来解决这道题。 - -1. **转换问题**:将原问题转变为,当选择结束时间最早的区间之后,再在剩下的时间内选出最多的区间(子问题)。 -2. **贪心选择性质**:每次选择时,选择结束时间最早的区间。这样选出来的区间一定是原问题最优解的区间之一。 -3. **最优子结构性质**:在上面的贪心策略下,贪心选择当前时间最早的区间 + 剩下的时间内选出最多区间的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使所有区间中不重叠区间的个数最多。 - -使用贪心算法的代码解决步骤描述如下: - -1. 将区间集合按照结束坐标升序排列,然后维护两个变量,一个是当前不重叠区间的结束时间 `end_pos`,另一个是不重叠区间的个数 `count`。初始情况下,结束坐标 `end_pos` 为第一个区间的结束坐标,`count` 为 `1`。 -2. 依次遍历每段区间。对于每段区间:`intervals[i]`: - 1. 如果 `end_pos <= intervals[i][0]`,即 `end_pos` 小于等于区间起始位置,则说明出现了不重叠区间,令不重叠区间数 `count` 加 `1`,`end_pos` 更新为新区间的结束位置。 -3. 最终返回「总区间个数 - 不重叠区间的最多个数」即 `len(intervals) - count` 作为答案。 - -##### 思路 1:代码 - -```python -class Solution: - def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: - if not intervals: - return 0 - intervals.sort(key=lambda x: x[1]) - end_pos = intervals[0][1] - count = 1 - for i in range(1, len(intervals)): - if end_pos <= intervals[i][0]: - count += 1 - end_pos = intervals[i][1] - - return len(intervals) - count -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是区间的数量。 -- **空间复杂度**:$O(\log n)$。 - -## 参考资料 - -- 【博文】[贪心 - OI Wiki](https://oi-wiki.org/basic/greedy/) -- 【博文】[贪心算法 | 算法吧](https://suanfa8.com/greedy/) -- 【博文】[贪心算法理论基础 - Carl - 代码随想录](https://github.com/youngyangyang04/leetcode-master/blob/master/problems/贪心算法理论基础.md) -- 【博文】[小白带你学 贪心算法(Greedy Algorithm) - 知乎](https://zhuanlan.zhihu.com/p/53334049) -- 【书籍】算法导论 第三版(中文版)- 殷建平等 译 -- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md b/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md deleted file mode 100644 index 21c32ffc..00000000 --- a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md +++ /dev/null @@ -1,30 +0,0 @@ -### 贪心算法题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0455 | [分发饼干](https://leetcode.cn/problems/assign-cookies/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0455.%20%E5%88%86%E5%8F%91%E9%A5%BC%E5%B9%B2.md) | 贪心、数组、双指针、排序 | 简单 | -| 0860 | [柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0860.%20%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.md) | 贪心、数组 | 简单 | -| 0056 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0056.%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.md) | 数组、排序 | 中等 | -| 0435 | [无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0435.%20%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.md) | 贪心、数组、动态规划、排序 | 中等 | -| 0452 | [用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0452.%20%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88%86%E6%B0%94%E7%90%83.md) | 贪心、数组、排序 | 中等 | -| 0055 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md) | 贪心、数组、动态规划 | 中等 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0392 | [判断子序列](https://leetcode.cn/problems/is-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0392.%20%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.md) | 双指针、字符串、动态规划 | 简单 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0561 | [数组拆分](https://leetcode.cn/problems/array-partition/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0561.%20%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86.md) | 贪心、数组、计数排序、排序 | 简单 | -| 1710 | [卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1710.%20%E5%8D%A1%E8%BD%A6%E4%B8%8A%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%95%E5%85%83%E6%95%B0.md) | 贪心、数组、排序 | 简单 | -| 1217 | [玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1217.%20%E7%8E%A9%E7%AD%B9%E7%A0%81.md) | 贪心、数组、数学 | 简单 | -| 1247 | [交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1247.%20%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%BD%BF%E5%BE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%90%8C.md) | 贪心、数学、字符串 | 中等 | -| 1400 | [构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1400.%20%E6%9E%84%E9%80%A0%20K%20%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 贪心、哈希表、字符串、计数 | 中等 | -| 0921 | [使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0921.%20%E4%BD%BF%E6%8B%AC%E5%8F%B7%E6%9C%89%E6%95%88%E7%9A%84%E6%9C%80%E5%B0%91%E6%B7%BB%E5%8A%A0.md) | 栈、贪心、字符串 | 中等 | -| 1029 | [两地调度](https://leetcode.cn/problems/two-city-scheduling/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1029.%20%E4%B8%A4%E5%9C%B0%E8%B0%83%E5%BA%A6.md) | 贪心、数组、排序 | 中等 | -| 1605 | [给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1605.%20%E7%BB%99%E5%AE%9A%E8%A1%8C%E5%92%8C%E5%88%97%E7%9A%84%E5%92%8C%E6%B1%82%E5%8F%AF%E8%A1%8C%E7%9F%A9%E9%98%B5.md) | 贪心、数组、矩阵 | 中等 | -| 0135 | [分发糖果](https://leetcode.cn/problems/candy/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0135.%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.md) | 贪心、数组 | 困难 | -| 0134 | [加油站](https://leetcode.cn/problems/gas-station/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0134.%20%E5%8A%A0%E6%B2%B9%E7%AB%99.md) | 贪心、数组 | 中等 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0376 | [摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0376.%20%E6%91%86%E5%8A%A8%E5%BA%8F%E5%88%97.md) | 贪心、数组、动态规划 | 中等 | -| 0738 | [单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0738.%20%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E%E7%9A%84%E6%95%B0%E5%AD%97.md) | 贪心、数学 | 中等 | -| 0402 | [移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | | 栈、贪心、字符串、单调栈 | 中等 | -| 0861 | [翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0861.%20%E7%BF%BB%E8%BD%AC%E7%9F%A9%E9%98%B5%E5%90%8E%E7%9A%84%E5%BE%97%E5%88%86.md) | 贪心、位运算、数组、矩阵 | 中等 | -| 0670 | [最大交换](https://leetcode.cn/problems/maximum-swap/) | | 贪心、数学 | 中等 | - diff --git a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/index.md b/Contents/09.Algorithm-Base/05.Greedy-Algorithm/index.md deleted file mode 100644 index c32ee657..00000000 --- a/Contents/09.Algorithm-Base/05.Greedy-Algorithm/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [贪心算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md) -- [贪心算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md b/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md deleted file mode 100644 index 4410f2ed..00000000 --- a/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md +++ /dev/null @@ -1,318 +0,0 @@ -## 1. 位运算简介 - -### 1.1 位运算与二进制简介 - -> **位运算(Bit Operation)**:在计算机内部,数是以「二进制(Binary)」的形式来进行存储。位运算就是直接对数的二进制进行计算操作,在程序中使用位运算进行操作,会大大提高程序的性能。 - -在学习二进制数的位运算之前,我们先来了解一下什么叫做「二进制数」。 - -![](https://qcdn.itcharge.cn/images/20230225233101.png) - -> **二进制数(Binary)**:由 $0$ 和 $1$ 两个数码来表示的数。二进制数中每一个 $0$ 或每一个 $1$ 都称为一个「位(Bit)」。 - -我们通常使用的十进制数有 $0 \sim 9$ 共 $10$ 个数字,进位规则是「满十进一」。例如: - -1. $7_{(10)} + 2_{(10)} = 9_{(10)}$:$7_{(10)}$ 加上 $2_{(10)}$ 等于 $9_{(10)}$。 -2. $9_{(10)} + 2_{(10)} = 11_{(10)}$:$9_{(10)}$ 加上 $2_{(10)}$ 之后个位大于等于 $10$,符合「满十进一」,结果等于 $11_{(10)}$。 - -而在二进制数中,我们只有 $0$ 和 $1$ 两个数码,它的进位规则是「逢二进一」。例如: - -1. $1_{(2)} + 0_{(2)} = 1_{(2)}$:$1_{(2)}$ 加上 $0_{(2)}$ 等于 $1_{(2)}$。 -2. $1_{(2)} + 1_{(2)} = 10_{(2)}$:$1_{(2)}$ 加上 $1_{(2)}$,大于等于 $2$,符合「逢二进一」,结果等于 $10_{(2)}$。 -3. $10_{(2)} + 1_{(2)} = 11_{(2)}$。 - -### 1.2 二进制数的转换 - -#### 1.2.1 二进制转十进制数 - -在十进制数中,数字 $2749_{(10)}$ 可以理解为 $2 \times 1000 + 7 \times 100 + 4 \times 10 + 9 * 1$,相当于 $2 \times 10^3 + 7 \times 10^2 + 4 \times 10^1 + 9 \times 10^0$,即 $2000 + 700 + 40 + 9 = 2749_{(10)}$。 - -同理,在二进制数中,$01101010_{(2)}$ 可以看作为 $(0 \times 2^7) + (1 \times 2^6) + (1 \times 2^5) + (0 \times 2^4) + (1 \times 2^3) + (0 \times 2^2) + (1 \times 2^1) + (0 \times 2^0)$,即 $0 + 64 + 32 + 0 + 8 + 0 + 2 + 0 = 106_{(10)}$。 - -![](https://qcdn.itcharge.cn/images/20230225233152.png) - -我们可以通过这样的方式,将一个二进制数转为十进制数。 - -#### 1.2.2 十进制转二进制数 - -十进制数转二进制数的方法是:**除二取余,逆序排列法**。 - -我们以十进制数中的 $106_{(10)}$ 为例。 - -$\begin{aligned} 106 \div 2 = 53 & \text{(余 0)} \cr 53 \div 2 = 26 & \text{(余 1)} \cr 26 \div 2 = 13 & \text{(余 0)} \cr 13 \div 2 = 6 & \text{(余 1)} \cr 6 \div 2 = 3 & \text{(余 0)} \cr 3 \div 2 = 1 & \text{(余 1)} \cr 1 \div 2 = 0 & \text{(余 1)} \cr 0 \div 2 = 0 & \text{(余 0)} \end{aligned}$ - -我们反向遍历每次计算的余数,依次是 $0$,$1$,$1$,$0$,$1$,$0$,$1$,$0$,即 $01101010_{(2)}$。 - -## 2. 位运算基础操作 - -在二进制的基础上,我们可以对二进制数进行相应的位运算。基本的位运算共有 $6$ 种,分别是:「按位与运算」、「按位或运算」、「按位异或运算」、「取反运算」、「左移运算」、「右移运算」。 - -这里的「按位与运算」、「按位或运算」、「按位异或运算」、「左移运算」、「右移运算」是双目运算。 - -- 「按位与运算」、「按位或运算」、「按位异或运算」是将两个整数作为二进制数,对二进制数表示中的每一位(即二进位)逐一进行相应运算,即双目运算。 -- 「左移运算」、「右移运算」是将左侧整数作为二进制数,将右侧整数作为移动位数,然后对左侧二进制数的全部位进行移位运算,每次移动一位,总共移动右侧整数次位,也是双目运算。 - -而「取反运算」是单目运算,是对一个整数的二进制数进行的位运算。 - -我们先来看下这 $6$ 种位运算的规则,再来进行详细讲解。 - -| 运算符 | 描述 | 规则 | -| ------------------- | -------------- | ----------------------------------------------------------------------------------------- | -| | | 按位或运算符 | 只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。 | -| `&` | 按位与运算符 | 只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。 | -| `<<` | 左移运算符 | 将二进制数的各个二进位全部左移若干位。`<<` 右侧数字指定了移动位数,高位丢弃,低位补 $0$。 | -| `>>` | 右移运算符 | 对二进制数的各个二进位全部右移若干位。`>>` 右侧数字指定了移动位数,低位丢弃,高位补 $0$。 | -| `^` | 按位异或运算符 | 对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。 | -| `~` | 取反运算符 | 对二进制数的每个二进位取反,使数字 $1$ 变为 $0$,$0$ 变为 $1$。 | - -### 2.1 按位与运算 - -> **按位与运算(AND)**:按位与运算符为 `&`。其功能是对两个二进制数的每一个二进位进行与运算。 - -- **按位与运算规则**:只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。 - - - `1 & 1 = 1` - - - `1 & 0 = 0` - - - `0 & 1 = 0` - - - `0 & 0 = 0` - - -举个例子,对二进制数 $01111100_{(2)}$ 与 $00111110_{(2)}$ 进行按位与运算,结果为 $00111100_{(2)}$,如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233202.png) - -### 2.2 按位或运算 - -> **按位或运算(OR)**:按位或运算符为 `|`。其功能对两个二进制数的每一个二进位进行或运算。 - -- **按位或运算规则**:只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。 - - `1 | 1 = 1` - - `1 | 0 = 1` - - `0 | 1 = 1` - - `0 | 0 = 0` - - -举个例子,对二进制数 $01001010_{(2)}$ 与 $01011011_{(2)}$ 进行按位或运算,结果为 $01011011_{(2)}$,如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233231.png) - -### 2.3 按位异或运算 - -> **按位异或运算(XOR)**:按位异或运算符为 `^`。其功能是对两个二进制数的每一个二进位进行异或运算。 - -- **按位异或运算规则**:对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。 -- `0 ^ 0 = 0` - -- `1 ^ 0 = 1` - -- `0 ^ 1 = 1` - -- `1 ^ 1 = 0` - - -举个例子,对二进制数 $01001010_{(2)}$ 与 $01000101_{(2)}$ 进行按位异或运算,结果为 $00001111_{(2)}$,如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233240.png) - -### 2.4 取反运算 - ->**取反运算(NOT)**:取反运算符为 `~`。其功能是对一个二进制数的每一个二进位进行取反运算。 - -- **取反运算规则**:使数字 $1$ 变为 $0$,$0$ 变为 $1$。 - - `~0 = 1` - - `~1 = 0` - -举个例子,对二进制数 $01101010_{(2)}$ 进行取反运算,结果如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233257.png) - -### 2.5 左移运算和右移运算 - -> **左移运算(SHL)**: 左移运算符为 `<<`。其功能是对一个二进制数的各个二进位全部左移若干位(高位丢弃,低位补 $0$)。 - -举个例子,对二进制数 $01101010_{(2)}$ 进行左移 $1$ 位运算,结果为 $11010100_{(2)}$,如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233308.png) - -> **右移运算(SHR)**: 右移运算符为 `>>`。其功能是对一个二进制数的各个二进位全部右移若干位(低位丢弃,高位补 $0$)。 - -举个例子,对二进制数 $01101010_{(2)}$ 进行右移 $1$ 位运算,结果为 $00110101_{(2)}$,如图所示: - -![](https://qcdn.itcharge.cn/images/20230225233317.png) - -## 3. 位运算的应用 - -### 3.1 位运算的常用操作 - -#### 3.1.1 判断整数奇偶 - -一个整数,只要是偶数,其对应二进制数的末尾一定为 $0$;只要是奇数,其对应二进制数的末尾一定为 $1$。所以,我们通过与 $1$ 进行按位与运算,即可判断某个数是奇数还是偶数。 - -1. `(x & 1) == 0` 为偶数。 -2. `(x & 1) == 1` 为奇数。 - -#### 3.1.2 二进制数选取指定位 - -如果我们想要从一个二进制数 $X$ 中取出某几位,使取出位置上的二进位保留原值,其余位置为 $0$,则可以使用另一个二进制数 $Y$,使该二进制数上对应取出位置为 $1$,其余位置为 $0$。然后令两个数进行按位与运算(`X & Y`),即可得到想要的数。 - -举个例子,比如我们要取二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$ (末尾 $4$ 位为 $1$,其余位为 $0$) 进行按位与运算,即 `01101010 & 00001111 == 00001010`。其结果 $00001010$ 就是我们想要的数(即二进制数 $01101010_{(2)}$ 的末尾 $4$ 位)。 - -#### 3.1.3 将指定位设置为 $1$ - -如果我们想要把一个二进制数 $X$ 中的某几位设置为 $1$,其余位置保留原值,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位或运算(`X | Y`),即可得到想要的数。 - -举个例子,比如我们想要将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位或运算,即 `01101010 | 00001111 = 01101111`。其结果 $01101111$ 就是我们想要的数(即将二进制数 $01101010_{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值)。 - -#### 3.1.4 反转指定位 - -如果我们想要把一个二进制数 $X$ 的某几位进行反转,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位异或运算(`X ^ Y`),即可得到想要的数。 - -举个例子,比如想要将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位进行反转,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位异或运算,即 `01101010 ^ 00001111 = 01100101`。其结果 $01100101$ 就是我们想要的数(即将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位进行反转)。 - -#### 3.1.5 交换两个数 - -通过按位异或运算可以实现交换两个数的目的(只能用于交换两个整数)。 - -```python -a, b = 10, 20 -a ^= b -b ^= a -a ^= b -print(a, b) -``` - -#### 3.1.6 将二进制最右侧为 $1$ 的二进位改为 $0$ - -如果我们想要将一个二进制数 $X$ 最右侧为 $1$ 的二进制位改为 $0$,则只需通过 `X & (X - 1)` 的操作即可完成。 - -比如 $X = 01101100_{(2)}$,$X - 1 = 01101011_{(2)}$,则 `X & (X - 1) == 01101100 & 01101011 == 01101000`,结果为 $01101000_{(2)}$(即将 $X$ 最右侧为 $1$ 的二进制为改为 $0$)。 - -#### 3.1.7 计算二进制中二进位为 $1$ 的个数 - -从 3.1.6 中得知,通过 `X & (X - 1)` 我们可以将二进制 $X$ 最右侧为 $1$ 的二进制位改为 $0$,那么如果我们不断通过 `X & (X - 1)` 操作,最终将二进制 $X$ 变为 $0$,并统计执行次数,则可以得到二进制中二进位为 $1$ 的个数。 - -具体代码如下: - -```python -class Solution: - def hammingWeight(self, n: int) -> int: - cnt = 0 - while n: - n = n & (n - 1) - cnt += 1 - return cnt -``` - -#### 3.1.8 判断某数是否为 $2$ 的幂次方 - -通过判断 `X & (X - 1) == 0` 是否成立,即可判断 $X$ 是否为 $2$ 的幂次方。 - -这是因为: - -1. 凡是 $2$ 的幂次方,其二进制数的某一高位为 $1$,并且仅此高位为 $1$,其余位都为 $0$。比如:$4_{(10)} = 00000100_{(2)}$、$8_{(10)} = 00001000_{(2)}$。 -2. 不是 $2$ 的幂次方,其二进制数存在多个值为 $1$ 的位。比如:$5_{10} = 00000101_{(2)}$、$6_{10} = 00000110_{(2)}$。 - -接下来我们使用 `X & (X - 1)` 操作,将原数对应二进制数最右侧为 $1$ 的二进位改为 $0$ 之后,得到新值: - -1. 如果原数是 $2$ 的幂次方,则通过 `X & (X - 1)` 操作之后,新值所有位都为 $0$,值为 $0$。 -2. 如果该数不是 $2$ 的幂次方,则通过 `X & (X - 1)` 操作之后,新值仍存在不为 $0$ 的位,值肯定不为 $0$。 - -所以我们可以通过是否为 $0$ 即可判断该数是否为 $2$ 的幂次方。 - -### 3.2 位运算的常用操作总结 - -| 功 能 | 位运算 | 示例 | -| ----------------------------------------- | ------------------------------------------------------- | ------------------------- | -| **从右边开始,把最后一个 `1` 改写成 `0`** | x & (x - 1) | `100101000 -> 100100000` | -| **去掉右边起第一个 `1` 的左边** | x & (x ^ (x - 1))x & (-x) | `100101000 -> 1000` | -| **去掉最后一位** | x >> 1 | `101101 -> 10110` | -| **取右数第 `k` 位** | x >> (k - 1) & 1 | `1101101 -> 1, k = 4` | -| **取末尾 `3` 位** | x & 7 | `1101101 -> 101` | -| **取末尾 `k` 位** | x & 15 | `1101101 -> 1101, k = 4` | -| **只保留右边连续的 `1`** | (x ^ (x + 1)) >> 1 | `100101111 -> 1111` | -| **右数第 `k` 位取反** | x ^ (1 << (k - 1)) | `101001 -> 101101, k = 3` | -| **在最后加一个 `0`** | x << 1 | `101101 -> 1011010` | -| **在最后加一个 `1`** | (x << 1) + 1 | `101101 -> 1011011` | -| **把右数第 `k` 位变成 `0`** | x & ~(1 << (k - 1)) | `101101 -> 101001, k = 3` | -| **把右数第 `k` 位变成 `1`** | x | (1 << (k - 1)) | `101001 -> 101101, k = 3` | -| **把右边起第一个 `0` 变成 `1`** | x | (x + 1) | `100101111 -> 100111111` | -| **把右边连续的 `0` 变成 `1`** | x | (x - 1) | `11011000 -> 11011111` | -| **把右边连续的 `1` 变成 `0`** | x & (x + 1) | `100101111 -> 100100000` | -| **把最后一位变成 `0`** | x | 1 - 1 | `101101 -> 101100` | -| **把最后一位变成 `1`** | x | 1 | `101100 -> 101101` | -| **把末尾 `k` 位变成 `1`** | x | (1 << k - 1) | `101001 -> 101111, k = 4` | -| **最后一位取反** | x ^ 1 | `101101 -> 101100` | -| **末尾 `k` 位取反** | x ^ (1 << k - 1) | `101001 -> 100110, k = 4` | - -### 3.3 二进制枚举子集 - -除了上面的这些常见操作,我们经常常使用二进制数第 $1 \sim n$ 位上 $0$ 或 $1$ 的状态来表示一个由 $1 \sim n$ 组成的集合。也就是说通过二进制来枚举子集。 - -#### 3.3.1 二进制枚举子集简介 - -先来介绍一下「子集」的概念。 - -- **子集**:如果集合 $A$ 的任意一个元素都是集合 $S$ 的元素,则称集合 $A$ 是集合 $S$ 的子集。可以记为 $A \in S$。 - -有时候我们会遇到这样的问题:给定一个集合 $S$,枚举其所有可能的子集。 - -枚举子集的方法有很多,这里介绍一种简单有效的枚举方法:「二进制枚举子集算法」。 - -对于一个元素个数为 $n$ 的集合 $S$ 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 $1$ 来表示选取该元素,用数字 $0$ 来表示不选取该元素。 - -那么我们就可以用一个长度为 $n$ 的二进制数来表示集合 $S$ 或者表示 $S$ 的子集。其中二进制的每一个二进位都对应了集合中某一个元素的选取状态。对于集合中第 $i$ 个元素来说,二进制对应位置上的 $1$ 代表该元素被选取,$0$ 代表该元素未被选取。 - -举个例子,比如长度为 $5$ 的集合 $S = \lbrace 5, 4, 3, 2, 1 \rbrace$,我们可以用一个长度为 $5$ 的二进制数来表示该集合。 - -比如二进制数 $11111_{(2)}$ 就表示选取集合的第 $1$ 位、第 $2$ 位、第 $3$ 位、第 $4$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 4, 3, 2, 1 \rbrace$,即集合 $S$ 本身。如下表所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :--: | :--: | :--: | :--: | :--: | -| 二进位对应值 | 1 | 1 | 1 | 1 | 1 | -| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | - -再比如二进制数 $10101_{(2)}$ 就表示选取集合的第 $1$ 位、第 $3$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 3, 1 \rbrace$。如下表所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :--: | :----: | :--: | :----: | :--: | -| 二进位对应值 | 1 | 0 | 1 | 0 | 1 | -| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | - -再比如二进制数 $01001_{(2)}$ 就表示选取集合的第 $1$ 位、第 $4$ 位元素,也就是集合 $\lbrace 4, 1 \rbrace$。如下标所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :----: | :--: | :----: | :----: | :--: | -| 二进位对应值 | 0 | 1 | 0 | 0 | 1 | -| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | - -通过上面的例子我们可以得到启发:对于长度为 $5$ 的集合 $S$ 来说,我们只需要从 $00000 \sim 11111$ 枚举一次(对应十进制为 $0 \sim 2^5 - 1$)即可得到长度为 $5$ 的集合 $S$ 的所有子集。 - -我们将上面的例子拓展到长度为 $n$ 的集合 $S$。可以总结为: - -- 对于长度为 $n$ 的集合 $S$ 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到集合 $S$ 的所有子集。 - -#### 3.3.2 二进制枚举子集代码 - -```python -class Solution: - def subsets(self, S): # 返回集合 S 的所有子集 - n = len(S) # n 为集合 S 的元素个数 - sub_sets = [] # sub_sets 用于保存所有子集 - for i in range(1 << n): # 枚举 0 ~ 2^n - 1 - sub_set = [] # sub_set 用于保存当前子集 - for j in range(n): # 枚举第 i 位元素 - if i >> j & 1: # 如果第 i 为元素对应二进位删改为 1,则表示选取该元素 - sub_set.append(S[j]) # 将选取的元素加入到子集 sub_set 中 - sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 - return sub_sets # 返回所有子集 -``` - -## 参考资料 - -- 【博文】[Python 中的按位运算符 |【生长吧!Python!】- 云社区 - 华为云](https://bbs.huaweicloud.com/blogs/280901) -- 【博文】[一文读懂位运算的使用 - 小黑说 Java - 掘金](https://juejin.cn/post/7011407264581943326) -- 【博文】[枚举排列和枚举子集 - CUC ACM-Wiki](https://cuccs.github.io/acm-wiki/search/enumeration/) -- 【博文】[Swift 运算符 | 菜鸟教程](https://www.runoob.com/swift/swift-operators.html) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md b/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md deleted file mode 100644 index 1177335e..00000000 --- a/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md +++ /dev/null @@ -1,22 +0,0 @@ -### 位运算题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0504 | [七进制数](https://leetcode.cn/problems/base-7/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0504.%20%E4%B8%83%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 数学 | 简单 | -| 0405 | [数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0405.%20%E6%95%B0%E5%AD%97%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 位运算、数学 | 简单 | -| 0190 | [颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0190.%20%E9%A2%A0%E5%80%92%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BD%8D.md) | 位运算、分治 | 简单 | -| 1009 | [十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1009.%20%E5%8D%81%E8%BF%9B%E5%88%B6%E6%95%B4%E6%95%B0%E7%9A%84%E5%8F%8D%E7%A0%81.md) | 位运算 | 简单 | -| 0191 | [位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0191.%20%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 位运算、分治 | 简单 | -| 0371 | [两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0371.%20%E4%B8%A4%E6%95%B4%E6%95%B0%E4%B9%8B%E5%92%8C.md) | 位运算、数学 | 中等 | -| 0089 | [格雷编码](https://leetcode.cn/problems/gray-code/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0089.%20%E6%A0%BC%E9%9B%B7%E7%BC%96%E7%A0%81.md) | 位运算、数学、回溯 | 中等 | -| 0201 | [数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0201.%20%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4%E6%8C%89%E4%BD%8D%E4%B8%8E.md) | 位运算 | 中等 | -| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 0136 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0136.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组 | 简单 | -| 0137 | [只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0137.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20II.md) | 位运算、数组 | 中等 | -| 0260 | [只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0260.%20%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0%E5%AD%97%20III.md) | 位运算、数组 | 中等 | -| 0268 | [丢失的数字](https://leetcode.cn/problems/missing-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0268.%20%E4%B8%A2%E5%A4%B1%E7%9A%84%E6%95%B0%E5%AD%97.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | -| 1349 | [参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 0645 | [错误的集合](https://leetcode.cn/problems/set-mismatch/) | | 位运算、数组、哈希表、排序 | 简单 | -| 0078 | [子集](https://leetcode.cn/problems/subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0078.%20%E5%AD%90%E9%9B%86.md) | 位运算、数组、回溯 | 中等 | -| 0090 | [子集 II](https://leetcode.cn/problems/subsets-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0090.%20%E5%AD%90%E9%9B%86%20II.md) | 位运算、数组、回溯 | 中等 | - diff --git a/Contents/09.Algorithm-Base/06.Bit-Operation/index.md b/Contents/09.Algorithm-Base/06.Bit-Operation/index.md deleted file mode 100644 index d28c1d38..00000000 --- a/Contents/09.Algorithm-Base/06.Bit-Operation/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [位运算知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md) -- [位运算题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) \ No newline at end of file diff --git a/Contents/09.Algorithm-Base/index.md b/Contents/09.Algorithm-Base/index.md deleted file mode 100644 index 46473ac8..00000000 --- a/Contents/09.Algorithm-Base/index.md +++ /dev/null @@ -1,31 +0,0 @@ -## 本章内容 - -### 枚举算法 - -- [枚举算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md) -- [枚举算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) - -### 递归算法 - -- [递归算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md) -- [递归算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) - -### 分治算法 - -- [分治算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md) -- [分治算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) - -### 回溯算法 - -- [回溯算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md) -- [回溯算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) - -### 贪心算法 - -- [贪心算法知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md) -- [贪心算法题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) - -### 位运算 - -- [位运算知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md) -- [位运算题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md b/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md deleted file mode 100644 index ef594aa2..00000000 --- a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md +++ /dev/null @@ -1,397 +0,0 @@ -## 1. 动态规划简介 - -### 1.1 动态规划的定义 - -> **动态规划(Dynamic Programming)**:简称 **DP**,是一种求解多阶段决策过程最优化问题的方法。在动态规划中,通过把原问题分解为相对简单的子问题,先求解子问题,再由子问题的解而得到原问题的解。 - -动态规划最早由理查德 · 贝尔曼于 1957 年在其著作「动态规划(Dynamic Programming)」一书中提出。这里的 Programming 并不是编程的意思,而是指一种「表格处理方法」,即将每一步计算的结果存储在表格中,供随后的计算查询使用。 - -### 1.2 动态规划的核心思想 - -> **动态规划的核心思想**: -> -> 1. 把「原问题」分解为「若干个重叠的子问题」,每个子问题的求解过程都构成一个 **「阶段」**。在完成一个阶段的计算之后,动态规划方法才会执行下一个阶段的计算。 -> 2. 在求解子问题的过程中,按照「自顶向下的记忆化搜索方法」或者「自底向上的递推方法」求解出「子问题的解」,把结果存储在表格中,当需要再次求解此子问题时,直接从表格中查询该子问题的解,从而避免了大量的重复计算。 - -这看起来很像是分治算法,但动态规划与分治算法的不同点在于: - -1. 适用于动态规划求解的问题,在分解之后得到的子问题往往是相互联系的,会出现若干个重叠子问题。 -2. 使用动态规划方法会将这些重叠子问题的解保存到表格里,供随后的计算查询使用,从而避免大量的重复计算。 - -### 1.3 动态规划的简单例子 - -下面我们先来通过一个简单的例子来介绍一下什么是动态规划算法,然后再来讲解动态规划中的各种术语。 - -> **斐波那契数列**:数列由 $f(0) = 1, f(1) = 2$ 开始,后面的每一项数字都是前面两项数字的和。也就是: -> -> $f(n) = \begin{cases} 0 & n = 0 \cr 1 & n = 1 \cr f(n - 2) + f(n - 1) & n > 1 \end{cases}$ - -通过公式 $f(n) = f(n - 2) + f(n - 1)$,我们可以将原问题 $f(n)$ 递归地划分为 $f(n - 2)$ 和 $f(n - 1)$ 这两个子问题。其对应的递归过程如下图所示: - -![](https://qcdn.itcharge.cn/images/20230307164107.png) - -从图中可以看出:如果使用传统递归算法计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$,这样 $f(3)$ 就进行了多次计算。同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,从而导致了重复计算问题。 - -为了避免重复计算,我们可以使用动态规划中的「表格处理方法」来处理。 - -这里我们使用「自底向上的递推方法」求解出子问题 $f(n - 2)$ 和 $f(n - 1)$ 的解,然后把结果存储在表格中,供随后的计算查询使用。具体过程如下: - -1. 定义一个数组 $dp$,用于记录斐波那契数列中的值。 -2. 初始化 $dp[0] = 0, dp[1] = 1$。 -3. 根据斐波那契数列的递推公式 $f(n) = f(n - 1) + f(n - 2)$,从 $dp(2)$ 开始递推计算斐波那契数列的每个数,直到计算出 $dp(n)$。 -4. 最后返回 $dp(n)$ 即可得到第 $n$ 项斐波那契数。 - -具体代码如下: - -```python -class Solution: - def fib(self, n: int) -> int: - if n == 0: - return 0 - if n == 1: - return 1 - - dp = [0 for _ in range(n + 1)] - dp[0] = 0 - dp[1] = 1 - - for i in range(2, n + 1): - dp[i] = dp[i - 2] + dp[i - 1] - - return dp[n] -``` - -这种使用缓存(哈希表、集合或数组)保存计算结果,从而避免子问题重复计算的方法,就是「动态规划算法」。 - -## 2. 动态规划的特征 - -究竟什么样的问题才可以使用动态规划算法解决呢? - -首先,能够使用动态规划方法解决的问题必须满足以下三个特征: - -1. **最优子结构性质** -2. **重叠子问题性质** -3. **无后效性** - -### 2.1 最优子结构性质 - -> **最优子结构**:指的是一个问题的最优解包含其子问题的最优解。 - -举个例子,如下图所示,原问题 $S = \lbrace a_1, a_2, a_3, a_4 \rbrace$,在 $a_1$ 步我们选出一个当前最优解之后,问题就转换为求解子问题 $S_{\text{子问题}} = \lbrace a_2, a_3, a_4 \rbrace$。如果原问题 $S$ 的最优解可以由「第 $a_1$ 步得到的局部最优解」和「 $S_{\text{子问题}}$ 的最优解」构成,则说明该问题满足最优子结构性质。 - -也就是说,如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。 - -![](https://qcdn.itcharge.cn/images/20220511175042.png) - -### 2.2 重叠子问题性质 - -> **重叠子问题性质**:指的是在求解子问题的过程中,有大量的子问题是重复的,一个子问题在下一阶段的决策中可能会被多次用到。如果有大量重复的子问题,那么只需要对其求解一次,然后用表格将结果存储下来,以后使用时可以直接查询,不需要再次求解。 - -![](https://qcdn.itcharge.cn/images/20230307175804.png) - -之前我们提到的「斐波那契数列」例子中,$f(0)$、$f(1)$、$f(2)$、$f(3)$ 都进行了多次重复计算。动态规划算法利用了子问题重叠的性质,在第一次计算 $f(0)$、$f(1)$、$f(2)$、$f(3)$ 时就将其结果存入表格,当再次使用时可以直接查询,无需再次求解,从而提升效率。 - -### 2.3 无后效性 - -> **无后效性**:指的是子问题的解(状态值)只与之前阶段有关,而与后面阶段无关。当前阶段的若干状态值一旦确定,就不再改变,不会再受到后续阶段决策的影响。 - -也就是说,**一旦某一个子问题的求解结果确定以后,就不会再被修改**。 - -举个例子,下图是一个有向无环带权图,我们在求解从 $A$ 点到 $F$ 点的最短路径问题时,假设当前已知从 $A$ 点到 $D$ 点的最短路径($2 + 7 = 9$)。那么无论之后的路径如何选择,都不会影响之前从 $A$ 点到 $D$ 点的最短路径长度。这就是「无后效性」。 - -而如果一个问题具有「后效性」,则可能需要先将其转化或者逆向求解来消除后效性,然后才可以使用动态规划算法。 - -![](https://qcdn.itcharge.cn/images/202303072158573.png) - -## 3. 动态规划的基本思路 - -如下图所示,我们在使用动态规划方法解决某些最优化问题时,可以将解决问题的过程按照一定顺序(时间顺序、空间顺序或其他顺序)分解为若干个相互联系的「阶段」。然后按照顺序对每一个阶段做出「决策」,这个决策既决定了本阶段的效益,也决定了下一阶段的初始状态。依次做完每个阶段的决策之后,就得到了一个整个问题的决策序列。 - -这样就将一个原问题分解为了一系列的子问题,再通过逐步求解从而获得最终结果。 - -![](https://qcdn.itcharge.cn/images/20220720180135.png) - -这种前后关联、具有链状结构的多阶段进行决策的问题也叫做「多阶段决策问题」。 - -通常我们使用动态规划方法来解决问题的基本思路如下: - -1. **划分阶段**:将原问题按顺序(时间顺序、空间顺序或其他顺序)分解为若干个相互联系的「阶段」。划分后的阶段⼀定是有序或可排序的,否则问题⽆法求解。 - - 这里的「阶段」指的是⼦问题的求解过程。每个⼦问题的求解过程都构成⼀个「阶段」,在完成前⼀阶段的求解后才会进⾏后⼀阶段的求解。 -2. **定义状态**:将和子问题相关的某些变量(位置、数量、体积、空间等等)作为一个「状态」表示出来。状态的选择要满⾜⽆后效性。 - - 一个「状态」对应一个或多个子问题,所谓某个「状态」下的值,指的就是这个「状态」所对应的子问题的解。 -3. **状态转移**:根据「上一阶段的状态」和「该状态下所能做出的决策」,推导出「下一阶段的状态」。或者说根据相邻两个阶段各个状态之间的关系,确定决策,然后推导出状态间的相互转移方式(即「状态转移方程」)。 -4. **初始条件和边界条件**:根据问题描述、状态定义和状态转移方程,确定初始条件和边界条件。 -5. **最终结果**:确定问题的求解目标,然后按照一定顺序求解每一个阶段的问题。最后根据状态转移方程的递推结果,确定最终结果。 - -## 4. 动态规划的应用 - -动态规划相关的问题往往灵活多变,思维难度大,没有特别明显的套路,并且经常会在各类算法竞赛和面试中出现。 - -动态规划问题的关键点在于「如何状态设计」和「推导状态转移条件」,还有各种各样的「优化方法」。这类问题一定要多练习、多总结,只有接触的题型多了,才能熟练掌握动态规划思想。 - -下面来介绍几道关于动态规划的基础题目。 - -### 4.1 斐波那契数 - -#### 4.1.1 题目链接 - -- [509. 斐波那契数 - 力扣](https://leetcode.cn/problems/fibonacci-number/) - -#### 4.1.2 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:计算第 $n$ 个斐波那契数。 - -**说明**: - -- 斐波那契数列的定义如下: - - $f(0) = 0, f(1) = 1$。 - - $f(n) = f(n - 1) + f(n - 2)$,其中 $n > 1$。 -- $0 \le n \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:1 -解释:F(2) = F(1) + F(0) = 1 + 0 = 1 -``` - -- 示例 2: - -```python -输入:n = 3 -输出:2 -解释:F(3) = F(2) + F(1) = 1 + 1 = 2 -``` - -#### 4.1.3 解题思路 - -###### 1. 划分阶段 - -我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:第 $i$ 个斐波那契数。 - -###### 3. 状态转移方程 - -根据题目中所给的斐波那契数列的定义 $f(n) = f(n - 1) + f(n - 2)$,则直接得出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 - -###### 4. 初始条件 - -根据题目中所给的初始条件 $f(0) = 0, f(1) = 1$ 确定动态规划的初始条件,即 $dp[0] = 0, dp[1] = 1$。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[n]$,即第 $n$ 个斐波那契数为 $dp[n]$。 - -#### 4.1.4 代码 - -```python -class Solution: - def fib(self, n: int) -> int: - if n <= 1: - return n - - dp = [0 for _ in range(n + 1)] - dp[0] = 0 - dp[1] = 1 - for i in range(2, n + 1): - dp[i] = dp[i - 2] + dp[i - 1] - - return dp[n] -``` - -#### 4.1.5 复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -### 4.2 爬楼梯 - -#### 4.2.1 题目链接 - -- [70. 爬楼梯 - 力扣](https://leetcode.cn/problems/climbing-stairs/) - -#### 4.2.2 题目大意 - -**描述**:假设你正在爬楼梯。需要 $n$ 阶你才能到达楼顶。每次你可以爬 $1$ 或 $2$ 个台阶。现在给定一个整数 $n$。 - -**要求**:计算出有多少种不同的方法可以爬到楼顶。 - -**说明**: - -- $1 \le n \le 45$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:2 -解释:有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 -``` - -- 示例 2: - -```python -输入:n = 3 -输出:3 -解释:有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 -``` - -#### 4.2.3 解题思路 - -###### 1. 划分阶段 - -我们按照台阶的阶层划分阶段,将其划分为 $0 \sim n$ 阶。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:爬到第 $i$ 阶台阶的方案数。 - -###### 3. 状态转移方程 - -根据题目大意,每次只能爬 $1$ 或 $2$ 个台阶。则第 $i$ 阶楼梯只能从第 $i - 1$ 阶向上爬 $1$ 阶上来,或者从第 $i - 2$ 阶向上爬 $2$ 阶上来。所以可以推出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 - -###### 4. 初始条件 - -- 第 $0$ 层台阶方案数:可以看做 $1$ 种方法(从 $0$ 阶向上爬 $0$ 阶),即 $dp[1] = 1$。 -- 第 $1$ 层台阶方案数:$1$ 种方法(从 $0$ 阶向上爬 $1$ 阶),即 $dp[1] = 1$。 -- 第 $2$ 层台阶方案数:$2$ 中方法(从 $0$ 阶向上爬 $2$ 阶,或者从 $1$ 阶向上爬 $1$ 阶)。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[n]$,即爬到第 $n$ 阶台阶(即楼顶)的方案数为 $dp[n]$。 - -虽然这道题跟上一道题的状态转移方程都是 $dp[i] = dp[i - 1] + dp[i - 2]$,但是两道题的考察方式并不相同,一定程度上也可以看出来动态规划相关题目的灵活多变。 - -#### 4.2.4 代码 - -```python -class Solution: - def climbStairs(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - dp[0] = 1 - dp[1] = 1 - for i in range(2, n + 1): - dp[i] = dp[i - 1] + dp[i - 2] - - return dp[n] -``` - -#### 4.2.5 复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 - -### 4.3 不同路径 - -#### 4.3.1 题目链接 - -- [62. 不同路径 - 力扣](https://leetcode.cn/problems/unique-paths/) - -#### 4.3.2 题目大意 - -**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 - -**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 - -**说明**: - -- $1 \le m, n \le 100$。 -- 题目数据保证答案小于等于 $2 \times 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:m = 3, n = 7 -输出:28 -``` - -- 示例 2: - -```python -输入:m = 3, n = 2 -输出:3 -解释: -从左上角开始,总共有 3 条路径可以到达右下角。 -1. 向右 -> 向下 -> 向下 -2. 向下 -> 向下 -> 向右 -3. 向下 -> 向右 -> 向下 -``` - -![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) - -#### 4.3.3 解题思路 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:从左上角到达 $(i, j)$ 位置的路径数量。 - -###### 3. 状态转移方程 - -因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 - -###### 4. 初始条件 - -- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 -- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 -- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 - -#### 4.3.4 代码 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - dp = [[0 for _ in range(n)] for _ in range(m)] - - for j in range(n): - dp[0][j] = 1 - for i in range(m): - dp[i][0] = 1 - - for i in range(1, m): - for j in range(1, n): - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - - return dp[m - 1][n - 1] -``` - -#### 4.3.5 复杂度分析 - -- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m \times n)$,所以总体时间复杂度为 $O(m \times n)$。 -- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $n$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(n)$。 - -## 参考资料 - -- 【文章】[动态规划基础 - OI Wiki](https://oi-wiki.org/dp/basic/) -- 【文章】[动态规划 1 ——基本概念 - 知乎](https://zhuanlan.zhihu.com/p/25441186) -- 【文章】[动态规划算法 | 曹世宏的博客](https://cshihong.github.io/2018/03/30/动态规划算法/) -- 【文章】[动态规划之初识动规:有了四步解题法模板,再也不害怕动态规划! - 知乎](https://zhuanlan.zhihu.com/p/91680256) -- 【文章】[第 6 节 最优子结构、重复子问题、无后效性 | 算法吧](https://suanfa8.com/dynamic-programming/06/) -- 【书籍】算法训练营 陈小玉 著 -- 【书籍】趣学算法 陈小玉 著 -- 【书籍】算法竞赛进阶指南 - 李煜东 著 -- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 diff --git a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md b/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md deleted file mode 100644 index 5a134bfc..00000000 --- a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md +++ /dev/null @@ -1,8 +0,0 @@ -### 动态规划基础题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | - diff --git a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/index.md b/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/index.md deleted file mode 100644 index c1d518cb..00000000 --- a/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [动态规划基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) -- [动态规划基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md b/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md deleted file mode 100644 index 2791c496..00000000 --- a/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md +++ /dev/null @@ -1,282 +0,0 @@ -## 1. 记忆化搜索简介 - ->**记忆化搜索(Memoization Search)**:是一种通过存储已经遍历过的状态信息,从而避免对同一状态重复遍历的搜索算法。 - -记忆化搜索是动态规划的一种实现方式。在记忆化搜索中,当算法需要计算某个子问题的结果时,它首先检查是否已经计算过该问题。如果已经计算过,则直接返回已经存储的结果;否则,计算该问题,并将结果存储下来以备将来使用。 - -举个例子,比如「斐波那契数列」的定义是:$f(0) = 0, f(1) = 1, f(n) = f(n - 1) + f(n - 2)$。如果我们使用递归算法求解第 $n$ 个斐波那契数,则对应的递推过程如下: - -![](https://qcdn.itcharge.cn/images/20230308105357.png) - -从图中可以看出:如果使用普通递归算法,想要计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$。这样 $f(3)$ 就进行了多次计算,同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,从而导致了重复计算问题。 - -为了避免重复计算,在递归的同时,我们可以使用一个缓存(数组或哈希表)来保存已经求解过的 $f(k)$ 的结果。如上图所示,当递归调用用到 $f(k)$ 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。 - -使用「记忆化搜索」方法解决斐波那契数列的代码如下: - -```python -class Solution: - def fib(self, n: int) -> int: - # 使用数组保存已经求解过的 f(k) 的结果 - memo = [0 for _ in range(n + 1)] - return self.my_fib(n, memo) - - def my_fib(self, n: int, memo: List[int]) -> int: - if n == 0: - return 0 - if n == 1: - return 1 - - # 已经计算过结果 - if memo[n] != 0: - return memo[n] - - # 没有计算过结果 - memo[n] = self.my_fib(n - 1, memo) + self.my_fib(n - 2, memo) - return memo[n] -``` - -## 2. 记忆化搜索与递推区别 - -「记忆化搜索」与「递推」都是动态规划的实现方式,但是两者之间有一些区别。 - -> **记忆化搜索**:「自顶向下」的解决问题,采用自然的递归方式编写过程,在过程中会保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。 -> -> - 优点:代码清晰易懂,可以有效的处理一些复杂的状态转移方程。有些状态转移方程是非常复杂的,使用记忆化搜索可以将复杂的状态转移方程拆分成多个子问题,通过递归调用来解决。 -> - 缺点:可能会因为递归深度过大而导致栈溢出问题。 -> -> **递推**:「自底向上」的解决问题,采用循环的方式编写过程,在过程中通过保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。 -> -> - 优点:避免了深度过大问题,不存在栈溢出问题。计算顺序比较明确,易于实现。 -> - 缺点:无法处理一些复杂的状态转移方程。有些状态转移方程非常复杂,如果使用递推方法来计算,就会导致代码实现变得非常困难。 - -根据记忆化搜索和递推的优缺点,我们可以在不同场景下使用这两种方法。 - -适合使用「记忆化搜索」的场景: - -1. 问题的状态转移方程比较复杂,递推关系不是很明确。 -2. 问题适合转换为递归形式,并且递归深度不会太深。 - -适合使用「递推」的场景: - -1. 问题的状态转移方程比较简单,递归关系比较明确。 -2. 问题不太适合转换为递归形式,或者递归深度过大容易导致栈溢出。 - -## 3. 记忆化搜索解题步骤 - -我们在使用记忆化搜索解决问题的时候,其基本步骤如下: - -1. 写出问题的动态规划「状态」和「状态转移方程」。 -2. 定义一个缓存(数组或哈希表),用于保存子问题的解。 -3. 定义一个递归函数,用于解决问题。在递归函数中,首先检查缓存中是否已经存在需要计算的结果,如果存在则直接返回结果,否则进行计算,并将结果存储到缓存中,再返回结果。 -4. 在主函数中,调用递归函数并返回结果。 - -## 4. 记忆化搜索的应用 - -### 4.1 目标和 - -#### 4.1.1 题目链接 - -- [494. 目标和 - 力扣](https://leetcode.cn/problems/target-sum/) - -#### 4.1.2 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个整数 $target$。数组长度不超过 $20$。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 - -**要求**:返回通过上述方法构造的、运算结果等于 $target$ 的不同表达式数目。 - -**说明**: - -- $1 \le nums.length \le 20$。 -- $0 \le nums[i] \le 1000$。 -- $0 \le sum(nums[i]) \le 1000$。 -- $-1000 \le target \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,1,1,1], target = 3 -输出:5 -解释:一共有 5 种方法让最终目标和为 3。 --1 + 1 + 1 + 1 + 1 = 3 -+1 - 1 + 1 + 1 + 1 = 3 -+1 + 1 - 1 + 1 + 1 = 3 -+1 + 1 + 1 - 1 + 1 = 3 -+1 + 1 + 1 + 1 - 1 = 3 -``` - -- 示例 2: - -```python -输入:nums = [1], target = 1 -输出:1 -``` - -#### 4.1.3 解题思路 - -##### 思路 1:深度优先搜索(超时) - -使用深度优先搜索对每位数字进行 `+` 或者 `-`,具体步骤如下: - -1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 -2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 $i$ 到达最后一个位置 $size$: - 1. 如果和 `cur_sum` 等于目标和 $target$,则返回方案数 $1$。 - 2. 如果和 `cur_sum` 不等于目标和 $target$,则返回方案数 $0$。 -4. 递归搜索 $i + 1$ 位置,和为 `cur_sum - nums[i]` 的方案数。 -5. 递归搜索 $i + 1$ 位置,和为 `cur_sum + nums[i]` 的方案数。 -6. 将 4 ~ 5 两个方案数加起来就是当前位置 $i$、和为 `cur_sum` 的方案数,返回该方案数。 -7. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 - -##### 思路 1:代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - size = len(nums) - - def dfs(i, cur_sum): - if i == size: - if cur_sum == target: - return 1 - else: - return 0 - ans = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) - return ans - - return dfs(0, 0) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 - -##### 思路 2:记忆化搜索 - -在思路 1 中我们单独使用深度优先搜索对每位数字进行 `+` 或者 `-` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。 - -这里我们使用哈希表 $table$ 记录遍历过的位置 $i$ 及所得到的的当前和`cur_sum` 下的方案数,来避免重复搜索。具体步骤如下: - -1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 -2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 $i$ 遍历完所有位置: - 1. 如果和 `cur_sum` 等于目标和 $target$,则返回方案数 $1$。 - 2. 如果和 `cur_sum` 不等于目标和 $target$,则返回方案数 $0$。 -4. 如果当前位置 $i$、和为 `cur_sum` 之前记录过(即使用 $table$ 记录过对应方案数),则返回该方案数。 -5. 如果当前位置 $i$、和为 `cur_sum` 之前没有记录过,则: - 1. 递归搜索 $i + 1$ 位置,和为 `cur_sum - nums[i]` 的方案数。 - 2. 递归搜索 $i + 1$ 位置,和为 `cur_sum + nums[i]` 的方案数。 - 3. 将上述两个方案数加起来就是当前位置 $i$、和为 `cur_sum` 的方案数,将其记录到哈希表 $table$ 中,并返回该方案数。 -6. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 - -##### 思路 2:代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - size = len(nums) - table = dict() - - def dfs(i, cur_sum): - if i == size: - if cur_sum == target: - return 1 - else: - return 0 - - if (i, cur_sum) in table: - return table[(i, cur_sum)] - - cnt = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) - table[(i, cur_sum)] = cnt - return cnt - - return dfs(0, 0) -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 - -### 4.2 第 N 个泰波那契数 - -#### 4.2.1 题目链接 - -- [1137. 第 N 个泰波那契数 - 力扣](https://leetcode.cn/problems/n-th-tribonacci-number/) - -#### 4.2.2 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回第 $n$ 个泰波那契数。 - -**说明**: - -- **泰波那契数**:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 -- $0 \le n \le 37$。 -- 答案保证是一个 32 位整数,即 $answer \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:4 -解释: -T_3 = 0 + 1 + 1 = 2 -T_4 = 1 + 1 + 2 = 4 -``` - -- 示例 2: - -```python -输入:n = 25 -输出:1389537 -``` - -#### 4.2.3 解题思路 - -##### 思路 1:记忆化搜索 - -1. 问题的状态定义为:第 $n$ 个泰波那契数。其状态转移方程为:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 -2. 定义一个长度为 $n + 1$ 数组 $memo$ 用于保存一斤个计算过的泰波那契数。 -3. 定义递归函数 `my_tribonacci(n, memo)`。 - 1. 当 $n = 0$ 或者 $n = 1$,或者 $n = 2$ 时直接返回结果。 - 2. 当 $n > 2$ 时,首先检查是否计算过 $T(n)$,即判断 $memo[n]$ 是否等于 $0$。 - 1. 如果 $memo[n] \ne 0$,说明已经计算过 $T(n)$,直接返回 $memo[n]$。 - 2. 如果 $memo[n] = 0$,说明没有计算过 $T(n)$,则递归调用 `my_tribonacci(n - 3, memo)`、`my_tribonacci(n - 2, memo)`、`my_tribonacci(n - 1, memo)`,并将计算结果存入 $memo[n]$ 中,并返回 $memo[n]$。 - -##### 思路 1:代码 - -```python -class Solution: - def tribonacci(self, n: int) -> int: - # 使用数组保存已经求解过的 T(k) 的结果 - memo = [0 for _ in range(n + 1)] - return self.my_tribonacci(n, memo) - - def my_tribonacci(self, n: int, memo: List[int]) -> int: - if n == 0: - return 0 - if n == 1 or n == 2: - return 1 - - if memo[n] != 0: - return memo[n] - memo[n] = self.my_tribonacci(n - 3, memo) + self.my_tribonacci(n - 2, memo) + self.my_tribonacci(n - 1, memo) - return memo[n] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -1. 【文章】[记忆化搜索 - OI Wiki](https://oi-wiki.org/dp/memo/) diff --git a/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md b/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md deleted file mode 100644 index 616b85d6..00000000 --- a/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md +++ /dev/null @@ -1,14 +0,0 @@ -### 记忆化搜索题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0375 | [猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md) | 数学、动态规划、博弈 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 0576 | [出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md) | 动态规划 | 中等 | -| 0087 | [扰乱字符串](https://leetcode.cn/problems/scramble-string/) | | 字符串、动态规划 | 困难 | -| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 | -| 0552 | [学生出勤记录 II](https://leetcode.cn/problems/student-attendance-record-ii/) | | 动态规划 | 困难 | -| 0913 | [猫和老鼠](https://leetcode.cn/problems/cat-and-mouse/) | | 图、拓扑排序、记忆化搜索、数学、动态规划、博弈 | 困难 | -| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | - diff --git a/Contents/10.Dynamic-Programming/02.Memoization/index.md b/Contents/10.Dynamic-Programming/02.Memoization/index.md deleted file mode 100644 index 8d8ac48e..00000000 --- a/Contents/10.Dynamic-Programming/02.Memoization/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [记忆化搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md) -- [记忆化搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md b/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md deleted file mode 100644 index 1f7497e3..00000000 --- a/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md +++ /dev/null @@ -1,745 +0,0 @@ -## 1. 线性动态规划简介 - -> **线性动态规划**:具有「线性」阶段划分的动态规划方法统称为线性动态规划(简称为「线性 DP」),如下图所示。 - -![](https://qcdn.itcharge.cn/images/202303122358154.png) - -如果状态包含多个维度,但是每个维度上都是线性划分的阶段,也属于线性 DP。比如背包问题、区间 DP、数位 DP 等都属于线性 DP。 - -线性 DP 问题的划分方法有多种方式。 - -- 如果按照「状态的维度数」进行分类,我们可以将线性 DP 问题分为:一维线性 DP 问题、二维线性 DP 问题,以及多维线性 DP 问题。 -- 如果按照「问题的输入格式」进行分类,我们可以将线性 DP 问题分为:单串线性 DP 问题、双串线性 DP 问题、矩阵线性 DP 问题,以及无串线性 DP 问题。 - -本文中,我们将按照问题的输入格式进行分类,对线性 DP 问题中各种类型问题进行一一讲解。 - -## 2. 单串线性 DP 问题 - -> **单串线性 DP** 问题:问题的输入为单个数组或单个字符串的线性 DP 问题。状态一般可定义为 $dp[i]$,表示为: -> -> 1. 「以数组中第 $i$ 个位置元素 $nums[i]$ 为结尾的子数组($nums[0]...nums[i]$)」的相关解。 -> 2. 「以数组中第 $i - 1$ 个位置元素 $nums[i - 1]$ 为结尾的子数组($nums[0]...nums[i - 1]$)」的相关解。 -> 3. 「以数组中前 $i$ 个元素为子数组($nums[0]...nums[i - 1]$)」的相关解。 - -这 $3$ 种状态的定义区别在于相差一个元素 $nums[i]$。 - -1. 第 $1$ 种状态:子数组的长度为 $i + 1$,子数组长度不可为空; -2. 第 $2$ 种状态、第 $3$ 种状态:这两种状态描述是相同的。子数组的长度为 $i$,子数组长度可为空。在 $i = 0$ 时,方便用于表示空数组(以数组中前 $0$ 个元素为子数组)。 - -### 2.1 最长递增子序列 - -单串线性 DP 问题中最经典的问题就是「最长递增子序列(Longest Increasing Subsequence,简称 LIS)」。 - -#### 2.1.1 题目链接 - -- [300. 最长递增子序列 - 力扣](https://leetcode.cn/problems/longest-increasing-subsequence/) - -#### 2.1.2 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:找到其中最长严格递增子序列的长度。 - -**说明**: - -- **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,$[3,6,2,7]$ 是数组 $[0,3,1,6,2,2,7]$ 的子序列。 -- $1 \le nums.length \le 2500$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [10,9,2,5,3,7,101,18] -输出:4 -解释:最长递增子序列是 [2,3,7,101],因此长度为 4。 -``` - -- 示例 2: - -```python -输入:nums = [0,1,0,3,2,3] -输出:4 -``` - -#### 2.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照子序列的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。 - -###### 3. 状态转移方程 - -一个较小的数后边如果出现一个较大的数,则会形成一个更长的递增子序列。 - -对于满足 $0 \le j < i$ 的数组元素 $nums[j]$ 和 $nums[i]$ 来说: - -- 如果 $nums[j] < nums[i]$,则 $nums[i]$ 可以接在 $nums[j]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[j]$ 结尾的最长递增子序列长度」的基础上加 $1$,即:$dp[i] = dp[j] + 1$。 - -- 如果 $nums[j] \le nums[i]$,则 $nums[i]$ 不可以接在 $nums[j]$ 后面,可以直接跳过。 - -综上,我们的状态转移方程为:$dp[i] = max(dp[i], dp[j] + 1), 0 \le j < i, nums[j] < nums[i]$。 - -###### 4. 初始条件 - -默认状态下,把数组中的每个元素都作为长度为 $1$ 的递增子序列。即 $dp[i] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。那为了计算出最大的最长递增子序列长度,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 - -##### 思路 1:动态规划代码 - -```python -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - size = len(nums) - dp = [1 for _ in range(size)] - - for i in range(size): - for j in range(i): - if nums[i] > nums[j]: - dp[i] = max(dp[i], dp[j] + 1) - - return max(dp) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -### 2.2 最大子数组和 - -单串线性 DP 问题中除了子序列相关的线性 DP 问题,还有子数组相关的线性 DP 问题。 - -> **注意**: -> -> - **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。 -> - **子数组**:指的是数组中的一个连续子序列。 -> -> 「子序列」与「子数组」都可以看做是原数组的一部分,而且都不会改变原来数组中元素的相对顺序。其区别在于数组元素是否要求连续。 - -#### 2.2.1 题目链接 - -- [53. 最大子数组和 - 力扣](https://leetcode.cn/problems/maximum-subarray/) - -#### 2.2.2 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 - -**说明**: - -- **子数组**:指的是数组中的一个连续部分。 -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [-2,1,-3,4,-1,2,1,-5,4] -输出:6 -解释:连续子数组 [4,-1,2,1] 的和最大,为 6。 -``` - -- 示例 2: - -```python -输入:nums = [1] -输出:1 -``` - -#### 2.2.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照连续子数组的结束位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。 - -###### 3. 状态转移方程 - -状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则我们可以从「第 $i - 1$ 个数结尾的连续子数组的最大和」,以及「第 $i$ 个数的值」来讨论 $dp[i]$。 - -- 如果 $dp[i - 1] < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,即:$dp[i - 1] + nums[i] < nums[i]$。所以,此时 $dp[i]$ 应取「第 $i$ 个数的值」,即 $dp[i] = nums[i]$。 -- 如果 $dp[i - 1] \ge 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,即:$dp[i - 1] + nums[i] \ge nums[i]$。所以,此时 $dp[i]$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」+「 第 $i$ 个数的值」,即 $dp[i] = dp[i - 1] + nums[i]$。 - -归纳一下,状态转移方程为: - -$dp[i] = \begin{cases} nums[i], & dp[i - 1] < 0 \cr dp[i - 1] + nums[i] & dp[i - 1] \ge 0 \end{cases}$ - -###### 4. 初始条件 - -- 第 $0$ 个数结尾的连续子数组的最大和为 $nums[0]$,即 $dp[0] = nums[0]$。 - -###### 5. 最终结果 - -根据状态定义,$dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则最终结果应为所有 $dp[i]$ 的最大值,即 $max(dp)$。 - -##### 思路 1:代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - size = len(nums) - dp = [0 for _ in range(size)] - - dp[0] = nums[0] - for i in range(1, size): - if dp[i - 1] < 0: - dp[i] = nums[i] - else: - dp[i] = dp[i - 1] + nums[i] - return max(dp) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -##### 思路 2:动态规划 + 滚动优化 - -因为 $dp[i]$ 只和 $dp[i - 1]$ 和当前元素 $nums[i]$ 相关,我们也可以使用一个变量 $subMax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。然后使用 $ansMax$ 来保存全局中最大值。 - -##### 思路 2:代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - size = len(nums) - subMax = nums[0] - ansMax = nums[0] - - for i in range(1, size): - if subMax < 0: - subMax = nums[i] - else: - subMax += nums[i] - ansMax = max(ansMax, subMax) - return ansMax -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(1)$。 - -### 2.3 最长的斐波那契子序列的长度 - -有一些单串线性 DP 问题在定义状态时需要考虑两个结束位置,只考虑一个结束位置的无法清楚描述问题。这时候我们就需要需要增加一个结束位置维度来定义状态。 - -#### 2.3.1 题目链接 - -- [873. 最长的斐波那契子序列的长度 - 力扣](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) - -#### 2.3.2 题目大意 - -**描述**:给定一个严格递增的正整数数组 $arr$。 - -**要求**:从数组 $arr$ 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 0。 - -**说明**: - -- **斐波那契式序列**:如果序列 $X_1, X_2, ..., X_n$ 满足: - - - $n \ge 3$; - - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 - - 则称该序列为斐波那契式序列。 - -- **斐波那契式子序列**:从序列 $A$ 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:$A = [3, 4, 5, 6, 7, 8]$。则 $[3, 5, 8]$ 是 $A$ 的一个斐波那契式子序列。 - -- $3 \le arr.length \le 1000$。 - -- $1 \le arr[i] < arr[i + 1] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入: arr = [1,2,3,4,5,6,7,8] -输出: 5 -解释: 最长的斐波那契式子序列为 [1,2,3,5,8]。 -``` - -- 示例 2: - -```python -输入: arr = [1,3,7,11,12,14,18] -输出: 3 -解释: 最长的斐波那契式子序列有 [1,11,12]、[3,11,14] 以及 [7,11,18]。 -``` - -#### 2.3.3 解题思路 - -##### 思路 1: 暴力枚举(超时) - -假设 $arr[i]$、$arr[j]$、$arr[k]$ 是序列 $arr$ 中的 $3$ 个元素,且满足关系:$arr[i] + arr[j] == arr[k]$,则 $arr[i]$、$arr[j]$、$arr[k]$ 就构成了 $arr$ 的一个斐波那契式子序列。 - -通过 $arr[i]$、$arr[j]$,我们可以确定下一个斐波那契式子序列元素的值为 $arr[i] + arr[j]$。 - -因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 $arr[i]$、$arr[j]$,则可以顺着 $arr$ 序列,从第 $j + 1$ 的元素开始,查找值为 $arr[i] + arr[j]$ 的元素 。找到 $arr[i] + arr[j]$ 之后,然后再顺着查找子序列的下一个元素。 - -简单来说,就是确定了 $arr[i]$、$arr[j]$,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 $arr[i]$、$arr[j]$,统计不同的斐波那契式子序列的长度。 - -最后将这些长度进行比较,其中最长的长度就是答案。 - -##### 思路 1:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - k = j + 1 - while k < size: - if arr[temp_i] + arr[temp_j] == arr[k]: - temp_ans += 1 - temp_i = temp_j - temp_j = k - k += 1 - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(1)$。 - -##### 思路 2:哈希表 - -对于 $arr[i]$、$arr[j]$,要查找的元素 $arr[i] + arr[j]$ 是否在 $arr$ 中,我们可以预先建立一个反向的哈希表。键值对关系为 $value : idx$,这样就能在 $O(1)$ 的时间复杂度通过 $arr[i] + arr[j]$ 的值查找到对应的 $arr[k]$,而不用像原先一样线性查找 $arr[k]$ 了。 - -##### 思路 2:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - idx_map = dict() - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - while arr[temp_i] + arr[temp_j] in idx_map: - temp_ans += 1 - k = idx_map[arr[temp_i] + arr[temp_j]] - temp_i = temp_j - temp_j = k - - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -##### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -##### 思路 3:动态规划 + 哈希表 - -###### 1. 划分阶段 - -按照斐波那契式子序列相邻两项的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。 - -###### 3. 状态转移方程 - -以 $arr[j]$、$arr[k]$ 结尾的斐波那契式子序列的最大长度 = 满足 $arr[i] + arr[j] = arr[k]$ 条件下,以 $arr[i]$、$arr[j]$ 结尾的斐波那契式子序列的最大长度加 $1$。即状态转移方程为:$dp[j][k] = max_{(A[i] + A[j] = A[k], \quad i < j < k)}(dp[i][j] + 1)$。 - -###### 4. 初始条件 - -默认状态下,数组中任意相邻两项元素都可以作为长度为 $2$ 的斐波那契式子序列,即 $dp[i][j] = 2$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。那为了计算出最大的最长递增子序列长度,则需要在进行状态转移时,求出最大值 $ans$ 即为最终结果。 - -因为题目定义中,斐波那契式中 $n \ge 3$,所以只有当 $ans \ge 3$ 时,返回 $ans$。如果 $ans < 3$,则返回 $0$。 - -> **注意**:在进行状态转移的同时,我们应和「思路 2:哈希表」一样采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 - -##### 思路 3:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - - dp = [[0 for _ in range(size)] for _ in range(size)] - ans = 0 - - # 初始化 dp - for i in range(size): - for j in range(i + 1, size): - dp[i][j] = 2 - - idx_map = {} - # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - if arr[i] + arr[j] in idx_map: - # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 - k = idx_map[arr[i] + arr[j]] - - dp[j][k] = max(dp[j][k], dp[i][j] + 1) - ans = max(ans, dp[j][k]) - - if ans >= 3: - return ans - return 0 -``` - -##### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -## 3. 双串线性 DP 问题 - -> **双串线性 DP 问题**:问题的输入为两个数组或两个字符串的线性 DP 问题。状态一般可定义为 $dp[i][j]$,表示为: -> -> 1. 「以第一个数组中第 $i$ 个位置元素 $nums1[i]$ 为结尾的子数组($nums1[0]...nums1[i]$)」与「以第二个数组中第 $j$ 个位置元素 $nums2[j]$ 为结尾的子数组($nums2[0]...nums2[j]$)」的相关解。 -> 2. 「以第一个数组中第 $i - 1$ 个位置元素 $nums1[i - 1]$ 为结尾的子数组($nums1[0]...nums1[i - 1]$)」与「以第二个数组中第 $j - 1$ 个位置元素 $nums2[j - 1]$ 为结尾的子数组($nums2[0]...nums2[j - 1]$)」的相关解。 -> 3. 「以第一个数组中前 $i$ 个元素为子数组($nums1[0]...nums1[i - 1]$)」与「以第二个数组中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的相关解。 - -这 $3$ 种状态的定义区别在于相差一个元素 $nums1[i]$ 或 $nums2[j]$。 - -1. 第 $1$ 种状态:子数组的长度为 $i + 1$ 或 $j + 1$,子数组长度不可为空 -2. 第 $2$ 种状态、第 $3$ 种状态:子数组的长度为 $i$ 或 $j$,子数组长度可为空。$i = 0$ 或 $j = 0$ 时,方便用于表示空数组(以数组中前 $0$ 个元素为子数组)。 - -### 3.1 最长公共子序列 - -双串线性 DP 问题中最经典的问题就是「最长公共子序列(Longest Common Subsequence,简称 LCS)」。 - -#### 3.1.1 题目链接 - -- [1143. 最长公共子序列 - 力扣](https://leetcode.cn/problems/longest-common-subsequence/) - -#### 3.1.2 题目大意 - -**描述**:给定两个字符串 $text1$ 和 $text2$。 - -**要求**:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 $0$。 - -**说明**: - -- **子序列**:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 -- **公共子序列**:两个字符串所共同拥有的子序列。 -- $1 \le text1.length, text2.length \le 1000$。 -- $text1$ 和 $text2$ 仅由小写英文字符组成。 - -**示例**: - -- 示例 1: - -```python -输入:text1 = "abcde", text2 = "ace" -输出:3 -解释:最长公共子序列是 "ace",它的长度为 3。 -``` - -- 示例 2: - -```python -输入:text1 = "abc", text2 = "abc" -输出:3 -解释:最长公共子序列是 "abc",它的长度为 3。 -``` - -#### 3.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度为 $dp[i][j]$。 - -###### 3. 状态转移方程 - -双重循环遍历字符串 $text1$ 和 $text2$,则状态转移方程为: - -1. 如果 $text1[i - 1] = text2[j - 1]$,说明两个子字符串的最后一位是相同的,所以最长公共子序列长度加 $1$。即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 -2. 如果 $text1[i - 1] \ne text2[j - 1]$,说明两个子字符串的最后一位是不同的,则 $dp[i][j]$ 需要考虑以下两种情况,取两种情况中最大的那种:$dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$。 - 1. 「以 $text1$ 中前 $i - 1$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i - 1][j]$。 - 2. 「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j - 1$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i][j - 1]$。 - -###### 4. 初始条件 - -1. 当 $i = 0$ 时,$str1$ 表示的是空串,空串与 $str2$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 -2. 当 $j = 0$ 时,$str2$ 表示的是空串,$str1$ 与 空串的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[sise1][size2]$(即 $text1$ 与 $text2$ 的最长公共子序列长度)即可,其中 $size1$、$size2$ 分别为 $text1$、$text2$ 的字符串长度。 - -##### 思路 1:代码 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - size1 = len(text1) - size2 = len(text2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if text1[i - 1] == text2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - else: - dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - - return dp[size1][size2] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $text1$、$text2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 -- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 - -### 3.2 最长重复子数组 - -#### 3.2.1 题目链接 - -- [718. 最长重复子数组 - 力扣](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) - -#### 3.2.2 题目大意 - -**描述**:给定两个整数数组 $nums1$、$nums2$。 - -**要求**:计算两个数组中公共的、长度最长的子数组长度。 - -**说明**: - -- $1 \le nums1.length, nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] -输出:3 -解释:长度最长的公共子数组是 [3,2,1] 。 -``` - -- 示例 2: - -```python -输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] -输出:5 -``` - -#### 3.2.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照子数组结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。 - -###### 3. 状态转移方程 - -1. 如果 $nums1[i - 1] = nums2[j - 1]$,则当前元素可以构成公共子数组,此时 $dp[i][j] = dp[i - 1][j - 1] + 1$。 -2. 如果 $nums1[i - 1] \ne nums2[j - 1]$,则当前元素不能构成公共子数组,此时 $dp[i][j] = 0$。 - -###### 4. 初始条件 - -- 当 $i = 0$ 时,$nums1[0]...nums1[i - 1]$ 表示的是空数组,空数组与 $nums2[0]...nums2[j - 1]$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 -- 当 $j = 0$ 时,$nums2[0]...nums2[j - 1]$ 表示的是空数组,空数组与 $nums1[0]...nums1[i - 1]$ 的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 - -###### 5. 最终结果 - -- 根据状态定义, $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。在遍历过程中,我们可以使用 $res$ 记录下所有 $dp[i][j]$ 中最大值即为答案。 - -##### 思路 1:代码 - -```python -class Solution: - def findLength(self, nums1: List[int], nums2: List[int]) -> int: - size1 = len(nums1) - size2 = len(nums2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - res = 0 - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if nums1[i - 1] == nums2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - if dp[i][j] > res: - res = dp[i][j] - - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 -- **空间复杂度**:$O(n \times m)$。 - -### 3.3 编辑距离 - -双串线性 DP 问题中除了经典的最长公共子序列问题之外,还包括字符串的模糊匹配问题。 - -#### 3.3.1 题目链接 - -- [72. 编辑距离 - 力扣](https://leetcode.cn/problems/edit-distance/) - -#### 3.3.2 题目大意 - -**描述**:给定两个单词 $word1$、$word2$。 - -对一个单词可以进行以下三种操作: - -- 插入一个字符 -- 删除一个字符 -- 替换一个字符 - -**要求**:计算出将 $word1$ 转换为 $word2$ 所使用的最少操作数。 - -**说明**: - -- $0 \le word1.length, word2.length \le 500$。 -- $word1$ 和 $word2$ 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:word1 = "horse", word2 = "ros" -输出:3 -解释: -horse -> rorse (将 'h' 替换为 'r') -rorse -> rose (删除 'r') -rose -> ros (删除 'e') -``` - -- 示例 2: - -```python -输入:word1 = "intention", word2 = "execution" -输出:5 -解释: -intention -> inention (删除 't') -inention -> enention (将 'i' 替换为 'e') -enention -> exention (将 'n' 替换为 'x') -exention -> exection (将 'n' 替换为 'c') -exection -> execution (插入 'u') -``` - -#### 3.3.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,所需要的最少操作次数。 - -###### 3. 状态转移方程 - -1. 如果当前字符相同($word1[i - 1] = word2[j - 1]$),无需插入、删除、替换。$dp[i][j] = dp[i - 1][j - 1]$。 -2. 如果当前字符不同($word1[i - 1] \ne word2[j - 1]$),$dp[i][j]$ 取源于以下三种情况中的最小情况: - 1. 替换($word1[i - 1]$ 替换为 $word2[j - 1]$):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上替换的操作数 $1$,即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 - 2. 插入($word1$ 在第 $i - 1$ 位置上插入元素):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」 变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,再加上插入需要的操作数 $1$,即:$dp[i][j] = dp[i - 1][j] + 1$。 - 3. 删除($word1$ 删除第 $i - 1$ 位置元素):最少操作次数依赖于「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上删除需要的操作数 $1$,即:$dp[i][j] = dp[i][j - 1] + 1$。 - -综合上述情况,状态转移方程为: - -$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & word1[i - 1] = word2[j - 1] \cr min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 & word1[i - 1] \ne word2[j - 1] \end{cases}$ - -###### 4. 初始条件 - -- 当 $i = 0$,「以 $word1$ 中前 $0$ 个字符组成的子字符串 $str1$」为空字符串,「$str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」时,至少需要插入 $j$ 次,即:$dp[0][j] = j$。 -- 当 $j = 0$,「以 $word2$ 中前 $0$ 个字符组成的子字符串 $str2$」为空字符串,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「$str2$」时,至少需要删除 $i$ 次,即:$dp[i][0] = i$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[sise1][size2]$(即 $word1$ 变为 $word2$ 所使用的最少操作数)即可。其中 $size1$、$size2$ 分别为 $word1$、$word2$ 的字符串长度。 - -##### 思路 1:代码 - -```python -class Solution: - def minDistance(self, word1: str, word2: str) -> int: - size1 = len(word1) - size2 = len(word2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - - for i in range(size1 + 1): - dp[i][0] = i - for j in range(size2 + 1): - dp[0][j] = j - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if word1[i - 1] == word2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] - else: - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 - return dp[size1][size2] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $word1$、$word2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 -- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 - -## 参考资料 - -- 【书籍】算法竞赛进阶指南 -- 【文章】[动态规划概念和基础线性DP | 潮汐朝夕](https://chengzhaoxi.xyz/1a4a2483.html) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md b/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md deleted file mode 100644 index ea89efb2..00000000 --- a/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md +++ /dev/null @@ -1,376 +0,0 @@ -## 4. 矩阵线性 DP问题 - -> **矩阵线性 DP 问题**:问题的输入为二维矩阵的线性 DP 问题。状态一般可定义为 $dp[i][j]$,表示为:从「位置 $(0, 0)$」到达「位置 $(i, j)$」的相关解。 - -### 4.1 最小路径和 - -#### 4.1.1 题目链接 - -- [64. 最小路径和 - 力扣](https://leetcode.cn/problems/minimum-path-sum/) - -#### 4.1.2 题目大意 - -**描述**:给定一个包含非负整数的 $m \times n$ 大小的网格 $grid$。 - -**要求**:找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 - -**说明**: - -- 每次只能向下或者向右移动一步。 -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 200$。 -- $0 \le grid[i][j] \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/05/minpath.jpg) - -```python -输入:grid = [[1,3,1],[1,5,1],[4,2,1]] -输出:7 -解释:因为路径 1→3→1→1→1 的总和最小。 -``` - -- 示例 2: - -```python -输入:grid = [[1,2,3],[4,5,6]] -输出:12 -``` - -#### 4.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:从位置 $(0, 0)$ 到达位置 $(i, j)$ 的最小路径和。 - -###### 3. 状态转移方程 - -当前位置 $(i, j)$ 只能从左侧位置 $(i, j - 1)$ 或者上方位置 $(i - 1, j)$ 到达。为了使得从左上角到达 $(i, j)$ 位置的最小路径和最小,应从 $(i, j - 1)$ 位置和 $(i - 1, j)$ 位置选择路径和最小的位置达到 $(i, j)$。 - -即状态转移方程为:$dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]$。 - -###### 4. 初始条件 - -- 当左侧和上方是矩阵边界时(即 $i = 0, j = 0$),$dp[i][j] = grid[i][j]$。 -- 当只有左侧是矩阵边界时(即 $i \ne 0, j = 0$),只能从上方到达,$dp[i][j] = dp[i - 1][j] + grid[i][j]$。 -- 当只有上方是矩阵边界时(即 $i = 0, j \ne 0$),只能从左侧到达,$dp[i][j] = dp[i][j - 1] + grid[i][j]$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[rows - 1][cols - 1]$(即从左上角到达 $(rows - 1, cols - 1)$ 位置的最小路径和)即可。其中 $rows$、$cols$ 分别为 $grid$ 的行数、列数。 - -##### 思路 1:代码 - -```python -class Solution: - def minPathSum(self, grid: List[List[int]]) -> int: - rows, cols = len(grid), len(grid[0]) - dp = [[0 for _ in range(cols)] for _ in range(rows)] - - dp[0][0] = grid[0][0] - - for i in range(1, rows): - dp[i][0] = dp[i - 1][0] + grid[i][0] - - for j in range(1, cols): - dp[0][j] = dp[0][j - 1] + grid[0][j] - - for i in range(1, rows): - for j in range(1, cols): - dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] - - return dp[rows - 1][cols - 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m * n)$,其中 $m$、$n$ 分别为 $grid$ 的行数和列数。 -- **空间复杂度**:$O(m * n)$。 - -### 4.2 最大正方形 - -#### 4.2.1 题目链接 - -- [221. 最大正方形 - 力扣](https://leetcode.cn/problems/maximal-square/) - -#### 4.2.2 题目大意 - -**描述**:给定一个由 `'0'` 和 `'1'` 组成的二维矩阵 $matrix$。 - -**要求**:找到只包含 `'1'` 的最大正方形,并返回其面积。 - -**说明**: - -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le m, n \le 300$。 -- $matrix[i][j]$ 为 `'0'` 或 `'1'`。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/26/max1grid.jpg) - -```python -输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] -输出:4 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/26/max2grid.jpg) - -```python -输入:matrix = [["0","1"],["1","0"]] -输出:1 -``` - -#### 4.2.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照正方形的右下角坐标进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。 - -###### 3. 状态转移方程 - -只有当矩阵位置 $(i, j)$ 值为 $1$ 时,才有可能存在正方形。 - -- 如果矩阵位置 $(i, j)$ 上值为 $0$,则 $dp[i][j] = 0$。 -- 如果矩阵位置 $(i, j)$ 上值为 $1$,则 $dp[i][j]$ 的值由该位置上方、左侧、左上方三者共同约束的,为三者中最小值加 $1$。即:$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1$。 - -###### 4. 初始条件 - -- 默认所有以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长都为 $0$,即 $dp[i][j] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。则最终结果为所有 $dp[i][j]$ 中的最大值。 - -##### 思路 1:代码 - -```python -class Solution: - def maximalSquare(self, matrix: List[List[str]]) -> int: - rows, cols = len(matrix), len(matrix[0]) - max_size = 0 - dp = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] - for i in range(rows): - for j in range(cols): - if matrix[i][j] == '1': - if i == 0 or j == 0: - dp[i][j] = 1 - else: - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 - max_size = max(max_size, dp[i][j]) - return max_size * max_size -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为二维矩阵 $matrix$ 的行数和列数。 -- **空间复杂度**:$O(m \times n)$。 - -## 5. 无串线性 DP 问题 - -> **无串线性 DP 问题**:问题的输入不是显式的数组或字符串,但依然可分解为若干子问题的线性 DP 问题。 - -### 5.1 整数拆分 - -#### 5.1.1 题目链接 - -- [343. 整数拆分 - 力扣](https://leetcode.cn/problems/integer-break/) - -#### 5.1.2 题目大意 - -**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 - -**要求**:返回可以获得的最大乘积。 - -**说明**: - -- $2 \le n \le 58$。 - -**示例**: - -- 示例 1: - -```python -输入: n = 2 -输出: 1 -解释: 2 = 1 + 1, 1 × 1 = 1。 -``` - -- 示例 2: - -```python -输入: n = 10 -输出: 36 -解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 -``` - -#### 5.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照正整数进行划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 - -###### 3. 状态转移方程 - -当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: - -1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 -2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 - -则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 - -由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: - -$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 - -###### 4. 初始条件 - -- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 - -##### 思路 1:代码 - -```python -class Solution: - def integerBreak(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - for i in range(2, n + 1): - for j in range(i): - dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) - return dp[n] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n)$。 - -### 5.2 只有两个键的键盘 - -#### 5.2.1 题目链接 - -- [650. 只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) - -#### 5.2.2 题目大意 - -**描述**:最初记事本上只有一个字符 `'A'`。你每次可以对这个记事本进行两种操作: - -- **Copy All(复制全部)**:复制这个记事本中的所有字符(不允许仅复制部分字符)。 -- **Paste(粘贴)**:粘贴上一次复制的字符。 - -现在,给定一个数字 $n$,需要使用最少的操作次数,在记事本上输出恰好 $n$ 个 `'A'` 。 - -**要求**:返回能够打印出 $n$ 个 `'A'` 的最少操作次数。 - -**说明**: - -- $1 \le n \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:3 -输出:3 -解释 -最初, 只有一个字符 'A'。 -第 1 步, 使用 Copy All 操作。 -第 2 步, 使用 Paste 操作来获得 'AA'。 -第 3 步, 使用 Paste 操作来获得 'AAA'。 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:0 -``` - -#### 5.2.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照字符 `'A'` 的个数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 - -###### 3. 状态转移方程 - -1. 对于 $i$ 个字符 `'A'`,如果 $i$ 可以被一个小于 $i$ 的整数 $j$ 除尽($j$ 是 $i$ 的因子),则说明 $j$ 个字符 `'A'` 可以通过「复制」+「粘贴」总共 $\frac{i}{j}$ 次得到 $i$ 个字符 `'A'`。 -2. 而得到 $j$ 个字符 `'A'`,最少需要的操作数可以通过 $dp[j]$ 获取。 - -则我们可以枚举 $i$ 的因子,从中找到在满足 $j$ 能够整除 $i$ 的条件下,最小的 $dp[j] + \frac{i}{j}$,即为 $dp[i]$,即 $dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j})$。 - -由于 $j$ 能够整除 $i$,则 $j$ 与 $\frac{i}{j}$ 都是 $i$ 的因子,两者中必有一个因子是小于等于 $\sqrt{i}$ 的,所以在枚举 $i$ 的因子时,我们只需要枚举区间 $[1, \sqrt{i}]$ 即可。 - -综上所述,状态转移方程为:$dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j}, dp[\frac{i}{j}] + j)$。 - -###### 4. 初始条件 - -- 当 $i = 1$ 时,最少需要的操作数为 $0$。所以 $dp[1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 所以最终结果为 $dp[n]$。 - -##### 思路 1:动态规划代码 - -```python -import math - -class Solution: - def minSteps(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - for i in range(2, n + 1): - dp[i] = float('inf') - for j in range(1, int(math.sqrt(n)) + 1): - if i % j == 0: - dp[i] = min(dp[i], dp[j] + i // j, dp[i // j] + j) - - return dp[n] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \sqrt{n})$。外层循环遍历的时间复杂度是 $O(n)$,内层循环遍历的时间复杂度是 $O(\sqrt{n})$,所以总体时间复杂度为 $O(n \sqrt{n})$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -## 参考资料 - -- 【书籍】算法竞赛进阶指南 -- 【文章】[动态规划概念和基础线性DP | 潮汐朝夕](https://chengzhaoxi.xyz/1a4a2483.html) diff --git a/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md b/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md deleted file mode 100644 index 47b000b8..00000000 --- a/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md +++ /dev/null @@ -1,91 +0,0 @@ -### 线性 DP 题目 - -#### 单串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 数组、二分查找、动态规划 | 中等 | -| 0673 | [最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0673.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、动态规划 | 中等 | -| 0354 | [俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0354.%20%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83%E4%BF%A1%E5%B0%81%E9%97%AE%E9%A2%98.md) | 数组、二分查找、动态规划、排序 | 困难 | -| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治、动态规划 | 中等 | -| 0152 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0152.%20%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划 | 中等 | -| 0918 | [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0918.%20%E7%8E%AF%E5%BD%A2%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 队列、数组、分治、动态规划、单调队列 | 中等 | -| 0198 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0198.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.md) | 数组、动态规划 | 中等 | -| 0213 | [打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0213.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20II.md) | 数组、动态规划 | 中等 | -| 0740 | [删除并获得点数](https://leetcode.cn/problems/delete-and-earn/) | | 数组、哈希表、动态规划 | 中等 | -| 1388 | [3n 块披萨](https://leetcode.cn/problems/pizza-with-3n-slices/) | | 贪心、数组、动态规划、堆(优先队列) | 困难 | -| 0873 | [最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0873.%20%E6%9C%80%E9%95%BF%E7%9A%84%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6.md) | 数组、哈希表、动态规划 | 中等 | -| 1027 | [最长等差数列](https://leetcode.cn/problems/longest-arithmetic-subsequence/) | | 数组、哈希表、二分查找、动态规划 | 中等 | -| 1055 | [形成字符串的最短路径](https://leetcode.cn/problems/shortest-way-to-form-string/) | | 贪心、双指针、字符串 | 中等 | -| 0368 | [最大整除子集](https://leetcode.cn/problems/largest-divisible-subset/) | | 数组、数学、动态规划、排序 | 中等 | -| 0032 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0032.%20%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7.md) | 栈、字符串、动态规划 | 困难 | -| 0413 | [等差数列划分](https://leetcode.cn/problems/arithmetic-slices/) | | 数组、动态规划 | 中等 | -| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 | -| 0639 | [解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0639.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95%20II.md) | 字符串、动态规划 | 困难 | -| 0132 | [分割回文串 II](https://leetcode.cn/problems/palindrome-partitioning-ii/) | | 字符串、动态规划 | 困难 | -| 1220 | [统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1220.%20%E7%BB%9F%E8%AE%A1%E5%85%83%E9%9F%B3%E5%AD%97%E6%AF%8D%E5%BA%8F%E5%88%97%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 动态规划 | 困难 | -| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 | -| 0801 | [使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0801.%20%E4%BD%BF%E5%BA%8F%E5%88%97%E9%80%92%E5%A2%9E%E7%9A%84%E6%9C%80%E5%B0%8F%E4%BA%A4%E6%8D%A2%E6%AC%A1%E6%95%B0.md) | 数组、动态规划 | 困难 | -| 0871 | [最低加油次数](https://leetcode.cn/problems/minimum-number-of-refueling-stops/) | | 贪心、数组、动态规划、堆(优先队列) | 困难 | -| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 | -| 0813 | [最大平均值和的分组](https://leetcode.cn/problems/largest-sum-of-averages/) | | 数组、动态规划、前缀和 | 中等 | -| 0887 | [鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0887.%20%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD.md) | 数学、二分查找、动态规划 | 困难 | -| 0256 | [粉刷房子](https://leetcode.cn/problems/paint-house/) | | 数组、动态规划 | 中等 | -| 0265 | [粉刷房子 II](https://leetcode.cn/problems/paint-house-ii/) | | 数组、动态规划 | 困难 | -| 1473 | [粉刷房子 III](https://leetcode.cn/problems/paint-house-iii/) | | 数组、动态规划 | 困难 | -| 0975 | [奇偶跳](https://leetcode.cn/problems/odd-even-jump/) | | 栈、数组、动态规划、有序集合、单调栈 | 困难 | -| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 | -| 1478 | [安排邮筒](https://leetcode.cn/problems/allocate-mailboxes/) | | 数组、数学、动态规划、排序 | 困难 | -| 1230 | [抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | | 数学、动态规划、概率与统计 | 中等 | -| 0410 | [分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0410.%20%E5%88%86%E5%89%B2%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | -| 1751 | [最多可以参加的会议数目 II](https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/) | | 数组、二分查找、动态规划、排序 | 困难 | -| 1787 | [使所有区间的异或结果为零](https://leetcode.cn/problems/make-the-xor-of-all-segments-equal-to-zero/) | | 位运算、数组、动态规划 | 困难 | -| 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | -| 0122 | [买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0122.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II.md) | 贪心、数组 | 中等 | -| 0123 | [买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0123.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20III.md) | 数组、动态规划 | 困难 | -| 0188 | [买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0188.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20IV.md) | 数组、动态规划 | 困难 | -| 0309 | [最佳买卖股票时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0309.%20%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.md) | 数组、动态规划 | 中等 | -| 0714 | [买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0714.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%E5%90%AB%E6%89%8B%E7%BB%AD%E8%B4%B9.md) | 贪心、数组 | 中等 | - -#### 双串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0712 | [两个字符串的最小ASCII删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) | | 字符串、动态规划 | 中等 | -| 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | -| 0583 | [两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0583.%20%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.md) | 字符串、动态规划 | 中等 | -| 0072 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0072.%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.md) | 字符串、动态规划 | 困难 | -| 0044 | [通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0044.%20%E9%80%9A%E9%85%8D%E7%AC%A6%E5%8C%B9%E9%85%8D.md) | 贪心、递归、字符串、动态规划 | 困难 | -| 0010 | [正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0010.%20%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D.md) | 递归、字符串、动态规划 | 困难 | -| 0097 | [交错字符串](https://leetcode.cn/problems/interleaving-string/) | | 字符串、动态规划 | 中等 | -| 0115 | [不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 困难 | -| 0087 | [扰乱字符串](https://leetcode.cn/problems/scramble-string/) | | 字符串、动态规划 | 困难 | - -#### 矩阵线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0118 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0118.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92.md) | 数组、动态规划 | 简单 | -| 0119 | [杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0119.%20%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%20II.md) | 数组、动态规划 | 简单 | -| 0120 | [三角形最小路径和](https://leetcode.cn/problems/triangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0120.%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划 | 中等 | -| 0064 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0064.%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 数组、动态规划、矩阵 | 中等 | -| 0174 | [地下城游戏](https://leetcode.cn/problems/dungeon-game/) | | 数组、动态规划、矩阵 | 困难 | -| 0221 | [最大正方形](https://leetcode.cn/problems/maximal-square/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0221.%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2.md) | 数组、动态规划、矩阵 | 中等 | -| 0931 | [下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum/) | | 数组、动态规划、矩阵 | 中等 | -| 0576 | [出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0576.%20%E5%87%BA%E7%95%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0.md) | 动态规划 | 中等 | -| 0085 | [最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | | 栈、数组、动态规划、矩阵、单调栈 | 困难 | -| 0363 | [矩形区域不超过 K 的最大数值和](https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/) | | 数组、二分查找、矩阵、有序集合、前缀和 | 困难 | -| 面试题 17.24 | [最大子矩阵](https://leetcode.cn/problems/max-submatrix-lcci/) | | 数组、动态规划、矩阵、前缀和 | 困难 | -| 1444 | [切披萨的方案数](https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/) | | 记忆化搜索、数组、动态规划、矩阵 | 困难 | - -#### 无串线性 DP 问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0650 | [只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0650.%20%E5%8F%AA%E6%9C%89%E4%B8%A4%E4%B8%AA%E9%94%AE%E7%9A%84%E9%94%AE%E7%9B%98.md) | 数学、动态规划 | 中等 | -| 0264 | [丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0264.%20%E4%B8%91%E6%95%B0%20II.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0343 | [整数拆分](https://leetcode.cn/problems/integer-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md) | 数学、动态规划 | 中等 | - diff --git a/Contents/10.Dynamic-Programming/03.Linear-DP/index.md b/Contents/10.Dynamic-Programming/03.Linear-DP/index.md deleted file mode 100644 index d5a94e0a..00000000 --- a/Contents/10.Dynamic-Programming/03.Linear-DP/index.md +++ /dev/null @@ -1,5 +0,0 @@ -## 本章内容 - -- [线性 DP 知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) -- [线性 DP 知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) -- [线性 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md deleted file mode 100644 index dbba9dad..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md +++ /dev/null @@ -1,270 +0,0 @@ -## 1. 背包问题简介 - -### 1.1 背包问题的定义 - -> **背包问题**:背包问题是线性 DP 问题中一类经典而又特殊的模型。背包问题可以描述为:给定一组物品,每种物品都有自己的重量、价格以及数量。再给定一个最多能装重量为 $W$ 的背包。现在选择将一些物品放入背包中,请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值总和是多少? - -![](https://qcdn.itcharge.cn/images/202303191755045.png) - -根据物品限制条件的不同,背包问题可分为:0-1 背包问题、完全背包问题、多重背包问题、分组背包问题,以及混合背包问题等。 - -### 1.2 背包问题的暴力解题思路 - -背包问题的暴力解题思路比较简单。假设有 $n$ 件物品。我们先枚举出这 $n$ 件物品所有可能的组合。然后再判断这些组合中的物品是否能放入背包,以及是否能得到最大价值。这种做法的时间复杂度是 $O(2^n)$。 - -背包问题暴力解法的时间复杂度是指数级别的,我们可以利用动态规划算法减少一下时间复杂度。 - -下面我们来讲解一下如何使用动态规划方法解决各种类型的背包问题。 - -## 2. 0-1 背包问题 - -> **0-1 背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/202303191759967.png) - -### 2.1 0-1 背包问题基本思路 - -> **0-1 背包问题的特点**:每种物品有且仅有 $1$ 件,可以选择不放入背包,也可以选择放入背包。 - -#### 思路 1:动态规划 + 二维基本思路 - -###### 1. 划分阶段 - -按照物品的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 - -###### 3. 状态转移方程 - -对于「将前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值 」这个子问题,如果我们只考虑第 $i - 1$ 件物品(前 $i$ 件物品中最后一件物品)的放入策略(放入背包和不放入背包两种策略)。则问题可以转换为一个只跟前 $i - 1$ 件物品相关的问题。 - -1. **第 $i - 1$ 件物品不放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w$ 的背包中 ,可以获得的最大价值」为 $dp[i - 1][w]$。 -2. **第 $i - 1$ 件物品放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w - weight[i - 1]$ 的背包中,可以获得的最大价值」为 $dp[i - 1][w - weight[i - 1]]$,再加上「放入的第 $i - 1$ 件物品的价值」为 $value[i - 1]$,则此时可以获得的最大价值为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$。 - -接下来我们再来考虑一下第 $i - 1$ 件物品满足什么条件时才能考虑是否放入背包,并且在什么条件下一定不能放入背包。 - -1. 如果当前背包的载重不足时(即 $w < weight[i - 1]$):第 $i - 1$ 件物品一定不能放入背包,此时背包的价值 $dp[i][w]$ 仍为 $dp[i - 1][w]$ 时的价值,即 $dp[i][w] = dp[i - 1][w]$。 -2. 如果当前背包的载重足够时(即 $w \ge weight[i - 1]$):第 $i - 1$ 件物品可以考虑放入背包,或者不放入背包,此时背包的价值取两种情况下的最大值,即 $dp[i][w] = max \lbrace dp[i - 1][w], dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace$。 - -则状态转移方程为: - -$dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 -- 无论背包载重上限是多少,前 $0$ 件物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的件数,$W$ 为背包的载重上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 思路 1:动态规划 + 二维基本思路 - def zeroOnePackMethod1(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1]: - # dp[i][w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」 - dp[i][w] = dp[i - 1][w] - else: - # dp[i][w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i - 1]] + value[i - 1]) - - return dp[size][W] -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(n \times W)$。 - -### 2.2 0-1 背包问题滚动数组优化 - -根据之前的求解过程可以看出:当我们依次处理前 $1 \sim n$ 件物品时,「前 $i$ 件物品的处理结果」只跟「前 $i - 1$ 件物品的处理结果」,而跟之前更早的处理结果没有太大关系。 - -也就是说在状态转移的过程中,我们只用到了当前行(第 $i$ 行)的 $dp[i][w]$ 以及上一行(第 $i - 1$ 行)的 $dp[i - 1][w]$、$dp[i - 1][w - weight[i - 1]]$。 - -所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。即:用 $dp[0][w]$ 保存原先 $dp[i - 1][w]$ 的状态,用 $dp[1][w]$ 保存当前 $dp[i][w]$ 的状态。 - -其实我们还可以进一步进行优化,我们只需要使用一个一维数组 $dp[w]$ 保存上一阶段的所有状态,采用使用「滚动数组」的方式对空间进行优化(去掉动态规划状态的第一维)。 - -#### 思路 2:动态规划 + 滚动数组优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = \begin{cases} dp[w] & w < weight[i - 1] \cr max \lbrace dp[w], dp[w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -在第 $i$ 轮计算之前,$dp[w]$ 中保存的是「第 $i - 1$ 阶段的所有状态值」。在第 $i$ 轮计算之后,$d[w]$ 中保存的是「第 $i$ 阶段的所有状态值」。 - -为了保证第 $i$ 轮计算过程中,$dp[w]$ 是由第 $i - 1$ 轮中 $dp[w]$ 和 $dp[w - weight[i - 1]]$ 两个状态递推而来的值,我们需要按照「从 $W \sim 0$ 逆序的方式」倒推 $dp[w]$。 - -这是因为如果我们采用「从 $0 \sim W$ 正序递推的方式」递推 $dp[w]$,如果当前状态 $dp[w - weight[i]]$ 已经更新为当前第 $i$ 阶段的状态值。那么在向右遍历到 $dp[w]$ 时,我们需要的是第 $i - 1$ 阶段的状态值(即上一阶段的 $dp[w - weight[i - 1]]$),而此时 $dp[w - weight[i - 1]]$ 已经更新了,会破坏当前阶段的状态值,从而无法推出正确结果。 - -而如果按照「从 $W \sim 0$ 逆序的方式」倒推 $dp[w]$ 则不会出现该问题。 - -因为 $w < weight[i - 1]$ 时,$dp[w]$ 只能取上一阶段的 $dp[w]$,其值相当于没有变化,这部分可以不做处理。所以我们在逆序倒推 $dp[w]$ 时,只需遍历到 $weight[i - 1]$ 时即可。 - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) - - return dp[W] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(W)$。 - -### 2.3 0-1 背包问题的应用 - -#### 2.3.1 题目链接 - -- [416. 分割等和子集 - 力扣](https://leetcode.cn/problems/partition-equal-subset-sum/) - -#### 2.3.2 题目大意 - -**描述**:给定一个只包含正整数的非空数组 $nums$。 - -**要求**:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 - -**说明**: - -- $1 \le nums.length \le 200$。 -- $1 \le nums[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,5,11,5] -输出:true -解释:数组可以分割成 [1, 5, 5] 和 [11]。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,3,5] -输出:false -解释:数组不能分割成两个元素和相等的子集。 -``` - -#### 2.3.3 解题思路 - -##### 思路 1:动态规划 - -这道题换一种说法就是:从数组中选择一些元素组成一个子集,使子集的元素和恰好等于整个数组元素和的一半。 - -这样的话,这道题就可以转变为「0-1 背包问题」。 - -1. 把整个数组中的元素和记为 $sum$,把元素和的一半 $target = \frac{sum}{2}$ 看做是「0-1 背包问题」中的背包容量。 -2. 把数组中的元素 $nums[i]$ 看做是「0-1 背包问题」中的物品。 -3. 第 $i$ 件物品的重量为 $nums[i]$,价值也为 $nums[i]$。 -4. 因为物品的重量和价值相等,如果能装满载重上限为 $target$ 的背包,那么得到的最大价值也应该是 $target$。 - -这样问题就转变为:给定一个数组 $nums$ 代表物品,数组元素和的一半 $target = \frac{sum}{2}$ 代表背包的载重上限。其中第 $i$ 件物品的重量为 $nums[i]$,价值为 $nums[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,能否将背包装满从而得到最大价值? - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $w$ 的背包中,得到的元素和最大为多少。 - -###### 3. 状态转移方程 - -$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], \quad dp[w - nums[i - 1]] + nums[i - 1] \rbrace & w \ge nums[i - 1] \end{cases}$ - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[target]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $target = \frac{sum}{2}$ 的背包中,得到的元素和最大值。 - -所以最后判断一下 $dp[target]$ 是否等于 $target$。如果 $dp[target] == target$,则说明集合中的子集刚好能够凑成总和 $target$,此时返回 `True`;否则返回 `False`。 - -##### 思路 1:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) - - return dp[W] - - def canPartition(self, nums: List[int]) -> bool: - sum_nums = sum(nums) - if sum_nums & 1: - return False - - target = sum_nums // 2 - return self.zeroOnePackMethod2(nums, nums, target) == target -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 是整个数组元素和的一半。 -- **空间复杂度**:$O(target)$。 - -## 参考资料 - -- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) -- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) -- 【文章】[背包问题 第四讲 - 宫水三叶的刷题日记](https://juejin.cn/post/7003243733604892685) -- 【文章】[Massive Algorithms: 讲透完全背包算法](https://massivealgorithms.blogspot.com/2015/06/unbounded-knapsack-problem.html) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md deleted file mode 100644 index 625a0f97..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md +++ /dev/null @@ -1,245 +0,0 @@ -## 3. 完全背包问题 - -> **完全背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每种物品数量没有限制。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/202303191800014.png) - -### 3.1 完全背包问题基本思路 - -> **完全背包问题的特点**:每种物品有无限件。 - -我们可以参考「0-1 背包问题」的状态定义和基本思路,对于容量为 $w$ 的背包,最多可以装 $\frac{w}{weight[i - 1]}$ 件第 $i - 1$ 件物品。那么我们可以多加一层循环,枚举第 $i - 1$ 件物品可以选择的件数($0 \sim \frac{w}{weight[i - 1]}$),从而将「完全背包问题」转换为「0-1 背包问题」。 - -#### 思路 1:动态规划 + 二维基本思路 - -###### 1. 划分阶段 - -按照物品种类的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 - -###### 3. 状态转移方程 - -由于每种物品可选的数量没有限制,因此状态 $dp[i][w]$ 可能从以下方案中选择最大值: - -1. 选择 $0$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w]$ -2. 选择 $1$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$。 -3. 选择 $2$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - 2 \times weight[i - 1]] + 2 \times value[i - 1]$。 -4. …… -5. 选择 $k$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1]$。 - -> 注意:选择 $k$ 件第 $i - 1$ 件物品的条件是 $0 \le k \times weight[i - 1] \le w$。 - -则状态转移方程为: - -$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \times weight[i - 1] \le w$。 - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 -- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 思路 1:动态规划 + 二维基本思路 - def completePackMethod1(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 枚举第 i - 1 种物品能取个数 - for k in range(w // weight[i - 1] + 1): - # dp[i][w] 取所有 dp[i - 1][w - k * weight[i - 1] + k * value[i - 1] 中最大值 - dp[i][w] = max(dp[i][w], dp[i - 1][w - k * weight[i - 1]] + k * value[i - 1]) - - return dp[size][W] -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W \times \sum\frac{W}{weight[i]})$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$weight[i]$ 是第 $i$ 种物品的重量。 -- **空间复杂度**:$O(n \times W)$。 - -### 3.2 完全背包问题状态转移方程优化 - -上之前的思路中,对于每种物品而言,每次我们都需要枚举所有可行的物品数目 $k$,这就大大增加了时间复杂度。 - -实际上,我们可以对之前的状态转移方程进行一些优化,从而减少一下算法的时间复杂度。 - -我们将之前的状态转移方程 - -$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \times weight[i - 1] \le w$ - -进行展开: - -$(1) \quad dp[i][w] = max \begin{cases} dp[i - 1][w] \cr dp[i - 1][w - weight[i - 1]] + value[i - 1] \cr dp[i - 1][w - 2 \times weight[i - 1]] + 2 \times value[i - 1] \cr …… \cr \cr dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \end{cases}, \quad 0 \le k \times weight[i - 1] \le w$ - -而对于 $dp[i][w - weight[i - 1]]$ 我们有: - -$(2) \quad dp[i][w - weight[i - 1]] = max \begin{cases} dp[i - 1][w - weight[i - 1]] \cr dp[i - 1][w - 2 \times weight[i - 1]] + value[i - 1] \cr dp[i - 1][w - 3 \times weight[i - 1]] + 2 \times value[i - 1] \cr …… \cr dp[i - 1][w - k \times weight[i - 1]] + (k - 1) \times value[i - 1] \end{cases}, \quad weight[i - 1] \le k \times weight[i - 1] \le w$ - -通过观察可以发现: - -1. $(1)$ 式中共有 $k + 1$ 项,$(2)$ 式中共有 $k$ 项; -2. $(2)$ 式整个式子与 $(1)$ 式第 $1 \sim k + 1$ 项刚好相差一个 $value[i - 1]$。 - -则我们将 $(2)$ 式加上 $value[i - 1]$,再代入 $(1)$ 式中,可得到简化后的「状态转移方程」为: - -$(3) \quad dp[i][w] = max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace, \quad 0 \le weight[i - 1] \le w$。 - -简化后的「状态转移方程」去除了对物品件数的依赖,也就不需要遍历 $k$ 了,三层循环降为了两层循环。 - -> 注意:式 $(3)$ 的满足条件为 $0 \le weight[i - 1] \le w$。当 $w < weight[i - 1]$ 时,$dp[i][w] = dp[i - 1][w]$。 - -则状态转移方程为: - -$\quad dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -从上述状态转移方程我们可以看出:该式子与 0-1 背包问题中「思路 1」的状态转移式极其相似。 - -> 唯一区别点在于: -> -> 1. 0-1 背包问题中状态为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$,这是第 $i - 1$ 阶段上的状态值。 -> 2. 完全背包问题中状态为 $dp[i][w - weight[i - 1]] + value[i - 1]$,这是第 $i$ 阶段上的状态值。 - -#### 思路 2:动态规划 + 状态转移方程优化 - -###### 1. 划分阶段 - -按照物品种类的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 - -###### 3. 状态转移方程 - -$\quad dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 -- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 思路 2:动态规划 + 状态转移方程优化 - def completePackMethod2(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1]: - # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 - dp[i][w] = dp[i - 1][w] - else: - # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 - dp[i][w] = max(dp[i - 1][w], dp[i][w - weight[i - 1]] + value[i - 1]) - - return dp[size][W] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(n \times W)$。 - -### 3.3 完全背包问题滚动数组优化 - -通过观察「思路 2」中的状态转移方程 - -$dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -可以看出:我们只用到了当前行(第 $i$ 行)的 $dp[i][w]$、$dp[i][w - weight[i - 1]]$,以及上一行(第 $i - 1$ 行)的 $dp[i - 1][w]$。 - -所以我们没必要保存所有阶段的状态,只需要使用一个一维数组 $dp[w]$ 保存上一阶段的所有状态,采用使用「滚动数组」的方式对空间进行优化(去掉动态规划状态的第一维)。 - -#### 思路 3:动态规划 + 滚动数组优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = \begin{cases} dp[w] & w < weight[i - 1] \cr max \lbrace dp[w], \quad dp[w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ - -> 注意:这里的 $dp[w - weight[i - 1]]$ 是第 $i$ 轮计算之后的「第 $i$ 阶段的状态值」。 - -因为在计算 $dp[w]$ 时,我们需要用到第 $i$ 轮计算之后的 $dp[w - weight[i - 1]]$,所以我们需要按照「从 $0 \sim W$ 正序递推的方式」递推 $dp[w]$,这样才能得到正确的结果。 - -因为 $w < weight[i - 1]$ 时,$dp[w]$ 只能取上一阶段的 $dp[w]$,其值相当于没有变化,这部分可以不做处理。所以我们在正序递推 $dp[w]$ 时,只需从 $weight[i - 1]$ 开始遍历即可。 - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 3:代码 - -```python -class Solution: - # 思路 3:动态规划 + 滚动数组优化 - def completePackMethod3(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 正序枚举背包装载重量 - for w in range(weight[i - 1], W + 1): - # dp[w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) - - return dp[W] -``` - -> 通过观察「0-1 背包问题滚动数组优化的代码」和「完全背包问题滚动数组优化的代码」可以看出,两者的唯一区别在于: -> -> 1. 0-1 背包问题滚动数组优化的代码采用了「从 $W \sim weight[i - 1]$ 逆序递推的方式」。 -> 2. 完全背包问题滚动数组优化的代码采用了「从 $weight[i - 1] \sim W$ 正序递推的方式」。 - -#### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(W)$。 - -## 参考资料 - -- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) -- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) -- 【文章】[背包问题 第四讲 - 宫水三叶的刷题日记](https://juejin.cn/post/7003243733604892685) -- 【题解】[『 套用完全背包模板 』详解完全背包(含数学推导) - 完全平方数 - 力扣](https://leetcode.cn/problems/perfect-squares/solution/by-flix-sve5/) -- 【题解】[『 一文搞懂完全背包问题 』从0-1背包到完全背包,逐层深入+推导 - 零钱兑换 - 力扣](https://leetcode.cn/problems/coin-change/solution/by-flix-su7s/) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md deleted file mode 100644 index 879ea89e..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md +++ /dev/null @@ -1,211 +0,0 @@ -## 4. 多重背包问题 - -> **多重背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,件数为 $count[i]$。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/202303191809178.png) - -### 4.1 多重背包问题基本思路 - -我们可以参考「0-1 背包问题」的状态定义和基本思路,对于容量为 $w$ 的背包,最多可以装 $min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$ 件第 $i - 1$ 件物品。那么我们可以多加一层循环,枚举第 $i - 1$ 件物品可以选择的件数($0 \sim min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$),从而将「完全背包问题」转换为「0-1 背包问题」。 - -#### 思路 1:动态规划 + 二维基本思路 - -###### 1. 划分阶段 - -按照物品种类的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 - -###### 3. 状态转移方程 - -$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \le min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$。 - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 -- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 思路 1:动态规划 + 二维基本思路 - def multiplePackMethod1(self, weight: [int], value: [int], count: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 枚举第 i - 1 种物品能取个数 - for k in range(min(count[i - 1], w // weight[i - 1]) + 1): - # dp[i][w] 取所有 dp[i - 1][w - k * weight[i - 1] + k * value[i - 1] 中最大值 - dp[i][w] = max(dp[i][w], dp[i - 1][w - k * weight[i - 1]] + k * value[i - 1]) - - return dp[size][W] -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$C$ 是物品的数量数组长度。因为 $n \times C = \sum count[i]$,所以时间复杂度也可以写成 $O(W \times \sum count[i])$。 -- **空间复杂度**:$O(n \times W)$。 - -### 4.2 多重背包问题滚动数组优化 - -在「完全背包问题」中,我们通过优化「状态转移方程」的方式,成功去除了对物品件数 $k$ 的依赖,从而将时间复杂度下降了一个维度。 - -而在「多重背包问题」中,我们在递推 $dp[i][w]$ 时,是无法从 $dp[i][w - weight[i - 1]]$ 状态得知目前究竟已经使用了多个件第 $i - 1$ 种物品,也就无法判断第 $i - 1$ 种物品是否还有剩余数量可选。这就导致了我们无法通过优化「状态转移方程」的方式将「多重背包问题」的时间复杂度降低。 - -但是我们可以参考「完全背包问题」+「滚动数组优化」的方式,将算法的空间复杂度下降一个维度。 - -#### 思路 2:动态规划 + 滚动数组优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = max \lbrace dp[w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \le min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$ - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def multiplePackMethod2(self, weight: [int], value: [int], count: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight[i - 1] - 1, -1): - # 枚举第 i - 1 种物品能取个数 - for k in range(min(count[i - 1], w // weight[i - 1]) + 1): - # dp[w] 取所有 dp[w - k * weight[i - 1]] + k * value[i - 1] 中最大值 - dp[w] = max(dp[w], dp[w - k * weight[i - 1]] + k * value[i - 1]) - - return dp[W] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$C$ 是物品的数量数组长度。因为 $n \times C = \sum count[i]$,所以时间复杂度也可以写成 $O(W \times \sum count[i])$。 -- **空间复杂度**:$O(W)$。 - -### 4.3 多重背包问题二进制优化 - -在「思路 2」中,我们通过「滚动数组优化」的方式,降低了算法的空间复杂度。同时也提到了无法通过优化「状态转移方程」的方式将「多重背包问题」的时间复杂度降低。 - -但我们还是可以从物品数量入手,通过「二进制优化」的方式,将算法的时间复杂度降低。 - -> **二进制优化**:简单来说,就是把物品的数量 $count[i]$ 拆分成「由 $1, 2, 4, …, 2^m$ 件单个物品组成的大物品」,以及「剩余不足 $2$ 的整数次幂数量的物品,由 $count[i] -2^{\lfloor \log_2(count[i] + 1) \rfloor - 1}$ 件单个物品组成大物品」。 - -举个例子,第 $i$ 件物品的数量为 $31$,采用「二进制优化」的方式,可以拆分成 $31 = 1 + 2 + 4 + 8 + 16$ 一共 $5$ 件物品。也将是将 $31$ 件物品分成了 $5$ 件大物品: - -1. 第 $1$ 件大物品有 $1$ 件第 $i$ 种物品组成; -2. 第 $2$ 件大物品有 $2$ 件第 $i$ 种物品组成; -3. 第 $3$ 件大物品有 $4$ 件第 $i$ 种物品组成; -4. 第 $4$ 件大物品有 $8$ 件第 $i$ 种物品组成; -5. 第 $5$ 件大物品有 $16$ 件第 $i$ 种物品组成。 - -这 $5$ 件大物品通过不同的组合,可表达出第 $i$ 种物品的数量范围刚好是 $0 \sim 31$。 - -这样本来第 $i$ 件物品数量需要枚举共计 $32$ 次($0 \sim 31$),而现在只需要枚举 $5$ 次即可。 - -再举几个例子: - -1. 第 $i$ 件物品的数量为 $6$,可以拆分为 $6 = 1 + 2 + 3$ 一共 $3$ 件物品。 -2. 第 $i$ 件物品的数量为 $8$,可以拆分为 $8 = 1 + 2 + 4 + 1$ 一共 $4$ 件物品。 -3. 第 $i$ 件物品的数量为 $18$,可以拆分为 $18 = 1 + 2 + 4 + 8 + 3$ 一共 $5$ 件物品。 - -经过「二进制优化」之后,算法的时间复杂度从 $O(W \times \sum count[i])$ 降到了 $O(W \times \sum \log_2{count[i]})$。 - -#### 思路 3:动态规划 + 二进制优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = max \lbrace dp[w - weight \underline{ } new[i - 1]] + value \underline{ } new[i - 1] \rbrace$ - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 3:代码 - -```python -class Solution: - # 思路 3:动态规划 + 二进制优化 - def multiplePackMethod3(self, weight: [int], value: [int], count: [int], W: int): - weight_new, value_new = [], [] - - # 二进制优化 - for i in range(len(weight)): - cnt = count[i] - k = 1 - while k <= cnt: - cnt -= k - weight_new.append(weight[i] * k) - value_new.append(value[i] * k) - k *= 2 - if cnt > 0: - weight_new.append(weight[i] * cnt) - value_new.append(value[i] * cnt) - - dp = [0 for _ in range(W + 1)] - size = len(weight_new) - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight_new[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight_new[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) - - return dp[W] -``` - -#### 思路 3:复杂度分析 - -- **时间复杂度**:$O(W \times \sum \log_2{count[i]})$,其中 $W$ 为背包的载重上限,$count[i]$ 是第 $i$ 种物品的数量。 -- **空间复杂度**:$O(W)$。 - -## 参考资料 - -- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) -- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) -- 【文章】[【动态规划/背包问题】多重背包の二进制优化](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486796&idx=1&sn=a382b38f8aed295410550bb1767437bd&chksm=fd9ca653caeb2f456262bbf70ffe1eeda8758b426a901a6ac15be184e7017870020e456c6fa2&scene=178&cur_album_id=1869157771795841024#rd) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md deleted file mode 100644 index 1647d715..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md +++ /dev/null @@ -1,332 +0,0 @@ - - -## 5. 混合背包问题 - -> **混合背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,件数为 $count[i]$。其中: -> -> 1. 当 $count[i] = -1$ 时,代表该物品只有 $1$ 件。 -> 2. 当 $count[i] = 0$ 时,代表该物品有无限件。 -> 3. 当 $count[i] > 0$ 时,代表该物品有 $count[i]$ 件。 -> -> 请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/20230329095653.png) - -#### 思路 1:动态规划 - -混合背包问题其实就是将「0-1 背包问题」、「完全背包问题」和「多重背包问题」这 $3$ 种背包问题综合起来,有的是能取 $1$ 件,有的能取无数件,有的只能取 $count[i]$ 件。 - -其实只要理解了之前讲解的这 $3$ 种背包问题的核心思想,只要将其合并在一起就可以了。 - -并且在「多重背包问题」中,我们曾经使用「二进制优化」的方式,将「多重背包问题」转换为「0-1 背包问题」,那么在解决「混合背包问题」时,我们也可以先将「多重背包问题」转换为「0-1 背包问题」,然后直接再区分是「0-1 背包问题」还是「完全背包问题」就可以了。 - -#### 思路 1:代码 - -```python -class Solution: - def mixedPackMethod1(self, weight: [int], value: [int], count: [int], W: int): - weight_new, value_new, count_new = [], [], [] - - # 二进制优化 - for i in range(len(weight)): - cnt = count[i] - # 多重背包问题,转为 0-1 背包问题 - if cnt > 0: - k = 1 - while k <= cnt: - cnt -= k - weight_new.append(weight[i] * k) - value_new.append(value[i] * k) - count_new.append(1) - k *= 2 - if cnt > 0: - weight_new.append(weight[i] * cnt) - value_new.append(value[i] * cnt) - count_new.append(1) - # 0-1 背包问题,直接添加 - elif cnt == -1: - weight_new.append(weight[i]) - value_new.append(value[i]) - count_new.append(1) - # 完全背包问题,标记并添加 - else: - weight_new.append(weight[i]) - value_new.append(value[i]) - count_new.append(0) - - dp = [0 for _ in range(W + 1)] - size = len(weight_new) - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 0-1 背包问题 - if count_new[i - 1] == 1: - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight_new[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight_new[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) - # 完全背包问题 - else: - # 正序枚举背包装载重量 - for w in range(weight_new[i - 1], W + 1): - # dp[w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) - - return dp[W] -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(W \times \sum \log_2{count[i]})$,其中 $W$ 为背包的载重上限,$count[i]$ 是第 $i$ 种物品的数量。 -- **空间复杂度**:$O(W)$。 - -## 6. 分组背包问题 - -> **分组背包问题**:有 $n$ 组物品和一个最多能装重量为 $W$ 的背包,第 $i$ 组物品的件数为 $group\underline{}count[i]$,第 $i$ 组的第 $j$ 个物品重量为 $weight[i][j]$,价值为 $value[i][j]$。每组物品中最多只能选择 $1$ 件物品装入背包。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/20230329095729.png) - -### 6.1 分组背包问题基本思路 - -#### 思路 1:动态规划 + 二维基本思路 - -###### 1. 划分阶段 - -按照物品种类的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品组数」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 - -###### 3. 状态转移方程 - -由于我们可以不选择 $i - 1$ 组物品中的任何物品,也可以从第 $i - 1$ 组物品的第 $0 \sim group\underline{}count[i - 1] - 1$ 件物品中随意选择 $1$ 件物品,所以状态 $dp[i][w]$ 可能从以下方案中选择最大值: - -1. 不选择第 $i - 1$ 组中的任何物品:可以获得的最大价值为 $dp[i - 1][w]$。 -2. 选择第 $i - 1$ 组物品中第 $0$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][0]] + value[i - 1][0]$。 -3. 选择第 $i - 1$ 组物品中第 $1$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][1]] + value[i - 1][1]$。 -4. …… -5. 选择第 $i - 1$ 组物品中最后 $1$ 件:假设 $k = group\underline{}count[i - 1] - 1$,则可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]$。 - -则状态转移方程为: - -$dp[i][w] = max \lbrace dp[i - 1][w], dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] \rbrace , \quad 0 \le k \le group\underline{}count[i - 1]$ - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 -- 无论背包载重上限是多少,前 $0$ 组物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 思路 1:动态规划 + 二维基本思路 - def groupPackMethod1(self, group_count: [int], weight: [[int]], value: [[int]], W: int): - size = len(group_count) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 组物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 枚举第 i - 1 组物品能取个数 - dp[i][w] = dp[i - 1][w] - for k in range(group_count[i - 1]): - if w >= weight[i - 1][k]: - # dp[i][w] 取所有 dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] 中最大值 - dp[i][w] = max(dp[i][w], dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]) -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{}count[i]$,所以时间复杂度也可以写成 $O(W \times \sum group\underline{}count[i])$。 -- **空间复杂度**:$O(n \times W)$。 - -### 6.2 分组背包问题滚动数组优化 - -#### 思路 2:动态规划 + 滚动数组优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = max \lbrace dp[w], \quad dp[w - weight[i - 1][k]] + value[i - 1][k] \rbrace , \quad 0 \le k \le group\underline{}count[i - 1]$ - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def groupPackMethod2(self, group_count: [int], weight: [[int]], value: [[int]], W: int): - size = len(group_count) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 组物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量 - for w in range(W, -1, -1): - # 枚举第 i - 1 组物品能取个数 - for k in range(group_count[i - 1]): - if w >= weight[i - 1][k]: - # dp[w] 取所有 dp[w - weight[i - 1][k]] + value[i - 1][k] 中最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1][k]] + value[i - 1][k]) - - return dp[W] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{}count[i]$,所以时间复杂度也可以写成 $O(W \times \sum group\underline{}count[i])$。 -- **空间复杂度**:$O(W)$。 - -## 7. 二维费用背包问题 - -> **二维费用背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$、容量为 $V$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,体积为 $volume[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限、容量上限的情况下,能装入背包的最大价值是多少? - -![](https://qcdn.itcharge.cn/images/20230329095857.png) - -### 7.1 二维费用背包问题基本思路 - -我们可以参考「0-1 背包问题」的状态定义和基本思路,在「0-1 背包问题」基本思路的基础上,增加一个维度用于表示物品的容量。 - -#### 思路 1:动态规划 + 三维基本思路 - -###### 1. 划分阶段 - -按照物品种类的序号、当前背包的载重上限、容量上限进行阶段划分 - -###### 2. 定义状态 - -定义状态 $dp[i][w][v]$ 为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]), \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ - -> 注意:采用这种「状态定义」和「状态转移方程」,往往会导致内存超出要求限制,所以一般我们会采用「滚动数组」对算法的空间复杂度进行优化。 - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$ 或者容量上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即: - - $dp[i][w][0] = 0, 0 \le i \le size, 0 \le w \le W$ - - $dp[i][0][v] = 0, 0 \le i \le size, 0 \le v \le V$ - -- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即: - - $dp[0][w][v] = 0, 0 \le w \le W, 0 \le v \le V$ - - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[i][w][v]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W][V]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 思路 1:动态规划 + 三维基本思路 - def twoDCostPackMethod1(self, weight: [int], volume: [int], value: [int], W: int, V: int): - size = len(weight) - dp = [[[0 for _ in range(V + 1)] for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 组物品 - for i in range(1, N + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 枚举背包装载容量 - for v in range(V + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1] or v < volume[i - 1]: - # dp[i][w][v] 取「前 i - 1 件物品装入装载重量为 w、装载容量为 v 的背包中的最大价值」 - dp[i][w][v] = dp[i - 1][w][v] - else: - # dp[i][w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 - dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) - - return dp[size][W][V] -``` - -#### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W \times V)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 -- **空间复杂度**:$O(n \times W \times V)$。 - -### 7.2 二维费用背包问题滚动数组优化 - -#### 思路 2:动态规划 + 滚动数组优化 - -###### 1. 划分阶段 - -按照当前背包的载重上限、容量上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w][v] = max \lbrace dp[w][v], \quad dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] \rbrace , \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ - -###### 4. 初始条件 - -- 如果背包载重上限为 $0$ 或者容量上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即: - - $dp[w][0] = 0, 0 \le w \le W$ - - $dp[0][v] = 0, 0 \le v \le V$ - - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W][V]$,其中 $W$ 为背包的载重上限,$V$ 为背包的容量上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def twoDCostPackMethod2(self, weight: [int], volume: [int], value: [int], W: int, V: int): - size = len(weight) - dp = [[0 for _ in range(V + 1)] for _ in range(W + 1)] - - # 枚举前 i 组物品 - for i in range(1, N + 1): - # 逆序枚举背包装载重量 - for w in range(W, weight[i - 1] - 1, -1): - # 逆序枚举背包装载容量 - for v in range(V, volume[i - 1] - 1, -1): - # dp[w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 - dp[w][v] = max(dp[w][v], dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) - - return dp[W][V] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W \times V)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 -- **空间复杂度**:$O(W \times V)$。 - -## 参考资料 - -- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) -- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) -- 【文章】[【动态规划/背包问题】分组背包问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487504&idx=1&sn=9ac523ec0ac14c8634a229f8c3f919d7&chksm=fd9cbb0fcaeb32196b80a40e4408f6a7e2651167e0b9e31aa6d7c6109fbc2117340a59db12a1&token=1936267333&lang=zh_CN&scene=21#wechat_redirect) -- 【文章】[【动态规划/背包问题】背包问题第一阶段最终章:混合背包问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487034&idx=1&sn=eaa05b76387d34aa77f7f14f35fa78a4&chksm=fd9ca525caeb2c33095d285222dcee0dd072465bf7288bda0aab39e90a04bb7b1af018b89fd4&token=1872331648&lang=zh_CN&scene=21#wechat_redirect) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md deleted file mode 100644 index e04dea6a..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md +++ /dev/null @@ -1,327 +0,0 @@ -## 8. 背包问题变种 - -### 8.1 求恰好装满背包的最大价值 - -> **背包问题求恰好装满背包的最大价值**:在给定背包重量 $W$,每件物品重量 $weight[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在恰好装满背包的情况下,能装入背包的最大价值总和是多少? - -在背包问题中,有的题目不要求把背包装满,而有的题目要求恰好装满背包。 - -如果题目要求「恰好装满背包」,则我们可在原有状态定义、状态转移方程的基础上,在初始化时,令 $dp[0] = 0$,以及 $d[w] = -\infty, 1 \le w \le W$。 这样就可以保证最终得到的 $dp[W]$ 为恰好装满背包的最大价值总和。 - -这是因为:初始化的 $dp$ 数组实际上就是在没有任何物品可以放入背包时的「合法状态」。 - -如果不要求恰好装满背包,那么: - -1. 任何载重上限下的背包,在不放入任何物品时,都有一个合法解,此时背包所含物品的最大价值为 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -而如果要求恰好装满背包,那么: - -1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为 $0$,即 $dp[0] = 0$。 -2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值应为 $-\infty$,即 $dp[w] = 0, 0 \le w \le W$。 - -这样在进行状态转移时,我们可以通过判断 $dp[w]$ 与 $-\infty$ 的关系,来判断是否能恰好装满背包。 - -下面我们以「0-1 背包问题」求恰好装满背包的最大价值为例。 - -> **0-1 背包问题求恰好装满背包的最大价值**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在恰好装满背包的情况下,能装入背包的最大价值总和是多少? - -#### 思路 1:动态规划 + 一维状态 - -1. **划分阶段**:按照当前背包的载重上限进行阶段划分。 -2. **定义状态**:定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 -3. **状态转移方程**:$dp[w] = dp[w] + dp[w - weight[i - 1]]$ -4. **初始条件**: - 1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为 $0$,即 $dp[0] = 0$。 - 2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值应为 $-\infty$,即 $dp[w] = 0, 0 \le w \le W$。 -5. **最终结果**:根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 1:代码 - -```python -class Solution: - # 0-1 背包问题 求恰好装满背包的最大价值 - def zeroOnePackJustFillUp(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [float('-inf') for _ in range(W + 1)] - dp[0] = 0 - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) - - if dp[W] == float('-inf'): - return -1 - return dp[W] -``` - -#### 思路 1:算法复杂度 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(W)$。 - -### 8.2 求方案总数 - -> **背包问题求方案数**:在给定背包重量 $W$,每件物品重量 $weight[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在总重量不超过背包载重上限的情况下,或者在总重量不超过某一指定重量的情况下,一共有多少种方案? - -这种问题就是将原有状态转移方程中的「求最大值」变为「求和」即可。 - -下面我们以「0-1 背包问题」求方案总数为例。 - -> **0-1 背包问题求方案数**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。 -> -> 请问在总重量不超过背包载重上限的情况下,一共有多少种方案? - -- 如果使用二维状态定义,可定义状态 $dp[i][w]$ 为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中的方案总数。则状态转移方程为:$dp[i][w] = dp[i - 1][w] + dp[i][w - weight[i - 1]]$。 -- 如果使用一维状态定义,可定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中的方案总数。则状态转移方程为:$dp[w] = dp[w] + dp[w - weight[i - 1]]$。 - -下面我们使用一维状态定义方式解决「0-1 背包问题求解方案数」问题。 - -#### 思路 2:动态规划 + 一维状态 - -1. **划分阶段**:按照物品种类的序号、当前背包的载重上限进行阶段划分。 -2. **定义状态**:定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中的方案总数。 -3. **状态转移方程**:$dp[w] = dp[w] + dp[w - weight[i - 1]]$ -4. **初始条件**:如果背包载重上限为 $0$,则一共有 $1$ 种方案(什么也不装),即 $dp[0] = 1$。 -5. **最终结果**:根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 - -#### 思路 2:代码 - -```python -class Solution: - # 0-1 背包问题求方案总数 - def zeroOnePackNumbers(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - dp[0] = 1 - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量 - for w in range(W, weight[i - 1] - 1, -1): - # dp[w] = 前 i - 1 件物品装入载重为 w 的背包中的方案数 + 前 i 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 件物品的方案数 - dp[w] = dp[w] + dp[w - weight[i - 1]] - - return dp[W] -``` - -#### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(W)$。 - -### 8.3 求最优方案数 - -> **背包问题求最优方案数**:在给定背包重量 $W$,每件物品重量 $weight[i]$、物品价值 $value[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在总重量不超过背包载重上限的情况下,使背包总价值最大的方案数是多少? - -通过结合「求背包最大可得价值」和「求方案数」两个问题的思路,我们可以分别定义两个状态: - -1. 定义 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可获得的最大价值。 -2. 定义 $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。 - -下面我们以「0-1 背包问题」求最优方案数为例。 - -> **0-1 背包问题求最优方案数**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,使背包总价值最大的方案数是多少? - -#### 思路 3:动态规划 - -1. **划分阶段**:按照物品种类的序号、当前背包的载重上限进行阶段划分。 -2. **定义状态**: - 1. 定义 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可获得的最大价值。 - 2. 定义 $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。 -3. **状态转移方程**: - 1. 如果 $dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明选择第 $i - 1$ 件物品获得价值更高,此时方案数 $op[i][w]$ 是在 $op[i - 1][w - weight[i - 1]]$ 基础上添加了第 $i - 1$ 件物品,因此方案数不变,即:$op[i][w] = op[i - 1][w - weight[i - 1]]$。 - 2. 如果 $dp[i - 1][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明选择与不选择第 $i - 1$ 件物品获得价格相等,此时方案数应为两者之和,即:$op[i][w] = op[i - 1][w] + op[i - 1][w - weight[i - 1]]$。 - 3. 如果 $dp[i - 1][w] > dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明不选择第 $i - 1$ 件物品获得价值更高,此时方案数等于之前方案数,即:$op[i][w] = op[i - 1][w]$。 -4. **初始条件**:如果背包载重上限为 $0$,则一共有 $1$ 种方案(什么也不装),即 $dp[0] = 1$。 -5. **最终结果**:根据我们之前定义的状态, $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。则最终结果为 $op[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 - -#### 思路 3:代码 - -```python -class Solution: - # 0-1 背包问题求最优方案数 思路 1 - def zeroOnePackMaxProfitNumbers1(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - op = [[1 for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1]: - # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 - dp[i][w] = dp[i - 1][w] - op[i][w] = op[i - 1][w] - else: - # 选择第 i - 1 件物品获得价值更高 - if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] - # 在之前方案基础上添加了第 i - 1 件物品,因此方案数量不变 - op[i][w] = op[i - 1][w - weight[i - 1]] - # 两种方式获得价格相等 - elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w] - # 方案数 = 不使用第 i - 1 件物品的方案数 + 使用第 i - 1 件物品的方案数 - op[i][w] = op[i - 1][w] + op[i - 1][w - weight[i - 1]] - # 不选择第 i - 1 件物品获得价值最高 - else: - dp[i][w] = dp[i - 1][w] - # 不选择第 i - 1 件物品,与之前方案数相等 - op[i][w] = op[i - 1][w] - - return op[size][W] -``` - -#### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(n \times W)$。 - -### 8.4 求具体方案 - -> **背包问题求具体方案**:在给定背包重量 $W$,每件物品重量 $weight[i]$、物品价值 $value[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问将哪些物品装入背包,可使这些物品的总重量不超过背包载重上限,且价值总和最大? - -一般背包问题都是求解一个最优值,但是如果要输出该最优值的具体方案,除了 $dp[i][w]$,我们可以再定义一个数组 $path[i][w]$ 用于记录状态转移时,所取的状态是状态转移方程中的哪一项,从而确定选择的具体物品。 - -下面我们以「0-1 背包问题」求具体方案为例。 - -> **0-1 背包问题求具体方案**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问将哪些物品装入背包,可使这些物品的总重量不超过背包载重上限,且价值总和最大? - -#### 4:动态规划 + 路径记录 - -0-1 背包问题的状态转移方程为:$dp[i][w] = max \lbrace dp[i - 1][w], \quad dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace$ - -则我们可以再定义一个 $path[i][w]$ 用于记录状态转移时,所取的状态是状态转移方程中的哪一项。 - -1. 如果 $path[i][w] = False$,说明:转移到 $dp[i][w]$ 时,选择了前一项 $dp[i - 1][w]$,并且具体方案中不包括第 $i - 1$ 件物品。 -2. 如果 $paht[i][w] = True$,说明:转移到 $dp[i][w]$ 时,选择了后一项 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$,并且具体方案中包括第 $i - 1$ 件物品。 - -#### 思路 4:代码 - -```python -class Solution: - # 0-1 背包问题求具体方案 - def zeroOnePackPrintPath(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - path = [[False for _ in range(W + 1)] for _ in range(size + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1]: - # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 - dp[i][w] = dp[i - 1][w] - path[i][w] = False - else: - # 选择第 i - 1 件物品获得价值更高 - if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] - # 取状态转移式第二项:在之前方案基础上添加了第 i - 1 件物品 - path[i][w] = True - # 两种方式获得价格相等 - elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w] - # 取状态转移式第二项:尽量使用第 i - 1 件物品 - path[i][w] = True - # 不选择第 i - 1 件物品获得价值最高 - else: - dp[i][w] = dp[i - 1][w] - # 取状态转移式第一项:不选择第 i - 1 件物品 - path[i][w] = False - - res = [] - i, w = size, W - while i >= 1 and w >= 0: - if path[i][w]: - res.append(str(i - 1)) - w -= weight[i - 1] - i -= 1 - - return " ".join(res[::-1]) -``` - -#### 思路 4:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(n \times W)$。 - -### 8.5 求字典序最小的具体方案 - -这里的「字典序最小」指的是序号为 $0 \sim size - 1$ 的物品选择方案排列出来之后的字典序最小。 - -我们仍以「0-1 背包问题」求字典序最小的具体方案为例。 - -为了使「字典序最小」。我们可以先将物品的序号进行反转,从 $0 \sim size - 1$ 变为 $size - 1 \sim 0$,然后在返回具体方案时,再根据 $i = size - 1$ 将序号变回来。 - -这是为了在选择物品时,尽可能的向后选择反转后序号大的物品(即原序号小的物品),从而保证原序号为 $0 \sim size - 1$ 的物品选择方案排列出来之后的字典序最小。 - -#### 思路 5:代码 - -```python -class Solution: - # 0-1 背包问题求具体方案,要求最小序输出 - def zeroOnePackPrintPathMinOrder(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] - path = [[False for _ in range(W + 1)] for _ in range(size + 1)] - - weight.reverse() - value.reverse() - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(W + 1): - # 第 i - 1 件物品装不下 - if w < weight[i - 1]: - # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 - dp[i][w] = dp[i - 1][w] - path[i][w] = False - else: - # 选择第 i - 1 件物品获得价值更高 - if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] - # 取状态转移式第二项:在之前方案基础上添加了第 i - 1 件物品 - path[i][w] = True - # 两种方式获得价格相等 - elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: - dp[i][w] = dp[i - 1][w] - # 取状态转移式第二项:尽量使用第 i - 1 件物品 - path[i][w] = True - # 不选择第 i - 1 件物品获得价值最高 - else: - dp[i][w] = dp[i - 1][w] - # 取状态转移式第一项:不选择第 i - 1 件物品 - path[i][w] = False - - res = [] - i, w = size, W - while i >= 1 and w >= 0: - if path[i][w]: - res.append(str(size - i)) - w -= weight[i - 1] - i -= 1 - - return " ".join(res) -``` - -#### 思路 5:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 -- **空间复杂度**:$O(n \times W)$。 - -## 参考资料 - -- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) -- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) -- 【文章】[背包问题——“01背包”最优方案总数分析及实现 - wumuzi 的博客](https://blog.csdn.net/wumuzi520/article/details/7019131) -- 【文章】[背包问题——“完全背包”最优方案总数分析及实现 - wumuzi的博客](https://blog.csdn.net/wumuzi520/article/details/7019661) -- 【文章】[背包问题——“01背包”及“完全背包”装满背包的方案总数分析及实现 - wumuzi的博客](https://blog.csdn.net/wumuzi520/article/details/7021210) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md deleted file mode 100644 index e2225bdc..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md +++ /dev/null @@ -1,39 +0,0 @@ -### 背包问题题目 - -#### 0-1 背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0416 | [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0416.%20%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.md) | 数组、动态规划 | 中等 | -| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 数组、动态规划、回溯 | 中等 | -| 1049 | [最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1049.%20%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F%20II.md) | 数组、动态规划 | 中等 | - -#### 完全背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | -| 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 广度优先搜索、数组、动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | -| 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | -| 0377 | [组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md) | 数组、动态规划 | 中等 | -| 0638 | [大礼包](https://leetcode.cn/problems/shopping-offers/) | | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | - -#### 多重背包问题 - -#### 分组背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1155 | [掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1155.%20%E6%8E%B7%E9%AA%B0%E5%AD%90%E7%AD%89%E4%BA%8E%E7%9B%AE%E6%A0%87%E5%92%8C%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 动态规划 | 中等 | -| 2585 | [获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2585.%20%E8%8E%B7%E5%BE%97%E5%88%86%E6%95%B0%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0.md) | 数组、动态规划 | 困难 | - -#### 多维背包问题 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0474 | [一和零](https://leetcode.cn/problems/ones-and-zeroes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0474.%20%E4%B8%80%E5%92%8C%E9%9B%B6.md) | 数组、字符串、动态规划 | 中等 | -| 0879 | [盈利计划](https://leetcode.cn/problems/profitable-schemes/) | | 数组、动态规划 | 困难 | -| 1995 | [统计特殊四元组](https://leetcode.cn/problems/count-special-quadruplets/) | | 数组、枚举 | 简单 | - diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/index.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/index.md deleted file mode 100644 index f9e0e6ae..00000000 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/index.md +++ /dev/null @@ -1,8 +0,0 @@ -## 本章内容 - -- [背包问题知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) -- [背包问题知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) -- [背包问题知识(三)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) -- [背包问题知识(四)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) -- [背包问题知识(五)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) -- [背包问题题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md b/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md deleted file mode 100644 index 2cf479c7..00000000 --- a/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md +++ /dev/null @@ -1,380 +0,0 @@ -## 1. 区间动态规划简介 - -### 1.1 区间动态规划定义 - -> **区间动态规划**:线性 DP 的一种,简称为「区间 DP」。以「区间长度」划分阶段,以两个坐标(区间的左、右端点)作为状态的维度。一个状态通常由被它包含且比它更小的区间状态转移而来。 - -区间 DP 的主要思想就是:先在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。 - -根据小区间向大区间转移情况的不同,常见的区间 DP 问题可以分为两种: - -1. 单个区间从中间向两侧更大区间转移的区间 DP 问题。比如从区间 $[i + 1, j - 1]$ 转移到更大区间 $[i, j]$。 -2. 多个(大于等于 $2$ 个)小区间转移到大区间的区间 DP 问题。比如从区间 $[i, k]$ 和区间 $[k, j]$ 转移到区间 $[i, j]$。 - -下面我们讲解一下这两种区间 DP 问题的基本解题思路。 - -### 1.2 区间 DP 问题的基本思路 - -#### 1.2.1 第 1 种区间 DP 问题基本思路 - -从中间向两侧转移的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max \lbrace dp[i + 1][j - 1], \quad dp[i + 1][j], \quad dp[i][j - 1] \rbrace + cost[i][j], \quad i \le j$。 - -1. 其中 $dp[i][j]$ 表示为:区间 $[i, j]$(即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 -2. $cost$ 表示为:从小区间转移到区间 $[i, j]$ 的代价。 -3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 - -从中间向两侧转移的区间 DP 问题的基本解题思路如下: - -1. 枚举区间的起点; -2. 枚举区间的终点; -3. 根据状态转移方程计算从小区间转移到更大区间后的最优值。 - -对应代码如下: - -```python -for i in range(size - 1, -1, -1): # 枚举区间起点 - for j in range(i + 1, size): # 枚举区间终点 - # 状态转移方程,计算转移到更大区间后的最优值 - dp[i][j] = max(dp[i + 1][j - 1], dp[i + 1][j], dp[i][j - 1]) + cost[i][j] -``` - -#### 1.2.3 第 2 种区间 DP 问题基本思路 - -多个(大于等于 $2$ 个)小区间转移到大区间的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost[i][j] \rbrace, \quad i < k \le j$。 - -1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 -2. $cost[i][j]$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中的元素合并为区间 $[i, j]$ 中的元素的代价。 -3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 - -多个小区间转移到大区间的区间 DP 问题的基本解题思路如下: - -1. 枚举区间长度; -2. 枚举区间的起点,根据区间起点和区间长度得出区间终点; -3. 枚举区间的分割点,根据状态转移方程计算合并区间后的最优值。 - -对应代码如下: - -```python -for l in range(1, n): # 枚举区间长度 - for i in range(n): # 枚举区间起点 - j = i + l - 1 # 根据起点和长度得到终点 - if j >= n: - break - dp[i][j] = float('-inf') # 初始化 dp[i][j] - for k in range(i, j + 1): # 枚举区间分割点 - # 状态转移方程,计算合并区间后的最优值 - dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + cost[i][j]) -``` - -## 2. 区间 DP 问题的应用 - -下面我们根据几个例子来讲解一下区间 DP 问题的具体解题思路。 - -### 2.1 最长回文子序列 - -#### 2.1.1 题目链接 - -- [516. 最长回文子序列 - 力扣](https://leetcode.cn/problems/longest-palindromic-subsequence/) - -#### 2.1.2 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:找出其中最长的回文子序列,并返回该序列的长度。 - -**说明**: - -- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 -- $1 \le s.length \le 1000$。 -- $s$ 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "bbbab" -输出:4 -解释:一个可能的最长回文子序列为 "bbbb"。 -``` - -- 示例 2: - -```python -输入:s = "cbbd" -输出:2 -解释:一个可能的最长回文子序列为 "bb"。 -``` - -#### 2.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 - -###### 3. 状态转移方程 - -我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: - -1. 如果 $s[i] = s[j]$,则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + $2$,即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 -2. 如果 $s[i] \ne s[j]$,则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: - 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 - 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 - -则状态转移方程为: - -$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ - -###### 4. 初始条件 - -- 单个字符的最长回文序列是 $1$,即 $dp[i][i] = 1$。 - -###### 5. 最终结果 - -由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1]$,所以我们应该按照从下到上、从左到右的顺序进行遍历。 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 - -##### 思路 1:代码 - -```python -class Solution: - def longestPalindromeSubseq(self, s: str) -> int: - size = len(s) - dp = [[0 for _ in range(size)] for _ in range(size)] - for i in range(size): - dp[i][i] = 1 - - for i in range(size - 1, -1, -1): - for j in range(i + 1, size): - if s[i] == s[j]: - dp[i][j] = dp[i + 1][j - 1] + 2 - else: - dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) - - return dp[0][size - 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 -- **空间复杂度**:$O(n^2)$。 - -### 2.2 戳气球 - -#### 2.2.1 题目链接 - -- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/) - -#### 2.2.2 题目大意 - -**描述**:有 $n$ 个气球,编号为 $0 \sim n - 1$,每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。 - -**要求**:求出能获得硬币的最大数量。 - -**说明**: - -- $n == nums.length$。 -- $1 \le n \le 300$。 -- $0 \le nums[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,1,5,8] -输出:167 -解释: -nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] -coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 -``` - -- 示例 2: - -```python -输入:nums = [1,5] -输出:10 -解释: -nums = [1,5] --> [5] --> [] -coins = 1*1*5 + 1*5*1 = 10 -``` - -#### 2.2.3 解题思路 - -##### 思路 1:动态规划 - -根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 $1$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 $0 \sim n + 1$。 - -对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 $1$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 $0 \sim n + 1$ 之间的所有气球(不包括编号 $0$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 - -###### 3. 状态转移方程 - -假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 - -$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: - -$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ - -###### 4. 初始条件 - -- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 $0$,即 $dp[i][j] = 0, \quad i \ge j - 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。所以最终结果为 $dp[0][n + 1]$。 - -##### 思路 1:代码 - -```python -class Solution: - def maxCoins(self, nums: List[int]) -> int: - size = len(nums) - arr = [0 for _ in range(size + 2)] - arr[0] = arr[size + 1] = 1 - for i in range(1, size + 1): - arr[i] = nums[i - 1] - - dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] - - for l in range(3, size + 3): - for i in range(0, size + 2): - j = i + l - 1 - if j >= size + 2: - break - for k in range(i + 1, j): - dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) - - return dp[0][size + 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为气球数量。 -- **空间复杂度**:$O(n^2)$。 - -### 2.3 切棍子的最小成本 - -#### 2.3.1 题目链接 - -- [1547. 切棍子的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) - -#### 2.3.2 题目大意 - -**描述**:给定一个整数 $n$,代表一根长度为 $n$ 个单位的木根,木棍从 $0 \sim n$ 标记了若干位置。例如,长度为 $6$ 的棍子可以标记如下: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg) - -再给定一个整数数组 $cuts$,其中 $cuts[i]$ 表示需要将棍子切开的位置。 - -我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 - -每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 - -**要求**:返回切棍子的最小总成本。 - -**说明**: - -- $2 \le n \le 10^6$。 -- $1 \le cuts.length \le min(n - 1, 100)$。 -- $1 \le cuts[i] \le n - 1$。 -- $cuts$ 数组中的所有整数都互不相同。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e1.jpg) - -```python -输入:n = 7, cuts = [1,3,4,5] -输出:16 -解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 -第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。 -``` - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e11.jpg) - -- 示例 2: - -```python -输入:n = 9, cuts = [5,6,1,4,2] -输出:22 -解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 -``` - -#### 2.3.3 解题思路 - -##### 思路 1:动态规划 - -我们可以预先在数组 $cuts$ 种添加位置 $0$ 和位置 $n$,然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 - -###### 3. 状态转移方程 - -假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k$,则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 - -而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 - -则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ - -###### 4. 初始条件 - -- 相邻位置之间没有切割点,不需要切割,最小成本为 $0$,即 $dp[i - 1][i] = 0$。 -- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 - -##### 思路 1:代码 - -```python -class Solution: - def minCost(self, n: int, cuts: List[int]) -> int: - cuts.append(0) - cuts.append(n) - cuts.sort() - - size = len(cuts) - dp = [[float('inf') for _ in range(size)] for _ in range(size)] - for i in range(1, size): - dp[i - 1][i] = 0 - - for l in range(3, size + 1): # 枚举区间长度 - for i in range(size): # 枚举区间起点 - j = i + l - 1 # 根据起点和长度得到终点 - if j >= size: - continue - dp[i][j] = float('inf') - for k in range(i + 1, j): # 枚举区间分割点 - # 状态转移方程,计算合并区间后的最优值 - dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) - return dp[0][size - 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m^3)$,其中 $m$ 为数组 $cuts$ 的元素个数。 -- **空间复杂度**:$O(m^2)$。 \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md b/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md deleted file mode 100644 index 45050e4a..00000000 --- a/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md +++ /dev/null @@ -1,19 +0,0 @@ -### 区间 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0486 | [预测赢家](https://leetcode.cn/problems/predict-the-winner/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0486.%20%E9%A2%84%E6%B5%8B%E8%B5%A2%E5%AE%B6.md) | 递归、数组、数学、动态规划、博弈 | 中等 | -| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | -| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | -| 1000 | [合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1000.%20%E5%90%88%E5%B9%B6%E7%9F%B3%E5%A4%B4%E7%9A%84%E6%9C%80%E4%BD%8E%E6%88%90%E6%9C%AC.md) | 数组、动态规划、前缀和 | 困难 | -| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划、排序 | 困难 | -| 0664 | [奇怪的打印机](https://leetcode.cn/problems/strange-printer/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0664.%20%E5%A5%87%E6%80%AA%E7%9A%84%E6%89%93%E5%8D%B0%E6%9C%BA.md) | 字符串、动态规划 | 困难 | -| 1039 | [多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1039.%20%E5%A4%9A%E8%BE%B9%E5%BD%A2%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%9A%84%E6%9C%80%E4%BD%8E%E5%BE%97%E5%88%86.md) | 数组、动态规划 | 中等 | -| 0546 | [移除盒子](https://leetcode.cn/problems/remove-boxes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0546.%20%E7%A7%BB%E9%99%A4%E7%9B%92%E5%AD%90.md) | 记忆化搜索、数组、动态规划 | 困难 | -| 0375 | [猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0375.%20%E7%8C%9C%E6%95%B0%E5%AD%97%E5%A4%A7%E5%B0%8F%20II.md) | 数学、动态规划、博弈 | 中等 | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | -| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | -| 0730 | [统计不同回文子序列](https://leetcode.cn/problems/count-different-palindromic-subsequences/) | | 字符串、动态规划 | 困难 | -| 2104 | [子数组范围和](https://leetcode.cn/problems/sum-of-subarray-ranges/) | | 栈、数组、单调栈 | 中等 | - diff --git a/Contents/10.Dynamic-Programming/05.Interval-DP/index.md b/Contents/10.Dynamic-Programming/05.Interval-DP/index.md deleted file mode 100644 index 0b639e35..00000000 --- a/Contents/10.Dynamic-Programming/05.Interval-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [区间 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) -- [区间 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md b/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md deleted file mode 100644 index bfaa0b36..00000000 --- a/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md +++ /dev/null @@ -1,399 +0,0 @@ -## 1. 树形动态规划简介 - -> **树形动态规划**:简称为「树形 DP」,是一种在树形结构上进行推导的动态规划方法。如下图所示,树形 DP 的求解过程一般以节点从深到浅(子树从小到大)的顺序作为动态规划的「阶段」。在树形 DP 中,第 $1$ 维通常是节点编号,代表以该节点为根的子树。 - -![](https://qcdn.itcharge.cn/images/20230418114342.png) - -树形 DP 问题的划分方法有多种方式。 - -如果按照「阶段转移的方向」进行划分,可以划分为以下两种: - -1. **自底向上**:通过递归的方式求解每棵子树,然后在回溯时,自底向上地从子节点向上进行状态转移。只有在当前节点的所有子树求解完毕之后,才可以求解当前节点,以及继续向上进行求解。 -2. **自顶向下**:从根节点开始向下递归,逐层计算子节点的状态。这种方法常常使用记忆化搜索来避免重复计算,提高效率。 - -自顶向下的树形 DP 问题比较少见,大部分树形 DP 都是采用「自底向上」的方向进行推导。 - -如果按照「是否有固定根」进行划分,可以划分为以下两种: - -1. **固定根的树形 DP**:事先指定根节点的树形 DP 问题,通常只需要从给定的根节点开始,使用 $1$ 次深度优先搜索。 -2. **不定根的树形 DP**:事先没有指定根节点的树形 DP 问题,并且根节点的变化会对一些值,例如子节点深度和、点权和等产生影响。通常需要使用 $2$ 次深度优先搜索,第 $1$ 次预处理诸如深度,点权和之类的信息,第 $2$ 次开始运行换根动态规划。 - -本文中,我们将按照「是否有固定根」进行分类,对树形 DP 问题中这两种类型问题进行一一讲解。 - -## 2. 固定根的树形 DP - -### 2.1 固定根的树形 DP 基本思路 - -固定根的树形 DP 问题,如果是二叉树,树通常是以根节点的形式给出。我们可以直接从指定根节点出发进行深度优先搜索。如果是多叉树,树是以一张 $n$ 个节点、$n - 1$ 条边的无向图形式给出的,并且事先给出指定根节点的编号。这种情况下,我们要先用邻接表存储下这 $n$ 个点和 $n - 1$ 条边,然后从指定根节点出发进行深度优先搜索,并注意标记节点是否已经被访问过,以避免在遍历中沿着反向边回到父节点。 - -下面以这两道题为例,介绍一下树形 DP 的一般解题思路。 - -### 2.2 二叉树中的最大路径和 - -#### 2.2.1 题目链接 - -- [124. 二叉树中的最大路径和 - 力扣](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) - -#### 2.2.2 题目大意 - -**描述**:给定一个二叉树的根节点 $root$。 - -**要求**:返回其最大路径和。 - -**说明**: - -- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 -- **路径和**:路径中各节点值的总和。 -- 树中节点数目范围是 $[1, 3 * 10^4]$。 -- $-1000 \le Node.val \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/13/exx1.jpg) - -```python -输入:root = [1,2,3] -输出:6 -解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/10/13/exx2.jpg) - -```python -输入:root = [-10,9,20,null,null,15,7] -输出:42 -解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42 -``` - -#### 2.2.3 解题思路 - -##### 思路 1:树形 DP + 深度优先搜索 - -根据最大路径和中对应路径是否穿过根节点,我们可以将二叉树分为两种: - -1. 最大路径和中对应路径穿过根节点。 -2. 最大路径和中对应路径不穿过根节点。 - -如果最大路径和中对应路径穿过根节点,则:**该二叉树的最大路径和 = 左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值**。 - -而如果最大路径和中对应路径不穿过根节点,则:**该二叉树的最大路径和 = 所有子树中最大路径和**。 - -即:**该二叉树的最大路径和 = max(左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值,所有子树中最大路径和)**。 - -对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大路径和变量 $ans$。 - -然后定义函数 ` def dfs(self, node):` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 - -计算的结果可能的情况有 $2$ 种: - -1. 经过空节点的最大贡献值等于 $0$。 -2. 经过非空节点的最大贡献值等于 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。如果该贡献值为负数,可以考虑舍弃,即最大贡献值为 $0$。 - -在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 $ans$ 即为答案。具体步骤如下: - -1. 如果根节点 $root$ 为空,则返回 $0$。 -2. 递归计算左子树的最大贡献值为 $left\underline{}max$。 -3. 递归计算右子树的最大贡献值为 $right\underline{}max$。 -4. 更新维护最大路径和变量,即 $self.ans = max \lbrace self.ans, \quad left\underline{}max + right\underline{}max + node.val \rbrace$。 -5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。 -6. 最终 $self.ans$ 即为答案。 - -##### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def __init__(self): - self.ans = float('-inf') - - def dfs(self, node): - if not node: - return 0 - left_max = max(self.dfs(node.left), 0) # 左子树提供的最大贡献值 - right_max = max(self.dfs(node.right), 0) # 右子树提供的最大贡献值 - - cur_max = left_max + right_max + node.val # 包含当前节点和左右子树的最大路径和 - self.ans = max(self.ans, cur_max) # 更新所有路径中的最大路径和 - - return max(left_max, right_max) + node.val # 返回包含当前节点的子树的最大贡献值 - - def maxPathSum(self, root: Optional[TreeNode]) -> int: - self.dfs(root) - return self.ans -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - -### 2.3 相邻字符不同的最长路径 - -#### 2.3.1 题目链接 - -- [2246. 相邻字符不同的最长路径 - 力扣](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) - -#### 2.3.2 题目大意 - -**描述**:给定一个长度为 $n$ 的数组 $parent$ 来表示一棵树(即一个连通、无向、无环图)。该树的节点编号为 $0 \sim n - 1$,共 $n$ 个节点,其中根节点的编号为 $0$。其中 $parent[i]$ 表示节点 $i$ 的父节点,由于节点 $0$ 是根节点,所以 $parent[0] == -1$。再给定一个长度为 $n$ 的字符串,其中 $s[i]$ 表示分配给节点 $i$ 的字符。 - -**要求**:找出路径上任意一对相邻节点都没有分配到相同字符的最长路径,并返回该路径的长度。 - -**说明**: - -- $n == parent.length == s.length$。 -- $1 \le n \le 10^5$。 -- 对所有 $i \ge 1$ ,$0 \le parent[i] \le n - 1$ 均成立。 -- $parent[0] == -1$。 -- $parent$ 表示一棵有效的树。 -- $s$ 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2022/03/25/testingdrawio.png) - -```python -输入:parent = [-1,0,0,1,1,2], s = "abacbe" -输出:3 -解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3。 -可以证明不存在满足上述条件且比 3 更长的路径。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2022/03/25/graph2drawio.png) - -```python -输入:parent = [-1,0,0,0], s = "aabc" -输出:3 -解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3。 -``` - -#### 2.3.3 解题思路 - -##### 思路 1:树形 DP + 深度优先搜索 - -因为题目给定的是表示父子节点的 $parent$ 数组,为了方便递归遍历相邻节点,我们可以根据 $partent$ 数组,建立一个由父节点指向子节点的有向图 $graph$。 - -如果不考虑相邻节点是否为相同字符这一条件,那么这道题就是在求树的直径(树的最长路径长度)中的节点个数。 - -对于根节点为 $u$ 的树来说: - -1. 如果其最长路径经过根节点 $u$,则:**最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 -2. 如果其最长路径不经过根节点 $u$,则:**最长路径长度 = 某个子树中的最长路径长度**。 - -即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 - -对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{}len$。 - -1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{}len$。 -2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{}len + v\underline{}len + 1)$。 -3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{}len = max(u\underline{}len, \quad v\underline{}len + 1)$。 - -因为题目限定了「相邻节点字符不同」,所以在更新全局最长路径长度和当前节点 $u$ 的最长路径长度时,我们需要判断一下节点 $u$ 与相邻节点 $v$ 的字符是否相同,只有在字符不同的条件下,才能够更新维护。 - -最后,因为题目要求的是树的直径(树的最长路径长度)中的节点个数,而:**路径的节点 = 路径长度 + 1**,所以最后我们返回 $self.ans + 1$ 作为答案。 - -##### 思路 1:代码 - -```python -class Solution: - def longestPath(self, parent: List[int], s: str) -> int: - size = len(parent) - - # 根据 parent 数组,建立有向图 - graph = [[] for _ in range(size)] - for i in range(1, size): - graph[parent[i]].append(i) - - ans = 0 - def dfs(u): - nonlocal ans - u_len = 0 # u 节点的最大路径长度 - for v in graph[u]: # 遍历 u 节点的相邻节点 - v_len = dfs(v) # 相邻节点的最大路径长度 - if s[u] != s[v]: # 相邻节点字符不同 - ans = max(ans, u_len + v_len + 1) # 维护最大路径长度 - u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 - return u_len # 返回 u 节点的最大路径长度 - - dfs(0) - return ans + 1 -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是树的节点数目。 -- **空间复杂度**:$O(n)$。 - -## 3. 不定根的树形 DP - -### 3.1 不定根的树形 DP 基本思路 - -不定根的树形 DP 问题,如果是二叉树,树通常是以一张 $n$ 个节点、$n - 1$ 条边的无向图形式给出的,并且事先没有指定根节点。通常需要以「每个节点为根节点」进行一系列统计。 - -这种情况下,我们一般通过「两次扫描与换根法」的方法求解这类题目: - -1. 第一次扫描时,任选一个节点为根,在「有固定根的树」上执行一次树形 DP,预处理树的一些相关信息。 -2. 第二次扫描时,从刚才的根节点出发,对整棵树再执行一次深度优先搜索,同时携带根节点的一些信息提供给子节点进行推导,计算出「换根」之后的解。 - -### 3.2 最小高度树 - -#### 3.2.1 题目链接 - -- [310. 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/) - -#### 3.2.2 题目大意 - -**描述**:有一棵包含 $n$ 个节点的树,节点编号为 $0 \sim n - 1$。给定一个数字 $n$ 和一个有 $n - 1$ 条无向边的 $edges$ 列表来表示这棵树。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间存在一条无向边。 - -可以选择树中的任何一个节点作为根,当选择节点 $x$ 作为根节点时,设结果树的高度为 $h$。在所有可能的树种,具有最小高度的树(即 $min(h)$)被成为最小高度树。 - -**要求**:找到所有的最小高度树并按照任意顺序返回他们的根节点编号列表。 - -**说明**: - -- **树的高度**:指根节点和叶子节点之间最长向下路径上边的数量。 -- $1 \le n \le 2 * 10^4$。 -- $edges.length == n - 1$。 -- $0 \le ai, bi < n$。 -- $ai \ne bi$。 -- 所有 $(ai, bi)$ 互不相同。 -- 给定的输入保证是一棵树,并且不会有重复的边。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/09/01/e1.jpg) - -```python -输入:n = 4, edges = [[1,0],[1,2],[1,3]] -输出:[1] -解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/09/01/e2.jpg) - -```python -输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] -输出:[3,4] -``` - -#### 3.2.3 解题思路 - -##### 思路 1:树形 DP + 二次遍历换根法 - -最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点,然后进行深度优先搜索,求出每棵树的高度。最后求出所有树中的最小高度即为答案。但这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 2 \times 10^4]$,这样做会导致超时,因此需要进行优化。 - -在上面的算法中,在一轮深度优先搜索中,除了可以得到整棵树的高度之外,在搜索过程中,其实还能得到以每个子节点为根节点的树的高度。如果我们能够利用这些子树的高度信息,快速得到以其他节点为根节点的树的高度,那么我们就能改进算法,以更小的时间复杂度解决这道题。这就是二次遍历与换根法的思想。 - -1. 第一次遍历:自底向上的计算出每个节点 $u$ 向下走(即由父节点 $u$ 向子节点 $v$ 走)的最长路径 $down1[u]$、次长路径 $down2[i]$,并记录向下走最长路径所经过的子节点 $p[u]$,方便第二次遍历时计算。 -2. 第二次遍历:自顶向下的计算出每个节点 $v$ 向上走(即由子节点 $v$ 向父节点 $u$ 走)的最长路径 $up[v]$。需要注意判断 $u$ 向下走的最长路径是否经过了节点 $v$。 - 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$。 - 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$。 -3. 接下来,我们通过枚举 $n$ 个节点向上走的最长路径与向下走的最长路径,从而找出所有树中的最小高度,并将所有最小高度树的根节点放入答案数组中并返回。 - -整个算法具体步骤如下: - -1. 使用邻接表的形式存储树。 -3. 定义第一个递归函数 `dfs(u, fa)` 用于计算每个节点向下走的最长路径 $down1[u]$、次长路径 $down2[u]$,并记录向下走的最长路径所经过的子节点 $p[u]$。 - 1. 对当前节点的相邻节点进行遍历。 - 2. 如果相邻节点是父节点,则跳过。 - 3. 递归调用 `dfs(v, u)` 函数计算邻居节点的信息。 - 4. 根据邻居节点的信息计算当前节点的高度,并更新当前节点向下走的最长路径 $down1[u]$、当前节点向下走的次长路径 $down2$、取得最长路径的子节点 $p[u]$。 -4. 定义第二个递归函数 `reroot(u, fa)` 用于计算每个节点作为新的根节点时向上走的最长路径 $up[v]$。 - 1. 对当前节点的相邻节点进行遍历。 - 2. 如果相邻节点是父节点,则跳过。 - 3. 根据当前节点 $u$ 的高度和相邻节点 $v$ 的信息更新 $up[v]$。同时需要判断节点 $u$ 向下走的最长路径是否经过了节点 $v$。 - 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down2[u]) + 1$。 - 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down1[u]) + 1$。 - 4. 递归调用 `reroot(v, u)` 函数计算邻居节点的信息。 -5. 调用 `dfs(0, -1)` 函数计算每个节点的最长路径。 -6. 调用 `reroot(0, -1)` 函数计算每个节点作为新的根节点时的最长路径。 -7. 找到所有树中的最小高度。 -8. 将所有最小高度的节点放入答案数组中并返回。 - -##### 思路 1:代码 - -```python -class Solution: - def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: - graph = [[] for _ in range(n)] - for u, v in edges: - graph[u].append(v) - graph[v].append(u) - - # down1 用于记录向下走的最长路径 - down1 = [0 for _ in range(n)] - # down2 用于记录向下走的最长路径 - down2 = [0 for _ in range(n)] - p = [0 for _ in range(n)] - # 自底向上记录最长路径、次长路径 - def dfs(u, fa): - for v in graph[u]: - if v == fa: - continue - # 自底向上统计信息 - dfs(v, u) - height = down1[v] + 1 - if height >= down1[u]: - down2[u] = down1[u] - down1[u] = height - p[u] = v - elif height > down2[u]: - down2[u] = height - - # 进行换根动态规划,自顶向下统计向上走的最长路径 - up = [0 for _ in range(n)] - def reroot(u, fa): - for v in graph[u]: - if v == fa: - continue - if p[u] == v: - up[v] = max(up[u], down2[u]) + 1 - else: - up[v] = max(up[u], down1[u]) + 1 - # 自顶向下统计信息 - reroot(v, u) - - dfs(0, -1) - reroot(0, -1) - - # 找到所有树中的最小高度 - min_h = 1e9 - for i in range(n): - min_h = min(min_h, max(down1[i], up[i])) - - # 将所有最小高度的节点放入答案数组中并返回 - res = [] - for i in range(n): - if max(down1[i], up[i]) == min_h: - res.append(i) - - return res -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[C++ 容易理解的换根动态规划解法 - 最小高度树](https://leetcode.cn/problems/minimum-height-trees/solution/c-huan-gen-by-vclip-sa84/) -- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) -- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) diff --git a/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md b/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md deleted file mode 100644 index 41bd0006..00000000 --- a/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md +++ /dev/null @@ -1,30 +0,0 @@ -### 树形 DP 题目 - -#### 固定根的树形 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0543 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0543.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 简单 | -| 0124 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0124.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 1245 | [树的直径](https://leetcode.cn/problems/tree-diameter/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1245.%20%E6%A0%91%E7%9A%84%E7%9B%B4%E5%BE%84.md) | 树、深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 2246 | [相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2246.%20%E7%9B%B8%E9%82%BB%E5%AD%97%E7%AC%A6%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%80%E9%95%BF%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、图、拓扑排序、数组、字符串 | 困难 | -| 0687 | [最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0687.%20%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84.md) | 树、深度优先搜索、二叉树 | 中等 | -| 0337 | [打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0337.%20%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%20III.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | -| 0333 | [最大 BST 子树](https://leetcode.cn/problems/largest-bst-subtree/) | | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 中等 | -| 1617 | [统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1617.%20%E7%BB%9F%E8%AE%A1%E5%AD%90%E6%A0%91%E4%B8%AD%E5%9F%8E%E5%B8%82%E4%B9%8B%E9%97%B4%E6%9C%80%E5%A4%A7%E8%B7%9D%E7%A6%BB.md) | 位运算、树、动态规划、状态压缩、枚举 | 困难 | -| 2538 | [最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2538.%20%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%E5%92%8C%E4%B8%8E%E6%9C%80%E5%B0%8F%E4%BB%B7%E5%80%BC%E5%92%8C%E7%9A%84%E5%B7%AE%E5%80%BC.md) | 树、深度优先搜索、数组、动态规划 | 困难 | -| 1569 | [将子数组重新排序得到同一个二叉搜索树的方案数](https://leetcode.cn/problems/number-of-ways-to-reorder-array-to-get-same-bst/) | | 树、并查集、二叉搜索树、记忆化搜索、数组、数学、分治、动态规划、二叉树、组合数学 | 困难 | -| 1372 | [二叉树中的最长交错路径](https://leetcode.cn/problems/longest-zigzag-path-in-a-binary-tree/) | | 树、深度优先搜索、动态规划、二叉树 | 中等 | -| 1373 | [二叉搜索子树的最大键值和](https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/) | | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 困难 | -| 0968 | [监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0968.%20%E7%9B%91%E6%8E%A7%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | -| 1273 | [删除树节点](https://leetcode.cn/problems/delete-tree-nodes/) | | 树、深度优先搜索、广度优先搜索 | 中等 | -| 1519 | [子树中标签相同的节点数](https://leetcode.cn/problems/number-of-nodes-in-the-sub-tree-with-the-same-label/) | | 树、深度优先搜索、广度优先搜索、哈希表、计数 | 中等 | - -#### 不定根的树形 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0310 | [最小高度树](https://leetcode.cn/problems/minimum-height-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0310.%20%E6%9C%80%E5%B0%8F%E9%AB%98%E5%BA%A6%E6%A0%91.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | -| 0834 | [树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0834.%20%E6%A0%91%E4%B8%AD%E8%B7%9D%E7%A6%BB%E4%B9%8B%E5%92%8C.md) | 树、深度优先搜索、图、动态规划 | 困难 | -| 2581 | [统计可能的树根数目](https://leetcode.cn/problems/count-number-of-possible-root-nodes/) | | 树、深度优先搜索、哈希表、动态规划 | 困难 | - diff --git a/Contents/10.Dynamic-Programming/06.Tree-DP/index.md b/Contents/10.Dynamic-Programming/06.Tree-DP/index.md deleted file mode 100644 index 1b72c8a4..00000000 --- a/Contents/10.Dynamic-Programming/06.Tree-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [树形 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) -- [树形 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md b/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md deleted file mode 100644 index b5eaad57..00000000 --- a/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md +++ /dev/null @@ -1,323 +0,0 @@ -## 1. 状态压缩 DP 简介 - -> **状态压缩 DP**:简称为「状压 DP」,是一种应用在「小规模数据」的数组 / 字符串上,结合「二进制」的性质来进行状态定义与状态转移的动态规划方法。 - -我们曾在「位运算知识」章节中,学习过「二进制枚举子集算法」。这里先来回顾一下如何通过二进制枚举子集。 - -### 1.1 二进制枚举子集 - -对于一个元素个数为 $n$ 的集合 $S$ 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 $1$ 来表示选取该元素,用数字 $0$ 来表示不选取该元素。 - -那么我们就可以用一个长度为 $n$ 的二进制数来表示集合 $S$ 或者表示 $S$ 的子集。其中二进制的每一个二进位都对应了集合中某一个元素的选取状态。对于集合中第 $i$ 个元素来说,二进制对应位置上的 $1$ 代表该元素被选取,$0$ 代表该元素未被选取。 - -举个例子,比如长度为 $5$ 的集合 $S = \lbrace 5, 4, 3, 2, 1 \rbrace$,我们可以用一个长度为 $5$ 的二进制数来表示该集合。 - -比如二进制数 $11111_{(2)}$ 就表示选取集合的第 $1$ 位、第 $2$ 位、第 $3$ 位、第 $4$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 4, 3, 2, 1 \rbrace$,即集合 $S$ 本身。如下表所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :--: | :--: | :--: | :--: | :--: | -| 二进位对应值 | 1 | 1 | 1 | 1 | 1 | -| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | - -再比如二进制数 $10101_{(2)}$ 就表示选取集合的第 $1$ 位、第 $3$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 3, 1 \rbrace$。如下表所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :--: | :----: | :--: | :----: | :--: | -| 二进位对应值 | 1 | 0 | 1 | 0 | 1 | -| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | - -再比如二进制数 $01001_{(2)}$ 就表示选取集合的第 $1$ 位、第 $4$ 位元素,也就是集合 $\lbrace 4, 1 \rbrace$。如下标所示: - -| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | -| :---------------- | :----: | :--: | :----: | :----: | :--: | -| 二进位对应值 | 0 | 1 | 0 | 0 | 1 | -| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | - -通过上面的例子我们可以得到启发:对于长度为 $5$ 的集合 $S$ 来说,我们只需要从 $00000 \sim 11111$ 枚举一次(对应十进制为 $0 \sim 2^5 - 1$)即可得到长度为 $5$ 的集合 $S$ 的所有子集。 - -我们将上面的例子拓展到长度为 $n$ 的集合 $S$。可以总结为: - -- 对于长度为 $n$ 的集合 $S$ 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到集合 $S$ 的所有子集。 - -### 1.2 状态定义与状态转移 - -#### 1.2.1 状态定义 - -在状压 DP 中,我们通常采用二进制数的形式来表示一维状态,即集合中每个元素的选取情况。 - -和「二进制枚举子集算法」一样,我们通过一个「 $n$ 位长度的二进制数」来表示「由 $n$ 个物品所组成的集合中所有物品的选择状态」。 - -二进制数的每一个二进位都对应了集合中某一个元素的选取状态。如果该二进制数的第 $i$ 位为 $1$,说明集合中第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明集合中第 $i$ 个元素在该状态中没有被选取。 - -#### 1.2.1 状态转移 - -一般来说,状压 DP 的状态转移方式有两种: - -1. 枚举子集:对于一个状态,枚举它的所有子集,或者枚举所有元素位置,找到比当前状态少选一个元素的子集。然后根据子集的值和状态之间的关系,更新当前状态的值。 -2. 枚举超集:对于一个状态,枚举它的所有超集。然后根据超集的值和状态之间的关系,更新当前状态的值。 - -其中,最常用的是「枚举子集」的方式。 - -### 1.3 状压 DP 的使用条件 - -对于元素个数不超过 $n$ 的集合来说,一共会出现 $2^n$ 个状态数量。因为在 $n$ 变大时会呈现指数级增长,所以状态压缩 DP 只适用于求解小数据规模问题(通常 $n \le 20$)。当 $n$ 过大时,使用状态压缩 DP 可能会超时。 - -## 2. 状态压缩 DP 中常用的位运算 - -在状压 DP 中,一维状态是集合,对状态进行操作或者状态之间进行转移,也就是要对集合进行操作。 - -因为我们使用二进制数来定义集合状态,所以对集合进行操作,就是对二进制数进行位运算操作。 - -如下所示,其中 $n$ 为集合中的元素个数,$A$、$B$ 为两个集合对应的二进制数,$i$ 表示某个元素位置。 - -- 总状态数量:`1 << n` -- 在集合 $A$ 中加入第 $i$ 位元素(将二进制数第 $i$ 位赋值为 $1$):`A = A | (1 << i)` -- 在集合 $A$ 中删除第 $i$ 位元素(将二进制数第 $i$ 位赋值为 $0$):`A = A & ~(1 << i)` -- 判断集合 $A$ 是否选取了第 $i$ 位元素(判断二进制数第 $i$ 位是否为 $1$) :`if A & (1 << i):` 或者 `if (A >> i) & 1:` -- 将集合 $A$ 设置为空集:`A = 0` -- 将集合 $A$ 设置为全集:`A = 1 << n` -- 求集合 $A$ 的补集:`A = A ^ ((1 << n) - 1)` -- 求集合 $A$ 与集合 $B$ 的并集:`A | B` -- 求集合 $A$ 与集合 $B$ 的交集:`A & B` -- 枚举集合 $A$ 的子集(包含 $A$): - - ```python - subA = A # 从集合 A 开始 - while subA > 0: - ... - subA = (subB - 1) & A # 获取下一个子集 - ``` - -- 枚举全集的所有子集: - - ```python - for state in range(1 << n): # state 为子集 - for i in range(n): # 枚举第 i 位元素 - if (state >> i) & i: # 如果第 i 位元素对应二进制位 1,则表示集合中选取了该元素 - ... - ``` - -## 3. 状态压缩 DP 的应用 - -### 3.1 两个数组最小的异或值之和 - -#### 3.1.1 题目链接 - -- [1879. 两个数组最小的异或值之和 - 力扣](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) - -#### 3.1.2 题目大意 - -**描述**:给定两个整数数组 $nums1$ 和 $nums2$,两个数组长度都为 $n$。 - -**要求**:将 $nums2$ 中的元素重新排列,使得两个数组的异或值之和最小。并返回重新排列之后的异或值之和。 - -**说明**: - -- **两个数组的异或值之和**:$(nums1[0] \oplus nums2[0]) + (nums1[1] \oplus nums2[1]) + ... + (nums1[n - 1] \oplus nums2[n - 1])$(下标从 $0$ 开始)。 -- 举个例子,$[1, 2, 3]$ 和 $[3,2,1]$ 的异或值之和 等于 $(1 \oplus 3) + (2 \oplus 2) + (3 \oplus 1) + (3 \oplus 1) = 2 + 0 + 2 = 4$。 -- $n == nums1.length$。 -- $n == nums2.length$。 -- $1 \le n \le 14$。 -- $0 \le nums1[i], nums2[i] \le 10^7$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2], nums2 = [2,3] -输出:2 -解释:将 nums2 重新排列得到 [3,2] 。 -异或值之和为 (1 XOR 3) + (2 XOR 2) = 2 + 0 = 2。 -``` - -- 示例 2: - -```python -输入:nums1 = [1,0,3], nums2 = [5,3,4] -输出:8 -解释:将 nums2 重新排列得到 [5,4,3] 。 -异或值之和为 (1 XOR 5) + (0 XOR 4) + (3 XOR 3) = 4 + 4 + 0 = 8。 -``` - -#### 3.1.3 解题思路 - -##### 思路 1:状态压缩 DP - -由于数组 $nums2$ 可以重新排列,所以我们可以将数组 $nums1$ 中的元素顺序固定,然后将数组 $nums1$ 中第 $i$ 个元素与数组 $nums2$ 中所有还没被选择的元素进行组合,找到异或值之和最小的组合。 - -同时因为两个数组长度 $n$ 的大小范围只有 $[1, 14]$,所以我们可以采用「状态压缩」的方式来表示 $nums2$ 中当前元素的选择情况。 - -「状态压缩」指的是使用一个 $n$ 位的二进制数 $state$ 来表示排列中数的选取情况。 - -如果二进制数 $state$ 的第 $i$ 位为 $1$,说明数组 $nums2$ 第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明数组 $nums2$ 中第 $i$ 个元素在该状态中没有被选取。 - -举个例子: - -1. $nums2 = \lbrace 1, 2, 3, 4 \rbrace, state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。 -2. $nums2 = \lbrace 1, 2, 3, 4, 5, 6 \rbrace, state = (011010)_2$,表示选择了第 $2$ 个元素、第 $4$ 个元素、第 $5$ 个元素,也就是 $2$、$4$、$5$。 - -这样,我们就可以通过动态规划的方式来解决这道题。 - -###### 1. 划分阶段 - -按照数组 $nums$ 中元素选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义当前数组 $nums2$ 中元素选择状态为 $state$,$state$ 对应选择的元素个数为 $count(state)$。 - -则可以定义状态 $dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 - -###### 3. 状态转移方程 - -对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以组成的异或值之和最小值,赋值给 $dp[state]$。 - -举个例子 $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。那么 $state$ 只能从 $(1000)_2$ 和 $(0001)_2$ 这两个状态转移而来,我们只需要枚举这两种状态,并求出转移过来的异或值之和最小值。 - -即状态转移方程为:$dp[state] = min(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + (nums1[i] \oplus nums2[one\underline{}cnt - 1]))$,其中 $state$ 第 $i$ 位一定为 $1$,$one\underline{}cnt$ 为 $state$ 中 $1$ 的个数。 - -###### 4. 初始条件 - -- 既然是求最小值,不妨将所有状态初始为最大值。 -- 未选择任何数时,异或值之和为 $0$,所以初始化 $dp[0] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } n$。 - -##### 思路 1:代码 - -```python -class Solution: - def minimumXORSum(self, nums1: List[int], nums2: List[int]) -> int: - ans = float('inf') - size = len(nums1) - states = 1 << size - - dp = [float('inf') for _ in range(states)] - dp[0] = 0 - for state in range(states): - one_cnt = bin(state).count('1') - for i in range(size): - if (state >> i) & 1: - dp[state] = min(dp[state], dp[state ^ (1 << i)] + (nums1[i] ^ nums2[one_cnt - 1])) - - return dp[states - 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 $nums1$、$nums2$ 的长度。 -- **空间复杂度**:$O(2^n)$。 - -### 3.2 数组的最大与和 - -#### 3.2.1 题目链接 - -- [2172. 数组的最大与和 - 力扣](https://leetcode.cn/problems/maximum-and-sum-of-array/) - -#### 3.2.2 题目大意 - -**描述**:给定一个长度为 $n$ 的整数数组 $nums$ 和一个整数 $numSlots$ 满足 $2 \times numSlots \ge n$。一共有 $numSlots$ 个篮子,编号为 $1 \sim numSlots$。 - -现在需要将所有 $n$ 个整数分到这些篮子中,且每个篮子最多有 $2$ 个整数。 - -**要求**:返回将 $nums$ 中所有数放入 $numSlots$ 个篮子中的最大与和。 - -**说明**: - -- **与和**:当前方案中,每个数与它所在篮子编号的按位与运算结果之和。 - - 比如,将数字 $[1, 3]$ 放入篮子 $1$ 中,$[4, 6]$ 放入篮子 $2$ 中,这个方案的与和为 $(1 \text{ AND } 1) + (3 \text{ AND } 1) + (4 \text{ AND } 2) + (6 \text{ AND } 2) = 1 + 1 + 0 + 2 = 4$。 -- $n == nums.length$。 -- $1 \le numSlots \le 9$。 -- $1 \le n \le 2 \times numSlots$。 -- $1 \le nums[i] \le 15$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,4,5,6], numSlots = 3 -输出:9 -解释:一个可行的方案是 [1, 4] 放入篮子 1 中,[2, 6] 放入篮子 2 中,[3, 5] 放入篮子 3 中。 -最大与和为 (1 AND 1) + (4 AND 1) + (2 AND 2) + (6 AND 2) + (3 AND 3) + (5 AND 3) = 1 + 0 + 2 + 2 + 3 + 1 = 9。 -``` - -- 示例 2: - -```python -输入:nums = [1,3,10,4,7,1], numSlots = 9 -输出:24 -解释:一个可行的方案是 [1, 1] 放入篮子 1 中,[3] 放入篮子 3 中,[4] 放入篮子 4 中,[7] 放入篮子 7 中,[10] 放入篮子 9 中。 -最大与和为 (1 AND 1) + (1 AND 1) + (3 AND 3) + (4 AND 4) + (7 AND 7) + (10 AND 9) = 1 + 1 + 3 + 4 + 7 + 8 = 24 。 -注意,篮子 2 ,5 ,6 和 8 是空的,这是允许的。 -``` - -#### 3.2.3 解题思路 - -##### 思路 1:状压 DP - -每个篮子最多可分 $2$ 个整数,则我们可以将 $1$ 个篮子分成两个篮子,这样总共有 $2 \times numSlots$ 个篮子,每个篮子中最多可以装 $1$ 个整数。 - -同时因为 $numSlots$ 的范围为 $[1, 9]$,$2 \times numSlots$ 的范围为 $[2, 19]$,范围不是很大,所以我们可以用「状态压缩」的方式来表示每个篮子中的整数放取情况。 - -即使用一个 $n \times numSlots$ 位的二进制数 $state$ 来表示每个篮子中的整数放取情况。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 个篮子里边放了整数,如果 $state$ 的第 $i$ 位为 $0$,表示第 $i$ 个篮子为空。 - -这样,我们就可以通过动态规划的方式来解决这道题。 - -###### 1. 划分阶段 - -按照 $2 \times numSlots$ 个篮子中的整数放取情况进行阶段划分。 - -###### 2. 定义状态 - -定义当前每个篮子中的整数放取情况为 $state$,$state$ 对应选择的整数个数为 $count(state)$。 - -则可以定义状态 $dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。 - -###### 3. 状态转移方程 - -对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以获得的最大与和,赋值给 $dp[state]$。 - -即状态转移方程为:$dp[state] = min(dp[state], dp[state \oplus (1 \text{ <}\text{< } i)] + (i // 2 + 1) \text{ \& } nums[one\underline{}cnt - 1])$,其中: - -1. $state$ 第 $i$ 位一定为 $1$。 -2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 -3. $i // 2 + 1$ 为篮子对应编号 -4. $nums[one\underline{}cnt - 1]$ 为当前正在考虑的数组元素。 - -###### 4. 初始条件 - -- 初始每个篮子中都没有放整数的情况下,可以获得的最大与和为 $0$,即 $dp[0] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。所以最终结果为 $max(dp)$。 - -> 注意:当 $one\underline{}cnt > len(nums)$ 时,无法通过递推得到 $dp[state]$,需要跳过。 - -##### 思路 1:代码 - -```python -class Solution: - def maximumANDSum(self, nums: List[int], numSlots: int) -> int: - states = 1 << (numSlots * 2) - dp = [0 for _ in range(states)] - - for state in range(states): - one_cnt = bin(state).count('1') - if one_cnt > len(nums): - continue - for i in range(numSlots * 2): - if (state >> i) & 1: - dp[state] = max(dp[state], dp[state ^ (1 << i)] + ((i // 2 + 1) & nums[one_cnt - 1])) - - return max(dp) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^m \times m)$,其中 $m = 2 \times numSlots$。 -- **空间复杂度**:$O(2^m)$。 diff --git a/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md b/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md deleted file mode 100644 index e361328c..00000000 --- a/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md +++ /dev/null @@ -1,26 +0,0 @@ -### 状态压缩 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 1879 | [两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1879.%20%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%9C%80%E5%B0%8F%E7%9A%84%E5%BC%82%E6%88%96%E5%80%BC%E4%B9%8B%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 2172 | [数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2172.%20%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B8%8E%E5%92%8C.md) | 位运算、数组、动态规划、状态压缩 | 困难 | -| 1947 | [最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1947.%20%E6%9C%80%E5%A4%A7%E5%85%BC%E5%AE%B9%E6%80%A7%E8%AF%84%E5%88%86%E5%92%8C.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1595 | [连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1595.%20%E8%BF%9E%E9%80%9A%E4%B8%A4%E7%BB%84%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 1494 | [并行课程 II](https://leetcode.cn/problems/parallel-courses-ii/) | | 位运算、图、动态规划、状态压缩 | 困难 | -| 1655 | [分配重复整数](https://leetcode.cn/problems/distribute-repeating-integers/) | | 位运算、数组、动态规划、回溯、状态压缩 | 困难 | -| 1986 | [完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1986.%20%E5%AE%8C%E6%88%90%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%9C%80%E5%B0%91%E5%B7%A5%E4%BD%9C%E6%97%B6%E9%97%B4%E6%AE%B5.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 1434 | [每个人戴不同帽子的方案数](https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other/) | | 位运算、数组、动态规划、状态压缩 | 困难 | -| 1799 | [N 次操作后的最大分数和](https://leetcode.cn/problems/maximize-score-after-n-operations/) | | 位运算、数组、数学、动态规划、回溯、状态压缩、数论 | 困难 | -| 1681 | [最小不兼容性](https://leetcode.cn/problems/minimum-incompatibility/) | | 位运算、数组、动态规划、状态压缩 | 困难 | -| 0526 | [优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0526.%20%E4%BC%98%E7%BE%8E%E7%9A%84%E6%8E%92%E5%88%97.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | -| 0351 | [安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md) | 动态规划、回溯 | 中等 | -| 0464 | [我能赢吗](https://leetcode.cn/problems/can-i-win/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0464.%20%E6%88%91%E8%83%BD%E8%B5%A2%E5%90%97.md) | 位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 | 中等 | -| 0847 | [访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0847.%20%E8%AE%BF%E9%97%AE%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84.md) | 位运算、广度优先搜索、图、动态规划、状态压缩 | 困难 | -| 0638 | [大礼包](https://leetcode.cn/problems/shopping-offers/) | | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 1994 | [好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1994.%20%E5%A5%BD%E5%AD%90%E9%9B%86%E7%9A%84%E6%95%B0%E7%9B%AE.md) | 位运算、数组、数学、动态规划、状态压缩 | 困难 | -| 1349 | [参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1349.%20%E5%8F%82%E5%8A%A0%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | -| 0698 | [划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0698.%20%E5%88%92%E5%88%86%E4%B8%BAk%E4%B8%AA%E7%9B%B8%E7%AD%89%E7%9A%84%E5%AD%90%E9%9B%86.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | -| 0943 | [最短超级串](https://leetcode.cn/problems/find-the-shortest-superstring/) | | 位运算、数组、字符串、动态规划、状态压缩 | 困难 | -| 0691 | [贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0691.%20%E8%B4%B4%E7%BA%B8%E6%8B%BC%E8%AF%8D.md) | 位运算、数组、字符串、动态规划、回溯、状态压缩 | 困难 | -| 0982 | [按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0982.%20%E6%8C%89%E4%BD%8D%E4%B8%8E%E4%B8%BA%E9%9B%B6%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84.md) | 位运算、数组、哈希表 | 困难 | - diff --git a/Contents/10.Dynamic-Programming/07.State-DP/index.md b/Contents/10.Dynamic-Programming/07.State-DP/index.md deleted file mode 100644 index d0d1ea87..00000000 --- a/Contents/10.Dynamic-Programming/07.State-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [状态压缩 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md) -- [状态压缩 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md b/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md deleted file mode 100644 index 50744e22..00000000 --- a/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md +++ /dev/null @@ -1,203 +0,0 @@ -## 1. 计数类 DP 简介 - -### 1.1 计数问题简介 - -> **计数问题**:计算满足特定条件下的可行方案数目的问题。 - -这里的「可行方案数目」指的是某个问题一共有多少种方法。 - -「计数问题」本身是组合数学中的重要内容,这类问题通常有两种经典的求解方法: - -1. 找到递归关系,然后以动态规划的方式,列出递推式,然后求解出方案数。 -2. 转为数学问题,计算出对应的组合数,如:卡特兰数、快速幂、排列数、组合数等。 - -在解决具体问题时,还需要根据题目的具体情况进行分析。一般情况下,我们使用第 $1$ 种方法来解决。这是因为: - -1. 采用动态规划的方式能够高效的处理大规模计数问题,并且使用较少时间和空间复杂度。 -2. 即使找到了组合数,还要面临计算组合数的困难(高阶组合数计算困难、计算效率较低)。 - -所以我们通常使用「动态规划」的方法来解决计数问题。 - -### 1.2 计数类 DP 简介 - -> **计数类 DP**:一类使用动态规划方法来统计可行方案数目的问题。区别于求解最优解,计数类 DP 需要统计所有满足条件的可行解数量,同时需要满足不重复、不遗漏的条件。 - -计数类 DP 的核心思想就是:通过动态规划的算法思想,去计算出解决这个问题有多少种方法。一般来说,计数类 DP 只关注方案数目,不关注具体方案情况。 - -比如,从一个矩阵的左上角走到右下角,每次只能向右走或者向下走,一共有多少条不同路径。**注意**:这里求解的是有多少条,而不是具体路线的走法。 - -## 2. 计数类 DP 的应用 - -### 2.1 不同路径 - -#### 2.1.1 题目链接 - -- [62. 不同路径 - 力扣](https://leetcode.cn/problems/unique-paths/) - -#### 2.1.2 题目大意 - -**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 - -**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 - -**说明**: - -- $1 \le m, n \le 100$。 -- 题目数据保证答案小于等于 $2 * 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:m = 3, n = 7 -输出:28 -``` - -![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) - -- 示例 2: - -```python -输入:m = 3, n = 2 -输出:3 -解释: -从左上角开始,总共有 3 条路径可以到达右下角。 -1. 向右 -> 向下 -> 向下 -2. 向下 -> 向下 -> 向右 -3. 向下 -> 向右 -> 向下 -``` - -#### 2.1.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:从左上角到达位置 $(i, j)$ 的路径数量。 - -###### 3. 状态转移方程 - -因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 - -###### 4. 初始条件 - -- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 -- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 -- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 - -##### 思路 1:动态规划代码 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - dp = [[0 for _ in range(n)] for _ in range(m)] - - for j in range(n): - dp[0][j] = 1 - for i in range(m): - dp[i][0] = 1 - - for i in range(1, m): - for j in range(1, n): - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - - return dp[m - 1][n - 1] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m * n)$,所以总体时间复杂度为 $O(m \times n)$。 -- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $m$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(m)$。 - -### 2.2 整数拆分 - -#### 2.2.1 题目链接 - -- [343. 整数拆分 - 力扣](https://leetcode.cn/problems/integer-break/) - -#### 2.2.2 题目大意 - -**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 - -**要求**:返回可以获得的最大乘积。 - -**说明**: - -- $2 \le n \le 58$。 - -**示例**: - -- 示例 1: - -```python -输入: n = 2 -输出: 1 -解释: 2 = 1 + 1, 1 × 1 = 1。 -``` - -- 示例 2: - -```python -输入: n = 10 -输出: 36 -解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 -``` - -#### 2.2.3 解题思路 - -##### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照正整数进行划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 - -###### 3. 状态转移方程 - -当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: - -1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 -2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 - -则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 - -由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: - -$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 - -###### 4. 初始条件 - -- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 - -##### 思路 1:代码 - -```python -class Solution: - def integerBreak(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - for i in range(2, n + 1): - for j in range(i): - dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) - return dp[n] -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n)$。 diff --git a/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md b/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md deleted file mode 100644 index b3154c55..00000000 --- a/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md +++ /dev/null @@ -1,15 +0,0 @@ -### 计数 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0062 | [不同路径](https://leetcode.cn/problems/unique-paths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0062.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.md) | 数学、动态规划、组合数学 | 中等 | -| 0063 | [不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0063.%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II.md) | 数组、动态规划、矩阵 | 中等 | -| 0343 | [整数拆分](https://leetcode.cn/problems/integer-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0343.%20%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.md) | 数学、动态规划 | 中等 | -| 0096 | [不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0096.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | -| 1259 | [不相交的握手](https://leetcode.cn/problems/handshakes-that-dont-cross/) | | 数学、动态规划 | 困难 | -| 0790 | [多米诺和托米诺平铺](https://leetcode.cn/problems/domino-and-tromino-tiling/) | | 动态规划 | 中等 | -| 0070 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0070.%20%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 记忆化搜索、数学、动态规划 | 简单 | -| 0746 | [使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0746.%20%E4%BD%BF%E7%94%A8%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9%E7%88%AC%E6%A5%BC%E6%A2%AF.md) | 数组、动态规划 | 简单 | -| 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | -| 1137 | [第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1137.%20%E7%AC%AC%20N%20%E4%B8%AA%E6%B3%B0%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 记忆化搜索、数学、动态规划 | 简单 | - diff --git a/Contents/10.Dynamic-Programming/08.Counting-DP/index.md b/Contents/10.Dynamic-Programming/08.Counting-DP/index.md deleted file mode 100644 index d17520dc..00000000 --- a/Contents/10.Dynamic-Programming/08.Counting-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [计数 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) -- [计数 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md b/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md deleted file mode 100644 index 0453bd72..00000000 --- a/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md +++ /dev/null @@ -1,372 +0,0 @@ -## 1. 数位 DP 简介 - -### 1.1 数位 DP 简介 - -> **数位动态规划**:简称为「数位 DP」,是一种与数位相关的一类计数类动态规划问题,即在数位上进行动态规划。这里的数位指的是个位、十位、百位、千位等。 - -数位 DP 一般用于求解给定区间 $[left, right]$ 中,满足特定条件的数值个数,或者用于求解满足特定条件的第 $k$ 小数。 - -数位 DP 通常有以下几个特征: - -1. 题目会提供一个查询区间(有时也会只提供区间上界)来作为统计限制。 -2. 题目中给定区间往往很大(比如 $10^9$),无法采用朴素的方法求解。 -3. 题目中给定的给定的限定条件往往与数位有关。 -4. 要求统计满足特定条件的数值个数,或者用于求解满足特定条件的第 $k$ 小数。 - -题目要求一段区间 $[left, right]$ 内满足特定条件的数值个数,如果能找到方法计算出前缀区间 $[0, n]$ 内满足特定条件的数值个数,那么我们就可以利用「前缀和思想」,分别计算出区间 $[0, left - 1]$ 与区间 $[0, right]$ 内满足特定条件的数值个数,然后将两者相减即为所求答案。即:$res[left, right] = res[0, right] - res[0, left - 1]$。 - -在使用「前缀和思想」思想后,问题转换为计算区间 $[0, n]$ 内满足特定条件的数值个数。 - -接下来就要用到数位 DP 的基本思想。 - -> **数位 DP 的基本思想**:将区间数字拆分为数位,然后逐位进行确定。 - -我们通过将区间上的数字按照数位进行拆分,然后逐位确定每一个数位上的可行方案,从而计算出区间内的可行方案个数。 - -数位 DP 可以通过「记忆化搜索」的方式实现,也可以通过「迭代递推」的方式实现。因为数位 DP 中需要考虑的参数很多,使用「记忆化搜索」的方式更加方便传入参数,所以这里我们采用「记忆化搜索」的方式来实现。 - -在使用「记忆化搜索」的时候,需要考虑的参数有: - -1. 当前枚举的数位位置($pos$)。 -2. 前一位数位(或前几位数位)的情况,比如前几位的总和($total$)、某个数字出现次数($cnt$)、前几位所选数字集合(通常使用「状态压缩」的方式,即用一个二进制整数 $state$ 来表示)等等。 -3. 前一位数位(或前几位数位)是否等于上界的前几位数字($isLimit$),用于限制本次搜索的数位范围。 -4. 前一位数位是否填了数字($isNum$),如果前一位数位填了数字,则当前位可以从 $0$ 开始填写数字;如果前一位没有填写数字,则当前位可以跳过,或者从 $1$ 开始填写数字。 -5. 当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 - -对应代码如下: - -```python -class Solution: - def digitDP(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for x in range(minX, maxX + 1): - # x 不在选择的数字集合中,即之前没有选择过 x - if (state >> x) & 1 == 0: - ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True) - return ans - - return dfs(0, 0, True, False) -``` - -接下来,我们通过一道简单的例题来具体了解一下数位 DP 以及解题思路。 - -### 1.2 统计特殊整数 - -#### 1.2.1 题目大意 - -**描述**:给定一个正整数 $n$。 - -**要求**:求区间 $[1, n]$ 内的所有整数中,特殊整数的数目。 - -**说明**: - -- **特殊整数**:如果一个正整数的每一个数位都是互不相同的,则称它是特殊整数。 -- $1 \le n \le 2 \times 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 20 -输出:19 -解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。 -``` - -- 示例 2: - -```python -输入:n = 5 -输出:5 -解释:1 到 5 所有整数都是特殊整数。 -``` - -#### 1.2.2 解题思路 - -##### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 - 4. 开始时没有填写数字。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), - 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $x$。 - 3. 如果之前没有选择 $x$,即 $x$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $x$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True)`。 - 1. `state | (1 << x)` 表示之前选择的数字集合 $state$ 加上 $x$。 - 2. `isLimit and x == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 - 3. $isNum == True$ 表示 $pos$ 位选择了数字。 - -##### 思路 1:代码 - -```python -class Solution: - def countSpecialNumbers(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for x in range(minX, maxX + 1): - # x 不在选择的数字集合中,即之前没有选择过 x - if (state >> x) & 1 == 0: - ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True) - return ans - - return dfs(0, 0, True, False) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(\log n \times 2^{10})$。 - -## 2. 数位 DP 的应用 - -### 2.1 至少有 1 位重复的数字 - -#### 2.1.1 题目链接 - -- [1012. 至少有 1 位重复的数字 - 力扣](https://leetcode.cn/problems/numbers-with-repeated-digits/) - -#### 2.1.2 题目大意 - -**描述**:给定一个正整数 $n$。 - -**要求**:返回在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数。 - -**说明**: - -- $1 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 20 -输出:1 -解释:具有至少 1 位重复数字的正数(<= 20)只有 11。 -``` - -- 示例 2: - -```python -输入:n = 100 -输出:10 -解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100。 -``` - -#### 2.1.3 解题思路 - -##### 思路 1:动态规划 + 数位 DP - -正向求解在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数不太容易,我们可以反向思考,先求解出在 $[1, n]$ 范围内各位数字都不重复的正整数的个数 $ans$,然后 $n - ans$ 就是题目答案。 - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 - 4. 开始时没有填写数字。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), - 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 - 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 - 3. $isNum == True$ 表示 $pos$ 位选择了数字。 -6. 最后的方案数为 `n - dfs(0, 0, True, False)`,将其返回即可。 - -##### 思路 1:代码 - -```python -class Solution: - def numDupDigitsAtMostN(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - # d 不在选择的数字集合中,即之前没有选择过 d - if (state >> d) & 1 == 0: - ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) - return ans - - return n - dfs(0, 0, True, False) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$。 -- **空间复杂度**:$O(\log n \times 2^{10})$。 - -### 2.2 数字 1 的个数 - -#### 2.2.1 题目链接 - -- [233. 数字 1 的个数 - 力扣](https://leetcode.cn/problems/number-of-digit-one/) - -#### 2.2.2 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:计算所有小于等于 $n$ 的非负整数中数字 $1$ 出现的个数。 - -**说明**: - -- $0 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 13 -输出:6 -``` - -- 示例 2: - -```python -输入:n = 0 -输出:0 -``` - -#### 2.2.3 解题思路 - -##### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $1$ 出现的个数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始数字 $1$ 出现的个数为 $0$。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $1$ 出现的个数 $cnt$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 - 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 - 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX)`。 - 1. `cnt + (d == 1)` 表示之前数字 $1$ 出现的个数加上当前位为数字 $1$ 的个数。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 -6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 - -##### 思路 1:代码 - -```python -class Solution: - def countDigitOne(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # cnt: 之前数字 1 出现的个数。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - def dfs(pos, cnt, isLimit): - if pos == len(s): - return cnt - - ans = 0 - # 不需要考虑前导 0,则最小可选择数字为 0 - minX = 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX) - return ans - - return dfs(0, 0, True) -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 - -## 参考资料 - -- 【文章】[AcWing 1081. 度的数量【数位DP基本概念+数位DP记忆化搜索】](https://www.acwing.com/solution/content/66855/) -- 【视频】[数位 DP 通用模板【力扣周赛 306】LeetCode - 灵茶山艾府](https://www.bilibili.com/video/BV1rS4y1s721/) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md b/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md deleted file mode 100644 index f0a0386f..00000000 --- a/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md +++ /dev/null @@ -1,18 +0,0 @@ -### 数位 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 2376 | [统计特殊整数](https://leetcode.cn/problems/count-special-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2376.%20%E7%BB%9F%E8%AE%A1%E7%89%B9%E6%AE%8A%E6%95%B4%E6%95%B0.md) | 数学、动态规划 | 困难 | -| 0357 | [统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0357.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E4%BD%8D%E6%95%B0%E5%AD%97%E9%83%BD%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97%E4%B8%AA%E6%95%B0.md) | 数学、动态规划、回溯 | 中等 | -| 1012 | [至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1012.%20%E8%87%B3%E5%B0%91%E6%9C%89%201%20%E4%BD%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 困难 | -| 0902 | [最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0902.%20%E6%9C%80%E5%A4%A7%E4%B8%BA%20N%20%E7%9A%84%E6%95%B0%E5%AD%97%E7%BB%84%E5%90%88.md) | 数组、数学、字符串、二分查找、动态规划 | 困难 | -| 0788 | [旋转数字](https://leetcode.cn/problems/rotated-digits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0788.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E5%AD%97.md) | 数学、动态规划 | 中等 | -| 0600 | [不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0600.%20%E4%B8%8D%E5%90%AB%E8%BF%9E%E7%BB%AD1%E7%9A%84%E9%9D%9E%E8%B4%9F%E6%95%B4%E6%95%B0.md) | 动态规划 | 困难 | -| 0233 | [数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0233.%20%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | -| 2719 | [统计整数数目](https://leetcode.cn/problems/count-of-integers/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/2719.%20%E7%BB%9F%E8%AE%A1%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 数学、字符串、动态规划 | 困难 | -| 0248 | [中心对称数 III](https://leetcode.cn/problems/strobogrammatic-number-iii/) | | 递归、数组、字符串 | 困难 | -| 1088 | [易混淆数 II](https://leetcode.cn/problems/confusing-number-ii/) | | 数学、回溯 | 困难 | -| 1067 | [范围内的数字计数](https://leetcode.cn/problems/digit-count-in-range/) | | 数学、动态规划 | 困难 | -| 1742 | [盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1742.%20%E7%9B%92%E5%AD%90%E4%B8%AD%E5%B0%8F%E7%90%83%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E9%87%8F.md) | 哈希表、数学、计数 | 简单 | -| 面试题 17.06 | [2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/%E9%9D%A2%E8%AF%95%E9%A2%98%2017.06.%202%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0.md) | 递归、数学、动态规划 | 困难 | - diff --git a/Contents/10.Dynamic-Programming/09.Digit-DP/index.md b/Contents/10.Dynamic-Programming/09.Digit-DP/index.md deleted file mode 100644 index 0b745e85..00000000 --- a/Contents/10.Dynamic-Programming/09.Digit-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [数位 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) -- [数位 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md b/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md deleted file mode 100644 index b1165944..00000000 --- a/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md +++ /dev/null @@ -1,75 +0,0 @@ -## 1. 概率 DP 简介 - -> **概率 DP**:一类使用动态规划方法来求解概率与期望的问题,也可以分别叫做「概率 DP」、「期望 DP」。由于概率和期望具有线性性质,使得可以在概率和期望之间建立一定的递推关系,从而通过动态规划的方式来解决一些概率问题。 - -概率 DP 和期望 DP 的难点主要有两点: - -1. 状态转移方程的推导。 -2. 概率论知识。 - -其中第 $1$ 点和一般动态规划并无太大差别,而第 $2$ 点涉及到对概率论基础知识的掌握。其中,「概率 PD」对应了概率论知识中的「全概率公式」,「期望 DP」则对应了「全期望公式」。 - -我们来看看「概率 DP」「期望 DP」中所涉及到的概率论基础知识。 - -## 2. 概率论知识 - -### 2.1 样本空间、事件和概率 - -> **基本事件**:在概率论中,我们将一次随机实验中的某个可能结果称为「样本点」或者「基本事件」。 -> -> **样本空间**:所有可能的结果组成的集合,称为「样本空间」,标记为 $S$。 -> -> **随机事件**:样本空间 $S$ 的一个子集 $A$($A \subseteq S$),称为「随机事件」。 - -> **概率**:对于样本空间 $S$ 中的每一个随机事件 $A$,如果都存在一种时间到实数的映射函数 $P(A)$,满足: -> -> 1. $P(S) = 1$。 ->2. $0 \le P(A) \le 1$。 -> 3. 对于两个互斥事件,$P(A \cup B) = P(A) + P(B)$。 -> -> 则称 $P(A)$ 为随机事件 $A$ 的概率。 - -### 2.2 随机变量、数学期望 - -> **随机变量**:对于样本空间 $S$ 中的任意事件 $i$,都有唯一的实数 $X_i$ 与之对应,则称 $X = X_i$ 为样本空间 $S$ 上的随机变量。 - -常见的随机变量主要有「离散型随机变量」与「连续型随机变量」。「概率 DP」主要涉及到离散型随机变量。 - -> **数学期望**:如果随机变量 $X = X_i$ 的概率为 $P(X = X_i) = p_i$,则称 $E(x) = \sum p_ix_i$ 为随机变量 $X$ 的数学期望。 - -数学期望的一些性质: - -1. 线性函数性质,满足:$E(aX + bY) = a \times E(X) + b \times E(Y)$。 -2. 如果随机变量 $X$、$Y$ 相互独立,那么:$E(XY) = E(X)E(Y)$。 - -其中数学期望的线性函数性质是我们能够对数学期望进行地推求解的基本依据。 - -### 2.3 全概率公式、全期望公式 - -概率 DP 的理论基础主要是「全概率公式」。 - -> **全概率公式**:设 $B_1, B_2, B_3, …, B_n$ 是样本空间 $S$ 中互不相交的一系列事件,并且满足 $S = \cup_{j = 1}^n B_j$,那么对于任意事件 $A$,有: $P(A) = \sum_{j = 1}^{n} P(A \text{ | } B_j)P(B_j)$ - -「概率 DP」中一般常见的状态转移方程式为:$dp[i] = \sum_{j = 1}^{n} p[i][j] \times dp[j]$。 - -- 其中 $dp[i]$ 对应全概率公式中的 $P(A)$,$p[i][j]$ 对应了 $P(B_j)$,$dp[j]$ 则对应了 $P(A \text{ | } B_j)$。 - -与概率 DP 类似,期望 DP 的理论基础主要是「全期望公式」。 - -> **全期望公式**:设 $X$、$Y$ 为随机变量,$E(Y) = E(E(Y \text{ | } X)) = \sum_{j = 1}^n P(x_j) E(Y \text{ | } x_j)$。 - -「期望 DP」中一般常见的状态转移方程式为:$dp[i] = \sum_{j = 1}^{n} p[i][j] \times dp[j]$。 - -- 其中 $dp[i]$ 对应全概率公式中的 $E(Y)$,$p[i][j]$ 对应了 $P(x_j)$,$dp[j]$ 则对应了 $E(Y \text{ | } x_j)$。 - -## 3. 概率 DP 的应用 - -### 3.1 - -## 参考资料 - -- 【文章】[概率动态规划简介 - 动态规划图文学: 树形、图上、概率 & 博弈动态 - LeetBook](https://leetcode.cn/leetbook/read/dynamic-programming-3-plus/nmnp61/) -- 【文章】[期望动态规划简介 - 动态规划图文学: 树形、图上、概率 & 博弈动态 - LeetBook](https://leetcode.cn/leetbook/read/dynamic-programming-3-plus/nmwjt6/) -- 【文章】浅析竞赛中一类数学期望问题的解决方法 - 汤可因 -- 【文章】有关概率和期望问题的研究 - 鬲融 -- 【文章】信息学竞赛中概率问题求解初探 - 梅诗珂 \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md b/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md deleted file mode 100644 index 9e1e26ce..00000000 --- a/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md +++ /dev/null @@ -1,13 +0,0 @@ -### 概率 DP 题目 - -| 题号 | 标题 | 题解 | 标签 | 难度 | -| :------ | :------ | :------ | :------ | :------ | -| 0688 | [骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0688.%20%E9%AA%91%E5%A3%AB%E5%9C%A8%E6%A3%8B%E7%9B%98%E4%B8%8A%E7%9A%84%E6%A6%82%E7%8E%87.md) | 动态规划 | 中等 | -| 0808 | [分汤](https://leetcode.cn/problems/soup-servings/) | | 数学、动态规划、概率与统计 | 中等 | -| 0837 | [新 21 点](https://leetcode.cn/problems/new-21-game/) | | 数学、动态规划、滑动窗口、概率与统计 | 中等 | -| 1230 | [抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | | 数学、动态规划、概率与统计 | 中等 | -| 1467 | [两个盒子中球的颜色数相同的概率](https://leetcode.cn/problems/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls/) | | 数组、数学、动态规划、回溯、组合数学、概率与统计 | 困难 | -| 1227 | [飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1227.%20%E9%A3%9E%E6%9C%BA%E5%BA%A7%E4%BD%8D%E5%88%86%E9%85%8D%E6%A6%82%E7%8E%87.md) | 脑筋急转弯、数学、动态规划、概率与统计 | 中等 | -| 1377 | [T 秒后青蛙的位置](https://leetcode.cn/problems/frog-position-after-t-seconds/) | | 树、深度优先搜索、广度优先搜索、图 | 困难 | -| 剑指 Offer 60 | [n个骰子的点数](https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/) | | 数学、动态规划、概率与统计 | 中等 | - diff --git a/Contents/10.Dynamic-Programming/10.Probability-DP/index.md b/Contents/10.Dynamic-Programming/10.Probability-DP/index.md deleted file mode 100644 index c719197e..00000000 --- a/Contents/10.Dynamic-Programming/10.Probability-DP/index.md +++ /dev/null @@ -1,4 +0,0 @@ -## 本章内容 - -- [概率 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) -- [概率 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md b/Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md b/Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md b/Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md b/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md deleted file mode 100644 index a3b5f199..00000000 --- a/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md +++ /dev/null @@ -1,2 +0,0 @@ -### 动态规划优化题目 - diff --git a/Contents/10.Dynamic-Programming/11.DP-Optimization/index.md b/Contents/10.Dynamic-Programming/11.DP-Optimization/index.md deleted file mode 100644 index 3292fa8a..00000000 --- a/Contents/10.Dynamic-Programming/11.DP-Optimization/index.md +++ /dev/null @@ -1,6 +0,0 @@ -## 本章内容 - -- [单调栈 / 优先队列优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md) -- [斜率优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md) -- [四边形不等式优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md) -- [动态规划优化题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/index.md b/Contents/10.Dynamic-Programming/index.md deleted file mode 100644 index 86a96ac0..00000000 --- a/Contents/10.Dynamic-Programming/index.md +++ /dev/null @@ -1,63 +0,0 @@ -## 本章内容 - -### 动态规划基础 - -- [动态规划基础知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) -- [动态规划基础题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) - -### 记忆化搜索 - -- [记忆化搜索知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md) -- [记忆化搜索题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) - -### 线性 DP - -- [线性 DP 知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) -- [线性 DP 知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) -- [线性 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) - -### 背包问题 - -- [背包问题知识(一)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) -- [背包问题知识(二)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) -- [背包问题知识(三)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) -- [背包问题知识(四)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) -- [背包问题知识(五)](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) -- [背包问题题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) - -### 区间 DP - -- [区间 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) -- [区间 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) - -### 树形 DP - -- [树形 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) -- [树形 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) - -### 状态压缩 DP - -- [状态压缩 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md) -- [状态压缩 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) - -### 计数 DP - -- [计数 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) -- [计数 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) - -### 数位 DP - -- [数位 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) -- [数位 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) - -### 概率 DP - -- [概率 DP 知识](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) -- [概率 DP 题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) - -### 动态规划优化 - -- [单调栈 / 优先队列优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md) -- [斜率优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md) -- [四边形不等式优化](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md) -- [动态规划优化题目](https://github.com/itcharge/LeetCode-Py/blob/main/Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) \ No newline at end of file diff --git a/Contents/Others/Update-Time.md b/Contents/Others/Update-Time.md deleted file mode 100644 index 70e54f9d..00000000 --- a/Contents/Others/Update-Time.md +++ /dev/null @@ -1,141 +0,0 @@ -## 2023-09 - -- 2023-09-07 完成「滑动窗口」内容优化 -- 2023-09-06 完成「数组双指针」内容优化 -- 2023-09-05 完成「广度优先搜索」内容优化 -- 2023-09-05 完成「深度优先搜索」内容优化 - -## 2023-08 - -- 2023-08-31 完成「堆排序」内容优化 -- 2023-08-24 完成「数组基础知识」内容优化 -- 2023-08-22 完成「基数排序」内容优化 -- 2023-08-22 完成「桶排序」内容优化 -- 2023-08-22 完成「计数排序」内容优化 -- 2023-08-18 完成「快速排序」内容优化 -- 2023-08-17 完成「归并排序」内容优化 -- 2023-08-16 完成「希尔排序」内容优化 -- 2023-08-16 完成「插入排序」内容优化 -- 2023-08-16 完成「选择排序」内容优化 -- 2023-08-16 完成「冒泡排序」内容优化 -- 2023-08-15 完成「序言」内容优化 - -## 2023-07 - -- 2023-07-17 完成「树形 DP」相关内容 - -## 2023-06 - -- 2023-06-29 完成「数位 DP」相关内容 -- 2023-06-02 完成「状态压缩 DP」相关内容 - -## 2023-05 - -- 2023-05-31 完成「计数类 DP」相关内容 -- 2023-05-08 完成「图的拓扑排序知识」相关内容 - -## 2023-04 - -- 2023-04-07 完成「区间 DP」相关内容 - -## 2023-03 - -- 2023-03-29 完成「背包问题知识(五)」相关内容 -- 2023-03-29 完成「背包问题知识(四)」相关内容 -- 2023-03-22 完成「背包问题知识(三)」相关内容 -- 2023-03-21 完成「背包问题知识(二)」相关内容 -- 2023-03-21 完成「背包问题知识(一)」相关内容 -- 2023-03-14 完成「线性 DP 知识(二)」相关内容 -- 2023-03-14 完成「线性 DP 知识(一)」相关内容 -- 2023-03-08 完成「位运算知识」相关内容 -- 2023-03-08 完成「动态规划基础知识(重写版)」相关内容 -- 2023-03-06 完成「记忆化搜索」相关内容 - -## 2022-07 - -- 2022-07-21 完成「动态规划基础知识」相关内容 - -## 2022-06 - -- 2022-06-13 完成「LeetCode 面试最常考 200 题」相关内容 -- 2022-06-10 完成「LeetCode 面试最常考 100 题」相关内容 - -## 2022-05 - -- 2022-05-11 完成「贪心算法知识」相关内容 -- 2022-05-02 完成「并查集知识」相关内容 - -## 2022-04 - -- 2022-04-26 完成「回溯算法知识」相关内容 -- 2022-04-14 完成「分治算法知识」相关内容 -- 2022-04-08 完成「递归算法知识」相关内容 - -## 2022-03 - -- 2022-03-29 完成「枚举算法知识」相关内容 -- 2022-03-18 完成「图的存储结构和问题应用」相关内容 -- 2022-03-13 完成「图的定义和分类」相关内容 -- 2022-03-03 完成「线段树知识」相关内容 - -## 2022-02 - -- 2022-02-28 完成「二叉搜索树」相关内容 -- 2022-02-23 完成「二叉树的还原」相关内容 -- 2022-02-22 完成「二叉树的遍历」相关内容 -- 2022-02-21 完成「树与二叉树基础知识」相关内容 -- 2022-02-05 完成「KMP 算法」相关内容 - -## 2022-01 - -- 2022-01-28 完成「Sunday 算法」相关内容 -- 2022-01-28 完成「Horspool 算法」相关内容 -- 2022-01-27 完成「BM 算法」相关内容 -- 2022-01-21 完成「RK 算法」相关内容 -- 2022-01-20 完成「BK 算法」相关内容 -- 2022-01-19 完成「字符串基础知识」相关内容 -- 2022-01-15 完成「哈希表基础知识」相关内容 -- 2022-01-09 完成「优先队列」相关内容 -- 2022-01-06 完成「单调栈」相关内容 - -## 2022-12 - -- 2022-12-21 完成「广度优先搜索」相关内容 -- 2022-12-14 完成「深度优先搜索」相关内容 -- 2022-12-12 完成「链表双指针」相关内容 -- 2022-12-10 完成「链表排序」相关内容 -- 2022-12-04 完成「队列基础知识」相关内容 -- 2022-12-04 完成「堆栈基础知识」相关内容 - -## 2021-11 - -- 2021-11-08 完成「滑动窗口」相关内容 -- 2021-11-08 完成「双指针」相关内容 -- 2021-11-04 完成「二分查找」相关内容 - -## 2021-10 - -- 2021-10-20 完成「基数排序」相关内容 -- 2021-10-20 完成「桶排序」相关内容 -- 2021-10-20 完成「计数排序」相关内容 -- 2021-10-19 完成「堆排序」相关内容 -- 2021-10-19 完成「快速排序」相关内容 -- 2021-10-19 完成「归并排序」相关内容 -- 2021-10-19 完成「希尔排序」相关内容 -- 2021-10-19 完成「插入排序」相关内容 -- 2021-10-19 完成「选择排序」相关内容 -- 2021-10-19 完成「冒牌排序」相关内容 - -## 2021-09 - -- 2021-09-17 完成「数组基础知识」相关内容 -- 2021-09-09 完成「算法复杂度」相关内容 - -## 2021-07 - -- 2021-07-05 完成「数据结构与算法」相关内容 - -## 2021-01 - -- 2021-01-26 完成「LeetCode 刷题顺序和技巧」相关内容 -- 2021-01-22 开始建立仓库 \ No newline at end of file diff --git a/Contents/index.md b/Contents/index.md deleted file mode 100644 index 9ed2f8a2..00000000 --- a/Contents/index.md +++ /dev/null @@ -1,218 +0,0 @@ -# 算法通关手册(LeetCode) - -# 内容章节 - -## 00. 绪论 - -- [算法与数据结构](./00.Introduction/01.Data-Structures-Algorithms.md) -- [算法复杂度](./00.Introduction/02.Algorithm-Complexity.md) -- [LeetCode 入门与攻略](./00.Introduction/03.LeetCode-Guide.md) -- [LeetCode 题解(字典序排序,700+ 道题解)](./00.Introduction/04.Solutions-List.md) -- [LeetCode 题解(按分类排序,推荐刷题列表 ★★★)](./00.Introduction/05.Categories-List.md) -- [LeetCode 面试最常考 100 题(按分类排序)](./00.Introduction/06.Interview-100-List.md) -- [LeetCode 面试最常考 200 题(按分类排序)](./00.Introduction/07.Interview-200-List.md) - -## 01. 数组 - -- 数组基础知识 - - [数组基础知识](./01.Array/01.Array-Basic/01.Array-Basic.md) - - [数组基础题目](./01.Array/01.Array-Basic/02.Array-Basic-List.md) -- 数组排序算法 - - [冒泡排序](./01.Array/02.Array-Sort/01.Array-Bubble-Sort.md) - - [选择排序](./01.Array/02.Array-Sort/02.Array-Selection-Sort.md) - - [插入排序](./01.Array/02.Array-Sort/03.Array-Insertion-Sort.md) - - [希尔排序](./01.Array/02.Array-Sort/04.Array-Shell-Sort.md) - - [归并排序](./01.Array/02.Array-Sort/05.Array-Merge-Sort.md) - - [快速排序](./01.Array/02.Array-Sort/06.Array-Quick-Sort.md) - - [堆排序](./01.Array/02.Array-Sort/07.Array-Heap-Sort.md) - - [计数排序](./01.Array/02.Array-Sort/08.Array-Counting-Sort.md) - - [桶排序](./01.Array/02.Array-Sort/09.Array-Bucket-Sort.md) - - [基数排序](./01.Array/02.Array-Sort/10.Array-Radix-Sort.md) - - [数组排序题目](./01.Array/02.Array-Sort/11.Array-Sort-List.md) -- 二分查找 - - [二分查找知识(一)](./01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md) - - [二分查找知识(二)](./01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md) - - [二分查找题目](./01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) -- 数组双指针 - - [数组双指针知识](./01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md) - - [数组双指针题目](./01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) -- 数组滑动窗口 - - [数组滑动窗口知识](./01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md) - - [数组滑动窗口题目](./01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -## 02. 链表 - -- 链表基础知识 - - [链表基础知识](./02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md) - - [链表经典题目](./02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) -- 链表排序 - - [链表排序知识](./02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md) - - [链表排序题目](./02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) -- 链表双指针 - - [链表双指针知识](./02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md) - - [链表双指针题目](./02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) - -## 03. 堆栈 - -- 堆栈基础知识 - - [堆栈基础知识](./03.Stack/01.Stack-Basic/01.Stack-Basic.md) - - [堆栈基础题目](./03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) -- 单调栈 - - [单调栈知识](./03.Stack/02.Monotone-Stack/01.Monotone-Stack.md) - - [单调栈题目](./03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -## 04. 队列 - -- 队列基础知识 - - [队列基础知识](./04.Queue/01.Queue-Basic/01.Queue-Basic.md) - - [队列基础题目](./04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) -- 优先队列 - - [优先队列知识](./04.Queue/02.Priority-Queue/01.Priority-Queue.md) - - [优先队列题目](./04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -## 05. 哈希表 - -- [哈希表知识](./05.Hash-Table/01.Hash-Table.md) -- [哈希表题目](./05.Hash-Table/02.Hash-Table-List.md) - -## 06. 字符串 - -- 字符串基础知识 - - [字符串基础知识](./06.String/01.String-Basic/01.String-Basic.md) - - [字符串经典题目](./06.String/01.String-Basic/02.String-Basic-List.md) -- 单模式串匹配 - - [Brute Force 算法](./06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md) - - [Rabin Karp 算法](./06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md) - - [KMP 算法](./06.String/02.String-Single-Pattern-Matching/03.String-KMP.md) - - [Boyer Moore 算法](./06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md) - - [Horspool 算法](./06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md) - - [Sunday 算法](./06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md) - - [单模式串匹配题目](./06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) -- 多模式串匹配 - - [字典树知识](./06.String/03.String-Multi-Pattern-Matching/01.Trie.md) - - [字典树题目](./06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) - - [AC 自动机知识](./06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md) - - [AC 自动机题目](./06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md) - - [后缀数组知识](./06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md) - - [后缀数组题目](./06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md) - - -## 07. 树 - -- 二叉树 - - [树与二叉树基础知识](./07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md) - - [二叉树的遍历知识](./07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md) - - [二叉树的遍历题目](./07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - - [二叉树的还原知识](./07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md) - - [二叉树的还原题目](./07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) -- 二叉搜索树 - - [二叉搜索树知识](./07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md) - - [二叉搜索树题目](./07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) -- 线段树 - - [线段树知识](./07.Tree/03.Segment-Tree/01.Segment-Tree.md) - - [线段树题目](./07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) -- 树状数组 - - [树状数组知识](./07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md) - - [树状数组题目](./07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) -- 并查集 - - [并查集知识](./07.Tree/05.Union-Find/01.Union-Find.md) - - [并查集题目](./07.Tree/05.Union-Find/02.Union-Find-List.md) - -## 08. 图论 - -- 图的基础知识 - - [图的定义和分类](./08.Graph/01.Graph-Basic/01.Graph-Basic.md) - - [图的存储结构和问题应用](./08.Graph/01.Graph-Basic/02.Graph-Structure.md) -- 图的遍历 - - [图的深度优先搜索知识](./08.Graph/02.Graph-Traversal/01.Graph-DFS.md) - - [图的深度优先搜索题目](./08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - - [图的广度优先搜索知识](./08.Graph/02.Graph-Traversal/03.Graph-BFS.md) - - [图的广度优先搜索题目](./08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - - [图的拓扑排序知识](./08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md) - - [图的拓扑排序题目](./08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) -- 图的生成树 - - [图的最小生成树知识](./08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md) - - [图的最小生成树题目](./08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) -- 最短路径 - - [单源最短路径知识(一)](./08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md) - - [单源最短路径知识(二)](./08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md) - - [单源最短路径题目](./08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) - - [多源最短路径知识](./08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md) - - [多源最短路径题目](./08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) - - [次短路径知识](./08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md) - - [次短路径题目](./08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) - - [差分约束系统知识](./08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md) - - [差分约束系统题目](./08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) -- 二分图 - - [二分图基础知识](./08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md) - - [二分图基础题目](./08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) - - [二分图最大匹配知识](./08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md) - - [匈牙利算法](./08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md) - - [Hopcroft-Karp 算法](./08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md) - - [二分图最大匹配题目](./08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) - -## 09. 基础算法 - -- 枚举算法 - - [枚举算法知识](./09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md) - - [枚举算法题目](./09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) -- 递归算法 - - [递归算法知识](./09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md) - - [递归算法题目](./09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) -- 分治算法 - - [分治算法知识](./09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md) - - [分治算法题目](./09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) -- 回溯算法 - - [回溯算法知识](./09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md) - - [回溯算法题目](./09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) -- 贪心算法 - - [贪心算法知识](./09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md) - - [贪心算法题目](./09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) -- 位运算 - - [位运算知识](./09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md) - - [位运算题目](./09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) - -## 10. 动态规划 - -- 动态规划基础 - - [动态规划基础知识](./10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) - - [动态规划基础题目](./10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) -- 记忆化搜索 - - [记忆化搜索知识](./10.Dynamic-Programming/02.Memoization/01.Memoization.md) - - [记忆化搜索题目](./10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) -- 线性 DP - - [线性 DP 知识(一)](./10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) - - [线性 DP 知识(二)](./10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) - - [线性 DP 题目](./10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) -- 背包问题 - - [背包问题知识(一)](./10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) - - [背包问题知识(二)](./10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) - - [背包问题知识(三)](./10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) - - [背包问题知识(四)](./10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) - - [背包问题知识(五)](./10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) - - [背包问题题目](./10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) -- 区间 DP - - [区间 DP 知识](./10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) - - [区间 DP 题目](./10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) -- 树形 DP - - [树形 DP 知识](./10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) - - [树形 DP 题目](./10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) -- 状态压缩 DP - - [状态压缩 DP 知识](./10.Dynamic-Programming/07.State-DP/01.State-DP.md) - - [状态压缩 DP 题目](./10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) -- 计数 DP - - [计数 DP 知识](./10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) - - [计数 DP 题目](./10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) -- 数位 DP - - [数位 DP 知识](./10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) - - [数位 DP 题目](./10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) -- 概率 DP - - [概率 DP 知识](./10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) - - [概率 DP 题目](./10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) -- 动态规划优化 - - [单调栈 / 优先队列优化](./10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md) - - [斜率优化](./10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md) - - [四边形不等式优化](./10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md) - - [动态规划优化题目](./10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) - -## 11. 附加内容 diff --git a/README.md b/README.md index c50b2ac7..752473e7 100644 --- a/README.md +++ b/README.md @@ -1,258 +1,67 @@ -# 算法通关手册(LeetCode) +## 1. 本书简介 -## 项目简介 +本书不仅仅只是一本算法题解书,更是一本算法与数据结构基础知识的讲解书。 -- **「算法与数据结构」** 基础知识的讲解教程,「LeetCode」800+ 道题目的详细解析。本项目易于理解,没有大跨度的思维跳跃,项目中使用部分图示、例子来帮助理解。 +- 超详细的 **「算法与数据结构」** 基础讲解教程,**「LeetCode 800+ 道」** 经典题目详细解析。 +- 本项目易于理解,没有大跨度的思维跳跃,项目中使用大量图示、例子来帮助理解。 +- 本项目先从基础的数据结构和算法开始讲解,再针对不同分类的数据结构和算法,进行具体题目的讲解分析。让读者可以通过「算法基础理论学习」和「编程实战学习」相结合的方式,彻底的掌握算法知识。 +- 本项目从各大知名互联网公司面试算法题中整理汇总了 **「LeetCode 200 道高频面试题」**,帮助面试者更有针对性的准备面试。 -- 本教程先从基础的数据结构和算法开始讲解,再针对不同分类的数据结构和算法,进行具体题目的讲解分析。让读者可以通过「算法基础理论学习」和「编程实战学习」相结合的方式,彻底的掌握算法知识。 +### 1.1 源码地址 -- 本教程采用 Python 作为编程语言,要求学习者已有基本 Python 程序设计的知识与经验。 +本书内容及代码都放在 [Github repo](https://github.com/itcharge/AlgoNote) 中,欢迎在下方项目中 **「Star ⭐️ 」** 和 **「Fork」**,这是对我最大的鼓励和支持。 -## 项目地址 +- Github 地址:[https://github.com/itcharge/AlgoNote](https://github.com/itcharge/AlgoNote) -欢迎右上角 **「Star ⭐️ 」** 和 **「Fork」**,这是对我最大的鼓励和支持。 +### 1.2 目标读者 -- GitHub 地址:[https://github.com/itcharge/LeetCode-Py](https://github.com/itcharge/LeetCode-Py) +- 拥有 Python 编程基础或其他编程语言基础的编程爱好者 +- 对 LeetCode 刷题感兴趣或准备算法面试的面试人员 +- 对算法感兴趣的计算机专业学生或程序员 +- 想要提升编程思维和问题解决能力的开发者 -支持黑暗模式的在线电子书《算法通关手册》。 +### 1.3 内容结构 -- 电子书地址:[https://algo.itcharge.cn](https://algo.itcharge.cn) +本书采用算法与数据结构相结合的方法,把内容分为如下几个主要部分: -![](./Assets/Images/algo-book-light.png) +- **0. 序言**:介绍数据结构与算法的基础知识、算法复杂度、LeetCode 的入门和攻略,为后面的学习打好基础。 +- **1. 数组**:讲解数组的基本概念、数组的基本操作。 +- **2. 链表**:讲解链表的基本概念、操作和应用,包括单链表、双向链表、循环链表等。 +- **3. 栈、队列、哈希表**:详细介绍栈、队列、哈希表这三种数据结构,包括它们的基本概念、实现方式、应用场景以及相关的经典算法题。 +- **4. 字符串**:讲解字符串的基本操作、单字符串匹配算法、多字符串匹配算法,以及字符串相关的经典算法题。 +- **5. 树结构**:介绍树的基本概念、二叉树、二叉搜索树、线段树、树状数组、并查集等数据结构。 +- **6. 图论**:讲解图的基本概念、表示方法、遍历算法和经典应用。 +- **7. 基础算法**:介绍基本的算法思想。包括枚举、递归、分治、回溯、贪心以及位运算。 +- **8. 动态规划**:介绍动态规划的基础知识、各种动态规划题型的解法。 +- **9. 附加内容**:作为全书的扩展模块。 +- **10. 题目解析**:讲解 LeetCode 上刷过的所有题目,可按照对应题号进行检索和学习。 -![](./Assets/Images/algo-book-dark.png) +### 1.4 使用说明 -## 关于作者 +- 本电子书的左侧为所有章节目录导航,可直接点击对应章节跳转阅读。 +- 本电子书左上角有搜索栏,可以帮你迅速找到想看的章节和题解文章。 +- 本电子书每页都接入了 giscus 评论系统,可在每页下方的评论框进行评论(需使用 GitHub 账号登录)。 +- 建议按照章节顺序学习,循序渐进地掌握各个知识点。 +- 每章末尾都配有练习题,建议及时完成以巩固所学知识。 -我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 +## 2. 相关说明 -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 800+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 - -在公众号 **「程序员充电站」** 里回复 "**算法打卡**",拉你进 LeetCode 算法打卡计划群一起组队打卡。 - -- 进群暗号:**算法打卡** -- 进群要求:少闲聊、多分享、改备注。 - -![](./Assets/Images/itcharge-qr-code.png) - -## 版权说明 - -- 本教程采用 [知识署名—非商业性使用—禁止演绎(BY-NC-ND)4.0 协议国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。 -- 本教程题解中的所有题目版权均归 [LeetCode](https://leetcode.com/) 和 [力扣中国](https://leetcode.cn/) 所有。 -# 内容章节 - -## 00. 绪论 - -- [算法与数据结构](./Contents/00.Introduction/01.Data-Structures-Algorithms.md) -- [算法复杂度](./Contents/00.Introduction/02.Algorithm-Complexity.md) -- [LeetCode 入门与攻略](./Contents/00.Introduction/03.LeetCode-Guide.md) -- [LeetCode 题解(字典序排序,700+ 道题解)](./Contents/00.Introduction/04.Solutions-List.md) -- [LeetCode 题解(按分类排序,推荐刷题列表 ★★★)](./Contents/00.Introduction/05.Categories-List.md) -- [LeetCode 面试最常考 100 题(按分类排序)](./Contents/00.Introduction/06.Interview-100-List.md) -- [LeetCode 面试最常考 200 题(按分类排序)](./Contents/00.Introduction/07.Interview-200-List.md) - -## 01. 数组 - -- 数组基础知识 - - [数组基础知识](./Contents/01.Array/01.Array-Basic/01.Array-Basic.md) - - [数组基础题目](./Contents/01.Array/01.Array-Basic/02.Array-Basic-List.md) -- 数组排序算法 - - [冒泡排序](./Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md) - - [选择排序](./Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md) - - [插入排序](./Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md) - - [希尔排序](./Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md) - - [归并排序](./Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md) - - [快速排序](./Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md) - - [堆排序](./Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md) - - [计数排序](./Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md) - - [桶排序](./Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md) - - [基数排序](./Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md) - - [数组排序题目](./Contents/01.Array/02.Array-Sort/11.Array-Sort-List.md) -- 二分查找 - - [二分查找知识(一)](./Contents/01.Array/03.Array-Binary-Search/01.Array-Binary-Search-01.md) - - [二分查找知识(二)](./Contents/01.Array/03.Array-Binary-Search/02.Array-Binary-Search-02.md) - - [二分查找题目](./Contents/01.Array/03.Array-Binary-Search/03.Array-Binary-Search-List.md) -- 数组双指针 - - [数组双指针知识](./Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md) - - [数组双指针题目](./Contents/01.Array/04.Array-Two-Pointers/02.Array-Two-Pointers-List.md) -- 数组滑动窗口 - - [数组滑动窗口知识](./Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md) - - [数组滑动窗口题目](./Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md) - -## 02. 链表 +### 2.1 关于作者 -- 链表基础知识 - - [链表基础知识](./Contents/02.Linked-List/01.Linked-List-Basic/01.Linked-List-Basic.md) - - [链表经典题目](./Contents/02.Linked-List/01.Linked-List-Basic/02.Linked-List-Basic-List.md) -- 链表排序 - - [链表排序知识](./Contents/02.Linked-List/02.Linked-List-Sort/01.Linked-List-Sort.md) - - [链表排序题目](./Contents/02.Linked-List/02.Linked-List-Sort/02.Linked-List-Sort-List.md) -- 链表双指针 - - [链表双指针知识](./Contents/02.Linked-List/03.Linked-List-Two-Pointers/01.Linked-List-Two-Pointers.md) - - [链表双指针题目](./Contents/02.Linked-List/03.Linked-List-Two-Pointers/02.Linked-List-Two-Pointers-List.md) +我是一名 iOS / macOS 的开发程序员,研究生毕业于北航软件学院。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 -## 03. 堆栈 - -- 堆栈基础知识 - - [堆栈基础知识](./Contents/03.Stack/01.Stack-Basic/01.Stack-Basic.md) - - [堆栈基础题目](./Contents/03.Stack/01.Stack-Basic/02.Stack-Basic-List.md) -- 单调栈 - - [单调栈知识](./Contents/03.Stack/02.Monotone-Stack/01.Monotone-Stack.md) - - [单调栈题目](./Contents/03.Stack/02.Monotone-Stack/02.Monotone-Stack-List.md) - -## 04. 队列 - -- 队列基础知识 - - [队列基础知识](./Contents/04.Queue/01.Queue-Basic/01.Queue-Basic.md) - - [队列基础题目](./Contents/04.Queue/01.Queue-Basic/02.Queue-Basic-List.md) -- 优先队列 - - [优先队列知识](./Contents/04.Queue/02.Priority-Queue/01.Priority-Queue.md) - - [优先队列题目](./Contents/04.Queue/02.Priority-Queue/02.Priority-Queue-List.md) - -## 05. 哈希表 - -- [哈希表知识](./Contents/05.Hash-Table/01.Hash-Table.md) -- [哈希表题目](./Contents/05.Hash-Table/02.Hash-Table-List.md) - -## 06. 字符串 - -- 字符串基础知识 - - [字符串基础知识](./Contents/06.String/01.String-Basic/01.String-Basic.md) - - [字符串经典题目](./Contents/06.String/01.String-Basic/02.String-Basic-List.md) -- 单模式串匹配 - - [Brute Force 算法](./Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md) - - [Rabin Karp 算法](./Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md) - - [KMP 算法](./Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md) - - [Boyer Moore 算法](./Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md) - - [Horspool 算法](./Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md) - - [Sunday 算法](./Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md) - - [单模式串匹配题目](./Contents/06.String/02.String-Single-Pattern-Matching/07.String-Single-Pattern-Matching-List.md) -- 多模式串匹配 - - [字典树知识](./Contents/06.String/03.String-Multi-Pattern-Matching/01.Trie.md) - - [字典树题目](./Contents/06.String/03.String-Multi-Pattern-Matching/02.Trie-List.md) - - [AC 自动机知识](./Contents/06.String/03.String-Multi-Pattern-Matching/03.AC-Automaton.md) - - [AC 自动机题目](./Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md) - - [后缀数组知识](./Contents/06.String/03.String-Multi-Pattern-Matching/05.Suffix-Array.md) - - [后缀数组题目](./Contents/06.String/03.String-Multi-Pattern-Matching/06.Suffix-Array-List.md) - - -## 07. 树 - -- 二叉树 - - [树与二叉树基础知识](./Contents/07.Tree/01.Binary-Tree/01.Binary-Tree-Basic.md) - - [二叉树的遍历知识](./Contents/07.Tree/01.Binary-Tree/02.Binary-Tree-Traverse.md) - - [二叉树的遍历题目](./Contents/07.Tree/01.Binary-Tree/03.Binary-Tree-Traverse-List.md) - - [二叉树的还原知识](./Contents/07.Tree/01.Binary-Tree/04.Binary-Tree-Reduction.md) - - [二叉树的还原题目](./Contents/07.Tree/01.Binary-Tree/05.Binary-Tree-Reduction-List.md) -- 二叉搜索树 - - [二叉搜索树知识](./Contents/07.Tree/02.Binary-Search-Tree/01.Binary-Search-Tree.md) - - [二叉搜索树题目](./Contents/07.Tree/02.Binary-Search-Tree/02.Binary-Search-Tree-List.md) -- 线段树 - - [线段树知识](./Contents/07.Tree/03.Segment-Tree/01.Segment-Tree.md) - - [线段树题目](./Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md) -- 树状数组 - - [树状数组知识](./Contents/07.Tree/04.Binary-Indexed-Tree/01.Binary-Indexed-Tree.md) - - [树状数组题目](./Contents/07.Tree/04.Binary-Indexed-Tree/02.Binary-Indexed-Tree-List.md) -- 并查集 - - [并查集知识](./Contents/07.Tree/05.Union-Find/01.Union-Find.md) - - [并查集题目](./Contents/07.Tree/05.Union-Find/02.Union-Find-List.md) +我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 800+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 -## 08. 图论 +### 2.2 互助与勘误 -- 图的基础知识 - - [图的定义和分类](./Contents/08.Graph/01.Graph-Basic/01.Graph-Basic.md) - - [图的存储结构和问题应用](./Contents/08.Graph/01.Graph-Basic/02.Graph-Structure.md) -- 图的遍历 - - [图的深度优先搜索知识](./Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md) - - [图的深度优先搜索题目](./Contents/08.Graph/02.Graph-Traversal/02.Graph-DFS-List.md) - - [图的广度优先搜索知识](./Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md) - - [图的广度优先搜索题目](./Contents/08.Graph/02.Graph-Traversal/04.Graph-BFS-List.md) - - [图的拓扑排序知识](./Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md) - - [图的拓扑排序题目](./Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md) -- 图的生成树 - - [图的最小生成树知识](./Contents/08.Graph/03.Graph-Spanning-Tree/01.Graph-Minimum-Spanning-Tree.md) - - [图的最小生成树题目](./Contents/08.Graph/03.Graph-Spanning-Tree/02.Graph-Minimum-Spanning-Tree-List.md) -- 最短路径 - - [单源最短路径知识(一)](./Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md) - - [单源最短路径知识(二)](./Contents/08.Graph/04.Graph-Shortest-Path/02.Graph-Single-Source-Shortest-Path-02.md) - - [单源最短路径题目](./Contents/08.Graph/04.Graph-Shortest-Path/03.Graph-Single-Source-Shortest-Path-List.md) - - [多源最短路径知识](./Contents/08.Graph/04.Graph-Shortest-Path/04.Graph-Multi-Source-Shortest-Path.md) - - [多源最短路径题目](./Contents/08.Graph/04.Graph-Shortest-Path/05.Graph-Multi-Source-Shortest-Path-List.md) - - [次短路径知识](./Contents/08.Graph/04.Graph-Shortest-Path/06.Graph-The-Second-Shortest-Path.md) - - [次短路径题目](./Contents/08.Graph/04.Graph-Shortest-Path/07.Graph-The-Second-Shortest-Path-List.md) - - [差分约束系统知识](./Contents/08.Graph/04.Graph-Shortest-Path/08.Graph-System-Of-Difference-Constraints.md) - - [差分约束系统题目](./Contents/08.Graph/04.Graph-Shortest-Path/09.Graph-System-Of-Difference-Constraints-List.md) -- 二分图 - - [二分图基础知识](./Contents/08.Graph/05.Graph-Bipartite/01.Graph-Bipartite-Basic.md) - - [二分图基础题目](./Contents/08.Graph/05.Graph-Bipartite/02.Graph-Bipartite-Basic-List.md) - - [二分图最大匹配知识](./Contents/08.Graph/05.Graph-Bipartite/03.Graph-Bipartite-Matching.md) - - [匈牙利算法](./Contents/08.Graph/05.Graph-Bipartite/04.Graph-Hungarian-Algorithm.md) - - [Hopcroft-Karp 算法](./Contents/08.Graph/05.Graph-Bipartite/05.Graph-Hopcroft-Karp.md) - - [二分图最大匹配题目](./Contents/08.Graph/05.Graph-Bipartite/06.Graph-Bipartite-Matching-List.md) +限于本人的水平和经验,书中一定不乏纰漏和谬误之处。恳切希望读者给予批评指正。这将有利于我改进和提高,以帮助更多的读者。如果您对本书有任何评论和建议,或者遇到问题需要帮助,可在每页评论区留言,或者致信作者邮箱 [i@itcharge.cn](mailto:i@itcharge.cn),我将不胜感激。 -## 09. 基础算法 +### 2.3 版权说明 -- 枚举算法 - - [枚举算法知识](./Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md) - - [枚举算法题目](./Contents/09.Algorithm-Base/01.Enumeration-Algorithm/02.Enumeration-Algorithm-List.md) -- 递归算法 - - [递归算法知识](./Contents/09.Algorithm-Base/02.Recursive-Algorithm/01.Recursive-Algorithm.md) - - [递归算法题目](./Contents/09.Algorithm-Base/02.Recursive-Algorithm/02.Recursive-Algorithm-List.md) -- 分治算法 - - [分治算法知识](./Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/01.Divide-And-Conquer-Algorithm.md) - - [分治算法题目](./Contents/09.Algorithm-Base/03.Divide-And-Conquer-Algorithm/02.Divide-And-Conquer-Algorithm-List.md) -- 回溯算法 - - [回溯算法知识](./Contents/09.Algorithm-Base/04.Backtracking-Algorithm/01.Backtracking-Algorithm.md) - - [回溯算法题目](./Contents/09.Algorithm-Base/04.Backtracking-Algorithm/02.Backtracking-Algorithm-List.md) -- 贪心算法 - - [贪心算法知识](./Contents/09.Algorithm-Base/05.Greedy-Algorithm/01.Greedy-Algorithm.md) - - [贪心算法题目](./Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) -- 位运算 - - [位运算知识](./Contents/09.Algorithm-Base/06.Bit-Operation/01.Bit-Operation.md) - - [位运算题目](./Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) +- 本书采用 [知识署名—非商业性使用—禁止演绎(BY-NC-ND)4.0 协议国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。 +- 本书题解中的所有题目版权均归 [LeetCode](https://leetcode.com/) 和 [力扣中国](https://leetcode.cn/) 所有。 -## 10. 动态规划 +### 2.4 致谢 -- 动态规划基础 - - [动态规划基础知识](./Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) - - [动态规划基础题目](./Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/02.Dynamic-Programming-Basic-List.md) -- 记忆化搜索 - - [记忆化搜索知识](./Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md) - - [记忆化搜索题目](./Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md) -- 线性 DP - - [线性 DP 知识(一)](./Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) - - [线性 DP 知识(二)](./Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) - - [线性 DP 题目](./Contents/10.Dynamic-Programming/03.Linear-DP/03.Linear-DP-List.md) -- 背包问题 - - [背包问题知识(一)](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) - - [背包问题知识(二)](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) - - [背包问题知识(三)](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) - - [背包问题知识(四)](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) - - [背包问题知识(五)](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) - - [背包问题题目](./Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md) -- 区间 DP - - [区间 DP 知识](./Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) - - [区间 DP 题目](./Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) -- 树形 DP - - [树形 DP 知识](./Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) - - [树形 DP 题目](./Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) -- 状态压缩 DP - - [状态压缩 DP 知识](./Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md) - - [状态压缩 DP 题目](./Contents/10.Dynamic-Programming/07.State-DP/02.State-DP-List.md) -- 计数 DP - - [计数 DP 知识](./Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) - - [计数 DP 题目](./Contents/10.Dynamic-Programming/08.Counting-DP/02.Counting-DP-List.md) -- 数位 DP - - [数位 DP 知识](./Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) - - [数位 DP 题目](./Contents/10.Dynamic-Programming/09.Digit-DP/02.Digit-DP-List.md) -- 概率 DP - - [概率 DP 知识](./Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) - - [概率 DP 题目](./Contents/10.Dynamic-Programming/10.Probability-DP/02.Probability-DP-List.md) -- 动态规划优化 - - [单调栈 / 优先队列优化](./Contents/10.Dynamic-Programming/11.DP-Optimization/01.Monotone-Stack-Queue-Optimization.md) - - [斜率优化](./Contents/10.Dynamic-Programming/11.DP-Optimization/02.Slope-Optimization.md) - - [四边形不等式优化](./Contents/10.Dynamic-Programming/11.DP-Optimization/03.Quadrangle-Optimization.md) - - [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) +在本书构思与写作阶段,很多朋友给我提出了有益的意见和建议。这些意见和建议令我受益匪浅。感谢在本书著作准备过程中,帮助过我的朋友,以及一起陪我刷题打卡的朋友,还有提供宝贵意见的读者。谢谢诸位。 -## 11. 附加内容 -## [12. LeetCode 题解(已完成 823 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file diff --git "a/Solutions/0001. \344\270\244\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0001. \344\270\244\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index 2823e04d..00000000 --- "a/Solutions/0001. \344\270\244\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0001. 两数之和](https://leetcode.cn/problems/two-sum/) - -- 标签:数组、哈希表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个整数目标值 $target$。 - -**要求**:在该数组中找出和为 $target$ 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。 - -**说明**: - -- $2 \le nums.length \le 10^4$。 -- $-10^9 \le nums[i] \le 10^9$。 -- $-10^9 \le target \le 10^9$。 -- 只会存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,7,11,15], target = 9 -输出:[0,1] -解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 -``` - -- 示例 2: - -```python -输入:nums = [3,2,4], target = 6 -输出:[1,2] -``` - -## 解题思路 - -### 思路 1:枚举算法 - -1. 使用两重循环枚举数组中每一个数 $nums[i]$、$nums[j]$,判断所有的 $nums[i] + nums[j]$ 是否等于 $target$。 -2. 如果出现 $nums[i] + nums[j] == target$,则说明数组中存在和为 $target$ 的两个整数,将两个整数的下标 $i$、$$j$ 输出即可。 - -### 思路 1:代码 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - for i in range(len(nums)): - for j in range(i + 1, len(nums)): - if i != j and nums[i] + nums[j] == target: - return [i, j] - return [] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 是数组 $nums$ 的元素数量。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:哈希表 - -哈希表中键值对信息为 $target-nums[i] :i,其中 $i$ 为下标。 - -1. 遍历数组,对于每一个数 $nums[i]$: - 1. 先查找字典中是否存在 $target - nums[i]$,存在则输出 $target - nums[i]$ 对应的下标和当前数组的下标 $i$。 - 2. 不存在则在字典中存入 $target - nums[i]$ 的下标 $i$。 - -### 思路 2:代码 - -```python -def twoSum(self, nums: List[int], target: int) -> List[int]: - numDict = dict() - for i in range(len(nums)): - if target-nums[i] in numDict: - return numDict[target-nums[i]], i - numDict[nums[i]] = i - return [0] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的元素数量。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0002. \344\270\244\346\225\260\347\233\270\345\212\240.md" "b/Solutions/0002. \344\270\244\346\225\260\347\233\270\345\212\240.md" deleted file mode 100644 index 1c09d8fa..00000000 --- "a/Solutions/0002. \344\270\244\346\225\260\347\233\270\345\212\240.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) - -- 标签:递归、链表、数学 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个非空的链表 `l1` 和 `l2`。分别用来表示两个非负整数,每位数字都是按照逆序的方式存储的,每个节点存储一位数字。 - -**要求**:计算两个非负整数的和,并逆序返回表示和的链表。 - -**说明**: - -- 每个链表中的节点数在范围 $[1, 100]$ 内。 -- $0 \le Node.val \le 9$。 -- 题目数据保证列表表示的数字不含前导零。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/01/02/addtwonumber1.jpg) - -```python -输入:l1 = [2,4,3], l2 = [5,6,4] -输出:[7,0,8] -解释:342 + 465 = 807. -``` - -- 示例 2: - -```python -输入:l1 = [0], l2 = [0] -输出:[0] -``` - -## 解题思路 - -### 思路 1:模拟 - -模拟大数加法,按位相加,将结果添加到新链表上。需要注意进位和对 $10$ 取余。 - -### 思路 1:代码 - -```python -class Solution: - def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: - head = curr = ListNode(0) - carry = 0 - while l1 or l2 or carry: - if l1: - num1 = l1.val - l1 = l1.next - else: - num1 = 0 - if l2: - num2 = l2.val - l2 = l2.next - else: - num2 = 0 - - sum = num1 + num2 + carry - carry = sum // 10 - - curr.next = ListNode(sum % 10) - curr = curr.next - - return head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(max(m, n))$。其中,$m$ 和 $n$ 分别是链表 `l1` 和 `l2` 的长度。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0003. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" "b/Solutions/0003. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" deleted file mode 100644 index 63f0943c..00000000 --- "a/Solutions/0003. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:找出其中不含有重复字符的最长子串的长度。 - -**说明**: - -- $0 \le s.length \le 5 * 10^4$。 -- `s` 由英文字母、数字、符号和空格组成。 - -**示例**: - -- 示例 1: - -```python -输入: s = "abcabcbb" -输出: 3 -解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 -``` - -- 示例 2: - -```python -输入: s = "bbbbb" -输出: 1 -解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 -``` - -## 解题思路 - -### 思路 1:滑动窗口(不定长度) - -用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。 - -1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 -2. 一开始,$left$、$right$ 都指向 $0$。 -3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。 -4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$。 -5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -6. 输出无重复字符的最长子串长度。 - -### 思路 1:代码 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - left = 0 - right = 0 - window = dict() - ans = 0 - - while right < len(s): - if s[right] not in window: - window[s[right]] = 1 - else: - window[s[right]] += 1 - - while window[s[right]] > 1: - window[s[left]] -= 1 - left += 1 - - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 diff --git "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" deleted file mode 100644 index 10268ac8..00000000 --- "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ /dev/null @@ -1,124 +0,0 @@ -# [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) - -- 标签:数组、二分查找、分治 -- 难度:困难 - -## 题目大意 - -**描述**:给定两个正序(从小到大排序)数组 $nums1$、$nums2$。 - -**要求**:找出并返回这两个正序数组的中位数。 - -**说明**: - -- 算法的时间复杂度应该为 $O(\log (m + n))$ 。 -- $nums1.length == m$。 -- $nums2.length == n$。 -- $0 \le m \le 1000$。 -- $0 \le n \le 1000$。 -- $1 \le m + n \le 2000$。 -- $-10^6 \le nums1[i], nums2[i] \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2], nums2 = [3,4] -输出:2.50000 -解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5 -``` - -- 示例 2: - -```python -输入:nums1 = [1,2], nums2 = [3,4] -输出:2.50000 -解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5 -``` - -## 解题思路 - -### 思路 1:二分查找 - -单个有序数组的中位数是中间元素位置的元素。如果中间元素位置有两个元素,则为两个元素的平均数。如果是两个有序数组,则可以使用归并排序的方式将两个数组拼接为一个大的有序数组。合并后有序数组中间位置的元素,即为中位数。 - -当然不合并的话,我们只需找到中位数的位置即可。我们用 $n1$、$n2$ 来表示数组 $nums1$、$nums2$ 的长度,则合并后的大的有序数组长度为 $(n1 + n2)$。 - -我们可以发现:**中位数把数组分割成了左右两部分,并且左右两部分元素个数相等。** - -- 如果 $(n1 + n2)$ 是奇数时,中位数是大的有序数组中第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 个(包含中位数)。 -- 如果 $(n1 + n2)$ 是偶数时,中位数是第 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 的元素和第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素的平均值,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 个。 - -因为是向下取整,上面两种情况综合可以写为:单侧元素个数为:$\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 个。 - -我们用 $k$ 来表示 $\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 。现在的问题就变为了:**如何在两个有序数组中找到前 k 小的元素位置?** - -如果我们从 $nums1$ 数组中取出前 $m1(m1 \le k)$ 个元素,那么从 $nums2$ 就需要取出前 $m2 = k - m1$ 个元素。 - -并且如果我们在 $nums1$ 数组中找到了合适的 $m1$ 位置,则 $m2$ 的位置也就确定了。 - -问题就可以进一步转换为:**如何从 $nums1$ 数组中取出前 $m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或者 $nums2$ 第 $m2 = k - m1$ 个元素为中位线位置**。 - -我们可以通过「二分查找」的方法,在数组 $nums1$ 中找到合适的 $m1$ 位置,具体做法如下: - -1. 让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 -2. 每次取中间位置作为 $m1$,则 $m2 = k - m1$。然后判断 $nums1$ 第 $m1$ 位置上元素和 $nums2$ 第 $m2 - 1$ 位置上元素之间的关系,即 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系。 - 1. 如果 $nums1[m1] < nums2[m2 - 1]$,则 $nums1$ 的前 $m1$ 个元素都不可能是第 $k$ 个元素。说明 $m1$ 取值有点小了,应该将 $m1$ 进行右移操作,即 $left = m1 + 1$。 - 2. 如果 $nums1[m1] \ge nums2[m2 - 1]$,则说明 $m1$ 取值可能有点大了,应该将 $m1$ 进行左移。根据二分查找排除法的思路(排除一定不存在的区间,在剩下区间中继续查找),这里应取 $right = m1$。 -3. 找到 $m1$ 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 - ---- - -上面之所以要判断 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系是因为: - -> 如果 $nums1[m1] < nums2[m2 - 1]$,则说明: -> -> - 最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小,所以 $nums1[m1]$ 左侧的 $m1$ 个元素都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 - -推理过程: - -如果 $nums1[m1] < nums2[m2 - 1]$,则: - -1. $nums1[m1]$ 左侧比 $nums1[m1]$ 小的一共有 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$ 共 $m1$ 个)。 -2. $nums2$ 数组最多有 $m2 - 1$ 个元素比 $nums1[m1]$ 小(即便是 $nums2[m2 - 1]$ 左侧所有元素都比 $nums1[m1]$ 小,也只有 $m2 - 1$ 个)。 -3. 综上所述,$nums1$、$nums2$ 数组中最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小。 -4. 所以 $nums1[m1]$ 左侧的 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$)都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 - -### 思路 1:代码 - -```python -class Solution: - def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: - n1 = len(nums1) - n2 = len(nums2) - if n1 > n2: - return self.findMedianSortedArrays(nums2, nums1) - - k = (n1 + n2 + 1) // 2 - left = 0 - right = n1 - while left < right: - m1 = left + (right - left) // 2 # 在 nums1 中取前 m1 个元素 - m2 = k - m1 # 在 nums2 中取前 m2 个元素 - if nums1[m1] < nums2[m2 - 1]: # 说明 nums1 中所元素不够多, - left = m1 + 1 - else: - right = m1 - - m1 = left - m2 = k - m1 - - c1 = max(float('-inf') if m1 <= 0 else nums1[m1 - 1], float('-inf') if m2 <= 0 else nums2[m2 - 1]) - if (n1 + n2) % 2 == 1: - return c1 - - c2 = min(float('inf') if m1 >= n1 else nums1[m1], float('inf') if m2 >= n2 else nums2[m2]) - - return (c1 + c2) / 2 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log_2 (m + n))$ 。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0005. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" "b/Solutions/0005. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" deleted file mode 100644 index cb323dac..00000000 --- "a/Solutions/0005. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:找到 $s$ 中最长的回文子串。 - -**说明**: - -- **回文串**:如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 -- $1 \le s.length \le 1000$。 -- $s$ 仅由数字和英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "babad" -输出:"bab" -解释:"aba" 同样是符合题意的答案。 -``` - -- 示例 2: - -```python -输入:s = "cbbd" -输出:"bb" -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内是否是一个回文串。 - -###### 3. 状态转移方程 - -- 当子串只有 $1$ 位或 $2$ 位的时候,如果 $s[i] == s[j]$,该子串为回文子串,即:`dp[i][j] = (s[i] == s[j])`。 -- 如果子串大于 $2$ 位,则如果 $s[i + 1...j - 1]$ 是回文串,且 $s[i] == s[j]$,则 $s[i...j]$ 也是回文串,即:`dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]`。 - -###### 4. 初始条件 - -- 初始状态下,默认字符串 $s$ 的所有子串都不是回文串。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内是否是一个回文串。当判断完 $s[i: j]$ 是否为回文串时,同时判断并更新最长回文子串的起始位置 $max\underline{}start$ 和最大长度 $max\underline{}len$。则最终结果为 $s[max\underline{}start, max\underline{}start + max\underline{}len]$。 - -### 思路 1:代码 - -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - n = len(s) - if n <= 1: - return s - - dp = [[False for _ in range(n)] for _ in range(n)] - max_start = 0 - max_len = 1 - - for j in range(1, n): - for i in range(j): - if s[i] == s[j]: - if j - i <= 2: - dp[i][j] = True - else: - dp[i][j] = dp[i + 1][j - 1] - if dp[i][j] and (j - i + 1) > max_len: - max_len = j - i + 1 - max_start = i - return s[max_start: max_start + max_len] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 是字符串的长度。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0007. \346\225\264\346\225\260\345\217\215\350\275\254.md" "b/Solutions/0007. \346\225\264\346\225\260\345\217\215\350\275\254.md" deleted file mode 100644 index 05f0791d..00000000 --- "a/Solutions/0007. \346\225\264\346\225\260\345\217\215\350\275\254.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0007. 整数反转](https://leetcode.cn/problems/reverse-integer/) - -- 标签:数学 -- 难度:中等 - -## 题目大意 - -给定一个 32 位有符号整数 x,将 x 进行反转。 - -## 解题思路 - -x 的范围为 $[-2^{31}, 2^{31}-1]$,即 $[-2147483648 ,2147483647]$。 - -反转的步骤就是让 x 不断对 10 取余,再除以 10,得到每一位的数字,同时累积结果。 - -注意累积结果的时候需要判断是否溢出。 - -当 ans * 10 + pop > INT_MAX ,或者 ans * 10 + pop < INT_MIN 时就会溢出。 - -按题设要求,无法在溢出之后对其判断。那么如何在进行累积操作之前判断溢出呢? - -ans * 10 + pop > INT_MAX 有两种情况: - -1. ans > INT_MAX / 10,这种情况下,无论是否考虑 pop 进位都会溢出; -2. ans == INT_MAX / 10,这种情况下,考虑进位,如果 pop 大于 IN_MAX 的个位数,就会导致溢出。 - -同理 ans * 10 + pop < INT_MIN 也有两种情况: - -1. ans < INT_MIN / 10 -2. ans == INT_MIN / 10 且 pop < INT_MIN 的个位数,就会导致溢出 - -## 代码 - -```python -class Solution: - def reverse(self, x: int) -> int: - INT_MAX_10 = (1<<31)//10 - INT_MIN_10 = int((-1<<31)/10) - INT_MAX_LAST = (1<<31) % 10 - INT_MIN_LAST = (-1<<31) % -10 - ans = 0 - while x: - pop = x % 10 if x > 0 else x % -10 - x = x // 10 if x > 0 else int(x / 10) - if ans > INT_MAX_10 or (ans == INT_MAX_10 and pop > INT_MAX_LAST): - return 0 - if ans < INT_MIN_10 or (ans == INT_MIN_10 and pop < INT_MIN_LAST): - return 0 - ans = ans*10+pop - return ans -``` - diff --git "a/Solutions/0008. \345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260 (atoi).md" "b/Solutions/0008. \345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260 (atoi).md" deleted file mode 100644 index 1366bbe4..00000000 --- "a/Solutions/0008. \345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\225\264\346\225\260 (atoi).md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0008. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) - -- 标签:字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:实现一个 `myAtoi(s)` 函数。使其能换成一个 32 位有符号整数(类似 C / C++ 中的 `atoi` 函数)。需要检测有效性,无法读取返回 $0$。 - -**说明**: - -- 函数 `myAtoi(s)` 的算法如下: - 1. 读入字符串并丢弃无用的前导空格。 - 2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。 - 3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。 - 4. 将前面步骤读入的这些数字转换为整数(即,`"123"` -> `123`, `"0032"` -> `32`)。如果没有读入数字,则整数为 `0` 。必要时更改符号(从步骤 2 开始)。 - 5. 如果整数数超过 32 位有符号整数范围 $[−2^{31}, 2^{31} − 1]$ ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 $−2^{31}$ 的整数应该被固定为 $−2^{31}$ ,大于 $2^{31} − 1$ 的整数应该被固定为 $2^{31} − 1$。 - 6. 返回整数作为最终结果。 -- 本题中的空白字符只包括空格字符 `' '` 。 -- 除前导空格或数字后的其余字符串外,请勿忽略任何其他字符。 -- $0 \le s.length \le 200$。 -- `s` 由英文字母(大写和小写)、数字(`0-9`)、`' '`、`'+'`、`'-'` 和 `'.'` 组成 - -**示例**: - -- 示例 1: - -```python -输入:s = "42" -输出:42 -解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。 -第 1 步:"42"(当前没有读入字符,因为没有前导空格) - ^ -第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+') - ^ -第 3 步:"42"(读入 "42") - ^ -解析得到整数 42 。 -由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。 -``` - -- 示例 2: - -```python -输入:s = " -42" -输出:-42 -解释: -第 1 步:" -42"(读入前导空格,但忽视掉) - ^ -第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数) - ^ -第 3 步:" -42"(读入 "42") - ^ -解析得到整数 -42 。 -由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 先去除前后空格。 -2. 检测正负号。 -3. 读入数字,并用字符串存储数字结果。 -4. 将数字字符串转为整数,并根据正负号转换整数结果。 -5. 判断整数范围,并返回最终结果。 - -### 思路 1:代码 - -```python -class Solution: - def myAtoi(self, s: str) -> int: - num_str = "" - positive = True - start = 0 - - s = s.lstrip() - if not s: - return 0 - - if s[0] == '-': - positive = False - start = 1 - elif s[0] == '+': - positive = True - start = 1 - elif not s[0].isdigit(): - return 0 - - for i in range(start, len(s)): - if s[i].isdigit(): - num_str += s[i] - else: - break - if not num_str: - return 0 - num = int(num_str) - if not positive: - num = -num - return max(num, -2 ** 31) - else: - return min(num, 2 ** 31 - 1) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0009. \345\233\236\346\226\207\346\225\260.md" "b/Solutions/0009. \345\233\236\346\226\207\346\225\260.md" deleted file mode 100644 index 1b9ad70e..00000000 --- "a/Solutions/0009. \345\233\236\346\226\207\346\225\260.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0009. 回文数](https://leetcode.cn/problems/palindrome-number/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -给定整数 x,判断 x 是否是回文数。要求不能用整数转为字符串的方式来解决这个问题。 - -回文数指的是正序(从左向右)和倒序(从右向左)读都是一样的整数。比如 12321。 - -## 解题思路 - -- 首先,负数,10 的倍数都不是回文数,可以直接排除。 -- 然后将原数进行按位取余,并按位反转,若与原数完全相等,则原数为回文数。 -- 其实,第二步在反转到一半的时候,就可以进行判断了。因为原数是回文数,那么在反转到中间的时候,留下的前半部分,应该与转换好的后半部分倒转过来相等。比如:1221,转换到一半,原数变为 12,转换好的数变为 12,则说明原数就是回文数。如果原数为奇数,比如:12321,转换到一半,原数变为 12,转换好的数变为 123,则应该将原数与 转换好的数对 10 取余的部分进行比较。 - -## 代码 - -```python -class Solution: - def isPalindrome(self, x: int) -> bool: - if x < 0 or (x % 10 == 0 and x != 0): - return False - - res = 0 - while x > res: - res = res * 10 + x % 10 - x = x // 10 - return x == res or x == res // 10 -``` - diff --git "a/Solutions/0010. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" "b/Solutions/0010. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" deleted file mode 100644 index 74ea1c1f..00000000 --- "a/Solutions/0010. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" +++ /dev/null @@ -1,102 +0,0 @@ -# [0010. 正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) - -- 标签:递归、字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个字符串 `s` 和一个字符模式串 `p`。 - -**要求**:实现一个支持 `'.'` 和 `'*'` 的正则表达式匹配。两个字符串完全匹配才算匹配成功。如果匹配成功,则返回 `True`,否则返回 `False`。 - -- `'.'` 匹配任意单个字符。 -- `'*'` 匹配零个或多个前面的那一个元素。 - -**说明**: - -- $1 \le s.length \le 20$。 -- $1 \le p.length \le 30$。 -- `s` 只包含从 `a` ~ `z` 的小写字母。 -- `p` 只包含从 `a` ~ `z` 的小写字母,以及字符 `.` 和 `*`。 -- 保证每次出现字符 `*` 时,前面都匹配到有效的字符。 - -**示例**: - -- 示例 1: - -```python -输入:s = "aa", p = "a*" -输出:True -解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。 -``` - -- 示例 2: - -```python -输入:s = "aa", p = "a" -输出:False -解释:"a" 无法匹配 "aa" 整个字符串。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。 - -###### 3. 状态转移方程 - -- 如果 `s[i - 1] == p[j - 1]`,则字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的。此时「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j - 1] `。 -- 如果 `p[j - 1] == '.'`,则字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的(同上)。此时 `dp[i][j] = dp[i - 1][j - 1] `。 -- 如果 `p[j - 1] == '*'`,则我们可以对字符 `p[j - 2]` 进行 `0` ~ 若干次数的匹配。 - - 如果 `s[i - 1] != p[j - 2]` 并且 `p[j - 2] != '.'`,则说明当前星号匹配不上,只能匹配 `0` 次(即匹配空字符串),则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」,即 `dp[i][j] = dp[i][j - 2] `。 - - 如果 `s[i - 1] == p[j - 2]` 或者 `p[j - 2] == '.'`,则说明当前星号前面的字符 `p[j - 2]` 可以匹配 `s[i - 1]`。 - - 如果匹配 `0` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 2]`。 - - 如果匹配 `1` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 1] `。 - - 如果匹配多个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j]`。 - -###### 4. 初始条件 - -- 默认状态下,两个空字符串是匹配的,即 `dp[0][0] = True`。 -- 当字符串 `s` 为空,字符串 `p` 右端有 `*` 时,想要匹配,则如果「空字符串」与「去掉字符串 `p` 右端的 `*` 和 `*` 之前的字符之后的字符串」匹配的话,则空字符串与字符串 `p` 匹配。也就是说如果 `p[j - 1] == '*'`,则 `dp[0][j] = dp[0][j - 2]`。 - -###### 5. 最终结果 - -根据我们之前定义的状态, `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。则最终结果为 `dp[size_s][size_p]`,其实 `size_s` 是字符串 `s` 的长度,`size_p` 是字符串 `p` 的长度。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def isMatch(self, s: str, p: str) -> bool: - size_s, size_p = len(s), len(p) - dp = [[False for _ in range(size_p + 1)] for _ in range(size_s + 1)] - - dp[0][0] = True - for j in range(1, size_p + 1): - if p[j - 1] == '*': - dp[0][j] = dp[0][j - 2] - - for i in range(1, size_s + 1): - for j in range(1, size_p + 1): - if s[i - 1] == p[j - 1] or p[j - 1] == '.': - dp[i][j] = dp[i - 1][j - 1] - elif p[j - 1] == '*': - if s[i - 1] != p[j - 2] and p[j - 2] != '.': - dp[i][j] = dp[i][j - 2] - else: - dp[i][j] = dp[i][j - 1] or dp[i][j - 2] or dp[i - 1][j] - - return dp[size_s][size_p] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了两重循环,外层循环遍历的时间复杂度是 $O(m)$,内层循环遍历的时间复杂度是 $O(n)$,所以总体的时间复杂度为 $O(m n)$。 -- **空间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了二维数组保存状态,且第一维的空间复杂度为 $O(m)$,第二位的空间复杂度为 $O(n)$,所以总体的空间复杂度为 $O(m n)$。 diff --git "a/Solutions/0011. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" "b/Solutions/0011. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" deleted file mode 100644 index 546a234b..00000000 --- "a/Solutions/0011. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [0011. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) - -- 标签:贪心、数组、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n$,每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 - -**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 - -**说明**: - -- $n == height.length$。 -- $2 \le n \le 10^5$。 -- $0 \le height[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) - -```python -输入:[1,8,6,2,5,4,8,3,7] -输出:49 -解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 -``` - -## 解题思路 - -### 思路 1:对撞指针 - -从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 `左右两端直线中较低直线的高度 * 两端直线之间的距离 ` 所决定的。所以我们应该使得 **较低直线的高度尽可能的高**,这样才能使盛水面积尽可能的大。 - -可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: - -1. 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 -2. 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 -3. 判断 `left` 和 `right` 的高度值大小。 - 1. 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 - 2. 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 -4. 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 - -### 思路 1:代码 - -```python -class Solution: - def maxArea(self, height: List[int]) -> int: - left = 0 - right = len(height) - 1 - ans = 0 - while left < right: - area = min(height[left], height[right]) * (right-left) - ans = max(ans, area) - if height[left] < height[right]: - left += 1 - else: - right -= 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0012. \346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.md" "b/Solutions/0012. \346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.md" deleted file mode 100644 index e5f74701..00000000 --- "a/Solutions/0012. \346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0012. 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman/) - -- 标签:哈希表、数学、字符串 -- 难度:中等 - -## 题目大意 - -给定一个整数,将其转换为罗马数字。 - -罗马数字规则: - -- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; -- 一般罗马数字较大数字在左边,较小数字在右边,此时值为两者之和,比如 XI = X + I = 10 + 1 = 11。 -- 例外情况下,较小数字在左边,较大数字在右边,此时值为后者减前者之差,比如 IX = X - I = 10 - 1 = 9。 - -## 解题思路 - -根据规则,可以得出: - -- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; -- CM 代表 900,CD 代表 400,XC 代表 90,XL 代表 40,IX 代表 9,IV 代表 4。 - -依次排序可得: - -- 1000 : M、900 : CM、D : 500、400 : CD、100 : C、90 : XC、50 : L、40 : XL、10 : X、9 : IX、5 : V、4 : IV、1 : I。 - -使用贪心算法。每次尽量用最大的数对应的罗马字符来表示。先选择 1000,再选择 900,然后 500,等等。 - -## 代码 - -```python -class Solution: - def intToRoman(self, num: int) -> str: - roman_dict = {1000:'M', 900:'CM', 500:'D', 400:'CD', 100:'C', 90:'XC', 50:'L', 40:'XL', 10:'X', 9:'IX', 5:'V', 4:'IV', 1:'I'} - res = "" - for key in roman_dict: - if num // key != 0: - res += roman_dict[key] * (num // key) - num %= key - return res -``` - diff --git "a/Solutions/0013. \347\275\227\351\251\254\346\225\260\345\255\227\350\275\254\346\225\264\346\225\260.md" "b/Solutions/0013. \347\275\227\351\251\254\346\225\260\345\255\227\350\275\254\346\225\264\346\225\260.md" deleted file mode 100644 index 594537c7..00000000 --- "a/Solutions/0013. \347\275\227\351\251\254\346\225\260\345\255\227\350\275\254\346\225\264\346\225\260.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0013. 罗马数字转整数](https://leetcode.cn/problems/roman-to-integer/) - -- 标签:哈希表、数学、字符串 -- 难度:简单 - -## 题目大意 - -给定一个罗马数字对应的字符串,将其转换为整数。 - -罗马数字规则: - -- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; -- 一般罗马数字较大数字在左边,较小数字在右边,此时值为两者之和,比如 XI = X + I = 10 + 1 = 11。 -- 例外情况下,较小数字在左边,较大数字在右边,此时值为后者减前者之差,比如 IX = X - I = 10 - 1 = 9。 - -## 解题思路 - -用一个哈希表存储罗马数字与对应数值关系。遍历罗马数字对应的字符串,判断相邻两个数大小关系,并计算对应结果。 - -## 代码 - -```python -class Solution: - def romanToInt(self, s: str) -> int: - nunbers = { - "I" : 1, - "V" : 5, - "X" : 10, - "L" : 50, - "C" : 100, - "D" : 500, - "M" : 1000 - } - sum = 0 - pre_num = nunbers[s[0]] - for i in range(1, len(s)): - cur_num = nunbers[s[i]] - if pre_num < cur_num: - sum -= pre_num - else: - sum += pre_num - pre_num = cur_num - sum += pre_num - return sum -``` - diff --git "a/Solutions/0014. \346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.md" "b/Solutions/0014. \346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.md" deleted file mode 100644 index 64647e7a..00000000 --- "a/Solutions/0014. \346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) - -- 标签:字典树、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串数组 `strs`。 - -**要求**:返回字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 `""`。 - -**说明**: - -- $1 \le strs.length \le 200$。 -- $0 \le strs[i].length \le 200$。 -- `strs[i]` 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:strs = ["flower","flow","flight"] -输出:"fl" -``` - -- 示例 2: - -```python -输入:strs = ["dog","racecar","car"] -输出:"" -解释:输入不存在公共前缀。 -``` - -## 解题思路 - -### 思路 1:纵向遍历 - -1. 依次遍历所有字符串的每一列,比较相同位置上的字符是否相同。 - 1. 如果相同,则继续对下一列进行比较。 - 2. 如果不相同,则当前列字母不再属于公共前缀,直接返回当前列之前的部分。 -2. 如果遍历结束,说明字符串数组中的所有字符串都相等,则可将字符串数组中的第一个字符串作为公共前缀进行返回。 - -### 思路 1:代码 - -```python -class Solution: - def longestCommonPrefix(self, strs: List[str]) -> str: - if not strs: - return "" - - length = len(strs[0]) - count = len(strs) - for i in range(length): - c = strs[0][i] - for j in range(1, count): - if len(strs[j]) == i or strs[j][i] != c: - return strs[0][:i] - return strs[0] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$ 是字符串数组中的字符串的平均长度,$n$ 是字符串的数量。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0015. \344\270\211\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0015. \344\270\211\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index ed5e3df7..00000000 --- "a/Solutions/0015. \344\270\211\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0015. 三数之和](https://leetcode.cn/problems/3sum/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:判断 $nums$ 中是否存在三个元素 $a$、$b$、$c$,满足 $a + b + c == 0$。要求找出所有满足要求的不重复的三元组。 - -**说明**: - -- $3 \le nums.length \le 3000$。 -- $-10^5 \le nums[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [-1,0,1,2,-1,-4] -输出:[[-1,-1,2],[-1,0,1]] -``` - -- 示例 2: - -```python -输入:nums = [0,1,1] -输出:[] -``` - -## 解题思路 - -### 思路 1:对撞指针 - -直接三重遍历查找 $a$、$b$、$c$ 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 - -先将数组进行排序,以保证按顺序查找 $a$、$b$、$c$ 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$。 - -第一重循环遍历 $a$,对于每个 $a$ 元素,从 $a$ 元素的下一个位置开始,使用对撞指针 $left$,$right$。$left$ 指向 $a$ 元素的下一个位置,$right$ 指向末尾位置。先将 $left$ 右移、$right$ 左移去除重复元素,再进行下边的判断。 - -1. 如果 $nums[a] + nums[left] + nums[right] == 0$,则得到一个解,将其加入答案数组中,并继续将 $left$ 右移,$right$ 左移; -2. 如果 $nums[a] + nums[left] + nums[right] > 0$,说明 $nums[right]$ 值太大,将 $right$ 向左移; -3. 如果 $nums[a] + nums[left] + nums[right] < 0$,说明 $nums[left]$ 值太小,将 $left$ 右移。 - -### 思路 1:代码 - -```python -class Solution: - def threeSum(self, nums: List[int]) -> List[List[int]]: - n = len(nums) - nums.sort() - ans = [] - - for i in range(n): - if i > 0 and nums[i] == nums[i - 1]: - continue - left = i + 1 - right = n - 1 - while left < right: - while left < right and left > i + 1 and nums[left] == nums[left - 1]: - left += 1 - while left < right and right < n - 1 and nums[right + 1] == nums[right]: - right -= 1 - if left < right and nums[i] + nums[left] + nums[right] == 0: - ans.append([nums[i], nums[left], nums[right]]) - left += 1 - right -= 1 - elif nums[i] + nums[left] + nums[right] > 0: - right -= 1 - else: - left += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0016. \346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0016. \346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index f80144bc..00000000 --- "a/Solutions/0016. \346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [0016. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -给你一个整数数组 `nums` 和 一个目标值 `target`。 - -要求:从 `nums` 中选出三个整数,使它们的和与 `target` 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。 - -## 解题思路 - -直接暴力枚举三个数的时间复杂度是 $O(n^3)$。很明显的容易超时。考虑使用双指针减少循环内的时间复杂度。具体做法如下: - -- 先对数组进行从小到大排序,使用 `ans` 记录最接近的三数之和。 -- 遍历数组,对于数组元素 `nums[i]`,使用两个指针 `left`、`right`。`left` 指向第 `0` 个元素位置,`right` 指向第 `i - 1` 个元素位置。 -- 计算 `nums[i]`、`nums[left]`、`nums[right]` 的和与 `target` 的差值,将其与 `ans` 与 `target` 的差值作比较。如果差值小,则更新 `ans`。 - - 如果 `nums[i] + nums[left] + nums[right] < target`,则说明 `left` 小了,应该将 `left` 右移,继续查找。 - - 如果 `nums[i] + nums[left] + nums[right] >= target`,则说明 `right` 太大了,应该将 `right` 左移,然后继续判断。 -- 当 `left == right` 时,区间搜索完毕,继续遍历 `nums[i + 1]`。 -- 最后输出 `ans`。 - -这种思路使用了两重循环,其中内层循环当 `left == right` 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$。 - -## 代码 - -```python -class Solution: - def threeSumClosest(self, nums: List[int], target: int) -> int: - nums.sort() - res = float('inf') - size = len(nums) - for i in range(2, size): - left = 0 - right = i - 1 - while left < right: - total = nums[left] + nums[right] + nums[i] - if abs(total - target) < abs(res - target): - res = total - if total < target: - left += 1 - else: - right -= 1 - - return res -``` - diff --git "a/Solutions/0017. \347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.md" "b/Solutions/0017. \347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.md" deleted file mode 100644 index f83820e5..00000000 --- "a/Solutions/0017. \347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0017. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) - -- 标签:哈希表、字符串、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含数字 2~9 的字符串 `digits`。给出数字到字母的映射如下(与电话按键相同)。注意 $1$ 不对应任何字母。 - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/original_images/17_telephone_keypad.png) - -**要求**:返回字符串 `digits` 在九宫格键盘上所能表示的所有字母组合。答案可以按 「任意顺序」返回。 - -**说明**: - -- $0 \le digits.length \le 4$。 -- `digits[i]` 是范围 $2 \sim 9$ 的一个数字。 - -**示例**: - -- 示例 1: - -```python -输入:digits = "23" -输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] -``` - -- 示例 2: - -```python -输入:digits = "2" -输出:["a","b","c"] -``` - -## 解题思路 - -### 思路 1:回溯算法 + 哈希表 - -用哈希表保存每个数字键位对应的所有可能的字母,然后进行回溯操作。 - -回溯过程中,维护一个字符串 combination,表示当前的字母排列组合。初始字符串为空,每次取电话号码的一位数字,从哈希表中取出该数字所对应的所有字母,并将其中一个插入到 combination 后面,然后继续处理下一个数字,知道处理完所有数字,得到一个完整的字母排列。开始进行回退操作,遍历其余的字母排列。 - -### 思路 1:代码 - -```python -class Solution: - def letterCombinations(self, digits: str) -> List[str]: - if not digits: - return [] - - phone_dict = { - "2": "abc", - "3": "def", - "4": "ghi", - "5": "jkl", - "6": "mno", - "7": "pqrs", - "8": "tuv", - "9": "wxyz" - } - - def backtrack(combination, index): - if index == len(digits): - combinations.append(combination) - else: - digit = digits[index] - for letter in phone_dict[digit]: - backtrack(combination + letter, index + 1) - - combinations = list() - backtrack('', 0) - return combinations -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(3^m \times 4^n)$,其中 $m$ 是 `digits` 中对应 $3$ 个字母的数字个数,$m$ 是 `digits` 中对应 $4$ 个字母的数字个数。 -- **空间复杂度**:$O(m + n)$。 - diff --git "a/Solutions/0018. \345\233\233\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0018. \345\233\233\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index e2d57363..00000000 --- "a/Solutions/0018. \345\233\233\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0018. 四数之和](https://leetcode.cn/problems/4sum/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a、b、c、d,使得 a + b + c + d = target。要求找出所有满足条件且不重复的四元组。 - -## 解题思路 - -和 [0015. 三数之和](https://leetcode.cn/problems/3sum/) 解法类似。 - -直接三重遍历查找 a、b、c、d 的时间复杂度是:$O(n^4)$。我们可以通过一些操作来降低复杂度。 - -先将数组进行排序,以保证按顺序查找 a、b、c、d 时,元素值为升序,从而保证所找到的四个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$ - -两重循环遍历元素 a、b,对于每个 a 元素,从 a 元素的下一个位置开始遍历元素 b。对于元素 a、b,使用双指针 left,right 来查找 c、d。left 指向 b 元素的下一个位置,right 指向末尾位置。先将 left 右移、right 左移去除重复元素,再进行下边的判断。 - -- 若 `nums[a] + nums[b] + nums[left] + nums[right] = target`,则得到一个解,将其加入答案数组中,并继续将 left 右移,right 左移; - -- 若 `nums[a] + nums[b] + nums[left] + nums[right] > target`,说明 nums[right] 值太大,将 right 向左移; -- 若 `nums[a] + nums[b] + nums[left] + nums[right] < target`,说明 nums[left] 值太小,将 left 右移。 - -## 代码 - -```python -class Solution: - def fourSum(self, nums: List[int], target: int) -> List[List[int]]: - n = len(nums) - nums.sort() - ans = [] - - for i in range(n): - if i > 0 and nums[i] == nums[i-1]: - continue - for j in range(i+1, n): - if j > i+1 and nums[j] == nums[j-1]: - continue - left = j + 1 - right = n - 1 - while left < right: - while left < right and left > j + 1 and nums[left] == nums[left - 1]: - left += 1 - while left < right and right < n - 1 and nums[right + 1] == nums[right]: - right -= 1 - if left < right and nums[i] + nums[j] + nums[left] + nums[right] == target: - ans.append([nums[i], nums[j], nums[left], nums[right]]) - left += 1 - right -= 1 - elif nums[i] + nums[j] + nums[left] + nums[right] > target: - right -= 1 - else: - left += 1 - return ans -``` - diff --git "a/Solutions/0019. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 N \344\270\252\347\273\223\347\202\271.md" "b/Solutions/0019. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 N \344\270\252\347\273\223\347\202\271.md" deleted file mode 100644 index ed370ef2..00000000 --- "a/Solutions/0019. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 N \344\270\252\347\273\223\347\202\271.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) - -- 标签:链表、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。 - -**说明**: - -- 要求使用一次遍历实现。 -- 链表中结点的数目为 `sz`。 -- $1 \le sz \le 30$。 -- $0 \le Node.val \le 100$。 -- $1 \le n \le sz$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg) - -```python -输入:head = [1,2,3,4,5], n = 2 -输出:[1,2,3,5] -``` - -- 示例 2: - -```python -输入:head = [1], n = 1 -输出:[] -``` - -## 解题思路 - -### 思路 1:快慢指针 - -常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 - -如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `n` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `n` 个节点位置。将该位置上的节点删除即可。 - -需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 - -### 思路 1:代码 - -```python -class Solution: - def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: - newHead = ListNode(0, head) - fast = head - slow = newHead - while n: - fast = fast.next - n -= 1 - while fast: - fast = fast.next - slow = slow.next - slow.next = slow.next.next - return newHead.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0020. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.md" "b/Solutions/0020. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.md" deleted file mode 100644 index cb619c9d..00000000 --- "a/Solutions/0020. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) - -- 标签:栈、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串 `s` 。 - -**要求**:判断字符串 `s` 是否有效(即括号是否匹配)。 - -**说明**: - -- 有效字符串需满足: - 1. 左括号必须用相同类型的右括号闭合。 - 2. 左括号必须以正确的顺序闭合。 - -**示例**: - -- 示例 1: - -```python -输入:s = "()" -输出:True -``` - -- 示例 2: - -```python -输入:s = "()[]{}" -输出:True -``` - -## 解题思路 - -### 思路 1:栈 - -括号匹配是「栈」的经典应用。我们可以用栈来解决这道题。具体做法如下: - -1. 先判断一下字符串的长度是否为偶数。因为括号是成对出现的,所以字符串的长度应为偶数,可以直接判断长度为奇数的字符串不匹配。如果字符串长度为奇数,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 -2. 使用栈 `stack` 来保存未匹配的左括号。然后依次遍历字符串 `s` 中的每一个字符。 - 1. 如果遍历到左括号时,将其入栈。 - 2. 如果遍历到右括号时,先看栈顶元素是否是与当前右括号相同类型的左括号。 - 1. 如果是与当前右括号相同类型的左括号,则令其出栈,继续向前遍历。 - 2. 如果不是与当前右括号相同类型的左括号,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 -3. 遍历完,还要再判断一下栈是否为空。 - 1. 如果栈为空,则说明字符串 `s` 中的括号匹配,返回 `True`。 - 2. 如果栈不为空,则说明字符串 `s` 中的括号不匹配,返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def isValid(self, s: str) -> bool: - if len(s) % 2 == 1: - return False - stack = list() - for ch in s: - if ch == '(' or ch == '[' or ch == '{': - stack.append(ch) - elif ch == ')': - if len(stack) !=0 and stack[-1] == '(': - stack.pop() - else: - return False - elif ch == ']': - if len(stack) !=0 and stack[-1] == '[': - stack.pop() - else: - return False - elif ch == '}': - if len(stack) !=0 and stack[-1] == '{': - stack.pop() - else: - return False - if len(stack) == 0: - return True - else: - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0021. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.md" "b/Solutions/0021. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.md" deleted file mode 100644 index fbde2ded..00000000 --- "a/Solutions/0021. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个升序链表的头节点 `list1` 和 `list2`。 - -**要求**:将其合并为一个升序链表。 - -**说明**: - -- 两个链表的节点数目范围是 $[0, 50]$。 -- $-100 \le Node.val \le 100$。 -- `list1` 和 `list2` 均按 **非递减顺序** 排列 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) - -```python -输入:list1 = [1,2,4], list2 = [1,3,4] -输出:[1,1,2,3,4,4] -``` - -- 示例 2: - -```python -输入:list1 = [], list2 = [] -输出:[] -``` - -## 解题思路 - -### 思路 1:归并排序 - -利用归并排序的思想,具体步骤如下: - -1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `curr` 指向 `dummy_head` 用于遍历。 -2. 然后判断 `list1` 和 `list2` 头节点的值,将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 -3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 -4. 将剩余链表链接到合并后的链表中。 -5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后有序链表的头节点返回。 - -### 思路 1:代码 - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - dummy_head = ListNode(-1) - - curr = dummy_head - while list1 and list2: - if list1.val <= list2.val: - curr.next = list1 - list1 = list1.next - else: - curr.next = list2 - list2 = list2.next - curr = curr.next - - curr.next = list1 if list1 is not None else list2 - - return dummy_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0022. \346\213\254\345\217\267\347\224\237\346\210\220.md" "b/Solutions/0022. \346\213\254\345\217\267\347\224\237\346\210\220.md" deleted file mode 100644 index 311e571d..00000000 --- "a/Solutions/0022. \346\213\254\345\217\267\347\224\237\346\210\220.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) - -- 标签:字符串、回溯算法 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$,代表生成括号的对数。 - -**要求**:生成所有有可能且有效的括号组合。 - -**说明**: - -- $1 \le n \le 8$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 3 -输出:["((()))","(()())","(())()","()(())","()()()"] -``` - -- 示例 2: - -```python -输入:n = 1 -输出:["()"] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 - -如果在当前组合中增加一个 `(`,则令 `symbol` 加 `1`,如果增加一个 `)`,则令 `symbol` 减 `1`。 - -显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 - -如果最终生成 $2 \times n$ 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:$2 \times n$ 的括号组合中的每个位置,都可以从 `(` 或者 `)` 中选出。并且,只有在 `symbol < n` 的时候,才能选择 `(`,在 `symbol > 0` 的时候,才能选择 `)`。 - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - `backtracking(symbol, index):` 函数的传入参数是 `symbol`(用于表示是否当前组合是否成对匹配),`index`(当前元素下标),全局变量是 `parentheses`(用于保存所有有效的括号组合),`parenthesis`(当前括号组合),。 - - `backtracking(symbol, index)` 函数代表的含义是:递归根据 `symbol`,在 `(` 和 `)` 中选择第 `index` 个元素。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑元素,到第 $2 \times n$ 个元素为止,枚举出所有可选的元素。对于每一个可选元素: - - 约束条件:`symbol < n` 或者 `symbol > 0`。 - - 选择元素:将其添加到当前括号组合 `parenthesis` 中。 - - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 - - 撤销选择:将该元素从当前括号组合 `parenthesis` 中移除。 - - ```python - if symbol < n: - parenthesis.append('(') - backtrack(symbol + 1, index + 1) - parenthesis.pop() - if symbol > 0: - parenthesis.append(')') - backtrack(symbol - 1, index + 1) - parenthesis.pop() - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是当 `index == 2 * n` 时,递归停止。 - - 并且在 `symbol == 0` 时,当前组合才是有效的,此时将其加入到最终答案数组中。 - -### 思路 1:代码 - -```python -class Solution: - def generateParenthesis(self, n: int) -> List[str]: - parentheses = [] # 存放所有括号组合 - parenthesis = [] # 存放当前括号组合 - def backtrack(symbol, index): - if n * 2 == index: - if symbol == 0: - parentheses.append("".join(parenthesis)) - else: - if symbol < n: - parenthesis.append('(') - backtrack(symbol + 1, index + 1) - parenthesis.pop() - if symbol > 0: - parenthesis.append(')') - backtrack(symbol - 1, index + 1) - parenthesis.pop() - backtrack(0, 0) - return parentheses -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\frac{2^{2 \times n}}{\sqrt{n}})$,其中 $n$ 为生成括号的对数。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[22. 括号生成 - 力扣(Leetcode)](https://leetcode.cn/problems/generate-parentheses/solutions/192912/gua-hao-sheng-cheng-by-leetcode-solution/) \ No newline at end of file diff --git "a/Solutions/0023. \345\220\210\345\271\266 K \344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.md" "b/Solutions/0023. \345\220\210\345\271\266 K \344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.md" deleted file mode 100644 index 88256449..00000000 --- "a/Solutions/0023. \345\220\210\345\271\266 K \344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.md" +++ /dev/null @@ -1,91 +0,0 @@ -# [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) - -- 标签:链表、分治、堆(优先队列)、归并排序 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个链表数组,每个链表都已经按照升序排列。 - -**要求**:将所有链表合并到一个升序链表中,返回合并后的链表。 - -**说明**: - -- $k == lists.length$。 -- $0 \le k \le 10^4$。 -- $0 \le lists[i].length \le 500$。 -- $-10^4 \le lists[i][j] \le 10^4$。 -- $lists[i]$ 按升序排列。 -- $lists[i].length$ 的总和不超过 $10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:lists = [[1,4,5],[1,3,4],[2,6]] -输出:[1,1,2,3,4,4,5,6] -解释:链表数组如下: -[ - 1->4->5, - 1->3->4, - 2->6 -] -将它们合并到一个有序链表中得到。 -1->1->2->3->4->4->5->6 -``` - -- 示例 2: - -```python -输入:lists = [] -输出:[] -``` - -## 解题思路 - -### 思路 1:分治算法 - -分而治之的思想。将链表数组不断二分,转为规模为二分之一的子问题,然后再进行归并排序。 - -### 思路 1:代码 - -```python -class Solution: - def merge_sort(self, lists: List[ListNode], left: int, right: int) -> ListNode: - if left == right: - return lists[left] - mid = left + (right - left) // 2 - node_left = self.merge_sort(lists, left, mid) - node_right = self.merge_sort(lists, mid + 1, right) - return self.merge(node_left, node_right) - - def merge(self, a: ListNode, b: ListNode) -> ListNode: - root = ListNode(-1) - cur = root - while a and b: - if a.val < b.val: - cur.next = a - a = a.next - else: - cur.next = b - b = b.next - cur = cur.next - if a: - cur.next = a - if b: - cur.next = b - return root.next - - def mergeKLists(self, lists: List[ListNode]) -> ListNode: - if not lists: - return None - size = len(lists) - return self.merge_sort(lists, 0, size - 1) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(k \times n \times \log_2k)$。 -- **空间复杂度**:$O(\log_2k)$。 - diff --git "a/Solutions/0024. \344\270\244\344\270\244\344\272\244\346\215\242\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" "b/Solutions/0024. \344\270\244\344\270\244\344\272\244\346\215\242\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" deleted file mode 100644 index f30c5b43..00000000 --- "a/Solutions/0024. \344\270\244\344\270\244\344\272\244\346\215\242\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) - -- 标签:递归、链表 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:按顺序将链表中每两个节点交换一下,并返回交换后的链表。 - -**说明**: - -- 需要实际进行节点交换,而不是纸改变节点内部的值。 -- 链表中节点的数目在范围 $[0, 100]$ 内。 -- $0 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/03/swap_ex1.jpg) - -```python -输入:head = [1,2,3,4] -输出:[2,1,4,3] -``` - -- 示例 2: - -```python -输入:head = [] -输出:[] -``` - -## 解题思路 - -### 思路 1:迭代 - -1. 创建一个哑节点 `new_head`,令 `new_head.next = head`。 -2. 遍历链表,并判断当前链表后两位节点是否为空。如果后两个节点不为空,则使用三个指针:`curr` 指向当前节点,`node1` 指向下一个节点,`node2` 指向下面第二个节点。 -3. 将 `curr` 指向 `node2`,`node1` 指向 `node2` 后边的节点,`node2` 指向 `node1`。则节点关系由 `curr → node1 → node2` 变为了 `curr → node2 → node1`。 -4. 依次类推,最终返回哑节点连接的后一个节点。 - -### 思路 1:代码 - -```python -class Solution: - def swapPairs(self, head: ListNode) -> ListNode: - new_head = ListNode(0) - new_head.next = head - curr = new_head - while curr.next and curr.next.next: - node1 = curr.next - node2 = curr.next.next - curr.next = node2 - node1.next = node2.next - node2.next = node1 - curr = node1 - return new_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为链表的节点数量。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0025. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" "b/Solutions/0025. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index b06bb2bb..00000000 --- "a/Solutions/0025. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) - -- 标签:递归、链表 -- 难度:困难 - -## 题目大意 - -**描述**:给你链表的头节点 `head` ,再给定一个正整数 `k`,`k` 的值小于或等于链表的长度。 - -**要求**:每 `k` 个节点一组进行翻转,并返回修改后的链表。如果链表节点总数不是 `k` 的整数倍,则将最后剩余的节点保持原有顺序。 - -**说明**: - -- 不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。 -- 假设链表中的节点数目为 `n`。 -- $1 \le k \le n \le 5000$。 -- $0 \le Node.val \le 1000$。 -- 要求设计一个只用 `O(1)` 额外内存空间的算法解决此问题。 - -**示例**: - -- 示例 1: - -```python -输入:head = [1,2,3,4,5], k = 2 -输出:[2,1,4,3,5] -``` - -## 解题思路 - -### 思路 1:迭代 - -在「[0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)」中我们可以通过迭代、递归两种方法将整个链表反转。而这道题要求以 `k` 为单位,对链表的区间进行反转。而区间反转其实就是「[0092. 反转链表 II ](https://leetcode.cn/problems/reverse-linked-list-ii/)」这道题的题目要求。 - -本题中,我们可以以 `k` 为单位对链表进行切分,然后分别对每个区间部分进行反转。最后再返回头节点即可。 - -但是需要注意一点,如果需要反转的区间包含了链表的第一个节点,那么我们可以事先创建一个哑节点作为链表初始位置开始遍历,这样就能避免找不到需要反转的链表区间的前一个节点。 - -这道题的具体解题步骤如下: - -1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,避免找不到需要反转的链表区间的前一个节点。使用变量 `index` 记录当前元素的序号。 -2. 使用两个指针 `cur`、`tail` 分别表示链表中待反转区间的首尾节点。初始 `cur` 赋值为 `dummy_head`,`tail` 赋值为 `dummy_head.next`,也就是 `head`。 -3. 将 `tail` 向右移动,每移动一步,就领 `index` 加 `1`。 - 1. 当 `index % k != 0` 时,直接将 `tail` 向右移动,直到移动到当前待反转区间的结尾位置。 - 2. 当 `index % k == 0` 时,说明 `tail` 已经移动到了当前待反转区间的结尾位置,此时调用 `cur = self.reverse(cur, tail.next)` ,将待反转区间进行反转,并返回反转后区间的起始节点赋值给当前反转区间的首节点 `cur`。然后将 `tail` 移动到 `cur` 的下一个节点。 -4. 最后返回新的头节点 `dummy_head.next`。 - -关于 `def reverse(self, head, tail):` 方法这里也说下具体步骤: - -1. `head` 代表当前待反转区间的第一个节点的前一个节点,`tail` 代表当前待反转区间的最后一个节点的后一个节点。 -2. 先用 `first` 保存一下待反转区间的第一个节点(反转之后为区间的尾节点),方便反转之后进行连接。 -3. 我们使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置,即 `pre` 指向需要反转节点的前一个节点,`cur` 指向需要反转的节点。初始时,`pre` 指向待反转区间的第一个节点的前一个节点 `head`,`cur` 指向待反转区间的第一个节点,即 `pre.next`。 -4. 当当前节点 `cur` 不等于 `tail` 时,将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: - 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; - 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; - 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; - 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 -5. 继续执行第 `4` 步中的 `1`、`2`、`3`、`4`步。 -6. 最后等到 `cur` 遍历到链表末尾(即 `cur == tail`)时,令「当前待反转区间的第一个节点的前一个节点」指向「反转区间后的头节点」 ,即 `head.next = pre`。令「待反转区间的第一个节点(反转之后为区间的尾节点)」指向「待反转分区间的最后一个节点的后一个节点」,即 `first.next = tail`。 -7. 最后返回新的头节点 `dummy_head.next`。 - -### 思路 1:代码 - -```python -# Definition for singly-linked list. -# class ListNode: -# def __init__(self, val=0, next=None): -# self.val = val -# self.next = next -class Solution: - def reverse(self, head, tail): - pre = head - cur = pre.next - first = cur - while cur != tail: - next = cur.next - cur.next = pre - pre = cur - cur = next - head.next = pre - first.next = tail - return first - - def reverseKGroup(self, head: ListNode, k: int) -> ListNode: - dummy_head = ListNode(0) - dummy_head.next = head - cur = dummy_head - tail = dummy_head.next - index = 0 - while tail: - index += 1 - if index % k == 0: - cur = self.reverse(cur, tail.next) - tail = cur.next - else: - tail = tail.next - return dummy_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为链表的总长度。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0026. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" "b/Solutions/0026. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" deleted file mode 100644 index 0177da0b..00000000 --- "a/Solutions/0026. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [0026. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) - -- 标签:数组、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个有序数组 `nums`。 - -**要求**:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 - -**说明**: - -- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,2] -输出:2, nums = [1,2,_] -解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 -``` - -- 示例 2: - -```python -输入:nums = [0,0,1,1,1,2,2,3,3,4] -输出:5, nums = [0,1,2,3,4] -解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 -``` - -## 解题思路 - -### 思路 1:快慢指针 - -因为数组是有序的,那么重复的元素一定会相邻。 - -删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: - -1. 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向去除重复元素后的数组的末尾位置。`fast` 指向当前元素。 -2. 令 `slow` 在后, `fast` 在前。令 `slow = 0`,`fast = 1`。 -3. 比较 `slow` 位置上元素值和 `fast` 位置上元素值是否相等。 - - 如果不相等,则将 `slow` 后移一位,将 `fast` 指向位置的元素复制到 `slow` 位置上。 -4. 将 `fast` 右移 `1` 位。 -5. 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 -6. 返回 `slow + 1` 即为新数组长度。 - -### 思路 1:代码 - -```python -class Solution: - def removeDuplicates(self, nums: List[int]) -> int: - if len(nums) <= 1: - return len(nums) - - slow, fast = 0, 1 - - while (fast < len(nums)): - if nums[slow] != nums[fast]: - slow += 1 - nums[slow] = nums[fast] - fast += 1 - - return slow + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0027. \347\247\273\351\231\244\345\205\203\347\264\240.md" "b/Solutions/0027. \347\247\273\351\231\244\345\205\203\347\264\240.md" deleted file mode 100644 index 81e6d495..00000000 --- "a/Solutions/0027. \347\247\273\351\231\244\345\205\203\347\264\240.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [0027. 移除元素](https://leetcode.cn/problems/remove-element/) - -- 标签:数组、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $nums$,和一个值 $val$。 - -**要求**:不使用额外数组空间,将数组中所有数值等于 $val$ 值的元素移除掉,并且返回新数组的长度。 - -**说明**: - -- $0 \le nums.length \le 100$。 -- $0 \le nums[i] \le 50$。 -- $0 \le val \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,2,2,3], val = 3 -输出:2, nums = [2,2] -解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。 -``` - -- 示例 2: - -```python -输入:nums = [0,1,2,2,3,0,4,2], val = 2 -输出:5, nums = [0,1,4,0,3] -解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。 -``` - -## 解题思路 - -### 思路 1:快慢指针 - -1. 使用两个指针 $slow$,$fast$。$slow$ 指向处理好的非 $val$ 值元素数组的尾部,$fast$ 指针指向当前待处理元素。 -2. 不断向右移动 $fast$ 指针,每次移动到非 $val$ 值的元素,则将左右指针对应的数交换,交换同时将 $slow$ 右移。 -3. 这样就将非 $val$ 值的元素进行前移,$slow$ 指针左边均为处理好的非 $val$ 值元素,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边都为 $val $值。 -4. 遍历结束之后,则所有 $val$ 值元素都移动到了右侧,且保持了非零数的相对位置。此时 $slow$ 就是新数组的长度。 - -### 思路 1:代码 - -```python -class Solution: - def removeElement(self, nums: List[int], val: int) -> int: - slow = 0 - fast = 0 - while fast < len(nums): - if nums[fast] != val: - nums[slow], nums[fast] = nums[fast], nums[slow] - slow += 1 - fast += 1 - return slow -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0028. \346\211\276\345\207\272\345\255\227\347\254\246\344\270\262\344\270\255\347\254\254\344\270\200\344\270\252\345\214\271\351\205\215\351\241\271\347\232\204\344\270\213\346\240\207.md" "b/Solutions/0028. \346\211\276\345\207\272\345\255\227\347\254\246\344\270\262\344\270\255\347\254\254\344\270\200\344\270\252\345\214\271\351\205\215\351\241\271\347\232\204\344\270\213\346\240\207.md" deleted file mode 100644 index 0209e66a..00000000 --- "a/Solutions/0028. \346\211\276\345\207\272\345\255\227\347\254\246\344\270\262\344\270\255\347\254\254\344\270\200\344\270\252\345\214\271\351\205\215\351\241\271\347\232\204\344\270\213\346\240\207.md" +++ /dev/null @@ -1,380 +0,0 @@ -# [0028. 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) - -- 标签:双指针、字符串、字符串匹配 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个字符串 `haystack` 和 `needle`。 - -**要求**:在 `haystack` 字符串中找出 `needle` 字符串出现的第一个位置(从 `0` 开始)。如果不存在,则返回 `-1`。 - -**说明**: - -- 当 `needle` 为空字符串时,返回 `0`。 -- $1 \le haystack.length, needle.length \le 10^4$。 -- `haystack` 和 `needle` 仅由小写英文字符组成。 - -**示例**: - -- 示例 1: - -```python -输入:haystack = "hello", needle = "ll" -输出:2 -解释:"sad" 在下标 0 和 6 处匹配。第一个匹配项的下标是 0 ,所以返回 0 。 -``` - -- 示例 2: - -```python -输入:haystack = "leetcode", needle = "leeto" -输出:-1 -解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。 -``` - -## 解题思路 - -字符串匹配的经典题目。常见的字符串匹配算法有:BF(Brute Force)算法、RK(Robin-Karp)算法、KMP(Knuth Morris Pratt)算法、BM(Boyer Moore)算法、Horspool 算法、Sunday 算法等。 - -### 思路 1:BF(Brute Force)算法 - -**BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 - -BF 算法具体步骤如下: - -1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 - 1. 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 - 2. 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 -3. 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 - -### 思路 1:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - i = 0 - j = 0 - len1 = len(haystack) - len2 = len(needle) - - while i < len1 and j < len2: - if haystack[i] == needle[j]: - i += 1 - j += 1 - else: - i = i - (j - 1) - j = 0 - - if j == len2: - return i - j - else: - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:平均时间复杂度为 $O(n + m)$,最坏时间复杂度为 $O(m \times n)$。其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:RK(Robin Karp)算法 - -**RK 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 - -RK 算法具体步骤如下: - -1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 -3. 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 -4. 然后逐个与模式串的哈希值比较大小。 - 1. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 - 2. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 - 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 - 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 -5. 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 - -### 思路 2:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - def rabinKarp(T: str, p: str) -> int: - len1, len2 = len(T), len(p) - - hash_p = hash(p) - for i in range(len1 - len2 + 1): - hash_T = hash(T[i: i + len2]) - if hash_p != hash_T: - continue - k = 0 - for j in range(len2): - if T[i + j] != p[j]: - break - k += 1 - if k == len2: - return i - return -1 - return rabinKarp(haystack, needle) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 -- **空间复杂度**:$O(m)$。 - -### 思路 3:KMP(Knuth Morris Pratt)算法 - -**KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 - -KMP 算法具体步骤如下: - -1. 根据 `next` 数组的构造步骤生成「前缀表」`next`。 -2. 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 -3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 -4. 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 -5. 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 -6. 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 -7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 - -### 思路 3:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - # KMP 匹配算法,T 为文本串,p 为模式串 - def kmp(T: str, p: str) -> int: - n, m = len(T), len(p) - - next = generateNext(p) # 生成 next 数组 - - i, j = 0, 0 - while i < n and j < m: - if j == -1 or T[i] == p[j]: - i += 1 - j += 1 - else: - j = next[j] - if j == m: - return i - j - - return -1 - - # 生成 next 数组 - # next[i] 表示坏字符在模式串中最后一次出现的位置 - def generateNext(p: str): - m = len(p) - - next = [-1 for _ in range(m)] # 初始化数组元素全部为 -1 - i, k = 0, -1 - while i < m - 1: # 生成下一个 next 元素 - if k == -1 or p[i] == p[k]: - i += 1 - k += 1 - if p[i] == p[k]: - next[i] = next[k] # 设置 next 元素 - else: - next[i] = k # 退到更短相同前缀 - else: - k = next[k] - return next - - return kmp(haystack, needle) -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 -- **空间复杂度**:$O(m)$。 - -### 思路 4:BM(Boyer Moore)算法 - -**BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -BM 算法具体步骤如下: - -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 - 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: - 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 - 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 - 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -### 思路 4:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - def boyerMoore(T: str, p: str) -> int: - n, m = len(T), len(p) - - bc_table = generateBadCharTable(p) # 生成坏字符位置表 - gs_list = generageGoodSuffixList(p) # 生成好后缀规则后移位数表 - - i = 0 - while i <= n - m: - j = m - 1 - while j > -1 and T[i + j] == p[j]: - j -= 1 - if j < 0: - return i - bad_move = j - bc_table.get(T[i + j], -1) - good_move = gs_list[j] - i += max(bad_move, good_move) - return -1 - - # 生成坏字符位置表 - def generateBadCharTable(p: str): - bc_table = dict() - - for i in range(len(p)): - bc_table[p[i]] = i # 坏字符在模式串中最后一次出现的位置 - return bc_table - - # 生成好后缀规则后移位数表 - def generageGoodSuffixList(p: str): - m = len(p) - gs_list = [m for _ in range(m)] - suffix = generageSuffixArray(p) - j = 0 - for i in range(m - 1, -1, -1): - if suffix[i] == i + 1: - while j < m - 1 - i: - if gs_list[j] == m: - gs_list[j] = m - 1 - i - j += 1 - - for i in range(m - 1): - gs_list[m - 1 - suffix[i]] = m - 1 - i - - return gs_list - - def generageSuffixArray(p: str): - m = len(p) - suffix = [m for _ in range(m)] - for i in range(m - 2, -1, -1): - start = i - while start >= 0 and p[start] == p[m - 1 - i + start]: - start -= 1 - suffix[i] = i - start - return suffix - - return boyerMoore(haystack, needle) -``` - -### 思路 4:复杂度分析 - -- **时间复杂度**:$O(n + \sigma)$,其中文本串 $T$ 的长度为 $n$,字符集的大小是 $\sigma$。 -- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 - -### 思路 5:Horspool 算法 - -**Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -Horspool 算法具体步骤如下: - -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 - 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -### 思路 5:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - def horspool(T: str, p: str) -> int: - n, m = len(T), len(p) - - bc_table = generateBadCharTable(p) - - i = 0 - while i <= n - m: - j = m - 1 - while j > -1 and T[i + j] == p[j]: - j -= 1 - if j < 0: - return i - i += bc_table.get(T[i + m - 1], m) - return -1 - - # 生成后移位置表 - # bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 - def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m - 1): - bc_table[p[i]] = m - i - 1 # 更新坏字符在模式串中最后一次出现的位置 - return bc_table - - return horspool(haystack, needle) -``` - -### 思路 5:复杂度分析 - -- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 -- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 - -### 思路 6:Sunday 算法 - -**Sunday 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 - -Sunday 算法具体步骤如下: - -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 - 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 - -### 思路 6:代码 - -```python -class Solution: - def strStr(self, haystack: str, needle: str) -> int: - # sunday 算法,T 为文本串,p 为模式串 - def sunday(T: str, p: str) -> int: - n, m = len(T), len(p) - if m == 0: - return 0 - - bc_table = generateBadCharTable(p) # 生成后移位数表 - - i = 0 - while i <= n - m: - if T[i: i + m] == p: - return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 - if i + m >= n: - return -1 - i += bc_table.get(T[i + m], m + 1) # 通过后移位数表,向右进行进行快速移动 - return -1 # 匹配失败 - - # 生成后移位数表 - # bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 - def generateBadCharTable(p: str): - m = len(p) - bc_table = dict() - - for i in range(m): - bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 - return bc_table - - return sunday(haystack, needle) -``` - -### 思路 6:复杂度分析 - -- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 -- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 \ No newline at end of file diff --git "a/Solutions/0029. \344\270\244\346\225\260\347\233\270\351\231\244.md" "b/Solutions/0029. \344\270\244\346\225\260\347\233\270\351\231\244.md" deleted file mode 100644 index cf0c3994..00000000 --- "a/Solutions/0029. \344\270\244\346\225\260\347\233\270\351\231\244.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [0029. 两数相除](https://leetcode.cn/problems/divide-two-integers/) - -- 标签:位运算、数学 -- 难度:中等 - -## 题目大意 - -给定两个整数,被除数 dividend 和除数 divisor。要求返回两数相除的商,并且不能使用乘法,除法和取余运算。取值范围在 $[-2^{31}, 2^{31}-1]$。如果结果溢出,则返回 $2^{31} - 1$。 - -## 解题思路 - -题目要求不能使用乘法,除法和取余运算。 - -可以把被除数和除数当做二进制,这样进行运算的时候,就可以通过移位运算来实现二进制的乘除。 - -- 先将除数不断左移,移位到位数大于或等于被除数。记录其移位次数 count。 - -- 然后再将除数右移 count 次,模拟二进制除法运算。 - - 如果当前被除数大于等于除数,则将 1 左移 count 位,即为当前位的商,并将其累加答案上。再用除数减去被除数,进行下一次运算。 - - - -## 代码 - -```python -class Solution: - def divide(self, dividend: int, divisor: int) -> int: - MIN_INT, MAX_INT = -2147483648, 2147483647 - # 标记被除数和除数是否异号 - symbol = True if (dividend ^ divisor) < 0 else False - # 将被除数和除数转换为正数处理 - if dividend < 0: - dividend = -dividend - if divisor < 0: - divisor = -divisor - - # 除数不断左移,移位到位数大于或等于被除数 - count = 0 - while dividend >= divisor: - count += 1 - divisor <<= 1 - - # 向右移位,不断模拟二进制除法运算 - res = 0 - while count > 0: - count -= 1 - divisor >>= 1 - if dividend >= divisor: - res += (1 << count) - dividend -= divisor - if symbol: - res = -res - if MIN_INT <= res <= MAX_INT: - return res - else: - return MAX_INT -``` - diff --git "a/Solutions/0032. \346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.md" "b/Solutions/0032. \346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.md" deleted file mode 100644 index 3ecd5322..00000000 --- "a/Solutions/0032. \346\234\200\351\225\277\346\234\211\346\225\210\346\213\254\345\217\267.md" +++ /dev/null @@ -1,142 +0,0 @@ -# [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) - -- 标签:栈、字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个只包含 `'('` 和 `')'` 的字符串。 - -**要求**:找出最长有效(格式正确且连续)括号子串的长度。 - -**说明**: - -- $0 \le s.length \le 3 * 10^4$。 -- `s[i]` 为 `'('` 或 `')'`。 - -**示例**: - -- 示例 1: - -```python -输入:s = "(()" -输出:2 -解释:最长有效括号子串是 "()" -``` - -- 示例 2: - -```python -输入:s = ")()())" -输出:4 -解释:最长有效括号子串是 "()()" -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照最长有效括号子串的结束位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:以字符 `s[i]` 为结尾的最长有效括号的长度。 - -###### 3. 状态转移方程 - -- 如果 `s[i] == '('`,此时以 `s[i]` 结尾的子串不可能构成有效括号对,则 `dp[i] = 0`。 -- 如果 `s[i] == ')'`,我们需要考虑 `s[i - 1]` 来判断是否能够构成有效括号对。 - - 如果 `s[i - 1] == '('`,字符串形如 `......()`,此时 `s[i - 1]` 与 `s[i]` 为 `()`,则: - - `dp[i]` 取决于「以字符 `s[i - 2]` 为结尾的最长有效括号长度」 + 「`s[i - 1]` 与 `s[i]` 构成的有效括号对长度(`2`)」,即 `dp[i] = dp[i - 2] + 2`。 - - 特别地,如果 `s[i - 2]` 不存在,即 `i - 2 < 0`,则 `dp[i]` 直接取决于 「`s[i - 1]` 与 `s[i]` 构成的有效括号对长度(`2`)」,即 `dp[i] = 2`。 - - 如果 `s[i - 1] == ')'`,字符串形如 `......))`,此时 `s[i - 1]` 与 `s[i]` 为 `))`。那么以 `s[i - 1]` 为结尾的最长有效长度为 `dp[i - 1]`,则我们需要看 `i - 1 - dp[i - 1]` 位置上的字符 `s[i - 1 - dp[i - 1]]`是否与 `s[i]` 匹配。 - - 如果 `s[i - 1 - dp[i - 1]] == '('`,则说明 `s[i - 1 - dp[i - 1]]`与 `s[i]` 相匹配,此时我们需要看以 `s[i - 1 - dp[i - 1]]` 的前一个字符 `s[i - 1 - dp[i - 2]]` 为结尾的最长括号长度是多少,将其加上 ``s[i - 1 - dp[i - 1]]`与 `s[i]`,从而构成更长的有效括号对: - - `dp[i]` 取决于「以字符 `s[i - 1]` 为结尾的最长括号长度」 + 「以字符 `s[i - 1 - dp[i - 2]]` 为结尾的最长括号长度」+ 「`s[i - 1 - dp[i - 1]]` 与 `s[i]` 的长度(`2`)」,即 `dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2`。 - - 特别地,如果 `s[i - dp[i - 1] - 2]` 不存在,即 `i - dp[i - 1] - 2 < 0`,则 `dp[i]` 直接取决于「以字符 `s[i - 1]` 为结尾的最长括号长度」+「`s[i - 1 - dp[i - 1]]` 与 `s[i]` 的长度(`2`)」,即 `dp[i] = dp[i - 1] + 2`。 - -###### 4. 初始条件 - -- 默认所有以字符 `s[i]` 为结尾的最长有效括号的长度为 `0`,即 `dp[i] = 0`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:以字符 `s[i]` 为结尾的最长有效括号的长度。则最终结果为 `max(dp[i])`。 - -### 思路 1:代码 - -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - dp = [0 for _ in range(len(s))] - ans = 0 - for i in range(1, len(s)): - if s[i] == '(': - continue - if s[i - 1] == '(': - if i >= 2: - dp[i] = dp[i - 2] + 2 - else: - dp[i] = 2 - elif i - dp[i - 1] > 0 and s[i - dp[i - 1] - 1] == '(': - if i - dp[i - 1] >= 2: - dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2 - else: - dp[i] = dp[i - 1] + 2 - ans = max(ans, dp[i]) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为字符串长度。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:栈 - -1. 定义一个变量 `ans` 用于维护最长有效括号的长度,初始时,`ans = 0`。 -2. 定义一个栈用于判定括号对是否匹配(栈中存储的是括号的下标),栈底元素始终保持「最长有效括号子串的开始元素的前一个元素下标」。 -3. 初始时,我们在栈中存储 `-1` 作为哨兵节点,表示「最长有效括号子串的开始元素的前一个元素下标为 `-1`」,即 `stack = [-1]`, -4. 然后从左至右遍历字符串。 - 1. 如果遇到左括号,即 `s[i] == '('`,则将其下标 `i` 压入栈,用于后续匹配右括号。 - 2. 如果遇到右括号,即 `s[i] == ')'`,则将其与最近的左括号进行匹配(即栈顶元素),弹出栈顶元素,与当前右括号进行匹配。弹出之后: - 1. 如果栈为空,则说明: - 1. 之前弹出的栈顶元素实际上是「最长有效括号子串的开始元素的前一个元素下标」,而不是左括号`(`,此时无法完成合法匹配。 - 2. 将当前右括号的坐标 `i` 压入栈中,充当「下一个有效括号子串的开始元素前一个下标」。 - 2. 如果栈不为空,则说明: - 1. 之前弹出的栈顶元素为左括号 `(`,此时可完成合法匹配。 - 2. 当前合法匹配的长度为「当前右括号的下标 `i`」 - 「最长有效括号子串的开始元素的前一个元素下标」。即 `i - stack[-1]`。 - 3. 更新最长匹配长度 `ans` 为 `max(ans, i - stack[-1])`。 -5. 遍历完输出答案 `ans`。 - -### 思路 2:代码 - -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - stack = [-1] - ans = 0 - for i in range(len(s)): - if s[i] == '(': - stack.append(i) - else: - stack.pop() - if stack: - ans = max(ans, i - stack[-1]) - else: - stack.append(i) - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为字符串长度。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[动态规划思路详解(C++)——32.最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/solutions/206995/dong-tai-gui-hua-si-lu-xiang-jie-c-by-zhanganan042/) -- 【题解】[32. 最长有效括号 - 力扣(Leetcode)](https://leetcode.cn/problems/longest-valid-parentheses/solutions/314683/zui-chang-you-xiao-gua-hao-by-leetcode-solution/) -- 【题解】[【Nick~Hot一百题系列】超简单思路栈!](https://leetcode.cn/problems/longest-valid-parentheses/solutions/1258643/nickhotyi-bai-ti-xi-lie-chao-jian-dan-si-ggi4/) \ No newline at end of file diff --git "a/Solutions/0033. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.md" "b/Solutions/0033. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.md" deleted file mode 100644 index af91e2de..00000000 --- "a/Solutions/0033. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204.md" +++ /dev/null @@ -1,105 +0,0 @@ -# [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$,数组中值互不相同。给定的 $nums$ 是经过升序排列后的又进行了「旋转」操作的。再给定一个整数 $target$。 - -**要求**:从 $nums$ 中找到 $target$ 所在位置,如果找到,则返回对应下标,找不到则返回 $-1$。 - -**说明**: - -- 旋转操作:升序排列的数组 nums 在预先未知的第 k 个位置进行了右移操作,变成了 $[nums[k]], nums[k+1], ... , nums[n-1], ... , nums[0], nums[1], ... , nums[k-1]$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [4,5,6,7,0,1,2], target = 0 -输出:4 -``` - -- 示例 2: - -```python -输入:nums = [4,5,6,7,0,1,2], target = 3 -输出:-1 -``` - -## 解题思路 - -### 思路 1:二分查找 - -原本为升序排列的数组 $nums$ 经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 - -```python - * - * - * - * - * -* -``` - -```python - * - * -* - * - * - * -``` - -最直接的办法就是遍历一遍,找到目标值 $target$。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 - -我们将旋转后的数组看成左右两个升序部分:左半部分和右半部分。 - -有人会说第一种情况不是只有一个部分吗?其实我们可以把第一种情况中的整个数组看做是左半部分,然后右半部分为空数组。 - -然后创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较,并考虑与 $target$ 的关系。 - -- 如果 $mid[mid] == target$,说明找到了 $target$,直接返回下标。 -- 如果 $nums[mid] \ge nums[left]$,则 $mid$ 在左半部分(因为右半部分值都比 $nums[left]$ 小)。 - - 如果 $nums[mid] \ge target$,并且 $target \ge nums[left]$,则 $target$ 在左半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 - - 否则如果 $nums[mid] \le target$,则 $target$ 在左半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 - - 否则如果 $nums[left] > target$,则 $target$ 在右半部分,应将 $left$ 移动到 $mid + 1$ 位置。 - -- 如果 $nums[mid] < nums[left]$,则 $mid$ 在右半部分(因为右半部分值都比 $nums[left]$ 小)。 - - 如果 $nums[mid] < target$,并且 $target \le nums[right]$,则 $target$ 在右半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 - - 否则如果 $nums[mid] \ge target$,则 $target$ 在右半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 - - 否则如果 $nums[right] < target$,则 $target$ 在左半部分,应将 $right$ 左移到 $mid - 1$ 位置。 - -### 思路 1:代码 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left = 0 - right = len(nums) - 1 - while left <= right: - mid = left + (right - left) // 2 - if nums[mid] == target: - return mid - - if nums[mid] >= nums[left]: - if nums[mid] > target and target >= nums[left]: - right = mid - 1 - else: - left = mid + 1 - else: - if nums[mid] < target and target <= nums[right]: - left = mid + 1 - else: - right = mid - 1 - - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0034. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.md" "b/Solutions/0034. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.md" deleted file mode 100644 index 7a8ed441..00000000 --- "a/Solutions/0034. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给你一个按照非递减顺序排列的整数数组 `nums`,和一个目标值 `target`。 - -**要求**:找出给定目标值在数组中的开始位置和结束位置。 - -**说明**: - -- 要求使用时间复杂度为 $O(\log n)$ 的算法解决问题。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [5,7,7,8,8,10], target = 8 -输出:[3,4] -``` - -- 示例 2: - -```python -输入:nums = [5,7,7,8,8,10], target = 6 -输出:[-1,-1] -``` - -## 解题思路 - -### 思路 1:二分查找 - -要求使用时间复杂度为 $O(\log n)$ 的算法解决问题,那么就需要使用「二分查找算法」了。 - -- 进行两次二分查找,第一次尽量向左搜索。第二次尽量向右搜索。 - -### 思路 1:代码 - -```python -class Solution: - def searchRange(self, nums: List[int], target: int) -> List[int]: - ans = [-1, -1] - n = len(nums) - if n == 0: - return ans - - left = 0 - right = n - 1 - while left < right: - mid = left + (right - left) // 2 - if nums[mid] < target: - left = mid + 1 - else: - right = mid - - if nums[left] != target: - return ans - - ans[0] = left - - left = 0 - right = n - 1 - while left < right: - mid = left + (right - left + 1) // 2 - if nums[mid] > target: - right = mid - 1 - else: - left = mid - - if nums[left] == target: - ans[1] = left - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log_2 n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0035. \346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.md" "b/Solutions/0035. \346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.md" deleted file mode 100644 index e1de1996..00000000 --- "a/Solutions/0035. \346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [0035. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个排好序的数组 $nums$,以及一个目标值 $target$。 - -**要求**:在数组中找到目标值,并返回下标。如果找不到,则返回目标值按顺序插入数组的位置。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $-10^4 \le nums[i] \le 10^4$。 -- $nums$ 为无重复元素的升序排列数组。 -- $-10^4 \le target \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,3,5,6], target = 5 -输出:2 -``` - -## 解题思路 - -### 思路 1:二分查找 - -设定左右节点为数组两端,即 `left = 0`,`right = len(nums) - 1`,代表待查找区间为 $[left, right]$(左闭右闭)。 - -取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 - -- 如果 $target == nums[mid]$,则当前中心位置为待插入数组的位置。 -- 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 -- 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 - -直到查找到目标值返回待插入数组的位置,或者等到 $left > right$ 时停止查找,此时 $left$ 所在位置就是待插入数组的位置。 - -### 思路 1:二分查找代码 - -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - size = len(nums) - left, right = 0, size - 1 - - while left <= right: - mid = left + (right - left) // 2 - if nums[mid] == target: - return mid - elif nums[mid] < target: - left = mid + 1 - else: - right = mid - 1 - - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0036. \346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.md" "b/Solutions/0036. \346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.md" deleted file mode 100644 index 77b17741..00000000 --- "a/Solutions/0036. \346\234\211\346\225\210\347\232\204\346\225\260\347\213\254.md" +++ /dev/null @@ -1,84 +0,0 @@ -# [0036. 有效的数独](https://leetcode.cn/problems/valid-sudoku/) - -- 标签:数组、哈希表、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数独,用 `9 * 9` 的二维字符数组 `board` 来表示,其中,未填入的空白用 "." 代替。 - -**要求**:判断该数独是否是一个有效的数独。 - -**说明**: - -- 一个有效的数独(部分已被填充)不一定是可解的。 -- 只需要根据以上规则,验证已经填入的数字是否有效即可。 -- 空白格用 `'.'` 表示。 - -一个有效的数独需满足: - -1. 数字 `1-9` 在每一行只能出现一次。 -2. 数字 `1-9` 在每一列只能出现一次。 -3. 数字 `1-9` 在每一个以粗实线分隔的 `3 * 3` 宫内只能出现一次。(请参考示例图) - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714svg.png) - -```python -输入:board = -[["5","3",".",".","7",".",".",".","."] -,["6",".",".","1","9","5",".",".","."] -,[".","9","8",".",".",".",".","6","."] -,["8",".",".",".","6",".",".",".","3"] -,["4",".",".","8",".","3",".",".","1"] -,["7",".",".",".","2",".",".",".","6"] -,[".","6",".",".",".",".","2","8","."] -,[".",".",".","4","1","9",".",".","5"] -,[".",".",".",".","8",".",".","7","9"]] -输出:True -``` - -## 解题思路 - -### 思路 1:哈希表 - -判断数独有效,需要分别看每一行、每一列、每一个 `3 * 3` 的小方格是否出现了重复数字,如果都没有出现重复数字就是一个有效的数独,如果出现了重复数字则不是有效的数独。 - -- 用 `3` 个 `9 * 9` 的数组分别来表示该数字是否在所在的行,所在的列,所在的方格出现过。其中方格角标的计算用 `box[(i / 3) * 3 + (j / 3)][n]` 来表示。 -- 双重循环遍历数独矩阵。如果对应位置上的数字如果已经在在所在的行 / 列 / 方格出现过,则返回 `False`。 -- 遍历完没有重复出现,则返回 `Ture`。 - -### 思路 1:代码 - -```python -class Solution: - def isValidSudoku(self, board: List[List[str]]) -> bool: - rows_map = [dict() for _ in range(9)] - cols_map = [dict() for _ in range(9)] - boxes_map = [dict() for _ in range(9)] - - for i in range(9): - for j in range(9): - if board[i][j] == '.': - continue - num = int(board[i][j]) - box_index = (i // 3) * 3 + j // 3 - row_num = rows_map[i].get(num, 0) - col_num = cols_map[j].get(num, 0) - box_num = boxes_map[box_index].get(num, 0) - if row_num > 0 or col_num > 0 or box_num > 0: - return False - rows_map[i][num] = 1 - cols_map[j][num] = 1 - boxes_map[box_index][num] = 1 - - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。数独总共 81 个单元格,对每个单元格遍历一次,可以看做是常数级的时间复杂度。 -- **空间复杂度**:$O(1)$。使用 81 个单位空间,可以看做是常数级的空间复杂度。 diff --git "a/Solutions/0037. \350\247\243\346\225\260\347\213\254.md" "b/Solutions/0037. \350\247\243\346\225\260\347\213\254.md" deleted file mode 100644 index f103e6f0..00000000 --- "a/Solutions/0037. \350\247\243\346\225\260\347\213\254.md" +++ /dev/null @@ -1,72 +0,0 @@ -# [0037. 解数独](https://leetcode.cn/problems/sudoku-solver/) - -- 标签:数组、哈希表、回溯、矩阵 -- 难度:困难 - -## 题目大意 - -给定一个二维的字符数组 `board` 用来表示数独,其中数字 `1-9` 表示该位置已经填入了数字,`.` 表示该位置还没有填入数字。 - -现在编写一个程序,通过填充空格的方式来解决数独问题,最终不用返回答案,将题目给定 `board` 修改为可行的方案即可。 - -数独解法需遵循如下规则: - -- 数字 `1-9` 在每一行只能出现一次。 -- 数字 `1-9` 在每一列只能出现一次。 -- 数字 `1-9` 在每一个以粗直线分隔的 `3 * 3` 宫格内只能出现一次。 - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714_solutionsvg.png) - -## 解题思路 - -使用回溯算法求解。对于每一行、每一列、每一个数字,都需要 1 重 for 循环来遍历,这样就是 3 重 for 循环。 - -对于第 i 行、第 j 列的元素来说,如果当前位置为空位,则尝试将第 k 个数字置于此处,并检验数独的有效性。如果有效,则继续遍历下一个空位,直到遍历完所有空位,得到可行方案或者遍历失败时结束。 - -遍历完下一个空位之后再将此位置进行回退,置为 `.`。 - - - -## 代码 - -```python -class Solution: - def backtrack(self, board: List[List[str]]): - for i in range(len(board)): - for j in range(len(board[0])): - if board[i][j] != '.': - continue - for k in range(1, 10): - if self.isValid(i, j, k, board): - board[i][j] = str(k) - if self.backtrack(board): - return True - board[i][j] = '.' - return False - return True - - def isValid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool: - for i in range(0, 9): - if board[row][i] == str(val): - return False - - for j in range(0, 9): - if board[j][col] == str(val): - return False - - start_row = (row // 3) * 3 - start_col = (col // 3) * 3 - - for i in range(start_row, start_row + 3): - for j in range(start_col, start_col + 3): - if board[i][j] == str(val): - return False - return True - - def solveSudoku(self, board: List[List[str]]) -> None: - self.backtrack(board) - """ - Do not return anything, modify board in-place instead. - """ -``` - diff --git "a/Solutions/0038. \345\244\226\350\247\202\346\225\260\345\210\227.md" "b/Solutions/0038. \345\244\226\350\247\202\346\225\260\345\210\227.md" deleted file mode 100644 index 89ead2f6..00000000 --- "a/Solutions/0038. \345\244\226\350\247\202\346\225\260\345\210\227.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0038. 外观数列](https://leetcode.cn/problems/count-and-say/) - -- 标签:字符串 -- 难度:中等 - -## 题目大意 - -给定一个正整数 n,$(1 \le n \le 30)$,要求输出外观数列的第 n 项。 - -外观数列:整数序列,数字由 1 开始,每一项都是对前一项的描述 - -例如: - -| 1. | 1 | 由 1 开始 | -| --- | ---: | ------------------- | -| 2. | 11 | 表示 1 个 1 | -| 3. | 21 | 表示 2 个 1 | -| 4. | 1211 | 表示 1 个 1,1 个 2 | - - - -## 解题思路 - -模拟题目遍历求解。 - -将 ans 设为 "1",每次遍历判断相邻且相同的数字有多少个,再将 ans 拼接上「数字个数 + 数字」。 - -## 代码 - -```python -class Solution: - def countAndSay(self, n: int) -> str: - ans = "1" - - for _ in range(1, n): - s = "" - start = 0 - for i in range(len(ans)): - if ans[i] != ans[start]: - s += str(i-start) + ans[start] - start = i - s += str(len(ans)-start) + ans[start] - ans = s - return ans -``` - diff --git "a/Solutions/0039. \347\273\204\345\220\210\346\200\273\345\222\214.md" "b/Solutions/0039. \347\273\204\345\220\210\346\200\273\345\222\214.md" deleted file mode 100644 index 7f0608fc..00000000 --- "a/Solutions/0039. \347\273\204\345\220\210\346\200\273\345\222\214.md" +++ /dev/null @@ -1,126 +0,0 @@ -# [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个无重复元素的正整数数组 `candidates` 和一个正整数 `target`。 - -**要求**:找出 `candidates` 中所有可以使数字和为目标数 `target` 的所有不同组合,并以列表形式返回。可以按照任意顺序返回这些组合。 - -**说明**: - -- 数组 `candidates` 中的数字可以无限重复选取。 -- 如果至少一个数字的被选数量不同,则两种组合是不同的。 -- $1 \le candidates.length \le 30$。 -- $2 \le candidates[i] \le 40$。 -- `candidates` 的所有元素互不相同。 -- $1 \le target \le 40$。 - -**示例**: - -- 示例 1: - -```python -输入:candidates = [2,3,6,7], target = 7 -输出:[[2,2,3],[7]] -解释: -2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 -7 也是一个候选, 7 = 7 。 -仅有这两种组合。 -``` - -- 示例 2: - -```python -输入: candidates = [2,3,5], target = 8 -输出: [[2,2,2,2],[2,3,3],[3,5]] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -定义回溯方法,start_index = 1 开始进行回溯。 - -- 如果 `sum > target`,则直接返回。 -- 如果 `sum == target`,则将 path 中的元素加入到 res 数组中。 -- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 - - 如果 `sum + candidates[i] > target`,可以直接跳出循环。 - - 将和累积,即 `sum += candidates[i]`,然后将当前元素 i 加入 path 数组。 - - 递归遍历 `[start_index, n]` 上的数。 - - 加之前的和回退,即 `sum -= candidates[i]`,然后将遍历的 i 元素进行回退。 -- 最终返回 res 数组。 - -根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:一个组合每个位置上的元素都可以从剩余可选元素中选出。 - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - `backtrack(total, start_index):` 函数的传入参数是 `total`(当前和)、`start_index`(剩余可选元素开始位置),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 - - `backtrack(total, start_index):` 函数代表的含义是:当前组合和为 `total`,递归从 `candidates` 的 `start_index` 位置开始,选择剩下的元素。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: - - 约束条件:之前已经选择的元素不再重复选用,只能从剩余元素中选择。 - - 选择元素:将其添加到当前数组 `path` 中。 - - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 - - 撤销选择:将该元素从当前结果数组 `path` 中移除。 - - ```python - for i in range(start_index, len(candidates)): - if total + candidates[i] > target: - break - - total += candidates[i] - path.append(candidates[i]) - backtrack(total, i) - total -= candidates[i] - path.pop() - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当不可能再出现解(`total > target`),或者遍历到决策树的叶子节点时(`total == target`)时,就终止了。 - - 当遍历到决策树的叶子节点时(`total == target`)时,将当前结果的数组 `path` 放入答案数组 `res` 中,递归停止。 - -### 思路 1:代码 - -```python -class Solution: - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - res = [] - path = [] - def backtrack(total, start_index): - if total > target: - return - - if total == target: - res.append(path[:]) - return - - for i in range(start_index, len(candidates)): - if total + candidates[i] > target: - break - - total += candidates[i] - path.append(candidates[i]) - backtrack(total, i) - total -= candidates[i] - path.pop() - candidates.sort() - backtrack(0, 0) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 `candidates` 的元素个数,$2^n$ 指的是所有状态数。 -- **空间复杂度**:$O(target)$,递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $O(target)$,所以空间复杂度为 $O(target)$。 - diff --git "a/Solutions/0040. \347\273\204\345\220\210\346\200\273\345\222\214 II.md" "b/Solutions/0040. \347\273\204\345\220\210\346\200\273\345\222\214 II.md" deleted file mode 100644 index 0f9b1024..00000000 --- "a/Solutions/0040. \347\273\204\345\220\210\346\200\273\345\222\214 II.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [0040. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 `candidates` 和一个目标数 `target`。 - -**要求**:找出 `candidates` 中所有可以使数字和为目标数 `target` 的组合。 - -**说明**: - -- 数组 `candidates` 中的数字在每个组合中只能使用一次。 -- $1 \le candidates.length \le 100$。 -- $1 \le candidates[i] \le 50$。 - -**示例**: - -- 示例 1: - -```python -输入: candidates = [10,1,2,7,6,1,5], target = 8, -输出: -[ -[1,1,6], -[1,2,5], -[1,7], -[2,6] -] -``` - -- 示例 2: - -```python -输入: candidates = [2,5,2,1,2], target = 5, -输出: -[ -[1,2,2], -[5] -] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -跟「[0039. 组合总和](https://leetcode.cn/problems/combination-sum/)」不一样的地方在于本题不能有重复组合,所以关键步骤在于去重。 - -在回溯遍历的时候,下一层递归的 `start_index` 要从当前节点的后一位开始遍历,即 `i + 1` 位开始。而且统一递归层不能使用相同的元素,即需要增加一句判断 `if i > start_index and candidates[i] == candidates[i - 1]: continue`。 - -### 思路 1:代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): - if sum > target: - return - if sum == target: - self.res.append(self.path[:]) - return - - for i in range(start_index, len(candidates)): - if sum + candidates[i] > target: - break - if i > start_index and candidates[i] == candidates[i - 1]: - continue - sum += candidates[i] - self.path.append(candidates[i]) - self.backtrack(candidates, target, sum, i + 1) - sum -= candidates[i] - self.path.pop() - - def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: - self.res.clear() - self.path.clear() - candidates.sort() - self.backtrack(candidates, target, 0, 0) - return self.res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 `candidates` 的元素个数,$2^n$ 指的是所有状态数。 -- **空间复杂度**:$O(target)$,递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $O(target)$,所以空间复杂度为 $O(target)$。 - diff --git "a/Solutions/0041. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260.md" "b/Solutions/0041. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260.md" deleted file mode 100644 index 04695407..00000000 --- "a/Solutions/0041. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) - -- 标签:数组、哈希表 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个未排序的整数数组 `nums`。 - -**要求**:找出其中没有出现的最小的正整数。 - -**说明**: - -- $1 \le nums.length \le 5 * 10^5$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- 要求实现时间复杂度为 `O(n)` 并且只使用常数级别额外空间的解决方案。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,0] -输出:3 -``` - -- 示例 2: - -```python -输入:nums = [3,4,-1,1] -输出:2 -``` - -## 解题思路 - -### 思路 1:哈希表、原地哈希 - -如果使用普通的哈希表,我们只需要遍历一遍数组,将对应整数存入到哈希表中,再从 `1` 开始,依次判断对应正数是否在哈希表中即可。但是这种做法的空间复杂度为 $O(n)$,不满足常数级别的额外空间要求。 - -我们可以将当前数组视为哈希表。一个长度为 `n` 的数组,对应存储的元素值应该为 `[1, n + 1]` 之间,其中还包含一个缺失的元素。 - -1. 我们可以遍历一遍数组,将当前元素放到其对应位置上(比如元素值为 `1` 的元素放到数组第 `0` 个位置上、元素值为 `2` 的元素放到数组第 `1` 个位置上,等等)。 -2. 然后再次遍历一遍数组。遇到第一个元素值不等于下标 + 1 的元素,就是答案要求的缺失的第一个正数。 -3. 如果遍历完没有在数组中找到缺失的第一个正数,则缺失的第一个正数是 `n + 1`。 -4. 最后返回我们找到的缺失的第一个正数。 - -### 思路 1:代码 - -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - size = len(nums) - - for i in range(size): - while 1 <= nums[i] <= size and nums[i] != nums[nums[i] - 1]: - index1 = i - index2 = nums[i] - 1 - nums[index1], nums[index2] = nums[index2], nums[index1] - - for i in range(size): - if nums[i] != i + 1: - return i + 1 - return size + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 `nums` 的元素个数。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0042. \346\216\245\351\233\250\346\260\264.md" "b/Solutions/0042. \346\216\245\351\233\250\346\260\264.md" deleted file mode 100644 index e8605682..00000000 --- "a/Solutions/0042. \346\216\245\351\233\250\346\260\264.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) - -- 标签:栈、数组、双指针、动态规划、单调栈 -- 难度:困难 - -## 题目大意 - -**描述**:给定 `n` 个非负整数表示每个宽度为 `1` 的柱子的高度图,用数组 `height` 表示,其中 `height[i]` 表示第 `i` 根柱子的高度。 - -**要求**:计算按此排列的柱子,下雨之后能接多少雨水。 - -**说明**: - -- $n == height.length$。 -- $1 \le n \le 2 * 10^4$。 -- $0 \le height[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/22/rainwatertrap.png) - -```python -输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] -输出:6 -解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 -``` - -- 示例 2: - -```python -输入:height = [4,2,0,3,2,5] -输出:9 -``` - -## 解题思路 - -### 思路 1:单调栈 - -1. 遍历高度数组 `height`。 -2. 如果当前柱体高度较小,小于等于栈顶柱体的高度,则将当前柱子高度入栈。 -3. 如果当前柱体高度较大,大于栈顶柱体的高度,则一直出栈,直到当前柱体小于等于栈顶柱体的高度。 -4. 假设当前柱体为 `C`,出栈柱体为 `B`,出栈之后新的栈顶柱体为 `A`。则说明: - 1. 当前柱体 `C` 是出栈柱体 `B` 向右找到的第一个大于当前柱体高度的柱体,那么以出栈柱体 `B` 为中心,可以向右将宽度扩展到当前柱体 `C`。 - 2. 新的栈顶柱体 `A` 是出栈柱体 `B` 向左找到的第一个大于当前柱体高度的柱体,那么以出栈柱体 `B` 为中心,可以向左将宽度扩展到当前柱体 `A`。 -5. 出栈后,以新的栈顶柱体 `A` 为左边界,以当前柱体 `C` 为右边界,以左右边界与出栈柱体 `B` 的高度差为深度,计算可以接到雨水的面积。然后记录并更新累积面积。 - -### 思路 1:代码 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - stack = [] - size = len(height) - for i in range(size): - while stack and height[i] > height[stack[-1]]: - cur = stack.pop(-1) - if stack: - left = stack[-1] + 1 - right = i - 1 - high = min(height[i], height[stack[-1]]) - height[cur] - ans += high * (right - left + 1) - else: - break - stack.append(i) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `height` 的长度。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0043. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" "b/Solutions/0043. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" deleted file mode 100644 index 49d67020..00000000 --- "a/Solutions/0043. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) - -- 标签:数学、字符串、模拟 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个以字符串形式表示的非负整数 `num1` 和 `num2`。 - -**要求**:返回 `num1` 和 `num2` 的乘积,它们的乘积也表示为字符串形式。 - -**说明**: - -- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 -- $1 \le num1.length, num2.length \le 200$。 -- `num1` 和 `num2` 只能由数字组成。 -- `num1` 和 `num2` 都不包含任何前导零,除了数字0本身。 - -**示例**: - -- 示例 1: - -```python -输入: num1 = "2", num2 = "3" -输出: "6" -``` - -- 示例 2: - -```python -输入: num1 = "123", num2 = "456" -输出: "56088" -``` - -## 解题思路 - -### 思路 1:模拟 - -我们可以使用数组来模拟大数乘法。长度为 `len(num1)` 的整数 `num1` 与长度为 `len(num2)` 的整数 `num2` 相乘的结果长度为 `len(num1) + len(num2) - 1` 或 `len(num1) + len(num2)`。所以我们可以使用长度为 `len(num1) + len(num2)` 的整数数组 `nums` 来存储两个整数相乘之后的结果。 - -整个计算流程的步骤如下: - -1. 从个位数字由低位到高位开始遍历 `num1`,取得每一位数字 `digit1`。从个位数字由低位到高位开始遍历 `num2`,取得每一位数字 `digit2`。 -2. 将 `digit1 * digit2` 的结果累积存储到 `nums` 对应位置 `i + j + 1` 上。 -3. 计算完毕之后从 `len(num1) + len(num2) - 1` 的位置由低位到高位遍历数组 `nums`。将每个数位上大于等于 `10` 的数字进行进位操作,然后对该位置上的数字进行取余操作。 -4. 最后判断首位是否有进位。如果首位为 `0`,则从第 `1` 个位置开始将答案数组拼接成字符串。如果首位不为 `0`,则从第 `0` 个位置开始将答案数组拼接成字符串。并返回答案字符串。 - -### 思路 1:代码 - -```python -class Solution: - def multiply(self, num1: str, num2: str) -> str: - if num1 == "0" or num2 == "0": - return "0" - - len1, len2 = len(num1), len(num2) - nums = [0 for _ in range(len1 + len2)] - - for i in range(len1 - 1, -1, -1): - digit1 = int(num1[i]) - for j in range(len2 - 1, -1, -1): - digit2 = int(num2[j]) - nums[i + j + 1] += digit1 * digit2 - - for i in range(len1 + len2 - 1, 0, -1): - nums[i - 1] += nums[i] // 10 - nums[i] %= 10 - - if nums[0] == 0: - ans = "".join(str(digit) for digit in nums[1:]) - else: - ans = "".join(str(digit) for digit in nums[:]) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$ 和 $n$ 分别为 `nums1` 和 `nums2` 的长度。 -- **空间复杂度**:$O(m + n)$。 - diff --git "a/Solutions/0044. \351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.md" "b/Solutions/0044. \351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.md" deleted file mode 100644 index a4869773..00000000 --- "a/Solutions/0044. \351\200\232\351\205\215\347\254\246\345\214\271\351\205\215.md" +++ /dev/null @@ -1,98 +0,0 @@ -# [0044. 通配符匹配](https://leetcode.cn/problems/wildcard-matching/) - -- 标签:贪心、递归、字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个字符串 `s` 和一个字符模式串 `p`。 - -**要求**:实现一个支持 `'?'` 和 `'*'` 的通配符匹配。两个字符串完全匹配才算匹配成功。如果匹配成功,则返回 `True`,否则返回 `False`。 - -- `'?'` 可以匹配任何单个字符。 -- `'*'` 可以匹配任意字符串(包括空字符串)。 - -**说明**: - -- `s` 可能为空,且只包含从 `a` ~ `z` 的小写字母。 -- `p` 可能为空,且只包含从 `a` ~ `z` 的小写字母,以及字符 `'?'` 和 `'*'`。 - -**示例**: - -- 示例 1: - -```python -输入:s = "aa" p = "a" -输出:False -解释:"a" 无法匹配 "aa" 整个字符串。 -``` - -- 示例 2: - -```python -输入:s = "aa" p = "*" -输出:True -解释:'*' 可以匹配任意字符串。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。 - -###### 3. 状态转移方程 - -- 如果 `s[i - 1] == p[j - 1]`,或者 `p[j - 1] == '?'`,则表示字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的。此时「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j - 1] `。 -- 如果 `p[j - 1] == '*'`,则字符串 `p` 的第 `j` 个字符可以对应字符串 `s` 中 `0` ~ 若干个字符。则: - - 如果当前星号没有匹配当前第 `i` 个字符,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」,即 `dp[i][j] = dp[i - 1][j]`。 - - 如果当前星号匹配了当前第 `i` 个字符,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」,即 `dp[i][j] = dp[i][j - 1]`。 - - 这两种情况只需匹配一种,就视为匹配,所以 `dp[i][j] = dp[i - 1][j] or dp[i][j - 1] `。 - -则动态转移方程为: - -$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & s[i - 1] == p[j - 1] \or p[j - 1] == '?' \cr dp[i - 1][j] or dp[i][j - 1] & p[j - 1] == '*' \end{cases}$ - -###### 4. 初始条件 - -- 默认状态下,两个空字符串是匹配的,即 `dp[0][0] = True`。 -- 当字符串 `s` 为空,字符串 `p` 开始字符为若干个 `*` 时,两个字符串是匹配的,即 `p[j - 1] == '*'` 时,`dp[0][j] = True`。 - -###### 5. 最终结果 - -根据我们之前定义的状态, `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。则最终结果为 `dp[size_s][size_p]`,其实 `size_s` 是字符串 `s` 的长度,`size_p` 是字符串 `p` 的长度。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def isMatch(self, s: str, p: str) -> bool: - size_s, size_p = len(s), len(p) - dp = [[False for _ in range(size_p + 1)] for _ in range(size_s + 1)] - dp[0][0] = True - - for j in range(1, size_p + 1): - if p[j - 1] != '*': - break - dp[0][j] = True - - for i in range(1, size_s + 1): - for j in range(1, size_p + 1): - if s[i - 1] == p[j - 1] or p[j - 1] == '?': - dp[i][j] = dp[i - 1][j - 1] - elif p[j - 1] == '*': - dp[i][j] = dp[i - 1][j] or dp[i][j - 1] - - return dp[size_s][size_p] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了两重循环,外层循环遍历的时间复杂度是 $O(m)$,内层循环遍历的时间复杂度是 $O(n)$,所以总体的时间复杂度为 $O(m n)$。 -- **空间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了二维数组保存状态,且第一维的空间复杂度为 $O(m)$,第二位的空间复杂度为 $O(n)$,所以总体的空间复杂度为 $O(m n)$。 diff --git "a/Solutions/0045. \350\267\263\350\267\203\346\270\270\346\210\217 II.md" "b/Solutions/0045. \350\267\263\350\267\203\346\270\270\346\210\217 II.md" deleted file mode 100644 index 54883b42..00000000 --- "a/Solutions/0045. \350\267\263\350\267\203\346\270\270\346\210\217 II.md" +++ /dev/null @@ -1,153 +0,0 @@ -# [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个非负整数数组 `nums`,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置为数组的第一个下标处。 - -**要求**:计算出到达最后一个下标处的最少的跳跃次数。假设你总能到达数组的最后一个下标处。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $0 \le nums[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,3,1,1,4] -输出:2 -解释:跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 -``` - -## 解题思路 - -### 思路 1:动态规划(超时) - -###### 1. 划分阶段 - -按照位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。 - -###### 3. 状态转移方程 - -对于当前位置 `i`,如果之前的位置 `j`($o \le j < i$) 能够跳到位置 `i` 需要满足:位置 `j`($o \le j < i$)加上位置 `j` 所能跳到的最远长度要大于等于 `i`,即 `j + nums[j] >= i` 。 - -而跳到下标 `i` 所需要的最小跳跃次数则等于满足上述要求的位置 `j` 中最小跳跃次数加 `1`,即 `dp[i] = min(dp[i], dp[j] + 1)`。 - -###### 4. 初始条件 - -初始状态下,跳到下标 `0` 需要的最小跳跃次数为 `0`,即 `dp[0] = 0`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。则最终结果为 `dp[size - 1]`。 - -### 思路 1:动态规划(超时)代码 - -```python -class Solution: - def jump(self, nums: List[int]) -> int: - size = len(nums) - dp = [float("inf") for _ in range(size)] - dp[0] = 0 - - for i in range(1, size): - for j in range(i): - if j + nums[j] >= i: - dp[i] = min(dp[i], dp[j] + 1) - - return dp[size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总体时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -### 思路 2:动态规划 + 贪心 - -因为本题的数据规模为 $10^4$,而思路 1 的时间复杂度是 $O(n^2)$,所以就超时了。那么我们有什么方法可以优化一下,减少一下时间复杂度吗? - -上文提到,在满足 `j + nums[j] >= i` 的情况下,`dp[i] = min(dp[i], dp[j] + 1)`。 - -通过观察可以发现,`dp[i]` 是单调递增的,也就是说 `dp[i - 1] <= dp[i] <= dp[i + 1]`。 - -举个例子,比如跳到下标 `i` 最少需要 `5` 步,即 `dp[i] = 5`,那么必然不可能出现少于 `5` 步就能跳到下标 `i + 1` 的情况,跳到下标 `i + 1` 至少需要 `5` 步或者更多步。 - -既然 `dp[i]` 是单调递增的,那么在更新 `dp[i]` 时,我们找到最早可以跳到 `i` 的点 `j`,从该点更新 `dp[i]`。即找到满足 `j + nums[j] >= i` 的第一个 `j`,使得 `dp[i] = dp[j] + 1`。 - -而查找第一个 `j` 的过程可以通过使用一个指针变量 `j` 从前向后迭代查找。 - -最后,将最终结果 `dp[size - 1]` 返回即可。 - -### 思路 2:动态规划 + 贪心代码 - -```python -class Solution: - def jump(self, nums: List[int]) -> int: - size = len(nums) - dp = [float("inf") for _ in range(size)] - dp[0] = 0 - - j = 0 - for i in range(1, size): - while j + nums[j] < i: - j += 1 - dp[i] = dp[j] + 1 - - return dp[size - 1] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。最外层循环遍历的时间复杂度是 $O(n)$,看似和内层循环结合遍历的时间复杂度是 $O(n^2)$,实际上内层循环只遍历了一遍,与外层循环遍历次数是相加关系,两者的时间复杂度和是 $O(2n)$,$O(2n) = O(n)$,所以总体时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -### 思路 2:贪心算法 - -如果第 `i` 个位置所能跳到的位置为 `[i + 1, i + nums[i]]`,则: - -- 第 `0` 个位置所能跳到的位置就是 `[0 + 1, 0 + nums[0]]`,即 `[1, nums[0]]`。 -- 第 `1` 个位置所能跳到的位置就是 `[1 + 1, 1 + nums[1]]`,即 `[2, 1 + nums[1]]`。 -- …… - -对于每一个位置 `i` 来说,所能跳到的所有位置都可以作为下一个起跳点,为了尽可能使用最少的跳跃次数,所以我们应该使得下一次起跳所能达到的位置尽可能的远。简单来说,就是每次在「可跳范围」内选择可以使下一次跳的更远的位置。这样才能获得最少跳跃次数。具体做法如下: - -1. 维护几个变量:当前所能达到的最远位置 `end`,下一步所能跳到的最远位置 `max_pos`,最少跳跃次数 `setps`。 -2. 遍历数组 `nums` 的前 `len(nums) - 1` 个元素: - 1. 每次更新第 `i` 位置下一步所能跳到的最远位置 `max_pos`。 - 2. 如果索引 `i` 到达了 `end` 边界,则:更新 `end` 为新的当前位置 `max_pos`,并令步数 `setps` 加 `1`。 -3. 最终返回跳跃次数 `steps`。 - -### 思路 2:贪心算法代码 - -```python -class Solution: - def jump(self, nums: List[int]) -> int: - end, max_pos = 0, 0 - steps = 0 - for i in range(len(nums) - 1): - max_pos = max(max_pos, nums[i] + i) - if i == end: - end = max_pos - steps += 1 - return steps -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(1)$。只用到了常数项的变量,所以总体空间复杂度为 $O(1)$。 - -## 参考资料 - -- 【题解】[【宫水三叶の相信科学系列】详解「DP + 贪心 + 双指针」解法,以及该如何猜 DP 的状态定义 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/xiang-jie-dp-tan-xin-shuang-zhi-zhen-jie-roh4/) -- 【题解】[动态规划+贪心,易懂。 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/dong-tai-gui-hua-tan-xin-yi-dong-by-optimjie/) diff --git "a/Solutions/0046. \345\205\250\346\216\222\345\210\227.md" "b/Solutions/0046. \345\205\250\346\216\222\345\210\227.md" deleted file mode 100644 index 9915462c..00000000 --- "a/Solutions/0046. \345\205\250\346\216\222\345\210\227.md" +++ /dev/null @@ -1,97 +0,0 @@ -# [0046. 全排列](https://leetcode.cn/problems/permutations/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个不含重复数字的数组 `nums`。 - -**要求**:返回其有可能的全排列。 - -**说明**: - -- $1 \le nums.length \le 6$ -- $-10 \le nums[i] \le 10$。 -- `nums` 中的所有整数互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3] -输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] -``` - -- 示例 2: - -```python -输入:nums = [0,1] -输出:[[0,1],[1,0]] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:全排列中每个位置上的元素都可以从剩余可选元素中选出,对此画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220425102048.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - `backtracking(nums):` 函数的传入参数是 `nums`(可选数组列表),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 - - `backtracking(nums):` 函数代表的含义是:递归在 `nums` 中选择剩下的元素。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: - - 约束条件:之前已经选择的元素不再重复选用,只能从剩余元素中选择。 - - 选择元素:将其添加到当前子集数组 `path` 中。 - - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 - - 撤销选择:将该元素从当前结果数组 `path` 中移除。 - - ```python - for i in range(len(nums)): # 枚举可选元素列表 - if nums[i] not in path: # 从当前路径中没有出现的数字中选择 - path.append(nums[i]) # 选择元素 - backtracking(nums) # 递归搜索 - path.pop() # 撤销选择 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是存放当前结果的数组 `path` 的长度等于给定数组 `nums` 的长度(即 `len(path) == len(nums)`)时,递归停止。 - -### 思路 1:代码 - -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - res = [] # 存放所有符合条件结果的集合 - path = [] # 存放当前符合条件的结果 - def backtracking(nums): # nums 为选择元素列表 - if len(path) == len(nums): # 说明找到了一组符合条件的结果 - res.append(path[:]) # 将当前符合条件的结果放入集合中 - return - - for i in range(len(nums)): # 枚举可选元素列表 - if nums[i] not in path: # 从当前路径中没有出现的数字中选择 - path.append(nums[i]) # 选择元素 - backtracking(nums) # 递归搜索 - path.pop() # 撤销选择 - - backtracking(nums) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times n!)$,其中 $n$ 为数组 `nums` 的元素个数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0047. \345\205\250\346\216\222\345\210\227 II.md" "b/Solutions/0047. \345\205\250\346\216\222\345\210\227 II.md" deleted file mode 100644 index d67b5dea..00000000 --- "a/Solutions/0047. \345\205\250\346\216\222\345\210\227 II.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0047. 全排列 II](https://leetcode.cn/problems/permutations-ii/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个可包含重复数字的序列 `nums`。 - -**要求**:按任意顺序返回所有不重复的全排列。 - -**说明**: - -- $1 \le nums.length \le 8$。 -- $-10 \le nums[i] \le 10$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,2] -输出:[[1,1,2],[1,2,1],[2,1,1]] -``` - -- 示例 2: - -```python -输入:nums = [1,2,3] -输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -这道题跟「[0046. 全排列](https://leetcode.cn/problems/permutations/)」不一样的地方在于增加了序列中的元素可重复这一条件。这就涉及到了如何去重。 - -我们可以先对数组 `nums` 进行排序,然后使用一个数组 `visited` 标记该元素在当前排列中是否被访问过。 - -如果未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 - -然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 - -然后再进行回溯遍历。 - -### 思路 1:代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, nums: List[int], visited: List[bool]): - if len(self.path) == len(nums): - self.res.append(self.path[:]) - return - for i in range(len(nums)): - if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: - continue - - if not visited[i]: - visited[i] = True - self.path.append(nums[i]) - self.backtrack(nums, visited) - self.path.pop() - visited[i] = False - - def permuteUnique(self, nums: List[int]) -> List[List[int]]: - self.res.clear() - self.path.clear() - nums.sort() - visited = [False for _ in range(len(nums))] - self.backtrack(nums, visited) - return self.res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times n!)$,其中 $n$ 为数组 `nums` 的元素个数。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0048. \346\227\213\350\275\254\345\233\276\345\203\217.md" "b/Solutions/0048. \346\227\213\350\275\254\345\233\276\345\203\217.md" deleted file mode 100644 index 33e03826..00000000 --- "a/Solutions/0048. \346\227\213\350\275\254\345\233\276\345\203\217.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) - -- 标签:数组、数学、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $n \times n$ 大小的二维矩阵(代表图像)$matrix$。 - -**要求**:将二维矩阵 $matrix$ 顺时针旋转 90°。 - -**说明**: - -- 不能使用额外的数组空间。 -- $n == matrix.length == matrix[i].length$。 -- $1 \le n \le 20$。 -- $-1000 \le matrix[i][j] \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/08/28/mat1.jpg) - -```python -输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] -输出:[[7,4,1],[8,5,2],[9,6,3]] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/08/28/mat2.jpg) - -```python -输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] -输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] -``` - -## 解题思路 - -### 思路 1:原地旋转 - -如果使用额外数组空间的话,将对应元素存放到对应位置即可。如果不使用额外的数组空间,则需要观察每一个位置上的点最初位置和最终位置有什么规律。 - -对于矩阵中第 $i$ 行的第 $j$ 个元素,在旋转后,它出现在倒数第 $i$ 列的第 $j$ 个位置。即 $matrixnew[j][n − i − 1] = matrix[i][j]$。 - -而 $matrixnew[j][n - i - 1]$ 的点经过旋转移动到了 $matrix[n − i − 1][n − j − 1]$ 的位置。 - -$matrix[n − i − 1][n − j − 1]$ 位置上的点经过旋转移动到了 $matrix[n − j − 1][i]$ 的位置。 - -$matrix[n− j − 1][i]$ 位置上的点经过旋转移动到了最初的 $matrix[i][j]$ 的位置。 - -这样就形成了一个循环,我们只需要通过一个临时变量 $temp$ 就可以将循环中的元素逐一进行交换。Python 中则可以直接使用语法直接交换。 - -### 思路 1:代码 - -```python -class Solution: - def rotate(self, matrix: List[List[int]]) -> None: - n = len(matrix) - - for i in range(n // 2): - for j in range((n + 1) // 2): - matrix[i][j], matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1] = matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1], matrix[i][j] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:原地翻转 - -通过观察可以得出:原矩阵可以通过一次「水平翻转」+「主对角线翻转」得到旋转后的二维矩阵。 - -### 思路 2:代码 - -```python -def rotate(self, matrix: List[List[int]]) -> None: - n = len(matrix) - - for i in range(n // 2): - for j in range(n): - matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j] - - for i in range(n): - for j in range(i): - matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0049. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" "b/Solutions/0049. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" deleted file mode 100644 index c9546760..00000000 --- "a/Solutions/0049. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [0049. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) - -- 标签:数组、哈希表、字符串、排序 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组,将包含字母相同的字符串组合在一起,不需要考虑输出顺序。 - -## 解题思路 - -使用哈希表记录字母相同的字符串。对每一个字符串进行排序,按照 排序字符串:字母相同的字符串数组 的键值顺序进行存储。 - -最终将哈希表的值转换为对应数组返回结果。 - -## 代码 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - str_dict = dict() - res = [] - for s in strs: - sort_s = str(sorted(s)) - if sort_s in str_dict: - str_dict[sort_s] += [s] - else: - str_dict[sort_s] = [s] - - for sort_s in str_dict: - res += [str_dict[sort_s]] - return res -``` - diff --git a/Solutions/0050. Pow(x, n).md b/Solutions/0050. Pow(x, n).md deleted file mode 100644 index e46a51e3..00000000 --- a/Solutions/0050. Pow(x, n).md +++ /dev/null @@ -1,80 +0,0 @@ -# [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) - -- 标签:递归、数学 -- 难度:中等 - -## 题目大意 - -**描述**:给定浮点数 $x$ 和整数 $n$。 - -**要求**:计算 $x$ 的 $n$ 次方(即 $x^n$)。 - -**说明**: - -- $-100.0 < x < 100.0$。 -- $-2^{31} \le n \le 2^{31} - 1$。 -- $n$ 是一个整数。 -- $-10^4 \le x^n \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:x = 2.00000, n = 10 -输出:1024.00000 -``` - -- 示例 2: - -```python -输入:x = 2.00000, n = -2 -输出:0.25000 -解释:2-2 = 1/22 = 1/4 = 0.25 -``` - -## 解题思路 - -### 思路 1:分治算法 - -常规方法是直接将 $x$ 累乘 $n$ 次得出结果,时间复杂度为 $O(n)$。 - -我们可以利用分治算法来减少时间复杂度。 - -根据 $n$ 的奇偶性,我们可以得到以下结论: - -1. 如果 $n$ 为偶数,$x^n = x^{n / 2} \times x^{n / 2}$。 -2. 如果 $n$ 为奇数,$x^n = x \times x^{(n - 1) / 2} \times x^{(n - 1) / 2}$。 - -$x^{(n / 2)}$ 或 $x^{(n - 1) / 2}$ 又可以继续向下递归划分。 - -则我们可以利用低纬度的幂计算结果,来得到高纬度的幂计算结果。 - -这样递归求解,时间复杂度为 $O(\log n)$,并且递归也可以转为递推来做。 - -需要注意如果 $n$ 为负数,可以转换为 $\frac{1}{x} ^{(-n)}$。 - -### 思路 1:代码 - -```python -class Solution: - def myPow(self, x: float, n: int) -> float: - if x == 0.0: - return 0.0 - res = 1 - if n < 0: - x = 1/x - n = -n - while n: - if n & 1: - res *= x - x *= x - n >>= 1 - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0051. N \347\232\207\345\220\216.md" "b/Solutions/0051. N \347\232\207\345\220\216.md" deleted file mode 100644 index c0cc836c..00000000 --- "a/Solutions/0051. N \347\232\207\345\220\216.md" +++ /dev/null @@ -1,148 +0,0 @@ -# [0051. N 皇后](https://leetcode.cn/problems/n-queens/) - -- 标签:数组、回溯 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:返回所有不同的「`n` 皇后问题」的解决方案。每一种解法包含一个不同的「`n` 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 - -**说明**: - -- **n 皇后问题**:将 `n` 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 -- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 -- $1 \le n \le 9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] -解释:如下图所示,4 皇后问题存在 2 个不同的解法。 -``` - -![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) - -## 解题思路 - -### 思路 1:回溯算法 - -这道题是经典的回溯问题。我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 - -对于 `n * n` 的棋盘来说,每一行有 `n` 列,也就有 `n` 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 - -并且在放置完之后,通过回溯的方式尝试其他可能的分支。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220426095225.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - 首先我们先使用一个 `n * n` 大小的二维矩阵 `chessboard` 来表示当前棋盘,`chessboard` 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 - - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 `chessboard`(棋盘数组)和 `row`(代表当前正在考虑放置第 `row` 行皇后),全局变量是 `res`(存放所有符合条件结果的集合数组)。 - - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 `row` 行皇后的情况下,递归放置剩下行的皇后。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 枚举出当前行所有的列。对于每一列位置: - - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 - - 选择元素:选择 `row, col` 位置放置皇后,将其棋盘对应位置设置为 `Q`。 - - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 - - 撤销选择:将棋盘上 `row, col` 位置设置为 `.`。 - - ```python - # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - ``` - - ```python - for col in range(n): # 枚举可放置皇后的列 - if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 - chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 - backtrack(row + 1, chessboard) # 递归放置 row + 1 行之后的皇后 - chessboard[row][col] = '.' # 撤销选择 row, col 位置 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后(即 `row == n`)时,递归停止。 - - 递归停止时,将当前符合条件的棋盘转换为答案需要的形式,然后将其存入答案数组 `res` 中即可。 - -### 思路 1:代码 - -```python -class Solution: - res = [] - def backtrack(self, n: int, row: int, chessboard: List[List[str]]): - if row == n: - temp_res = [] - for temp in chessboard: - temp_str = ''.join(temp) - temp_res.append(temp_str) - self.res.append(temp_res) - return - for col in range(n): - if self.isValid(n, row, col, chessboard): - chessboard[row][col] = 'Q' - self.backtrack(n, row + 1, chessboard) - chessboard[row][col] = '.' - - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - - def solveNQueens(self, n: int) -> List[List[str]]: - self.res.clear() - chessboard = [['.' for _ in range(n)] for _ in range(n)] - self.backtrack(n, 0, chessboard) - return self.res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 -- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 diff --git "a/Solutions/0052. N \347\232\207\345\220\216 II.md" "b/Solutions/0052. N \347\232\207\345\220\216 II.md" deleted file mode 100644 index 9dbdb452..00000000 --- "a/Solutions/0052. N \347\232\207\345\220\216 II.md" +++ /dev/null @@ -1,115 +0,0 @@ -# [0052. N 皇后 II](https://leetcode.cn/problems/n-queens-ii/) - -- 标签:回溯 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:返回「`n` 皇后问题」不同解决方案的数量。 - -**说明**: - -- **n 皇后问题**:将 `n` 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 -- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 -- $1 \le n \le 9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:2 -解释:如下图所示,4 皇后问题存在两个不同的解法。 -``` - -![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) - -## 解题思路 - -### 思路 1:回溯算法 - -和「[51. N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/)」做法一致。区别在于「[51. N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/)」需要返回所有解决方案,而这道题只需要得到所有解决方案的数量即可。下面来说一下这道题的解题思路。 - -我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 - -对于 `n * n` 的棋盘来说,每一行有 `n` 列,也就有 `n` 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 - -并且在放置完之后,通过回溯的方式尝试其他可能的分支。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220426095225.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - 首先我们先使用一个 `n * n` 大小的二维矩阵 `chessboard` 来表示当前棋盘,`chessboard` 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 - - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 `chessboard`(棋盘数组)和 `row`(代表当前正在考虑放置第 `row` 行皇后),全局变量是 `ans`(所有可行方案的数量)。 - - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 `row` 行皇后的情况下,递归放置剩下行的皇后。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 枚举出当前行所有的列。对于每一列位置: - - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 - - 选择元素:选择 `row, col` 位置放置皇后,将其棋盘对应位置设置为 `Q`。 - - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 - - 撤销选择:将棋盘上 `row, col` 位置设置为 `.`。 - -### 思路 1:代码 - -```python -class Solution: - # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - - def totalNQueens(self, n: int) -> int: - chessboard = [['.' for _ in range(n)] for _ in range(n)] # 棋盘初始化 - - ans = 0 - def backtrack(chessboard: List[List[str]], row: int): # 正在考虑放置第 row 行的皇后 - if row == n: # 遇到终止条件 - nonlocal ans - ans += 1 - return - - for col in range(n): # 枚举可放置皇后的列 - if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 - chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 - backtrack(chessboard, row + 1) # 递归放置 row + 1 行之后的皇后 - chessboard[row][col] = '.' # 撤销选择 row, col 位置 - - backtrack(chessboard, 0) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 -- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 diff --git "a/Solutions/0053. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" "b/Solutions/0053. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" deleted file mode 100644 index 30072c86..00000000 --- "a/Solutions/0053. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" +++ /dev/null @@ -1,165 +0,0 @@ -# [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) - -- 标签:数组、分治、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 - -**说明**: - -- **子数组**:指的是数组中的一个连续部分。 -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [-2,1,-3,4,-1,2,1,-5,4] -输出:6 -解释:连续子数组 [4,-1,2,1] 的和最大,为 6。 -``` - -- 示例 2: - -```python -输入:nums = [1] -输出:1 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照连续子数组的结束位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。 - -###### 3. 状态转移方程 - -状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则我们可以从「第 $i - 1$ 个数结尾的连续子数组的最大和」,以及「第 $i$ 个数的值」来讨论 $dp[i]$。 - -- 如果 $dp[i - 1] < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,即:$dp[i - 1] + nums[i] < nums[i]$。所以,此时 $dp[i]$ 应取「第 $i$ 个数的值」,即 $dp[i] = nums[i]$。 -- 如果 $dp[i - 1] \ge 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,即:$dp[i - 1] + nums[i] \ge nums[i]$。所以,此时 $dp[i]$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」+「 第 $i$ 个数的值」,即 $dp[i] = dp[i - 1] + nums[i]$。 - -归纳一下,状态转移方程为: - -$dp[i] = \begin{cases} nums[i], & dp[i - 1] < 0 \cr dp[i - 1] + nums[i] & dp[i - 1] \ge 0 \end{cases}$ - -###### 4. 初始条件 - -- 第 $0$ 个数结尾的连续子数组的最大和为 $nums[0]$,即 $dp[0] = nums[0]$。 - -###### 5. 最终结果 - -根据状态定义,$dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则最终结果应为所有 $dp[i]$ 的最大值,即 $max(dp)$。 - -### 思路 1:代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - size = len(nums) - dp = [0 for _ in range(size)] - - dp[0] = nums[0] - for i in range(1, size): - if dp[i - 1] < 0: - dp[i] = nums[i] - else: - dp[i] = dp[i - 1] + nums[i] - return max(dp) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:动态规划 + 滚动优化 - -因为 $dp[i]$ 只和 $dp[i - 1]$ 和当前元素 $nums[i]$ 相关,我们也可以使用一个变量 $subMax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。然后使用 $ansMax$ 来保存全局中最大值。 - -### 思路 2:代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - size = len(nums) - subMax = nums[0] - ansMax = nums[0] - - for i in range(1, size): - if subMax < 0: - subMax = nums[i] - else: - subMax += nums[i] - ansMax = max(ansMax, subMax) - return ansMax -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(1)$。 - -### 思路 3:分治算法 - -我们将数组 $nums$ 根据中心位置分为左右两个子数组。则具有最大和的连续子数组可能存在以下 $3$ 种情况: - -1. 具有最大和的连续子数组在左子数组中。 -2. 具有最大和的连续子数组在右子数组中。 -3. 具有最大和的连续子数组跨过中心位置,一部分在左子数组中,另一部分在右子树组中。 - -那么我们要求出具有最大和的连续子数组的最大和,则分别对上面 $3$ 种情况求解即可。具体步骤如下: - -1. 将数组 $nums$ 根据中心位置递归分为左右两个子数组,直到所有子数组长度为 $1$。 -2. 长度为 $1$ 的子数组最大和肯定是数组中唯一的数,将其返回即可。 -3. 求出左子数组的最大和 $leftMax$。 -4. 求出右子树组的最大和 $rightMax$。 -5. 求出跨过中心位置,一部分在左子数组中,另一部分在右子树组的子数组最大和 $leftTotal + rightTotal$。 -6. 求出 $3$、$4$、$5$ 中的最大值,即为当前数组的最大和,将其返回即可。 - -### 思路 3:代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - def max_sub_array(low, high): - if low == high: - return nums[low] - - mid = low + (high - low) // 2 - leftMax = max_sub_array(low, mid) - rightMax = max_sub_array(mid + 1, high) - - total = 0 - leftTotal = -inf - for i in range(mid, low - 1, -1): - total += nums[i] - leftTotal = max(leftTotal, total) - - total = 0 - rightTotal = -inf - for i in range(mid + 1, high + 1): - total += nums[i] - rightTotal = max(rightTotal, total) - - return max(leftMax, rightMax, leftTotal + rightTotal) - - return max_sub_array(0, len(nums) - 1) -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(\log n)$。 \ No newline at end of file diff --git "a/Solutions/0054. \350\236\272\346\227\213\347\237\251\351\230\265.md" "b/Solutions/0054. \350\236\272\346\227\213\347\237\251\351\230\265.md" deleted file mode 100644 index 5f9d20e0..00000000 --- "a/Solutions/0054. \350\236\272\346\227\213\347\237\251\351\230\265.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) - -- 标签:数组、矩阵、模拟 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的二维矩阵 $matrix$。 - -**要求**:按照顺时针旋转的顺序,返回矩阵中的所有元素。 - -**说明**: - -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le m, n \le 10$。 -- $-100 \le matrix[i][j] \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/13/spiral1.jpg) - -```python -输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] -输出:[1,2,3,6,9,8,7,4,5] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/13/spiral.jpg) - -```python -输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] -输出:[1,2,3,4,8,12,11,10,9,5,6,7] -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 使用数组 $ans$ 存储答案。然后定义一下上、下、左、右的边界。 -2. 然后按照逆时针的顺序从边界上依次访问元素。 -3. 当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 -4. 最后返回答案数组 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def spiralOrder(self, matrix: List[List[int]]) -> List[int]: - up, down, left, right = 0, len(matrix)-1, 0, len(matrix[0])-1 - ans = [] - while True: - for i in range(left, right + 1): - ans.append(matrix[up][i]) - up += 1 - if up > down: - break - for i in range(up, down + 1): - ans.append(matrix[i][right]) - right -= 1 - if right < left: - break - for i in range(right, left - 1, -1): - ans.append(matrix[down][i]) - down -= 1 - if down < up: - break - for i in range(down, up - 1, -1): - ans.append(matrix[i][left]) - left += 1 - if left > right: - break - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$、$n$ 分别为二维矩阵的行数和列数。 -- **空间复杂度**:$O(m \times n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(m \times n)$。不算上则空间复杂度为 $O(1)$。 - diff --git "a/Solutions/0055. \350\267\263\350\267\203\346\270\270\346\210\217.md" "b/Solutions/0055. \350\267\263\350\267\203\346\270\270\346\210\217.md" deleted file mode 100644 index 544d7202..00000000 --- "a/Solutions/0055. \350\267\263\350\267\203\346\270\270\346\210\217.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [0055. 跳跃游戏](https://leetcode.cn/problems/jump-game/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个非负整数数组 `nums`,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置位于数组的第一个下标处。 - -**要求**:判断是否能够到达最后一个下标。 - -**说明**: - -- $1 \le nums.length \le 3 \times 10^4$。 -- $0 \le nums[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,3,1,1,4] -输出:true -解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 -``` - -- 示例 2: - -```python -输入:nums = [3,2,1,0,4] -输出:false -解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -如果我们能通过前面的某个位置 $j$,到达后面的某个位置 $i$,则我们一定能到达区间 $[j, i]$ 中所有的点($j \le i$)。 - -而前面的位置 $j$ 肯定也是通过 $j$ 前面的点到达的。所以我们可以通过贪心算法来计算出所能到达的最远位置。具体步骤如下: - -1. 初始化能到达的最远位置 $max_i$ 为 $0$。 -2. 遍历数组 `nums`。 -3. 如果能到达当前位置,即 $max_i \le i$,并且当前位置 + 当前位置最大跳跃长度 > 能到达的最远位置,即 $i + nums[i] > max_i$,则更新能到达的最远位置 $max_i$。 -4. 遍历完数组,最后比较能到达的最远位置 $max_i$ 和数组最远距离 `size - 1` 的关系。如果 $max_i >= len(nums)$,则返回 `True`,否则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - size = len(nums) - max_i = 0 - for i in range(size): - if max_i >= i and i + nums[i] > max_i: - max_i = i + nums[i] - - return max_i >= size - 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `nums` 的长度。 -- **空间复杂度**: - -### 思路 2:动态规划 - -###### 1. 划分阶段 - -按照位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:从位置 $0$ 出发,经过 $j \le i$,可以跳出的最远距离。 - -###### 3. 状态转移方程 - -- 如果能通过 $0 \sim i - 1$ 个位置到达 $i$,即 $dp[i-1] \le i$,则 $dp[i] = max(dp[i-1], i + nums[i])$。 -- 如果不能通过 $0 \sim i - 1$ 个位置到达 $i$,即 $dp[i - 1] < i$,则 $dp[i] = dp[i - 1]$。 - -###### 4. 初始条件 - -初始状态下,从 $0$ 出发,经过 $0$,可以跳出的最远距离为 `nums[0]`,即 `dp[0] = nums[0]`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:从位置 $0$ 出发,经过 $j \le i$,可以跳出的最远距离。则我们需要判断 `dp[size - 1]` 与数组最远距离 `size - 1` 的关系。 - -### 思路 2:代码 - -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - size = len(nums) - dp = [0 for _ in range(size)] - dp[0] = nums[0] - for i in range(1, size): - if i <= dp[i - 1]: - dp[i] = max(dp[i - 1], i + nums[i]) - else: - dp[i] = dp[i - 1] - return dp[size - 1] >= size - 1 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `nums` 的长度。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0056. \345\220\210\345\271\266\345\214\272\351\227\264.md" "b/Solutions/0056. \345\220\210\345\271\266\345\214\272\351\227\264.md" deleted file mode 100644 index 86d5d8fb..00000000 --- "a/Solutions/0056. \345\220\210\345\271\266\345\214\272\351\227\264.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) - -- 标签:数组、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定数组 `intervals` 表示若干个区间的集合,其中单个区间为 `intervals[i] = [starti, endi]` 。 - -**要求**:合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。 - -**说明**: - -- $1 \le intervals.length \le 10^4$。 -- $intervals[i].length == 2$。 -- $0 \le starti \le endi \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:intervals = [[1,3],[2,6],[8,10],[15,18]] -输出:[[1,6],[8,10],[15,18]] -解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. -``` - -- 示例 2: - -```python -输入:intervals = [[1,4],[4,5]] -输出:[[1,5]] -解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 -``` - -## 解题思路 - -### 思路 1:排序 - -1. 设定一个数组 `ans` 用于表示最终不重叠的区间数组,然后对原始区间先按照区间左端点大小从小到大进行排序。 -2. 遍历所有区间。 -3. 先将第一个区间加入 `ans` 数组中。 -4. 然后依次考虑后边的区间: - 1. 如果第 `i` 个区间左端点在前一个区间右端点右侧,则这两个区间不会重合,直接将该区间加入 `ans` 数组中。 - 2. 否则的话,这两个区间重合,判断一下两个区间的右区间值,更新前一个区间的右区间值为较大值,然后继续考虑下一个区间,以此类推。 -5. 最后返回数组 `ans`。 - -### 思路 1:代码 - -```python -class Solution: - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - intervals.sort(key=lambda x: x[0]) - - ans = [] - for interval in intervals: - if not ans or ans[-1][1] < interval[0]: - ans.append(interval) - else: - ans[-1][1] = max(ans[-1][1], interval[1]) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2 n)$。其中 $n$ 为区间数量。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0058. \346\234\200\345\220\216\344\270\200\344\270\252\345\215\225\350\257\215\347\232\204\351\225\277\345\272\246.md" "b/Solutions/0058. \346\234\200\345\220\216\344\270\200\344\270\252\345\215\225\350\257\215\347\232\204\351\225\277\345\272\246.md" deleted file mode 100644 index 2d241ba4..00000000 --- "a/Solutions/0058. \346\234\200\345\220\216\344\270\200\344\270\252\345\215\225\350\257\215\347\232\204\351\225\277\345\272\246.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0058. 最后一个单词的长度](https://leetcode.cn/problems/length-of-last-word/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -给定一个字符串 s,返回字符串中最后一个单词长度。 - -- 「单词」:指仅由字母组成、不包含任何空格字符的最大子字符串。 - -## 解题思路 - -从字符串末尾开始逆序遍历,先过滤掉末尾空白字符,然后统计字符数量,直到遇到空格或到达字符串开始位置。 - -## 代码 - -```python -class Solution: - def lengthOfLastWord(self, s: str) -> int: - ans = 0 - for i in range(len(s)-1, -1, -1): - if s[i] == " ": - if ans == 0: - continue - else: - return ans - else: - ans += 1 - return ans -``` - diff --git "a/Solutions/0059. \350\236\272\346\227\213\347\237\251\351\230\265 II.md" "b/Solutions/0059. \350\236\272\346\227\213\347\237\251\351\230\265 II.md" deleted file mode 100644 index d8090d19..00000000 --- "a/Solutions/0059. \350\236\272\346\227\213\347\237\251\351\230\265 II.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [0059. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) - -- 标签:数组、矩阵、模拟 -- 难度:中等 - -## 题目大意 - -给你一个正整数 $n$。 - -要求:生成一个包含 $1 \sim n^2$ 的所有元素,且元素按顺时针顺序螺旋排列的 $n \times n$ 正方形矩阵 $matrix$。 - -## 解题思路 - -### 思路 1:模拟 - -这道题跟「[54. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)」思路是一样的。 - -1. 构建一个 $n \times n$ 大小的数组 $matrix$ 存储答案。然后定义一下上、下、左、右的边界。 -2. 然后按照逆时针的顺序从边界上依次给数组 $matrix$ 相应位置赋值。 -3. 当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 -4. 最后返回 $matrix$。 - -### 思路 1:代码 - -```python -class Solution: - def generateMatrix(self, n: int) -> List[List[int]]: - matrix = [[0 for _ in range(n)] for _ in range(n)] - up, down, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 - index = 1 - while True: - for i in range(left, right + 1): - matrix[up][i] = index - index += 1 - up += 1 - if up > down: - break - for i in range(up, down + 1): - matrix[i][right] = index - index += 1 - right -= 1 - if right < left: - break - for i in range(right, left - 1, -1): - matrix[down][i] = index - index += 1 - down -= 1 - if down < up: - break - for i in range(down, up - 1, -1): - matrix[i][left] = index - index += 1 - left += 1 - if left > right: - break - return matrix -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0061. \346\227\213\350\275\254\351\223\276\350\241\250.md" "b/Solutions/0061. \346\227\213\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index 4e9f3a98..00000000 --- "a/Solutions/0061. \346\227\213\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0061. 旋转链表](https://leetcode.cn/problems/rotate-list/) - -- 标签:链表、双指针 -- 难度:中等 - -## 题目大意 - -给定一个链表和整数 k,将链表每个节点向右移动 k 个位置。 - -## 解题思路 - -我们可以将链表先连成环,然后将链表在指定位置断开。 - -先遍历一遍,求出链表节点个数 n。注意到 k 可能很大,我们只需将链表右移 k % n 个位置即可。 - -第二次遍历到 n - k % n 的位置,记录下断开后新链表头节点位置,再将其断开并返回新的头节点。 - -## 代码 - -```python -class Solution: - def rotateRight(self, head: ListNode, k: int) -> ListNode: - if k == 0 or not head or not head.next: - return head - curr = head - count = 1 - while curr.next: - count += 1 - curr = curr.next - cut = count - k % count - curr.next = head - while cut: - curr = curr.next - cut -= 1 - newHead = curr.next - curr.next = None - return newHead -``` - diff --git "a/Solutions/0062. \344\270\215\345\220\214\350\267\257\345\276\204.md" "b/Solutions/0062. \344\270\215\345\220\214\350\267\257\345\276\204.md" deleted file mode 100644 index 826a4dc0..00000000 --- "a/Solutions/0062. \344\270\215\345\220\214\350\267\257\345\276\204.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) - -- 标签:数学、动态规划、组合数学 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 - -**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 - -**说明**: - -- $1 \le m, n \le 100$。 -- 题目数据保证答案小于等于 $2 \times 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:m = 3, n = 7 -输出:28 -``` - -![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) - -- 示例 2: - -```python -输入:m = 3, n = 2 -输出:3 -解释: -从左上角开始,总共有 3 条路径可以到达右下角。 -1. 向右 -> 向下 -> 向下 -2. 向下 -> 向下 -> 向右 -3. 向下 -> 向右 -> 向下 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:从左上角到达位置 $(i, j)$ 的路径数量。 - -###### 3. 状态转移方程 - -因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 - -###### 4. 初始条件 - -- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 -- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 -- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - dp = [[0 for _ in range(n)] for _ in range(m)] - - for j in range(n): - dp[0][j] = 1 - for i in range(m): - dp[i][0] = 1 - - for i in range(1, m): - for j in range(1, n): - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - - return dp[m - 1][n - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m \times n)$,所以总体时间复杂度为 $O(m \times n)$。 -- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $n$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0063. \344\270\215\345\220\214\350\267\257\345\276\204 II.md" "b/Solutions/0063. \344\270\215\345\220\214\350\267\257\345\276\204 II.md" deleted file mode 100644 index 2ab7c21d..00000000 --- "a/Solutions/0063. \344\270\215\345\220\214\350\267\257\345\276\204 II.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0063. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) - -- 标签:数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:一个机器人位于一个 $m \times n$ 网格的左上角。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。但是网格中有障碍物,不能通过。 - -现在给定一个二维数组表示网格,$1$ 代表障碍物,$0$ 表示空位。 - -**要求**:计算出从左上角到右下角会有多少条不同的路径。 - -**说明**: - -- $m == obstacleGrid.length$。 -- $n == obstacleGrid[i].length$。 -- $1 \le m, n \le 100$。 -- $obstacleGrid[i][j]$ 为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/04/robot1.jpg) - -```python -输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] -输出:2 -解释:3x3 网格的正中间有一个障碍物。 -从左上角到右下角一共有 2 条不同的路径: -1. 向右 -> 向右 -> 向下 -> 向下 -2. 向下 -> 向下 -> 向右 -> 向右 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/04/robot2.jpg) - -```python -输入:obstacleGrid = [[0,1],[0,0]] -输出:1 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:从 $(0, 0)$ 到 $(i, j)$ 的不同路径数。 - -###### 3. 状态转移方程 - -因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。则状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,其中 $obstacleGrid[i][j] == 0$。 - -###### 4. 初始条件 - -- 对于第一行、第一列,因为只能超一个方向走,所以 $dp[i][0] = 1$,$dp[0][j] = 1$。如果在第一行、第一列遇到障碍,则终止赋值,跳出循环。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:从 $(0, 0)$ 到 $(i, j)$ 的不同路径数。所以最终结果为 $dp[m - 1][n - 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int: - m = len(obstacleGrid) - n = len(obstacleGrid[0]) - dp = [[0 for _ in range(n)] for _ in range(m)] - - for i in range(m): - if obstacleGrid[i][0] == 1: - break - dp[i][0] = 1 - - for j in range(n): - if obstacleGrid[0][j] == 1: - break - dp[0][j] = 1 - - for i in range(1, m): - for j in range(1, n): - if obstacleGrid[i][j] == 1: - continue - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - return dp[m - 1][n - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0064. \346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" "b/Solutions/0064. \346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" deleted file mode 100644 index 338958c7..00000000 --- "a/Solutions/0064. \346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) - -- 标签:数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个包含非负整数的 $m \times n$ 大小的网格 $grid$。 - -**要求**:找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 - -**说明**: - -- 每次只能向下或者向右移动一步。 -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 200$。 -- $0 \le grid[i][j] \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/05/minpath.jpg) - -```python -输入:grid = [[1,3,1],[1,5,1],[4,2,1]] -输出:7 -解释:因为路径 1→3→1→1→1 的总和最小。 -``` - -- 示例 2: - -```python -输入:grid = [[1,2,3],[4,5,6]] -输出:12 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:从左上角到达 $(i, j)$ 位置的最小路径和。 - -###### 3. 状态转移方程 - -当前位置 $(i, j)$ 只能从左侧位置 $(i, j - 1)$ 或者上方位置 $(i - 1, j)$ 到达。为了使得从左上角到达 $(i, j)$ 位置的最小路径和最小,应从 $(i, j - 1)$ 位置和 $(i - 1, j)$ 位置选择路径和最小的位置达到 $(i, j)$。 - -即状态转移方程为:$dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]$。 - -###### 4. 初始条件 - -- 当左侧和上方是矩阵边界时(即 $i = 0, j = 0$),$dp[i][j] = grid[i][j]$。 -- 当只有左侧是矩阵边界时(即 $i \ne 0, j = 0$),只能从上方到达,$dp[i][j] = dp[i - 1][j] + grid[i][j]$。 -- 当只有上方是矩阵边界时(即 $i = 0, j \ne 0$),只能从左侧到达,$dp[i][j] = dp[i][j - 1] + grid[i][j]$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[rows - 1][cols - 1]$(即从左上角到达 $(rows - 1, cols - 1)$ 位置的最小路径和)即可。其中 $rows$、$cols$ 分别为 $grid$ 的行数、列数。 - -### 思路 1:代码 - -```python -class Solution: - def minPathSum(self, grid: List[List[int]]) -> int: - rows, cols = len(grid), len(grid[0]) - dp = [[0 for _ in range(cols)] for _ in range(rows)] - - dp[0][0] = grid[0][0] - - for i in range(1, rows): - dp[i][0] = dp[i - 1][0] + grid[i][0] - - for j in range(1, cols): - dp[0][j] = dp[0][j - 1] + grid[0][j] - - for i in range(1, rows): - for j in range(1, cols): - dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] - - return dp[rows - 1][cols - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m * n)$,其中 $m$、$n$ 分别为 $grid$ 的行数和列数。 -- **空间复杂度**:$O(m * n)$。 diff --git "a/Solutions/0066. \345\212\240\344\270\200.md" "b/Solutions/0066. \345\212\240\344\270\200.md" deleted file mode 100644 index 271b6056..00000000 --- "a/Solutions/0066. \345\212\240\344\270\200.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [0066. 加一](https://leetcode.cn/problems/plus-one/) - -- 标签:数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个非负整数数组,数组每一位对应整数的一位数字。 - -**要求**:计算整数加 $1$ 后的结果。 - -**说明**: - -- $1 \le digits.length \le 100$。 -- $0 \le digits[i] \le 9$。 - -**示例**: - -- 示例 1: - -```python -输入:digits = [1,2,3] -输出:[1,2,4] -解释:输入数组表示数字 123,加 1 之后为 124。 -``` - -- 示例 2: - -```python -输入:digits = [4,3,2,1] -输出:[4,3,2,2] -解释:输入数组表示数字 4321。 -``` - -## 解题思路 - -### 思路 1:模拟 - -这道题把整个数组看成了一个整数,然后个位数加 $1$。问题的实质是利用数组模拟加法运算。 - -如果个位数不为 $9$ 的话,直接把个位数加 $1$ 就好。如果个位数为 $9$ 的话,还要考虑进位。 - -具体步骤: - -1. 数组前补 $0$ 位。 -2. 将个位数字进行加 $1$ 计算。 -3. 遍历数组 - 1. 如果该位数字大于等于 $10$,则向下一位进 $1$,继续下一位判断进位。 - 2. 如果该位数字小于 $10$,则跳出循环。 - -### 思路 1:代码 - -```python -def plusOne(self, digits: List[int]) -> List[int]: - digits = [0] + digits - digits[len(digits) - 1] += 1 - for i in range(len(digits)-1, 0, -1): - if digits[i] != 10: - break - else: - digits[i] = 0 - digits[i - 1] += 1 - - if digits[0] == 0: - return digits[1:] - else: - return digits -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$ 。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0067. \344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.md" "b/Solutions/0067. \344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.md" deleted file mode 100644 index ccdc4fdc..00000000 --- "a/Solutions/0067. \344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [0067. 二进制求和](https://leetcode.cn/problems/add-binary/) - -- 标签:位运算、数学、字符串、模拟 -- 难度:简单 - -## 题目大意 - -给定两个二进制数的字符串 a、b。计算 a 和 b 的和,返回结果也用二进制表示。 - -## 解题思路 - -这道题可以直接将 a、b 转换为十进制数,相加后再转换为二进制数。 - -也可以利用位运算的一些知识,直接求和。 - -因为 a、b 为二进制的字符串,先将其转换为二进制数。 - -本题用到的位运算知识: - -- 异或运算 x ^ y :可以获得 x + y 无进位的加法结果。 -- 与运算 x & y:对应位置为 1,说明 x、y 该位置上原来都为 1,则需要进位。 -- 座椅运算 x << 1:将 a 对应二进制数左移 1 位。 - -这样,通过 x ^ y 运算,我们可以得到相加后无进位结果,再根据 (x & y) << 1,计算进位后结果。 - -进行 x ^ y 和 (x & y) << 1操作之后判断进位是否为 0,若不为 0,则继续上一步操作,直到进位为 0。 - -最后将其结果转为 2 进制返回。 - -## 代码 - -```python -class Solution: - def addBinary(self, a: str, b: str) -> str: - x = int(a, 2) - y = int(b, 2) - ans = 0 - while y: - carry = ((x & y) << 1) - x ^= y - y = carry - return bin(x)[2:] -``` - diff --git "a/Solutions/0069. x \347\232\204\345\271\263\346\226\271\346\240\271.md" "b/Solutions/0069. x \347\232\204\345\271\263\346\226\271\346\240\271.md" deleted file mode 100644 index 8359af6a..00000000 --- "a/Solutions/0069. x \347\232\204\345\271\263\346\226\271\346\240\271.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) - -- 标签:数学、二分查找 -- 难度:简单 - -## 题目大意 - -**要求**:实现 `int sqrt(int x)` 函数。计算并返回 $x$ 的平方根(只保留整数部分),其中 $x$ 是非负整数。 - -**说明**: - -- $0 \le x \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:x = 4 -输出:2 -``` - -- 示例 2: - -```python -输入:x = 8 -输出:2 -解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -因为求解的是 $x$ 开方的整数部分。所以我们可以从 $0 \sim x$ 的范围进行遍历,找到 $k^2 \le x$ 的最大结果。 - -为了减少算法的时间复杂度,我们使用二分查找的方法来搜索答案。 - -### 思路 1:代码 - -```python -class Solution: - def mySqrt(self, x: int) -> int: - left = 0 - right = x - ans = -1 - while left <= right: - mid = (left + right) // 2 - if mid * mid <= x: - ans = mid - left = mid + 1 - else: - right = mid - 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0070. \347\210\254\346\245\274\346\242\257.md" "b/Solutions/0070. \347\210\254\346\245\274\346\242\257.md" deleted file mode 100644 index ee6a715c..00000000 --- "a/Solutions/0070. \347\210\254\346\245\274\346\242\257.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) - -- 标签:记忆化搜索、数学、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:假设你正在爬楼梯。需要 $n$ 阶你才能到达楼顶。每次你可以爬 $1$ 或 $2$ 个台阶。现在给定一个整数 $n$。 - -**要求**:计算出有多少种不同的方法可以爬到楼顶。 - -**说明**: - -- $1 \le n \le 45$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:2 -解释:有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 -``` - -- 示例 2: - -```python -输入:n = 3 -输出:3 -解释:有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 -``` - -## 解题思路 - -### 思路 1:递归(超时) - -根据我们的递推三步走策略,写出对应的递归代码。 - -1. 写出递推公式:$f(n) = f(n - 1) + f(n - 2)$。 -2. 明确终止条件:$f(0) = 0, f(1) = 1$。 -3. 翻译为递归代码: - 1. 定义递归函数:`climbStairs(self, n)` 表示输入参数为问题的规模 $n$,返回结果为爬 $n$ 阶台阶到达楼顶的方案数。 - 2. 书写递归主体:`return self.climbStairs(n - 1) + self.climbStairs(n - 2)`。 - 3. 明确递归终止条件: - 1. `if n == 0: return 0` - 2. `if n == 1: return 1` - -### 思路 1:代码 - -```python -class Solution: - def climbStairs(self, n: int) -> int: - if n == 1: - return 1 - if n == 2: - return 2 - return self.climbStairs(n - 1) + self.climbStairs(n - 2) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。 -- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 - -### 思路 2:动态规划 - -###### 1. 划分阶段 - -按照台阶的层数进行划分为 $0 \sim n$。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:爬到第 $i$ 阶台阶的方案数。 - -###### 3. 状态转移方程 - -根据题目大意,每次只能爬 $1$ 或 $2$ 个台阶。则第 $i$ 阶楼梯只能从第 $i - 1$ 阶向上爬 $1$ 阶上来,或者从第 $i - 2$ 阶向上爬 $2$ 阶上来。所以可以推出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 - -###### 4. 初始条件 - -- 第 $0$ 层台阶方案数:可以看做 $1$ 种方法(从 $0$ 阶向上爬 $0$ 阶),即 $dp[0] = 1$。 -- 第 $1$ 层台阶方案数:$1$ 种方法(从 $0$ 阶向上爬 $1$ 阶),即 $dp[1] = 1$。 -- 第 $2$ 层台阶方案数:$2$ 种方法(从 $0$ 阶向上爬 $2$ 阶,或者从 $1$ 阶向上爬 $1$ 阶)。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[n]$,即爬到第 $n$ 阶台阶(即楼顶)的方案数为 $dp[n]$。 - -### 思路 2:代码 - -```python -class Solution: - def climbStairs(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - dp[0] = 1 - dp[1] = 1 - for i in range(2, n + 1): - dp[i] = dp[i - 1] + dp[i - 2] - - return dp[n] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 - diff --git "a/Solutions/0072. \347\274\226\350\276\221\350\267\235\347\246\273.md" "b/Solutions/0072. \347\274\226\350\276\221\350\267\235\347\246\273.md" deleted file mode 100644 index 47c01de5..00000000 --- "a/Solutions/0072. \347\274\226\350\276\221\350\267\235\347\246\273.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) - -- 标签:字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定两个单词 $word1$、$word2$。 - -对一个单词可以进行以下三种操作: - -- 插入一个字符 -- 删除一个字符 -- 替换一个字符 - -**要求**:计算出将 $word1$ 转换为 $word2$ 所使用的最少操作数。 - -**说明**: - -- $0 \le word1.length, word2.length \le 500$。 -- $word1$ 和 $word2$ 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:word1 = "horse", word2 = "ros" -输出:3 -解释: -horse -> rorse (将 'h' 替换为 'r') -rorse -> rose (删除 'r') -rose -> ros (删除 'e') -``` - -- 示例 2: - -```python -输入:word1 = "intention", word2 = "execution" -输出:5 -解释: -intention -> inention (删除 't') -inention -> enention (将 'i' 替换为 'e') -enention -> exention (将 'n' 替换为 'x') -exention -> exection (将 'n' 替换为 'c') -exection -> execution (插入 'u') -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,所需要的最少操作次数。 - -###### 3. 状态转移方程 - -1. 如果当前字符相同($word1[i - 1] = word2[j - 1]$),无需插入、删除、替换。$dp[i][j] = dp[i - 1][j - 1]$。 -2. 如果当前字符不同($word1[i - 1] \ne word2[j - 1]$),$dp[i][j]$ 取源于以下三种情况中的最小情况: - 1. 替换($word1[i - 1]$ 替换为 $word2[j - 1]$):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上替换的操作数 $1$,即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 - 2. 插入($word1$ 在第 $i - 1$ 位置上插入元素):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」 变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,再加上插入需要的操作数 $1$,即:$dp[i][j] = dp[i - 1][j] + 1$。 - 3. 删除($word1$ 删除第 $i - 1$ 位置元素):最少操作次数依赖于「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上删除需要的操作数 $1$,即:$dp[i][j] = dp[i][j - 1] + 1$。 - -综合上述情况,状态转移方程为: - -$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & word1[i - 1] = word2[j - 1] \cr min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 & word1[i - 1] \ne word2[j - 1] \end{cases}$ - -###### 4. 初始条件 - -- 当 $i = 0$,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」为空字符串,「$str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」时,至少需要插入 $j$ 次,即:$dp[0][j] = j$。 -- 当 $j = 0$,「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」为空字符串,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「$str2$」时,至少需要删除 $i$ 次,即:$dp[i][0] = i$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[sise1][size2]$(即 $word1$ 变为 $word2$ 所使用的最少操作数)即可。其中 $size1$、$size2$ 分别为 $word1$、$word2$ 的字符串长度。 - -### 思路 1:代码 - -```python -class Solution: - def minDistance(self, word1: str, word2: str) -> int: - size1 = len(word1) - size2 = len(word2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - - for i in range(size1 + 1): - dp[i][0] = i - for j in range(size2 + 1): - dp[0][j] = j - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if word1[i - 1] == word2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] - else: - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 - return dp[size1][size2] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $word1$、$word2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 -- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 diff --git "a/Solutions/0073. \347\237\251\351\230\265\347\275\256\351\233\266.md" "b/Solutions/0073. \347\237\251\351\230\265\347\275\256\351\233\266.md" deleted file mode 100644 index 634bd106..00000000 --- "a/Solutions/0073. \347\237\251\351\230\265\347\275\256\351\233\266.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0073. 矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) - -- 标签:数组、哈希表、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的矩阵 $matrix$。 - -**要求**:如果一个元素为 $0$,则将其所在行和列所有元素都置为 $0$。 - -**说明**: - -- 请使用「原地」算法。 -- $m == matrix.length$。 -- $n == matrix[0].length$。 -- $1 \le m, n \le 200$。 -- $-2^{31} \le matrix[i][j] \le 2^{31} - 1$。 -- **进阶**: - - 一个直观的解决方案是使用 $O(m \times n)$ 的额外空间,但这并不是一个好的解决方案。 - - 一个简单的改进方案是使用 $O(m + n)$ 的额外空间,但这仍然不是最好的解决方案。 - - 你能想出一个仅使用常量空间的解决方案吗? - - -**示例**: - -- 示例 1: -- ![](https://assets.leetcode.com/uploads/2020/08/17/mat1.jpg) - -```python -输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] -输出:[[1,0,1],[0,0,0],[1,0,1]] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/08/17/mat2.jpg) - -``` -输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] -输出:[[1,0,1],[0,0,0],[1,0,1]] -``` - -## 解题思路 - -### 思路 1:使用标记变量 - -直观上可以使用两个数组来标记行和列出现 $0$ 的情况,但这样空间复杂度就是 $O(m+n)$ 了,不符合题意。 - -考虑使用数组原本的元素进行记录出现 $0$ 的情况。 - -1. 设定两个变量 $flag\underline{}row0$、$flag\underline{}col0$ 来标记第一行、第一列是否出现了 $0$。 -2. 接下来我们使用数组第一行、第一列来标记 $0$ 的情况。 -3. 对数组除第一行、第一列之外的每个元素进行遍历,如果某个元素出现 $0$ 了,则使用数组的第一行、第一列对应位置来存储 $0$ 的标记。 -4. 再对数组除第一行、第一列之外的每个元素进行遍历,通过对第一行、第一列的标记 $0$ 情况,进行置为 $0$ 的操作。 -5. 最后再根据 $flag\underline{}row0$、$flag\underline{}col0$ 的标记情况,对第一行、第一列进行置为 $0$ 的操作。 - -### 思路 1:代码 - -```python -class Solution: - def setZeroes(self, matrix: List[List[int]]) -> None: - m = len(matrix) - n = len(matrix[0]) - flag_col0 = False - flag_row0 = False - for i in range(m): - if matrix[i][0] == 0: - flag_col0 = True - break - - for j in range(n): - if matrix[0][j] == 0: - flag_row0 = True - break - - for i in range(1, m): - for j in range(1, n): - if matrix[i][j] == 0: - matrix[i][0] = matrix[0][j] = 0 - - for i in range(1, m): - for j in range(1, n): - if matrix[i][0] == 0 or matrix[0][j] == 0: - matrix[i][j] = 0 - - if flag_col0: - for i in range(m): - matrix[i][0] = 0 - - if flag_row0: - for j in range(n): - matrix[0][j] = 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0074. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.md" "b/Solutions/0074. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.md" deleted file mode 100644 index 588f66fb..00000000 --- "a/Solutions/0074. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.md" +++ /dev/null @@ -1,118 +0,0 @@ -# [0074. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) - -- 标签:数组、二分查找、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的有序二维矩阵 $matrix$。矩阵中每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 $target$。 - -**要求**:判断矩阵中是否存在目标值 $target$。 - -**说明**: - -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le m, n \le 100$。 -- $-10^4 \le matrix[i][j], target \le 10^4$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/05/mat.jpg) - -```python -输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 -输出:True -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/mat2.jpg) - -```python -输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 -输出:False -``` - -## 解题思路 - -### 思路 1:二分查找 - -二维矩阵是有序的,可以考虑使用二分搜索来进行查找。 - -1. 首先二分查找遍历对角线元素,假设对角线元素的坐标为 $(row, col)$。把数组元素按对角线分为右上角部分和左下角部分。 -2. 然后对于当前对角线元素右侧第 $row$ 行、对角线元素下侧第 $col$ 列进行二分查找。 - 1. 如果找到目标,直接返回 `True`。 - 2. 如果找不到目标,则缩小范围,继续查找。 - 3. 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - # 二分查找对角线元素 - def diagonalBinarySearch(self, matrix, diagonal, target): - left = 0 - right = diagonal - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][mid] < target: - left = mid + 1 - else: - right = mid - return left - - def rowBinarySearch(self, matrix, begin, cols, target): - left = begin - right = cols - while left < right: - mid = left + (right - left) // 2 - if matrix[begin][mid] < target: - left = mid + 1 - elif matrix[begin][mid] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= cols and matrix[begin][left] == target - - def colBinarySearch(self, matrix, begin, rows, target): - left = begin + 1 - right = rows - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][begin] < target: - left = mid + 1 - elif matrix[mid][begin] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= rows and matrix[left][begin] == target - - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - rows = len(matrix) - if rows == 0: - return False - cols = len(matrix[0]) - if cols == 0: - return False - - min_val = min(rows, cols) - index = self.diagonalBinarySearch(matrix, min_val - 1, target) - if matrix[index][index] == target: - return True - for i in range(index + 1): - row_search = self.rowBinarySearch(matrix, i, cols - 1, target) - col_search = self.colBinarySearch(matrix, i, rows - 1, target) - if row_search or col_search: - return True - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log m + \log n)$,其中 $m$、$n$ 分别是矩阵的行数和列数。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0075. \351\242\234\350\211\262\345\210\206\347\261\273.md" "b/Solutions/0075. \351\242\234\350\211\262\345\210\206\347\261\273.md" deleted file mode 100644 index d7ec4f2e..00000000 --- "a/Solutions/0075. \351\242\234\350\211\262\345\210\206\347\261\273.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $nums$,元素值只有 $0$、$1$、$2$,分别代表红色、白色、蓝色。 - -**要求**:将数组进行排序,使得红色在前,白色在中间,蓝色在最后。 - -**说明**: - -- 要求不使用标准库函数,同时仅用常数空间,一趟扫描解决。 -- $n == nums.length$。 -- $1 \le n \le 300$。 -- $nums[i]$ 为 $0$、$1$ 或 $2$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,0,2,1,1,0] -输出:[0,0,1,1,2,2] -``` - -- 示例 2: - -```python -输入:nums = [2,0,1] -输出:[0,1,2] -``` - -## 解题思路 - -### 思路 1:双指针 + 快速排序思想 - -快速排序算法中的 $partition$ 过程,利用双指针,将序列中比基准数 $pivot$ 大的元素移动到了基准数右侧,将比基准数 $pivot$ 小的元素移动到了基准数左侧。从而将序列分为了三部分:比基准数小的部分、基准数、比基准数大的部分。 - -这道题我们也可以借鉴快速排序算法中的 $partition$ 过程,将 $1$ 作为基准数 $pivot$,然后将序列分为三部分:$0$(即比 $1$ 小的部分)、等于 $1$ 的部分、$2$(即比 $1$ 大的部分)。具体步骤如下: - -1. 使用两个指针 $left$、$right$,分别指向数组的头尾。$left$ 表示当前处理好红色元素的尾部,$right$ 表示当前处理好蓝色的头部。 -2. 再使用一个下标 $index$ 遍历数组,如果遇到 $nums[index] == 0$,就交换 $nums[index]$ 和 $nums[left]$,同时将 $left$ 右移。如果遇到 $nums[index] == 2$,就交换 $nums[index]$ 和 $nums[right]$,同时将 $right$ 左移。 -3. 直到 $index$ 移动到 $right$ 位置之后,停止遍历。遍历结束之后,此时 $left$ 左侧都是红色,$right$ 右侧都是蓝色。 - -注意:移动的时候需要判断 $index$ 和 $left$ 的位置,因为 $left$ 左侧是已经处理好的数组,所以需要判断 $index$ 的位置是否小于 $left$,小于的话,需要更新 $index$ 位置。 - -### 思路 1:代码 - -```python -class Solution: - def sortColors(self, nums: List[int]) -> None: - left = 0 - right = len(nums) - 1 - index = 0 - while index <= right: - if index < left: - index += 1 - elif nums[index] == 0: - nums[index], nums[left] = nums[left], nums[index] - left += 1 - elif nums[index] == 2: - nums[index], nums[right] = nums[right], nums[index] - right -= 1 - else: - index += 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0076. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" "b/Solutions/0076. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" deleted file mode 100644 index b82b56a8..00000000 --- "a/Solutions/0076. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个字符串 `s`、一个字符串 `t`。 - -**要求**:返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""`。 - -**说明**: - -- $1 \le s.length, t.length \le 10^5$。 -- `s` 和 `t` 由英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "ADOBECODEBANC", t = "ABC" -输出:"BANC" -``` - -- 示例 2: - -```python -输入:s = "a", t = "a" -输出:"a" -``` - -## 解题思路 - -### 思路 1:滑动窗口 - -1. `left`、`right` 表示窗口的边界,一开始都位于下标 `0` 处。`need` 用于记录短字符串需要的字符数。`window` 记录当前窗口内的字符数。 -2. 将 `right` 右移,直到出现了 `t` 中全部字符,开始右移 `left`,减少滑动窗口的大小,并记录下最小覆盖子串的长度和起始位置。 -3. 最后输出结果。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - def minWindow(self, s: str, t: str) -> str: - need = collections.defaultdict(int) - window = collections.defaultdict(int) - for ch in t: - need[ch] += 1 - - left, right = 0, 0 - valid = 0 - start = 0 - size = len(s) + 1 - - while right < len(s): - insert_ch = s[right] - right += 1 - - if insert_ch in need: - window[insert_ch] += 1 - if window[insert_ch] == need[insert_ch]: - valid += 1 - - while valid == len(need): - if right - left < size: - start = left - size = right - left - remove_ch = s[left] - left += 1 - if remove_ch in need: - if window[remove_ch] == need[remove_ch]: - valid -= 1 - window[remove_ch] -= 1 - if size == len(s) + 1: - return '' - return s[start:start+size] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是字符串 $s$ 的长度。 -- **空间复杂度**:$O(| \sum |)$。$| \sum |$ 是 $s$ 和 $t$ 的字符集大小。 \ No newline at end of file diff --git "a/Solutions/0077. \347\273\204\345\220\210.md" "b/Solutions/0077. \347\273\204\345\220\210.md" deleted file mode 100644 index 46bcebe5..00000000 --- "a/Solutions/0077. \347\273\204\345\220\210.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [0077. 组合](https://leetcode.cn/problems/combinations/) - -- 标签:回溯 -- 难度:中等 - -## 题目大意 - -给定两个整数 `n` 和 `k`,返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。可以按任何顺序返回答案。 - -## 解题思路 - -组合问题通常可以用回溯算法来解决。定义两个数组 res、path。res 用来存放最终答案,path 用来存放当前符合条件的一个结果。再使用一个变量 start_index 来表示从哪一个数开始遍历。 - -定义回溯方法,start_index = 1 开始进行回溯。 - -- 如果 path 数组的长度等于 k,则将 path 中的元素加入到 res 数组中。 -- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 - - 将当前元素 i 加入 path 数组。 - - 递归遍历 `[start_index, n]` 上的数。 - - 将遍历的 i 元素进行回退。 -- 最终返回 res 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, n: int, k: int, start_index: int): - if len(self.path) == k: - self.res.append(self.path[:]) - return - for i in range(start_index, n - (k - len(self.path)) + 2): - self.path.append(i) - self.backtrack(n, k, i + 1) - self.path.pop() - - def combine(self, n: int, k: int) -> List[List[int]]: - self.res.clear() - self.path.clear() - self.backtrack(n, k, 1) - return self.res -``` - diff --git "a/Solutions/0078. \345\255\220\351\233\206.md" "b/Solutions/0078. \345\255\220\351\233\206.md" deleted file mode 100644 index 5e97bd38..00000000 --- "a/Solutions/0078. \345\255\220\351\233\206.md" +++ /dev/null @@ -1,155 +0,0 @@ -# [0078. 子集](https://leetcode.cn/problems/subsets/) - -- 标签:位运算、数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`,数组中的元素互不相同。 - -**要求**:返回该数组所有可能的不重复子集。可以按任意顺序返回解集。 - -**说明**: - -- $1 \le nums.length \le 10$。 -- $-10 \le nums[i] \le 10$。 -- `nums` 中的所有元素互不相同。 - -**示例**: - -- 示例 1: - -```python -输入 nums = [1,2,3] -输出 [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] -``` - -- 示例 2: - -```python -输入:nums = [0] -输出:[[],[0]] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -数组的每个元素都有两个选择:选与不选。 - -我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 - -下面我们根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:根据数组中每个位置上的元素选与不选两种选择,画出决策树,如下图所示。 - - - ![](https://qcdn.itcharge.cn/images/20220425210640.png) - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - 1. 定义回溯函数: - - - `backtracking(nums, index):` 函数的传入参数是 `nums`(可选数组列表)和 `index`(代表当前正在考虑元素是 `nums[i]` ),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 - - `backtracking(nums, index):` 函数代表的含义是:在选择 `nums[index]` 的情况下,递归选择剩下的元素。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: - - 约束条件:之前选过的元素不再重复选用。每次从 `index` 位置开始遍历而不是从 `0` 位置开始遍历就是为了避免重复。集合跟全排列不一样,子集中 `{1, 2}` 和 `{2, 1}` 是等价的。为了避免重复,我们之前考虑过的元素,就不再重复考虑了。 - - 选择元素:将其添加到当前子集数组 `path` 中。 - - 递归搜索:在选择该元素的情况下,继续递归考虑下一个位置上的元素。 - - 撤销选择:将该元素从当前子集数组 `path` 中移除。 - ```python - for i in range(index, len(nums)): # 枚举可选元素列表 - path.append(nums[i]) # 选择元素 - backtracking(nums, i + 1) # 递归搜索 - path.pop() # 撤销选择 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是当正在考虑的元素位置到达数组末尾(即 `start >= len(nums)`)时,递归停止。 - - 从决策树中也可以看出,子集需要存储的答案集合应该包含决策树上所有的节点,应该需要保存递归搜索的所有状态。所以无论是否达到终止条件,我们都应该将当前符合条件的结果放入到集合中。 - -### 思路 1:代码 - -```python -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - res = [] # 存放所有符合条件结果的集合 - path = [] # 存放当前符合条件的结果 - def backtracking(nums, index): # 正在考虑可选元素列表中第 index 个元素 - res.append(path[:]) # 将当前符合条件的结果放入集合中 - if index >= len(nums): # 遇到终止条件(本题) - return - - for i in range(index, len(nums)): # 枚举可选元素列表 - path.append(nums[i]) # 选择元素 - backtracking(nums, i + 1) # 递归搜索 - path.pop() # 撤销选择 - - backtracking(nums, 0) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 -- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 - -### 思路 2:二进制枚举 - -对于一个元素个数为 `n` 的集合 `nums` 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 `1` 来表示选取该元素,用数字 `0` 来表示不选取该元素。 - -那么我们就可以用一个长度为 `n` 的二进制数来表示集合 `nums` 或者表示 `nums` 的子集。其中二进制的每一位数都对应了集合中某一个元素的选取状态。对于集合中第 `i` 个元素(`i` 从 `0` 开始编号)来说,二进制对应位置上的 `1` 代表该元素被选取,`0` 代表该元素未被选取。 - -举个例子来说明一下,比如长度为 `5` 的集合 `nums = {5, 4, 3, 2, 1}`,我们可以用一个长度为 `5` 的二进制数来表示该集合。 - -比如二进制数 `11111` 就表示选取集合的第 `0` 位、第 `1` 位、第 `2` 位、第 `3` 位、第 `4` 位元素,也就是集合 `{5, 4, 3, 2, 1}` ,即集合 `nums` 本身。如下表所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :--: | :--: | :--: | :--: | :--: | -| 二进制数对应位数 | 1 | 1 | 1 | 1 | 1 | -| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | - -再比如二进制数 `10101` 就表示选取集合的第 `0` 位、第 `2` 位、第 `5` 位元素,也就是集合 `{5, 3, 1}`。如下表所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :--: | :----: | :--: | :----: | :--: | -| 二进制数对应位数 | 1 | 0 | 1 | 0 | 1 | -| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | - -再比如二进制数 `01001` 就表示选取集合的第 `0` 位、第 `3` 位元素,也就是集合 `{5, 2}`。如下标所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :----: | :--: | :----: | :----: | :--: | -| 二进制数对应位数 | 0 | 1 | 0 | 0 | 1 | -| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | - -通过上面的例子我们可以得到启发:对于长度为 `5` 的集合 `nums` 来说,我们只需要从 `00000` ~ `11111` 枚举一次(对应十进制为 $0 \sim 2^4 - 1$)即可得到长度为 `5` 的集合 `S` 的所有子集。 - -我们将上面的例子拓展到长度为 `n` 的集合 `nums`。可以总结为: - -- 对于长度为 `5` 的集合 `nums` 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到所有的子集。 - -### 思路 2:代码 - -```python -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - n = len(nums) # n 为集合 nums 的元素个数 - sub_sets = [] # sub_sets 用于保存所有子集 - for i in range(1 << n): # 枚举 0 ~ 2^n - 1 - sub_set = [] # sub_set 用于保存当前子集 - for j in range(n): # 枚举第 i 位元素 - if i >> j & 1: # 如果第 i 为元素对应二进制位为 1,则表示选取该元素 - sub_set.append(nums[j]) # 将选取的元素加入到子集 sub_set 中 - sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 - return sub_sets # 返回所有子集 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 -- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 - diff --git "a/Solutions/0079. \345\215\225\350\257\215\346\220\234\347\264\242.md" "b/Solutions/0079. \345\215\225\350\257\215\346\220\234\347\264\242.md" deleted file mode 100644 index d8149565..00000000 --- "a/Solutions/0079. \345\215\225\350\257\215\346\220\234\347\264\242.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0079. 单词搜索](https://leetcode.cn/problems/word-search/) - -- 标签:数组、回溯、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的二维字符矩阵 `board` 和一个字符串单词 `word`。 - -**要求**:如果 `word` 存在于网格中,返回 `True`,否则返回 `False`。 - -**说明**: - -- 单词必须按照字母顺序通过上下左右相邻的单元格字母构成。且同一个单元格内的字母不允许被重复使用。 -- $m == board.length$。 -- $n == board[i].length$。 -- $1 \le m, n \le 6$。 -- $1 \le word.length \le 15$。 -- `board` 和 `word` 仅由大小写英文字母组成。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/04/word2.jpg) - -```python -输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" -输出:true -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/04/word-1.jpg) - -```python -输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE" -输出:true -``` - -## 解题思路 - -### 思路 1:回溯算法 - -使用回溯算法在二维矩阵 `board` 中按照上下左右四个方向递归搜索。 - -设函数 `backtrack(i, j, index)` 表示从 `board[i][j]` 出发,能否搜索到单词字母 `word[index]`,以及 `index` 位置之后的后缀子串。如果能搜索到,则返回 `True`,否则返回 `False`。 - -`backtrack(i, j, index)` 执行步骤如下: - -1. 如果 $board[i][j] = word[index]$,而且 index 已经到达 word 字符串末尾,则返回 True。 -2. 如果 $board[i][j] = word[index]$,而且 index 未到达 word 字符串末尾,则遍历当前位置的所有相邻位置。如果从某个相邻位置能搜索到后缀子串,则返回 True,否则返回 False。 -3. 如果 $board[i][j] \ne word[index]$,则当前字符不匹配,返回 False。 - -### 思路 1:代码 - -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - rows = len(board) - if rows == 0: - return False - cols = len(board[0]) - visited = [[False for _ in range(cols)] for _ in range(rows)] - - def backtrack(i, j, index): - if index == len(word) - 1: - return board[i][j] == word[index] - - if board[i][j] == word[index]: - visited[i][j] = True - for direct in directs: - new_i = i + direct[0] - new_j = j + direct[1] - if 0 <= new_i < rows and 0 <= new_j < cols and visited[new_i][new_j] == False: - if backtrack(new_i, new_j, index + 1): - return True - visited[i][j] = False - return False - - for i in range(rows): - for j in range(cols): - if backtrack(i, j, 0): - return True - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n \times 2^l)$,其中 $m$、$n$ 为二维矩阵 `board`的行数和列数。$l$ 为字符串 `word` 的长度。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0080. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" "b/Solutions/0080. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" deleted file mode 100644 index 558ab094..00000000 --- "a/Solutions/0080. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [0080. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) - -- 标签:数组、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个有序数组 $nums$。 - -**要求**:在原数组空间基础上删除重复出现 $2$ 次以上的元素,并返回删除后数组的新长度。 - -**说明**: - -- $1 \le nums.length \le 3 * 10^4$。 -- $-10^4 \le nums[i] \le 10^4$。 -- $nums$ 已按升序排列。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,1,2,2,3] -输出:5, nums = [1,1,2,2,3] -解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。 -``` - -- 示例 2: - -```python -输入:nums = [0,0,1,1,1,1,2,3,3] -输出:7, nums = [0,0,1,1,2,3,3] -解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 不需要考虑数组中超出新长度后面的元素。 -``` - -## 解题思路 - -### 思路 1:快慢指针 - -因为数组是有序的,所以重复元素必定是连续的。可以使用快慢指针来解决。具体做法如下: - -1. 使用两个指针 $slow$,$fast$。$slow$ 指针指向即将放置元素的位置,$fast$ 指针指向当前待处理元素。 -2. 本题要求相同元素最多出现 $2$ 次,并且 $slow - 2$ 是上上次放置了元素的位置。则应该检查 $nums[slow - 2]$ 和当前待处理元素 $nums[fast]$ 是否相同。 - 1. 如果 $nums[slow - 2] == nums[fast]$ 时,此时必有 $nums[slow - 2] == nums[slow - 1] == nums[fast]$,则当前 $nums[fast]$ 不保留,直接向右移动快指针 $fast$。 - 2. 如果 $nums[slow - 2] \ne nums[fast]$ 时,则保留 $nums[fast]$。将 $nums[fast]$ 赋值给 $nums[slow]$ ,同时将 $slow$ 右移。然后再向右移动快指针 $fast$。 -3. 这样 $slow$ 指针左边均为处理好的数组元素,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边都为舍弃的重复元素。 -4. 遍历结束之后,此时 $slow$ 就是新数组的长度。 - -### 思路 1:代码 - -```python -class Solution: - def removeDuplicates(self, nums: List[int]) -> int: - size = len(nums) - if size <= 2: - return size - slow, fast = 2, 2 - while (fast < size): - if nums[slow - 2] != nums[fast]: - nums[slow] = nums[fast] - slow += 1 - fast += 1 - return slow -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0081. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II.md" "b/Solutions/0081. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II.md" deleted file mode 100644 index a9968e0a..00000000 --- "a/Solutions/0081. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II.md" +++ /dev/null @@ -1,119 +0,0 @@ -# [0081. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:一个按照升序排列的整数数组 $nums$,在位置的某个下标 $k$ 处进行了旋转操作。(例如:$[0, 1, 2, 5, 6, 8]$ 可能变为 $[5, 6, 8, 0, 1, 2]$)。 - -现在给定旋转后的数组 $nums$ 和一个整数 $target$。 - -**要求**:编写一个函数来判断给定的 $target$ 是否存在与数组中。如果存在则返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le nums.length \le 5000$。 -- $-10^4 \le nums[i] \le 10^4$。 -- 题目数据保证 $nums$ 在预先未知的某个下标上进行了旋转。 -- $-10^4 \le target \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,5,6,0,0,1,2], target = 0 -输出:true -``` - -- 示例 2: - -```python -输入:nums = [2,5,6,0,0,1,2], target = 3 -输出:false -``` - -## 解题思路 - -### 思路 1:二分查找 - -这道题算是「[0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)」的变形,只不过输出变为了判断。 - -原本为升序排列的数组 nums 经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 - -``` - * - * - * - * - * -* -``` - -``` - * - * -* - * - * - * -``` - -最直接的办法就是遍历一遍,找到目标值 target。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 - -我们将旋转后的数组看成左右两个升序部分:左半部分和右半部分。 - -有人会说第一种情况不是只有一个部分吗?其实我们可以把第一种情况中的整个数组看做是左半部分,然后右半部分为空数组。 - -然后创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较,并考虑与 $target$ 的关系。 - -- 如果 $nums[mid] > nums[left]$,则 $mid$ 在左半部分(因为右半部分值都比 $nums[left]$ 小)。 - - 如果 $nums[mid] \ge target$,并且 $target \ge nums[left]$,则 $target$ 在左半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 - - 否则如果 $nums[mid] < target$,则 $target$ 在左半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$。 - - 否则如果 $nums[left] > target$,则 $target$ 在右半部分,应将 $left$ 移动到 $mid + 1$ 位置。 - -- 如果 $nums[mid] < nums[left]$,则 $mid$ 在右半部分(因为右半部分值都比 $nums[left]$ 小)。 - - 如果 $nums[mid] < target$,并且 $target \le nums[right]$,则 $target$ 在右半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 - - 否则如果 $nums[mid] \ge target$,则 $target$ 在右半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 - - 否则如果 $nums[right] < target$,则 $target$ 在左半部分,应将 $right$ 左移到 $mid - 1$ 位置。 -- 最终判断 $nums[left]$ 是否等于 $target$,如果等于,则返回 `True`,否则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> bool: - n = len(nums) - if n == 0: - return False - - left = 0 - right = len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - - if nums[mid] > nums[left]: - if nums[left] <= target and target <= nums[mid]: - right = mid - else: - left = mid + 1 - elif nums[mid] < nums[left]: - if nums[mid] < target and target <= nums[right]: - left = mid + 1 - else: - right = mid - else: - if nums[mid] == target: - return True - else: - left = left + 1 - - return nums[left] == target -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的长度。最坏情况下数组元素均相等且不为 $target$,我们需要访问所有位置才能得出结果。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0082. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" "b/Solutions/0082. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" deleted file mode 100644 index 031abdc0..00000000 --- "a/Solutions/0082. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) - -- 标签:链表、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个已排序的链表的头 `head`。 - -**要求**:删除原始链表中所有重复数字的节点,只留下不同的数字。返回已排序的链表。 - -**说明**: - -- 链表中节点数目在范围 $[0, 300]$ 内。 -- $-100 \le Node.val \le 100$。 -- 题目数据保证链表已经按升序排列。 - -**示例**: - -- 示例 1: - -```python -输入:head = [1,2,3,3,4,4,5] -输出:[1,2,5] -``` - -## 解题思路 - -### 思路 1:遍历 - -这道题的题意是需要保留所有不同数字,而重复出现的所有数字都要删除。因为给定的链表是升序排列的,所以我们要删除的重复元素在链表中的位置是连续的。所以我们可以对链表进行一次遍历,然后将连续的重复元素从链表中删除即可。具体步骤如下: - -- 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以防止从 `head` 开始就是重复元素。 -- 然后使用指针 `cur` 表示链表中当前元素,从 `head` 开始遍历。 -- 当指针 `cur` 的下一个元素和下下一个元素存在时: - - 如果下一个元素值和下下一个元素值相同,则我们使用指针 `temp` 保存下一个元素,并使用 `temp` 向后遍历,跳过所有重复元素,然后令 `cur` 的下一个元素指向 `temp` 的下一个元素,继续向后遍历。 - - 如果下一个元素值和下下一个元素值不同,则令 `cur` 向右移动一位,继续向后遍历。 -- 当指针 `cur` 的下一个元素或者下下一个元素不存在时,说明已经遍历完,则返回哑节点 `dummy_head` 的下一个节点作为头节点。 - -### 思路 1:代码 - -```python -# Definition for singly-linked list. -# class ListNode: -# def __init__(self, val=0, next=None): -# self.val = val -# self.next = next -class Solution: - def deleteDuplicates(self, head: ListNode) -> ListNode: - dummy_head = ListNode(-1) - dummy_head.next = head - - cur = dummy_head - while cur.next and cur.next.next: - if cur.next.val == cur.next.next.val: - temp = cur.next - while temp and temp.next and temp.val == temp.next.val: - temp = temp.next - cur.next = temp.next - else: - cur = cur.next - return dummy_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为链表长度。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0083. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" "b/Solutions/0083. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" deleted file mode 100644 index 65e7cbad..00000000 --- "a/Solutions/0083. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) - -- 标签:链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个已排序的链表的头 `head`。 - -**要求**:删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。 - -**说明**: - -- 链表中节点数目在范围 $[0, 300]$ 内。 -- $-100 \le Node.val \le 100$。 -- 题目数据保证链表已经按升序排列。 - -**示例**: - -- 示例 1: - -```python -输入:head = [1,1,2,3,3] -输出:[1,2,3] -``` - -## 解题思路 - -### 思路 1:遍历 - -- 使用指针 `curr` 遍历链表,先将 `head` 保存到 `curr` 指针。 -- 判断当前元素的值和当前元素下一个节点元素值是否相等。 -- 如果相等,则让当前指针指向当前指针下两个节点。 -- 否则,让 `curr` 继续向后遍历。 -- 遍历完之后返回头节点 `head`。 - -### 思路 1:遍历代码 - -```python -class Solution: - def deleteDuplicates(self, head: ListNode) -> ListNode: - if head == None: - return head - - curr = head - while curr.next: - if curr.val == curr.next.val: - curr.next = curr.next.next - else: - curr = curr.next - return head -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为链表长度。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0084. \346\237\261\347\212\266\345\233\276\344\270\255\346\234\200\345\244\247\347\232\204\347\237\251\345\275\242.md" "b/Solutions/0084. \346\237\261\347\212\266\345\233\276\344\270\255\346\234\200\345\244\247\347\232\204\347\237\251\345\275\242.md" deleted file mode 100644 index dc771d44..00000000 --- "a/Solutions/0084. \346\237\261\347\212\266\345\233\276\344\270\255\346\234\200\345\244\247\347\232\204\347\237\251\345\275\242.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0084. 柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) - -- 标签:栈、数组、单调栈 -- 难度:困难 - -## 题目大意 - -给定一个非负整数数组 `heights` ,`heights[i]` 用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 - -要求:计算出在该柱状图中,能够勾勒出来的矩形的最大面积。 - -## 解题思路 - -思路一:枚举「宽度」。一重循环枚举所有柱子,第二重循环遍历柱子右侧的柱子,所得的宽度就是两根柱子形成区间的宽度,高度就是这段区间中的最小高度。然后计算出对应面积,记录并更新最大面积。这样下来,时间复杂度为 $O(n^2)$。 - -思路二:枚举「高度」。一重循环枚举所有柱子,以柱子高度为当前矩形高度,然后向两侧延伸,遇到小于当前矩形高度的情况就停止。然后计算当前矩形面积,记录并更新最大面积。这样下来,时间复杂度也是 $O(n^2)$。 - -思路三:利用「单调栈」减少两侧延伸的复杂度。 - -- 枚举所有柱子。 -- 如果当前柱子高度较大,大于等于栈顶柱体的高度,则直接将当前柱体入栈。 -- 如果当前柱体高度较小,小于栈顶柱体的高度,则一直出栈,直到当前柱体大于等于栈顶柱体高度。 - - 出栈后,说明当前柱体是出栈柱体向右找到的第一个小于当前柱体高度的柱体,那么就可以向右将宽度扩展到当前柱体。 - - 出栈后,说明新的栈顶柱体是出栈柱体向左找到的第一个小于新的栈顶柱体高度的柱体,那么就可以向左将宽度扩展到新的栈顶柱体。 - - 以新的栈顶柱体为左边界,当前柱体为右边界,以出栈柱体为高度。计算矩形面积,然后记录并更新最大面积。 - -## 代码 - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - heights.append(0) - ans = 0 - stack = [] - for i in range(len(heights)): - while stack and heights[stack[-1]] >= heights[i]: - cur = stack.pop(-1) - left = stack[-1] + 1 if stack else 0 - right = i - 1 - ans = max(ans, (right - left + 1) * heights[cur]) - stack.append(i) - - return ans -``` - diff --git "a/Solutions/0088. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.md" "b/Solutions/0088. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.md" deleted file mode 100644 index 7dbdb923..00000000 --- "a/Solutions/0088. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.md" +++ /dev/null @@ -1,72 +0,0 @@ -# [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) - -- 标签:数组、双指针、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个有序数组 $nums1$、$nums2$。 - -**要求**:将 $nums2$ 合并到 $nums1$ 中,使 $nums1$ 成为一个有序数组。 - -**说明**: - -- 给定数组 $nums1$ 空间大小为$ m + n$ 个,其中前 $m$ 个为 $nums1$ 的元素。$nums2$ 空间大小为 $n$。这样可以用 $nums1$ 的空间来存储最终的有序数组。 -- $nums1.length == m + n$。 -- $nums2.length == n$。 -- $0 \le m, n \le 200$。 -- $1 \le m + n \le 200$。 -- $-10^9 \le nums1[i], nums2[j] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 -输出:[1,2,2,3,5,6] -解释:需要合并 [1,2,3] 和 [2,5,6] 。 -合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 -``` - -- 示例 2: - -```python -输入:nums1 = [1], m = 1, nums2 = [], n = 0 -输出:[1] -解释:需要合并 [1] 和 [] 。 -合并结果是 [1] 。 -``` - -## 解题思路 - -### 思路 1:快慢指针 - -1. 将两个指针 $index1$、$index2$ 分别指向 $nums1$、$nums2$ 数组的尾部,再用一个指针 $index$ 指向数组 $nums1$ 的尾部。 -2. 从后向前判断当前指针下 $nums1[index1]$ 和 $nums[index2]$ 的值大小,将较大值存入 $num1[index]$ 中,然后继续向前遍历。 -3. 最后再将 $nums2$ 中剩余元素赋值到 $num1$ 前面对应位置上。 - -### 思路 1:代码 - -```python -class Solution: - def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: - index1 = m - 1 - index2 = n - 1 - index = m + n - 1 - while index1 >= 0 and index2 >= 0: - if nums1[index1] < nums2[index2]: - nums1[index] = nums2[index2] - index2 -= 1 - else: - nums1[index] = nums1[index1] - index1 -= 1 - index -= 1 - - nums1[:index2+1] = nums2[:index2+1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$。 -- **空间复杂度**:$O(m + n)$。 diff --git "a/Solutions/0089. \346\240\274\351\233\267\347\274\226\347\240\201.md" "b/Solutions/0089. \346\240\274\351\233\267\347\274\226\347\240\201.md" deleted file mode 100644 index e529af9e..00000000 --- "a/Solutions/0089. \346\240\274\351\233\267\347\274\226\347\240\201.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [0089. 格雷编码](https://leetcode.cn/problems/gray-code/) - -- 标签:位运算、数学、回溯 -- 难度:中等 - -## 题目大意 - -- 格雷编码:二进制数字系统,两个连续的数值仅有一个位数的差异。 - -现在给定一个代表格雷编码总位数的非负整数 `n`,打印对应的格雷编码序列。只需要返回其中一个答案即可。 - -## 解题思路 - -- 格雷编码生成规则:以二进制值为 `0` 的格雷编码作为第 `0` 项,第一次改变最右边的数位,第二次改变从右边数第一个为 `1` 的数位左边的数位,第三次跟第一次一样,改变最右边的数位,第四次跟第二次一样,改变从右边数第一个为 `1` 的数位左边的数位。此后,第五、六次,第七、八次 ... 都跟第一二次一样反复进行,直到生成 $2^n$​ 个格雷编码。 - -- 也可以直接利用二进制转换为格雷编码公式: - - ![image.png](https://pic.leetcode-cn.com/1013850d7f6c8cf1d99dc0ac3292264b74f6a52d84e0215f540c80952e184f41-image.png) - -## 代码 - -```python -class Solution: - def grayCode(self, n: int) -> List[int]: - gray = [] - binary = 0 - while binary < (1 << n): - gray.append(binary ^ binary >> 1) - binary += 1 - return gray -``` diff --git "a/Solutions/0090. \345\255\220\351\233\206 II.md" "b/Solutions/0090. \345\255\220\351\233\206 II.md" deleted file mode 100644 index 5bbbabed..00000000 --- "a/Solutions/0090. \345\255\220\351\233\206 II.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [0090. 子集 II](https://leetcode.cn/problems/subsets-ii/) - -- 标签:位运算、数组、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`,其中可能包含重复元素。 - -**要求**:返回该数组所有可能的子集(幂集)。 - -**说明**: - -- 解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。 -- $1 \le nums.length \le 10$。 -- $-10 \le nums[i] \le 10$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,2] -输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -数组的每个元素都有两个选择:选与不选。 - -我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 - -因为数组中可能包含重复元素,所以我们可以先将数组排序,然后在回溯时,判断当前元素是否和上一个元素相同,如果相同,则直接跳过,从而去除重复元素。 - -回溯算法解决这道题的步骤如下: - -- 先对数组 `nums` 进行排序。 -- 从第 `0` 个位置开始,调用 `backtrack` 方法进行深度优先搜索。 -- 将当前子集数组 `sub_set` 添加到答案数组 `sub_sets` 中。 -- 然后从当前位置开始,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: - - 如果当前元素与上一个元素相同,则跳过当前生成的子集。 - - 将可选元素添加到当前子集数组 `sub_set` 中。 - - 在选择该元素的情况下,继续递归考虑下一个元素。 - - 进行回溯,撤销选择该元素。即从当前子集数组 `sub_set` 中移除之前添加的元素。 - -### 思路 1:代码 - -```python -class Solution: - def backtrack(self, nums, index, res, path): - res.append(path[:]) - - for i in range(index, len(nums)): - if i > index and nums[i] == nums[i - 1]: - continue - path.append(nums[i]) - self.backtrack(nums, i + 1, res, path) - path.pop() - - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: - nums.sort() - res, path = [], [] - self.backtrack(nums, 0, res, path) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 -- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 - -### 思路 2:二进制枚举 - -对于一个元素个数为 `n` 的集合 `nums` 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 `1` 来表示选取该元素,用数字 `0` 来表示不选取该元素。 - -那么我们就可以用一个长度为 `n` 的二进制数来表示集合 `nums` 或者表示 `nums` 的子集。其中二进制的每一位数都对应了集合中某一个元素的选取状态。对于集合中第 `i` 个元素(`i` 从 `0` 开始编号)来说,二进制对应位置上的 `1` 代表该元素被选取,`0` 代表该元素未被选取。 - -举个例子来说明一下,比如长度为 `5` 的集合 `nums = {5, 4, 3, 2, 1}`,我们可以用一个长度为 `5` 的二进制数来表示该集合。 - -比如二进制数 `11111` 就表示选取集合的第 `0` 位、第 `1` 位、第 `2` 位、第 `3` 位、第 `4` 位元素,也就是集合 `{5, 4, 3, 2, 1}` ,即集合 `nums` 本身。如下表所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :--: | :--: | :--: | :--: | :--: | -| 二进制数对应位数 | 1 | 1 | 1 | 1 | 1 | -| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | - -再比如二进制数 `10101` 就表示选取集合的第 `0` 位、第 `2` 位、第 `5` 位元素,也就是集合 `{5, 3, 1}`。如下表所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :--: | :----: | :--: | :----: | :--: | -| 二进制数对应位数 | 1 | 0 | 1 | 0 | 1 | -| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | - -再比如二进制数 `01001` 就表示选取集合的第 `0` 位、第 `3` 位元素,也就是集合 `{5, 2}`。如下标所示: - -| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | -| :------------------------- | :----: | :--: | :----: | :----: | :--: | -| 二进制数对应位数 | 0 | 1 | 0 | 0 | 1 | -| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | - -通过上面的例子我们可以得到启发:对于长度为 `5` 的集合 `nums` 来说,我们只需要从 `00000` ~ `11111` 枚举一次(对应十进制为 $0 \sim 2^4 - 1$)即可得到长度为 `5` 的集合 `S` 的所有子集。 - -我们将上面的例子拓展到长度为 `n` 的集合 `nums`。可以总结为: - -- 对于长度为 `5` 的集合 `nums` 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到所有的子集。 - -因为数组中可能包含重复元素,所以我们可以先对数组进行排序。然后在枚举过程中,如果发现当前元素和上一个元素相同,则直接跳过当前生层的子集,从而去除重复元素。 - -### 思路 2:代码 - -```python -class Solution: - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: - nums.sort() - n = len(nums) # n 为集合 nums 的元素个数 - sub_sets = [] # sub_sets 用于保存所有子集 - for i in range(1 << n): # 枚举 0 ~ 2^n - 1 - sub_set = [] # sub_set 用于保存当前子集 - flag = True # flag 用于判断重复元素 - for j in range(n): # 枚举第 i 位元素 - if i >> j & 1: # 如果第 i 为元素对应二进制位为 1,则表示选取该元素 - if j > 0 and (i >> (j - 1) & 1) == 0 and nums[j] == nums[j - 1]: - flag = False # 如果出现重复元素,则跳过当前生成的子集 - break - sub_set.append(nums[j]) # 将选取的元素加入到子集 sub_set 中 - if flag: - sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 - return sub_sets # 返回所有子集 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 -- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 diff --git "a/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" "b/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" deleted file mode 100644 index 18045cf5..00000000 --- "a/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0091. 解码方法](https://leetcode.cn/problems/decode-ways/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数字字符串 `s`。该字符串已经按照下面的映射关系进行了编码: - -- `A` 映射为 `1`。 -- `B` 映射为 `2`。 -- ... -- `Z` 映射为 `26`。 - -基于上述映射的方法,现在对字符串 `s` 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: - -- `"AAJF"`,将消息分组为 `(1 1 10 6)`。 -- `"KJF"`,将消息分组为 `(11 10 6)`。 - -**要求**:计算出共有多少种可能的解码方案。 - -**说明**: - -- $1 \le s.length \le 100$。 -- `s` 只包含数字,并且可能包含前导零。 -- 题目数据保证答案肯定是一个 `32` 位的整数。 - -**示例**: - -- 示例 1: - -```python -输入:s = "226" -输出:3 -解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。 - -###### 3. 状态转移方程 - -`dp[i]` 的来源有两种情况: - -1. 使用了一个字符,对 `s[i]` 进行翻译。只要 `s[i] != 0`,就可以被翻译为 `A` ~ `I` 的某个字母,此时方案数为 `dp[i] = dp[i - 1]`。 -2. 使用了两个字符,对 `s[i - 1]` 和 `s[i]` 进行翻译,只有 `s[i - 1] != 0`,且 `s[i - 1]` 和 `s[i]` 组成的整数必须小于等于 `26` 才能翻译,可以翻译为 `J` ~ `Z` 中的某字母,此时方案数为 `dp[i] = dp[i - 2]`。 - -这两种情况有可能是同时存在的,也有可能都不存在。在进行转移的时候,将符合要求的方案数累加起来即可。 - -状态转移方程可以写为: - -$dp[i] += \begin{cases} \begin{array} \ dp[i-1] & s[i] \ne 0 \cr dp[i-2] & s[i-1] \ne 0,s[i-1:i] \le 26 \end{array} \end{cases}$ - -###### 4. 初始条件 - -- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 `dp[0] = 1`。 -- 字符串只有一个字符时,需要考虑该字符是否为 `0`,不为 `0` 的话,`dp[1] = 1`,为 `0` 的话,`dp[0] = 0`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。则最终结果为 `dp[size]`,`size` 为字符串长度。 - - -### 思路 1:动态规划代码 - -```python -class Solution: - def numDecodings(self, s: str) -> int: - size = len(s) - dp = [0 for _ in range(size + 1)] - dp[0] = 1 - for i in range(1, size + 1): - if s[i - 1] != '0': - dp[i] += dp[i - 1] - if i > 1 and s[i - 2] != '0' and int(s[i - 2: i]) <= 26: - dp[i] += dp[i - 2] - return dp[size] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 diff --git "a/Solutions/0092. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" "b/Solutions/0092. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" deleted file mode 100644 index df8b71a0..00000000 --- "a/Solutions/0092. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" +++ /dev/null @@ -1,145 +0,0 @@ -# [0092. 反转链表 II ](https://leetcode.cn/problems/reverse-linked-list-ii/) - -- 标签:链表 -- 难度:中等 - -## 题目大意 - -**描述**:给定单链表的头指针 `head` 和两个整数 `left` 和 `right` ,其中 `left <= right`。 - -**要求**:反转从位置 `left` 到位置 `right` 的链表节点,返回反转后的链表 。 - -**说明**: - -- 链表中节点数目为 `n`。 -- $1 \le n \le 500$。 -- $-500 \le Node.val \le 500$。 -- $1 \le left \le right \le n$。 - -**示例**: - -- 示例 1: - -```python -输入:head = [1,2,3,4,5], left = 2, right = 4 -输出:[1,4,3,2,5] -``` - -## 解题思路 - -在「[0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)」中我们可以通过迭代、递归两种方法将整个链表反转。这道题而这道题要求对链表的部分区间进行反转。我们同样可以通过迭代、递归两种方法将链表的部分区间进行反转。 - -### 思路 1:迭代 - -我们可以先遍历到需要反转的链表区间的前一个节点,然后对需要反转的链表区间进行迭代反转。最后再返回头节点即可。 - -但是需要注意一点,如果需要反转的区间包含了链表的第一个节点,那么我们可以事先创建一个哑节点作为链表初始位置开始遍历,这样就能避免找不到需要反转的链表区间的前一个节点。 - -这道题的具体解题步骤如下: - -1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。使用 `index` 记录当前元素的序号。 -2. 我们使用一个指针 `reverse_start`,初始赋值为 `dummy_head`。然后向右逐步移动到需要反转的区间的前一个节点。 -3. 然后再使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置,即 `pre` 指向需要反转节点的前一个节点,`cur` 指向需要反转的节点。初始时,`pre` 指向 `reverse_start`,`cur` 指向 `pre.next`。 -4. 当当前节点 `cur` 不为空,且 `index` 在反转区间内时,将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: - 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; - 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; - 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; - 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 - 5. 然后令 `index` 加 `1`。 -5. 继续执行第 `4` 步中的 `1`、`2`、`3`、`4`、`5` 步。 -6. 最后等到 `cur` 遍历到链表末尾(即 `cur == None`)或者遍历到需要反转区间的末尾时(即 `index > right`) 时,将反转区间的头尾节点分别与之前保存的需要反转的区间的前一个节点 `reverse_start` 相连,即 `reverse_start.next.next = cur`,`reverse_start.next = pre`。 -7. 最后返回新的头节点 `dummy_head.next`。 - -### 思路 1:代码 - -```python -# Definition for singly-linked list. -# class ListNode: -# def __init__(self, val=0, next=None): -# self.val = val -# self.next = next -class Solution: - def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode: - index = 1 - dummy_head = ListNode(0) - dummy_head.next = head - pre = dummy_head - - reverse_start = dummy_head - while reverse_start.next and index < left: - reverse_start = reverse_start.next - index += 1 - - pre = reverse_start - cur = pre.next - while cur and index <= right: - next = cur.next - cur.next = pre - pre = cur - cur = next - index += 1 - - reverse_start.next.next = cur - reverse_start.next = pre - - return dummy_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是链表节点个数。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:递归算法 - -#### 1. 翻转链表前 n 个节点 - -1. 当 `left == 1` 时,无论 `right` 等于多少,实际上都是将当前链表到 `right` 部分进行翻转,也就是将前 `right` 个节点进行翻转。 - -2. 我们可以先定义一个递归函数 `reverseN(self, head, n)`,含义为:将链表前第 $n$ 个节点位置进行翻转。 - 1. 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表的的前 $n - 1$ 个位置进行反转,并返回该链表的新头节点 `new_head`。 - 2. 然后改变 `head`(原先头节点)和 `new_head`(新头节点)之间的指向关系,即将 `head` 指向的节点作为 `head` 下一个节点的下一个节点。 - 3. 先保存 `head.next` 的 `next` 指针,也就是新链表前 $n$ 个节点的尾指针,即 `last = head.next.next`。 - 4. 将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 - 5. 然后让当前节点 `head` 的 `next` 指针指向 `last`,则完成了前 $n - 1$ 个位置的翻转。 - -3. 递归终止条件:当 `n == 1` 时,相当于翻转第一个节点,直接返回 `head` 即可。 - -4. #### 翻转链表 `[left, right]` 上的节点。 - -接下来我们来翻转区间上的节点。 - -1. 定义递归函数 `reverseBetween(self, head, left, right)` 为 -2. - -### 思路 2:代码 - -```python -class Solution: - def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]: - if left == 1: - return self.reverseN(head, right) - - head.next = self.reverseBetween(head.next, left - 1, right - 1) - return head - - def reverseN(self, head, n): - if n == 1: - return head - last = self.reverseN(head.next, n - 1) - next = head.next.next - head.next.next = head - head.next = next - return last -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。最多需要 $n$ 层栈空间。 - -## 参考资料 - -- 【题解】[动画图解:翻转链表的指定区间 - 反转链表 II - 力扣](https://leetcode.cn/problems/reverse-linked-list-ii/solution/dong-hua-tu-jie-fan-zhuan-lian-biao-de-z-n4px/) -- 【题解】[【宫水三叶】一个能应用所有「链表」题里的「哨兵」技巧 - 反转链表 II - 力扣](https://leetcode.cn/problems/reverse-linked-list-ii/solution/yi-ge-neng-ying-yong-suo-you-lian-biao-t-vjx6/) - diff --git "a/Solutions/0093. \345\244\215\345\216\237 IP \345\234\260\345\235\200.md" "b/Solutions/0093. \345\244\215\345\216\237 IP \345\234\260\345\235\200.md" deleted file mode 100644 index c085a047..00000000 --- "a/Solutions/0093. \345\244\215\345\216\237 IP \345\234\260\345\235\200.md" +++ /dev/null @@ -1,125 +0,0 @@ -# [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) - -- 标签:字符串、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含数字的字符串 `s`,用来表示一个 IP 地址 - -**要求**:返回所有由 `s` 构成的有效 IP 地址,这些地址可以通过在 `s` 中插入 `'.'` 来形成。不能重新排序或删除 `s` 中的任何数字。可以按任何顺序返回答案。 - -**说明**: - -- **有效 IP 地址**:正好由四个整数(每个整数由 $0 \sim 255$ 的数构成,且不能含有前导 0),整数之间用 `.` 分割。 -- $1 \le s.length \le 20$。 -- `s` 仅由数字组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "25525511135" -输出:["255.255.11.135","255.255.111.35"] -``` - -- 示例 2: - -```python -输入:s = "0000" -输出:["0.0.0.0"] -``` - -## 解题思路 - -### 思路 1:回溯算法 - -一个有效 IP 地址由四个整数构成,中间用 $3$ 个点隔开。现在给定的是无分隔的整数字符串,我们可以通过在整数字符串中间的不同位置插入 $3$ 个点来生成不同的 IP 地址。这个过程可以通过回溯算法来生成。 - -根据回溯算法三步走,写出对应的回溯算法。 - -1. **明确所有选择**:全排列中每个位置上的元素都可以从剩余可选元素中选出,对此画出决策树,如下图所示。 - -2. **明确终止条件**: - - - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 - -3. **将决策树和终止条件翻译成代码:** - - 1. 定义回溯函数: - - - `backtracking(index):` 函数的传入参数是 `index`(剩余字符开始位置),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 - - `backtracking(index):` 函数代表的含义是:递归从 `index` 位置开始,从剩下字符中,选择当前子段的值。 - 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 - - 从当前正在考虑的字符,到字符串结束为止,枚举出所有可作为当前子段值的字符。对于每一个子段值: - - 约束条件:只能从 `index` 位置开始选择,并且要符合规则要求。 - - 选择元素:将其添加到当前子集数组 `path` 中。 - - 递归搜索:在选择该子段值的情况下,继续递归从剩下字符中,选择下一个子段值。 - - 撤销选择:将该子段值从当前结果数组 `path` 中移除。 - - ```python - for i in range(index, len(s)): # 枚举可选元素列表 - sub = s[index: i + 1] - # 如果当前值不在 0 ~ 255 之间,直接跳过 - if int(sub) > 255: - continue - # 如果当前值为 0,但不是单个 0("00..."),直接跳过 - if int(sub) == 0 and i != index: - continue - # 如果当前值大于 0,但是以 0 开头("0XX..."),直接跳过 - if int(sub) > 0 and s[index] == '0': - continue - - path.append(sub) # 选择元素 - backtracking(i + 1) # 递归搜索 - path.pop() # 撤销选择 - ``` - - 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 - - 当遍历到决策树的叶子节点时,就终止了。也就是存放当前结果的数组 `path` 的长度等于 $4$,并且剩余字符开始位置为字符串结束位置(即 `len(path) == 4 and index == len(s)`)时,递归停止。 - - 如果回溯过程中,切割次数大于 4(即 `len(path) > 4`),递归停止,直接返回。 - -### 思路 1:代码 - -```python -class Solution: - def restoreIpAddresses(self, s: str) -> List[str]: - res = [] - path = [] - def backtracking(index): - # 如果切割次数大于 4,直接返回 - if len(path) > 4: - return - - # 切割完成,将当前结果加入答案结果数组中 - if len(path) == 4 and index == len(s): - res.append('.'.join(path)) - return - - for i in range(index, len(s)): - sub = s[index: i + 1] - # 如果当前值不在 0 ~ 255 之间,直接跳过 - if int(sub) > 255: - continue - # 如果当前值为 0,但不是单个 0("00..."),直接跳过 - if int(sub) == 0 and i != index: - continue - # 如果当前值大于 0,但是以 0 开头("0XX..."),直接跳过 - if int(sub) > 0 and s[index] == '0': - continue - - path.append(sub) - backtracking(i + 1) - path.pop() - - - backtracking(0) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(3^4 \times |s|)$,其中 $|s|$ 是字符串 `s` 的长度。由于 IP 地址的每一子段位数不会超过 $3$,因此在递归时,我们最多只会深入到下一层中的 $3$ 种情况。而 IP 地址由 $4$ 个子段构成,所以递归的最大层数为 $4$ 层,则递归的时间复杂度为 $O(3^4)$。而每次将有效的 IP 地址添加到答案数组的时间复杂度为 $|s|$,所以总的时间复杂度为 $3^4 \times |s|$。 -- **空间复杂度**:$O(|s|)$,只记录除了用来存储答案数组之外的空间复杂度。 - diff --git "a/Solutions/0094. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0094. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index e16ae1cc..00000000 --- "a/Solutions/0094. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,108 +0,0 @@ -# [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) - -- 标签:栈、树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:返回该二叉树的中序遍历结果。 - -**说明**: - -- 树中节点数目在范围 $[0, 100]$ 内。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) - -```python -输入:root = [1,null,2,3] -输出:[1,3,2] -``` - -- 示例 2: - -```python -输入:root = [] -输出:[] -``` - -## 解题思路 - -### 思路 1:递归遍历 - -二叉树的前序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先访问根节点。 -3. 然后递归遍历左子树。 -4. 最后递归遍历右子树。 - -### 思路 1:代码 - -```python -class Solution: - def inorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - def inorder(root): - if not root: - return - inorder(root.left) - res.append(root.val) - inorder(root.right) - - inorder(root) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:模拟栈迭代遍历 - -二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 `stack` 来模拟递归的过程。 - -前序遍历的顺序为:根节点 - 左子树 - 右子树,而根据栈的「先入后出」特点,所以入栈的顺序应该为:先放入右子树,再放入左子树。这样可以保证最终遍历顺序为前序遍历顺序。 - -二叉树的前序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个栈,将根节点入栈。 -3. 当栈不为空时: - 1. 弹出栈顶元素 `node`,并访问该元素。 - 2. 如果 `node` 的右子树不为空,则将 `node` 的右子树入栈。 - 3. 如果 `node` 的左子树不为空,则将 `node` 的左子树入栈。 - -### 思路 2:代码 - -```python -class Solution: - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - if not root: # 二叉树为空直接返回 - return [] - - res = [] - stack = [] - - while root or stack: # 根节点或栈不为空 - while root: - stack.append(root) # 将当前树的根节点入栈 - root = root.left # 找到最左侧节点 - - node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 - res.append(node.val) # 访问该节点 - root = node.right # 尝试访问该节点的右子树 - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" "b/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" deleted file mode 100644 index eb13d9d2..00000000 --- "a/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0095. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) - -- 标签:树、二叉搜索树、动态规划、回溯、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`,请返回以 `1` 到 `n` 为节点构成的「二叉搜索树」,可以按任意顺序返回答案。 - -## 解题思路 - -如果根节点为 `i`,则左子树的节点为 `(1, 2, ..., i - 1)`,右子树的节点为 `(i + 1, i + 2, ..., n)`。可以递归的构建二叉树。 - -定义递归函数 `generateTrees(start, end)`,表示生成 `[left, ..., right]` 构成的所有可能的二叉搜索树。 - -- 如果 `start > end`,返回 [None]。 -- 初始化存放所有可能二叉搜索树的数组。 -- 遍历 `[left, ..., right]` 的每一个节点 `i`,将其作为根节点。 - - 递归构建左右子树。 - - 将所有符合要求的左右子树组合起来,将其加入到存放二叉搜索树的数组中。 -- 返回存放二叉搜索树的数组。 - -## 代码 - -```python -class Solution: - def generateTrees(self, n: int) -> List[TreeNode]: - if n == 0: - return [] - - def generateTrees(start, end): - if start > end: - return [None] - trees = [] - for i in range(start, end+1): - left_trees = generateTrees(start, i - 1) - right_trees = generateTrees(i + 1, end) - for left_tree in left_trees: - for right_tree in right_trees: - curr_tree = TreeNode(i) - curr_tree.left = left_tree - curr_tree.right = right_tree - trees.append(curr_tree) - return trees - return generateTrees(1, n) -``` - diff --git "a/Solutions/0096. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0096. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index f063b8bc..00000000 --- "a/Solutions/0096. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [0096. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) - -- 标签:树、二叉搜索树、数学、动态规划、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:求以 $1$ 到 $n$ 为节点构成的「二叉搜索树」有多少种? - -**说明**: - -- $1 \le n \le 19$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/01/18/uniquebstn3.jpg) - -```python -输入:n = 3 -输出:5 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:1 -``` - -## 解题思路 - -### 思路 1:动态规划 - -一棵搜索二叉树的左、右子树,要么也是搜索二叉树,要么就是空树。 - -如果定义 $f[i]$ 表示以 $i$ 为根的二叉搜索树个数,定义 $g(i)$ 表示 $i$ 个节点可以构成的二叉搜索树个数,则有: - -- $g(i) = f(1) + f(2) + f(3) + … + f(i)$。 - -其中当 $i$ 为根节点时,则用 $(1, 2, …, i - 1)$ 共 $i - 1$ 个节点去递归构建左子搜索二叉树,用 $(i + 1, i + 2, …, n)$ 共 $n - i$ 个节点去递归构建右子搜索树。则有: - -- $f(i) = g(i - 1) \times g(n - i)$。 - -综合上面两个式子 $\begin{cases} g(i) = f(1) + f(2) + f(3) + … + f(i) \cr f(i) = g(i - 1) \times g(n - i) \end{cases}$ 可得出: - -- $g(n) = g(0) \times g(n - 1) + g(1) \times g(n - 2) + … + g(n - 1) \times g(0)$。 - -将 $n$ 换为 $i$,可变为: - -- $g(i) = g(0) \times g(i - 1) + g(1) \times g(i - 2) + … + g(i - 1) \times g(0)$。 - -再转换一下,可变为: - -- $g(i) = \sum_{1 \le j \le i} \lbrace g(j - 1) \times g(i - j) \rbrace$。 - -则我们可以通过动态规划的方法,递推求解 $g(i)$,并求解出 $g(n)$。具体步骤如下: - -###### 1. 划分阶段 - -按照根节点的编号进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为: $i$ 个节点可以构成的二叉搜索树个数。 - -###### 3. 状态转移方程 - -$dp[i] = \sum_{1 \le j \le i} \lbrace dp[j - 1] \times dp[i - j] \rbrace$ - -###### 4. 初始条件 - -- $0$ 个节点可以构成的二叉搜索树个数为 $1$(空树),即 $dp[0] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为: $i$ 个节点可以构成的二叉搜索树个数。。 所以最终结果为 $dp[n]$。 - -### 思路 1:代码 - -```python -class Solution: - def numTrees(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - dp[0] = 1 - for i in range(1, n + 1): - for j in range(1, i + 1): - dp[i] += dp[j - 1] * dp[i - j] - return dp[n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[画解算法:96. 不同的二叉搜索树 - 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/solution/hua-jie-suan-fa-96-bu-tong-de-er-cha-sou-suo-shu-b/) - diff --git "a/Solutions/0098. \351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0098. \351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index 3a6a6a7c..00000000 --- "a/Solutions/0098. \351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:判断其是否是一个有效的二叉搜索树。 - -**说明**: - -- **二叉搜索树特征**: - - 节点的左子树只包含小于当前节点的数。 - - 节点的右子树只包含大于当前节点的数。 - - 所有左子树和右子树自身必须也是二叉搜索树。 -- 树中节点数目范围在$[1, 10^4]$ 内。 -- $-2^{31} \le Node.val \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg) - -```python -输入:root = [2,1,3] -输出:true -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg) - -```python -输入:root = [5,1,4,null,null,3,6] -输出:false -解释:根节点的值是 5 ,但是右子节点的值是 4 。 -``` - -## 解题思路 - -### 思路 1:递归遍历 - -根据题意进行递归遍历即可。前序、中序、后序遍历都可以。 - -1. 以前序遍历为例,递归函数为:`preorderTraversal(root, min_v, max_v)`。 -2. 前序遍历时,先判断根节点的值是否在 `(min_v, max_v)` 之间。 - 1. 如果不在则直接返回 `False`。 - 2. 如果在区间内,则继续递归检测左右子树是否满足,都满足才是一棵二叉搜索树。 -3. 当递归遍历左子树的时候,要将上界 `max_v` 改为左子树的根节点值,因为左子树上所有节点的值均小于根节点的值。 -4. 当递归遍历右子树的时候,要将下界 `min_v` 改为右子树的根节点值,因为右子树上所有节点的值均大于根节点。 - -### 思路 1:代码 - -```python -class Solution: - def isValidBST(self, root: TreeNode) -> bool: - def preorderTraversal(root, min_v, max_v): - if root == None: - return True - if root.val >= max_v or root.val <= min_v: - return False - return preorderTraversal(root.left, min_v, root.val) and preorderTraversal(root.right, root.val, max_v) - - return preorderTraversal(root, float('-inf'), float('inf')) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0100. \347\233\270\345\220\214\347\232\204\346\240\221.md" "b/Solutions/0100. \347\233\270\345\220\214\347\232\204\346\240\221.md" deleted file mode 100644 index 6c783c32..00000000 --- "a/Solutions/0100. \347\233\270\345\220\214\347\232\204\346\240\221.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0100. 相同的树](https://leetcode.cn/problems/same-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定两个二叉树 p 和 q。判断这两棵树是否相同。 - -两棵树相同的定义: - -- 结构上相同; -- 节点具有相同的值 - -## 解题思路 - -先判断两棵树的根节点是否相同,在递归地判断左右子树是否相同。 - -## 代码 - -```python -class Solution: - def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: - if not p and not q: - return True - if not p or not q: - return False - if p.val != q.val: - return False - return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) -``` - diff --git "a/Solutions/0101. \345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0101. \345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 306de530..00000000 --- "a/Solutions/0101. \345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,84 +0,0 @@ -# [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:判断该二叉树是否是左右对称的。 - -**说明**: - -- 树中节点数目在范围 $[1, 1000]$ 内。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/19/symtree1.jpg) - -```python -输入:root = [1,2,2,3,4,4,3] -输出:true -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/02/19/symtree2.jpg) - -```python -输入:root = [1,2,2,null,3,null,3] -输出:false -``` - -## 解题思路 - -### 思路 1:递归遍历 - -如果一棵二叉树是对称的,那么其左子树和右子树的外侧节点的节点值应当是相等的,并且其左子树和右子树的内侧节点的节点值也应当是相等的。 - -那么我们可以通过递归方式,检查其左子树与右子树外侧节点和内测节点是否相等。即递归检查左子树的左子节点值与右子树的右子节点值是否相等(外侧节点值是否相等),递归检查左子树的右子节点值与右子树的左子节点值是否相等(内测节点值是否相等)。 - -具体步骤如下: - -1. 如果当前根节点为 `None`,则直接返回 `True`。 -2. 如果当前根节点不为 `None`,则调用 `check(left, right)` 方法递归检查其左右子树是否对称。 - 1. 如果左子树节点为 `None`,并且右子树节点也为 `None`,则直接返回 `True`。 - 2. 如果左子树节点为 `None`,并且右子树节点不为 `None`,则直接返回 `False`。 - 3. 如果左子树节点不为 `None`,并且右子树节点为 `None`,则直接返回 `False`。 - 4. 如果左子树节点值不等于右子树节点值,则直接返回 `False`。 - 5. 如果左子树节点不为 `None`,并且右子树节点不为 `None`,并且左子树节点值等于右子树节点值,则: - 1. 递归检测左右子树的外侧节点是否相等。 - 2. 递归检测左右子树的内测节点是否相等。 - 3. 如果左右子树的外侧节点、内测节点值相等,则返回 `True`。 - -### 思路 1:代码 - -```python -class Solution: - def isSymmetric(self, root: TreeNode) -> bool: - if root == None: - return True - return self.check(root.left, root.right) - - def check(self, left: TreeNode, right: TreeNode): - if left == None and right == None: - return True - elif left == None and right != None: - return False - elif left != None and right == None: - return False - elif left.val != right.val: - return False - - return self.check(left.left, right.right) and self.check(left.right, right.left) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index 1c00080d..00000000 --- "a/Solutions/0102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,75 +0,0 @@ -# [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:返回该二叉树按照「层序遍历」得到的节点值。 - -**说明**: - -- 返回结果为二维数组,每一层都要存为数组返回。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) - -```python -输入:root = [3,9,20,null,null,15,7] -输出:[[3],[9,20],[15,7]] -``` - -- 示例 2: - -```python -输入:root = [1] -输出:[[1] -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -广度优先搜索,需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 $i$ 层上所有元素。 - -具体步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 令根节点入队。 -3. 当队列不为空时,求出当前队列长度 $s_i$。 -4. 依次从队列中取出这 $s_i$ 个元素,并对这 $s_i$ 个元素依次进行访问。然后将其左右孩子节点入队,然后继续遍历下一层节点。 -5. 当队列为空时,结束遍历。 - -### 思路 1:代码 - -```python -class Solution: - def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - while queue: - level = [] - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - level.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(level) - return order -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0103. \344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0103. \344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index b2f79dc1..00000000 --- "a/Solutions/0103. \344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:返回其节点值的锯齿形层序遍历结果。 - -**说明**: - -- **锯齿形层序遍历**:从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) - -```python -输入:root = [3,9,20,null,null,15,7] -输出:[[3],[20,9],[15,7]] -``` - -- 示例 2: - -```python -输入:root = [1] -输出:[[1]] -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -在二叉树的层序遍历的基础上需要增加一些变化。 - -普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 `i` 层上所有元素。 - -新增一个变量 `odd`,用于判断当前层数是奇数层,还是偶数层。从而判断元素遍历方向。 - -存储每层元素的 `level` 列表改用双端队列,如果是奇数层,则从末尾添加元素。如果是偶数层,则从头部添加元素。 - -具体步骤如下: - -1. 使用列表 `order` 存放锯齿形层序遍历结果,使用整数 `odd` 变量用于判断奇偶层,使用双端队列 `level` 存放每层元素,使用列表 `queue` 用于进行广度优先搜索。 -2. 将根节点放入入队列中,即 `queue = [root]`。 -3. 当队列 `queue` 不为空时,求出当前队列长度 $s_i$,并判断当前层数的奇偶性。 -4. 依次从队列中取出这 $s_i$ 个元素。 - 1. 如果当前层为奇数层,如果是奇数层,则从 `level` 末尾添加元素。 - 2. 如果当前层是偶数层,则从 `level` 头部添加元素。 - 3. 然后将当前元素的左右子节点加入队列 `queue` 中,然后继续迭代。 -5. 将存储当前层元素的 `level` 存入答案列表 `order` 中。 -6. 当队列为空时,结束。返回锯齿形层序遍历结果 `order`。 - -### 思路 1:代码 - -```python -import collections -class Solution: - def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - odd = True - while queue: - level = collections.deque() - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - if odd: - level.append(curr.val) - else: - level.appendleft(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(list(level)) - odd = not odd - return order -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0104. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.md" "b/Solutions/0104. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.md" deleted file mode 100644 index 11d67699..00000000 --- "a/Solutions/0104. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:找出该二叉树的最大深度。 - -**说明**: - -- **二叉树的深度**:根节点到最远叶子节点的最长路径上的节点数。 -- **叶子节点**:没有子节点的节点。 - -**示例**: - -- 示例 1: - -```python -输入:[3,9,20,null,null,15,7] -对应二叉树 - 3 - / \ - 9 20 - / \ - 15 7 -输出:3 -解释:该二叉树的最大深度为 3 -``` - -## 解题思路 - -### 思路 1: 递归算法 - -根据递归三步走策略,写出对应的递归代码。 - -1. 写出递推公式:`当前二叉树的最大深度 = max(当前二叉树左子树的最大深度, 当前二叉树右子树的最大深度) + 1`。 - - 即:先得到左右子树的高度,在计算当前节点的高度。 -2. 明确终止条件:当前二叉树为空。 -3. 翻译为递归代码: - 1. 定义递归函数:`maxDepth(self, root)` 表示输入参数为二叉树的根节点 `root`,返回结果为该二叉树的最大深度。 - 2. 书写递归主体:`return max(self.maxDepth(root.left) + self.maxDepth(root.right))`。 - 3. 明确递归终止条件:`if not root: return 0` - -### 思路 1:代码 - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - if not root: - return 0 - - return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index cebb9c28..00000000 --- "a/Solutions/0105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,72 +0,0 @@ -# [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) - -- 标签:树、数组、哈希表、分治、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一棵二叉树的前序遍历结果 `preorder` 和中序遍历结果 `inorder`。 - -**要求**:构造出该二叉树并返回其根节点。 - -**说明**: - -- $1 \le preorder.length \le 3000$。 -- $inorder.length == preorder.length$。 -- $-3000 \le preorder[i], inorder[i] \le 3000$。 -- `preorder` 和 `inorder` 均无重复元素。 -- `inorder` 均出现在 `preorder`。 -- `preorder` 保证为二叉树的前序遍历序列。 -- `inorder` 保证为二叉树的中序遍历序列。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) - -```python -输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] -输出: [3,9,20,null,null,15,7] -``` - -- 示例 2: - -```python -输入: preorder = [-1], inorder = [-1] -输出: [-1] -``` - -## 解题思路 - -### 思路 1:递归遍历 - -前序遍历的顺序是:根 -> 左 -> 右。中序遍历的顺序是:左 -> 根 -> 右。根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: - -1. 从前序遍历顺序中当前根节点的位置在 `postorder[0]`。 -2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 -3. 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 思路 1:代码 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def createTree(preorder, inorder, n): - if n == 0: - return None - k = 0 - while preorder[0] != inorder[k]: - k += 1 - node = TreeNode(inorder[k]) - node.left = createTree(preorder[1: k+1], inorder[0: k], k) - node.right = createTree(preorder[k+1:], inorder[k+1:], n-k-1) - return node - return createTree(preorder, inorder, len(inorder)) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index b1f69122..00000000 --- "a/Solutions/0106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) - -- 标签:树、数组、哈希表、分治、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一棵二叉树的中序遍历结果 `inorder` 和后序遍历结果 `postorder`。 - -**要求**:构造出该二叉树并返回其根节点。 - -**说明**: - -- $1 \le inorder.length \le 3000$。 -- $postorder.length == inorder.length$。 -- $-3000 \le inorder[i], postorder[i] \le 3000$。 -- `inorder` 和 `postorder` 都由不同的值组成。 -- `postorder` 中每一个值都在 `inorder` 中。 -- `inorder` 保证是二叉树的中序遍历序列。 -- `postorder` 保证是二叉树的后序遍历序列。 -- `inorder` 保证为二叉树的中序遍历序列。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) - -```python -输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] -输出:[3,9,20,null,null,15,7] -``` - -- 示例 2: - -```python -输入:inorder = [-1], postorder = [-1] -输出:[-1] -``` - -## 解题思路 - -### 思路 1:递归 - -中序遍历的顺序是:左 -> 根 -> 右。后序遍历的顺序是:左 -> 右 -> 根。根据后序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: - -1. 从后序遍历顺序中当前根节点的位置在 `postorder[n - 1]`。 -2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 -3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 思路 1:代码 - -```python -class Solution: - def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: - def createTree(inorder, postorder, n): - if n == 0: - return None - k = 0 - while postorder[n-1] != inorder[k]: - k += 1 - node = TreeNode(inorder[k]) - node.right = createTree(inorder[k+1: n], postorder[k: n-1], n-k-1) - node.left = createTree(inorder[0: k], postorder[0: k], k) - return node - return createTree(inorder, postorder, len(postorder)) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0107. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206 II.md" "b/Solutions/0107. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206 II.md" deleted file mode 100644 index 6a0e7af9..00000000 --- "a/Solutions/0107. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206 II.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树,返回其「自底向上」,且按「层序遍历」得到的节点值。 - -## 解题思路 - -先得到层次遍历的节点顺序,再将其进行反转返回即可。 - -其中层次遍历用到了广度优先搜索,不过需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 - -具体步骤如下: - -- 根节点入队 -- 当队列不为空时,求出当前队列长度 $s_i$ - - 依次从队列中取出这 $s_i$ 个元素,将其左右子节点入队,然后继续迭代 -- 当队列为空时,结束 - -## 代码 - -```python -class Solution: - def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - while queue: - level = [] - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - level.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(level) - return order[::-1] -``` - diff --git "a/Solutions/0108. \345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0108. \345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index 9445bfda..00000000 --- "a/Solutions/0108. \345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [0108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) - -- 标签:树、二叉搜索树、数组、分治、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个升序的有序数组 `nums`。 - -**要求**:将其转换为一棵高度平衡的二叉搜索树。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $-10^4 \le nums[i] \le 10^4$。 -- `nums` 按严格递增顺序排列。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2021/02/18/btree1.jpg) - -```python -输入:nums = [-10,-3,0,5,9] -输出:[0,-3,9,-10,null,5] -解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案 -``` - -- 示例 2: - -![img](https://assets.leetcode.com/uploads/2021/02/18/btree.jpg) - -```python -输入:nums = [1,3] -输出:[3,1] -解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。 -``` - -## 解题思路 - -### 思路 1:递归遍历 - -直观上,如果把数组的中间元素当做根,那么数组左侧元素都小于根节点,右侧元素都大于根节点,且左右两侧元素个数相同,或最多相差 $1$ 个。那么构建的树高度差也不会超过 $1$。 - -所以猜想出:如果左右子树越平均,树就越平衡。这样我们就可以每次取中间元素作为当前的根节点,两侧的元素作为左右子树递归建树,左侧区间 $[L, mid - 1]$ 作为左子树,右侧区间 $[mid + 1, R]$ 作为右子树。 - -### 思路 1:代码 - -```python -class Solution: - def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]: - def build(left, right): - if left > right: - return - mid = left + (right - left) // 2 - root = TreeNode(nums[mid]) - root.left = build(left, mid - 1) - root.right = build(mid + 1, right) - return root - return build(0, len(nums) - 1) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是数组的长度。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0110. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0110. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index d71d3306..00000000 --- "a/Solutions/0110. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:判断该二叉树是否是高度平衡的二叉树。 - -**说明**: - -- **高度平衡二叉树**:二叉树中每个节点的左右两个子树的高度差的绝对值不超过 $1$。 -- 树中的节点数在范围 $[0, 5000]$ 内。 -- $-10^4 \le Node.val \le 10^4$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg) - -```python -输入:root = [3,9,20,null,null,15,7] -输出:True -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg) - -```python -输入:root = [1,2,2,3,3,null,null,4,4] -输出:False -``` - -## 解题思路 - -### 思路 1:递归遍历 - -1. 先递归遍历左右子树,判断左右子树是否平衡,再判断以当前节点为根节点的左右子树是否平衡。 -2. 如果遍历的子树是平衡的,则返回它的高度,否则返回 -1。 -3. 只要出现不平衡的子树,则该二叉树一定不是平衡二叉树。 - -### 思路 1:代码 - -```python -class Solution: - def isBalanced(self, root: TreeNode) -> bool: - def height(root: TreeNode) -> int: - if root == None: - return False - leftHeight = height(root.left) - rightHeight = height(root.right) - if leftHeight == -1 or rightHeight == -1 or abs(leftHeight-rightHeight) > 1: - return -1 - else: - return max(leftHeight, rightHeight)+1 - return height(root) >= 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0111. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.md" "b/Solutions/0111. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.md" deleted file mode 100644 index 1f31ee91..00000000 --- "a/Solutions/0111. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索 -- 难度:简单 - -## 题目大意 - -给定一个二叉树,找出其最小深度。 - -- 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 - -## 解题思路 - -深度优先搜索递归遍历左右子树,记录最小深度。 - -对于每一个非叶子节点,计算其左右子树的最小叶子节点深度,将较小的深度+1 即为当前节点的最小叶子节点深度。 - -## 代码 - -```python -class Solution: - def minDepth(self, root: TreeNode) -> int: - # 遍历到空节点,直接返回 0 - if root == None: - return 0 - - # 左右子树为空,说明为叶子节点 返回 1 - if root.left == None and root.right == None: - return 1 - - leftHeight = self.minDepth(root.left) - rightHeight = self.minDepth(root.right) - - # 当前节点的左右子树的最小叶子节点深度 - min_depth = 0xffffff - if root.left: - min_depth = min(leftHeight, min_depth) - if root.right: - min_depth = min(rightHeight, min_depth) - - # 当前节点的最小叶子节点深度 - return min_depth + 1 -``` - diff --git "a/Solutions/0112. \350\267\257\345\276\204\346\200\273\345\222\214.md" "b/Solutions/0112. \350\267\257\345\276\204\346\200\273\345\222\214.md" deleted file mode 100644 index 28d697fc..00000000 --- "a/Solutions/0112. \350\267\257\345\276\204\346\200\273\345\222\214.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0112. 路径总和](https://leetcode.cn/problems/path-sum/) - -- 标签:树、深度优先搜索 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root` 和一个值 `targetSum`。 - -**要求**:判断该树中是否存在从根节点到叶子节点的路径,使得这条路径上所有节点值相加等于 `targetSum`。如果存在,返回 `True`;否则,返回 `False`。 - -**说明**: - -- 树中节点的数目在范围 $[0, 5000]$ 内。 -- $-1000 \le Node.val \le 1000$。 -- $-1000 \le targetSum \le 1000$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2021/01/18/pathsum1.jpg) - -```python -输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 -输出:true -解释:等于目标和的根节点到叶节点路径如上图所示。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/01/18/pathsum2.jpg) - -```python -输入:root = [1,2,3], targetSum = 5 -输出:false -解释:树中存在两条根节点到叶子节点的路径: -(1 --> 2): 和为 3 -(1 --> 3): 和为 4 -不存在 sum = 5 的根节点到叶子节点的路径。 -``` - -## 解题思路 - -### 思路 1:递归遍历 - -1. 定义一个递归函数,递归函数传入当前根节点 `root`,目标节点和 `targetSum`,以及新增变量 `currSum`(表示为从根节点到当前节点的路径上所有节点值之和)。 -2. 递归遍历左右子树,同时更新维护 `currSum` 值。 -3. 如果当前节点为叶子节点时,判断 `currSum` 是否与 `targetSum` 相等。 - 1. 如果 `currSum` 与 `targetSum` 相等,则返回 `True`。 - 2. 如果 `currSum` 不与 `targetSum` 相等,则返回 `False`。 -4. 如果当前节点不为叶子节点,则继续递归遍历左右子树。 - -### 思路 1:代码 - -```python -class Solution: - def hasPathSum(self, root: TreeNode, targetSum: int) -> bool: - return self.sum(root, targetSum, 0) - - def sum(self, root: TreeNode, targetSum: int, curSum:int) -> bool: - if root == None: - return False - curSum += root.val - if root.left == None and root.right == None: - return curSum == targetSum - else: - return self.sum(root.left, targetSum, curSum) or self.sum(root.right, targetSum, curSum) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0113. \350\267\257\345\276\204\346\200\273\345\222\214 II.md" "b/Solutions/0113. \350\267\257\345\276\204\346\200\273\345\222\214 II.md" deleted file mode 100644 index 0d2b5704..00000000 --- "a/Solutions/0113. \350\267\257\345\276\204\346\200\273\345\222\214 II.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) - -- 标签:树、深度优先搜索、回溯、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一棵二叉树的根节点 `root` 和一个整数目标 `targetSum`。 - -**要求**:找出「所有从根节点到叶子节点路径总和」等于给定目标和 `targetSum` 的路径。 - -**说明**: - -- **叶子节点**:指没有子节点的节点。 -- 树中节点总数在范围 $[0, 5000]$ 内。 -- $-1000 \le Node.val \le 1000$。 -- $-1000 \le targetSum \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/01/18/pathsumii1.jpg) - -```python -输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 -输出:[[5,4,11,2],[5,8,4,5]] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/01/18/pathsum2.jpg) - -```python -输入:root = [1,2,3], targetSum = 5 -输出:[] -``` - -## 解题思路 - -### 思路 1:回溯 - -在回溯的同时,记录下当前路径。同时维护 `targetSum`,每遍历到一个节点,就减去该节点值。如果遇到叶子节点,并且 `targetSum == 0` 时,将当前路径加入答案数组中。然后递归遍历左右子树,并回退当前节点,继续遍历。 - -具体步骤如下: - -1. 使用列表 `res` 存储所有路径,使用列表 `path` 存储当前路径。 -2. 如果根节点为空,则直接返回。 -3. 将当前节点值添加到当前路径 `path` 中。 -4. `targetSum` 减去当前节点值。 -5. 如果遇到叶子节点,并且 `targetSum == 0` 时,将当前路径加入答案数组中。 -6. 递归遍历左子树。 -7. 递归遍历右子树。 -8. 回退当前节点,继续递归遍历。 - -### 思路 1:代码 - -```python -class Solution: - def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]: - res = [] - path = [] - - def dfs(root: TreeNode, targetSum: int): - if not root: - return - path.append(root.val) - targetSum -= root.val - if not root.left and not root.right and targetSum == 0: - res.append(path[:]) - dfs(root.left, targetSum) - dfs(root.right, targetSum) - path.pop() - - dfs(root, targetSum) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index 9d94a0a1..00000000 --- "a/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) - -- 标签:字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定两个字符串 `s` 和 `t`。 - -**要求**:计算在 `s` 的子序列中 `t` 出现的个数。 - -**说明**: - -- **字符串的子序列**:通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,`"ACE"` 是 `"ABCDE"` 的一个子序列,而 `"AEC"` 不是)。 -- $0 \le s.length, t.length \le 1000$。 -- `s` 和 `t` 由英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "rabbbit", t = "rabbit" -输出:3 -解释:如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 -``` - -$\underline{rabb}b\underline{it}$ -$\underline{ra}b\underline{bbit}$ -$\underline{rab}b\underline{bit}$ - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照子序列的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。 - -###### 3. 状态转移方程 - -双重循环遍历字符串 `s` 和 `t`,则状态转移方程为: - -- 如果 `s[i - 1] == t[j - 1]`,则:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`。即 `dp[i][j]` 来源于两部分: - - 使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 2` 为结尾的 `t` 的个数,即 `dp[i - 1][j - 1]`。 - - 不使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数,即 `dp[i - 1][j]`。 -- 如果 `s[i - 1] != t[j - 1]`,那么肯定不能用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于 `dp[i - 1][j]`。 - -###### 4. 初始条件 - -- `dp[i][0]` 表示以 `i - 1` 为结尾的 `s` 子序列中出现空字符串的个数。把 `s` 中的元素全删除,出现空字符串的个数就是 `1`,则 `dp[i][0] = 1`。 -- `dp[0][j]` 表示空字符串中出现以 `j - 1` 结尾的 `t` 的个数,空字符串无论怎么变都不会变成 `t`,则 `dp[0][j] = 0` -- `dp[0][0]` 表示空字符串中出现空字符串的个数,这个应该是 `1`,即 `dp[0][0] = 1`。 - -##### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。则最终结果为 `dp[size_s][size_t]`,将其返回即可。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def numDistinct(self, s: str, t: str) -> int: - size_s = len(s) - size_t = len(t) - dp = [[0 for _ in range(size_t + 1)] for _ in range(size_s + 1)] - for i in range(size_s): - dp[i][0] = 1 - for i in range(1, size_s + 1): - for j in range(1, size_t + 1): - if s[i - 1] == t[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] - else: - dp[i][j] = dp[i - 1][j] - return dp[size_s][size_t] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 diff --git "a/Solutions/0116. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.md" "b/Solutions/0116. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.md" deleted file mode 100644 index d025c511..00000000 --- "a/Solutions/0116. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) - -- 标签:树、深度优先搜索、广度优先搜索、链表、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个完美二叉树,所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树结构如下: - -```python -struct Node { - int val; - Node *left; - Node *right; - Node *next; -} -``` - -要求填充每个 next 指针,是的这个指针指向下一个右侧节点。如果找不到下一个右侧节点,则将 next 置为 None。 -示例: - -![](https://assets.leetcode.com/uploads/2019/02/14/116_sample.png) - -## 解题思路 - -层次遍历。在层次遍历的过程中,依次取出每一层的节点,并进行连接。然后再扩展下一层节点。 - -## 代码 - -```python -import collections - -class Solution: - def connect(self, root: 'Node') -> 'Node': - if not root: - return root - queue = collections.deque() - queue.append(root) - while queue: - size = len(queue) - for i in range(size): - node = queue.popleft() - if i < size - 1: - node.next = queue[0] - - if node.left: - queue.append(node.left) - if node.right: - queue.append(node.right) - return root -``` - diff --git "a/Solutions/0117. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210 II.md" "b/Solutions/0117. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210 II.md" deleted file mode 100644 index 8adf567f..00000000 --- "a/Solutions/0117. \345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210 II.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) - -- 标签:树、深度优先搜索、广度优先搜索、链表、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个完美二叉树,二叉树结构如下: - -```python -struct Node { - int val; - Node *left; - Node *right; - Node *next; -} -``` - -要求填充每个 next 指针,是的这个指针指向下一个右侧节点。如果找不到下一个右侧节点,则将 next 置为 None。 -示例: - -## 解题思路 - -层次遍历。在层次遍历的过程中,依次取出每一层的节点,并进行连接。然后再扩展下一层节点。 - -## 代码 - -```python -import collections - -class Solution: - def connect(self, root: 'Node') -> 'Node': - if not root: - return root - queue = collections.deque() - queue.append(root) - while queue: - size = len(queue) - for i in range(size): - node = queue.popleft() - if i < size - 1: - node.next = queue[0] - - if node.left: - queue.append(node.left) - if node.right: - queue.append(node.right) - return root -``` - diff --git "a/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" "b/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" deleted file mode 100644 index 2060990c..00000000 --- "a/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" +++ /dev/null @@ -1,117 +0,0 @@ -# [0118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) - -- 标签:数组、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 `numRows`。 - -**要求**:生成前 `numRows` 行的杨辉三角。 - -**说明**: - -- $1 \le numRows \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入:numRows = 5 -输出:[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] -即 -[ - [1], - [1,1], - [1,2,1], - [1,3,3,1], - [1,4,6,4,1] -] -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照行数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 为:杨辉三角第 `i` 行、第 `j` 列位置上的值。 - -###### 3. 状态转移方程 - -根据观察,很容易得出状态转移方程为:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`,此时 `i > 0,j > 0`。 - -###### 4. 初始条件 - -- 每一行第一列都为 `1`,即 `dp[i][0] = 1`。 -- 每一行最后一列都为 `1`,即 `dp[i][i] = 1`。 - -###### 5. 最终结果 - -根据题意和状态定义,我们将每行结果存入答案数组中,将其返回。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - dp = [[0] * i for i in range(1, numRows + 1)] - - for i in range(numRows): - dp[i][0] = 1 - dp[i][i] = 1 - - res = [] - for i in range(numRows): - for j in range(i): - if i != 0 and j != 0: - dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] - res.append(dp[i]) - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。初始条件赋值的时间复杂度为 $O(n)$,两重循环遍历的时间复杂度为 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 - -### 思路 2:动态规划 + 滚动数组优化 - -因为 `dp[i][j]` 仅依赖于上一行(第 `i - 1` 行)的 `dp[i - 1][j - 1]` 和 `dp[i - 1][j]`,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 - -其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 - -定义 `dp[j]` 为杨辉三角第 `i` 行第 `j` 列位置上的值。则第 `i + 1` 行、第 `j` 列的值可以通过 `dp[j]` + `dp[j - 1]` 所得到。 - -这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 - -需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 `dp[j]` 已经更新为当前阶段第 `j` 列位置的状态值之后,右侧 `dp[j + 1]` 想要更新的话,需要的是上一阶段的状态值 `dp[j]`,而此时 `dp[j]` 已经更新了,会破坏当前阶段的状态值。而如果用从右向左的顺序,则不会出现该问题。 - -### 思路 2:动态规划 + 滚动数组优化代码 - -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - dp = [1 for _ in range(numRows + 1)] - - res = [] - - for i in range(numRows): - for j in range(i - 1, -1, -1): - if i != 0 and j != 0: - dp[j] = dp[j - 1] + dp[j] - res.append(dp[:i + 1]) - - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n)$。不考虑最终返回值的空间占用,则总的空间复杂度为 $O(n)$。 diff --git "a/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" "b/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" deleted file mode 100644 index 91b41a60..00000000 --- "a/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" +++ /dev/null @@ -1,114 +0,0 @@ -# [0119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) - -- 标签:数组、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个非负整数 `rowIndex`。 - -**要求**:返回杨辉三角的第 `rowIndex` 行。 - -**说明**: - -- $0 \le rowIndex \le 33$。 -- 要求使用 $O(k)$ 的空间复杂度。 - -**示例**: - -- 示例 1: - -```python -输入:rowIndex = 3 -输出:[1,3,3,1] -``` - -## 解题思路 - -### 思路 1:动态规划 - -因为这道题是从 `0` 行开始计算,则可以先将 `rowIndex` 加 `1`,计算出总共的行数,即 `numRows = rowIndex + 1`。 - -###### 1. 划分阶段 - -按照行数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 为:杨辉三角第 `i` 行、第 `j` 列位置上的值。 - -###### 3. 状态转移方程 - -根据观察,很容易得出状态转移方程为:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`,此时 `i > 0,j > 0`。 - -###### 4. 初始条件 - -- 每一行第一列都为 `1`,即 `dp[i][0] = 1`。 -- 每一行最后一列都为 `1`,即 `dp[i][i] = 1`。 - -###### 5. 最终结果 - -根据题意和状态定义,将 `dp` 最后一行返回。 - -### 思路 1:代码 - -```python -class Solution: - def getRow(self, rowIndex: int) -> List[int]: - # 本题从 0 行开始计算 - numRows = rowIndex + 1 - - dp = [[0] * i for i in range(1, numRows + 1)] - - for i in range(numRows): - dp[i][0] = 1 - dp[i][i] = 1 - - for i in range(numRows): - for j in range(i): - if i != 0 and j != 0: - dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] - - return dp[-1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。初始条件赋值的时间复杂度为 $O(n)$,两重循环遍历的时间复杂度为 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 - -### 思路 2:动态规划 + 滚动数组优化 - -因为 `dp[i][j]` 仅依赖于上一行(第 `i - 1` 行)的 `dp[i - 1][j - 1]` 和 `dp[i - 1][j]`,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 - -其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 - -定义 `dp[j]` 为杨辉三角第 `i` 行第 `j` 列位置上的值。则第 `i + 1` 行、第 `j` 列的值可以通过 `dp[j]` + `dp[j - 1]` 所得到。 - -这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 - -需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 `dp[j]` 已经更新为当前阶段第 `j` 列位置的状态值之后,右侧 `dp[j + 1]` 想要更新的话,需要的是上一阶段的状态值 `dp[j]`,而此时 `dp[j]` 已经更新了,会破坏当前阶段的状态值。而是用从左向左的顺序,则不会出现该问题。 - -### 思路 2:动态规划 + 滚动数组优化代码 - -```python -class Solution: - def getRow(self, rowIndex: int) -> List[int]: - # 本题从 0 行开始计算 - numRows = rowIndex + 1 - - dp = [1 for _ in range(numRows)] - - for i in range(numRows): - for j in range(i - 1, -1, -1): - if i != 0 and j != 0: - dp[j] = dp[j - 1] + dp[j] - - return dp -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n)$。不考虑最终返回值的空间占用,则总的空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" "b/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" deleted file mode 100644 index 22e28c5a..00000000 --- "a/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个代表三角形的二维数组 `triangle`,`triangle` 共有 `n` 行,其中第 `i` 行(从 `0` 开始编号)包含了 `i + 1` 个数。 - -我们每一步只能从当前位置移动到下一行中相邻的节点上。也就是说,如果正位于第 `i` 行第 `j` 列的节点,那么下一步可以移动到第 `i + 1` 行第 `j` 列的位置上,或者第 `i + 1` 行,第 `j + 1` 列的位置上。 - -**要求**:找出自顶向下的最小路径和。 - -**说明**: - -- $1 \le triangle.length \le 200$。 -- $triangle[0].length == 1$。 -- $triangle[i].length == triangle[i - 1].length + 1$。 -- $-10^4 \le triangle[i][j] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] -输出:11 -解释:如下面简图所示: - 2 - 3 4 - 6 5 7 -4 1 8 3 -自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照行数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:从顶部走到第 `i` 行(从 `0` 开始编号)、第 `j` 列的位置时的最小路径和。 - -###### 3. 状态转移方程 - -由于每一步只能从当前位置移动到下一行中相邻的节点上,想要移动到第 `i` 行、第 `j` 列的位置,那么上一步只能在第 `i - 1` 行、第 `j - 1` 列的位置上,或者在第 `i - 1` 行、第 `j` 列的位置上。则状态转移方程为: - -`dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]`。其中 `triangle[i][j]` 表示第 `i` 行、第 `j` 列位置上的元素值。 - -###### 4. 初始条件 - - 在第 `0` 行、第 `j` 列时,最小路径和为 `triangle[0][0]`,即 `dp[0][0] = triangle[0][0]`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:从顶部走到第 `i` 行(从 `0` 开始编号)、第 `j` 列的位置时的最小路径和。为了计算出最小路径和,则需要再遍历一遍 `dp[size - 1]` 行的每一列,求出最小值即为最终结果。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def minimumTotal(self, triangle: List[List[int]]) -> int: - size = len(triangle) - dp = [[0 for _ in range(size)] for _ in range(size)] - dp[0][0] = triangle[0][0] - - for i in range(1, size): - dp[i][0] = dp[i - 1][0] + triangle[i][0] - for j in range(1, i): - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j] - dp[i][i] = dp[i - 1][i - 1] + triangle[i][i] - - return min(dp[size - 1]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最小值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 diff --git "a/Solutions/0121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" "b/Solutions/0121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" deleted file mode 100644 index 62f4eb89..00000000 --- "a/Solutions/0121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) - -- 标签:数组、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 `prices` ,它的第 `i` 个元素 `prices[i]` 表示一支给定股票第 `i` 天的价格。只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。 - -**要求**:计算出能获取的最大利润。如果你不能获取任何利润,返回 $0$。 - -**说明**: - -- $1 \le prices.length \le 10^5$。 -- $0 \le prices[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:[7,1,5,3,6,4] -输出:5 -解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 - 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 -``` - -- 示例 2: - -```python -输入:prices = [7,6,4,3,1] -输出:0 -解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 -``` - -## 解题思路 - -最简单的思路当然是两重循环暴力枚举,寻找不同天数下的最大利润。但更好的做法是进行一次遍历,递推求解。 - -### 思路 1:递推 - - -1. 设置两个变量 `minprice`(用来记录买入的最小值)、`maxprofit`(用来记录可获取的最大利润)。 -2. 从左到右进行遍历数组 `prices`。 -3. 如果遇到当前价格比 `minprice` 还要小的,就更新 `minprice`。 -4. 如果遇到当前价格大于或者等于 `minprice`,则判断一下以当前价格卖出的话能卖多少,如果比 `maxprofit` 还要大,就更新 `maxprofit`。 -5. 最后输出 `maxprofit`。 - -### 思路 1:代码 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - minprice = 10010 - maxprofit = 0 - for price in prices: - if price < minprice: - minprice = price - elif price - minprice > maxprofit: - maxprofit = price - minprice - return maxprofit -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `prices` 的元素个数。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" "b/Solutions/0122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" deleted file mode 100644 index 1e96384b..00000000 --- "a/Solutions/0122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `prices` ,其中 `prices[i]` 表示某支股票第 `i` 天的价格。在每一天,你可以决定是否购买 / 出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。 - -**要求**:计算出能获取的最大利润。 - -**说明**: - -- $1 \le prices.length \le 3 * 10^4$。 -- $0 \le prices[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:prices = [7,1,5,3,6,4] -输出:7 -解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 - 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 - 总利润为 4 + 3 = 7。 -``` - -- 示例 2: - -```python -输入:prices = [1,2,3,4,5] -输出:4 -解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 - 总利润为 4 。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -股票买卖获取利润主要是看差价,必然是低点买入,高点卖出才会赚钱。而要想获取最大利润,就要在跌入谷底的时候买入,在涨到波峰的时候卖出利益才会最大化。所以我们购买股票的策略变为了: - -1. 连续跌的时候不买。 -2. 跌到最低点买入。 -3. 涨到最高点卖出。 - -在这种策略下,只要计算波峰和谷底的差值即可。而波峰和谷底的差值可以通过两两相减所得的差值来累加计算。 - -### 思路 1:代码 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - ans = 0 - for i in range(1, len(prices)): - ans += max(0, prices[i]-prices[i-1]) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `prices` 的元素个数。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0123. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 III.md" "b/Solutions/0123. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 III.md" deleted file mode 100644 index 570eb8b9..00000000 --- "a/Solutions/0123. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 III.md" +++ /dev/null @@ -1,75 +0,0 @@ -# [0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -给定一个数组 `prices` 代表一只股票,其中 `prices[i]` 代表这只股票第 `i` 天的价格。最多可完成两笔交易,且不同同时参与躲避交易(必须在再次购买前出售掉之前的股票)。 - -现在要求:计算所能获取的最大利润。 - -## 解题思路 - -动态规划求解。 - -最多可完成两笔交易意味着总共有三种情况:买卖一次,买卖两次,不买卖。 - -具体到每一天结束总共有 5 种状态: - -0. 未进行买卖状态; -1. 第一次买入状态; -2. 第一次卖出状态; -3. 第二次买入状态; -4. 第二次卖出状态。 - -所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 4`)下,所获取的最大利润。 - -注意:这里第第 `j` 种情况,并不一定是这一天一定要买入或卖出,而是这一天所处于的买入卖出状态。比如说前一天是第一次买入,第二天没有操作,则第二天就沿用前一天的第一次买入状态。 - -接下来确定状态转移公式: - -- 第 `0` 种状态下显然利润为 `0`,可以直接赋值为昨天获取的最大利润,即 `dp[i][0] = dp[i - 1][0]`。 -- 第 `1` 种状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][1] = dp[i - 1][1]`。 - - 第一次买入:`dp[i][1] = dp[i - 1][0] - prices[i]`。 -- 第 `2` 种状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][2] = dp[i - 1][2]`。 - - 第一次卖出:`dp[i][2] = dp[i - 1][1] + prices[i]`。 -- 第 `3` 种状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][3] = dp[i - 1][3]`。 - - 第二次买入:`dp[i][3] = dp[i - 1][2] - prices[i]`。 -- 第 `4` 种状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][4] = dp[i - 1][4]`。 - - 第二次卖出:`dp[i][4] = dp[i - 1][3] + prices[i]`。 - -下面确定初始化的边界值: - -可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第一次买入就是 `dp[0][1] = -prices[i]`。 - -第一次卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][2] = 0`。第二次买入的话,就是 `dp[0][3] = -prices[i]`。同理第二次卖出就是 `dp[0][4] = 0`。 - -在递推结束后,最大利润肯定是无操作、第一次卖出、第二次卖出这三种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第一次卖出、第二次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为一笔交易,那么在转移状态时,我们允许在一天内进行两次交易,则一笔交易的状态可以转移至两笔交易。所以最终答案为 `dp[size - 1][4]`。`size` 为股票天数。 - -## 代码 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - size = len(prices) - if size == 0: - return 0 - dp = [[0 for _ in range(5)] for _ in range(size)] - - dp[0][1] = -prices[0] - dp[0][3] = -prices[0] - - for i in range(1, size): - dp[i][0] = dp[i - 1][0] - dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) - dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]) - dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]) - dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]) - return dp[size - 1][4] -``` - diff --git "a/Solutions/0124. \344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.md" "b/Solutions/0124. \344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.md" deleted file mode 100644 index 12bba562..00000000 --- "a/Solutions/0124. \344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) - -- 标签:树、深度优先搜索、动态规划、二叉树 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 $root$。 - -**要求**:返回其最大路径和。 - -**说明**: - -- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 -- **路径和**:路径中各节点值的总和。 -- 树中节点数目范围是 $[1, 3 * 10^4]$。 -- $-1000 \le Node.val \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/13/exx1.jpg) - -```python -输入:root = [1,2,3] -输出:6 -解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/10/13/exx2.jpg) - -```python -输入:root = [-10,9,20,null,null,15,7] -输出:42 -解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -根据最大路径和中对应路径是否穿过根节点,我们可以将二叉树分为两种: - -1. 最大路径和中对应路径穿过根节点。 -2. 最大路径和中对应路径不穿过根节点。 - -如果最大路径和中对应路径穿过根节点,则:**该二叉树的最大路径和 = 左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值**。 - -而如果最大路径和中对应路径不穿过根节点,则:**该二叉树的最大路径和 = 所有子树中最大路径和**。 - -即:**该二叉树的最大路径和 = max(左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值,所有子树中最大路径和)**。 - -对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大路径和变量 $ans$。 - -然后定义函数 ` def dfs(self, node):` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 - -计算的结果可能的情况有 $2$ 种: - -1. 经过空节点的最大贡献值等于 $0$。 -2. 经过非空节点的最大贡献值等于 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。如果该贡献值为负数,可以考虑舍弃,即最大贡献值为 $0$。 - -在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 $ans$ 即为答案。具体步骤如下: - -1. 如果根节点 $root$ 为空,则返回 $0$。 -2. 递归计算左子树的最大贡献值为 $left\underline{}max$。 -3. 递归计算右子树的最大贡献值为 $right\underline{}max$。 -4. 更新维护最大路径和变量,即 $self.ans = max \lbrace self.ans, \quad left\underline{}max + right\underline{}max + node.val \rbrace$。 -5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。 -6. 最终 $self.ans$ 即为答案。 - -### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def __init__(self): - self.ans = float('-inf') - - def dfs(self, node): - if not node: - return 0 - left_max = max(self.dfs(node.left), 0) # 左子树提供的最大贡献值 - right_max = max(self.dfs(node.right), 0) # 右子树提供的最大贡献值 - - cur_max = left_max + right_max + node.val # 包含当前节点和左右子树的最大路径和 - self.ans = max(self.ans, cur_max) # 更新所有路径中的最大路径和 - - return max(left_max, right_max) + node.val # 返回包含当前节点的子树的最大贡献值 - - def maxPathSum(self, root: Optional[TreeNode]) -> int: - self.dfs(root) - return self.ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0125. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.md" "b/Solutions/0125. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.md" deleted file mode 100644 index a004e5eb..00000000 --- "a/Solutions/0125. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 - -**说明**: - -- 回文串:正着读和反着读都一样的字符串。 -- $1 \le s.length \le 2 * 10^5$。 -- `s` 仅由可打印的 ASCII 字符组成。 - -**示例**: - -- 示例 1: - -```python -输入: "A man, a plan, a canal: Panama" -输出:true -解释:"amanaplanacanalpanama" 是回文串。 -``` - -- 示例 2: - -```python -输入:"race a car" -输出:false -解释:"raceacar" 不是回文串。 -``` - -## 解题思路 - -### 思路 1:对撞指针 - -1. 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 -2. 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 -3. 然后判断 `s[left]` 是否和 `s[right]` 相等(注意大小写)。 - 1. 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 - 2. 如果不相等,则说明不是回文串,直接返回 `False`。 -4. 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 - -### 思路 1:代码 - -```python -class Solution: - def isPalindrome(self, s: str) -> bool: - left = 0 - right = len(s) - 1 - - while left < right: - if not s[left].isalnum(): - left += 1 - continue - if not s[right].isalnum(): - right -= 1 - continue - - if s[left].lower() == s[right].lower(): - left += 1 - right -= 1 - else: - return False - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(len(s))$。 -- **空间复杂度**:$O(len(s))$。 diff --git "a/Solutions/0127. \345\215\225\350\257\215\346\216\245\351\276\231.md" "b/Solutions/0127. \345\215\225\350\257\215\346\216\245\351\276\231.md" deleted file mode 100644 index 950ac389..00000000 --- "a/Solutions/0127. \345\215\225\350\257\215\346\216\245\351\276\231.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0127. 单词接龙](https://leetcode.cn/problems/word-ladder/) - -- 标签:广度优先搜索、哈希表、字符串 -- 难度:困难 - -## 题目大意 - -给定两个单词 `beginWord` 和 `endWord`,以及一个字典 `wordList`。找到从 `beginWord` 到 `endWord` 的最短转换序列中的单词数目。如果不存在这样的转换序列,则返回 0。 - -转换需要遵守的规则如下: - -- 每次转换只能改变一个字母。 -- 转换过程中的中间单词必须为字典中的单词。 - -## 解题思路 - -广度优先搜索。使用队列存储将要遍历的单词和单词数目。 - -从 `beginWord` 开始变换,把单词的每个字母都用 `a ~ z` 变换一次,变换后的单词是否是 `endWord`,如果是则直接返回。 - -否则查找变换后的词是否在 `wordList` 中。如果在 `wordList` 中找到就加入队列,找不到就输出 `0`。然后按照广度优先搜索的算法急需要遍历队列中的节点,直到所有单词都出队时结束。 - -## 代码 - -```python -class Solution: - def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: - if not wordList or endWord not in wordList: - return 0 - word_set = set(wordList) - if beginWord in word_set: - word_set.remove(beginWord) - - queue = collections.deque() - queue.append((beginWord, 1)) - while queue: - word, level = queue.popleft() - if word == endWord: - return level - - for i in range(len(word)): - for j in range(26): - new_word = word[:i] + chr(ord('a') + j) + word[i + 1:] - if new_word in word_set: - word_set.remove(new_word) - queue.append((new_word, level + 1)) - - return 0 -``` - diff --git "a/Solutions/0128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" "b/Solutions/0128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" deleted file mode 100644 index 1bc37af8..00000000 --- "a/Solutions/0128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) - -- 标签:并查集、数组、哈希表 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个未排序的整数数组 `nums`。 - -**要求**:找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。并且要用时间复杂度为 $O(n)$ 的算法解决此问题。 - -**说明**: - -- $0 \le nums.length \le 10^5$。 -- $-10^9 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [100,4,200,1,3,2] -输出:4 -解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 -``` - -- 示例 2: - -```python -输入:nums = [0,3,7,2,5,8,4,6,0,1] -输出:9 -``` - -## 解题思路 - -暴力做法有两种思路。 - -- 第 1 种思路是先排序再依次判断,这种做法时间复杂度最少是 $O(n \log_2 n)$。 -- 第 2 种思路是枚举数组中的每个数 `num`,考虑以其为起点,不断尝试匹配 `num + 1`、`num + 2`、`...` 是否存在,最长匹配次数为 `len(nums)`。这样下来时间复杂度为 $O(n^2)$。 - -我们可以使用哈希表优化这个过程。 - -### 思路 1:哈希表 - -1. 先将数组存储到集合中进行去重,然后使用 `curr_streak` 维护当前连续序列长度,使用 `ans` 维护最长连续序列长度。 -2. 遍历集合中的元素,对每个元素进行判断,如果该元素不是序列的开始(即 `num - 1` 在集合中),则跳过。 -3. 如果 `num - 1` 不在集合中,说明 `num` 是序列的开始,判断 `num + 1` 、`nums + 2`、`...` 是否在哈希表中,并不断更新当前连续序列长度 `curr_streak`。并在遍历结束之后更新最长序列的长度。 -4. 最后输出最长序列长度。 - -### 思路 1:代码 - -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - ans = 0 - nums_set = set(nums) - for num in nums_set: - if num - 1 not in nums_set: - curr_num = num - curr_streak = 1 - - while curr_num + 1 in nums_set: - curr_num += 1 - curr_streak += 1 - ans = max(ans, curr_streak) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。将数组存储到集合中进行去重的操作的时间复杂度是 $O(n)$。查询每个数是否在集合中的时间复杂度是 $O(1)$ ,并且跳过了所有不是起点的元素。更新当前连续序列长度 `curr_streak` 的时间复杂度是 $O(n)$,所以最终的时间复杂度是 $O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[128. 最长连续序列 - 力扣(Leetcode)](https://leetcode.cn/problems/longest-consecutive-sequence/solutions/1176496/xiao-bai-lang-ha-xi-ji-he-ha-xi-biao-don-j5a2/) diff --git "a/Solutions/0129. \346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.md" "b/Solutions/0129. \346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.md" deleted file mode 100644 index 49225cd8..00000000 --- "a/Solutions/0129. \346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`,树中每个节点都存放有一个 `0` 到 `9` 之间的数字。每条从根节点到叶节点的路径都代表一个数字。例如,从根节点到叶节点的路径是 `1` -> `2` -> `3`,表示数字 `123`。 - -**要求**:计算从根节点到叶节点生成的所有数字的和。 - -**说明**: - -- **叶节点**:指没有子节点的节点。 -- 树中节点的数目在范围 $[1, 1000]$ 内。 -- $0 \le Node.val \le 9$。 -- 树的深度不超过 $10$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/19/num1tree.jpg) - -```python -输入:root = [1,2,3] -输出:25 -解释: -从根到叶子节点路径 1->2 代表数字 12 -从根到叶子节点路径 1->3 代表数字 13 -因此,数字总和 = 12 + 13 = 25 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/02/19/num2tree.jpg) - -```python -输入:root = [4,9,0,5,1] -输出:1026 -解释: -从根到叶子节点路径 4->9->5 代表数字 495 -从根到叶子节点路径 4->9->1 代表数字 491 -从根到叶子节点路径 4->0 代表数字 40 -因此,数字总和 = 495 + 491 + 40 = 1026 -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -1. 记录下路径上所有节点构成的数字,使用变量 `pre_total` 保存下当前路径上构成的数字。 -2. 如果遇到叶节点,则直接返回当前数字。 -3. 如果没有遇到叶节点,则递归遍历左右子树,并累加对应结果。 - -### 思路 1:代码 - -```python -class Solution: - def dfs(self, root, pre_total): - if not root: - return 0 - total = pre_total * 10 + root.val - if not root.left and not root.right: - return total - return self.dfs(root.left, total) + self.dfs(root.right, total) - - def sumNumbers(self, root: Optional[TreeNode]) -> int: - return self.dfs(root, 0) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - - - diff --git "a/Solutions/0130. \350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.md" "b/Solutions/0130. \350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.md" deleted file mode 100644 index 8737f777..00000000 --- "a/Solutions/0130. \350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [0130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 `m * n` 的矩阵 `board`,由若干字符 `X` 和 `O` 构成。 - -**要求**:找到所有被 `X` 围绕的区域,并将这些区域里所有的 `O` 用 `X` 填充。 - -**说明**: - -- $m == board.length$。 -- $n == board[i].length$。 -- $1 <= m, n <= 200$。 -- $board[i][j]$ 为 `'X'` 或 `'O'`。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/19/xogrid.jpg) - -```python -输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] -输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] -解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 -``` - -- 示例 2: - -```python -输入:board = [["X"]] -输出:[["X"]] -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -根据题意,任何边界上的 `O` 都不会被填充为`X`。而被填充 `X` 的 `O` 一定在内部不在边界上。 - -所以我们可以用深度优先搜索先搜索边界上的 `O` 以及与边界相连的 `O`,将其先标记为 `#`。 - -最后遍历一遍 `board`,将所有 `#` 变换为 `O`,将所有 `O` 变换为 `X`。 - -### 思路 1:代码 - -```python -class Solution: - def solve(self, board: List[List[str]]) -> None: - """ - Do not return anything, modify board in-place instead. - """ - if not board: - return - rows, cols = len(board), len(board[0]) - - def dfs(x, y): - if not 0 <= x < rows or not 0 <= y < cols or board[x][y] != 'O': - return - board[x][y] = '#' - dfs(x + 1, y) - dfs(x - 1, y) - dfs(x, y + 1) - dfs(x, y - 1) - - for i in range(rows): - dfs(i, 0) - dfs(i, cols - 1) - - for j in range(cols - 1): - dfs(0, j) - dfs(rows - 1, j) - - for i in range(rows): - for j in range(cols): - if board[i][j] == '#': - board[i][j] = 'O' - elif board[i][j] == 'O': - board[i][j] = 'X' -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(n \times m)$。 \ No newline at end of file diff --git "a/Solutions/0131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" "b/Solutions/0131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" deleted file mode 100644 index d09962c4..00000000 --- "a/Solutions/0131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [0131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) - -- 标签:字符串、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`,将 `s` 分割成一些子串,保证每个子串都是「回文串」。返回 `s` 所有可能的分割方案。 - -## 解题思路 - -回溯算法,建立两个数组 res、path。res 用于存放所有满足题意的组合,path 用于存放当前满足题意的一个组合。 - -在回溯的时候判断当前子串是否为回文串,如果不是则跳过,如果是则继续向下一层遍历。 - -定义判断是否为回文串的方法和回溯方法,从 `start_index = 0` 的位置开始回溯。 - -- 如果 `start_index >= len(s)`,则将 path 中的元素加入到 res 数组中。 -- 然后对 `[start_index, len(s) - 1]` 范围内的子串进行遍历取值。 - - 如果字符串 `s` 在范围 `[start_index, i]` 所代表的子串是回文串,则将其加入 path 数组。 - - 递归遍历 `[i + 1, len(s) - 1]` 范围上的子串。 - - 然后将遍历的范围 `[start_index, i]` 所代表的子串进行回退。 -- 最终返回 res 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, s: str, start_index: int): - if start_index >= len(s): - self.res.append(self.path[:]) - return - for i in range(start_index, len(s)): - if self.ispalindrome(s, start_index, i): - self.path.append(s[start_index: i+1]) - self.backtrack(s, i + 1) - self.path.pop() - - def ispalindrome(self, s: str, start: int, end: int): - i, j = start, end - while i < j: - if s[i] != s[j]: - return False - i += 1 - j -= 1 - return True - - def partition(self, s: str) -> List[List[str]]: - self.res.clear() - self.path.clear() - self.backtrack(s, 0) - return self.res -``` - diff --git "a/Solutions/0133. \345\205\213\351\232\206\345\233\276.md" "b/Solutions/0133. \345\205\213\351\232\206\345\233\276.md" deleted file mode 100644 index c5d05591..00000000 --- "a/Solutions/0133. \345\205\213\351\232\206\345\233\276.md" +++ /dev/null @@ -1,127 +0,0 @@ -# [0133. 克隆图](https://leetcode.cn/problems/clone-graph/) - -- 标签:深度优先搜索、广度优先搜索、图、哈希表 -- 难度:中等 - -## 题目大意 - -**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 - -**要求**:返回该图的深拷贝。 - -**说明**: - -- 节点数不超过 $100$。 -- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 -- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 -- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 -- 图是连通图,你可以从给定节点访问到所有节点。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) - -```python -输入:adjList = [[2,4],[1,3],[2,4],[1,3]] -输出:[[2,4],[1,3],[2,4],[1,3]] -解释: -图中有 4 个节点。 -节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 -节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 -节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 -节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) - -```python -输入:adjList = [[2],[1]] -输出:[[2],[1]] -``` - -## 解题思路 - -所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。 - -可以用深度优先搜索或者广度优先搜索来做。 - -### 思路 1:深度优先搜索 - -1. 使用哈希表 $visitedDict$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。 -2. 从给定节点开始,以深度优先搜索的方式遍历原图。 - 1. 如果当前节点被访问过,则返回隆图中对应节点。 - 2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。 - 3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。 -3. 递归结束,返回克隆节点。 - -### 思路 1:代码 - -```python -class Solution: - def cloneGraph(self, node: 'Node') -> 'Node': - if not node: - return node - visited = dict() - - def dfs(node: 'Node') -> 'Node': - if node in visited: - return visited[node] - - clone_node = Node(node.val, []) - visited[node] = clone_node - for neighbor in node.neighbors: - clone_node.neighbors.append(dfs(neighbor)) - return clone_node - - return dfs(node) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:广度优先搜索 - -1. 使用哈希表 $visited$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。使用队列 $queue$ 存放节点。 -2. 根据起始节点 $node$,创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列中,即 `queue.append(node)`。 -3. 从队列中取出第一个节点 $node\underline{}u$。访问节点 $node\underline{}u$。 -4. 遍历节点 $node\underline{}u$ 的所有未访问邻接节点 $node\underline{}v$(节点 $node\underline{}v$ 不在 $visited$ 中)。 -5. 根据节点 $node\underline{}v$ 创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node_v] = Node(node_v.val, [])`。 -6. 然后将节点 $node\underline{}v$ 放入队列 $queue$ 中,即 `queue.append(node_v)`。 -7. 重复步骤 $3 \sim 6$,直到队列 $queue$ 为空。 -8. 广度优先搜索结束,返回起始节点的克隆节点(即 $visited[node]$)。 - -### 思路 2:代码 - -```python -class Solution: - def cloneGraph(self, node: 'Node') -> 'Node': - if not node: - return node - - visited = dict() - queue = collections.deque() - - visited[node] = Node(node.val, []) - queue.append(node) - - while queue: - node_u = queue.popleft() - for node_v in node_u.neighbors: - if node_v not in visited: - visited[node_v] = Node(node_v.val, []) - queue.append(node_v) - visited[node_u].neighbors.append(visited[node_v]) - - return visited[node] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0134. \345\212\240\346\262\271\347\253\231.md" "b/Solutions/0134. \345\212\240\346\262\271\347\253\231.md" deleted file mode 100644 index f9cad2f5..00000000 --- "a/Solutions/0134. \345\212\240\346\262\271\347\253\231.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [0134. 加油站](https://leetcode.cn/problems/gas-station/) - -- 标签:贪心、数组 -- 难度:中等 - -## 题目大意 - -一条环路上有 N 个加油站,第 i 个加油站有 gas[i] 升汽油。 - -现在有一辆油箱无限容量的汽车,从第 i 个加油站开往第 i + 1 个加油站需要消耗汽油 cost[i] 升。如果汽车上携带的有两不够 cost[i],则无法从第 i 个加油站开往第 i + 1 个加油站。 - -现在从其中一个加油站开始出发,且出发时油箱为空。如果能绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。 - -## 解题思路 - -1. 暴力求解 - -分别考虑从第 0 个点、第 1 个点、…、第 i 个点出发,能否回到第 0 个点、第 1 个点、…、第 i 个点。 - -2. 贪心算法 - -- 如果加油站提供的油总和大于等于消耗的汽油量,则必定可以绕环路行驶一周 -- 假设先不考虑油量为负的情况,我们从「第 0 个加油站」出发,环行一周。记录下汽油量 gas[i] 和 cost[i] 差值总和 sum_diff,同时记录下油箱剩余油量的最小值 min_sum。 -- 如果差值总和 sum_diff < 0,则无论如何都不能环行一周。油不够啊,亲!! -- 如果 min_sum ≥ 0,则行驶过程中油箱始终有油,则可以从 0 个加油站出发环行一周。 -- 如果 min_sum < 0,则说明行驶过程中油箱油不够了,那么考虑更换开始的起点。 - - 从右至左遍历,计算汽油量 gas[i] 和 cost[i] 差值,看哪个加油站能将 min_sum 填平。如果最终达到 min_sum ≥ 0,则说明从该点开始出发,油箱中的油始终不为空,则返回该点下标。 - - 如果找不到最返回 -1。 - -## 代码 - -```python -class Solution: - def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int: - sum_diff, min_sum = 0, float('inf') - for i in range(len(gas)): - sum_diff += gas[i] - cost[i] - min_sum = min(min_sum, sum_diff) - - if sum_diff < 0: - return -1 - - if min_sum >= 0: - return 0 - - for i in range(len(gas)-1, -1, -1): - min_sum += gas[i] - cost[i] - if min_sum >= 0: - return i - return -1 -``` - -## 参考链接 - -- [贪心算法/前缀和 - 加油站 - 力扣(LeetCode)](https://leetcode.cn/problems/gas-station/solution/tan-xin-suan-fa-qian-zhui-he-by-antione/) diff --git "a/Solutions/0135. \345\210\206\345\217\221\347\263\226\346\236\234.md" "b/Solutions/0135. \345\210\206\345\217\221\347\263\226\346\236\234.md" deleted file mode 100644 index 99693544..00000000 --- "a/Solutions/0135. \345\210\206\345\217\221\347\263\226\346\236\234.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0135. 分发糖果](https://leetcode.cn/problems/candy/) - -- 标签:贪心、数组 -- 难度:困难 - -## 题目大意 - -**描述**:$n$ 个孩子站成一排。老师会根据每个孩子的表现,给每个孩子进行评分。然后根据下面的规则给孩子们分发糖果: - -- 每个孩子至少得 $1$ 个糖果。 -- 评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果。 - -现在给定 $n$ 个孩子的表现分数数组 `ratings`,其中 `ratings[i]` 表示第 $i$ 个孩子的评分。 - -**要求**:返回最少需要准备的糖果数目。 - -**说明**: - -- $n == ratings.length$。 -- $1 \le n \le 2 \times 10^4$。 -- $0 \le ratings[i] \le 2 * 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:ratings = [1,0,2] -输出:5 -解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。 -``` - -- 示例 2: - -```python -输入:ratings = [1,2,2] -输出:4 -解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 - 第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -先来看分发糖果的规则。 - -「每个孩子至少得 1 个糖果」:说明糖果数目至少为 N 个。 - -「评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果」:可以看做为以下两种条件: - -- 当 $ratings[i - 1] < ratings[i]$ 时,第 i 个孩子的糖果数量比第 $i - 1$ 个孩子的糖果数量多; -- 当 $ratings[i] > ratings[i + 1]$ 时,第 i 个孩子的糖果数量比第$ i + 1$ 个孩子的糖果数量多。 - -根据以上信息,我们可以设定一个长度为 N 的数组 sweets 来表示每个孩子分得的最少糖果数,初始每个孩子分得糖果数都为 1。 - -然后遍历两遍数组,第一遍遍历满足当 $ratings[i - 1] < ratings[i]$ 时,第 $i$ 个孩子的糖果数量比第 $i - 1$ 个孩子的糖果数量多 $1$ 个。第二遍遍历满足当 $ratings[i] > ratings[i + 1]$ 时,第 $i$ 个孩子的糖果数量取「第 $i + 1$ 个孩子的糖果数量多 $1$ 个」和「第 $i + 1$ 个孩子目前拥有的糖果数量」中的最大值。 - -然后再遍历求所有孩子的糖果数量和即为答案。 - -### 思路 1:代码 - -```python -class Solution: - def candy(self, ratings: List[int]) -> int: - size = len(ratings) - sweets = [1 for _ in range(size)] - - for i in range(1, size): - if ratings[i] > ratings[i - 1]: - sweets[i] = sweets[i - 1] + 1 - - for i in range(size - 2, -1, -1): - if ratings[i] > ratings[i + 1]: - sweets[i] = max(sweets[i], sweets[i + 1] + 1) - - res = sum(sweets) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `ratings` 的长度。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0136. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" "b/Solutions/0136. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 2480ea23..00000000 --- "a/Solutions/0136. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) - -- 标签:位运算、数组 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个非空整数数组 `nums`,`nums` 中除了某个元素只出现一次以外,其余每个元素均出现两次。 - -**要求**:找出那个只出现了一次的元素。 - -**说明**: - -- 要求不能使用额外的存储空间。 - -**示例**: - -- 示例 1: - -```python -输入: [2,2,1] -输出: 1 -``` - -- 示例 2: - -```python -输入: [4,1,2,1,2] -输出: 4 -``` - -## 解题思路 - -### 思路 1:位运算 - -如果没有时间复杂度和空间复杂度的限制,可以使用哈希表 / 集合来存储每个元素出现的次数,如果哈希表中没有该数字,则将该数字加入集合,如果集合中有了该数字,则从集合中删除该数字,最终成对的数字都被删除了,只剩下单次出现的元素。 - -但是题目要求不使用额外的存储空间,就需要用到位运算中的异或运算。 - -> 异或运算 $\oplus$ 的三个性质: -> -> 1. 任何数和 $0$ 做异或运算,结果仍然是原来的数,即 $a \oplus 0 = a$。 -> 2. 数和其自身做异或运算,结果是 $0$,即 $a \oplus a = 0$。 -> 3. 异或运算满足交换率和结合律:$a \oplus b \oplus a = b \oplus a \oplus a = b \oplus (a \oplus a) = b \oplus 0 = b$。 - -根据异或运算的性质,对 $n$ 个数不断进行异或操作,最终可得到单次出现的元素。 - -### 思路 1:代码 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - if len(nums) == 1: - return nums[0] - ans = 0 - for i in range(len(nums)): - ans ^= nums[i] - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0137. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 II.md" "b/Solutions/0137. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 II.md" deleted file mode 100644 index 8896c5fb..00000000 --- "a/Solutions/0137. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 II.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [0137. 只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) - -- 标签:位运算、数组 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$,除了某个元素仅出现一次外,其余每个元素恰好出现三次。 - -**要求**:找到并返回那个只出现了一次的元素。 - -**说明**: - -- $1 \le nums.length \le 3 * 10^4$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- $nums$ 中,除某个元素仅出现一次外,其余每个元素都恰出现三次。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,2,3,2] -输出:3 -``` - -- 示例 2: - -```python -输入:nums = [0,1,0,1,0,1,99] -输出:99 -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 利用哈希表统计出每个元素的出现次数。 -2. 再遍历一次哈希表,找到仅出现一次的元素。 - -### 思路 1:代码 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - nums_dict = dict() - for num in nums: - if num in nums_dict: - nums_dict[num] += 1 - else: - nums_dict[num] = 1 - for key in nums_dict: - value = nums_dict[key] - if value == 1: - return key - return 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:位运算 - -将出现三次的元素换成二进制形式放在一起,其二进制对应位置上,出现 $1$ 的个数一定是 $3$ 的倍数(包括 $0$)。此时,如果在放进来只出现一次的元素,则某些二进制位置上出现 $1$ 的个数就不是 $3$ 的倍数了。 - -将这些二进制位置上出现 $1$ 的个数不是 $3$ 的倍数位置值置为 $1$,是 $3$ 的倍数则置为 $0$。这样对应下来的二进制就是答案所求。 - -注意:因为 Python 的整数没有位数限制,所以不能通过最高位确定正负。所以 Python 中负整数的补码会被当做正整数。所以在遍历到最后 $31$ 位时进行 $ans -= (1 << 31)$ 操作,目的是将负数的补码转换为「负号 + 原码」的形式。这样就可以正常识别二进制下的负数。参考:[Two's Complement Binary in Python? - Stack Overflow](https://stackoverflow.com/questions/12946116/twos-complement-binary-in-python/12946226) - -### 思路 2:代码 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - ans = 0 - for i in range(32): - count = 0 - for j in range(len(nums)): - count += (nums[j] >> i) & 1 - if count % 3 != 0: - if i == 31: - ans -= (1 << 31) - else: - ans = ans | 1 << i - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \log m)$,其中 $n$ 是数组 $nums$ 的长度,$m$ 是数据范围,本题中 $m = 32$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0138. \345\244\215\345\210\266\345\270\246\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250.md" "b/Solutions/0138. \345\244\215\345\210\266\345\270\246\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250.md" deleted file mode 100644 index 8b0dc838..00000000 --- "a/Solutions/0138. \345\244\215\345\210\266\345\270\246\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250.md" +++ /dev/null @@ -1,72 +0,0 @@ -# [0138. 复制带随机指针的链表](https://leetcode.cn/problems/copy-list-with-random-pointer/) - -- 标签:哈希表、链表 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`,链表中每个节点除了 `next` 指针之外,还包含一个随机指针 `random`,该指针可以指向链表中的任何节点或者空节点。 - -**要求**:将该链表进行深拷贝。返回复制链表的头节点。 - -**说明**: - -- $0 \le n \le 1000$。 -- $-10^4 \le Node.val \le 10^4$。 -- `Node.random` 为 `null` 或指向链表中的节点。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e1.png) - -```python -输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] -输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e2.png) - -```python -输入:head = [[1,1],[2,1]] -输出:[[1,1],[2,1]] -``` - -## 解题思路 - -### 思路 1:迭代 - -1. 遍历链表,利用哈希表,以 `旧节点: 新节点` 为映射关系,将节点关系存储下来。 -2. 再次遍历链表,将新链表的 `next` 和 `random` 指针设置好。 - -### 思路 1:代码 - -```python -class Solution: - def copyRandomList(self, head: 'Node') -> 'Node': - if not head: - return None - node_dict = dict() - curr = head - while curr: - new_node = Node(curr.val, None, None) - node_dict[curr] = new_node - curr = curr.next - curr = head - while curr: - if curr.next: - node_dict[curr].next = node_dict[curr.next] - if curr.random: - node_dict[curr].random = node_dict[curr.random] - curr = curr.next - return node_dict[head] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0139. \345\215\225\350\257\215\346\213\206\345\210\206.md" "b/Solutions/0139. \345\215\225\350\257\215\346\213\206\345\210\206.md" deleted file mode 100644 index 057ae04a..00000000 --- "a/Solutions/0139. \345\215\225\350\257\215\346\213\206\345\210\206.md" +++ /dev/null @@ -1,89 +0,0 @@ -# [0139. 单词拆分](https://leetcode.cn/problems/word-break/) - -- 标签:字典树、记忆化搜索、数组、哈希表、字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个非空字符串 `s` 和一个包含非空单词的列表 `wordDict` 作为字典。 - -**要求**:判断是否可以利用字典中出现的单词拼接出 `s` 。 - -**说明**: - -- 不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 -- $1 \le s.length \le 300$。 -- $1 \le wordDict.length \le 1000$。 -- $1 \le wordDict[i].length \le 20$。 -- `s` 和 `wordDict[i]` 仅有小写英文字母组成。 -- `wordDict` 中的所有字符串互不相同。 - -**示例**: - -- 示例 1: - -```python -输入: s = "leetcode", wordDict = ["leet", "code"] -输出: true -解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。 -``` - -- 示例 2: - -```python -输入: s = "applepenapple", wordDict = ["apple", "pen"] -输出: true -解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。 - 注意,你可以重复使用字典中的单词。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照单词结尾位置进行阶段划分。 - -###### 2. 定义状态 - -`s` 能否拆分为单词表的单词,可以分解为: - -- 前 $i$ 个字符构成的字符串,能否分解为单词。 -- 剩余字符串,能否分解为单词。 - -定义状态 `dp[i]` 表示:长度为 $i$ 的字符串 `s[0: i]` 能否拆分成单词,如果为 `True` 则表示可以拆分,如果为 `False` 则表示不能拆分。 - -###### 3. 状态转移方程 - -- 如果 `s[0: j]` 可以拆分为单词(即 `dp[j] == True`),并且字符串 `s[j: i]` 出现在字典中,则 `dp[i] = True`。 -- 如果 `s[0: j]` 不可以拆分为单词(即 `dp[j] == False`),或者字符串 `s[j: i]` 没有出现在字典中,则 `dp[i] = False`。 - -###### 4. 初始条件 - -- 长度为 $0$ 的字符串 `s[0: i]` 可以拆分为单词,即 `dp[0] = True`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示:长度为 $i$ 的字符串 `s[0: i]` 能否拆分成单词。则最终结果为 `dp[size]`,`size` 为字符串长度。 - -### 思路 1:代码 - -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - size = len(s) - dp = [False for _ in range(size + 1)] - dp[0] = True - for i in range(size + 1): - for j in range(i): - if dp[j] and s[j: i] in wordDict: - dp[i] = True - return dp[size] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 `s` 的长度。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0140. \345\215\225\350\257\215\346\213\206\345\210\206 II.md" "b/Solutions/0140. \345\215\225\350\257\215\346\213\206\345\210\206 II.md" deleted file mode 100644 index 5ca99a10..00000000 --- "a/Solutions/0140. \345\215\225\350\257\215\346\213\206\345\210\206 II.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [0140. 单词拆分 II](https://leetcode.cn/problems/word-break-ii/) - -- 标签:字典树、记忆化搜索、数组、哈希表、字符串、动态规划、回溯 -- 难度:困难 - -## 题目大意 - -给定一个非空字符串 `s` 和一个包含非空单词列表的字典 `wordDict`。 - -要求:在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。 - -说明: - -- 分隔时可以重复使用字典中的单词。 -- 你可以假设字典中没有重复的单词。 - -## 解题思路 - -回溯 + 记忆化搜索。 - -对于字符串 `s`,如果某个位置左侧部分是单词列表中的单词,则拆分出该单词,然后对 `s` 右侧剩余部分进行递归拆分。如果可以将整个字符串 `s` 拆分成单词列表中的单词,则得到一个句子。 - -使用 `memo` 数组进行记忆化存储,这样可以减少重复计算。 - -## 代码 - -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: - size = len(s) - memo = [None for _ in range(size + 1)] - - def dfs(start): - if start > size - 1: - return [[]] - if memo[start]: - return memo[start] - res = [] - for i in range(start, size): - word = s[start: i + 1] - if word in wordDict: - rest_res = dfs(i + 1) - for item in rest_res: - res.append([word] + item) - memo[start] = res - return res - res = dfs(0) - ans = [] - for item in res: - ans.append(" ".join(item)) - return ans -``` - diff --git "a/Solutions/0141. \347\216\257\345\275\242\351\223\276\350\241\250.md" "b/Solutions/0141. \347\216\257\345\275\242\351\223\276\350\241\250.md" deleted file mode 100644 index b6cc241d..00000000 --- "a/Solutions/0141. \347\216\257\345\275\242\351\223\276\350\241\250.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) - -- 标签:哈希表、链表、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:判断链表中是否有环。如果有环则返回 `True`,否则返回 `False`。 - -**说明**: - -- 链表中节点的数目范围是 $[0, 10^4]$。 -- $-10^5 \le Node.val \le 10^5$。 -- `pos` 为 `-1` 或者链表中的一个有效索引。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) - -```python -输入:head = [3,2,0,-4], pos = 1 -输出:True -解释:链表中有一个环,其尾部连接到第二个节点。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) - -```python -输入:head = [1,2], pos = 0 -输出:True -解释:链表中有一个环,其尾部连接到第一个节点。 -``` - -## 解题思路 - -### 思路 1:哈希表 - -最简单的思路是遍历所有节点,每次遍历节点之前,使用哈希表判断该节点是否被访问过。如果访问过就说明存在环,如果没访问过则将该节点添加到哈希表中,继续遍历判断。 - -### 思路 1:代码 - -```python -class Solution: - def hasCycle(self, head: ListNode) -> bool: - nodeset = set() - - while head: - if head in nodeset: - return True - nodeset.add(head) - head = head.next - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:快慢指针(Floyd 判圈算法) - -这种方法类似于在操场跑道跑步。两个人从同一位置同时出发,如果跑道有环(环形跑道),那么快的一方总能追上慢的一方。 - -基于上边的想法,Floyd 用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 - -### 思路 2:代码 - -```python -class Solution: - def hasCycle(self, head: ListNode) -> bool: - if head == None or head.next == None: - return False - - slow = head - fast = head.next - - while slow != fast: - if fast == None or fast.next == None: - return False - slow = slow.next - fast = fast.next.next - - return True -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" "b/Solutions/0142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" deleted file mode 100644 index 634fae6a..00000000 --- "a/Solutions/0142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) - -- 标签:哈希表、链表、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 `None`。 - -**说明**: - -- 链表中节点的数目范围在范围 $[0, 10^4]$ 内。 -- $-10^5 \le Node.val \le 10^5$。 -- `pos` 的值为 `-1` 或者链表中的一个有效索引。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) - -```python -输入:head = [3,2,0,-4], pos = 1 -输出:返回索引为 1 的链表节点 -解释:链表中有一个环,其尾部连接到第二个节点。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) - -```python -输入:head = [1,2], pos = 0 -输出:返回索引为 0 的链表节点 -解释:链表中有一个环,其尾部连接到第一个节点。 -``` - -## 解题思路 - -### 思路 1:快慢指针(Floyd 判圈算法) - -1. 利用两个指针,一个慢指针 `slow` 每次前进一步,快指针 `fast` 每次前进两步(两步或多步效果是等价的)。 -2. 如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环。 -3. 否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 -4. 如果有环,则再定义一个指针 `ans`,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 - -这是因为:假设入环位置为 `A`,快慢指针在 `B` 点相遇,则相遇时慢指针走了 $a + b$ 步,快指针走了 $a + n(b+c) + b$ 步。 - -因为快指针总共走的步数是慢指针走的步数的两倍,即 $2(a + b) = a + n(b + c) + b$,所以可以推出:$a = c + (n-1)(b + c)$。 - -我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 - -### 思路 1:代码 - -```python -class Solution: - def detectCycle(self, head: ListNode) -> ListNode: - fast, slow = head, head - while True: - if not fast or not fast.next: - return None - fast = fast.next.next - slow = slow.next - if fast == slow: - break - - ans = head - while ans != slow: - ans, slow = ans.next, slow.next - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0143. \351\207\215\346\216\222\351\223\276\350\241\250.md" "b/Solutions/0143. \351\207\215\346\216\222\351\223\276\350\241\250.md" deleted file mode 100644 index daeb8e5a..00000000 --- "a/Solutions/0143. \351\207\215\346\216\222\351\223\276\350\241\250.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) - -- 标签:栈、递归、链表、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个单链表 `L` 的头节点 `head`,单链表 `L` 表示为:$L_0$ -> $L_1$ -> $L_2$ -> ... -> $L_{n-1}$ -> $L_n$。 - -**要求**:将单链表 `L` 重新排列为:$L_0$ -> $L_n$ -> $L_1$ -> $L_{n-1}$ -> $L_2$ -> $L_{n-2}$ -> $L_3$ -> $L_{n-3}$ -> ...。 - -**说明**: - -- 需要将实际节点进行交换。 - -**示例**: - -- 示例 1: - -![](https://pic.leetcode-cn.com/1626420311-PkUiGI-image.png) - -```python -输入:head = [1,2,3,4] -输出:[1,4,2,3] -``` - -- 示例 2: - -![](https://pic.leetcode-cn.com/1626420320-YUiulT-image.png) - -```python -输入:head = [1,2,3,4,5] -输出:[1,5,2,4,3] -``` - -## 解题思路 - -### 思路 1:线性表 - -因为链表无法像数组那样直接进行随机访问。所以我们可以先将链表转为线性表,然后直接按照提要要求的排列顺序访问对应数据元素,重新建立链表。 - -### 思路 1:代码 - -```python -class Solution: - def reorderList(self, head: ListNode) -> None: - """ - Do not return anything, modify head in-place instead. - """ - if not head: - return - - vec = [] - node = head - while node: - vec.append(node) - node = node.next - - left, right = 0, len(vec) - 1 - while left < right: - vec[left].next = vec[right] - left += 1 - if left == right: - break - vec[right].next = vec[left] - right -= 1 - vec[left].next = None -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0144. \344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0144. \344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index 3b63da6c..00000000 --- "a/Solutions/0144. \344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) - -- 标签:栈、树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:返回该二叉树的前序遍历结果。 - -**说明**: - -- 树中节点数目在范围 $[0, 100]$ 内。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) - -```python -输入:root = [1,null,2,3] -输出:[1,2,3] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/09/15/inorder_4.jpg) - -```python -输入:root = [1,null,2] -输出:[1,2] -``` - -## 解题思路 - -### 思路 1:递归遍历 - -二叉树的前序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先访问根节点。 -3. 然后递归遍历左子树。 -4. 最后递归遍历右子树。 - -### 思路 1:代码 - -```python -class Solution: - def preorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - - def preorder(root): - if not root: - return - res.append(root.val) - preorder(root.left) - preorder(root.right) - - preorder(root) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:模拟栈迭代遍历 - -二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 `stack` 来模拟递归的过程。 - -前序遍历的顺序为:根节点 - 左子树 - 右子树,而根据栈的「先入后出」特点,所以入栈的顺序应该为:先放入右子树,再放入左子树。这样可以保证最终为前序遍历顺序。 - -二叉树的前序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个栈,将根节点入栈。 -3. 当栈不为空时: - 1. 弹出栈顶元素 `node`,并访问该元素。 - 2. 如果 `node` 的右子树不为空,则将 `node` 的右子树入栈。 - 3. 如果 `node` 的左子树不为空,则将 `node` 的左子树入栈。 - -### 思路 2:代码 - -```python -class Solution: - def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - if not root: # 二叉树为空直接返回 - return [] - - res = [] - stack = [root] - - while stack: # 栈不为空 - node = stack.pop() # 弹出根节点 - res.append(node.val) # 访问根节点 - if node.right: - stack.append(node.right) # 右子树入栈 - if node.left: - stack.append(node.left) # 左子树入栈 - - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0145. \344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0145. \344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index e13c818b..00000000 --- "a/Solutions/0145. \344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,117 +0,0 @@ -# [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) - -- 标签:栈、树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:返回该二叉树的后序遍历结果。 - -**说明**: - -- 树中节点数目在范围 $[0, 100]$ 内。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2020/08/28/pre1.jpg) - -```python -输入:root = [1,null,2,3] -输出:[3,2,1] -``` - -- 示例 2: - -```python -输入:root = [] -输出:[] -``` - -## 解题思路 - -### 思路 1:递归遍历 - -二叉树的后序遍历递归实现步骤为: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 先递归遍历左子树。 -3. 然后递归遍历右子树。 -4. 最后访问根节点。 - -### 思路 1:代码 - -```python -class Solution: - def postorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - def postorder(root): - if not root: - return - postorder(root.left) - postorder(root.right) - res.append(root.val) - - postorder(root) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:模拟栈迭代遍历 - -我们可以使用一个显式栈 `stack` 来模拟二叉树的后序遍历递归的过程。 - -与前序、中序遍历不同,在后序遍历中,根节点的访问要放在左右子树访问之后。因此,我们要保证:**在左右孩子节点访问结束之前,当前节点不能提前出栈**。 - -我们应该从根节点开始,先将根节点放入栈中,然后依次遍历左子树,不断将当前子树的根节点放入栈中,直到遍历到左子树最左侧的那个节点,从栈中弹出该元素,并判断该元素的右子树是否已经访问完毕,如果访问完毕,则访问该元素。如果未访问完毕,则访问该元素的右子树。 - -二叉树的后序遍历显式栈实现步骤如下: - -1. 判断二叉树是否为空,为空则直接返回。 -2. 初始化维护一个空栈,使用 `prev` 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕。 -3. 当根节点或者栈不为空时,从当前节点开始: - 1. 如果当前节点有左子树,则不断遍历左子树,并将当前根节点压入栈中。 - 2. 如果当前节点无左子树,则弹出栈顶元素 `node`。 - 3. 如果栈顶元素 `node` 无右子树(即 `not node.right`)或者右子树已经访问完毕(即 `node.right == prev`),则访问该元素,然后记录前一节点,并将当前节点标记为空节点。 - 4. 如果栈顶元素有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子树。 - -### 思路 2:代码 - -```python -class Solution: - def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - res = [] - stack = [] - prev = None # 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕 - - while root or stack: # 根节点或栈不为空 - while root: - stack.append(root) # 将当前树的根节点入栈 - root = root.left # 继续访问左子树,找到最左侧节点 - - node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 - - # 如果当前节点无右子树或者右子树访问完毕 - if not node.right or node.right == prev: - res.append(node.val)# 访问该节点 - prev = node # 记录前一节点 - root = None # 将当前根节点标记为空 - else: - stack.append(node) # 右子树尚未访问完毕,将当前节点重新压回栈中 - root = node.right # 继续访问右子树 - - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0147. \345\257\271\351\223\276\350\241\250\350\277\233\350\241\214\346\217\222\345\205\245\346\216\222\345\272\217.md" "b/Solutions/0147. \345\257\271\351\223\276\350\241\250\350\277\233\350\241\214\346\217\222\345\205\245\346\216\222\345\272\217.md" deleted file mode 100644 index f0e3eb44..00000000 --- "a/Solutions/0147. \345\257\271\351\223\276\350\241\250\350\277\233\350\241\214\346\217\222\345\205\245\346\216\222\345\272\217.md" +++ /dev/null @@ -1,91 +0,0 @@ -# [0147. 对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) - -- 标签:链表、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定链表的头节点 `head`。 - -**要求**:对链表进行插入排序。 - -**说明**: - -- 插入排序算法: - - 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 - - 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 - - 重复直到所有输入数据插入完为止。 -- 列表中的节点数在 $[1, 5000]$ 范围内。 -- $-5000 \le Node.val \le 5000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/04/sort1linked-list.jpg) - -```python -输入: head = [4,2,1,3] -输出: [1,2,3,4] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/04/sort2linked-list.jpg) - -```python -输入: head = [-1,5,3,4,0] -输出: [-1,0,3,4,5] -``` - -## 解题思路 - -### 思路 1:链表插入排序 - -1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 - -2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 -3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 -4. 比较 `sorted_list` 和 `cur` 的节点值。 - - - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 - - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 - -5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 -6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 - -### 思路 1:代码 - -```python - - def insertionSortList(self, head: ListNode) -> ListNode: - if not head or not head.next: - return head - - dummy_head = ListNode(-1) - dummy_head.next = head - sorted_list = head - cur = head.next - - while cur: - if sorted_list.val <= cur.val: - # 将 cur 插入到 sorted_list 之后 - sorted_list = sorted_list.next - else: - prev = dummy_head - while prev.next.val <= cur.val: - prev = prev.next - # 将 cur 到链表中间 - sorted_list.next = cur.next - cur.next = prev.next - prev.next = cur - cur = sorted_list.next - - return dummy_head.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0148. \346\216\222\345\272\217\351\223\276\350\241\250.md" "b/Solutions/0148. \346\216\222\345\272\217\351\223\276\350\241\250.md" deleted file mode 100644 index 7d19dcb6..00000000 --- "a/Solutions/0148. \346\216\222\345\272\217\351\223\276\350\241\250.md" +++ /dev/null @@ -1,519 +0,0 @@ -# [0148. 排序链表](https://leetcode.cn/problems/sort-list/) - -- 标签:链表、双指针、分治、排序、归并排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定链表的头节点 `head`。 - -**要求**:按照升序排列并返回排序后的链表。 - -**说明**: - -- 链表中节点的数目在范围 $[0, 5 * 10^4]$ 内。 -- $-10^5 \le Node.val \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/09/14/sort_list_1.jpg) - -```python -输入:head = [4,2,1,3] -输出:[1,2,3,4] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/09/14/sort_list_2.jpg) - -```python -输入:head = [-1,5,3,4,0] -输出:[-1,0,3,4,5] -``` - -## 解题思路 - -### 思路 1:链表冒泡排序(超时) - -1. 使用三个指针 `node_i`、`node_j` 和 `tail`。其中 `node_i` 用于控制外循环次数,循环次数为链节点个数(链表长度)。`node_j` 和 `tail` 用于控制内循环次数和循环结束位置。 - -2. 排序开始前,将 `node_i` 、`node_j` 置于头节点位置。`tail` 指向链表末尾,即 `None`。 - -3. 比较链表中相邻两个元素 `node_j.val` 与 `node_j.next.val` 的值大小,如果 `node_j.val > node_j.next.val`,则值相互交换。否则不发生交换。然后向右移动 `node_j` 指针,直到 `node_j.next == tail` 时停止。 - -4. 一次循环之后,将 `tail` 移动到 `node_j` 所在位置。相当于 `tail` 向左移动了一位。此时 `tail` 节点右侧为链表中最大的链节点。 - -5. 然后移动 `node_i` 节点,并将 `node_j` 置于头节点位置。然后重复第 3、4 步操作。 -6. 直到 `node_i` 节点移动到链表末尾停止,排序结束。 -7. 返回链表的头节点 `head`。 - -### 思路 1:代码 - -```python -class Solution: - def bubbleSort(self, head: ListNode): - node_i = head - tail = None - # 外层循环次数为 链表节点个数 - while node_i: - node_j = head - while node_j and node_j.next != tail: - if node_j.val > node_j.next.val: - # 交换两个节点的值 - node_j.val, node_j.next.val = node_j.next.val, node_j.val - node_j = node_j.next - # 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表 - tail = node_j - node_i = node_i.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.bubbleSort(head) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:链表选择排序(超时) - -1. 使用两个指针 `node_i`、`node_j`。`node_i` 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。 -2. 使用 `min_node` 记录当前未排序链表中值最小的链节点。 -3. 每一趟排序开始时,先令 `min_node = node_i`(即暂时假设链表中 `node_i` 节点为值最小的节点,经过比较后再确定最小值节点位置)。 -4. 然后依次比较未排序链表中 `node_j.val` 与 `min_node.val` 的值大小。如果 `node_j.val < min_node.val`,则更新 `min_node` 为 `node_j`。 -5. 这一趟排序结束时,未排序链表中最小值节点为 `min_node`,如果 `node_i != min_node`,则将 `node_i` 与 `min_node` 值进行交换。如果 `node_i == min_node`,则不用交换。 -6. 排序结束后,继续向右移动 `node_i`,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 `node_i` 进行比较和交换,直到 `node_i == None` 或者 `node_i.next == None` 时,停止排序。 -7. 返回链表的头节点 `head`。 - -### 思路 2:代码 - -```python -class Solution: - def sectionSort(self, head: ListNode): - node_i = head - # node_i 为当前未排序链表的第一个链节点 - while node_i and node_i.next: - # min_node 为未排序链表中的值最小节点 - min_node = node_i - node_j = node_i.next - while node_j: - if node_j.val < min_node.val: - min_node = node_j - node_j = node_j.next - # 交换值最小节点与未排序链表中第一个节点的值 - if node_i != min_node: - node_i.val, min_node.val = min_node.val, node_i.val - node_i = node_i.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.sectionSort(head) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 3:链表插入排序(超时) - -1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 -2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 -3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 -4. 比较 `sorted_list` 和 `cur` 的节点值。 - - - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 - - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 - -5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 -6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 - -### 思路 3:代码 - -```python -class Solution: - def insertionSort(self, head: ListNode): - if not head or not head.next: - return head - - dummy_head = ListNode(-1) - dummy_head.next = head - sorted_list = head - cur = head.next - - while cur: - if sorted_list.val <= cur.val: - # 将 cur 插入到 sorted_list 之后 - sorted_list = sorted_list.next - else: - prev = dummy_head - while prev.next.val <= cur.val: - prev = prev.next - # 将 cur 到链表中间 - sorted_list.next = cur.next - cur.next = prev.next - prev.next = cur - cur = sorted_list.next - - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.insertionSort(head) -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 4:链表归并排序(通过) - -1. **分割环节**:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。 - 1. 使用快慢指针 `fast = head.next`、`slow = head`,让 `fast` 每次移动 `2` 步,`slow` 移动 `1` 步,移动到链表末尾,从而找到链表中心链节点,即 `slow`。 - 2. 从中心位置将链表从中心位置分为左右两个链表 `left_head` 和 `right_head`,并从中心位置将其断开,即 `slow.next = None`。 - 3. 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。 -2. **归并环节**:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。 - 1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `cur` 指向 `dummy_head` 用于遍历。 - 2. 比较两个链表头节点 `left` 和 `right` 的值大小。将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 - 3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 - 4. 将剩余链表插入到合并中的链表中。 - 5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后的头节点返回。 - -### 思路 4:代码 - -```python -class Solution: - def merge(self, left, right): - # 归并环节 - dummy_head = ListNode(-1) - cur = dummy_head - while left and right: - if left.val <= right.val: - cur.next = left - left = left.next - else: - cur.next = right - right = right.next - cur = cur.next - - if left: - cur.next = left - elif right: - cur.next = right - - return dummy_head.next - - def mergeSort(self, head: ListNode): - # 分割环节 - if not head or not head.next: - return head - - # 快慢指针找到中心链节点 - slow, fast = head, head.next - while fast and fast.next: - slow = slow.next - fast = fast.next.next - - # 断开左右链节点 - left_head, right_head = head, slow.next - slow.next = None - - # 归并操作 - return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.mergeSort(head) -``` - -### 思路 4:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 5:链表快速排序(超时) - -1. 从链表中找到一个基准值 `pivot`,这里以头节点为基准值。 -2. 然后通过快慢指针 `node_i`、`node_j` 在链表中移动,使得 `node_i` 之前的节点值都小于基准值,`node_i` 之后的节点值都大于基准值。从而把数组拆分为左右两个部分。 -3. 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。 - -> 注意: -> -> 虽然链表快速排序算法的平均时间复杂度为 $O(n \times \log_2n)$。但链表快速排序算法中基准值 `pivot` 的取值做不到数组快速排序算法中的随机选择。一旦给定序列是有序链表,时间复杂度就会退化到 $O(n^2)$。这也是这道题目使用链表快速排序容易超时的原因。 - -### 思路 5:代码 - -```python -class Solution: - def partition(self, left: ListNode, right: ListNode): - # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 - if left == right or left.next == right: - return left - # 选择头节点为基准节点 - pivot = left.val - # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 - node_i, node_j = left, left.next - - while node_j != right: - # 发现一个小与基准值的元素 - if node_j.val < pivot: - # 因为 node_i 之前节点都小于基准值,所以先将 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) - node_i = node_i.next - # 将小于基准值的元素 node_j 与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 - node_i.val, node_j.val = node_j.val, node_i.val - node_j = node_j.next - # 将基准节点放到正确位置上 - node_i.val, left.val = left.val, node_i.val - return node_i - - def quickSort(self, left: ListNode, right: ListNode): - if left == right or left.next == right: - return left - pi = self.partition(left, right) - self.quickSort(left, pi) - self.quickSort(pi.next, right) - return left - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - if not head or not head.next: - return head - return self.quickSort(head, None) -``` - -### 思路 5:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 6:链表计数排序(通过) - -1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 -2. 使用数组 `counts` 存储节点出现次数。 -3. 再次使用 `cur` 指针遍历一遍链表。将链表中每个值为 `cur.val` 的节点出现次数,存入数组对应第 `cur.val - list_min` 项中。 -4. 反向填充目标链表: - 1. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 - 2. 从小到大遍历一遍数组 `counts`。对于每个 `counts[i] != 0` 的元素建立一个链节点,值为 `i + list_min`,将其插入到 `cur.next` 上。并向右移动 `cur`。同时 `counts[i] -= 1`。直到 `counts[i] == 0` 后继续向后遍历数组 `counts`。 -5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 - -### 思路 6:代码 - -```python -class Solution: - def countingSort(self, head: ListNode): - if not head: - return head - - # 找出链表中最大值 list_max 和最小值 list_min - list_min, list_max = float('inf'), float('-inf') - cur = head - while cur: - if cur.val < list_min: - list_min = cur.val - if cur.val > list_max: - list_max = cur.val - cur = cur.next - - size = list_max - list_min + 1 - counts = [0 for _ in range(size)] - - cur = head - while cur: - counts[cur.val - list_min] += 1 - cur = cur.next - - dummy_head = ListNode(-1) - cur = dummy_head - for i in range(size): - while counts[i]: - cur.next = ListNode(i + list_min) - counts[i] -= 1 - cur = cur.next - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.countingSort(head) -``` - -### 思路 6:复杂度分析 - -- **时间复杂度**:$O(n + k)$,其中 $k$ 代表待排序链表中所有元素的值域。 -- **空间复杂度**:$O(k)$。 - -### 思路 7:链表桶排序(通过) - -1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 -2. 通过 `(最大值 - 最小值) / 每个桶的大小` 计算出桶的个数,即 `bucket_count = (list_max - list_min) // bucket_size + 1` 个桶。 -3. 定义数组 `buckets` 为桶,桶的个数为 `bucket_count` 个。 -4. 使用 `cur` 指针再次遍历一遍链表,将每个元素装入对应的桶中。 -5. 对每个桶内的元素单独排序,可以使用链表插入排序(超时)、链表归并排序(通过)、链表快速排序(超时)等算法。 -6. 最后按照顺序将桶内的元素拼成新的链表,并返回。 - -### 思路 7:代码 - -```python -class ListNode: - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -class Solution: - # 将链表节点值 val 添加到对应桶 buckets[index] 中 - def insertion(self, buckets, index, val): - if not buckets[index]: - buckets[index] = ListNode(val) - return - - node = ListNode(val) - node.next = buckets[index] - buckets[index] = node - - # 归并环节 - def merge(self, left, right): - dummy_head = ListNode(-1) - cur = dummy_head - while left and right: - if left.val <= right.val: - cur.next = left - left = left.next - else: - cur.next = right - right = right.next - cur = cur.next - - if left: - cur.next = left - elif right: - cur.next = right - - return dummy_head.next - - def mergeSort(self, head: ListNode): - # 分割环节 - if not head or not head.next: - return head - - # 快慢指针找到中心链节点 - slow, fast = head, head.next - while fast and fast.next: - slow = slow.next - fast = fast.next.next - - # 断开左右链节点 - left_head, right_head = head, slow.next - slow.next = None - - # 归并操作 - return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) - - def bucketSort(self, head: ListNode, bucket_size=5): - if not head: - return head - - # 找出链表中最大值 list_max 和最小值 list_min - list_min, list_max = float('inf'), float('-inf') - cur = head - while cur: - if cur.val < list_min: - list_min = cur.val - if cur.val > list_max: - list_max = cur.val - cur = cur.next - - # 计算桶的个数,并定义桶 - bucket_count = (list_max - list_min) // bucket_size + 1 - buckets = [[] for _ in range(bucket_count)] - - # 将链表节点值依次添加到对应桶中 - cur = head - while cur: - index = (cur.val - list_min) // bucket_size - self.insertion(buckets, index, cur.val) - cur = cur.next - - dummy_head = ListNode(-1) - cur = dummy_head - # 将元素依次出桶,并拼接成有序链表 - for bucket_head in buckets: - bucket_cur = self.mergeSort(bucket_head) - while bucket_cur: - cur.next = bucket_cur - cur = cur.next - bucket_cur = bucket_cur.next - - return dummy_head.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.bucketSort(head) -``` - -### 思路 7:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 - -### 思路 8:链表基数排序(解答错误,普通链表基数排序只适合非负数) - -1. 使用 `cur` 指针遍历链表,获取节点值位数最长的位数 `size`。 -2. 从个位到高位遍历位数。因为 `0` ~ `9` 共有 `10` 位数字,所以建立 `10` 个桶。 -3. 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。 -4. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 -5. 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。 -6. 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。 -7. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 - -### 思路 8:代码 - -```python -class Solution: - def radixSort(self, head: ListNode): - # 计算位数最长的位数 - size = 0 - cur = head - while cur: - val_len = len(str(cur.val)) - if val_len > size: - size = val_len - cur = cur.next - - # 从个位到高位遍历位数 - for i in range(size): - buckets = [[] for _ in range(10)] - cur = head - while cur: - # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中 - buckets[cur.val // (10 ** i) % 10].append(cur.val) - cur = cur.next - - # 生成新的链表 - dummy_head = ListNode(-1) - cur = dummy_head - for bucket in buckets: - for num in bucket: - cur.next = ListNode(num) - cur = cur.next - head = dummy_head.next - - return head - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.radixSort(head) -``` - -### 思路 8:复杂度分析 - -- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 -- **空间复杂度**:$O(n + k)$。 - -## 参考资料 - -- 【文章】[单链表的冒泡排序_zhao_miao的博客 - CSDN博客](https://blog.csdn.net/zhao_miao/article/details/81708454) -- 【文章】[链表排序总结(全)(C++)- 阿祭儿 - CSDN博客](https://blog.csdn.net/qq_32523711/article/details/107402873) -- 【题解】[快排、冒泡、选择排序实现列表排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/kuai-pai-mou-pao-xuan-ze-pai-xu-shi-xian-ula7/) -- 【题解】[归并排序+快速排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/gui-bing-pai-xu-kuai-su-pai-xu-by-datacruiser/) -- 【题解】[排序链表(递归+迭代)详解 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/pai-xu-lian-biao-di-gui-die-dai-xiang-jie-by-cherr/) -- 【题解】[Sort List (归并排序链表) - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) diff --git "a/Solutions/0149. \347\233\264\347\272\277\344\270\212\346\234\200\345\244\232\347\232\204\347\202\271\346\225\260.md" "b/Solutions/0149. \347\233\264\347\272\277\344\270\212\346\234\200\345\244\232\347\232\204\347\202\271\346\225\260.md" deleted file mode 100644 index 7c98ae64..00000000 --- "a/Solutions/0149. \347\233\264\347\272\277\344\270\212\346\234\200\345\244\232\347\232\204\347\202\271\346\225\260.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0149. 直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) - -- 标签:几何、数组、哈希表、数学 -- 难度:困难 - -## 题目大意 - -给定一个平面上的 n 个点的坐标数组 points,求解最多有多少个点在同一条直线上。 - -## 解题思路 - -两个点可以确定一条直线,固定其中一个点,求其他点与该点的斜率,斜率相同的点则在同一条直线上。可以考虑把斜率当做哈希表的键值,存储经过该点,不同斜率的直线上经过的点数目。 - -对于点 i,查找经过该点的直线只需要考虑 (i+1,n-1) 位置上的点即可,因为 i-1 之前的点已经在遍历点 i-2 的时候考虑过了。 - -斜率的计算公式为 $\frac{dy}{dx} = \frac{y_j - y_i}{x_j - x_i}$。 - -因为斜率是小数会有精度误差,所以我们考虑使用 (dx, dy) 的元组作为哈希表的 key。 - -> 注意: -> -> 需要处理倍数关系,dy、dx 异号情况,以及处理垂直直线(两点横坐标差为 0)的水平直线(两点横坐标差为 0)的情况。 - -## 代码 - -```python -class Solution: - def maxPoints(self, points: List[List[int]]) -> int: - n = len(points) - if n < 3: - return n - ans = 0 - for i in range(n): - line_dict = dict() - line_dict[0] = 0 - same = 1 - for j in range(i+1, n): - dx = points[j][0] - points[i][0] - dy = points[j][1] - points[i][1] - if dx == 0 and dy == 0: - same += 1 - continue - gcd_dx_dy = math.gcd(abs(dx), abs(dy)) - if (dx > 0 and dy > 0) or (dx < 0 and dy < 0): - dx = abs(dx) // gcd_dx_dy - dy = abs(dy) // gcd_dx_dy - elif dx < 0 and dy > 0: - dx = -dx // gcd_dx_dy - dy = -dy // gcd_dx_dy - elif dx > 0 and dy < 0: - dx = dx // gcd_dx_dy - dy = dy // gcd_dx_dy - elif dx == 0 and dy != 0: - dy = 1 - elif dx != 0 and dy == 0: - dx = 1 - key = (dx, dy) - if key in line_dict: - line_dict[key] += 1 - else: - line_dict[key] = 1 - ans = max(ans, same + max(line_dict.values())) - return ans -``` - diff --git "a/Solutions/0150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" "b/Solutions/0150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" deleted file mode 100644 index 43bc11ef..00000000 --- "a/Solutions/0150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) - -- 标签:栈、数组、数学 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串数组 `tokens`,表示「逆波兰表达式」。 - -**要求**:求解表达式的值。 - -**说明**: - -- **逆波兰表达式**:也称为后缀表达式。 - - 中缀表达式 `( 1 + 2 ) * ( 3 + 4 ) `,对应的逆波兰表达式为 ` ( ( 1 2 + ) ( 3 4 + ) * )` 。 - -- $1 \le tokens.length \le 10^4$。 -- `tokens[i]` 是一个算符(`+`、`-`、`*` 或 `/`),或是在范围 $[-200, 200]$ 内的一个整数。 - -**示例**: - -- 示例 1: - -```python -输入:tokens = ["4","13","5","/","+"] -输出:6 -解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 -``` - -- 示例 2: - -```python -输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] -输出:22 -解释:该算式转化为常见的中缀算术表达式为: - ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 -= ((10 * (6 / (12 * -11))) + 17) + 5 -= ((10 * (6 / -132)) + 17) + 5 -= ((10 * 0) + 17) + 5 -= (0 + 17) + 5 -= 17 + 5 -= 22 -``` - -## 解题思路 - -### 思路 1:栈 - -这道题是栈的典型应用。我们先来简单介绍一下逆波兰表达式。 - -逆波兰表达式,也叫做后缀表达式,特点是:没有括号,运算符总是放在和它相关的操作数之后。 -我们平常见到的表达式是中缀表达式,可写为:`A 运算符 B`。其中 `A`、`B` 都是操作数。 -而后缀表达式可写为:`A B 运算符`。 - -逆波兰表达式的计算遵循从左到右的规律。我们在计算逆波兰表达式的值时,可以使用一个栈来存放当前的操作数,从左到右依次遍历逆波兰表达式,计算出对应的值。具体操作步骤如下: - -1. 使用列表 `stack` 作为栈存放操作数,然后遍历表达式的字符串数组。 -2. 如果当前字符为运算符,则取出栈顶两个元素,在进行对应的运算之后,再将运算结果入栈。 -3. 如果当前字符为数字,则直接将数字入栈。 -4. 遍历结束后弹出栈中最后剩余的元素,这就是最终结果。 - -### 思路 1:代码 - -```python -class Solution: - def evalRPN(self, tokens: List[str]) -> int: - stack = [] - for token in tokens: - if token == '+': - stack.append(stack.pop() + stack.pop()) - elif token == '-': - stack.append(-stack.pop() + stack.pop()) - elif token == '*': - stack.append(stack.pop() * stack.pop()) - elif token == '/': - stack.append(int(1 / stack.pop() * stack.pop())) - else: - stack.append(int(token)) - return stack.pop() -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" "b/Solutions/0151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" deleted file mode 100644 index 7d34abfc..00000000 --- "a/Solutions/0151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) - -- 标签:双指针、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:反转字符串中所有单词的顺序。 - -**说明**: - -- **单词**:由非空格字符组成的字符串。`s` 中使用至少一个空格将字符串中的单词分隔开。 -- 输入字符串 `s`中可能会存在前导空格、尾随空格或者单词间的多个空格。 -- 返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 -- $1 \le s.length \le 10^4$。 -- `s` 包含英文大小写字母、数字和空格 `' '` -- `s` 中至少存在一个单词。 - -**示例**: - -- 示例 1: - -```python -输入:s = " hello world " -输出:"world hello" -解释:反转后的字符串中不能存在前导空格和尾随空格。 -``` - -- 示例 2: - -```python -输入:s = "a good example" -输出:"example good a" -解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。 -``` - -## 解题思路 - -### 思路 1:调用库函数 - -直接调用 Python 的库函数,对字符串进行切片,翻转,然后拼合成字符串。 - -### 思路 1:代码 - -```python -class Solution: - def reverseWords(self, s: str) -> str: - return " ".join(reversed(s.split())) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:模拟 - -第二种思路根据 API 的思路写出模拟代码,具体步骤如下: - -- 使用数组 `words` 存放单词,使用字符串变量 `cur` 存放当前单词。 -- 遍历字符串,对于当前字符 `ch`。 -- 如果遇到空格,则: - - 如果当前单词不为空,则将当前单词存入数组 `words` 中,并将当前单词置为空串 -- 如果遇到字符,则: - - 将其存入当前单词中,即 `cur += ch`。 -- 如果遍历完,当前单词不为空,则将当前单词存入数组 `words` 中。 -- 然后对数组 `words` 进行翻转操作,令 `words[i]`, `words[len(words) - 1 - i]` 交换元素。 -- 最后将 `words` 中的单词连接起来,中间拼接上空格,将其作为答案返回。 - -### 思路 2:代码 - -```python -class Solution: - def reverseWords(self, s: str) -> str: - words = [] - cur = "" - for ch in s: - if ch == ' ': - if cur: - words.append(cur) - cur = "" - else: - cur += ch - - if cur: - words.append(cur) - - for i in range(len(words) // 2): - words[i], words[len(words) - 1 - i] = words[len(words) - 1 - i], words[i] - - return " ".join(words) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0152. \344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0152. \344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index c99fcd93..00000000 --- "a/Solutions/0152. \344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,118 +0,0 @@ -# [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`。 - -**要求**:找出数组中乘积最大的连续子数组(最少包含一个数字),并返回该子数组对应的乘积。 - -**说明**: - -- 测试用例的答案是一个 32-位整数。 -- **子数组**:数组的连续子序列。 -- $1 \le nums.length \le 2 * 10^4$。 -- $-10 \le nums[i] \le 10$。 -- `nums` 的任何前缀或后缀的乘积都保证是一个 32-位整数。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [2,3,-2,4] -输出: 6 -解释: 子数组 [2,3] 有最大乘积 6。 -``` - -- 示例 2: - -```python -输入: nums = [-2,0,-1] -输出: 0 -解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题跟「[0053. 最大子序和](https://leetcode.cn/problems/maximum-subarray/)」有点相似,不过一个求的是和的最大值,这道题求解的是乘积的最大值。 - -乘积有个特殊情况,两个正数、两个负数相乘都会得到正数。所以求解的时候需要考虑负数的情况。 - -若想要最终的乘积最大,则应该使子数组中的正数元素尽可能的大,负数元素尽可能的小。所以我们可以维护一个最大值变量和最小值变量。 - -###### 1. 划分阶段 - -按照子数组的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp_max[i]` 为:以第 $i$ 个元素结尾的乘积最大子数组的乘积。 - -定义状态 `dp_min[i]` 为:以第 $i$ 个元素结尾的乘积最小子数组的乘积。 - -###### 3. 状态转移方程 - -- `dp_max[i] = max(dp_max[i - 1] * nums[i], nums[i], dp_min[i - 1] * nums[i])` -- `dp_min[i] = min(dp_min[i - 1] * nums[i], nums[i], dp_max[i - 1] * nums[i])` - -###### 4. 初始条件 - -- 以第 $0$ 个元素结尾的乘积最大子数组的乘积为 `nums[0]`,即 `dp_max[0] = nums[0]`。 -- 以第 $0$ 个元素结尾的乘积最小子数组的乘积为 `nums[0]`,即 `dp_min[0] = nums[0]`。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp_{max}$ 中最大值,即乘积最大子数组的乘积。 - -### 思路 1:代码 - -```python -class Solution: - def maxProduct(self, nums: List[int]) -> int: - size = len(nums) - dp_max = [0 for _ in range(size)] - dp_min = [0 for _ in range(size)] - dp_max[0] = nums[0] - dp_min[0] = nums[0] - ans = nums[0] - for i in range(1, size): - dp_max[i] = max(dp_max[i - 1] * nums[i], nums[i], dp_min[i - 1] * nums[i]) - dp_min[i] = min(dp_min[i - 1] * nums[i], nums[i], dp_max[i - 1] * nums[i]) - return max(dp_max) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为整数数组 `nums` 的元素个数。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:动态规划 + 滚动优化 - -因为状态转移方程中只涉及到当前元素和前一个元素,所以我们也可以不使用数组,只使用两个变量来维护 $dp_{max}[i]$ 和 $dp_{min}[i]$。 - -### 思路 2:代码 - -```python -class Solution: - def maxProduct(self, nums: List[int]) -> int: - size = len(nums) - max_num, min_num = nums[0], nums[0] - ans = nums[0] - for i in range(1, size): - temp_max = max_num - temp_min = min_num - max_num = max(temp_max * nums[i], nums[i], temp_min * nums[i]) - min_num = min(temp_min * nums[i], nums[i], temp_max * nums[i]) - ans = max(max_num, ans) - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为整数数组 `nums` 的元素个数。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" "b/Solutions/0153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" deleted file mode 100644 index e9f721d7..00000000 --- "a/Solutions/0153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $nums$,$nums$ 是有升序数组经过「旋转」得到的。但是旋转次数未知。数组中不存在重复元素。 - -**要求**:找出数组中的最小元素。 - -**说明**: - -- 旋转操作:将数组整体右移若干位置。 -- $n == nums.length$。 -- $1 \le n \le 5000$。 -- $-5000 \le nums[i] \le 5000$。 -- $nums$ 中的所有整数互不相同。 -- $nums$ 原来是一个升序排序的数组,并进行了 $1$ 至 $n$ 次旋转。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,4,5,1,2] -输出:1 -解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。 -``` - -- 示例 2: - -```python -输入:nums = [4,5,6,7,0,1,2] -输出:0 -解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 - -第一种的最小值在最左边。第二种最小值在第二段升序序列的第一个元素。 - -```python - * - * - * - * - * -* -``` - -```python - * - * -* - * - * - * -``` - -最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 - -创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较。 - -1. 如果 $nums[mid] > nums[right]$,则最小值不可能在 $mid$ 左侧,一定在 $mid$ 右侧,则将 $left$ 移动到 $mid + 1$ 位置,继续查找右侧区间。 -2. 如果 $nums[mid] \le nums[right]$,则最小值一定在 $mid$ 左侧,或者 $mid$ 位置,将 $right$ 移动到 $mid$ 位置上,继续查找左侧区间。 - -### 思路 1:代码 - -```python -class Solution: - def findMin(self, nums: List[int]) -> int: - left = 0 - right = len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - if nums[mid] > nums[right]: - left = mid + 1 - else: - right = mid - return nums[left] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II.md" "b/Solutions/0154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II.md" deleted file mode 100644 index 6802095a..00000000 --- "a/Solutions/0154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) - -- 标签:数组、二分查找 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个数组 $nums$,$nums$ 是有升序数组经过 $1 \sim n$ 次「旋转」得到的。但是旋转次数未知。数组中可能存在重复元素。 - -**要求**:找出数组中的最小元素。 - -**说明**: - -- 旋转:将数组整体右移 $1$ 位。数组 $[a[0], a[1], a[2], ..., a[n-1]]$ 旋转一次的结果为数组 $[a[n-1], a[0], a[1], a[2], ..., a[n-2]]$。 -- $n == nums.length$。 -- $1 \le n \le 5000$。 -- $-5000 \le nums[i] \le 5000$ -- $nums$ 原来是一个升序排序的数组,并进行了 $1 \sim n$ 次旋转。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,3,5] -输出:1 -``` - -- 示例 2: - -```python -输入:nums = [2,2,2,0,1] -输出:0 -``` - -## 解题思路 - -### 思路 1:二分查找 - -数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 - -第一种的最小值在最左边。 - -``` - * - * - * - * - * -* -``` - -第二种最小值在第二段升序序列的第一个元素。 - -``` - * - * -* - * - * - * -``` - -最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 - -创建两个指针 $left$、$right$,分别指向数组首尾。然后计算出两个指针中间值 $mid$。将 $mid$ 与右边界进行比较。 - -1. 如果 $nums[mid] > nums[right]$,则最小值不可能在 $mid$ 左侧,一定在 $mid$ 右侧,则将 $left$ 移动到 $mid + 1$ 位置,继续查找右侧区间。 -2. 如果 $nums[mid] < nums[right]$,则最小值一定在 $mid$ 左侧,令右边界 $right$ 为 $mid$,继续查找左侧区间。 -3. 如果 $nums[mid] == nums[right]$,无法判断在 $mid$ 的哪一侧,可以采用 `right = right - 1` 逐步缩小区域。 - -### 思路 1:代码 - -```python -class Solution: - def findMin(self, nums: List[int]) -> int: - left = 0 - right = len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - if nums[mid] > nums[right]: - left = mid + 1 - elif nums[mid] < nums[right]: - right = mid - else: - right = right - 1 - return nums[left] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0155. \346\234\200\345\260\217\346\240\210.md" "b/Solutions/0155. \346\234\200\345\260\217\346\240\210.md" deleted file mode 100644 index f48b7e99..00000000 --- "a/Solutions/0155. \346\234\200\345\260\217\346\240\210.md" +++ /dev/null @@ -1,133 +0,0 @@ -# [0155. 最小栈](https://leetcode.cn/problems/min-stack/) - -- 标签:栈、设计 -- 难度:中等 - -## 题目大意 - -**要求**:设计一个「栈」。实现 `push` ,`pop` ,`top` ,`getMin` 操作,其中 `getMin` 要求能在常数时间内实现。 - -**说明**: - -- $-2^{31} \le val \le 2^{31} - 1$。 -- `pop`、`top` 和 `getMin` 操作总是在非空栈上调用 -- `push`,`pop`,`top` 和 `getMin` 最多被调用 $3 * 10^4$ 次。 - -**示例**: - -- 示例 1: - -```python -输入: -["MinStack","push","push","push","getMin","pop","top","getMin"] -[[],[-2],[0],[-3],[],[],[],[]] - -输出: -[null,null,null,null,-3,null,0,-2] - -解释: -MinStack minStack = new MinStack(); -minStack.push(-2); -minStack.push(0); -minStack.push(-3); -minStack.getMin(); --> 返回 -3. -minStack.pop(); -minStack.top(); --> 返回 0. -minStack.getMin(); --> 返回 -2. -``` - -## 解题思路 - -题目要求在常数时间内获取最小值,所以我们不能在 `getMin` 操作时,再去计算栈中的最小值。而是应该在 `push`、`pop` 操作时就已经计算好了最小值。我们有两种思路来解决这道题。 - -### 思路 1:辅助栈 - -使用辅助栈保存当前栈中的最小值。在元素入栈出栈时,两个栈同步保持插入和删除。具体做法如下: - -- `push` 操作:当一个元素入栈时,取辅助栈的栈顶存储的最小值,与当前元素进行比较得出最小值,将最小值插入到辅助栈中;该元素也插入到正常栈中。 -- `pop` 操作:当一个元素要出栈时,将辅助栈的栈顶元素一起弹出。 -- `top` 操作:返回正常栈的栈顶元素值。 -- `getMin` 操作:返回辅助栈的栈顶元素值。 - -### 思路 1:代码 - -```python -class MinStack: - - def __init__(self): - self.stack = [] - self.minstack = [] - - def push(self, val: int) -> None: - if not self.stack: - self.stack.append(val) - self.minstack.append(val) - else: - self.stack.append(val) - self.minstack.append(min(val, self.minstack[-1])) - - def pop(self) -> None: - self.stack.pop() - self.minstack.pop() - - def top(self) -> int: - return self.stack[-1] - - def getMin(self) -> int: - return self.minstack[-1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。栈的插入、删除、读取操作都是 $O(1)$。 -- **空间复杂度**:$O(n)$。其中 $n$ 为总操作数。 - -### 思路 2:单个栈 - -使用单个栈,保存元组:(当前元素值,当前栈内最小值)。具体操作如下: - -- `push` 操作:如果栈不为空,则判断当前元素值与栈顶元素所保存的最小值,并更新当前最小值,然后将新元素和当前最小值组成的元组保存到栈中。 -- `pop`操作:正常出栈,即将栈顶元素弹出。 -- `top` 操作:返回栈顶元素保存的值。 -- `getMin` 操作:返回栈顶元素保存的最小值。 - -### 思路 2:代码 - -```python -class MinStack: - def __init__(self): - """ - initialize your data structure here. - """ - self.stack = [] - - class Node: - def __init__(self, x): - self.val = x - self.min = x - - def push(self, val: int) -> None: - node = self.Node(val) - if len(self.stack) == 0: - self.stack.append(node) - else: - topNode = self.stack[-1] - if node.min > topNode.min: - node.min = topNode.min - - self.stack.append(node) - - def pop(self) -> None: - self.stack.pop() - - def top(self) -> int: - return self.stack[-1].val - - def getMin(self) -> int: - return self.stack[-1].min -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(1)$。栈的插入、删除、读取操作都是 $O(1)$。 -- **空间复杂度**:$O(n)$。其中 $n$ 为总操作数。 \ No newline at end of file diff --git "a/Solutions/0159. \350\207\263\345\244\232\345\214\205\345\220\253\344\270\244\344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" "b/Solutions/0159. \350\207\263\345\244\232\345\214\205\345\220\253\344\270\244\344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" deleted file mode 100644 index 7116e678..00000000 --- "a/Solutions/0159. \350\207\263\345\244\232\345\214\205\345\220\253\344\270\244\344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0159. 至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 s,找出之多包含两个不同字符的最长子串 t,并返回该子串的长度。 - -## 解题思路 - -使用滑动窗口来求解。 - -left,right 指向字符串开始位置。 - -不断向右移动 right 指针,使用 count 变量来统计滑动窗口中共有多少个字符,以及使用哈希表来统计当前字符的频数。 - -当滑动窗口的字符多于 2 个时,向右 移动 left 指针,并减少哈希表中对应原 left 指向字符的频数。 - -最后使用 max_count 来维护最长子串 t 的长度。 - -## 代码 - -```python -import collections -class Solution: - def lengthOfLongestSubstringTwoDistinct(self, s: str) -> int: - max_count = 0 - k = 2 - counts = collections.defaultdict(int) - count = 0 - left, right = 0, 0 - while right < len(s): - if counts[s[right]] == 0: - count += 1 - counts[s[right]] += 1 - right += 1 - if count > k: - if counts[s[left]] == 1: - count -= 1 - counts[s[left]] -= 1 - left += 1 - max_count = max(max_count, right - left) - return max_count -``` - diff --git "a/Solutions/0160. \347\233\270\344\272\244\351\223\276\350\241\250.md" "b/Solutions/0160. \347\233\270\344\272\244\351\223\276\350\241\250.md" deleted file mode 100644 index ae0f6ec0..00000000 --- "a/Solutions/0160. \347\233\270\344\272\244\351\223\276\350\241\250.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) - -- 标签:哈希表、链表、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定 `listA`、`listB` 两个链表。 - -**要求**:判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 `None`。 - -**说明**: - -- `listA` 中节点数目为 $m$。 -- `listB` 中节点数目为 $n$。 -- $1 \le m, n \le 3 * 10^4$。 -- $1 \le Node.val \le 10^5$。 -- $0 \le skipA \le m$。 -- $0 \le skipB \le n$。 -- 如果 `listA` 和 `listB` 没有交点,`intersectVal` 为 $0$。 -- 如果 `listA` 和 `listB` 有交点,`intersectVal == listA[skipA] == listB[skipB]`。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) - -```python -输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 -输出:Intersected at '8' -解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 -从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 -在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 -— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/05/160_example_2.png) - -```python -输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 -输出:Intersected at '2' -解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 -从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。 -在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 -``` - -## 解题思路 - -### 思路 1:双指针 - -如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 `listA` 的长度为 $m$、链表 `listB` 的长度为 $n$,他们的相交序列有 $k$ 个,则相交情况可以如下如所示: - -![](https://qcdn.itcharge.cn/images/20210401113538.png) - -现在问题是如何找到 $m - k$ 或者 $n - k$ 的位置。 - -考虑将链表 `listA` 的末尾拼接上链表 `listB`,链表 `listB` 的末尾拼接上链表 `listA`。 - -然后使用两个指针 `pA` 、`pB`,分别从链表 `listA`、链表 `listB` 的头节点开始遍历,如果走到共同的节点,则返回该节点。 - -否则走到两个链表末尾,返回 `None`。 - -![](https://qcdn.itcharge.cn/images/20210401114100.png) - -### 思路 1:代码 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: - if headA == None or headB == None: - return None - pA = headA - pB = headB - while pA != pB: - pA = pA.next if pA != None else headB - pB = pB.next if pB != None else headA - return pA -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0162. \345\257\273\346\211\276\345\263\260\345\200\274.md" "b/Solutions/0162. \345\257\273\346\211\276\345\263\260\345\200\274.md" deleted file mode 100644 index c88b66da..00000000 --- "a/Solutions/0162. \345\257\273\346\211\276\345\263\260\345\200\274.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`。 - -**要求**:找到峰值元素并返回其索引。必须实现时间复杂度为 $O(\log n)$ 的算法来解决此问题。 - -**说明**: - -- **峰值元素**:指其值严格大于左右相邻值的元素。 -- 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 -- 可以假设 $nums[-1] = nums[n] = -∞$。 -- $1 \le nums.length \le 1000$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- 对于所有有效的 $i$ 都有 $nums[i] != nums[i + 1]$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,1] -输出:2 -解释:3 是峰值元素,你的函数应该返回其索引 2。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,1,3,5,6,4] -输出:1 或 5 -解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -1. 使用两个指针 `left`、`right` 。`left` 指向数组第一个元素,`right` 指向数组最后一个元素。 -2. 取区间中间节点 `mid`,并比较 `nums[mid]` 和 `nums[mid + 1]` 的值大小。 - 1. 如果 `nums[mid]` 小于 `nums[mid + 1]`,则右侧存在峰值,令 `left = mid + 1`。 - 2. 如果 `nums[mid]` 大于等于 `nums[mid + 1]`,则左侧存在峰值,令 `right = mid`。 -3. 最后,当 `left == right` 时,跳出循环,返回 `left`。 - -### 思路 1:代码 - -```python -class Solution: - def findPeakElement(self, nums: List[int]) -> int: - left = 0 - right = len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - if nums[mid] < nums[mid + 1]: - left = mid + 1 - else: - right = mid - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log_2 n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0164. \346\234\200\345\244\247\351\227\264\350\267\235.md" "b/Solutions/0164. \346\234\200\345\244\247\351\227\264\350\267\235.md" deleted file mode 100644 index 821eab59..00000000 --- "a/Solutions/0164. \346\234\200\345\244\247\351\227\264\350\267\235.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) - -- 标签:数组、桶排序、基数排序、排序 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个无序数组 $nums$。 - -**要求**:找出数组在排序之后,相邻元素之间最大的差值。如果数组元素个数小于 $2$,则返回 $0$。 - -**说明**: - -- 所有元素都是非负整数,且数值在 $32$ 位有符号整数范围内。 -- 请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [3,6,9,1] -输出: 3 -解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。 -``` - -- 示例 2: - -```python -输入: nums = [10] -输出: 0 -解释: 数组元素个数小于 2,因此返回 0。 -``` - -## 解题思路 - -### 思路 1:基数排序 - -这道题的难点在于要求时间复杂度和空间复杂度为 $O(n)$。 - -这道题分为两步: - -1. 数组排序。 -2. 计算相邻元素之间的差值。 - -第 2 步直接遍历数组求解即可,时间复杂度为 $O(n)$。所以关键点在于找到一个时间复杂度和空间复杂度为 $O(n)$ 的排序算法。根据题意可知所有元素都是非负整数,且数值在 32 位有符号整数范围内。所以我们可以选择基数排序。基数排序的步骤如下: - -- 遍历数组元素,获取数组最大值元素,并取得位数。 -- 以个位元素为索引,对数组元素排序。 -- 合并数组。 -- 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,进行排序,并合并数组,最终完成排序。 - -最后,还要注意数组元素个数小于 $2$ 的情况需要特别判断一下。 - -### 思路 1:代码 - -```python -class Solution: - def radixSort(self, arr): - size = len(str(max(arr))) - - for i in range(size): - buckets = [[] for _ in range(10)] - for num in arr: - buckets[num // (10 ** i) % 10].append(num) - arr.clear() - for bucket in buckets: - for num in bucket: - arr.append(num) - - return arr - - def maximumGap(self, nums: List[int]) -> int: - if len(nums) < 2: - return 0 - arr = self.radixSort(nums) - return max(arr[i] - arr[i - 1] for i in range(1, len(arr))) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0166. \345\210\206\346\225\260\345\210\260\345\260\217\346\225\260.md" "b/Solutions/0166. \345\210\206\346\225\260\345\210\260\345\260\217\346\225\260.md" deleted file mode 100644 index f6bcd122..00000000 --- "a/Solutions/0166. \345\210\206\346\225\260\345\210\260\345\260\217\346\225\260.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0166. 分数到小数](https://leetcode.cn/problems/fraction-to-recurring-decimal/) - -- 标签:哈希表、数学、字符串 -- 难度:中等 - -## 题目大意 - -给定两个整数,分别表示分数的分子 numerator 和分母 denominator,要求以字符串的形式返回该分数对应小数结果。 - -- 如果小数部分为循环小数,则将循环的小数部分括在括号内。 - -## 解题思路 - -先处理特殊数据,例如 0、负数等。 - -然后利用整除运算,计算出分数的整数部分。在根据取余运算结果,判断是否含有小数部分。 - -因为小数部分可能会有循环部分,所以使用哈希表来判断是否出现了循环小数。哈希表所存键值为 数字:数字开始位置。 - -然后计算小数部分,每次将被除数 * 10 然后对除数进行整除,再对被除数进行取余操作,直到被除数变为 0,或者在字典中出现了循环小数为止。 - -## 代码 - -```python -class Solution: - def fractionToDecimal(self, numerator: int, denominator: int) -> str: - if numerator == 0: - return '0' - res = [] - if numerator ^ denominator < 0: - res.append('-') - numerator, denominator = abs(numerator), abs(denominator) - res.append(str(numerator // denominator)) - numerator %= denominator - if numerator == 0: - return ''.join(res) - res.append('.') - - record = dict() - while numerator: - if numerator not in record: - record[numerator] = len(res) - numerator *= 10 - res.append(str(numerator // denominator)) - numerator %= denominator - else: - res.insert(record[numerator], '(') - res.append(')') - break - return ''.join(res) -``` - diff --git "a/Solutions/0167. \344\270\244\346\225\260\344\271\213\345\222\214 II - \350\276\223\345\205\245\346\234\211\345\272\217\346\225\260\347\273\204.md" "b/Solutions/0167. \344\270\244\346\225\260\344\271\213\345\222\214 II - \350\276\223\345\205\245\346\234\211\345\272\217\346\225\260\347\273\204.md" deleted file mode 100644 index 920bb750..00000000 --- "a/Solutions/0167. \344\270\244\346\225\260\344\271\213\345\222\214 II - \350\276\223\345\205\245\346\234\211\345\272\217\346\225\260\347\273\204.md" +++ /dev/null @@ -1,126 +0,0 @@ -# [0167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) - -- 标签:数组、双指针、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个下标从 $1$ 开始计数、升序排列的整数数组:$numbers$ 和一个目标值 $target$。 - -**要求**:从数组中找出满足相加之和等于 $target$ 的两个数,并返回两个数在数组中下的标值。 - -**说明**: - -- $2 \le numbers.length \le 3 \times 10^4$。 -- $-1000 \le numbers[i] \le 1000$。 -- $numbers$ 按非递减顺序排列。 -- $-1000 \le target \le 1000$。 -- 仅存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:numbers = [2,7,11,15], target = 9 -输出:[1,2] -解释:2 与 7 之和等于目标数 9。因此 index1 = 1, index2 = 2。返回 [1, 2]。 -``` - -- 示例 2: - -```python -输入:numbers = [2,3,4], target = 6 -输出:[1,3] -解释:2 与 4 之和等于目标数 6。因此 index1 = 1, index2 = 3。返回 [1, 3]。 -``` - -## 解题思路 - -这道题如果暴力遍历数组,从中找到相加之和等于 $target$ 的两个数,时间复杂度为 $O(n^2)$,可以尝试一下。 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - size = len(numbers) - for i in range(size): - for j in range(i + 1, size): - if numbers[i] + numbers[j] == target: - return [i + 1, j + 1] - return [-1, -1] -``` - -结果不出意外的超时了。所以我们要想办法降低时间复杂度。 - -### 思路 1:二分查找 - -因为数组是有序的,可以考虑使用二分查找来减少时间复杂度。具体做法如下: - -1. 使用一重循环遍历数组,先固定第一个数,即 $numsbers[i]$。 -2. 然后使用二分查找的方法寻找符合要求的第二个数。 -3. 使用两个指针 $left$,$right$。$left$ 指向数组第一个数的下一个数,$right$ 指向数组值最大元素位置。 -4. 判断第一个数 $numsbers[i]$ 和两个指针中间元素 $numbers[mid]$ 的和与目标值的关系。 - 1. 如果 $numbers[mid] + numbers[i] < target$,排除掉不可能区间 $[left, mid]$,在 $[mid + 1, right]$ 中继续搜索。 - 2. 如果 $numbers[mid] + numbers[i] \ge target$,则第二个数可能在 $[left, mid]$ 中,则在 $[left, mid]$ 中继续搜索。 -5. 直到 $left$ 和 $right$ 移动到相同位置停止检测。如果 $numbers[left] + numbers[i] == target$,则返回两个元素位置 $[left + 1, i + 1]$(下标从 $1$ 开始计数)。 -6. 如果最终仍没找到,则返回 $[-1, -1]$。 - -### 思路 1:代码 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - for i in range(len(numbers)): - left, right = i + 1, len(numbers) - 1 - while left < right: - mid = left + (right - left) // 2 - if numbers[mid] + numbers[i] < target: - left = mid + 1 - else: - right = mid - if numbers[left] + numbers[i] == target: - return [i + 1, left + 1] - - return [-1, -1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:对撞指针 - -可以考虑使用对撞指针来减少时间复杂度。具体做法如下: - -1. 使用两个指针 $left$,$right$。$left$ 指向数组第一个值最小的元素位置,$right$ 指向数组值最大元素位置。 -2. 判断两个位置上的元素的和与目标值的关系。 - 1. 如果元素和等于目标值,则返回两个元素位置。 - 2. 如果元素和大于目标值,则让 $right$ 左移,继续检测。 - 3. 如果元素和小于目标值,则让 $left$ 右移,继续检测。 -3. 直到 $left$ 和 $right$ 移动到相同位置停止检测。 -4. 如果最终仍没找到,则返回 $[-1, -1]$。 - -### 思路 2:代码 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - left = 0 - right = len(numbers) - 1 - while left < right: - total = numbers[left] + numbers[right] - if total == target: - return [left + 1, right + 1] - elif total < target: - left += 1 - else: - right -= 1 - return [-1, -1] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0168. Excel\350\241\250\345\210\227\345\220\215\347\247\260.md" "b/Solutions/0168. Excel\350\241\250\345\210\227\345\220\215\347\247\260.md" deleted file mode 100644 index ed896e7b..00000000 --- "a/Solutions/0168. Excel\350\241\250\345\210\227\345\220\215\347\247\260.md" +++ /dev/null @@ -1,30 +0,0 @@ -# [0168. Excel表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) - -- 标签:数学、字符串 -- 难度:简单 - -## 题目大意 - -描述:给定一个正整数 columnNumber。 - -要求:返回它在 Excel 表中相对应的列名称。 - -1 -> A,2 -> B,3 -> C,…,26 -> Z,…,28 -> AB - -## 解题思路 - -实质上就是 10 进制转 26 进制。不过映射范围是 1~26,而不是 0~25,如果将 columnNumber 直接对 26 取余,则结果为 0~25,而本题余数为 1~26。可以直接将 columnNumber = columnNumber - 1,这样就可以将范围变为 0~25 就更加容易判断了。 - -## 代码 - -```python -class Solution: - def convertToTitle(self, columnNumber: int) -> str: - s = "" - while columnNumber: - columnNumber -= 1 - s = chr(65 + columnNumber % 26) + s - columnNumber //= 26 - return s -``` - diff --git "a/Solutions/0169. \345\244\232\346\225\260\345\205\203\347\264\240.md" "b/Solutions/0169. \345\244\232\346\225\260\345\205\203\347\264\240.md" deleted file mode 100644 index b636f82a..00000000 --- "a/Solutions/0169. \345\244\232\346\225\260\345\205\203\347\264\240.md" +++ /dev/null @@ -1,114 +0,0 @@ -# [0169. 多数元素](https://leetcode.cn/problems/majority-element/) - -- 标签:数组、哈希表、分治、计数、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个大小为 $n$ 的数组 `nums`。 - -**要求**:返回其中相同元素个数最多的元素。 - -**说明**: - -- $n == nums.length$。 -- $1 \le n \le 5 * 10^4$。 -- $-10^9 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,2,3] -输出:3 -``` - -- 示例 2: - -```python -输入:nums = [2,2,1,1,1,2,2] -输出:2 -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 遍历数组 `nums`。 -2. 对于当前元素 `num`,用哈希表统计每个元素 `num` 出现的次数。 -3. 再遍历一遍哈希表,找出元素个数最多的元素即可。 - -### 思路 1:代码 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - numDict = dict() - for num in nums: - if num in numDict: - numDict[num] += 1 - else: - numDict[num] = 1 - max = float('-inf') - max_index = -1 - for num in numDict: - if numDict[num] > max: - max = numDict[num] - max_index = num - return max_index -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:分治算法 - -如果 `num` 是数组 `nums` 的众数,那么我们将 `nums` 分为两部分,则 `num` 至少是其中一部分的众数。 - -则我们可以用分治法来解决这个问题。具体步骤如下: - -1. 将数组 `nums` 递归地将当前序列平均分成左右两个数组,直到所有子数组长度为 `1`。 -2. 长度为 $1$ 的子数组众数肯定是数组中唯一的数,将其返回即可。 -3. 将两个子数组依次向上两两合并。 - 1. 如果两个子数组的众数相同,则说明合并后的数组众数为:两个子数组的众数。 - 2. 如果两个子数组的众数不同,则需要比较两个众数在整个区间的众数。 - -4. 最后返回整个数组的众数。 - -### 思路 2:代码 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - def get_mode(low, high): - if low == high: - return nums[low] - - mid = low + (high - low) // 2 - left_mod = get_mode(low, mid) - right_mod = get_mode(mid + 1, high) - - if left_mod == right_mod: - return left_mod - - left_mod_cnt, right_mod_cnt = 0, 0 - for i in range(low, high + 1): - if nums[i] == left_mod: - left_mod_cnt += 1 - if nums[i] == right_mod: - right_mod_cnt += 1 - - if left_mod_cnt > right_mod_cnt: - return left_mod - return right_mod - - return get_mode(0, len(nums) - 1) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(\log n)$。 \ No newline at end of file diff --git "a/Solutions/0170. \344\270\244\346\225\260\344\271\213\345\222\214 III - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" "b/Solutions/0170. \344\270\244\346\225\260\344\271\213\345\222\214 III - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" deleted file mode 100644 index d2f1e4d9..00000000 --- "a/Solutions/0170. \344\270\244\346\225\260\344\271\213\345\222\214 III - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [0170. 两数之和 III - 数据结构设计](https://leetcode.cn/problems/two-sum-iii-data-structure-design/) - -- 标签:设计、数组、哈希表、双指针、数据流 -- 难度:简单 - -## 题目大意 - -设计一个接受整数流的数据结构,使该数据结构支持检查是否存在两数之和等于特定值。 - -实现 TwoSum 类: - -- `TwoSum()`:使用空数组初始化 TwoSum 对象 -- `def add(self, number: int) -> None:`向数据结构添加一个数 number -- `def find(self, value: int) -> bool:`寻找数据结构中是否存在一对整数,使得两数之和与给定的值 value 相等。如果存在,返回 True ;否则,返回 False 。 - -## 解题思路 - -使用哈希表存储数组元素值与元素频数的关系。哈希表中键值对信息为 number: count。count 为 number 在数组中的频数。 - -- `add(number)` 函数中:在哈希表添加 number 与其频数之间的关系。 -- `find(number)` 函数中:遍历哈希表,对于每个 number,检测哈希表中是否存在 value - number,如果存在则终止循环并返回结果。 - - 如果 `number == value - number`,则判断哈希表中 number 的数目是否大于等于 2。 - -## 代码 - -```python -class TwoSum: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.num_counts = dict() - - - def add(self, number: int) -> None: - """ - Add the number to an internal data structure.. - """ - if number in self.num_counts: - self.num_counts[number] += 1 - else: - self.num_counts[number] = 1 - - - def find(self, value: int) -> bool: - """ - Find if there exists any pair of numbers which sum is equal to the value. - """ - for number in self.num_counts.keys(): - number2 = value - number - if number == number2: - if self.num_counts[number] > 1: - return True - else: - if number2 in self.num_counts: - return True - return False -``` diff --git "a/Solutions/0171. Excel \350\241\250\345\210\227\345\272\217\345\217\267.md" "b/Solutions/0171. Excel \350\241\250\345\210\227\345\272\217\345\217\267.md" deleted file mode 100644 index 8cacceb2..00000000 --- "a/Solutions/0171. Excel \350\241\250\345\210\227\345\272\217\345\217\267.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0171. Excel 表列序号](https://leetcode.cn/problems/excel-sheet-column-number/) - -- 标签:数学、字符串 -- 难度:简单 - -## 题目大意 - -给你一个字符串 `columnTitle` ,表示 Excel 表格中的列名称。 - -要求:返回该列名称对应的列序号。 - -## 解题思路 - -Excel 表的列名称由大写字母组成,共有 26 个,因此列名称的表示实质是 26 进制,需要将 26 进制转换成十进制。转换过程如下: - -- 将每一位对应列名称转换成整数(注意列序号从 `1` 开始)。 -- 将当前结果乘上进制数(`26`),然后累加上当前位上的整数。 - -最后输出答案。 - -## 代码 - -```python -class Solution: - def titleToNumber(self, columnTitle: str) -> int: - ans = 0 - for ch in columnTitle: - num = ord(ch) - ord('A') + 1 - ans = ans * 26 + num - return ans -``` - diff --git "a/Solutions/0172. \351\230\266\344\271\230\345\220\216\347\232\204\351\233\266.md" "b/Solutions/0172. \351\230\266\344\271\230\345\220\216\347\232\204\351\233\266.md" deleted file mode 100644 index 1febd45b..00000000 --- "a/Solutions/0172. \351\230\266\344\271\230\345\220\216\347\232\204\351\233\266.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [0172. 阶乘后的零](https://leetcode.cn/problems/factorial-trailing-zeroes/) - -- 标签:数学 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:返回 `n!` 结果中尾随零的数量。 - -注意:$0 <= n <= 10^4$ - -## 解题思路 - -阶乘中,末尾 `0` 的来源只有 `2 * 5`。所以尾随 `0` 的个数为 `2` 的倍数个数和 `5` 的倍数个数的最小值。又因为 `2 < 5`,`2` 的倍数个数肯定小于等于 `5` 的倍数,所以直接统计 `5` 的倍数个数即可。 - -## 代码 - -```python -class Solution: - def trailingZeroes(self, n: int) -> int: - count = 0 - while n > 0: - count += n // 5 - n = n // 5 - return count -``` - diff --git "a/Solutions/0173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" "b/Solutions/0173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" deleted file mode 100644 index 3007767c..00000000 --- "a/Solutions/0173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0173. 二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) - -- 标签:栈、树、设计、二叉搜索树、二叉树、迭代器 -- 难度:中等 - -## 题目大意 - -实现一个二叉搜索树的迭代器 BSTIterator。表示一个按中序遍历二叉搜索树(BST)的迭代器: - -- `def __init__(self, root: TreeNode):`:初始化 BSTIterator 类的一个对象,会给出二叉搜索树的根节点。 -- `def hasNext(self) -> bool:`:如果向右指针遍历存在数字,则返回 True,否则返回 False。 -- `def next(self) -> int:`:将指针向右移动,返回指针处的数字。 - -## 解题思路 - -中序遍历的顺序是:左、根、右。我们使用一个栈来保存节点,以便于迭代的时候取出对应节点。 - -- 初始的遍历当前节点的左子树,将其路径上的节点存储到栈中。 -- 调用 next 方法的时候,从栈顶取出节点,因为之前已经将路径上的左子树全部存入了栈中,所以此时该节点的左子树为空,这时候取出节点右子树,再将右子树的左子树进行递归遍历,并将其路径上的节点存储到栈中。 -- 调用 hasNext 的方法的时候,直接判断栈中是否有值即可。 - -## 代码 - -```python -class BSTIterator: - - def __init__(self, root: TreeNode): - self.stack = [] - self.in_order(root) - - def in_order(self, node): - while node: - self.stack.append(node) - node = node.left - - def next(self) -> int: - node = self.stack.pop() - if node.right: - self.in_order(node.right) - return node.val - - def hasNext(self) -> bool: - return len(self.stack) != 0 -``` - diff --git "a/Solutions/0179. \346\234\200\345\244\247\346\225\260.md" "b/Solutions/0179. \346\234\200\345\244\247\346\225\260.md" deleted file mode 100644 index f2681d25..00000000 --- "a/Solutions/0179. \346\234\200\345\244\247\346\225\260.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [0179. 最大数](https://leetcode.cn/problems/largest-number/) - -- 标签:贪心、数组、字符串、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个非负整数数组 `nums`。 - -**要求**:重新排列数组中每个数的顺序,使之将数组中所有数字按顺序拼接起来所组成的整数最大。 - -**说明**: - -- $1 \le nums.length \le 100$。 -- $0 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [10,2] -输出:"210" -``` - -- 示例 2: - -```python -输入:nums = [3,30,34,5,9] -输出:"9534330" -``` - -## 解题思路 - -### 思路 1:排序 - -本质上是给数组进行排序。假设 `x`、`y` 是数组 `nums` 中的两个元素。如果拼接字符串 `x + y < y + x`,则 `y > x `。`y` 应该排在 `x` 前面。反之,则 `y < x`。 - -按照上述规则,对原数组进行排序即可。这里我们使用了 `functools.cmp_to_key` 自定义排序函数。 - -### 思路 1:代码 - -```python -import functools - -class Solution: - def largestNumber(self, nums: List[int]) -> str: - def cmp(a, b): - if a + b == b + a: - return 0 - elif a + b > b + a: - return 1 - else: - return -1 - nums_s = list(map(str, nums)) - nums_s.sort(key=functools.cmp_to_key(cmp), reverse=True) - return str(int(''.join(nums_s))) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。其中 $n$ 是给定数组 `nums` 的大小。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0188. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 IV.md" "b/Solutions/0188. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 IV.md" deleted file mode 100644 index 0dec06fa..00000000 --- "a/Solutions/0188. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 IV.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -给定一个数组 `prices` 代表一只股票,其中 `prices[i]` 代表这只股票第 `i` 天的价格。再给定一个整数 `k`,表示最多可完成 `k` 笔交易,且不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。 - -现在要求:计算所能获取的最大利润。 - -## 解题思路 - -动态规划求解。这道题是「[0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/)」的升级版,不过思路一样 - -最多可完成两笔交易意味着总共有三种情况:买卖一次,买卖两次,不买卖。 - -具体到每一天结束总共有 `2 * k + 1` 种状态: - -0. 未进行买卖状态; -1. 第 `1` 次买入状态; -2. 第 `1` 次卖出状态; -3. 第 `2` 次买入状态; -4. 第 `2` 次卖出状态。 -5. ... -6. 第 `m` 次买入状态。 -7. 第 `m` 次卖出状态。 - -因为买入、卖出为两种状态,干脆我们直接让偶数序号表示买入状态,奇数序号表示卖出状态。 - -所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 2 * k`)下,所获取的最大利润。 - -注意:这里第 `j` 种情况,并不一定是这一天一定要买入或卖出,而是这一天所处于的买入卖出状态。比如说前一天是第一次买入,第二天没有操作,则第二天就沿用前一天的第一次买入状态。 - -接下来确定状态转移公式: - -- 第 `0` 种状态下显然利润为 `0`,可以直接赋值为昨天获取的最大利润,即 `dp[i][0] = dp[i - 1][0]`。 -- 第 `1` 次买入状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][1] = dp[i - 1][1]`。 - - 第 `1` 次买入:`dp[i][1] = dp[i - 1][0] - prices[i]`。 -- 第 `1` 次卖出状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][2] = dp[i - 1][2]`。 - - 第 `1` 次卖出:`dp[i][2] = dp[i - 1][1] + prices[i]`。 -- 第 `2` 次买入状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][3] = dp[i - 1][3]`。 - - 第 `2` 次买入:`dp[i][3] = dp[i - 1][2] - prices[i]`。 -- 第 `2` 次卖出状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][4] = dp[i - 1][4]`。 - - 第 `2` 次卖出:`dp[i][4] = dp[i - 1][3] + prices[i]`。 -- ... -- 第 `m` 次(`j = 2 * m`)买入状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][j] = dp[i - 1][j]`。 - - 第 `m` 次买入:`dp[i][j] = dp[i - 1][j - 1] - prices[i]`。 -- 第 `m` 次(`j = 2 * m + 1`)卖出状态下可以有两种状态推出,取最大的那一种赋值: - - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][j] = dp[i - 1][j]`。 - - 第 `m` 次卖出:`dp[i][j] = dp[i - 1][j - 1] + prices[i]`。 - -下面确定初始化的边界值: - -可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第 `m` 次买入(`j = 2 * m`)就是 `dp[0][j] = -prices[i]`。 - -第 `m` 次(`j = 2 * m + 1`)卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][j] = 0`。 - -在递推结束后,最大利润肯定是无操作、第 `m` 次卖出这几种种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第 `m` 次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为 `m - 1` 笔交易,那么在转移状态时,我们允许在一天内进行多次交易,则 `m - 1` 笔交易的状态可以转移至 `m` 笔交易,最终都可以转移至 `k` 比交易。 - -所以最终答案为 `dp[size - 1][2 * k]`。`size` 为股票天数。 - -## 代码 - -```python -class Solution: - def maxProfit(self, k: int, prices: List[int]) -> int: - size = len(prices) - if size == 0: - return 0 - - dp = [[0 for _ in range(2 * k + 1)] for _ in range(size)] - - for j in range(1, 2 * k, 2): - dp[0][j] = -prices[0] - - for i in range(1, size): - for j in range(1, 2 * k + 1): - if j % 2 == 1: - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]) - else: - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]) - return dp[size - 1][2 * k] -``` - diff --git "a/Solutions/0189. \350\275\256\350\275\254\346\225\260\347\273\204.md" "b/Solutions/0189. \350\275\256\350\275\254\346\225\260\347\273\204.md" deleted file mode 100644 index a9d8f2c4..00000000 --- "a/Solutions/0189. \350\275\256\350\275\254\346\225\260\347\273\204.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0189. 轮转数组](https://leetcode.cn/problems/rotate-array/) - -- 标签:数组、数学、双指针 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $nums$,再给定一个数字 $k$。 - -**要求**:将数组中的元素向右移动 $k$ 个位置。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- $0 \le k \le 10^5$。 -- 使用空间复杂度为 $O(1)$ 的原地算法解决这个问题。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [1,2,3,4,5,6,7], k = 3 -输出: [5,6,7,1,2,3,4] -解释: -向右轮转 1 步: [7,1,2,3,4,5,6] -向右轮转 2 步: [6,7,1,2,3,4,5] -向右轮转 3 步: [5,6,7,1,2,3,4] -``` - -- 示例 2: - -```py -输入:nums = [-1,-100,3,99], k = 2 -输出:[3,99,-1,-100] -解释: -向右轮转 1 步: [99,-1,-100,3] -向右轮转 2 步: [3,99,-1,-100] -``` - -## 解题思路 - -### 思路 1: 数组翻转 - -可以用一个新数组,先保存原数组的后 $k$ 个元素,再保存原数组的前 $n - k$ 个元素。但题目要求不使用额外的数组空间,那么就需要在原数组上做操作。 - -我们可以先把整个数组翻转一下,这样后半段元素就到了前边,前半段元素就到了后边,只不过元素顺序是反着的。我们再从 $k$ 位置分隔开,将 $[0...k - 1]$ 区间上的元素和 $[k...n - 1]$ 区间上的元素再翻转一下,就得到了最终结果。 - -具体步骤: - -1. 将数组 $[0, n - 1]$ 位置上的元素全部翻转。 -2. 将数组 $[0, k - 1]$ 位置上的元素进行翻转。 -3. 将数组 $[k, n - 1]$ 位置上的元素进行翻转。 - -### 思路 1:代码 - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - n = len(nums) - k = k % n - self.reverse(nums, 0, n-1) - self.reverse(nums, 0, k-1) - self.reverse(nums, k, n-1) - def reverse(self, nums: List[int], left: int, right: int) -> None: - while left < right : - tmp = nums[left] - nums[left] = nums[right] - nums[right] = tmp - left += 1 - right -= 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。翻转的时间复杂度为 $O(n)$ 。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.md" "b/Solutions/0190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.md" deleted file mode 100644 index fe7dc599..00000000 --- "a/Solutions/0190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [0190. 颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) - -- 标签:位运算、分治 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个 $32$ 位无符号整数 $n$。 - -**要求**:将 $n$ 所有二进位进行翻转,并返回翻转后的整数。 - -**说明**: - -- 输入是一个长度为 $32$ 的二进制字符串。 - -**示例**: - -- 示例 1: - -```python -输入:n = 00000010100101000001111010011100 -输出:964176192 (00111001011110000010100101000000) -解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, - 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 -``` - -- 示例 2: - -```python -输入:n = 11111111111111111111111111111101 -输出:3221225471 (10111111111111111111111111111111) -解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293, - 因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111。 -``` - -## 解题思路 - -### 思路 1:逐位翻转 - -1. 用一个变量 $res$ 存储翻转后的结果。 -2. 将 $n$ 不断进行右移(即 `n >> 1`),从低位到高位进行枚举,此时 $n$ 的最低位就是我们枚举的二进位。 -3. 同时 $res$ 不断左移(即 `res << 1`),并将当前枚举的二进位翻转后的结果(即 `n & 1`)拼接到 $res$ 的末尾(即 `(res << 1) | (n & 1)`)。 - -### 思路 1:代码 - -```python -class Solution: - def reverseBits(self, n: int) -> int: - res = 0 - for i in range(32): - res = (res << 1) | (n & 1) - n >>= 1 - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0191. \344\275\2151\347\232\204\344\270\252\346\225\260.md" "b/Solutions/0191. \344\275\2151\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index 18a712af..00000000 --- "a/Solutions/0191. \344\275\2151\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0191. 位1的个数](https://leetcode.cn/problems/number-of-1-bits/) - -- 标签:位运算、分治 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个无符号整数 $n$。 - -**要求**:统计其对应二进制表达式中 $1$ 的个数。 - -**说明**: - -- 输入必须是长度为 $32$ 的二进制串。 - -**示例**: - -- 示例 1: - -```python -输入:n = 00000000000000000000000000001011 -输出:3 -解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 -``` - -- 示例 2: - -```python -输入:n = 00000000000000000000000010000000 -输出:1 -解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 -``` - -## 解题思路 - -### 思路 1:循环按位计算 - -1. 对整数 $n$ 的每一位进行按位与运算,并统计结果。 - -### 思路 1:代码 - -```python -class Solution: - def hammingWeight(self, n: int) -> int: - ans = 0 - while n: - ans += (n & 1) - n = n >> 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(k)$,其中 $k$ 是二进位的位数,$k = 32$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:改进位运算 - -利用 `n & (n - 1)`。这个运算刚好可以将 $n$ 的二进制中最低位的 $1$ 变为 $0$。 - -比如 $n = 6$ 时,$6 = 110_{(2)}$,$6 - 1 = 101_{(2)}$,`110 & 101 = 100`。 - -利用这个位运算,不断的将 $n$ 中最低位的 $1$ 变为 $0$,直到 $n$ 变为 $0$ 即可,其变换次数就是我们要求的结果。 - -### 思路 2:代码 - -```python -class Solution: - def hammingWeight(self, n: int) -> int: - ans = 0 - while n: - n = n & (n - 1) - ans += 1 - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" "b/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" deleted file mode 100644 index 097a58b3..00000000 --- "a/Solutions/0198. \346\211\223\345\256\266\345\212\253\350\210\215.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $nums$,$nums[i]$ 代表第 $i$ 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 - -**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 - -**说明**: - -- $1 \le nums.length \le 100$。 -- $0 \le nums[i] \le 400$。 - -**示例**: - -- 示例 1: - -```python -输入:[1,2,3,1] -输出:4 -解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 - 偷窃到的最高金额 = 1 + 3 = 4。 -``` - -- 示例 2: - -```python -输入:[2,7,9,3,1] -输出:12 -解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 - 偷窃到的最高金额 = 2 + 9 + 1 = 12。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照房屋序号进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。 - -###### 3. 状态转移方程 - -$i$ 间房屋的最后一个房子是 $nums[i - 1]$。 - -如果房屋数大于等于 $2$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态: - -1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$; -1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$。 - -然后这两种状态取最大值即可,即状态转移方程为: - -$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$ - -###### 4. 初始条件 - -- 前 $0$ 间房屋所能偷窃到的最高金额为 $0$,即 $dp[0] = 0$。 -- 前 $1$ 间房屋所能偷窃到的最高金额为 $nums[0]$,即:$dp[1] = nums[0]$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。则最终结果为 $dp[size]$,$size$ 为总的房屋数。 - -### 思路 1:代码 - -```python -class Solution: - def rob(self, nums: List[int]) -> int: - size = len(nums) - if size == 0: - return 0 - - dp = [0 for _ in range(size + 1)] - dp[0] = 0 - dp[1] = nums[0] - - for i in range(2, size + 1): - dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1]) - - return dp[size] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0199. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.md" "b/Solutions/0199. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.md" deleted file mode 100644 index 9f3714cd..00000000 --- "a/Solutions/0199. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一棵二叉树的根节点 `root`。 - -**要求**:按照从顶部到底部的顺序,返回从右侧能看到的节点值。 - -**说明**: - -- 二叉树的节点个数的范围是 $[0,100]$。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/14/tree.jpg) - -```python -输入: [1,2,3,null,5,null,4] -输出: [1,3,4] -``` - -- 示例 2: - -```python -输入: [1,null,3] -输出: [1,3] -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -使用广度优先搜索对二叉树进行层次遍历。在遍历每层节点的时候,只需要将最后一个节点加入结果数组即可。 - -### 思路 1:代码 - -```python -class Solution: - def rightSideView(self, root: TreeNode) -> List[int]: - if not root: - return [] - queue = [root] - order = [] - while queue: - size = len(queue) - for i in range(size): - curr = queue.pop(0) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if i == size - 1: - order.append(curr.val) - return order -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - - - diff --git "a/Solutions/0200. \345\262\233\345\261\277\346\225\260\351\207\217.md" "b/Solutions/0200. \345\262\233\345\261\277\346\225\260\351\207\217.md" deleted file mode 100644 index 22ac0e8a..00000000 --- "a/Solutions/0200. \345\262\233\345\261\277\346\225\260\351\207\217.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由字符 `'1'`(陆地)和字符 `'0'`(水)组成的的二维网格 $grid$。 - -**要求**:计算网格中岛屿的数量。 - -**说明**: - -- 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 -- 此外,你可以假设该网格的四条边均被水包围。 -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 300$。 -- $grid[i][j]$ 的值为 `'0'` 或 `'1'`。 - -**示例**: - -- 示例 1: - -```python -输入:grid = [ - ["1","1","1","1","0"], - ["1","1","0","1","0"], - ["1","1","0","0","0"], - ["0","0","0","0","0"] -] -输出:1 -``` - -- 示例 2: - -```python -输入:grid = [ - ["1","1","0","0","0"], - ["1","1","0","0","0"], - ["0","0","1","0","0"], - ["0","0","0","1","1"] -] -输出:3 -``` - -## 解题思路 - -如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。 - -使用深度优先搜索或者广度优先搜索都可以。 - -### 思路 1:深度优先搜索 - -1. 遍历 $grid$。 -2. 对于每一个字符为 `'1'` 的元素,遍历其上下左右四个方向,并将该字符置为 `'0'`,保证下次不会被重复遍历。 -3. 如果超出边界,则返回 $0$。 -4. 对于 $(i, j)$ 位置的元素来说,递归遍历的位置就是 $(i - 1, j)$、$(i, j - 1)$、$(i + 1, j)$、$(i, j + 1)$ 四个方向。每次遍历到底,统计数记录一次。 -5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。 - -### 思路 1:代码 - -```python -class Solution: - def dfs(self, grid, i, j): - n = len(grid) - m = len(grid[0]) - if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0': - return 0 - grid[i][j] = '0' - self.dfs(grid, i + 1, j) - self.dfs(grid, i, j + 1) - self.dfs(grid, i - 1, j) - self.dfs(grid, i, j - 1) - - def numIslands(self, grid: List[List[str]]) -> int: - count = 0 - for i in range(len(grid)): - for j in range(len(grid[0])): - if grid[i][j] == '1': - self.dfs(grid, i, j) - count += 1 - return count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0201. \346\225\260\345\255\227\350\214\203\345\233\264\346\214\211\344\275\215\344\270\216.md" "b/Solutions/0201. \346\225\260\345\255\227\350\214\203\345\233\264\346\214\211\344\275\215\344\270\216.md" deleted file mode 100644 index fbccef57..00000000 --- "a/Solutions/0201. \346\225\260\345\255\227\350\214\203\345\233\264\346\214\211\344\275\215\344\270\216.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0201. 数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) - -- 标签:位运算 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数 $left$ 和 $right$,表示区间 $[left, right]$。 - -**要求**:返回此区间内所有数字按位与的结果(包含 $left$、$right$ 端点)。 - -**说明**: - -- $0 \le left \le right \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:left = 5, right = 7 -输出:4 -``` - -- 示例 2: - -```python -输入:left = 1, right = 2147483647 -输出:0 -``` - -## 解题思路 - -### 思路 1:位运算 - -很容易想到枚举算法:对于区间 $[left, right]$,如果使用枚举算法,对区间范围内的数依次进行按位与操作,最后输出结果。 - -但是枚举算法在区间范围很大的时候会超时,所以我们应该换个思路来解决这道题。 - -我们知道与运算的规则如下: - -- `0 & 0 == 0` -- `0 & 1 == 0` -- `1 & 0 == 0` -- `1 & 1 == 1`。 - -只有对应位置上都为 $1$ 的情况下,按位与才能得到 $1$。而对应位置上只要出现 $0$,则该位置上最终的按位与结果一定为 $0$。 - -那么我们可以先来求一下区间所有数对应二进制的公共前缀,假设这个前缀的长度为 $x$。 - -公共前缀部分因为每个位置上的二进制值完全一样,所以按位与的结果也相同。 - -接下来考虑除了公共前缀的剩余的二进制位部分。 - -这时候剩余部分有两种情况: - -- $x = 31$。则 $left == right$,其按位与结果就是 $left$ 本身。 -- $0 \le x < 31$。这种情况下因为 $left < right$,所以 $left$ 的第 $x + 1$ 位必然为 $0$,$right$ 的第 $x + 1$ 位必然为 $1$。 - - 注意:$left$、$right$ 第 $x + 1$ 位上不可能同为 $0$ 或 $1$,这样就是公共前缀了。 - - 注意:同样不可能是 $left$ 第 $x + 1$ 位为 $1$,$right$ 第 $x + 1$ 位为 $0$,这样就是 $left > right$ 了。 - -而从第 $x + 1$ 位起,从 $left$ 到 $right$。肯定会经过 $10000...$ 的位置,从而使得除了公共前缀的剩余部分(后面的 $31 - x$ 位)的按位与结果一定为 $0$。 - -举个例子,$x = 27$,则除了公共前缀的剩余部分长度为 $4$。则剩余部分从 $0XXX$ 到 $1XXX$ 必然会经过 $1000$,则剩余部分的按位与结果为 $0000$。 - -那么这道题就转变为了求 $[left, right]$ 区间范围内所有数的二进制公共前缀,然后在后缀位置上补上 $0$。 - -求解公共前缀,我们借助于 Brian Kernigham 算法中的 `n & (n - 1)` 公式来计算。 - -- `n & (n - 1)` 公式:对 $n$ 和 $n - 1$ 进行按位与运算后,$n$ 最右边的 $1$ 会变成 $0$,也就是清除了 $n$ 对应二进制的最右侧的 $1$。比如 $n = 10110100_{(2)}$,进行 `n & (n - 1)` 操作之后,就变为了 $n = 10110000_{(2)}$。 - -具体计算步骤如下: - -1. 对于给定的区间范围 $[left, right]$,对 $right$ 进行 `right & (right - 1)` 迭代。 -2. 直到 $right$ 小于等于 $left$,此时区间内非公共前缀的 $1$ 均变为了 $0$。 -3. 最后输出 $right$ 作为答案。 - -### 思路 1:位运算代码 - -```python -class Solution: - def rangeBitwiseAnd(self, left: int, right: int) -> int: - while left < right: - right = right & (right - 1) - return right -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- 【题解】[巨好理解的位运算思路 - 数字范围按位与 - 力扣](https://leetcode.cn/problems/bitwise-and-of-numbers-range/solution/ju-hao-li-jie-de-wei-yun-suan-si-lu-by-time-limit/) diff --git "a/Solutions/0202. \345\277\253\344\271\220\346\225\260.md" "b/Solutions/0202. \345\277\253\344\271\220\346\225\260.md" deleted file mode 100644 index fdebc689..00000000 --- "a/Solutions/0202. \345\277\253\344\271\220\346\225\260.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0202. 快乐数](https://leetcode.cn/problems/happy-number/) - -- 标签:哈希表、数学、双指针 -- 难度:简单 - -## 题目大意 - -给定一个整数 n,判断 n 是否为快乐数。 - -快乐数定义: - -- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 -- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 -- 如果 可以变为 1,那么这个数就是快乐数。 - -## 解题思路 - -根据题意,不断重复操作,数可能变为 1,也可能是无限循环。无限循环其实就相当于链表形成了闭环,可以用哈希表来存储为一位生成的数,每次判断该数是否存在于哈希表中。如果已经出现在哈希表里,则说明进入了无限循环,该数就不是快乐数。如果没有出现则将该数加入到哈希表中,进行下一次计算。不断重复这个过程,直到形成闭环或者变为 1 。 - -## 代码 - -```python -class Solution: - def getNext(self, n: int): - total_sum = 0 - while n > 0: - n, digit = divmod(n, 10) - total_sum += digit ** 2 - return total_sum - - - def isHappy(self, n: int) -> bool: - num_set = set() - while n != 1 and n not in num_set: - num_set.add(n) - n = self.getNext(n) - return n == 1 -``` - diff --git "a/Solutions/0203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.md" "b/Solutions/0203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.md" deleted file mode 100644 index e5f1fafb..00000000 --- "a/Solutions/0203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head` 和一个值 `val`。 - -**要求**:删除链表中值为 `val` 的节点,并返回新的链表头节点。 - -**说明**: - -- 列表中的节点数目在范围 $[0, 10^4]$ 内。 -- $1 \le Node.val \le 50$。 -- $0 \le val \le 50$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/06/removelinked-list.jpg) - -```python -输入:head = [1,2,6,3,4,5,6], val = 6 -输出:[1,2,3,4,5] -``` - -- 示例 2: - -```python -输入:head = [], val = 1 -输出:[] -``` - -## 解题思路 - -### 思路 1:迭代 - -- 使用两个指针 `prev` 和 `curr`。`prev` 指向前一节点和当前节点,`curr` 指向当前节点。 -- 从前向后遍历链表,遇到值为 `val` 的节点时,将 `prev` 的 `next` 指针指向当前节点的下一个节点,继续递归遍历。没有遇到则将 `prev` 指针向后移动一步。 -- 向右移动 `curr`,继续遍历。 - -需要注意的是:因为要删除的节点可能包含了头节点,我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则直接删除原头节点,然后最后返回新建头节点的下一个节点即可。 - -### 思路 1:代码 - -```python -class Solution: - def removeElements(self, head: ListNode, val: int) -> ListNode: - newHead = ListNode(0, head) - newHead.next = head - - prev, curr = newHead, head - while curr: - if curr.val == val: - prev.next = curr.next - else: - prev = curr - curr = curr.next - return newHead.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0204. \350\256\241\346\225\260\350\264\250\346\225\260.md" "b/Solutions/0204. \350\256\241\346\225\260\350\264\250\346\225\260.md" deleted file mode 100644 index 115fb64f..00000000 --- "a/Solutions/0204. \350\256\241\346\225\260\350\264\250\346\225\260.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0204. 计数质数](https://leetcode.cn/problems/count-primes/) - -- 标签:数组、数学、枚举、数论 -- 难度:中等 - -## 题目大意 - -**描述**:给定 一个非负整数 $n$。 - -**要求**:统计小于 $n$ 的质数数量。 - -**说明**: - -- $0 \le n \le 5 * 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入 n = 10 -输出 4 -解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:0 -``` - -## 解题思路 - -### 思路 1:枚举算法(超时) - -对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整数的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 - -这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。 - -在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终返回该数目作为答案。 - -考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。 - -利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n})$,检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。 - -### 思路 1:代码 - -```python -class Solution: - def isPrime(self, x): - for i in range(2, int(pow(x, 0.5)) + 1): - if x % i == 0: - return False - return True - - def countPrimes(self, n: int) -> int: - cnt = 0 - for x in range(2, n): - if self.isPrime(x): - cnt += 1 - return cnt -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \sqrt{n})$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:埃氏筛法 - -可以用「埃氏筛」进行求解。这种方法是由古希腊数学家埃拉托斯尼斯提出的,具体步骤如下: - -- 使用长度为 $n$ 的数组 `is_prime` 来判断一个数是否是质数。如果 `is_prime[i] == True` ,则表示 $i$ 是质数,如果 `is_prime[i] == False`,则表示 $i$ 不是质数。并使用变量 `count` 标记质数个数。 -- 然后从 $[2, n - 1]$ 的第一个质数(即数字 $2$) 开始,令 `count` 加 $1$,并将该质数在 $[2, n - 1]$ 范围内所有倍数(即 $4$、$6$、$8$、...)都标记为非质数。 -- 然后根据数组 `is_prime` 中的信息,找到下一个没有标记为非质数的质数(即数字 $3$),令 `count` 加 $1$,然后将该质数在 $[2, n - 1]$ 范围内的所有倍数(即 $6$、$9$、$12$、…)都标记为非质数。 -- 以此类推,直到所有小于或等于 $n - 1$ 的质数和质数的倍数都标记完毕时,输出 `count`。 - -优化:对于一个质数 $x$,我们可以直接从 $x \times x$ 开始标记,这是因为 $2 \times x$、$3 \times x$、… 这些数已经在 $x$ 之前就被其他数的倍数标记过了,例如 $2$ 的所有倍数、$3$ 的所有倍数等等。 - -### 思路 2:代码 - -```python -class Solution: - def countPrimes(self, n: int) -> int: - is_prime = [True] * n - count = 0 - for i in range(2, n): - if is_prime[i]: - count += 1 - for j in range(i * i, n, i): - is_prime[j] = False - return count -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2{log_2n})$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0205. \345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0205. \345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 649fc21a..00000000 --- "a/Solutions/0205. \345\220\214\346\236\204\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [0205. 同构字符串](https://leetcode.cn/problems/isomorphic-strings/) - -- 标签:哈希表、字符串 -- 难度:简单 - -## 题目大意 - -给定两个字符串 s 和 t,判断两者是否是同构字符串。 - -如果 s 中的字符可以按某种映射关系替换得到 t 相同位置上的字符,那么两个字符串是同构的。 - -每个字符都应当映射到另一个字符,且不改变字符顺序。不同字符不能映射到统一字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。 - -## 解题思路 - -根据题目意思,s 和 t 每个位置上的字符是一一对应的。s 的每个字符都与 t 对应位置上的字符对应。可以考虑用哈希表来存储 s[i]: t[i] 的对应关系。但是这样不能只能保证对应位置上的字符是对应的,但不能保证是唯一对应的。所以还需要另一个哈希表来存储 t[i]:s[i] 的对应关系来判断是否是唯一对应的。 - -## 代码 - -```python -class Solution: - def isIsomorphic(self, s: str, t: str) -> bool: - s_dict = dict() - t_dict = dict() - for i in range(len(s)): - if s[i] in s_dict and s_dict[s[i]] != t[i]: - return False - if t[i] in t_dict and t_dict[t[i]] != s[i]: - return False - s_dict[s[i]] = t[i] - t_dict[t[i]] = s[i] - return True -``` - diff --git "a/Solutions/0206. \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/Solutions/0206. \345\217\215\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index aba16e38..00000000 --- "a/Solutions/0206. \345\217\215\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,104 +0,0 @@ -# [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个单链表的头节点 `head`。 - -**要求**:将该单链表进行反转。可以迭代或递归地反转链表。 - -**说明**: - -- 链表中节点的数目范围是 $[0, 5000]$。 -- $-5000 \le Node.val \le 5000$。 - -**示例**: - -- 示例 1: - -```python -输入:head = [1,2,3,4,5] -输出:[5,4,3,2,1] -解释: -翻转前 1->2->3->4->5->NULL -反转后 5->4->3->2->1->NULL -``` - -## 解题思路 - -### 思路 1:迭代 - -1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 - -2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: - 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; - 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; - 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; - 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 -3. 继续执行第 2 步中的 1、2、3、4。 -4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 - -使用迭代法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111133639.png) - -### 思路 1:代码 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - pre = None - cur = head - while cur != None: - next = cur.next - cur.next = pre - pre = cur - cur = next - return pre -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:递归 - -具体做法如下: - -1. 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 -2. 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 -3. 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 -4. 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 -5. 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 -6. 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 -7. 最后返回反转后的链表头节点 `new_head`。 - -使用递归法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111134246.png) - -### 思路 2:代码 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - if head == None or head.next == None: - return head - new_head = self.reverseList(head.next) - head.next.next = head - head.next = None - return new_head -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$ -- **空间复杂度**:$O(n)$。最多需要 $n$ 层栈空间。 - -## 参考资料 - -- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) -- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) diff --git "a/Solutions/0207. \350\257\276\347\250\213\350\241\250.md" "b/Solutions/0207. \350\257\276\347\250\213\350\241\250.md" deleted file mode 100644 index aa0c035f..00000000 --- "a/Solutions/0207. \350\257\276\347\250\213\350\241\250.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0207. 课程表](https://leetcode.cn/problems/course-schedule/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 - -**要求**:判断是否可能完成所有课程的学习。如果可以,返回 `True`,否则,返回 `False`。 - -**说明**: - -- $1 \le numCourses \le 10^5$。 -- $0 \le prerequisites.length \le 5000$。 -- $prerequisites[i].length == 2$。 -- $0 \le ai, bi < numCourses$。 -- $prerequisites[i]$ 中所有课程对互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:numCourses = 2, prerequisites = [[1,0]] -输出:true -解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。这是可能的。 -``` - -- 示例 2: - -```python -输入:numCourses = 2, prerequisites = [[1,0],[0,1]] -输出:false -解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。 -``` - -## 解题思路 - -### 思路 1:拓扑排序 - -1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 -2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 -3. 从队列中选择一个节点 $u$,并令课程数减 $1$。 -4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 -5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 -6. 最后判断剩余课程数是否为 $0$,如果为 $0$,则返回 `True`,否则,返回 `False`。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - def topologicalSorting(self, numCourses, graph): - indegrees = {u: 0 for u in graph} - for u in graph: - for v in graph[u]: - indegrees[v] += 1 - - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - - while S: - u = S.pop() - numCourses -= 1 - for v in graph[u]: - indegrees[v] -= 1 - if indegrees[v] == 0: - S.append(v) - - if numCourses == 0: - return True - return False - - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - graph = dict() - for i in range(numCourses): - graph[i] = [] - - for v, u in prerequisites: - graph[u].append(v) - - return self.topologicalSorting(numCourses, graph) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 -- **空间复杂度**:$O(n + m)$。 - diff --git "a/Solutions/0208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221).md" "b/Solutions/0208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221).md" deleted file mode 100644 index 3275a58f..00000000 --- "a/Solutions/0208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221).md" +++ /dev/null @@ -1,109 +0,0 @@ -# [0208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**要求**:实现前缀树数据结构的相关类 `Trie` 类。 - -`Trie` 类: - -- `Trie()` 初始化前缀树对象。 -- `void insert(String word)` 向前缀树中插入字符串 `word`。 -- `boolean search(String word)` 如果字符串 `word` 在前缀树中,返回 `True`(即,在检索之前已经插入);否则,返回 `False`。 -- `boolean startsWith(String prefix)` 如果之前已经插入的字符串 `word` 的前缀之一为 `prefix`,返回 `True`;否则,返回 `False`。 - -**说明**: - -- $1 \le word.length, prefix.length \le 2000$。 -- `word` 和 `prefix` 仅由小写英文字母组成。 -- `insert`、`search` 和 `startsWith` 调用次数 **总计** 不超过 $3 * 10^4$ 次。 - -**示例**: - -- 示例 1: - -```python -输入: -["Trie", "insert", "search", "search", "startsWith", "insert", "search"] -[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] -输出: -[null, null, true, false, true, null, true] - -解释: -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 -``` - -## 解题思路 - -### 思路 1:前缀树(字典树) - -前缀树(字典树)是一棵多叉树,其中每个节点包含指向子节点的指针数组 `children`,以及布尔变量 `isEnd`。`children` 用于存储当前字符节点,一般长度为所含字符种类个数,也可以使用哈希表代替指针数组。`isEnd` 用于判断该节点是否为字符串的结尾。 - -下面依次讲解插入、查找前缀的具体步骤: - -**插入字符串**: - -- 从根节点开始插入字符串。对于待插入的字符,有两种情况: - - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续处理下一个字符。 - - 如果该字符对应的节点不存在,则创建一个新的节点,保存在 `children` 中对应位置上,然后沿着指针移动到子节点,继续处理下一个字符。 -- 重复上述步骤,直到最后一个字符,然后将该节点标记为字符串的结尾。 - -**查找前缀**: - -- 从根节点开始查找前缀,对于待查找的字符,有两种情况: - - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续查找下一个字符。 - - 如果该字符对应的节点不存在,则说明字典树中不包含该前缀,直接返回空指针。 -- 重复上述步骤,直到最后一个字符搜索完毕,则说明字典树中存在该前缀。 - -### 思路 1:代码 - -```python -class Node: - def __init__(self): - self.children = dict() - self.isEnd = False - -class Trie: - - def __init__(self): - self.root = Node() - - def insert(self, word: str) -> None: - cur = self.root - for ch in word: - if ch not in cur.children: - cur.children[ch] = Node() - cur = cur.children[ch] - cur.isEnd = True - - def search(self, word: str) -> bool: - cur = self.root - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - - def startsWith(self, prefix: str) -> bool: - cur = self.root - for ch in prefix: - if ch not in cur.children: - return False - cur = cur.children[ch] - return cur is not None -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:初始化为 $O(1)$。插入操作、查找操作的时间复杂度为 $O(|S|)$。其中 $|S|$ 是每次插入或查找字符串的长度。 -- **空间复杂度**:$O(|T| \times \sum)$。其中 $|T|$ 是所有插入字符串的长度之和,$\sum$ 是字符集的大小。 - diff --git "a/Solutions/0209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 97087fa8..00000000 --- "a/Solutions/0209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) - -- 标签:数组、二分查找、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含正整数的数组 $nums$ 和一个正整数 $target$。 - -**要求**:找出数组中满足和大于等于 $target$ 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 $0$。 - -**说明**: - -- $1 \le target \le 10^9$。 -- $1 \le nums.length \le 10^5$。 -- $1 \le nums[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:target = 7, nums = [2,3,1,2,4,3] -输出:2 -解释:子数组 [4,3] 是该条件下的长度最小的子数组。 -``` - -- 示例 2: - -```python -输入:target = 4, nums = [1,4,4] -输出:1 -``` - -## 解题思路 - -### 思路 1:滑动窗口(不定长度) - -最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 - -用滑动窗口来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 $target$。 - -1. 一开始,$left$、$right$ 都指向 $0$。 -2. 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{}sum$ 中。 -3. 如果 $window\underline{}sum \ge target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{}sum < target$。 -4. 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -5. 输出窗口和的最小值作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def minSubArrayLen(self, target: int, nums: List[int]) -> int: - size = len(nums) - ans = size + 1 - left = 0 - right = 0 - window_sum = 0 - - while right < size: - window_sum += nums[right] - - while window_sum >= target: - ans = min(ans, right - left + 1) - window_sum -= nums[left] - left += 1 - - right += 1 - - return ans if ans != size + 1 else 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0210. \350\257\276\347\250\213\350\241\250 II.md" "b/Solutions/0210. \350\257\276\347\250\213\350\241\250 II.md" deleted file mode 100644 index 009d8fbd..00000000 --- "a/Solutions/0210. \350\257\276\347\250\213\350\241\250 II.md" +++ /dev/null @@ -1,98 +0,0 @@ -# [0210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 - -**要求**:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 - -**说明**: - -- $1 \le numCourses \le 2000$。 -- $0 \le prerequisites.length \le numCourses \times (numCourses - 1)$。 -- $prerequisites[i].length == 2$。 -- $0 \le ai, bi < numCourses$。 -- $ai \ne bi$。 -- 所有$[ai, bi]$ 互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:numCourses = 2, prerequisites = [[1,0]] -输出:[0,1] -解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]。 -``` - -- 示例 2: - -```python -输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] -输出:[0,2,1,3] -解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 -因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]。 -``` - -## 解题思路 - -### 思路 1:拓扑排序 - -这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组 $order$ 即可。 - -1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 -2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 -3. 从队列中选择一个节点 $u$,并将其加入到答案数组 $order$ 中。 -4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 -5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 -6. 最后判断总的顶点数和拓扑序列中的顶点数是否相等,如果相等,则返回答案数组 $order$,否则,返回空数组。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingKahn(self, graph: dict): - indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 - for u in graph: - for v in graph[u]: - indegrees[v] += 1 # 统计所有顶点入度 - - # 将入度为 0 的顶点存入集合 S 中 - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - order = [] # order 用于存储拓扑序列 - - while S: - u = S.pop() # 从集合中选择一个没有前驱的顶点 0 - order.append(u) # 将其输出到拓扑序列 order 中 - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 - if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 - S.append(v) # 将其放入集合 S 中 - - if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 - return [] - return order # 返回拓扑序列 - - - def findOrder(self, numCourses: int, prerequisites): - graph = dict() - for i in range(numCourses): - graph[i] = [] - - for v, u in prerequisites: - graph[u].append(v) - - return self.topologicalSortingKahn(graph) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 -- **空间复杂度**:$O(n + m)$。 - diff --git "a/Solutions/0211. \346\267\273\345\212\240\344\270\216\346\220\234\347\264\242\345\215\225\350\257\215 - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" "b/Solutions/0211. \346\267\273\345\212\240\344\270\216\346\220\234\347\264\242\345\215\225\350\257\215 - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" deleted file mode 100644 index 5ad7c810..00000000 --- "a/Solutions/0211. \346\267\273\345\212\240\344\270\216\346\220\234\347\264\242\345\215\225\350\257\215 - \346\225\260\346\215\256\347\273\223\346\236\204\350\256\276\350\256\241.md" +++ /dev/null @@ -1,126 +0,0 @@ -# [0211. 添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) - -- 标签:深度优先搜索、设计、字典树、字符串 -- 难度:中等 - -## 题目大意 - -**要求**:设计一个数据结构,支持「添加新单词」和「查找字符串是否与任何先前添加的字符串匹配」。 - -实现词典类 WordDictionary: - -- `WordDictionary()` 初始化词典对象。 -- `void addWord(word)` 将 `word` 添加到数据结构中,之后可以对它进行匹配 -- `bool search(word)` 如果数据结构中存在字符串与 `word` 匹配,则返回 `True`;否则,返回 `False`。`word` 中可能包含一些 `.`,每个 `.` 都可以表示任何一个字母。 - -**说明**: - -- $1 \le word.length \le 25$。 -- `addWord` 中的 `word` 由小写英文字母组成。 -- `search` 中的 `word` 由 `'.'` 或小写英文字母组成。 -- 最多调用 $10^4$ 次 `addWord` 和 `search`。 - -**示例**: - -- 示例 1: - -```python -输入: -["WordDictionary","addWord","addWord","addWord","search","search","search","search"] -[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] -输出: -[null,null,null,null,false,true,true,true] - -解释: -WordDictionary wordDictionary = new WordDictionary(); -wordDictionary.addWord("bad"); -wordDictionary.addWord("dad"); -wordDictionary.addWord("mad"); -wordDictionary.search("pad"); // 返回 False -wordDictionary.search("bad"); // 返回 True -wordDictionary.search(".ad"); // 返回 True -wordDictionary.search("b.."); // 返回 True -``` - -## 解题思路 - -### 思路 1:字典树 - -使用前缀树(字典树)。具体做法如下: - -- 初始化词典对象时,构造一棵字典树。 -- 添加 `word` 时,将 `word` 插入到字典树中。 -- 搜索 `word` 时: - - 如果遇到 `.`,则递归匹配当前节点所有子节点,并依次向下查找。匹配到了,则返回 `True`,否则返回 `False`。 - - 如果遇到其他小写字母,则按 `word` 顺序匹配节点。 - - 如果当前节点为 `word` 的结尾,则放回 `True`。 - -### 思路 1:代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - - def dfs(index, node) -> bool: - if index == len(word): - return node.isEnd - - ch = word[index] - if ch == '.': - for child in node.children.values(): - if child is not None and dfs(index + 1, child): - return True - else: - if ch not in node.children: - return False - child = node.children[ch] - if child is not None and dfs(index + 1, child): - return True - return False - - return dfs(0, self) - - -class WordDictionary: - - def __init__(self): - self.trie_tree = Trie() - - - def addWord(self, word: str) -> None: - self.trie_tree.insert(word) - - - def search(self, word: str) -> bool: - return self.trie_tree.search(word) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:初始化操作为 $O(1)$。添加单词为 $O(|S|)$,搜索单词的平均时间复杂度为 $O(|S|)$,最坏情况下所有字符都是 `'.'`,所以最坏时间复杂度为 $O(|S|^\sum)$。其中 $|S|$ 为单词长度,$\sum$ 为字符集的大小,此处为 $26$。 -- **空间复杂度**:$O(|T| * n)$。其中 $|T|$ 为所有添加单词的最大长度,$n$ 为添加字符串个数。 - diff --git "a/Solutions/0212. \345\215\225\350\257\215\346\220\234\347\264\242 II.md" "b/Solutions/0212. \345\215\225\350\257\215\346\220\234\347\264\242 II.md" deleted file mode 100644 index 088adfb5..00000000 --- "a/Solutions/0212. \345\215\225\350\257\215\346\220\234\347\264\242 II.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0212. 单词搜索 II](https://leetcode.cn/problems/word-search-ii/) - -- 标签:字典树、数组、字符串、回溯、矩阵 -- 难度:困难 - -## 题目大意 - -给定一个 `m * n` 二维字符网格 `board` 和一个单词(字符串)列表 `words`。 - -要求:找出所有同时在二维网格和字典中出现的单词。 - -注意:单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中「相邻」单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 - -## 解题思路 - -- 先将单词列表 `words` 中的所有单词存入字典树中。 - -- 然后遍历二维字符网络 `board` 的每一个字符 `board[i][j]`。 - -- 从当前单元格出发,从上下左右四个方向深度优先搜索遍历路径。每经过一个单元格,就将该单元格的字母修改为特殊字符,避免重复遍历,深度优先搜索完毕之后再恢复该单元格。 - - 如果当前路径恰好是 `words` 列表中的单词,则将结果添加到答案数组中。 - - 如果是 `words` 列表中单词的前缀,则继续搜索。 - - 如果不是 `words` 列表中单词的前缀,则停止搜索。 -- 最后输出答案数组。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.word = "" - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.word = word - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - rows = len(board) - cols = len(board[0]) - - def dfs(cur, row, col): - ch = board[row][col] - if ch not in cur.children: - return - - cur = cur.children[ch] - if cur.isEnd: - ans.add(cur.word) - - board[row][col] = "#" - for direct in directs: - new_row = row + direct[0] - new_col = col + direct[1] - if 0 <= new_row < rows and 0 <= new_col < cols: - dfs(cur, new_row, new_col) - board[row][col] = ch - - ans = set() - for i in range(rows): - for j in range(cols): - dfs(trie_tree, i, j) - - return list(ans) -``` - diff --git "a/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" "b/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" deleted file mode 100644 index 2aa1e49f..00000000 --- "a/Solutions/0213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" +++ /dev/null @@ -1,109 +0,0 @@ -# [0213. 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $nums$,$num[i]$ 代表第 $i$ 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 - -**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 - -**说明**: - -- $1 \le nums.length \le 100$。 -- $0 \le nums[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,3,2] -输出:3 -解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,3,1] -输出:4 -解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题可以看做是「[198. 打家劫舍](https://leetcode.cn/problems/house-robber/)」的升级版。 - -如果房屋数大于等于 $3$ 间,偷窃了第 $1$ 间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第 $1$ 间房屋。 - -假设总共房屋数量为 $size$,这种情况可以转换为分别求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额,然后再取这两种情况下的最大值。而求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额问题就跟「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」所求问题一致了。 - -这里来复习一下「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」的解题思路。 - -###### 1. 划分阶段 - -按照房屋序号进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。 - -###### 3. 状态转移方程 - -$i$ 间房屋的最后一个房子是 $nums[i - 1]$。 - -如果房屋数大于等于 $2$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态: - -1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$; -1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$。 - -然后这两种状态取最大值即可,即状态转移方程为: - -$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$ - -###### 4. 初始条件 - -- 前 $0$ 间房屋所能偷窃到的最高金额为 $0$,即 $dp[0] = 0$。 -- 前 $1$ 间房屋所能偷窃到的最高金额为 $nums[0]$,即:$dp[1] = nums[0]$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。假设求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下( $size$ 为总的房屋数)首尾不相连的房屋所能偷窃的最高金额问题分别为 $ans1$、$ans2$,则最终结果为 $max(ans1, ans2)$。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def helper(self, nums): - size = len(nums) - if size == 0: - return 0 - - dp = [0 for _ in range(size + 1)] - dp[0] = 0 - dp[1] = nums[0] - - for i in range(2, size + 1): - dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1]) - - return dp[size] - - def rob(self, nums: List[int]) -> int: - size = len(nums) - if size == 1: - return nums[0] - - ans1 = self.helper(nums[:size - 1]) - ans2 = self.helper(nums[1:]) - return max(ans1, ans2) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" "b/Solutions/0215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" deleted file mode 100644 index e804aaed..00000000 --- "a/Solutions/0215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" +++ /dev/null @@ -1,216 +0,0 @@ -# [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) - -- 标签:数组、分治、快速排序、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -**描述**:给定一个未排序的整数数组 $nums$ 和一个整数 $k$。 - -**要求**:返回数组中第 $k$ 个最大的元素。 - -**说明**: - -- 要求使用时间复杂度为 $O(n)$ 的算法解决此问题。 -- $1 \le k \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入: [3,2,1,5,6,4], k = 2 -输出: 5 -``` - -- 示例 2: - -```python -输入: [3,2,3,1,2,4,5,5,6], k = 4 -输出: 4 -``` - -## 解题思路 - -很不错的一道题,面试常考。 - -直接可以想到的思路是:排序后输出数组上对应第 $k$ 位大的数。所以问题关键在于排序方法的复杂度。 - -冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,很容易超时。 - -可考虑堆排序、归并排序、快速排序。 - -这道题的要求是找到第 $k$ 大的元素,使用归并排序只有到最后排序完毕才能返回第 $k$ 大的数。而堆排序每次排序之后,就会确定一个元素的准确排名,同理快速排序也是如此。 - -### 思路 1:堆排序 - -升序堆排序的思路如下: - -1. 将无序序列构造成第 $1$ 个大顶堆(初始堆),使得 $n$ 个元素的最大值处于序列的第 $1$ 个位置。 - -2. **调整堆**:交换序列的第 $1$ 个元素(最大值元素)与第 $n$ 个元素的位置。将序列前 $n - 1$ 个元素组成的子序列调整成一个新的大顶堆,使得 $n - 1$ 个元素的最大值处于序列第 $1$ 个位置,从而得到第 $2$ 个最大值元素。 - -3. **调整堆**:交换子序列的第 $1$ 个元素(最大值元素)与第 $n - 1$ 个元素的位置。将序列前 $n - 2$ 个元素组成的子序列调整成一个新的大顶堆,使得 $n - 2$ 个元素的最大值处于序列第 $1$ 个位置,从而得到第 $3$ 个最大值元素。 - -4. 依次类推,不断交换子序列的第 $1$ 个元素(最大值元素)与当前子序列最后一个元素位置,并将其调整成新的大顶堆。直到获取第 $k$ 个最大值元素为止。 - - -### 思路 1:代码 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 调整为大顶堆 - def heapify(nums, index, end): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if nums[left] > nums[max_index]: - max_index = left - if right <= end and nums[right] > nums[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(nums): - size = len(nums) - # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - heapify(nums, i, size - 1) - return nums - - buildMaxHeap(nums) - size = len(nums) - for i in range(k-1): - nums[0], nums[size-i-1] = nums[size-i-1], nums[0] - heapify(nums, 0, size-i-2) - return nums[0] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:快速排序 - -使用快速排序在每次调整时,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了左右两个子数组,左子数组中的元素都比该元素小,右子树组中的元素都比该元素大。 - -这样,只要某次划分的元素恰好是第 $k$ 个下标就找到了答案。并且我们只需关注第 $k$ 个最大元素所在区间的排序情况,与第 $k$ 个最大元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 - -### 思路 2:代码 - -```python -import random - -class Solution: - # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 - def randomPartition(self, nums: [int], low: int, high: int) -> int: - # 随机挑选一个基准数 - i = random.randint(low, high) - # 将基准数与最低位互换 - nums[i], nums[low] = nums[low], nums[i] - # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 - return self.partition(nums, low, high) - - # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 - def partition(self, nums: [int], low: int, high: int) -> int: - # 以第 1 位元素为基准数 - pivot = nums[low] - - i, j = low, high - while i < j: - # 从右向左找到第 1 个小于基准数的元素 - while i < j and nums[j] >= pivot: - j -= 1 - # 从左向右找到第 1 个大于基准数的元素 - while i < j and nums[i] <= pivot: - i += 1 - # 交换元素 - nums[i], nums[j] = nums[j], nums[i] - - # 将基准数放到正确位置上 - nums[j], nums[low] = nums[low], nums[j] - return j - - def quickSort(self, nums: [int], low: int, high: int, k: int, size: int) -> [int]: - if low < high: - # 按照基准数的位置,将数组划分为左右两个子数组 - pivot_i = self.randomPartition(nums, low, high) - if pivot_i == size - k: - return nums[size - k] - if pivot_i > size - k: - self.quickSort(nums, low, pivot_i - 1, k, size) - if pivot_i < size - k: - self.quickSort(nums, pivot_i + 1, high, k, size) - - return nums[size - k] - - - def findKthLargest(self, nums: List[int], k: int) -> int: - size = len(nums) - return self.quickSort(nums, 0, len(nums) - 1, k, size) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。证明过程可参考「算法导论 9.2:期望为线性的选择算法」。 -- **空间复杂度**:$O(\log n)$。递归使用栈空间的空间代价期望为 $O(\log n)$。 - -### 思路 3:借用标准库(不建议) - -提交代码中的最快代码是调用了 Python 的 `sort` 方法。这种做法适合在打算法竞赛的时候节省时间,日常练习可以尝试一下自己写。 - -### 思路 3:代码 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - nums.sort() - return nums[len(nums) - k] -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 4:优先队列 - -1. 遍历数组元素,对于挡圈元素 $num$: - 1. 如果优先队列中的元素个数小于 $k$ 个,则将当前元素 $num$ 放入优先队列中。 - 2. 如果优先队列中的元素个数大于等于 $k$ 个,并且当前元素 $num$ 大于优先队列的队头元素,则弹出队头元素,并将当前元素 $num$ 插入到优先队列中。 -2. 遍历完,此时优先队列的队头元素就是第 $k$ 个最大元素,将其弹出并返回即可。 - -这里我们借助了 Python 中的 `heapq` 模块实现优先队列算法,这一步也可以通过手写堆的方式实现优先队列。 - -### 思路 4:代码 - -```python -import heapq -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - res = [] - for num in nums: - if len(res) < k: - heapq.heappush(res, num) - elif num > res[0]: - heapq.heappop(res) - heapq.heappush(res, num) - return heapq.heappop(res) -``` - -### 思路 4:复杂度分析 - -- **时间复杂度**:$O(n \times \log k)$。 -- **空间复杂度**:$O(k)$。 \ No newline at end of file diff --git "a/Solutions/0217. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.md" "b/Solutions/0217. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.md" deleted file mode 100644 index ca03782c..00000000 --- "a/Solutions/0217. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.md" +++ /dev/null @@ -1,101 +0,0 @@ -# [0217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) - -- 标签:数组、哈希表、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`。 - -**要求**:判断是否存在重复元素。如果有元素在数组中出现至少两次,返回 `True`;否则返回 `False`。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-10^9 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,1] -输出:True -``` - -- 示例 2: - -```python -输入:nums = [1,2,3,4] -输出:False -``` - -## 解题思路 - -### 思路 1:哈希表 - -- 使用一个哈希表存储元素和对应元素数量。 -- 遍历元素,如果哈希表中出现了该元素,则直接输出 `True`。如果没有出现,则向哈希表中插入该元素。 -- 如果遍历完也没发现重复元素,则输出 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def containsDuplicate(self, nums: List[int]) -> bool: - numDict = dict() - for num in nums: - if num in numDict: - return True - else: - numDict[num] = num - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:集合 - -- 使用一个 `set` 集合存储数组中所有元素。 -- 如果集合中元素个数与数组元素个数不同,则说明出现了重复元素,返回 `True`。 -- 如果集合中元素个数与数组元素个数相同,则说明没有出现了重复元素,返回 `False`。 - -### 思路 2:集合代码 - -```python -class Solution: - def containsDuplicate(self, nums: List[int]) -> bool: - return len(set(nums)) != len(nums) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 3:排序 - -- 对数组进行排序。 -- 排序之后,遍历数组,判断相邻元素之间是否出现重复元素。 -- 如果相邻元素相同,则说明出现了重复元素,返回 `True`。 -- 如果遍历完也没发现重复元素,则输出 `False`。 - -### 思路 3:排序代码 - -```python -class Solution: - def containsDuplicate(self, nums: List[int]) -> bool: - nums.sort() - for i in range(1, len(nums)): - if nums[i - 1] == nums[i]: - return True - return False -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0218. \345\244\251\351\231\205\347\272\277\351\227\256\351\242\230.md" "b/Solutions/0218. \345\244\251\351\231\205\347\272\277\351\227\256\351\242\230.md" deleted file mode 100644 index fd9174c1..00000000 --- "a/Solutions/0218. \345\244\251\351\231\205\347\272\277\351\227\256\351\242\230.md" +++ /dev/null @@ -1,84 +0,0 @@ -# [0218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem/) - -- 标签:树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) -- 难度:困难 - -## 题目大意 - -城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。 - -给定所有建筑物的位置和高度所组成的数组 `buildings`。其中三元素 `buildings[i] = [left_i, right_i, height_i]` 表示 `left_i` 是第 `i` 座建筑物左边界的 `x` 坐标。`right_i` 是第 `i` 座建筑物右边界的 `x` 坐标,`height_i` 是第 `i` 做建筑物的高度。 - -要求:返回由这些建筑物形成的天际线 。 - -- 天际线:由 “关键点” 组成的列表,格式 `[[x1, y1], [x2, y2], [x3, y3], ...]`,并按 `x` 坐标进行排序。 -- 关键点:水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,`y` 坐标始终为 `0`,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。 - -注意:输出天际线中不得有连续的相同高度的水平线。 - -- 例如 `[..., [2 3], [4 5], [7 5], [11 5], [12 7], ...]` 是不正确的答案;三条高度为 `5` 的线应该在最终输出中合并为一个:`[..., [2 3], [4 5], [12 7], ...]`。 - -示例: - -![](https://assets.leetcode.com/uploads/2020/12/01/merged.jpg) - -- 图 A 显示输入的所有建筑物的位置和高度。 -- 图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。 - -## 解题思路 - -可以看出来:关键点的横坐标都在建筑物的左右边界上。 - -我们可以将左右边界最高处的坐标存入 `points` 数组中,然后按照建筑物左边界、右边界的高度进行排序。 - -然后用一条条「垂直于 x 轴的扫描线」,从所有建筑物的最左侧依次扫描到最右侧。从而将建筑物分割成规则的矩形。 - -不难看出:相邻的两个坐标的横坐标与矩形所能达到的最大高度构成了一个矩形。相邻两个坐标的横坐标可以从排序过的 `points` 数组中依次获取,矩形所能达到的最大高度可以用一个优先队列(堆)`max_heap` 来维护。使用数组 `ans` 来作为答案答案。 - -在依次从左到右扫描坐标时: - -- 当扫描到建筑物的左边界时,说明必然存在一条向右延伸的边。此时将高度加入到优先队列中。 -- 当扫描到建筑物的右边界时,说明从之前的左边界延伸的边结束了,此时将高度从优先队列中移除。 - -因为三条高度相同的线应该合并为一个,所以我们用 `prev` 来记录之前上一个矩形高度。 - -- 如果当前矩形高度 `curr` 与之前矩形高度 `prev` 相同,则跳过。 -- 如果当前矩形高度 `curr` 与之前矩形高度 `prev `不相同,则将其加入到答案数组中,并更新上一矩形高度 `prev` 的值。 - -最后,输出答案 `ans`。 - -## 代码 - -```python -from sortedcontainers import SortedList - -class Solution: - def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]: - ans = [] - points = [] - for building in buildings: - left, right, hight = building[0], building[1], building[2] - points.append([left, -hight]) - points.append([right, hight]) - points.sort(key=lambda x:(x[0], x[1])) - - prev = 0 - max_heap = SortedList([prev]) - - for point in points: - x, height = point[0], point[1] - if height < 0: - max_heap.add(-height) - else: - max_heap.remove(height) - - curr = max_heap[-1] - if curr != prev: - ans.append([x, curr]) - prev = curr - return ans -``` - -## 参考资料 - -- 【题解】[【宫水三叶】扫描线算法基本思路 & 优先队列维护当前最大高度 - 天际线问题 - 力扣](https://leetcode.cn/problems/the-skyline-problem/solution/gong-shui-san-xie-sao-miao-xian-suan-fa-0z6xc/) diff --git "a/Solutions/0219. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 II.md" "b/Solutions/0219. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 II.md" deleted file mode 100644 index 3c6f2207..00000000 --- "a/Solutions/0219. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 II.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) - -- 标签:数组、哈希表、滑动窗口 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 `nums` 和一个整数 `k`。 - -**要求**:判断是否存在 $nums[i] == nums[j](i \ne j)$,并且 `i` 和 `j` 的差绝对值至多为 `k`。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-10^9 <= nums[i] <= 10^9$。 -- $0 \le k \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,1], k = 3 -输出:True -``` - -## 解题思路 - -### 思路 1:哈希表 - -维护一个最多有 `k` 个元素的哈希表。遍历 `nums`,对于数组中的每个整数 `nums[i]`,判断哈希表中是否存在这个整数。 - -- 如果存在,则说明出现了两次,且 $i \ne j$,直接返回 `True`。 - -- 如果不存在,则将 `nums[i]` 加入哈希表。 -- 判断哈希表长度是否超过了 `k`,如果超过了 `k`,则删除哈希表中最旧的元素 `nums[i - k]`。 -- 如果遍历完仍旧找不到,则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: - nums_dict = dict() - for i in range(len(nums)): - if nums[i] in nums_dict: - return True - nums_dict[nums[i]] = 1 - if len(nums_dict) > k: - del nums_dict[nums[i - k]] - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III.md" "b/Solutions/0220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III.md" deleted file mode 100644 index dc9b1145..00000000 --- "a/Solutions/0220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III.md" +++ /dev/null @@ -1,149 +0,0 @@ -# [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) - -- 标签:数组、桶排序、有序集合、排序、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$,以及两个整数 $k$、$t$。 - -**要求**:判断数组中是否存在两个不同下标的 $i$ 和 $j$,其对应元素满足 $abs(nums[i] - nums[j]) \le t$,同时满足 $abs(i - j) \le k$。如果满足条件则返回 `True`,不满足条件返回 `False`。 - -**说明**: - -- $0 \le nums.length \le 2 \times 10^4$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- $0 \le k \le 10^4$。 -- $0 \le t \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,1], k = 3, t = 0 -输出:True -``` - -- 示例 2: - -```python -输入:nums = [1,0,1,1], k = 1, t = 2 -输出:True -``` - -## 解题思路 - -题目中需要满足两个要求,一个是元素值的要求($abs(nums[i] - nums[j]) \le t$) ,一个是下标范围的要求($abs(i - j) \le k$)。 - -对于任意一个位置 $i$ 来说,合适的 $j$ 应该在区间 $[i - k, i + k]$ 内,同时 $nums[j]$ 值应该在区间 $[nums[i] - t, nums[i] + t]$ 内。 - -最简单的做法是两重循环遍历数组,第一重循环遍历位置 $i$,第二重循环遍历 $[i - k, i + k]$ 的元素,判断是否满足 $abs(nums[i] - nums[j]) \le t$。但是这样做的时间复杂度为 $O(n \times k)$,其中 $n$ 是数组 $nums$ 的长度。 - -我们需要优化一下检测相邻 $2 \times k$ 个元素是否满足 $abs(nums[i] - nums[j]) \le t$ 的方法。有两种思路:「桶排序」和「滑动窗口(固定长度)」。 - -### 思路 1:桶排序 - -1. 利用桶排序的思想,将桶的大小设置为 $t + 1$。只需要使用一重循环遍历位置 $i$,然后根据 $\lfloor \frac{nums[i]}{t + 1} \rfloor$,从而决定将 $nums[i]$ 放入哪个桶中。 -2. 这样在同一个桶内各个元素之间的差值绝对值都小于等于 $t$。而相邻桶之间的元素,只需要校验一下两个桶之间的差值是否不超过 $t$。这样就可以以 $O(1)$ 的时间复杂度检测相邻 $2 \times k$ 个元素是否满足 $abs(nums[i] - nums[j]) \le t$。 -3. 而 $abs(i - j) \le k$ 条件则可以通过在一重循环遍历时,将超出范围的 $nums[i - k]$ 从对应桶中删除,从而保证桶中元素一定满足 $abs(i - j) \le k$。 - -具体步骤如下: - -1. 将每个桶的大小设置为 $t + 1$。我们将元素按照大小依次放入不同的桶中。 -2. 遍历数组 $nums$ 中的元素,对于元素$ nums[i]$ : - 1. 如果 $nums[i]$ 放入桶之前桶里已经有元素了,那么这两个元素必然满足 $abs(nums[i] - nums[j]) \le t$, - 2. 如果之前桶里没有元素,那么就将 $nums[i]$ 放入对应桶中。 - 3. 再判断左右桶的左右两侧桶中是否有元素满足 $abs(nums[i] - nums[j]) <= t$。 - 4. 然后将 $nums[i - k]$ 之前的桶清空,因为这些桶中的元素与 $nums[i]$ 已经不满足 $abs(i - j) \le k$ 了。 -3. 最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: - bucket_dict = dict() - for i in range(len(nums)): - # 将 nums[i] 划分到大小为 t + 1 的不同桶中 - num = nums[i] // (t + 1) - - # 桶中已经有元素了 - if num in bucket_dict: - return True - - # 把 nums[i] 放入桶中 - bucket_dict[num] = nums[i] - - # 判断左侧桶是否满足条件 - if (num - 1) in bucket_dict and abs(bucket_dict[num - 1] - nums[i]) <= t: - return True - # 判断右侧桶是否满足条件 - if (num + 1) in bucket_dict and abs(bucket_dict[num + 1] - nums[i]) <= t: - return True - # 将 i - k 之前的旧桶清除,因为之前的桶已经不满足条件了 - if i >= k: - bucket_dict.pop(nums[i - k] // (t + 1)) - - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。$n$ 是给定数组长度。 -- **空间复杂度**:$O(min(n, k))$。桶中最多包含 $min(n, k + 1)$ 个元素。 - -### 思路 2:滑动窗口(固定长度) - -1. 使用一个长度为 $k$ 的滑动窗口,每次遍历到 $nums[right]$ 时,滑动窗口内最多包含 $nums[right]$ 之前最多 $k$ 个元素。只需要检查前 $k$ 个元素是否在 $[nums[right] - t, nums[right] + t]$ 区间内即可。 -2. 检查 $k$ 个元素是否在 $[nums[right] - t, nums[right] + t]$ 区间,可以借助保证有序的数据结构(比如 `SortedList`)+ 二分查找来解决,从而减少时间复杂度。 - -具体步骤如下: - -1. 使用有序数组类 $window$ 维护一个长度为 $k$ 的窗口,满足数组内元素有序,且支持增加和删除操作。 -2. $left$、$right$ 都指向序列的第一个元素。即:`left = 0`,`right = 0`。 -3. 将当前元素填入窗口中,即 `window.add(nums[right])`。 -4. 当窗口元素大于 $k$ 个时,即当 $right - left > k$ 时,移除窗口最左侧元素,并向右移动 $left$。 -5. 当窗口元素小于等于 $k$ 个时: - 1. 使用二分查找算法,查找 $nums[right]$ 在 $window$ 中的位置 $idx$。 - 2. 判断 $window[idx]$ 与相邻位置上元素差值绝对值,若果满足 $abs(window[idx] - window[idx - 1]) \le t$ 或者 $abs(window[idx + 1] - window[idx]) \le t$ 时返回 `True`。 -6. 向右移动 $right$。 -7. 重复 $3 \sim 6$ 步,直到 $right$ 到达数组末尾,如果还没找到满足条件的情况,则返回 `False`。 - -### 思路 2:代码 - -```python -from sortedcontainers import SortedList - -class Solution: - def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: - size = len(nums) - window = SortedList() - left, right = 0, 0 - while right < size: - window.add(nums[right]) - - if right - left > k: - window.remove(nums[left]) - left += 1 - - idx = bisect.bisect_left(window, nums[right]) - - if idx > 0 and abs(window[idx] - window[idx - 1]) <= t: - return True - if idx < len(window) - 1 and abs(window[idx + 1] - window[idx]) <= t: - return True - - right += 1 - - return False -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \log (min(n, k)))$。 -- **空间复杂度**:$O(min(n, k))$。 - -## 参考资料 - -- 【题解】[利用桶的原理O(n),Python3 - 存在重复元素 III - 力扣](https://leetcode.cn/problems/contains-duplicate-iii/solution/li-yong-tong-de-yuan-li-onpython3-by-zhou-pen-chen/) diff --git "a/Solutions/0221. \346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.md" "b/Solutions/0221. \346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.md" deleted file mode 100644 index 6d34487e..00000000 --- "a/Solutions/0221. \346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) - -- 标签:数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由 `'0'` 和 `'1'` 组成的二维矩阵 $matrix$。 - -**要求**:找到只包含 `'1'` 的最大正方形,并返回其面积。 - -**说明**: - -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le m, n \le 300$。 -- $matrix[i][j]$ 为 `'0'` 或 `'1'`。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/26/max1grid.jpg) - -```python -输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] -输出:4 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/26/max2grid.jpg) - -```python -输入:matrix = [["0","1"],["1","0"]] -输出:1 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照正方形的右下角坐标进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。 - -###### 3. 状态转移方程 - -只有当矩阵位置 $(i, j)$ 值为 $1$ 时,才有可能存在正方形。 - -- 如果矩阵位置 $(i, j)$ 上值为 $0$,则 $dp[i][j] = 0$。 -- 如果矩阵位置 $(i, j)$ 上值为 $1$,则 $dp[i][j]$ 的值由该位置上方、左侧、左上方三者共同约束的,为三者中最小值加 $1$。即:$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1$。 - -###### 4. 初始条件 - -- 默认所有以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长都为 $0$,即 $dp[i][j] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。则最终结果为所有 $dp[i][j]$ 中的最大值。 - -### 思路 1:代码 - -```python -class Solution: - def maximalSquare(self, matrix: List[List[str]]) -> int: - rows, cols = len(matrix), len(matrix[0]) - max_size = 0 - dp = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] - for i in range(rows): - for j in range(cols): - if matrix[i][j] == '1': - if i == 0 or j == 0: - dp[i][j] = 1 - else: - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 - max_size = max(max_size, dp[i][j]) - return max_size * max_size -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为二维矩阵 $matrix$ 的行数和列数。 -- **空间复杂度**:$O(m \times n)$。 diff --git "a/Solutions/0222. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.md" "b/Solutions/0222. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.md" deleted file mode 100644 index 44649f6a..00000000 --- "a/Solutions/0222. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.md" +++ /dev/null @@ -1,27 +0,0 @@ -# [0222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) - -- 标签:树、深度优先搜索、二分查找、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵完全二叉树的根节点 `root`,返回该树的节点个数。 - -- 完全二叉树:除了最底层节点可能没有填满外,其余各层节点数都达到了最大值,并且最下面一层的节点都集中在盖层最左边的若干位置。若最底层在第 `h` 层,则该层包含 $1 \sim 2^h$ 个节点。 - -## 解题思路 - -根据题意可知公式:当前根节点的节点个数 = 左子树节点个数 + 右子树节点个数 + 1。 - -根据上述公式递归遍历左右子树节点,并返回左右子树节点数 + 1。 - -## 代码 - -```python -class Solution: - def countNodes(self, root: TreeNode) -> int: - if not root: - return 0 - return 1 + self.countNodes(root.left) + self.countNodes(root.right) -``` - diff --git "a/Solutions/0223. \347\237\251\345\275\242\351\235\242\347\247\257.md" "b/Solutions/0223. \347\237\251\345\275\242\351\235\242\347\247\257.md" deleted file mode 100644 index d891d67e..00000000 --- "a/Solutions/0223. \347\237\251\345\275\242\351\235\242\347\247\257.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [0223. 矩形面积](https://leetcode.cn/problems/rectangle-area/) - -- 标签:几何、数学 -- 难度:中等 - -## 题目大意 - -给定两个矩形的左下角坐标、右上角坐标 `(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)`。其中 `(ax1, ay1)` 表示第一个矩形左下角坐标,`(ax2, ay2)` 表示第一个矩形右上角坐标,`(bx1, by1)` 表示第二个矩形左下角坐标,`(bx2, by2)` 表示第二个矩形右上角坐标。 - -要求:计算出两个矩形覆盖的总面积。 - -## 解题思路 - -两个矩形覆盖的总面积 = 第一个矩形面积 + 第二个矩形面积 - 重叠部分面积。 - -需要分别计算出两个矩形面积,还有求出相交部分的长、宽,并计算出对应重叠部分的面积。 - -## 代码 - -```python -class Solution: - def computeArea(self, ax1: int, ay1: int, ax2: int, ay2: int, bx1: int, by1: int, bx2: int, by2: int) -> int: - area_a = (ax2 - ax1) * (ay2 - ay1) - area_b = (bx2 - bx1) * (by2 - by1) - overlap_width = max(0, min(ax2, bx2) - max(ax1, bx1)) - overlap_height = max(0, min(ay2, by2) - max(ay1, by1)) - area_overlap = overlap_width * overlap_height - - return area_a + area_b - area_overlap -``` - diff --git "a/Solutions/0225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" "b/Solutions/0225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" deleted file mode 100644 index 8aa684ad..00000000 --- "a/Solutions/0225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) - -- 标签:栈、设计、队列 -- 难度:简单 - -## 题目大意 - -**要求**:仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的四种操作:`push`、`top`、`pop` 和 `empty`。 - -要求实现 `MyStack` 类: - -- `void push(int x)` 将元素 `x` 压入栈顶。 -- `int pop()` 移除并返回栈顶元素。 -- `int top()` 返回栈顶元素。 -- `boolean empty()` 如果栈是空的,返回 `True`;否则,返回 `False`。 - -**说明**: - -- 只能使用队列的基本操作 —— 也就是 `push to back`、`peek/pop from front`、`size` 和 `is empty` 这些操作。 -- 所使用的语言也许不支持队列。 你可以使用 `list` (列表)或者 `deque`(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。 - -**示例**: - -- 示例 1: - -```python -输入: -["MyStack", "push", "push", "top", "pop", "empty"] -[[], [1], [2], [], [], []] -输出: -[null, null, null, 2, 2, false] - -解释: -MyStack myStack = new MyStack(); -myStack.push(1); -myStack.push(2); -myStack.top(); // 返回 2 -myStack.pop(); // 返回 2 -myStack.empty(); // 返回 False -``` - -## 解题思路 - -### 思路 1:双队列 - -使用两个队列。`pushQueue` 用作入栈,`popQueue` 用作出栈。 - -- `push` 操作:将新加入的元素压入 `pushQueue` 队列中,并且将之前保存在 `popQueue` 队列中的元素从队头开始依次压入 `pushQueue` 中,此时 `pushQueue` 队列中头节点存放的是新加入的元素,尾部存放的是之前的元素。 而 `popQueue` 则为空。再将 `pushQueue` 和 `popQueue` 相互交换,保持 `pushQueue` 为空,`popQueue` 则用于 `pop`、`top` 等操作。 -- `pop` 操作:直接将 `popQueue` 队头元素取出。 -- `top` 操作:返回 `popQueue` 队头元素。 -- `empty`:判断 `popQueue` 是否为空。 - -### 思路 1:代码 - -```python -class MyStack: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.pushQueue = collections.deque() - self.popQueue = collections.deque() - - - def push(self, x: int) -> None: - """ - Push element x onto stack. - """ - self.pushQueue.append(x) - while self.popQueue: - self.pushQueue.append(self.popQueue.popleft()) - self.pushQueue, self.popQueue = self.popQueue, self.pushQueue - - def pop(self) -> int: - """ - Removes the element on top of the stack and returns that element. - """ - return self.popQueue.popleft() - - - def top(self) -> int: - """ - Get the top element. - """ - return self.popQueue[0] - - - def empty(self) -> bool: - """ - Returns whether the stack is empty. - """ - return not self.popQueue - - -# Your MyStack object will be instantiated and called as such: -# obj = MyStack() -# obj.push(x) -# param_2 = obj.pop() -# param_3 = obj.top() -# param_4 = obj.empty() -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:入栈操作的时间复杂度为 $O(n)$。出栈、取栈顶元素、判断栈是否为空的时间复杂度为 $O(1)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0226. \347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0226. \347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 1c5098f8..00000000 --- "a/Solutions/0226. \347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:将该二叉树进行左右翻转。 - -**说明**: - -- 树中节点数目范围在 $[0, 100]$ 内。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/14/invert1-tree.jpg) - -```python -输入:root = [4,2,7,1,3,6,9] -输出:[4,7,2,9,6,3,1] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/14/invert2-tree.jpg) - -```python -输入:root = [2,1,3] -输出:[2,3,1] -``` - -## 解题思路 - -### 思路 1:递归遍历 - -根据我们的递推三步走策略,写出对应的递归代码。 - -1. 写出递推公式: - - 1. 递归遍历翻转左子树。 - 2. 递归遍历翻转右子树。 - 3. 交换当前根节点 `root` 的左右子树。 - -2. 明确终止条件:当前节点 `root` 为 `None`。 - -3. 翻译为递归代码: - 1. 定义递归函数:`invertTree(self, root)` 表示输入参数为二叉树的根节点 `root`,返回结果为翻转后二叉树的根节点。 - - 2. 书写递归主体: - - ```python - left = self.invertTree(root.left) - right = self.invertTree(root.right) - root.left = right - root.right = left - return root - ``` - - 3. 明确递归终止条件:`if not root: return None` - -4. 返回根节点 `root`。 - -### 思路 1:代码 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if not root: - return None - left = self.invertTree(root.left) - right = self.invertTree(root.right) - root.left = right - root.right = left - return root -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0227. \345\237\272\346\234\254\350\256\241\347\256\227\345\231\250 II.md" "b/Solutions/0227. \345\237\272\346\234\254\350\256\241\347\256\227\345\231\250 II.md" deleted file mode 100644 index 51c1b998..00000000 --- "a/Solutions/0227. \345\237\272\346\234\254\350\256\241\347\256\227\345\231\250 II.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) - -- 标签:栈、数学、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串表达式 `s`,表达式中所有整数为非负整数,运算符只有 `+`、`-`、`*`、`/`,没有括号。 - -**要求**:实现一个基本计算器来计算并返回它的值。 - -**说明**: - -- $1 \le s.length \le 3 * 10^5$。 -- `s` 由整数和算符(`+`、`-`、`*`、`/`)组成,中间由一些空格隔开。 -- `s` 表示一个有效表达式。 -- 表达式中的所有整数都是非负整数,且在范围 $[0, 2^{31} - 1]$ 内。 -- 题目数据保证答案是一个 32-bit 整数。 - -**示例**: - -- 示例 1: - -```python -输入:s = "3+2*2" -输出:7 -``` - -- 示例 2: - -```python -输入:s = " 3/2 " -输出:1 -``` - -## 解题思路 - -### 思路 1:栈 - -计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 - -可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 - -具体做法: - -1. 遍历字符串 `s`,使用变量 `op` 来标记数字之前的运算符,默认为 `+`。 -2. 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 - 1. 如果 `op` 为 `+`,则将 `num` 压入栈中。 - 2. 如果 `op` 为 `-`,则将 `-num` 压入栈中。 - 3. 如果 `op` 为 `*`,则将栈顶元素 `top` 取出,计算 `top * num`,并将计算结果压入栈中。 - 4. 如果 `op` 为 `/`,则将栈顶元素 `top` 取出,计算 `int(top / num)`,并将计算结果压入栈中。 -3. 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 `op`。 -4. 最后将栈中整数进行累加,并返回结果。 - -### 思路 1:代码 - -```python -class Solution: - def calculate(self, s: str) -> int: - size = len(s) - stack = [] - op = '+' - index = 0 - while index < size: - if s[index] == ' ': - index += 1 - continue - if s[index].isdigit(): - num = ord(s[index]) - ord('0') - while index + 1 < size and s[index+1].isdigit(): - index += 1 - num = 10 * num + ord(s[index]) - ord('0') - if op == '+': - stack.append(num) - elif op == '-': - stack.append(-num) - elif op == '*': - top = stack.pop() - stack.append(top * num) - elif op == '/': - top = stack.pop() - stack.append(int(top / num)) - elif s[index] in "+-*/": - op = s[index] - index += 1 - return sum(stack) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0231. 2 \347\232\204\345\271\202.md" "b/Solutions/0231. 2 \347\232\204\345\271\202.md" deleted file mode 100644 index 1049d92f..00000000 --- "a/Solutions/0231. 2 \347\232\204\345\271\202.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0231. 2 的幂](https://leetcode.cn/problems/power-of-two/) - -- 标签:位运算、递归、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:判断该整数 $n$ 是否是 $2$ 的幂次方。如果是,返回 `True`;否则,返回 `False`。 - -**说明**: - -- $-2^{31} \le n \le 2^{31} - 1$ - -**示例**: - -- 示例 1: - -```python -输入:n = 1 -输出:True -解释:2^0 = 1 -``` - -- 示例 2: - -```python -输入:n = 16 -输出:True -解释:2^4 = 16 -``` - -## 解题思路 - -### 思路 1:循环判断 - -1. 不断判断 $n$ 是否能整除 $2$。 - 1. 如果不能整除,则返回 `False`。 - 2. 如果能整除,则让 $n$ 整除 $2$,直到 $n < 2$。 -2. 如果最后 $n == 1$,则返回 `True`,否则则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def isPowerOfTwo(self, n: int) -> bool: - if n <= 0: - return False - - while n % 2 == 0: - n //= 2 - return n == 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log_2 n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:数论判断 - -因为 $n$ 能取的最大值为 $2^{31}-1$。我们可以计算出:在 $n$ 的范围内,$2$ 的幂次方最大为 $2^{30} = 1073741824$。 - -因为 $2$ 为质数,则 $2^{30}$ 的除数只有 $2^0, 2^1, …, 2^{30}$。所以如果 $n$ 为 $2$ 的幂次方,则 $2^{30}$ 肯定能被 $n$ 整除,直接判断即可。 - -### 思路 2:代码 - -```python -class Solution: - def isPowerOfTwo(self, n: int) -> bool: - return n > 0 and 1073741824 % n == 0 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0232. \347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" "b/Solutions/0232. \347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" deleted file mode 100644 index 2e10bc9e..00000000 --- "a/Solutions/0232. \347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) - -- 标签:栈、设计、队列 -- 难度:简单 - -## 题目大意 - -**要求**:仅使用两个栈实现先入先出队列。 - -要求实现 `MyQueue` 类: - -- `void push(int x)` 将元素 `x` 推到队列的末尾。 -- `int pop()` 从队列的开头移除并返回元素。 -- `int peek()` 返回队列开头的元素。 -- `boolean empty()` 如果队列为空,返回 `True`;否则,返回 `False`。 - -**说明**: - -- 只能使用标准的栈操作 —— 也就是只有 `push to top`, `peek / pop from top`, `size`, 和 `is empty` 操作是合法的。 -- 可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 -- $1 <= x <= 9$。 -- 最多调用 $100$ 次 `push`、`pop`、`peek` 和 `empty`。 -- 假设所有操作都是有效的 (例如,一个空的队列不会调用 `pop` 或者 `peek` 操作)。 -- 进阶:实现每个操作均摊时间复杂度为 `O(1)` 的队列。换句话说,执行 `n` 个操作的总时间复杂度为 `O(n)`,即使其中一个操作可能花费较长时间。 - -**示例**: - -- 示例 1: - -```python -输入: -["MyQueue", "push", "push", "peek", "pop", "empty"] -[[], [1], [2], [], [], []] -输出: -[null, null, null, 1, 1, false] - -解释: -MyQueue myQueue = new MyQueue(); -myQueue.push(1); // queue is: [1] -myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) -myQueue.peek(); // return 1 -myQueue.pop(); // return 1, queue is [2] -myQueue.empty(); // return false -``` - -## 解题思路 - -### 思路 1:双栈 - -使用两个栈,`inStack` 用于输入,`outStack` 用于输出。 - -- `push` 操作:将元素压入 `inStack` 中。 -- `pop` 操作:如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 -- `peek` 操作:和 `pop` 操作类似,只不过最后一步不需要取出顶层元素,直接将其返回即可。 -- `empty` 操作:如果 `inStack` 和 `outStack` 都为空,则队列为空,否则队列不为空。 - -### 思路 1:代码 - -```python -class MyQueue: - - def __init__(self): - self.inStack = [] - self.outStack = [] - """ - Initialize your data structure here. - """ - - - def push(self, x: int) -> None: - self.inStack.append(x) - """ - Push element x to the back of queue. - """ - - - def pop(self) -> int: - if(len(self.outStack) == 0): - while(len(self.inStack) != 0): - self.outStack.append(self.inStack[-1]) - self.inStack.pop() - top = self.outStack[-1] - self.outStack.pop() - return top - """ - Removes the element from in front of queue and returns that element. - """ - - - def peek(self) -> int: - if (len(self.outStack) == 0): - while (len(self.inStack) != 0): - self.outStack.append(self.inStack[-1]) - self.inStack.pop() - top = self.outStack[-1] - return top - """ - Get the front element. - """ - - - def empty(self) -> bool: - return len(self.outStack) == 0 and len(self.inStack) == 0 - """ - Returns whether the queue is empty. - """ -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:`push` 和 `empty` 为 $O(1)$,`pop` 和 `peek` 为均摊 $O(1)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0233. \346\225\260\345\255\227 1 \347\232\204\344\270\252\346\225\260.md" "b/Solutions/0233. \346\225\260\345\255\227 1 \347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index 618ad95d..00000000 --- "a/Solutions/0233. \346\225\260\345\255\227 1 \347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [0233. 数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) - -- 标签:递归、数学、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:计算所有小于等于 $n$ 的非负整数中数字 $1$ 出现的个数。 - -**说明**: - -- $0 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 13 -输出:6 -``` - -- 示例 2: - -```python -输入:n = 0 -输出:0 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $1$ 出现的个数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始数字 $1$ 出现的个数为 $0$。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $1$ 出现的个数 $cnt$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 - 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 - 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX)`。 - 1. `cnt + (d == 1)` 表示之前数字 $1$ 出现的个数加上当前位为数字 $1$ 的个数。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 -6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def countDigitOne(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # cnt: 之前数字 1 出现的个数。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - def dfs(pos, cnt, isLimit): - if pos == len(s): - return cnt - - ans = 0 - # 不需要考虑前导 0,则最小可选择数字为 0 - minX = 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX) - return ans - - return dfs(0, 0, True) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/0234. \345\233\236\346\226\207\351\223\276\350\241\250.md" "b/Solutions/0234. \345\233\236\346\226\207\351\223\276\350\241\250.md" deleted file mode 100644 index 7ca5b395..00000000 --- "a/Solutions/0234. \345\233\236\346\226\207\351\223\276\350\241\250.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) - -- 标签:栈、递归、链表、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:判断该链表是否为回文链表。 - -**说明**: - -- 链表中节点数目在范围 $[1, 10^5]$ 内。 -- $0 \le Node.val \le 9$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/03/pal1linked-list.jpg) - -```python -输入:head = [1,2,2,1] -输出:True -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/03/pal2linked-list.jpg) - -```python -输入:head = [1,2] -输出:False -``` - -## 解题思路 - -### 思路 1:利用数组 + 双指针 - -1. 利用数组,将链表元素依次存入。 -2. 然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置。 -3. 依次判断首尾对应元素是否相等,如果都相等,则为回文链表。如果不相等,则不是回文链表。 - -### 思路 1:代码 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - nodes = [] - p1 = head - while p1 != None: - nodes.append(p1.val) - p1 = p1.next - return nodes == nodes[::-1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Solutions/0235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" deleted file mode 100644 index e5b8a51c..00000000 --- "a/Solutions/0235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" +++ /dev/null @@ -1,71 +0,0 @@ -# [0235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉搜索树的根节点 `root`,以及两个指定节点 `p` 和 `q`。 - -**要求**:找到该树中两个指定节点的最近公共祖先。 - -**说明**: - -- **祖先**:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 -- **最近公共祖先**:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 -- 所有节点的值都是唯一的。 -- `p`、`q` 为不同节点且均存在于给定的二叉搜索树中。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/binarysearchtree_improved.png) - -```python -输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 -输出: 6 -解释: 节点 2 和节点 8 的最近公共祖先是 6。 -``` - -- 示例 2: - -```python -输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 -输出: 2 -解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 -``` - -## 解题思路 - -### 思路 1:递归遍历 - -对于节点 `p`、节点 `q`,最近公共祖先就是从根节点分别到它们路径上的分岔点,也是路径中最后一个相同的节点,现在我们的问题就是求这个分岔点。 - -我们可以使用递归遍历查找二叉搜索树的最近公共祖先,具体方法如下。 - -1. 从根节点 `root` 开始遍历。 -2. 如果当前节点的值大于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的左子树,因此将当前节点移动到它的左子节点,继续遍历; -3. 如果当前节点的值小于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的右子树,因此将当前节点移动到它的右子节点,继续遍历; -4. 如果当前节点不满足上面两种情况,则说明 `p` 和 `q` 分别在当前节点的左右子树上,则当前节点就是分岔点,直接返回该节点即可。 - -### 思路 1:代码 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - ancestor = root - while True: - if ancestor.val > p.val and ancestor.val > q.val: - ancestor = ancestor.left - elif ancestor.val < p.val and ancestor.val < q.val: - ancestor = ancestor.right - else: - break - return ancestor -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点个数。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Solutions/0236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" deleted file mode 100644 index 6aa6e4ab..00000000 --- "a/Solutions/0236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`,以及二叉树中两个节点 `p` 和 `q`。 - -**要求**:找到该二叉树中指定节点 `p`、`q` 的最近公共祖先。 - -**说明**: - -- **祖先**:如果节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 -- **最近公共祖先**:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 -- 树中节点数目在范围 $[2, 10^5]$ 内。 -- $-10^9 \le Node.val \le 10^9$。 -- 所有 `Node.val` 互不相同。 -- `p != q`。 -- `p` 和 `q` 均存在于给定的二叉树中。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) - -```python -输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 -输出:3 -解释:节点 5 和节点 1 的最近公共祖先是节点 3 。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) - -```python -输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 -输出:5 -解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。 -``` - -## 解题思路 - -### 思路 1:递归遍历 - -设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: - -1. `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 -2. `p == lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 -3. `q == lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 - -下面递归求解 `lca_node`。递归需要满足以下条件: - -- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 -- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 -- 如果 `p`、`q` 都不存在,则返回 `None`。 - -具体思路为: - -1. 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node`。 -2. 如果当前节点 `node` 不为 `None`,则递归遍历左子树、右子树,并判断左右子树结果。 - 1. 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 - 2. 如果左子树为空,则返回右子树。 - 3. 如果右子树为空,则返回左子树。 - 4. 如果左右子树都为空,则返回 `None`。 -3. 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 - -### 思路 1:代码 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - if root == p or root == q: - return root - - if root: - node_left = self.lowestCommonAncestor(root.left, p, q) - node_right = self.lowestCommonAncestor(root.right, p, q) - if node_left and node_right: - return root - elif not node_left: - return node_right - else: - return node_left - return None -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0237. \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" "b/Solutions/0237. \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" deleted file mode 100644 index 898596fb..00000000 --- "a/Solutions/0237. \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\347\232\204\350\212\202\347\202\271.md" +++ /dev/null @@ -1,22 +0,0 @@ -# [0237. 删除链表中的节点](https://leetcode.cn/problems/delete-node-in-a-linked-list/) - -- 标签:链表 -- 难度:中等 - -## 题目大意 - -删除链表的给定节点。 - -## 解题思路 - -直接将该节点的后续节点覆盖该节点即可。即让该节点的值等于下一节点值,并让其 next 指针指向下一节点的下一节点。 - -## 代码 - -```python -class Solution: - def deleteNode(self, node): - node.val = node.next.val - node.next = node.next.next -``` - diff --git "a/Solutions/0238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" "b/Solutions/0238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" deleted file mode 100644 index bfce1bb4..00000000 --- "a/Solutions/0238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [0238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) - -- 标签:数组、前缀和 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 nums。 - -**要求**:返回数组 $answer$,其中 $answer[i]$ 等于 $nums$ 中除 $nums[i]$ 之外其余各元素的乘积。 - -**说明**: - -- 题目数据保证数组 $nums$ 之中任意元素的全部前缀元素和后缀的乘积都在 $32$ 位整数范围内。 -- 请不要使用除法,且在 $O(n)$ 时间复杂度内解决问题。 -- **进阶**:在 $O(1)$ 的额外空间复杂度内完成这个题目。 -- $2 \le nums.length \le 10^5$。 -- $-30 \le nums[i] \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [1,2,3,4] -输出: [24,12,8,6] -``` - -- 示例 2: - -```python -输入: nums = [-1,1,0,-3,3] -输出: [0,0,9,0,0] -``` - -## 解题思路 - -### 思路 1:两次遍历 - -1. 构造一个答案数组 $res$,长度和数组 $nums$ 长度一致。 -2. 先从左到右遍历一遍 $nums$ 数组,将 $nums[i]$ 左侧的元素乘积累积起来,存储到 $res$ 数组中。 -3. 再从右到左遍历一遍,将 $nums[i]$ 右侧的元素乘积累积起来,再乘以原本 $res[i]$ 的值,即为 $nums$ 中除了 $nums[i]$ 之外的其他所有元素乘积。 - -### 思路 1:代码 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - size = len(nums) - res = [1 for _ in range(size)] - - left = 1 - for i in range(size): - res[i] *= left - left *= nums[i] - - right = 1 - for i in range(size-1, -1, -1): - res[i] *= right - right *= nums[i] - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - - - diff --git "a/Solutions/0239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.md" "b/Solutions/0239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index ee7b36b7..00000000 --- "a/Solutions/0239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) - -- 标签:队列、数组、滑动窗口、单调队列、堆(优先队列) -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`,再给定一个整数 `k`,表示为大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 `k` 个数字,滑动窗口每次只能向右移动一位。 - -**要求**:返回滑动窗口中的最大值。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 -- $1 \le k \le nums.length$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 -输出:[3,3,5,5,6,7] -解释: -滑动窗口的位置 最大值 ---------------- ----- -[1 3 -1] -3 5 3 6 7 3 - 1 [3 -1 -3] 5 3 6 7 3 - 1 3 [-1 -3 5] 3 6 7 5 - 1 3 -1 [-3 5 3] 6 7 5 - 1 3 -1 -3 [5 3 6] 7 6 - 1 3 -1 -3 5 [3 6 7] 7 -``` - -- 示例 2: - -```python -输入:nums = [1], k = 1 -输出:[1] -``` - -## 解题思路 - -暴力求解的话,需要使用二重循环遍历,其时间复杂度为 $O(n * k)$。根据题目给定的数据范围,肯定会超时。 - -我们可以使用优先队列来做。 - -### 思路 1:优先队列 - -1. 初始的时候将前 `k` 个元素加入优先队列的二叉堆中。存入优先队列的是数组值与索引构成的元组。优先队列将数组值作为优先级。 -2. 然后滑动窗口从第 `k` 个元素开始遍历,将当前数组值和索引的元组插入到二叉堆中。 -3. 当二叉堆堆顶元素的索引已经不在滑动窗口的范围中时,即 `q[0][1] <= i - k` 时,不断删除堆顶元素,直到最大值元素的索引在滑动窗口的范围中。 -4. 将最大值加入到答案数组中,继续向右滑动。 -5. 滑动结束时,输出答案数组。 - -### 思路 1:代码 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - size = len(nums) - q = [(-nums[i], i) for i in range(k)] - heapq.heapify(q) - res = [-q[0][0]] - - for i in range(k, size): - heapq.heappush(q, (-nums[i], i)) - while q[0][1] <= i - k: - heapq.heappop(q) - res.append(-q[0][0]) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(k)$。 - diff --git "a/Solutions/0240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II.md" "b/Solutions/0240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II.md" deleted file mode 100644 index 7ea64636..00000000 --- "a/Solutions/0240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II.md" +++ /dev/null @@ -1,120 +0,0 @@ -# [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) - -- 标签:二分查找、分治算法 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的有序整数矩阵 $matrix$。$matrix$ 中的每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 $target$。 - -**要求**:判断矩阵中是否可以找到 $target$,如果可以找到 $target$,返回 `True`,否则返回 `False`。 - -**说明**: - -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le n, m \le 300$。 -- $-10^9 \le matrix[i][j] \le 10^9$。 -- 每行的所有元素从左到右升序排列。 -- 每列的所有元素从上到下升序排列。 -- $-10^9 \le target \le 10^9$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid2.jpg) - -```python -输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5 -输出:True -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid.jpg) - -```python -输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20 -输出:False -``` - -## 解题思路 - -### 思路 1:二分查找 - -矩阵是有序的,可以考虑使用二分查找来做。 - -1. 迭代对角线元素,假设对角线元素的坐标为 $(row, col)$。把数组元素按对角线分为右上角部分和左下角部分。 -2. 对于当前对角线元素右侧第 $row$ 行、对角线元素下侧第 $col$ 列分别进行二分查找。 - 1. 如果找到目标,直接返回 `True`。 - 2. 如果找不到目标,则缩小范围,继续查找。 - 3. 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def diagonalBinarySearch(self, matrix, diagonal, target): - left = 0 - right = diagonal - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][mid] < target: - left = mid + 1 - else: - right = mid - return left - - def rowBinarySearch(self, matrix, begin, cols, target): - left = begin - right = cols - while left < right: - mid = left + (right - left) // 2 - if matrix[begin][mid] < target: - left = mid + 1 - elif matrix[begin][mid] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= cols and matrix[begin][left] == target - - def colBinarySearch(self, matrix, begin, rows, target): - left = begin + 1 - right = rows - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][begin] < target: - left = mid + 1 - elif matrix[mid][begin] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= rows and matrix[left][begin] == target - - def searchMatrix(self, matrix, target: int) -> bool: - rows = len(matrix) - if rows == 0: - return False - cols = len(matrix[0]) - if cols == 0: - return False - - min_val = min(rows, cols) - index = self.diagonalBinarySearch(matrix, min_val - 1, target) - if matrix[index][index] == target: - return True - for i in range(index + 1): - row_search = self.rowBinarySearch(matrix, i, cols - 1, target) - col_search = self.colBinarySearch(matrix, i, rows - 1, target) - if row_search or col_search: - return True - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(min(m, n) \times (\log_2 m + \log_2 n))$,其中 $m$ 是矩阵的行数,$n$ 是矩阵的列数。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" "b/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" deleted file mode 100644 index e2ee372b..00000000 --- "a/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0241. 为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) - -- 标签:递归、记忆化搜索、数学、字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由数字和运算符组成的字符串 `expression`。 - -**要求**:按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以按任意顺序返回答案。 - -**说明**: - -- 生成的测试用例满足其对应输出值符合 $32$ 位整数范围,不同结果的数量不超过 $10^4$。 -- $1 \le expression.length \le 20$。 -- `expression` 由数字和算符 `'+'`、`'-'` 和 `'*'` 组成。 -- 输入表达式中的所有整数值在范围 $[0, 99]$。 - -**示例**: - -- 示例 1: - -```python -输入:expression = "2-1-1" -输出:[0,2] -解释: -((2-1)-1) = 0 -(2-(1-1)) = 2 -``` - -- 示例 2: - -```python -输入:expression = "2*3-4*5" -输出:[-34,-14,-10,-10,10] -解释: -(2*(3-(4*5))) = -34 -((2*3)-(4*5)) = -14 -((2*(3-4))*5) = -10 -(2*((3-4)*5)) = -10 -(((2*3)-4)*5) = 10 -``` - -## 解题思路 - -### 思路 1:分治算法 - -给定的字符串 `expression` 只包含有数字和字符,可以写成类似 `x op y` 的形式,其中 $x$、$y$ 为表达式或数字,$op$ 为字符。 - -则我们可以根据字符的位置,将其递归分解为 $x$、$y$ 两个部分,接着分别计算 $x$ 部分的结果与 $y$ 部分的结果。然后再将其合并。 - -### 思路 1:代码 - -```python -class Solution: - def diffWaysToCompute(self, expression: str) -> List[int]: - res = [] - if len(expression) <= 2: - res.append(int(expression)) - return res - - for i in range(len(expression)): - ch = expression[i] - if ch == '+' or ch == '-' or ch == '*': - left_cnts = self.diffWaysToCompute(expression[ :i]) - right_cnts = self.diffWaysToCompute(expression[i + 1:]) - - for left in left_cnts: - for right in right_cnts: - if ch == '+': - res.append(left + right) - elif ch == '-': - res.append(left - right) - else: - res.append(left * right) - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(C_n)$,其中 $n$ 为结果数组的大小,$C_n$ 是第 $k$ 个卡特兰数。 -- **空间复杂度**:$O(C_n)$。 diff --git "a/Solutions/0242. \346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" "b/Solutions/0242. \346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" deleted file mode 100644 index 3f6df973..00000000 --- "a/Solutions/0242. \346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [0242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) - -- 标签:哈希表、字符串、排序 -- 难度:简单 - -## 题目大意 - -给定两个字符串 s 和 t ,判断 t 和 s 是否使用了相同的字符构成(字符出现的种类和数目都相同)。 - -## 解题思路 - -1. 先判断字符串 s 和 t 的长度,不一样直接返回 false; -2. 分别遍历字符串 s 和 t。先遍历字符串 s,用哈希表存储字符串 s 中字符出现的频次; -3. 再遍历字符串 t,哈希表中减去对应字符的频次,出现频次 < 0 则输出 false; -4. 如果没出现频次 < 0,则输出 true。 - -## 代码 - -```python -def isAnagram(self, s: str, t: str) -> bool: - if len(s) != len(t): - return False - strDict = dict() - for ch in s: - if ch in strDict: - strDict[ch] += 1 - else: - strDict[ch] = 1 - for ch in t: - if ch in strDict: - strDict[ch] -= 1 - if strDict[ch] < 0: - return False - else: - return False - return True -``` - diff --git "a/Solutions/0249. \347\247\273\344\275\215\345\255\227\347\254\246\344\270\262\345\210\206\347\273\204.md" "b/Solutions/0249. \347\247\273\344\275\215\345\255\227\347\254\246\344\270\262\345\210\206\347\273\204.md" deleted file mode 100644 index cb337c8d..00000000 --- "a/Solutions/0249. \347\247\273\344\275\215\345\255\227\347\254\246\344\270\262\345\210\206\347\273\204.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [0249. 移位字符串分组](https://leetcode.cn/problems/group-shifted-strings/) - -- 标签:数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -给定一个仅包含小写字母的字符串列表。其中每个字符串都可以进行「移位」操作,也就是将字符串中的每个字母变为其在字母表中后续的字母。比如:`abc` -> `bcd`。 - -要求:将该列表中满足「移位」操作规律的组合进行分组并返回。 - -## 解题思路 - -我们可以先将满足相同「移位」操作规律的组合翻译为相同的模式,然后利用哈希表进行存储。哈希表对应关系为 翻译后模式:该模式对应的原字符串列表。 - -## 代码 - -```python -import collections -class Solution: - def groupStrings(self, strings: List[str]) -> List[List[str]]: - str_dict = collections.defaultdict(list) - for string in strings: - if string[0] == 'a': - str_dict[string].append(string) - else: - list_string = list(string) - for i in range(len(list_string)): - num = (ord(list_string[i]) - ord(string[0]) + 26) % 26 - list_string[i] = chr(num + ord('a')) - temp_string = ''.join(list_string) - str_dict[temp_string].append(string) - res = list() - for string, sublist in str_dict.items(): - res.append(sublist) - return res -``` - diff --git "a/Solutions/0257. \344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.md" "b/Solutions/0257. \344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.md" deleted file mode 100644 index 44f8882f..00000000 --- "a/Solutions/0257. \344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) - -- 标签:树、深度优先搜索、字符串、回溯、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树,返回所有从根节点到叶子节点的路径。 - -## 解题思路 - -深度优先搜索。在递归遍历时,需考虑当前节点和左右孩子节点。 - -- 如果当前节点不是叶子节点,则当前拼接路径中加入该点,并继续递归遍历。 -- 如果当前节点是叶子节点,则当前拼接路径中加入该点,并将当前路径加入答案数组。 - -## 代码 - -```python -class Solution: - def binaryTreePaths(self, root: TreeNode) -> List[str]: - res = [] - def dfs(root, path): - if not root: - return - path += str(root.val) - if not root.left and not root.right: - res.append(path) - elif not root.right: - dfs(root.left, path + "->") - elif not root.left: - dfs(root.right, path + "->") - else: - dfs(root.left, path + "->") - dfs(root.right, path + "->") - dfs(root, "") - return res -``` - diff --git "a/Solutions/0258. \345\220\204\344\275\215\347\233\270\345\212\240.md" "b/Solutions/0258. \345\220\204\344\275\215\347\233\270\345\212\240.md" deleted file mode 100644 index b28c673a..00000000 --- "a/Solutions/0258. \345\220\204\344\275\215\347\233\270\345\212\240.md" +++ /dev/null @@ -1,27 +0,0 @@ -# [0258. 各位相加](https://leetcode.cn/problems/add-digits/) - -- 标签:数学、数论、模拟 -- 难度:简单 - -## 题目大意 - -给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。 - -## 解题思路 - -根据题意,循环模拟累加即可。 - -## 代码 - -```python -class Solution: - def addDigits(self, num: int) -> int: - while num >= 10: - cur = 0 - while num: - cur += num % 10 - num //= 10 - num = cur - return num -``` - diff --git "a/Solutions/0259. \350\276\203\345\260\217\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0259. \350\276\203\345\260\217\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index e3b53b9b..00000000 --- "a/Solutions/0259. \350\276\203\345\260\217\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) - -- 标签:数组、双指针、二分查找、排序 -- 难度:中等 - -## 题目大意 - -给定一个长度为 `n` 的整数数组和一个目标值 `target`。 - -要求:寻找能够使条件 `nums[i] + nums[j] + nums[k] < target` 成立的三元组 (`i`, `j`, `k`) 的个数(`0 <= i < j < k < n`)。 - -注意:最好在 $O(n^2)$ 的时间复杂度内解决问题。 - -## 解题思路 - -三元组直接枚举的时间复杂度是 $O(n^3)$,明显不符合题目要求。那么可以考虑使用双指针减少循环内的时间复杂度。具体做法如下: - -- 先对数组进行从小到大排序。 -- 遍历数组,对于数组元素 `nums[i]`,使用两个指针 `left`、`right`。`left` 指向第 `i + 1` 个元素位置,`right` 指向数组的最后一个元素位置。 -- 在区间 `[left, right]` 中查找满足 `nums[i] + nums[left] + nums[right] < target`的方案数。 -- 计算 `nums[i]`、`nums[left]`、`nums[right]` 的和,将其与 `target` 比较。 - - 如果 `nums[i] + nums[left] + nums[right] < target`,则说明 `i`、`left`、`right` 作为三元组满足题目要求,同时说明区间 `[left, right]` 中的元素作为 `right` 都满足条件,此时将 `left` 右移,继续判断。 - - 如果 `nums[i] + nums[left] + nums[right] >= target`,则说明 `right` 太大了,应该缩小 `right`,然后继续判断。 -- 当 `left == right` 时,区间搜索完毕,继续遍历 `nums[i + 1]`。 - -这种思路使用了两重循环,其中内层循环当 `left == right` 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$,符合题目要求。 - -## 代码 - -```python -class Solution: - def threeSumSmaller(self, nums: List[int], target: int) -> int: - nums.sort() - size = len(nums) - res = 0 - for i in range(size): - left, right = i + 1, size - 1 - while left < right: - total = nums[i] + nums[left] + nums[right] - if total < target: - res += (right - left) - left += 1 - else: - right -= 1 - return res -``` - diff --git "a/Solutions/0260. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 III.md" "b/Solutions/0260. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 III.md" deleted file mode 100644 index 406403bf..00000000 --- "a/Solutions/0260. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227 III.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0260. 只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) - -- 标签:位运算、数组 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。$nums$ 中恰好有两个元素只出现一次,其余所有元素均出现两次。 - -**要求**:找出只出现一次的那两个元素。可以按任意顺序返回答案。要求时间复杂度是 $O(n)$,空间复杂度是 $O(1)$。 - -**说明**: - -- $2 \le nums.length \le 3 \times 10^4$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 -- 除两个只出现一次的整数外,$nums$ 中的其他数字都出现两次。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,1,3,2,5] -输出:[3,5] -解释:[5, 3] 也是有效的答案。 -``` - -- 示例 2: - -```python -输入:nums = [-1,0] -输出:[-1,0] -``` - -## 解题思路 - -### 思路 1:位运算 - -求解这道题之前,我们先来看看如何求解「一个数组中除了某个元素只出现一次以外,其余每个元素均出现两次。」即「[136. 只出现一次的数字](https://leetcode.cn/problems/single-number/)」问题。 - -我们可以对所有数不断进行异或操作,最终可得到单次出现的元素。 - -下面我们再来看这道题。 - -如果数组中有两个数字只出现一次,其余每个元素均出现两次。那么经过全部异或运算。我们可以得到只出现一次的两个数字的异或结果。 - -根据异或结果的性质,异或运算中如果某一位上为 $1$,则说明异或的两个数在该位上是不同的。根据这个性质,我们将数字分为两组: - -1. 一组是和该位为 $0$ 的数字, -2. 一组是该位为 $1$ 的数字。 - -然后将这两组分别进行异或运算,就可以得到最终要求的两个数字。 - -### 思路 1:代码 - -```python -class Solution: - def singleNumbers(self, nums: List[int]) -> List[int]: - all_xor = 0 - for num in nums: - all_xor ^= num - # 获取所有异或中最低位的 1 - mask = 1 - while all_xor & mask == 0: - mask <<= 1 - - a_xor, b_xor = 0, 0 - for num in nums: - if num & mask == 0: - a_xor ^= num - else: - b_xor ^= num - - return a_xor, b_xor -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 中的元素个数。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0263. \344\270\221\346\225\260.md" "b/Solutions/0263. \344\270\221\346\225\260.md" deleted file mode 100644 index c317efa9..00000000 --- "a/Solutions/0263. \344\270\221\346\225\260.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [0263. 丑数](https://leetcode.cn/problems/ugly-number/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 `n`。 - -要求:判断 `n` 是否为丑数。如果是,则返回 `True`,否则,返回 `False`。 - -- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 - -## 解题思路 - -- 如果 `n <= 0`,则 `n` 必然不是丑数,直接返回 `False`。 -- 对 `n` 分别进行 `2`、`3`、`5` 的整除操作,直到 `n` 被除完,如果 `n` 最终为 `1`,则 `n` 是丑数,否则不是丑数。 - -## 代码 - -```python -class Solution: - def isUgly(self, n: int) -> bool: - if n <= 0: - return False - factors = [2, 3, 5] - for factor in factors: - while n % factor == 0: - n //= factor - - return n == 1 -``` - diff --git "a/Solutions/0264. \344\270\221\346\225\260 II.md" "b/Solutions/0264. \344\270\221\346\225\260 II.md" deleted file mode 100644 index 8fdabb94..00000000 --- "a/Solutions/0264. \344\270\221\346\225\260 II.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) - -- 标签:哈希表、数学、动态规划、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:找出并返回第 `n` 个丑数。 - -- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 - -## 解题思路 - -动态规划求解。 - -定义状态 `dp[i]` 表示第 `i` 个丑数。 - -状态转移方程为:`dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)` ,其中 `p2`、`p3`、`p5` 分别表示当前 `i` 中 `2`、`3`、`5` 的质因子数量。 - -## 代码 - -```python -class Solution: - def nthUglyNumber(self, n: int) -> int: - dp = [1 for _ in range(n)] - p2, p3, p5 = 0, 0, 0 - for i in range(1, n): - dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5) - if dp[i] == dp[p2] * 2: - p2 += 1 - if dp[i] == dp[p3] * 3: - p3 += 1 - if dp[i] == dp[p5] * 5: - p5 += 1 - return dp[n - 1] -``` - diff --git "a/Solutions/0268. \344\270\242\345\244\261\347\232\204\346\225\260\345\255\227.md" "b/Solutions/0268. \344\270\242\345\244\261\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 025a7970..00000000 --- "a/Solutions/0268. \344\270\242\345\244\261\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) - -- 标签:位运算、数组、哈希表、数学、二分查找、排序 -- 难度:简单 - -## 题目大意 - -给定一个长度为 n 的数组 nums,nums 包含 [0, n] 中的 n 个数,要求找出 [0, n] 范围内没有出现在数组中的那个数。 - -- n == len(nums) - -- $1 \le n \le 10^4$ - -- $0 \le nums[i] \le n$ - -- nums 中的数字是唯一的 - - - -## 解题思路 - -[0, n] 的范围有 n+1 个数(包含 0)。现在给了我们 n 个数,要求找出其中缺失的那个数。 - -### 1. 哈希表 - -将 nums 中所有元素插入到哈希表中,然后遍历 [0, n],找到缺失的数字。 - -这里的哈希表也可以用长度为 n+1 的数组代替。 - -### 2. 数学计算 - -已知 [0, n] 的求和公式为:$\sum_{i=0}^n i = \frac{n*(n+1)}{2}$,则用 [0, n] 的和,减去数组中所有元素的和,就得到了缺失数字。 - -## 代码 - -1. 哈希表 -```python -class Solution: - def missingNumber(self, nums: List[int]) -> int: - numSet = set(nums) - - for num in range(len(nums)+1): - if num not in numSet: - return num -``` - -2. 数学计算 -```python -class Solution: - def missingNumber(self, nums: List[int]) -> int: - sum_nums = sum(nums) - n = len(nums) - return (n+1)*n//2 - sum_nums -``` - diff --git "a/Solutions/0270. \346\234\200\346\216\245\350\277\221\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\200\274.md" "b/Solutions/0270. \346\234\200\346\216\245\350\277\221\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\200\274.md" deleted file mode 100644 index 8544d829..00000000 --- "a/Solutions/0270. \346\234\200\346\216\245\350\277\221\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\200\274.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [0270. 最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) - -- 标签:树、深度优先搜索、二叉搜索树、二分查找、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个不为空的二叉搜索树,以及一个目标值 target。要求在二叉搜索树中找到最接近目标值 target 的数值。 - -## 解题思路 - -题目中最接近目标值 target 的数值指的就是 与 target 相减绝对值最小的数值。 - -而且根据二叉搜索树的性质,我们可以利用二分搜索的方式,查找与 target 相减绝对值最小的数值。具体做法为: - -- 定义一个变量 closest 表示与 target 最接近的数值,初始赋值为根节点的值 root.val。 -- 判断当前节点的值域 closet 值哪个更接近 target,如果当前值更接近,则更新 closest。 -- 如果 target < 当前节点值,则从当前节点的左子树继续查找。 -- 如果 target ≥ 当前节点值,则从当前节点的右子树继续查找。 - -## 代码 - -```python -class Solution: - def closestValue(self, root: TreeNode, target: float) -> int: - closest = root.val - while root: - if abs(target - root.val) < abs(target - closest): - closest = root.val - if target < root.val: - root = root.left - else: - root = root.right - return closest -``` - diff --git "a/Solutions/0278. \347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.md" "b/Solutions/0278. \347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.md" deleted file mode 100644 index 504f17dd..00000000 --- "a/Solutions/0278. \347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [0278. 第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -**描述**:给你一个整数 $n$,代表已经发布的版本号。还有一个用于检测版本是否出错的接口 `isBadVersion(version):` 。 - -**要求**:找出第一次出错的版本号 $bad$。 - -**说明**: - -- 要求尽可能减少对 `isBadVersion(version):` 接口的调用。 -- $1 \le bad \le n \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 5, bad = 4 -输出:4 -解释: -调用 isBadVersion(3) -> false -调用 isBadVersion(5) -> true -调用 isBadVersion(4) -> true -所以,4 是第一个错误的版本。 -``` - -- 示例 2: - -```python -输入:n = 1, bad = 1 -输出:1 -``` - -## 解题思路 - -### 思路 1:二分查找 - -题目要求尽可能减少对 `isBadVersion(version):` 接口的调用,所以不能对每个版本都调用接口,而是应该将接口调用的次数降到最低。 - -可以注意到:如果检测某个版本不是错误版本时,则该版本之前的所有版本都不是错误版本。而当某个版本是错误版本时,则该版本之后的所有版本都是错误版本。我们可以利用这样的性质,在 $[1, n]$ 的区间内使用二分查找方法,从而在 $O(\log n)$ 时间复杂度内找到第一个出错误的版本。 - -### 思路 1:代码 - -```python -class Solution: - def firstBadVersion(self, n): - left = 1 - right = n - while left < right: - mid = (left + right) // 2 - if isBadVersion(mid): - right = mid - else: - left = mid + 1 - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" "b/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" deleted file mode 100644 index 3d206cba..00000000 --- "a/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" +++ /dev/null @@ -1,153 +0,0 @@ -# [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) - -- 标签:广度优先搜索、数学、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个正整数 $n$。从中找到若干个完全平方数(比如 $1, 4, 9, 16 …$),使得它们的和等于 $n$。 - -**要求**:返回和为 $n$ 的完全平方数的最小数量。 - -**说明**: - -- $1 \le n \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 12 -输出:3 -解释:12 = 4 + 4 + 4 -``` - -- 示例 2: - -```python -输入:n = 13 -输出:2 -解释:13 = 4 + 9 -``` - -## 解题思路 - -暴力枚举思路:对于小于 $n$ 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 - -并且对于所有小于 $n$ 的完全平方数($k = 1, 4, 9, 16, ...$),存在公式:$ans(n) = min(ans(n - k) + 1), k = 1, 4, 9, 16 ...$ - -即: **n 的完全平方数的最小数量 == n - k 的完全平方数的最小数量 + 1**。 - -我们可以使用递归解决这个问题。但是因为重复计算了中间解,会产生堆栈溢出。 - -那怎么解决重复计算问题和避免堆栈溢出? - -我们可以转换一下思维。 - -1. 将 $n$ 作为根节点,构建一棵多叉数。 -2. 从 $n$ 节点出发,如果一个小于 $n$ 的数刚好与 $n$ 相差一个平方数,则以该数为值构造一个节点,与 $n$ 相连。 - -那么求解和为 $n$ 的完全平方数的最小数量就变成了求解这棵树从根节点 $n$ 到节点 $0$ 的最短路径,或者说树的最小深度。 - -这个过程可以通过广度优先搜索来做。 - -### 思路 1:广度优先搜索 - -1. 定义 $visited$ 为标记访问节点的 set 集合变量,避免重复计算。定义 $queue$ 为存放节点的队列。使用 $count$ 表示为树的最小深度,也就是和为 $n$ 的完全平方数的最小数量。 -2. 首先,我们将 $n$ 标记为已访问,即 `visited.add(n)`。并将其加入队列 $queue$ 中,即 `queue.append(n)`。 -3. 令 $count$ 加 $1$,表示最小深度加 $1$。然后依次将队列中的节点值取出。 -4. 对于取出的节点值 $value$,遍历可能出现的平方数(即遍历 $[1, \sqrt{value} + 1]$ 中的数)。 -5. 每次从当前节点值减去一个平方数,并将减完的数加入队列。 - 1. 如果此时的数等于 $0$,则满足题意,返回当前树的最小深度。 - 2. 如果此时的数不等于 $0$,则将其加入队列,继续查找。 - -### 思路 1:代码 - -```python -class Solution: - def numSquares(self, n: int) -> int: - if n == 0: - return 0 - - visited = set() - queue = collections.deque([]) - - visited.add(n) - queue.append(n) - - count = 0 - while queue: - // 最少步数 - count += 1 - size = len(queue) - for _ in range(size): - value = queue.pop() - for i in range(1, int(math.sqrt(value)) + 1): - x = value - i * i - if x == 0: - return count - if x not in visited: - queue.appendleft(x) - visited.add(x) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \sqrt{n})$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:动态规划 - -我们可以将这道题转换为「完全背包问题」中恰好装满背包的方案数问题。 - -1. 将 $k = 1, 4, 9, 16, ...$ 看做是 $k$ 种物品,每种物品都可以无限次使用。 -2. 将 $n$ 看做是背包的装载上限。 -3. 这道题就变成了,从 $k$ 种物品中选择一些物品,装入装载上限为 $n$ 的背包中,恰好装满背包最少需要多少件物品。 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:从完全平方数中挑选一些数,使其和恰好凑成 $w$ ,最少需要多少个完全平方数。 - -###### 3. 状态转移方程 - -$dp[w] = min \lbrace dp[w], dp[w - num] + 1$ - -###### 4. 初始条件 - -- 恰好凑成和为 $0$,最少需要 $0$ 个完全平方数。 -- 默认情况下,在不使用完全平方数时,都不能恰好凑成和为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入装载上限为 $w$ 的背包中,恰好装满背包,最少需要多少件物品。 所以最终结果为 $dp[n]$。 - -1. 如果 $dp[n] \ne n + 1$,则说明:$dp[n]$ 为装入装载上限为 $n$ 的背包,恰好装满背包,最少需要的物品数量,则返回 $dp[n]$。 -2. 如果 $dp[n] = n + 1$,则说明:无法恰好装满背包,则返回 $-1$。因为 $n$ 肯定能由 $n$ 个 $1$ 组成,所以这种情况并不会出现。 - -### 思路 2:代码 - -```python -class Solution: - def numSquares(self, n: int) -> int: - dp = [n + 1 for _ in range(n + 1)] - dp[0] = 0 - - for i in range(1, int(sqrt(n)) + 1): - num = i * i - for w in range(num, n + 1): - dp[w] = min(dp[w], dp[w - num] + 1) - - if dp[n] != n + 1: - return dp[n] - return -1 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \sqrt{n})$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0283. \347\247\273\345\212\250\351\233\266.md" "b/Solutions/0283. \347\247\273\345\212\250\351\233\266.md" deleted file mode 100644 index e486d41e..00000000 --- "a/Solutions/0283. \347\247\273\345\212\250\351\233\266.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) - -- 标签:数组、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $nums$。 - -**要求**:将所有 $0$ 移动到末尾,并保持原有的非 $0$ 数字的相对顺序。 - -**说明**: - -- 只能在原数组上进行操作。 -- $1 \le nums.length \le 10^4$。 -- $-2^{31} \le nums[i] \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [0,1,0,3,12] -输出: [1,3,12,0,0] -``` - -- 示例 2: - -```python -输入: nums = [0] -输出: [0] -``` - -## 解题思路 - -### 思路 1:冒泡排序(超时) - -冒泡排序的思想,就是通过相邻元素的比较与交换,使得较大元素从前面移到后面。 - -我们可以借用冒泡排序的思想,将值为 $0$ 的元素移动到数组末尾。 - -因为数据规模为 $10^4$,而冒泡排序的时间复杂度为 $O(n^2)$。所以这种做法会导致超时。 - -### 思路 1:代码 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - for i in range(len(nums)): - for j in range(len(nums) - i - 1): - if nums[j] == 0 and nums[j + 1] != 0: - nums[j], nums[j + 1] = nums[j + 1], nums[j] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:快慢指针 - -1. 使用两个指针 $slow$,$fast$。$slow$ 指向处理好的非 $0$ 数字数组的尾部,$fast$ 指针指向当前待处理元素。 -2. 不断向右移动 $fast$ 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 $slow$ 右移。 -3. 此时,$slow$ 指针左侧均为处理好的非零数,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边为止都为 $0$。 - -遍历结束之后,则所有 $0$ 都移动到了右侧,且保持了非零数的相对位置。 - -### 思路 2:代码 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - slow = 0 - fast = 0 - while fast < len(nums): - if nums[fast] != 0: - nums[slow], nums[fast] = nums[fast], nums[slow] - slow += 1 - fast += 1 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0285. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" "b/Solutions/0285. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" deleted file mode 100644 index 4fbd7cbc..00000000 --- "a/Solutions/0285. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0285. 二叉搜索树中的中序后继](https://leetcode.cn/problems/inorder-successor-in-bst/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root`。 - -要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 - -## 解题思路 - -可以分为两步: - -1. 中序遍历二叉搜索树,将节点先存储到列表中。 -2. 将列表中的节点构造成一棵递增顺序搜索树。 - -中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 - -构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 - -最后返回 `head.right` 即可。 - -## 代码 - -```python -class Solution: - def inOrder(self, root, res): - if not root: - return - self.inOrder(root.left, res) - res.append(root) - self.inOrder(root.right, res) - - def increasingBST(self, root: TreeNode) -> TreeNode: - res = [] - self.inOrder(root, res) - - if not res: - return - head = TreeNode(-1) - cur = head - for node in res: - node.left = node.right = None - cur.right = node - cur = cur.right - return head.right -``` - diff --git "a/Solutions/0286. \345\242\231\344\270\216\351\227\250.md" "b/Solutions/0286. \345\242\231\344\270\216\351\227\250.md" deleted file mode 100644 index dec1371d..00000000 --- "a/Solutions/0286. \345\242\231\344\270\216\351\227\250.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0286. 墙与门](https://leetcode.cn/problems/walls-and-gates/) - -- 标签:广度优先搜索、数组、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 的二维网络 `rooms`。其中每个元素有三种初始值: - -- `-1` 表示墙或者障碍物 -- `0` 表示一扇门 -- `INF` 表示为一个空的房间。这里用 $2^{31} = 2147483647$ 表示 `INF`。通往门的距离总是小于 $2^{31}$。 - -要求:给每个空房间填上该房间到最近的门的距离,如果无法到达门,则填 `INF`。 - -## 解题思路 - -从每个表示门开始,使用广度优先搜索去照门。因为广度优先搜索保证我们在搜索 `dist + 1` 距离的位置时,距离为 `dist` 的位置都已经搜索过了。所以每到达一个房间的时候一定是最短距离。 - -## 代码 - -```python -class Solution: - def wallsAndGates(self, rooms: List[List[int]]) -> None: - """ - Do not return anything, modify rooms in-place instead. - """ - INF = 2147483647 - rows = len(rooms) - if rows == 0: - return - cols = len(rooms[0]) - - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - queue = [] - for i in range(rows): - for j in range(cols): - if rooms[i][j] == 0: - queue.append((i, j, 0)) - - while queue: - i, j, dist = queue.pop(0) - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - if 0 <= new_i < rows and 0 <= new_j < cols and rooms[new_i][new_j] == INF: - rooms[new_i][new_j] = dist + 1 - queue.append((new_i, new_j, dist + 1)) -``` - diff --git "a/Solutions/0287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" "b/Solutions/0287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" deleted file mode 100644 index 236a6bee..00000000 --- "a/Solutions/0287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) - -- 标签:位运算、数组、双指针、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个包含 $n + 1$ 个整数的数组 $nums$,里边包含的值都在 $1 \sim n$ 之间。可知至少存在一个重复的整数。 - -**要求**:假设 $nums$ 中只存在一个重复的整数,要求找出这个重复的数。 - -**说明**: - -- $1 \le n \le 10^5$。 -- $nums.length == n + 1$。 -- $1 \le nums[i] \le n$。 -- 要求使用空间复杂度为常数级 $O(1)$,时间复杂度小于 $O(n^2)$ 的解决方法。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,3,4,2,2] -输出:2 -``` - -- 示例 2: - -```python -输入:nums = [3,1,3,4,2] -输出:3 -``` - -## 解题思路 - -### 思路 1:二分查找 - -利用二分查找的思想。 - -1. 使用两个指针 $left$,$right$。$left$ 指向 $1$,$right$ 指向 $n$。 -2. 将区间 $[1, n]$ 分为 $[left, mid]$ 和 $[mid + 1, right]$。 -3. 对于中间数 $mid$,统计 $nums$ 中小于等于 $mid$ 的数个数 $cnt$。 -4. 如果 $cnt \le mid$,则重复数一定不会出现在左侧区间,那么从右侧区间开始搜索。 -5. 如果 $cut > mid$,则重复数出现在左侧区间,则从左侧区间开始搜索。 - -### 思路 1:代码 - -```python -class Solution: - def findDuplicate(self, nums: List[int]) -> int: - n = len(nums) - left = 1 - right = n - 1 - while left < right: - mid = left + (right - left) // 2 - cnt = 0 - for num in nums: - if num <= mid: - cnt += 1 - - if cnt <= mid: - left = mid + 1 - else: - right = mid - - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0288. \345\215\225\350\257\215\347\232\204\345\224\257\344\270\200\347\274\251\345\206\231.md" "b/Solutions/0288. \345\215\225\350\257\215\347\232\204\345\224\257\344\270\200\347\274\251\345\206\231.md" deleted file mode 100644 index eebaafd7..00000000 --- "a/Solutions/0288. \345\215\225\350\257\215\347\232\204\345\224\257\344\270\200\347\274\251\345\206\231.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [0288. 单词的唯一缩写](https://leetcode.cn/problems/unique-word-abbreviation/) - -- 标签:设计、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -单词缩写规则:<起始字母><中间字母><结尾字母>。如果单词长度不超过 2,则单词本身就是缩写。 - -举例: - -- `dog --> d1g`:第一个字母`d`,最后一个字母 `g`,中间隔着 1 个字母。 -- `internationalization --> i18n`:第一个字母 `i` ,最后一个字母 `n`,中间隔着 18 个字母。 -- `it --> it`:单词只有两个字符,它就是它自身的缩写。 - -要求实现 ValidWordAbbr 类: - -- `ValidWordAbbr(dictionary: List[str]):`使用单词字典初始化对象 -- `def isUnique(self, word: str) -> bool:` - - 如果字典 dictionary 中没有其他单词的缩写与该单词 word 的缩写相同,返回 True。 - - 如果字典 dictionary 中所有与该单词 word 的缩写相同的单词缩写都与 word 相同。 - -## 解题思路 - -将相同缩写的单词进行分类,利用哈希表进行存储。键值对格式为 缩写:该缩写对应的 word 列表。 - -然后初始化的时候,将 dictionary 里的单词按照缩写进行哈希表存储。 - -在判断的时候,先判断单词 word 的缩写是否能在哈希表中找到对应的映射关系。 - -- 如果 word 的缩写 abbr 没有在哈希表中,则返回 True。 -- 如果 word 的缩写 abbr 在哈希表中: - - 如果缩写 abbr 对应的字符串列表只有一个字符串,并且就是 word,则返回 True。Ï - - 否则返回 False。 -- 不满足上述要求也返回 False。 - -## 代码 - -```python - def isUnique(self, word: str) -> bool: - if len(word) <= 2: - abbr = word - else: - abbr = word[0] + chr(len(word)-2) + word[-1] - if abbr not in self.abbr_dict: - return True - if len(set(self.abbr_dict[abbr])) == 1 and word in set(self.abbr_dict[abbr]): - return True - return False -``` - diff --git "a/Solutions/0289. \347\224\237\345\221\275\346\270\270\346\210\217.md" "b/Solutions/0289. \347\224\237\345\221\275\346\270\270\346\210\217.md" deleted file mode 100644 index 11e944b5..00000000 --- "a/Solutions/0289. \347\224\237\345\221\275\346\270\270\346\210\217.md" +++ /dev/null @@ -1,109 +0,0 @@ -# [0289. 生命游戏](https://leetcode.cn/problems/game-of-life/) - -- 标签:数组、矩阵、模拟 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的二维数组 $board$,每一个格子都可以看做是一个细胞。每个细胞都有一个初始状态:$1$ 代表活细胞,$0$ 代表死细胞。每个细胞与其相邻的八个位置(水平、垂直、对角线)细胞遵循以下生存规律: - -- 如果活细胞周围八个位置的活细胞数少于 $2$ 个,则该位置活细胞死亡; -- 如果活细胞周围八个位置有 $2$ 个或 $3$ 个活细胞,则该位置活细胞仍然存活; -- 如果活细胞周围八个位置有超过 $3$ 个活细胞,则该位置活细胞死亡; -- 如果死细胞周围正好有 $3$ 个活细胞,则该位置死细胞复活。 - -二维数组代表的下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的的。其中细胞的出生和死亡是同时发生的。 - -现在给定 $m \times n$ 的二维数组 $board$ 的当前状态。 - -**要求**:返回下一个状态。 - -**说明**: - -- $m == board.length$。 -- $n == board[i].length$。 -- $1 \le m, n \le 25$。 -- $board[i][j]$ 为 $0$ 或 $1$。 -- **进阶**: - - 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。 - - 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题? - - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/12/26/grid1.jpg) - -```python -输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]] -输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/12/26/grid2.jpg) - -```python -输入:board = [[1,1],[1,0]] -输出:[[1,1],[1,1]] -``` - -## 解题思路 - -### 思路 1:模拟 - -因为下一个状态隐含了过去细胞的状态,所以不能直接在原二维数组上直接进行修改。细胞的状态总共有四种情况: - -- 死细胞 -> 死细胞,即 $0 \rightarrow 0$。 -- 死细胞 -> 活细胞,即 $0 \rightarrow 1$。 -- 活细胞 -> 活细胞,即 $1 \rightarrow 1$。 -- 活细胞 -> 死细胞,即 $1 \rightarrow 0$。 - -死细胞 -> 死细胞,活细胞 -> 活细胞,不会对前后状态造成影响,所以主要考虑另外两种情况。我们把活细胞 -> 死细胞暂时标记为 $-1$,并且统计每个细胞周围活细胞数量时,使用绝对值统计,这样 $abs(-1)$ 也可以暂时标记为活细胞。然后把死细胞 -> 活细胞暂时标记为 $2$,这样判断的时候也不会统计上去。然后开始遍历。 - -- 遍历二维数组的每一个位置。并对该位置遍历周围八个位置,计算出八个位置上的活细胞数量。 - - 如果此位置是活细胞,并且周围活细胞少于 $2$ 个或超过 $3$ 个,则将其暂时标记为 $-1$,意为此细胞死亡。 - - 如果此位置是死细胞,并且周围有 $3$ 个活细胞,则将暂时标记为 $2$,意为此细胞复活。 -- 遍历完之后,再次遍历一遍二维数组,如果该位置为 $-1$,将其赋值为 $0$,如果该位置为 $2$,将其赋值为 $1$。 - -### 思路 1:代码 - -```python -class Solution: - def gameOfLife(self, board: List[List[int]]) -> None: - """ - Do not return anything, modify board in-place instead. - """ - directions = {(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)} - - rows = len(board) - cols = len(board[0]) - - for row in range(rows): - for col in range(cols): - lives = 0 - for direction in directions: - new_row = row + direction[0] - new_col = col + direction[1] - - if 0 <= new_row < rows and 0 <= new_col < cols and abs(board[new_row][new_col]) == 1: - lives += 1 - if board[row][col] == 1 and (lives < 2 or lives > 3): - board[row][col] = -1 - if board[row][col] == 0 and lives == 3: - board[row][col] = 2 - - for row in range(rows): - for col in range(cols): - if board[row][col] == -1: - board[row][col] = 0 - elif board[row][col] == 2: - board[row][col] = 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为 $board$ 的行数和列数。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0290. \345\215\225\350\257\215\350\247\204\345\276\213.md" "b/Solutions/0290. \345\215\225\350\257\215\350\247\204\345\276\213.md" deleted file mode 100644 index fcedd8d8..00000000 --- "a/Solutions/0290. \345\215\225\350\257\215\350\247\204\345\276\213.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [0290. 单词规律](https://leetcode.cn/problems/word-pattern/) - -- 标签:哈希表、字符串 -- 难度:简单 - -## 题目大意 - -给定一种规律 `pattern` 和一个字符串 `str` ,判断 `str` 是否完全匹配相同的规律。 - -- 完全匹配相同的规律:pattern 的每个字母和字符串 str 中的每个非空单词之间存在这双向连接的对应规律。 -- 比如:pattern = "abba", str = "dog cat cat dog",其对应关系为:`a <=> dog,b <=> cat` - -## 解题思路 - -这道题要求判断规律串中的字符与所给字符串中的非空单词,是否是一一对应的。即每个字符都能映射到对应的非空单词,每个非空单词也能映射为字符。 - -考虑使用两个哈希表,一个用来存储字符到非空单词的映射,另一个用来存储非空单词到字符的映射。 - -遍历 pattern 中的字符: - -- 如果字符出现在第一个字典中,且字典中的值不等于对应的非空单词,则返回 False。 -- 如果单词出现在第二个字典中,且字典中的值不等于对应的字符,则返回 False。 - -- 如果遍历完仍没发现不满足要求的情况,则返回 True。 - -## 代码 - -```python -class Solution: - def wordPattern(self, pattern: str, s: str) -> bool: - pattern_dict = dict() - word_dict = dict() - words = s.split() - - if len(pattern) != len(words): - return False - - for i in range(len(words)): - p = pattern[i] - word = words[i] - if p in pattern_dict and pattern_dict[p] != word: - return False - if word in word_dict and word_dict[word] != p: - return False - pattern_dict[p] = word - word_dict[word] = p - return True -``` - diff --git "a/Solutions/0292. Nim \346\270\270\346\210\217.md" "b/Solutions/0292. Nim \346\270\270\346\210\217.md" deleted file mode 100644 index 3bddf16b..00000000 --- "a/Solutions/0292. Nim \346\270\270\346\210\217.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [0292. Nim 游戏](https://leetcode.cn/problems/nim-game/) - -- 标签:脑筋急转弯、数学、博弈 -- 难度:简单 - -## 题目大意 - -两个人玩 Nim 游戏。游戏规则是这样的: - -- 桌上有一堆石子,两个人轮流从石子堆中拿走 1~3 块石头。拿掉最后一块石头的人就是获胜者。 - -- 假如每个人都尽可能的想赢得比赛,所以每一轮都是最优解。 - -现在给定一个整数 n 代表石头数目。如果你作为先手,问最终能否赢得比赛。 - -## 解题思路 - -假设石子的数量为 1~3,那么我作为先手,肯定第一次就将所有的石子都拿完了,所以肯定能赢。 - -假设石子的数量为 4,那么我作为先手,无论第一次拿走 1、2、3 块石头,都不能拿完,而第二个人再拿的时候,会直接将剩下的石头一次性全拿走,所以肯定不会赢。 - -如果石子数量多于 4,那么我作为先手,为了赢,应该尽可能使得本轮拿走后的石子数为 4,这样对手拿完一次之后,自己肯定会获胜。 - -所以石子树为 5、6、7 块的时候,我可以通过分别拿走 1、2、3 块石头,使得剩下的石头数为 4,从而在下一轮获得胜利。 - -如果石子数为 8 块的时候,我无论怎么拿都不能使剩下石子为 4。而对方又会利用这个机会使得他拿走之后的石子数变为 4,从而使我失败。 - -所以,很显然:当 n 不是 4 的整数倍时,我一定赢得比赛。当 n 为 4 的整数倍时,我一定赢不了比赛。 - -## 代码 - -```python -class Solution: - def canWinNim(self, n: int) -> bool: - return n % 4 != 0 -``` - diff --git "a/Solutions/0295. \346\225\260\346\215\256\346\265\201\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0295. \346\225\260\346\215\256\346\265\201\347\232\204\344\270\255\344\275\215\346\225\260.md" deleted file mode 100644 index 455492c3..00000000 --- "a/Solutions/0295. \346\225\260\346\215\256\346\265\201\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [0295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) - -- 标签:设计、双指针、数据流、排序、堆(优先队列) -- 难度:困难 - -## 题目大意 - -要求:设计一个支持一下两种操作的数组结构: - -- `void addNum(int num)`:从数据流中添加一个整数到数据结构中。 -- `double findMedian()`:返回目前所有元素的中位数。 - -## 解题思路 - -使用一个大顶堆 `queMax` 记录大于中位数的数,使用一个小顶堆 `queMin` 小于中位数的数。 - -- 当添加元素数量为偶数: `queMin` 和 `queMax` 中元素数量相同,则中位数为它们队头的平均值。 -- 当添加元素数量为奇数:`queMin` 中的数比 `queMax` 多一个,此时中位数为 `queMin` 的队头。 - -为了满足上述条件,在进行 `addNum` 操作时,我们应当分情况处理: - -- `num > max{queMin}`:此时 `num` 大于中位数,将该数添加到大顶堆 `queMax` 中。新的中位数将大于原来的中位数,所以可能需要将 `queMax` 中的最小数移动到 `queMin` 中。 -- `num ≤ max{queMin}`:此时 `num` 小于中位数,将该数添加到小顶堆 `queMin` 中。新的中位数将小于等于原来的中位数,所以可能需要将 `queMin` 中最大数移动到 `queMax` 中。 - -## 代码 - -```python -import heapq - -class MedianFinder: - - def __init__(self): - """ - initialize your data structure here. - """ - self.queMin = list() - self.queMax = list() - - - def addNum(self, num: int) -> None: - if not self.queMin or num < -self.queMin[0]: - heapq.heappush(self.queMin, -num) - if len(self.queMax) + 1 < len(self.queMin): - heapq.heappush(self.queMax, -heapq.heappop(self.queMin)) - else: - heapq.heappush(self.queMax, num) - if len(self.queMax) > len(self.queMin): - heapq.heappush(self.queMin, -heapq.heappop(self.queMax)) - - - def findMedian(self) -> float: - if len(self.queMin) > len(self.queMax): - return -self.queMin[0] - return (-self.queMin[0] + self.queMax[0]) / 2 -``` - diff --git "a/Solutions/0297. \344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.md" "b/Solutions/0297. \344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.md" deleted file mode 100644 index 093c0dc2..00000000 --- "a/Solutions/0297. \344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 -- 难度:困难 - -## 题目大意 - -**要求**:设计一个算法,来实现二叉树的序列化与反序列化。 - -**说明**: - -- 不限定序列化 / 反序列化算法执行逻辑,只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 -- 树中结点数在范围 $[0, 10^4]$ 内。 -- $-1000 \le Node.val \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) - -```python -输入:root = [1,2,3,null,null,4,5] -输出:[1,2,3,null,null,4,5] -``` - -- 示例 2: - -```python -输入:root = [1,2] -输出:[1,2] -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -#### 1. 序列化:将二叉树转为字符串数据表示 - -1. 按照前序顺序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 - -> 注意:如果遇到空节点,则将其标记为 `None`,这样在反序列化时才能唯一确定一棵二叉树。 - -#### 2. 反序列化:将字符串数据转为二叉树结构 - -1. 先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 -2. 从数组左侧取出一个元素。 - 1. 如果当前元素为 `None`,则返回 `None`。 - 2. 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 -3. 最后返回当前根节点。 - -### 思路 1:代码 - -```python -class Codec: - - def serialize(self, root): - """Encodes a tree to a single string. - - :type root: TreeNode - :rtype: str - """ - if not root: - return 'None' - return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) - - def deserialize(self, data): - """Decodes your encoded data to tree. - - :type data: str - :rtype: TreeNode - """ - def dfs(datalist): - val = datalist.pop(0) - if val == 'None': - return None - root = TreeNode(int(val)) - root.left = dfs(datalist) - root.right = dfs(datalist) - return root - - datalist = data.split(',') - return dfs(datalist) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index f63c826c..00000000 --- "a/Solutions/0300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) - -- 标签:数组、二分查找、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:找到其中最长严格递增子序列的长度。 - -**说明**: - -- **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,$[3,6,2,7]$ 是数组 $[0,3,1,6,2,2,7]$ 的子序列。 -- $1 \le nums.length \le 2500$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [10,9,2,5,3,7,101,18] -输出:4 -解释:最长递增子序列是 [2,3,7,101],因此长度为 4。 -``` - -- 示例 2: - -```python -输入:nums = [0,1,0,3,2,3] -输出:4 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照子序列的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。 - -###### 3. 状态转移方程 - -一个较小的数后边如果出现一个较大的数,则会形成一个更长的递增子序列。 - -对于满足 $0 \le j < i$ 的数组元素 $nums[j]$ 和 $nums[i]$ 来说: - -- 如果 $nums[j] < nums[i]$,则 $nums[i]$ 可以接在 $nums[j]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[j]$ 结尾的最长递增子序列长度」的基础上加 $1$,即 $dp[i] = dp[j] + 1$。 - -- 如果 $nums[j] \le nums[i]$,则 $nums[i]$ 不可以接在 $nums[j]$ 后面,可以直接跳过。 - -综上,我们的状态转移方程为:$dp[i] = max(dp[i], dp[j] + 1), 0 \le j < i, nums[j] < nums[i]$。 - -###### 4. 初始条件 - -默认状态下,把数组中的每个元素都作为长度为 $1$ 的递增子序列。即 $dp[i] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。那为了计算出最大的最长递增子序列长度,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - size = len(nums) - dp = [1 for _ in range(size)] - - for i in range(size): - for j in range(i): - if nums[i] > nums[j]: - dp[i] = max(dp[i], dp[j] + 1) - - return max(dp) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.md" "b/Solutions/0303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.md" deleted file mode 100644 index d43f61ac..00000000 --- "a/Solutions/0303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.md" +++ /dev/null @@ -1,157 +0,0 @@ -# [0303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) - -- 标签:设计、数组、前缀和 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`。 - -**要求**:实现 `NumArray` 类,该类能处理区间为 `[left, right]` 之间的区间求和的多次查询。 - -`NumArray` 类: - -- `NumArray(int[] nums)` 使用数组 `nums` 初始化对象。 -- `int sumRange(int i, int j)` 返回数组 `nums` 中索引 `left` 和 `right` 之间的元素的 总和 ,包含 `left` 和 `right` 两点(也就是 `nums[left] + nums[left + 1] + ... + nums[right]`)。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $-10^5 \le nums[i] \le 10^5$。 -- $0 \le left \le right < nums.length$。 -- `sumRange` 方法调用次数不超过 $10^4$ 次。 - -**示例**: - -- 示例 1: - -```python -给定 nums = [-2, 0, 3, -5, 2, -1] - -求和 sumRange(0, 2) -> 1 -求和 sumRange(2, 5) -> -1 -求和 sumRange(0, 5) -> -3 -``` - -## 解题思路 - -### 思路 1:线段树 - -- 根据 `nums` 数组,构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。 - -这样构建线段树的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 - -### 思路 1 线段树代码: - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - - # 单点更新实现方法:将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - - # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - -class NumArray: - - def __init__(self, nums: List[int]): - self.STree = SegmentTree(nums, lambda x, y: x + y) - - - def sumRange(self, left: int, right: int) -> int: - return self.STree.query_interval(left, right) -``` diff --git "a/Solutions/0304. \344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.md" "b/Solutions/0304. \344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.md" deleted file mode 100644 index b6cafff1..00000000 --- "a/Solutions/0304. \344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [0304. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) - -- 标签:设计、数组、矩阵、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个二维矩阵 `matrix`。 - -要求:满足以下多个请求: - -- ` def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:`计算以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和。 -- `def __init__(self, matrix: List[List[int]]):` 对二维矩阵 `matrix` 进行初始化操作。 - -## 解题思路 - -在进行初始化的时候做预处理,这样在多次查询时可以减少重复计算,也可以减少时间复杂度。 - -在进行初始化的时候,使用一个二维数组 `pre_sum` 记录下以 `(0, 0)` 为左上角,以当前 `(row, col)` 为右下角的子数组各个元素和,即 `pre_sum[row + 1][col + 1]`。 - -则在查询时,以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和就等于以 `(0, 0)` 到 `(row2, col2)` 的大子矩阵减去左边 `(0, 0)` 到 `(row2, col1 - 1)`的子矩阵,再减去上边 `(0, 0)` 到 `(row1 - 1, col2)` 的子矩阵,再加上左上角 `(0, 0)` 到 `(row1 - 1, col1 - 1)` 的子矩阵(因为之前重复减了)。即 `pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1]`。 - -## 代码 - -```python -class NumMatrix: - - def __init__(self, matrix: List[List[int]]): - rows = len(matrix) - cols = len(matrix[0]) - self.pre_sum = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] - for row in range(rows): - for col in range(cols): - self.pre_sum[row + 1][col + 1] = self.pre_sum[row + 1][col] + self.pre_sum[row][col + 1] - self.pre_sum[row][col] + matrix[row][col] - - - def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int: - return self.pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1] -``` - diff --git "a/Solutions/0307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" "b/Solutions/0307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" deleted file mode 100644 index eb5d48d0..00000000 --- "a/Solutions/0307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" +++ /dev/null @@ -1,165 +0,0 @@ -# [0307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) - -- 标签:设计、树状数组、线段树、数组 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 `nums`。 - -**要求**: - -1. 完成两类查询: - 1. 要求将数组元素 `nums[index]` 的值更新为 `val`。 - 2. 要求返回数组 `nums` 中区间 `[left, right]` 之间(包含 `left`、`right`)的 `nums` 元素的和。其中 $left \le right$。 -2. 实现 `NumArray` 类: - 1. `NumArray(int[] nums)` 用整数数组 `nums` 初始化对象。 - 2. `void update(int index, int val)` 将 `nums[index]` 的值更新为 `val`。 - 3. `int sumRange(int left, int right)` 返回数组 `nums` 中索引 `left` 和索引 `right` 之间( 包含 )的 `nums` 元素的和(即 `nums[left] + nums[left + 1], ..., nums[right]`)。 - -**说明**: - -- $1 \le nums.length \le 3 * 10^4$。 -- $-100 \le nums[i] \le 100$。 -- $0 <= index < num.length$。 -- $0 \le left \le right < nums.length$。 -- 调用 `update` 和 `sumRange` 的方法次数不大于 $3 * 10^4$ 次。 - -**示例**: - -- 示例 1: - -``` -给定 nums = [1, 3, 5] - -求和 sumRange(0, 2) -> 9 -更新 update(1, 2) -求和 sumRange(0, 2) -> 8 -``` - -## 解题思路 - -### 思路 1:线段树 - -根据 `nums` 数组,构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。 - -这样构建线段树的时间复杂度为 $O(\log n)$,每次单点更新的时间复杂度为 $O(\log n)$,每次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 - -### 思路 1 线段树代码: - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - - # 单点更新实现方法:将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - - # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - -class NumArray: - - def __init__(self, nums: List[int]): - self.STree = SegmentTree(nums, lambda x, y: x + y) - - - def update(self, index: int, val: int) -> None: - self.STree.update_point(index, val) - - - def sumRange(self, left: int, right: int) -> int: - return self.STree.query_interval(left, right) -``` - diff --git "a/Solutions/0309. \346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" "b/Solutions/0309. \346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" deleted file mode 100644 index a456d46e..00000000 --- "a/Solutions/0309. \346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0309. 最佳买卖股票时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个整数数组,其中第 `i` 个元素代表了第 `i` 天的股票价格 。 - -设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): - -- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 -- 卖出股票后,你无法在第二天买入股票(即冷冻期为 `1` 天)。 - -## 解题思路 - -这道题是「[0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)」的升级版。 - -冷冻期的意思是:如果昨天卖出了,那么今天不能买。在考虑的时候只要判断一下前一天是不是刚卖出。 - -对于每一天结束时的状态总共有以下几种: - -- 买入状态: - - 今日买入 - - 之前买入,之后一直持有无操作 -- 卖出状态: - - 今日买出,正处于冷冻期 - - 昨天卖出,今天结束后度过了冷冻期 - - 之前卖出,度过了冷冻期后无操作 - -在买入状态中,今日买入和之前买入的状态其实可以看做是股票的持有状态,可以将其合并为一种状态。 - -在卖出状态中,昨天卖出和之前卖出的状态其实可以看做是无股票并度过了冷冻期状态,可以将其合并为一种状态。 - -这样总结下来可以划分为三个状态: - -- 股票的持有状态。 -- 无股票,并且处于冷冻期状态。 -- 无股票,并且不处于冷冻期状态。 - -所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 2`)下,所获取的最大利润。 - -注意:这里第第 `j` 种情况, - -接下来确定状态转移公式: - -- 第 `0` 种状态(股票的持有状态)下可以有两种状态推出,取最大的那一种赋值: - - 昨天就已经持有的:`dp[i][0] = dp[i - 1][0]`: - - 今天刚买入的(则昨天不能持有股票也不能处于冷冻期,应来自于前天卖出状态):`dp[i][0] = dp[i - 1][2] - prices[i]` -- 第 `1` 种状态(无股票,并且处于冷冻期状态)下可以有一种状态推出: - - 今天卖出:`dp[i] = dp[i - 1][0] + prices[i]` -- 第 `2` 种状态(无股票,并且不处于冷冻期状态)下可以有两种状态推出,取最大的那一种赋值: - - 昨天卖出:`dp[i] = dp[i - 1][1]` - - 之前卖出:`dp[i] = dp[i - 1][2]` - -下面确定初始化的边界值: - -可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第一次买入就是 `dp[0][1] = -prices[i]`。 - -第一次卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][2] = 0`。第二次买入的话,就是 `dp[0][3] = -prices[i]`。同理第二次卖出就是 `dp[0][4] = 0`。 - -在递推结束后,最大利润肯定是无操作、第一次卖出、第二次卖出这三种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第一次卖出、第二次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为一笔交易,那么在转移状态时,我们允许在一天内进行两次交易,则一笔交易的状态可以转移至两笔交易。所以最终答案为 `dp[size - 1][4]`。`size` 为股票天数。 - -## 代码 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - size = len(prices) - if size == 0: - return 0 - dp = [[0 for _ in range(4)] for _ in range(size)] - - dp[0][0] = -prices[0] - for i in range(1, size): - dp[i][0] = max(dp[i - 1][0], dp[i - 1][2] - prices[i]) - dp[i][1] = dp[i - 1][0] + prices[i] - dp[i][2] = max(dp[i - 1][1], dp[i - 1][2]) - return max(dp[size - 1][0], dp[size - 1][1], dp[size - 1][2]) -``` - diff --git "a/Solutions/0310. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" "b/Solutions/0310. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" deleted file mode 100644 index cec4fa52..00000000 --- "a/Solutions/0310. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" +++ /dev/null @@ -1,148 +0,0 @@ -# [0310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -**描述**:有一棵包含 $n$ 个节点的树,节点编号为 $0 \sim n - 1$。给定一个数字 $n$ 和一个有 $n - 1$ 条无向边的 $edges$ 列表来表示这棵树。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间存在一条无向边。 - -可以选择树中的任何一个节点作为根,当选择节点 $x$ 作为根节点时,设结果树的高度为 $h$。在所有可能的树种,具有最小高度的树(即 $min(h)$)被成为最小高度树。 - -**要求**:找到所有的最小高度树并按照任意顺序返回他们的根节点编号列表。 - -**说明**: - -- **树的高度**:指根节点和叶子节点之间最长向下路径上边的数量。 -- $1 \le n \le 2 * 10^4$。 -- $edges.length == n - 1$。 -- $0 \le ai, bi < n$。 -- $ai \ne bi$。 -- 所有 $(ai, bi)$ 互不相同。 -- 给定的输入保证是一棵树,并且不会有重复的边。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/09/01/e1.jpg) - -```python -输入:n = 4, edges = [[1,0],[1,2],[1,3]] -输出:[1] -解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/09/01/e2.jpg) - -```python -输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] -输出:[3,4] -``` - -## 解题思路 - -### 思路 1:树形 DP + 二次遍历换根法 - -最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点,然后进行深度优先搜索,求出每棵树的高度。最后求出所有树中的最小高度即为答案。但这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 2 * 10^4]$,这样做会导致超时,因此需要进行优化。 - -在上面的算法中,在一轮深度优先搜索中,除了可以得到整棵树的高度之外,在搜索过程中,其实还能得到以每个子节点为根节点的树的高度。如果我们能够利用这些子树的高度信息,快速得到以其他节点为根节点的树的高度,那么我们就能改进算法,以更小的时间复杂度解决这道题。这就是二次遍历与换根法的思想。 - -1. 第一次遍历:自底向上的计算出每个节点 $u$ 向下走(即由父节点 $u$ 向子节点 $v$ 走)的最长路径 $down1[u]$、次长路径 $down2[i]$,并记录向下走最长路径所经过的子节点 $p[u]$,方便第二次遍历时计算。 -2. 第二次遍历:自顶向下的计算出每个节点 $v$ 向上走(即由子节点 $v$ 向父节点 $u$ 走)的最长路径 $up[v]$。需要注意判断 $u$ 向下走的最长路径是否经过了节点 $v$。 - 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$。 - 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$。 -3. 接下来,我们通过枚举 $n$​ 个节点向上走的最长路径与向下走的最长路径,从而找出所有树中的最小高度,并将所有最小高度树的根节点放入答案数组中并返回。 - -整个算法具体步骤如下: - -1. 使用邻接表的形式存储树。 -3. 定义第一个递归函数 `dfs(u, fa)` 用于计算每个节点向下走的最长路径 $down1[u]$、次长路径 $down2[u]$,并记录向下走的最长路径所经过的子节点 $p[u]$。 - 1. 对当前节点的相邻节点进行遍历。 - 2. 如果相邻节点是父节点,则跳过。 - 3. 递归调用 `dfs(v, u)` 函数计算邻居节点的信息。 - 4. 根据邻居节点的信息计算当前节点的高度,并更新当前节点向下走的最长路径 $down1[u]$、当前节点向下走的次长路径 $down2$、取得最长路径的子节点 $p[u]$。 -4. 定义第二个递归函数 `reroot(u, fa)` 用于计算每个节点作为新的根节点时向上走的最长路径 $up[v]$。 - 1. 对当前节点的相邻节点进行遍历。 - 2. 如果相邻节点是父节点,则跳过。 - 3. 根据当前节点 $u$ 的高度和相邻节点 $v$ 的信息更新 $up[v]$。同时需要判断节点 $u$ 向下走的最长路径是否经过了节点 $v$。 - 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down2[u]) + 1$。 - 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down1[u]) + 1$。 - 4. 递归调用 `reroot(v, u)` 函数计算邻居节点的信息。 -5. 调用 `dfs(0, -1)` 函数计算每个节点的最长路径。 -6. 调用 `reroot(0, -1)` 函数计算每个节点作为新的根节点时的最长路径。 -7. 找到所有树中的最小高度。 -8. 将所有最小高度的节点放入答案数组中并返回。 - -### 思路 1:代码 - -```python -class Solution: - def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: - graph = [[] for _ in range(n)] - for u, v in edges: - graph[u].append(v) - graph[v].append(u) - - # down1 用于记录向下走的最长路径 - down1 = [0 for _ in range(n)] - # down2 用于记录向下走的最长路径 - down2 = [0 for _ in range(n)] - p = [0 for _ in range(n)] - # 自底向上记录最长路径、次长路径 - def dfs(u, fa): - for v in graph[u]: - if v == fa: - continue - # 自底向上统计信息 - dfs(v, u) - height = down1[v] + 1 - if height >= down1[u]: - down2[u] = down1[u] - down1[u] = height - p[u] = v - elif height > down2[u]: - down2[u] = height - - # 进行换根动态规划,自顶向下统计向上走的最长路径 - up = [0 for _ in range(n)] - def reroot(u, fa): - for v in graph[u]: - if v == fa: - continue - if p[u] == v: - up[v] = max(up[u], down2[u]) + 1 - else: - up[v] = max(up[u], down1[u]) + 1 - # 自顶向下统计信息 - reroot(v, u) - - dfs(0, -1) - reroot(0, -1) - - # 找到所有树中的最小高度 - min_h = 1e9 - for i in range(n): - min_h = min(min_h, max(down1[i], up[i])) - - # 将所有最小高度的节点放入答案数组中并返回 - res = [] - for i in range(n): - if max(down1[i], up[i]) == min_h: - res.append(i) - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -## 参考资料 - -- 【题解】[C++ 容易理解的换根动态规划解法 - 最小高度树](https://leetcode.cn/problems/minimum-height-trees/solution/c-huan-gen-by-vclip-sa84/) -- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) -- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) diff --git "a/Solutions/0312. \346\210\263\346\260\224\347\220\203.md" "b/Solutions/0312. \346\210\263\346\260\224\347\220\203.md" deleted file mode 100644 index f93d6779..00000000 --- "a/Solutions/0312. \346\210\263\346\260\224\347\220\203.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0312. 戳气球](https://leetcode.cn/problems/burst-balloons/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:有 $n$ 个气球,编号为 $0 \sim n - 1$,每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。 - -**要求**:求出能获得硬币的最大数量。 - -**说明**: - -- $n == nums.length$。 -- $1 \le n \le 300$。 -- $0 \le nums[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,1,5,8] -输出:167 -解释: -nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] -coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 -``` - -- 示例 2: - -```python -输入:nums = [1,5] -输出:10 -解释: -nums = [1,5] --> [5] --> [] -coins = 1*1*5 + 1*5*1 = 10 -``` - -## 解题思路 - -### 思路 1:动态规划 - -根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 $1$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 $0 \sim n + 1$。 - -对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 $1$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 $0 \sim n + 1$ 之间的所有气球(不包括编号 $0$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 - -###### 3. 状态转移方程 - -假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 - -$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: - -$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ - -###### 4. 初始条件 - -- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 $0$,即 $dp[i][j] = 0, \quad i \ge j - 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。。所以最终结果为 $dp[0][n + 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def maxCoins(self, nums: List[int]) -> int: - size = len(nums) - arr = [0 for _ in range(size + 2)] - arr[0] = arr[size + 1] = 1 - for i in range(1, size + 1): - arr[i] = nums[i - 1] - - dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] - - for l in range(3, size + 3): - for i in range(0, size + 2): - j = i + l - 1 - if j >= size + 2: - break - for k in range(i + 1, j): - dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) - - return dp[0][size + 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为气球数量。 -- **空间复杂度**:$O(n^2)$。 diff --git "a/Solutions/0315. \350\256\241\347\256\227\345\217\263\344\276\247\345\260\217\344\272\216\345\275\223\345\211\215\345\205\203\347\264\240\347\232\204\344\270\252\346\225\260.md" "b/Solutions/0315. \350\256\241\347\256\227\345\217\263\344\276\247\345\260\217\344\272\216\345\275\223\345\211\215\345\205\203\347\264\240\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index bbfe8a5d..00000000 --- "a/Solutions/0315. \350\256\241\347\256\227\345\217\263\344\276\247\345\260\217\344\272\216\345\275\223\345\211\215\345\205\203\347\264\240\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,176 +0,0 @@ -# [0315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) - -- 标签:树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$ 。 - -**要求**:返回一个新数组 $counts$ 。其中 $counts[i]$ 的值是 $nums[i]$ 右侧小于 $nums[i]$ 的元素的数量。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [5,2,6,1] -输出:[2,1,1,0] -解释: -5 的右侧有 2 个更小的元素 (2 和 1) -2 的右侧仅有 1 个更小的元素 (1) -6 的右侧有 1 个更小的元素 (1) -1 的右侧有 0 个更小的元素 -``` - -- 示例 2: - -```python -输入:nums = [-1] -输出:[0] -``` - -## 解题思路 - -### 思路 1:归并排序 - -在使用归并排序对数组进行排序时,每当遇到 $left\underline{}nums[left\underline{}i] \le right\underline{}nums[right\underline{}i]$ 时,意味着:在合并前,左子数组当前元素 $left\underline{}nums[left\underline{}i]$ 右侧一定有 $left\underline{}i$ 个元素比 $left\underline{}nums[left\underline{}i]$ 小。则我们可以在归并排序的同时,记录 $nums[i]$ 右侧小于 $nums[i]$ 的元素的数量。 - -1. 将元素值、对应下标、右侧小于 nums[i] 的元素的数量存入数组中。 -2. 对其进行归并排序。 -3. 当遇到 $left\underline{}nums[left\underline{}i] \le right\underline{}nums[right\underline{}i]$ 时,记录 $left\underline{}nums[left\underline{}i]$ 右侧比 $left\underline{}nums[left\underline{}i]$ 小的元素数量,即:`left_nums[left_i][2] += right_i`。 -4. 当合并时 $left\underline{}nums[left\underline{}i]$ 仍有剩余时,说明 $left\underline{}nums[left\underline{}i]$ 右侧有 $right\underline{}i$ 个小于 $left\underline{}nums[left\underline{}i]$ 的元素,记录下来,即:`left_nums[left_i][2] += right_i`。 -5. 根据下标及右侧小于 $nums[i]$ 的元素的数量,组合出答案数组,并返回答案数组。 - -### 思路 1:代码 - -```python -class Solution: - # 合并过程 - def merge(self, left_nums, right_nums): - nums = [] - left_i, right_i = 0, 0 - while left_i < len(left_nums) and right_i < len(right_nums): - # 将两个有序子数组中较小元素依次插入到结果数组中 - if left_nums[left_i] <= right_nums[right_i]: - nums.append(left_nums[left_i]) - # left_nums[left_i] 右侧有 right_i 个比 left_nums[left_i] 小的 - left_nums[left_i][2] += right_i - left_i += 1 - else: - nums.append(right_nums[right_i]) - right_i += 1 - - # 如果左子数组有剩余元素,则将其插入到结果数组中 - while left_i < len(left_nums): - nums.append(left_nums[left_i]) - # left_nums[left_i] 右侧有 right_i 个比 left_nums[left_i] 小的 - left_nums[left_i][2] += right_i - left_i += 1 - - # 如果右子数组有剩余元素,则将其插入到结果数组中 - while right_i < len(right_nums): - nums.append(right_nums[right_i]) - right_i += 1 - - # 返回合并后的结果数组 - return nums - - # 分解过程 - def mergeSort(self, nums) : - # 数组元素个数小于等于 1 时,直接返回原数组 - if len(nums) <= 1: - return nums - - mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 - left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 - right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 - return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 - - - def countSmaller(self, nums: List[int]) -> List[int]: - size = len(nums) - - # 将元素值、对应下标、右侧小于 nums[i] 的元素的数量存入数组中 - nums = [[num, i, 0] for i, num in enumerate(nums)] - nums = self.mergeSort(nums) - ans = [0 for _ in range(size)] - - for num in nums: - ans[num[1]] = num[2] - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:树状数组 - -1. 首先对数组进行离散化处理。把原始数组中的数据映射到 $[0, len(nums) - 1]$ 这个区间。 -2. 然后逆序顺序从数组 $nums$ 中遍历元素 $nums[i]$。 - 1. 计算其离散化后的排名 $index$,查询比 $index$ 小的数有多少个。将其记录到答案数组的对应位置 $ans[i]$ 上。 - 2. 然后在树状数组下标为 $index$ 的位置上,更新值为 $1$。 -3. 遍历完所有元素,最后输出答案数组 $ans$ 即可。 - -### 思路 2:代码 - -```python -import bisect - -class BinaryIndexTree: - - def __init__(self, n): - self.size = n - self.tree = [0 for _ in range(n + 1)] - - def lowbit(self, index): - return index & (-index) - - def update(self, index, delta): - while index <= self.size: - self.tree[index] += delta - index += self.lowbit(index) - - def query(self, index): - res = 0 - while index > 0: - res += self.tree[index] - index -= self.lowbit(index) - return res - -class Solution: - def countSmaller(self, nums: List[int]) -> List[int]: - size = len(nums) - if size == 0: - return [] - if size == 1: - return [0] - - # 离散化 - sort_nums = list(set(nums)) - sort_nums.sort() - size_s = len(sort_nums) - bit = BinaryIndexTree(size_s) - - ans = [0 for _ in range(size)] - for i in range(size - 1, -1, -1): - index = bisect.bisect_left(sort_nums, nums[i]) + 1 - ans[i] = bit.query(index - 1) - bit.update(index, 1) - - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0316. \345\216\273\351\231\244\351\207\215\345\244\215\345\255\227\346\257\215.md" "b/Solutions/0316. \345\216\273\351\231\244\351\207\215\345\244\215\345\255\227\346\257\215.md" deleted file mode 100644 index cafa083f..00000000 --- "a/Solutions/0316. \345\216\273\351\231\244\351\207\215\345\244\215\345\255\227\346\257\215.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) - -- 标签:栈、贪心、字符串、单调栈 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:去除字符串中重复的字母,使得每个字母只出现一次。需要保证 **「返回结果的字典序最小(要求不能打乱其他字符的相对位置)」**。 - -**说明**: - -- $1 \le s.length \le 10^4$。 -- `s` 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "bcabc" -输出:"abc" -``` - -- 示例 2: - -```python -输入:s = "cbacdcbc" -输出:"acdb" -``` - -## 解题思路 - -### 思路 1:哈希表 + 单调栈 - -针对题目的三个要求:去重、不能打乱其他字符顺序、字典序最小。我们来一一分析。 - -1. **去重**:可以通过 **「使用哈希表存储字母出现次数」** 的方式,将每个字母出现的次数统计起来,再遍历一遍,去除重复的字母。 -2. **不能打乱其他字符顺序**:按顺序遍历,将非重复的字母存储到答案数组或者栈中,最后再拼接起来,就能保证不打乱其他字符顺序。 -3. **字典序最小**:意味着字典序小的字母应该尽可能放在前面。 - 1. 对于第 `i` 个字符 `s[i]` 而言,如果第 `0` ~ `i - 1` 之间的某个字符 `s[j]` 在 `s[i]` 之后不再出现了,那么 `s[j]` 必须放到 `s[i]` 之前。 - 2. 而如果 `s[j]` 在之后还会出现,并且 `s[j]` 的字典序大于 `s[i]`,我们则可以先舍弃 `s[j]`,把 `s[i]` 尽可能的放到前面。后边再考虑使用 `s[j]` 所对应的字符。 - - -要满足第 3 条需求,我们可以使用 **「单调栈」** 来解决。我们使用单调栈存储 `s[i]` 之前出现的非重复、并且字典序最小的字符序列。整个算法步骤如下: - -1. 先遍历一遍字符串,用哈希表 `letter_counts` 统计出每个字母出现的次数。 -2. 然后使用单调递减栈保存当前字符之前出现的非重复、并且字典序最小的字符序列。 -3. 当遍历到 `s[i]` 时,如果 `s[i]` 没有在栈中出现过: - 1. 比较 `s[i]` 和栈顶元素 `stack[-1]` 的字典序。如果 `s[i]` 的字典序小于栈顶元素 `stack[-1]`,并且栈顶元素之后的出现次数大于 `0`,则将栈顶元素弹出。 - 2. 然后继续判断 `s[i]` 和栈顶元素 `stack[-1]`,并且知道栈顶元素出现次数为 `0` 时停止弹出。此时将 `s[i]` 添加到单调栈中。 -4. 从哈希表 `letter_counts` 中减去 `s[i]` 出现的次数,继续遍历。 -5. 最后将单调栈中的字符依次拼接为答案字符串,并返回。 - -### 思路 1:代码 - -```python -class Solution: - def removeDuplicateLetters(self, s: str) -> str: - stack = [] - letter_counts = dict() - for ch in s: - if ch in letter_counts: - letter_counts[ch] += 1 - else: - letter_counts[ch] = 1 - - for ch in s: - if ch not in stack: - while stack and ch < stack[-1] and stack[-1] in letter_counts and letter_counts[stack[-1]] > 0: - stack.pop() - stack.append(ch) - letter_counts[ch] -= 1 - - return ''.join(stack) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 为字符集合,$|\sum|$ 为字符种类个数。由于栈中字符不能重复,因此栈中最多有 $|\sum|$ 个字符。 - -## 参考资料 - -- 【题解】[去除重复数组 - 去除重复字母 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicate-letters/solution/qu-chu-zhong-fu-shu-zu-by-lu-shi-zhe-sokp/) diff --git "a/Solutions/0318. \346\234\200\345\244\247\345\215\225\350\257\215\351\225\277\345\272\246\344\271\230\347\247\257.md" "b/Solutions/0318. \346\234\200\345\244\247\345\215\225\350\257\215\351\225\277\345\272\246\344\271\230\347\247\257.md" deleted file mode 100644 index 24dba754..00000000 --- "a/Solutions/0318. \346\234\200\345\244\247\345\215\225\350\257\215\351\225\277\345\272\246\344\271\230\347\247\257.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [0318. 最大单词长度乘积](https://leetcode.cn/problems/maximum-product-of-word-lengths/) - -- 标签:位运算、数组、字符串 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `words`。字符串中只包含英语的小写字母。 - -要求:计算当两个字符串 `words[i]` 和 `words[j]` 不包含相同字符时,它们长度的乘积的最大值。如果没有不包含相同字符的一对字符串,返回 0。 - -## 解题思路 - -这道题的核心难点是判断任意两个字符串之间是否包含相同字符。最直接的做法是先遍历第一个字符串的每个字符,再遍历第二个字符串查看是否有相同字符。但是这样做的话,时间复杂度过高。考虑怎么样可以优化一下。 - -题目中说字符串中只包含英语的小写字母,也就是 `26` 种字符。一个 `32` 位的 `int` 整数每一个二进制位都可以表示一种字符的有无,那么我们就可以通过一个整数来表示一个字符串中所拥有的字符种类。延伸一下,我们可以用一个整数数组来表示一个字符串数组中,每个字符串所拥有的字符种类。 - -接下来事情就简单了,两重循环遍历整数数组,遇到两个字符串不包含相同字符的情况,就计算一下他们长度的乘积,并维护一个乘积最大值。最后输出最大值即可。 - -## 代码 - -```python -class Solution: - def maxProduct(self, words: List[str]) -> int: - size = len(words) - arr = [0 for _ in range(size)] - for i in range(size): - word = words[i] - len_word = len(word) - for j in range(len_word): - arr[i] |= 1 << (ord(word[j]) - ord('a')) - ans = 0 - for i in range(size): - for j in range(i + 1, size): - if arr[i] & arr[j] == 0: - k = len(words[i]) * len(words[j]) - ans = k if ans < k else ans - return ans -``` - diff --git "a/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" "b/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" deleted file mode 100644 index 80d60b4e..00000000 --- "a/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" +++ /dev/null @@ -1,142 +0,0 @@ -# [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) - -- 标签:广度优先搜索、数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定代表不同面额的硬币数组 $coins$ 和一个总金额 $amount$。 - -**要求**:求出凑成总金额所需的最少的硬币个数。如果无法凑出,则返回 $-1$。 - -**说明**: - -- $1 \le coins.length \le 12$。 -- $1 \le coins[i] \le 2^{31} - 1$。 -- $0 \le amount \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:coins = [1, 2, 5], amount = 11 -输出:3 -解释:11 = 5 + 5 + 1 -``` - -- 示例 2: - -```python -输入:coins = [2], amount = 3 -输出:-1 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -我们可以从 $amount$ 开始,每次从 $coins$ 的硬币中选中 $1$ 枚硬币,并记录当前挑选硬币的次数。则最快减到 $0$ 的次数就是凑成总金额所需的最少的硬币个数。这道题就变成了从 $amount$ 减到 $0$ 的最短路径问题。我们可以用广度优先搜索的方法来做。 - -1. 定义 $visited$ 为标记已访问值的集合变量,$queue$ 为存放值的队列。 -2. 将 $amount$ 状态标记为访问,并将其加入队列 $queue$。 -3. 令当前步数加 $1$,然后将当前队列中的所有值依次出队,并遍历硬币数组: - 1. 如果当前值等于当前硬币值,则说明当前硬币刚好能凑成当前值,则直接返回当前次数。 - 2. 如果当前值大于当前硬币值,并且当前值减去当前硬币值的差值没有出现在已访问集合 $visited$ 中,则将差值添加到队列和访问集合中。 - -4. 重复执行第 $3$ 步,直到队列为空。 -5. 如果队列为空,也未能减到 $0$,则返回 $-1$。 - -### 思路 1:代码 - -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - if amount == 0: - return 0 - - visited = set([amount]) - queue = collections.deque([amount]) - - step = 0 - while queue: - step += 1 - size = len(queue) - for _ in range(size): - cur = queue.popleft() - for coin in coins: - if cur == coin: - step += 1 - return step - elif cur > coin and cur - coin not in visited: - queue.append(cur - coin) - visited.add(cur - coin) - - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(amount \times size)$。其中 $amount$ 表示总金额,$size$ 表示硬币的种类数。 -- **空间复杂度**:$O(amount)$。 - -### 思路 2:完全背包问题 - -这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问恰好凑成总金额为 $amount$ 的背包,最少需要多少硬币? - -与普通完全背包问题不同的是,这里求解的是最少硬币数量。我们可以改变一下「状态定义」和「状态转移方程」。 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。 - -###### 3. 状态转移方程 - -$dp[c] = \begin{cases} dp[c] & c < coins[i - 1] \cr min \lbrace dp[c], dp[c - coins[i - 1]] + 1 \rbrace & c \ge coins[i - 1] \end{cases}$ - -1. 当 $c < coins[i - 1]$ 时: - 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 -2. 当 $c \ge coins[i - 1]$ 时,取下面两种情况中的较小值: - 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 - 2. 凑成金额 $c - coins[i - 1]$ 的最少硬币数量,再加上当前硬币的数量 $1$,即 $dp[c - coins[i - 1]] + 1$。 - -###### 4. 初始条件 - -- 凑成总金额为 $0$ 的最少硬币数量为 $0$,即 $dp[0] = 0$。 -- 默认情况下,在不使用硬币时,都不能恰好凑成总金额为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。则最终结果为 $dp[amount]$。 - -1. 如果 $dp[amount] \ne amount + 1$,则说明: $dp[amount]$ 为凑成金额 $amount$ 的最少硬币数量,则返回 $dp[amount]$。 -2. 如果 $dp[amount] = amount + 1$,则说明:无法凑成金额 $amount$,则返回 $-1$。 - -### 思路 2:代码 - -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - size = len(coins) - dp = [(amount + 1) for _ in range(amount + 1)] - dp[0] = 0 - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 正序枚举背包装载重量 - for c in range(coins[i - 1], amount + 1): - dp[c] = min(dp[c], dp[c - coins[i - 1]] + 1) - - if dp[amount] != amount + 1: - return dp[amount] - return -1 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(amount \times size)$。其中 $amount$ 表示总金额,$size$ 表示硬币的种类数。 -- **空间复杂度**:$O(amount)$。 \ No newline at end of file diff --git "a/Solutions/0323. \346\227\240\345\220\221\345\233\276\344\270\255\350\277\236\351\200\232\345\210\206\351\207\217\347\232\204\346\225\260\347\233\256.md" "b/Solutions/0323. \346\227\240\345\220\221\345\233\276\344\270\255\350\277\236\351\200\232\345\210\206\351\207\217\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index 342b738c..00000000 --- "a/Solutions/0323. \346\227\240\345\220\221\345\233\276\344\270\255\350\277\236\351\200\232\345\210\206\351\207\217\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -**描述**:给定 `n` 个节点(编号从 `0` 到 `n - 1`)的图的无向边列表 `edges`,其中 `edges[i] = [u, v]` 表示节点 `u` 和节点 `v` 之间有一条无向边。 - -**要求**:计算该无向图中连通分量的数量。 - -**说明**: - -- $1 \le n \le 2000$。 -- $1 \le edges.length \le 5000$。 -- $edges[i].length == 2$。 -- $0 \le ai \le bi < n$。 -- $ai != bi$。 -- `edges` 中不会出现重复的边。 - -**示例**: - -- 示例 1: - -```python -输入: n = 5 和 edges = [[0, 1], [1, 2], [3, 4]] - 0 3 - | | - 1 --- 2 4 -输出: 2 -``` - -- 示例 2: - -```python -输入: n = 5 和 edges = [[0, 1], [1, 2], [2, 3], [3, 4]] - 0 4 - | | - 1 --- 2 --- 3 -输出: 1 -``` - -## 解题思路 - -先来看一下图论中相关的名次解释。 - -- **连通图**:在无向图中,如果可以从顶点 $v_i$ 到达 $v_j$,则称 $v_i$ 和 $v_j$ 连通。如果图中任意两个顶点之间都连通,则称该图为连通图。 -- **无向图的连通分量**:如果该图为连通图,则连通分量为本身;否则将无向图中的极大连通子图称为连通分量,每个连通分量都是一个连通图。 -- **无向图的连通分量个数**:无向图的极大连通子图的个数。 - -接下来我们来解决这道题。 - -### 思路 1:深度优先搜索 - -1. 使用 `visited` 数组标记遍历过的节点,使用 `count` 记录连通分量数量。 -2. 从未遍历过的节点 `u` 出发,连通分量数量加 1。然后遍历与 `u` 节点构成无向边,且为遍历过的的节点 `v`。 -3. 再从 `v` 出发继续深度遍历。 -4. 直到遍历完与`u` 直接相关、间接相关的节点之后,再遍历另一个未遍历过的节点,继续上述操作。 -5. 最后输出连通分量数目。 - -### 思路 1:代码 - -```python -class Solution: - def dfs(self, visited, i, graph): - visited[i] = True - for j in graph[i]: - if not visited[j]: - self.dfs(visited, j, graph) - - def countComponents(self, n: int, edges: List[List[int]]) -> int: - count = 0 - visited = [False for _ in range(n)] - graph = [[] for _ in range(n)] - - for x, y in edges: - graph[x].append(y) - graph[y].append(x) - - for i in range(n): - if not visited[i]: - count += 1 - self.dfs(visited, i, graph) - return count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中$n$ 是顶点个数。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:广度优先搜索 - -1. 使用变量 `count` 记录连通分量个数。使用集合变量 `visited` 记录访问过的节点,使用邻接表 `graph` 记录图结构。 -2. 从 `0` 开始,依次遍历 `n` 个节点。 -3. 如果第 `i` 个节点未访问过: - 1. 将其添加到 `visited` 中。 - 2. 并且连通分量个数累加,即 `count += 1`。 - 3. 定义一个队列 `queue`,将第 `i` 个节点加入到队列中。 - 4. 从队列中取出第一个节点,遍历与其链接的节点,并将未遍历过的节点加入到队列 `queue` 和 `visited` 中。 - 5. 直到队列为空,则继续向后遍历。 -4. 最后输出连通分量数目 `count`。 - -### 思路 2:代码 - -```python -import collections - -class Solution: - def countComponents(self, n: int, edges: List[List[int]]) -> int: - count = 0 - visited = set() - graph = [[] for _ in range(n)] - - for x, y in edges: - graph[x].append(y) - graph[y].append(x) - - for i in range(n): - if i not in visited: - visited.add(i) - count += 1 - queue = collections.deque([i]) - while queue: - node_u = queue.popleft() - for node_v in graph[node_u]: - if node_v not in visited: - visited.add(node_v) - queue.append(node_v) - return count -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。其中$n$ 是顶点个数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0324. \346\221\206\345\212\250\346\216\222\345\272\217 II.md" "b/Solutions/0324. \346\221\206\345\212\250\346\216\222\345\272\217 II.md" deleted file mode 100644 index b5bb2faf..00000000 --- "a/Solutions/0324. \346\221\206\345\212\250\346\216\222\345\272\217 II.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [0324. 摆动排序 II](https://leetcode.cn/problems/wiggle-sort-ii/) - -- 标签:数组、分治、快速选择、排序 -- 难度:中等 - -## 题目大意 - -给你一个整数数组 `nums`。 - -要求:将它重新排列成 `nums[0] < nums[1] > nums[2] < nums[3] ...` 的顺序。可以假设所有输入数组都可以得到满足题目要求的结果。 - -注意: - -- $1 \le nums.length \le 5 * 10^4$。 -- $0 \le nums[i] \le 5000$。 - -## 解题思路 - -`num[i]` 的取值在 `[0, 5000]`。所以我们可以用桶排序算法将排序算法的时间复杂度降到 $O(n)$。然后按照下标的奇偶性遍历两次数组,第一次遍历将桶中的元素从末尾到头部依次放到对应奇数位置上。第二次遍历将桶中剩余元素从末尾到头部依次放到对应偶数位置上。 - -## 代码 - -```python -class Solution: - def wiggleSort(self, nums: List[int]) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - buckets = [0 for _ in range(5010)] - for num in nums: - buckets[num] += 1 - - size = len(nums) - big = size - 2 if (size & 1) == 1 else size - 1 - small = size - 1 if (size & 1) == 1 else size - 2 - - index = 5000 - for i in range(1, big + 1, 2): - while buckets[index] == 0: - index -= 1 - nums[i] = index - buckets[index] -= 1 - for i in range(0, small + 1, 2): - while buckets[index] == 0: - index -= 1 - nums[i] = index - buckets[index] -= 1 -``` - diff --git "a/Solutions/0326. 3 \347\232\204\345\271\202.md" "b/Solutions/0326. 3 \347\232\204\345\271\202.md" deleted file mode 100644 index cdcbbf5c..00000000 --- "a/Solutions/0326. 3 \347\232\204\345\271\202.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [0326. 3 的幂](https://leetcode.cn/problems/power-of-three/) - -- 标签:递归、数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 n,判断 n 是否是 3 的幂次方。$-2^{31} \le n \le 2^{31}-1$ - -## 解题思路 - -首先排除负数,因为 3 的幂次方不可能为负数。 - -因为 n 的最大值为 $2^{31}-1$。计算出在 n 的范围内,3 的幂次方最大为 $3^{19} = 1162261467$。 - -3 为质数,则 $3^{19}$ 的除数只有 $3^0, 3^1, …, 3^{19}$。所以若 n 为 3 的幂次方,则 n 肯定能被 $3^{19}$ 整除,直接判断即可。 - -## 代码 - -```python -class Solution: - def isPowerOfThree(self, n: int) -> bool: - if n <= 0: - return False - if (3 ** 19) % n == 0: - return True - return False -``` - diff --git "a/Solutions/0328. \345\245\207\345\201\266\351\223\276\350\241\250.md" "b/Solutions/0328. \345\245\207\345\201\266\351\223\276\350\241\250.md" deleted file mode 100644 index 75038a56..00000000 --- "a/Solutions/0328. \345\245\207\345\201\266\351\223\276\350\241\250.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) - -- 标签:链表 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个单链表的头节点 `head`。 - -**要求**:将链表中的奇数位置上的节点排在前面,偶数位置上的节点排在后面,返回新的链表节点。 - -**说明**: - -- 要求空间复杂度为 $O(1)$。 -- $n$ 等于链表中的节点数。 -- $0 \le n \le 10^4$。 -- $-10^6 \le Node.val \le 10^6$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/10/oddeven-linked-list.jpg) - -```python -输入: head = [1,2,3,4,5] -输出: [1,3,5,2,4] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/10/oddeven2-linked-list.jpg) - -```python -输入: head = [2,1,3,5,6,4,7] -输出: [2,3,6,7,1,5,4] -``` - -## 解题思路 - -### 思路 1:拆分后合并 - -1. 使用两个指针 `odd`、`even` 分别表示奇数节点链表和偶数节点链表。 -2. 先将奇数位置上的节点和偶数位置上的节点分成两个链表,再将偶数节点的链表接到奇数链表末尾。 -3. 过程中需要使用几个必要指针用于保留必要位置(比如原链表初始位置、偶数链表初始位置、当前遍历节点位置)。 - -### 思路 1:代码 - -```python -class Solution: - def oddEvenList(self, head: ListNode) -> ListNode: - if not head or not head.next or not head.next.next: - return head - - evenHead = head.next - odd, even = head, evenHead - isOdd = True - - curr = head.next.next - - while curr: - if isOdd: - odd.next = curr - odd = curr - else: - even.next = curr - even = curr - isOdd = not isOdd - curr = curr.next - odd.next = evenHead - even.next = None - return head -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0329. \347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" "b/Solutions/0329. \347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" deleted file mode 100644 index 1378031f..00000000 --- "a/Solutions/0329. \347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 -- 难度:困难 - -## 题目大意 - -给定一个 `m * n` 大小的整数矩阵 `matrix`。要求:找出其中最长递增路径的长度。 - -对于每个单元格,可以往上、下、左、右四个方向移动,不能向对角线方向移动或移动到边界外。 - -## 解题思路 - -深度优先搜索。使用二维数组 `record` 存储遍历过的单元格最大路径长度,已经遍历过的单元格就不需要再次遍历了。 - -## 代码 - -```python -class Solution: - max_len = 0 - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - - def longestIncreasingPath(self, matrix: List[List[int]]) -> int: - if not matrix: - return 0 - rows, cols = len(matrix), len(matrix[0]) - record = [[0 for _ in range(cols)] for _ in range(rows)] - - def dfs(i, j): - record[i][j] = 1 - for direction in self.directions: - new_i, new_j = i + direction[0], j + direction[1] - if 0 <= new_i < rows and 0 <= new_j < cols and matrix[new_i][new_j] > matrix[i][j]: - if record[new_i][new_j] == 0: - dfs(new_i, new_j) - record[i][j] = max(record[i][j], record[new_i][new_j] + 1) - self.max_len = max(self.max_len, record[i][j]) - - for i in range(rows): - for j in range(cols): - if record[i][j] == 0: - dfs(i, j) - return self.max_len -``` - diff --git "a/Solutions/0334. \351\200\222\345\242\236\347\232\204\344\270\211\345\205\203\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0334. \351\200\222\345\242\236\347\232\204\344\270\211\345\205\203\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index d75be091..00000000 --- "a/Solutions/0334. \351\200\222\345\242\236\347\232\204\344\270\211\345\205\203\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0334. 递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) - -- 标签:贪心、数组 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`。 - -要求:判断数组中是否存在长度为 3 的递增子序列。要求算法时间复杂度为 $O(n)$、空间复杂度为 $O(1)$。 - -- 长度为 3 的递增子序列:存在这样的三元组下标 (`i`, `j`, `k`) 且满足 `i < j < k` ,使得 `nums[i] < nums[j] < nums[k]`。 - -## 解题思路 - -常规方法是三重 `for` 循环遍历三个数,但是时间复杂度为 $O(n^3)$,肯定会超时的。 - -那么如何才能只进行一次遍历,就找到长度为 3 的递增子序列呢? - -假设长度为 3 的递增子序列元素为 `a`、`b`、`c`,`a < b < c`。 - -先来考虑 `a` 和 `b`。如果我们要使得一个数组 `i < j`,并且 `nums[i] < nums[j]`。那么应该使得 `a` 尽可能的小,这样子我们下一个数字 `b` 才可以尽可能地满足条件。 - -同样对于 `b` 和 `c`,也应该使得 `b` 尽可能的小,下一个数字 `c` 才可以尽可能的满足条件。 - -所以,我们的目的是:在 `a < b` 的前提下,保证 a 尽可能小。在 `b < c` 的条件下,保证 `b` 尽可能小。 - -我们可以使用两个数 `a`、`b` 指向无穷大。遍历数组: - -- 如果当前数字小于等于 `a` ,则更新 `a = num`; -- 如果当前数字大于等于 `a`,则说明当前数满足 `num > a`,则判断: - - 如果 `num` 小于等于 `b`,则更新 `b = num`; - - 如果 `num` 大于 `b`,则说明找到了长度为 3 的递增子序列,直接输出 `True`。 -- 如果遍历完仍未找到,则输出 `False`。 - -## 代码 - -```python -class Solution: - def increasingTriplet(self, nums: List[int]) -> bool: - a = float('inf') - b = float('inf') - for num in nums: - if num <= a: - a = num - elif num <= b: - b = num - else: - return True - return False -``` - diff --git "a/Solutions/0336. \345\233\236\346\226\207\345\257\271.md" "b/Solutions/0336. \345\233\236\346\226\207\345\257\271.md" deleted file mode 100644 index eb013220..00000000 --- "a/Solutions/0336. \345\233\236\346\226\207\345\257\271.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [0336. 回文对](https://leetcode.cn/problems/palindrome-pairs/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:困难 - -## 题目大意 - -给定一组互不相同的单词列表 `words`。 - -要求:找出所有不同的索引对 `(i, j)`,使得列表中的两个单词 `words[i] + words[j]` ,可拼接成回文串。 - -## 解题思路 - -如果字符串 `words[i] + words[j]` 能构成一个回文串,把 `words[i]` 分成 `words_left[i]` 和 `words_right[i]` 两部分。即 `words[i] + words[j] = words_left[i] + words_right[i] + words[j]`。则: - -- `words_right[i]` 本身是回文串,`words_left[i]` 和 `words[j]` 互为逆序。 - -同理,如果 `words[j] + word[i]` 能构成一个回文串,把 `word[i]` 分成 `words_left[i]` 和 `words_right[i]` 两部分。即 `words[j] + word[i] = words[j] + words_left[i] + words_right[i]`。则: - -- `words_left[i]` 本身是回文串,`words[j]` 和 `words_right[i]` 互为逆序。 - -从上面的表述可以得知,`words[j]` 可以通过拆分 `words[i]` 之后逆序得出。 - -我们使用两重循环遍历。一重循环遍历单词列表 `words` 中的每一个单词 `words[i]`,二重循环遍历每个单词的拆分位置 `j`。然后将每一个单词 `words[i]` 拆分成 `words[i][0:j+1]` 和 `words[i][j+1:]`。然后分别判断 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序是否在单词列表中,如果在单词列表中,则将「`words[i]` 和 `words[i][0:j+1]` 对应的索引」或者 「`words[i]` 和 `words[i][j+1:]` 对应的索引」插入到答案数组中。 - -至于判断 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序是否在单词列表中,以及获取 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序所对应单词的索引下标可以通过构建字典树的方式获取。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.index = -1 - - - def insert(self, word: str, index: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.index = index - - def search(self, word: str) -> int: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return -1 - cur = cur.children[ch] - - if cur is not None and cur.isEnd: - return cur.index - return -1 - -class Solution: - def isPalindrome(self, word: str) -> bool: - left, right = 0, len(word) - 1 - while left < right: - if word[left] != word[right]: - return False - left += 1 - right -= 1 - return True - - def palindromePairs(self, words: List[str]) -> List[List[int]]: - trie_tree = Trie() - size = len(words) - for i in range(size): - word = words[i] - trie_tree.insert(word, i) - - res = [] - for i in range(size): - word = words[i] - for j in range(len(word)): - if self.isPalindrome(word[:j+1]): - temp = word[j+1:][::-1] - index = trie_tree.search(temp) - if index != i and index != -1: - res.append([index, i]) - if temp == "": - res.append([i, index]) - if self.isPalindrome(word[j+1:]): - temp = word[:j+1][::-1] - index = trie_tree.search(temp) - if index != i and index != -1: - res.append([i, index]) - return res -``` - diff --git "a/Solutions/0337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" "b/Solutions/0337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" deleted file mode 100644 index bbf241e6..00000000 --- "a/Solutions/0337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [0337. 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) - -- 标签:树、深度优先搜索、动态规划、二叉树 -- 难度:中等 - -## 题目大意 - -小偷发现了一个新的可行窃的地区,这个地区的形状是一棵二叉树。这个地区只有一个入口,称为「根」。除了「根」之外,每栋房子只有一个「父」房子与之相连。如果两个直接相连的房子在同一天被打劫,房屋将自动报警。 - -现在给定这个代表地区房间的二叉树,每个节点值代表该房间所拥有的金额。要求计算在不触动警报的情况下,小偷一晚上能盗取的最高金额。 - -## 解题思路 - -树形动态规划问题。 - -对于当前节点 `cur`,不能选择子节点,也不能选择父节点。所以对于一棵子树来说,有两种情况: - -- 选择了根节点 -- 没有选择根节点 - -### 1. 选择根节点 - -如果选择了根节点,则不能再选择左右儿子节点,这种情况下的最大值为:当前节点 + 左子树不选择根节点 + 右子树不选择根节点。 - -### 2. 不选择根节点 - -如果不选择根节点,则可以选择左右儿子节点,共四种可能: - -- 左子树选择根节点 + 右子树选择根节点 -- 左子树选择根节点 + 右子树不选根节点 -- 左子树不选根节点 + 右子树选择根节点 -- 左子树不选根节点 + 右子树不选根节点 - -选择其中最大值。 - -上述描述中,当前节点的选择来自于子节点信息的选择,然后逐层向上,直到根节点。所以我们使用「后序遍历」的方式进行递归遍历。 - -## 代码 - -```python -class Solution: - def dfs(self, root: TreeNode): - if not root: - return [0, 0] - left = self.dfs(root.left) - right = self.dfs(root.right) - - val_steal = root.val + left[1] + right[1] - val_no_steal = max(left[0], left[1]) + max(right[0], right[1]) - return [val_steal, val_no_steal] - def rob(self, root: TreeNode) -> int: - res = self.dfs(root) - return max(res[0], res[1]) -``` - diff --git "a/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" "b/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" deleted file mode 100644 index 4121425e..00000000 --- "a/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0338. 比特位计数](https://leetcode.cn/problems/counting-bits/) - -- 标签:位运算、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 - -**说明**: - -- $0 \le n \le 10^5$。 -- 使用线性时间复杂度 $O(n)$ 解决此问题。 -- 不使用任何内置函数解决此问题。 - -**示例**: - -- 示例 1: - -```python -输入:n = 5 -输出:[0,1,1,2,1,2] -解释: -0 --> 0 -1 --> 1 -2 --> 10 -3 --> 11 -4 --> 100 -5 --> 101 -``` - -## 解题思路 - -### 思路 1:动态规划 - -根据整数的二进制特点可以将整数分为两类: - -- 奇数:其二进制表示中 `1` 的个数一定比前面相邻的偶数多一个 `1`。 -- 偶数:其二进制表示中 `1` 的个数一定与该数除以 `2` 之后的数一样多。 - -另外,边界 `0` 的二进制表示中 `1` 的个数为 `0`。 - -于是可以根据规律,从 `0` 开始到 `n` 进行递推求解。 - -###### 1. 划分阶段 - -按照整数 `n` 进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:整数 `i` 对应二进制表示中 `1` 的个数。 - -###### 3. 状态转移方程 - -- 如果 `i` 为奇数,则整数 `i` 对应二进制表示中 `1` 的个数等于整数 `i - 1` 对应二进制表示中 `1` 的个数加 `1`,即 `dp[i] = dp[i - 1] + 1`。 -- 如果 `i` 为偶数,则整数 `i` 对应二进制表示中 `1` 的个数等于整数 `i // 2` 对应二进制表示中 `1` 的个数,即 `dp[i] = dp[i // 2]`。 - -###### 4. 初始条件 - -整数 `0` 对应二进制表示中 `1` 的个数为 `0`。 - -###### 5. 最终结果 - -整个 `dp` 数组即为最终结果,将其返回即可。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def countBits(self, n: int) -> List[int]: - dp = [0 for _ in range(n + 1)] - for i in range(1, n + 1): - if i % 2 == 1: - dp[i] = dp[i - 1] + 1 - else: - dp[i] = dp[i // 2] - return dp -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一位数组保存状态,所以总的时间复杂度为 $O(n)$。 - diff --git "a/Solutions/0340. \350\207\263\345\244\232\345\214\205\345\220\253 K \344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" "b/Solutions/0340. \350\207\263\345\244\232\345\214\205\345\220\253 K \344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" deleted file mode 100644 index 030f5488..00000000 --- "a/Solutions/0340. \350\207\263\345\244\232\345\214\205\345\220\253 K \344\270\252\344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [0340. 至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`, - -要求:返回至多包含 `k` 个不同字符的最长子串 `t` 的长度。 - -## 解题思路 - -用滑动窗口 `window_counts` 来记录各个字符个数,`window_counts` 为哈希表类型。用 `ans` 来维护至多包含 `k` 个不同字符的最长子串 `t` 的长度。 - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中不超过 `k` 种字符。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧字符 `s[right]` 加入当前窗口 `window_counts` 中,记录该字符个数,向右移动 `right`。 -- 如果该窗口中字符的种数多于 `k` 个,即 `len(window_counts) > k`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `len(window_counts) <= k`。 -- 维护更新至多包含 `k` 个不同字符的最长子串 `t` 的长度。然后继续右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int: - ans = 0 - window_counts = dict() - left, right = 0, 0 - - while right < len(s): - if s[right] in window_counts: - window_counts[s[right]] += 1 - else: - window_counts[s[right]] = 1 - - while(len(window_counts) > k): - window_counts[s[left]] -= 1 - if window_counts[s[left]] == 0: - del window_counts[s[left]] - left += 1 - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - diff --git "a/Solutions/0341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250.md" "b/Solutions/0341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250.md" deleted file mode 100644 index dae241e4..00000000 --- "a/Solutions/0341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0341. 扁平化嵌套列表迭代器](https://leetcode.cn/problems/flatten-nested-list-iterator/) - -- 标签:栈、树、深度优先搜索、设计、队列、迭代器 -- 难度:中等 - -## 题目大意 - -给定一个嵌套的整数列表 `nestedList` 。列表中元素类型为 NestedInteger 类。每个元素(NestedInteger 对象)要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。 - -NestedInteger 类提供了三个方法: - -- `isInteger()`,判断当前存储的对象是否为 int; -- `getInteger()` ,如果当前存储的元素是 int 型的,那么返回当前的结果 int,否则调用会失败; -- `getList()`,如果当前存储的元素是 `List` 型的,那么返回该 List,否则调用会失败。 - -要求:实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。 - -实现扁平迭代器类 NestedIterator: - -- `NestedIterator(List nestedList)` 用嵌套列表 `nestedList` 初始化迭代器。 -- `int next()` 返回嵌套列表的下一个整数。 -- `boolean hasNext()` 如果仍然存在待迭代的整数,返回 `True`;否则,返回 `False`。 - -## 解题思路 - -初始化时不对元素进行预处理。而是将所有的 `NestedInteger` 逆序放到栈中,当需要展开的时候才进行展开。 - -## 代码 - -```python -class NestedIterator: - def __init__(self, nestedList: [NestedInteger]): - self.stack = [] - size = len(nestedList) - for i in range(size - 1, -1, -1): - self.stack.append(nestedList[i]) - - - def next(self) -> int: - cur = self.stack.pop() - return cur.getInteger() - - - def hasNext(self) -> bool: - while self.stack: - cur = self.stack[-1] - if cur.isInteger(): - return True - self.stack.pop() - for i in range(len(cur.getList()) - 1, -1, -1): - self.stack.append(cur.getList()[i]) - return False -``` - diff --git "a/Solutions/0342. 4\347\232\204\345\271\202.md" "b/Solutions/0342. 4\347\232\204\345\271\202.md" deleted file mode 100644 index 6e7dae09..00000000 --- "a/Solutions/0342. 4\347\232\204\345\271\202.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0342. 4的幂](https://leetcode.cn/problems/power-of-four/) - -- 标签:位运算、递归、数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 $n$,判断 $n$ 是否是 $4$ 的幂次方,如果是的话,返回 True。不是的话,返回 False。 - -## 解题思路 - -通过循环可以直接做。但有更好的方法。 - -$n$ 如果是 $4$ 的幂次方,那么 $n$ 肯定是 $2$ 的幂次方,$2$ 的幂次方二进制表示只含有一个 $1$,可以通过 $n \text{ \& } (n - 1)$ 将 $n$ 的最后位置上 的 $1$ 置为 $0$,通过判断 $n$ 是否满足 $n \text { \& } (n - 1) == 0$ 来判断 $n$ 是否是 $2$ 的幂次方。 - -若根据上述判断,得出 $n$ 是 $2$ 的幂次方,则可以写为:$n = x^{2k}$ 或者 $n = x^{2k+1}$。如果 $n$ 是 $4$ 的幂次方,则 $n = 2^{k}$。 - -下面来看一下 $2^{2x}$、$2^{2x}+1$ 的情况: - -- $(2^{2x} \mod 3) = (4^x \mod 3) = ((3+1)^x \mod 3) == 1$ -- $(2^{2x+1} \mod 3) = ((2 \times 4^x) \mod 3) = ((2 \times (3+1)^x) \mod 3) == 2$ - -则如果 $n \mod 3 == 1$,则 $n$ 为 $4$ 的幂次方。 - -## 代码 - -```python -class Solution: - def isPowerOfFour(self, n: int) -> bool: - return n > 0 and (n & (n-1)) == 0 and (n-1) % 3 == 0 -``` - diff --git "a/Solutions/0343. \346\225\264\346\225\260\346\213\206\345\210\206.md" "b/Solutions/0343. \346\225\264\346\225\260\346\213\206\345\210\206.md" deleted file mode 100644 index 30413ed6..00000000 --- "a/Solutions/0343. \346\225\264\346\225\260\346\213\206\345\210\206.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0343. 整数拆分](https://leetcode.cn/problems/integer-break/) - -- 标签:数学、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 - -**要求**:返回可以获得的最大乘积。 - -**说明**: - -- $2 \le n \le 58$。 - -**示例**: - -- 示例 1: - -```python -输入: n = 2 -输出: 1 -解释: 2 = 1 + 1, 1 × 1 = 1。 -``` - -- 示例 2: - -```python -输入: n = 10 -输出: 36 -解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照正整数进行划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 - -###### 3. 状态转移方程 - -当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: - -1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 -2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 - -则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 - -由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: - -$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 - -###### 4. 初始条件 - -- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 - -### 思路 1:代码 - -```python -class Solution: - def integerBreak(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - for i in range(2, n + 1): - for j in range(i): - dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) - return dp[n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0344. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0344. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 4aa3b7ab..00000000 --- "a/Solutions/0344. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符数组 $s$。 - -**要求**:将其反转。 - -**说明**: - -- 不能使用额外的数组空间,必须原地修改输入数组、使用 $O(1)$ 的额外空间解决问题。 -- $1 \le s.length \le 10^5$。 -- $s[i]$ 都是 ASCII 码表中的可打印字符。 - -**示例**: - -- 示例 1: - -```python -输入:s = ["h","e","l","l","o"] -输出:["o","l","l","e","h"] -``` - -- 示例 2: - -```python -输入:s = ["H","a","n","n","a","h"] -输出:["h","a","n","n","a","H"] -``` - -## 解题思路 - -### 思路 1:对撞指针 - -1. 使用两个指针 $left$,$right$。$left$ 指向字符数组开始位置,$right$ 指向字符数组结束位置。 -2. 交换 $s[left]$ 和 $s[right]$,将 $left$ 右移、$right$ 左移。 -3. 如果遇到 $left == right$,跳出循环。 - -### 思路 1:代码 - -```python -class Solution: - def reverseString(self, s: List[str]) -> None: - left, right = 0, len(s) - 1 - while left < right: - s[left], s[right] = s[right], s[left] - left += 1 - right -= 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0345. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\351\237\263\345\255\227\346\257\215.md" "b/Solutions/0345. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\351\237\263\345\255\227\346\257\215.md" deleted file mode 100644 index 016f26e0..00000000 --- "a/Solutions/0345. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\351\237\263\345\255\227\346\257\215.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [0345. 反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:将字符串中的元音字母进行反转。 - -**说明**: - -- 元音字母包括 `'a'`、`'e'`、`'i'`、`'o'`、`'u'`,且可能以大小写两种形式出现不止一次。 -- $1 \le s.length \le 3 \times 10^5$。 -- $s$ 由可打印的 ASCII 字符组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "hello" -输出:"holle" -``` - -- 示例 2: - -```python -输入:s = "leetcode" -输出:"leotcede" -``` - -## 解题思路 - -### 思路 1:对撞指针 - -1. 因为 Python 的字符串是不可变的,所以我们先将字符串转为数组。 -2. 使用两个指针 $left$,$right$。$left$ 指向字符串开始位置,$right$ 指向字符串结束位置。 -3. 然后 $left$ 依次从左到右移动查找元音字母,$right$ 依次从右到左查找元音字母。 -4. 如果都找到了元音字母,则交换字符,然后继续进行查找。 -5. 如果遇到 $left == right$ 时停止。 -6. 最后返回对应的字符串即可。 - -### 思路 1:代码 - -```python -class Solution: - def reverseVowels(self, s: str) -> str: - vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'] - left = 0 - right = len(s)-1 - s_list = list(s) - while left < right: - if s_list[left] not in vowels: - left += 1 - continue - if s_list[right] not in vowels: - right -= 1 - continue - s_list[left], s_list[right] = s_list[right], s_list[left] - left += 1 - right -= 1 - return "".join(s_list) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为字符串 $s$ 的长度。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0346. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\247\273\345\212\250\345\271\263\345\235\207\345\200\274.md" "b/Solutions/0346. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\247\273\345\212\250\345\271\263\345\235\207\345\200\274.md" deleted file mode 100644 index fafbe81f..00000000 --- "a/Solutions/0346. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\247\273\345\212\250\345\271\263\345\235\207\345\200\274.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0346. 数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) - -- 标签:设计、队列、数组、数据流 -- 难度:简单 - -## 题目大意 - -给定一个整数 `val` 和一个窗口大小 `size`。 - -要求:根据滑动窗口的大小,计算滑动窗口里所有数字的平均值。要实现 `MovingAverage` 类: - -- `MovingAverage(int size)` 用窗口大小 `size` 初始化对象。 -- `double next(int val)` 成员函数 `next` 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 `size` 个值的移动平均值,即滑动窗口里所有数字的平均值。 - -## 解题思路 - -使用队列保存滑动窗口的元素,并记录对应窗口大小和元素和。 - -在小于窗口大小的时候,直接向队列中添加元素,并记录元素和。 - -在等于窗口大小的时候,先将队列头部元素弹出,再添加元素,并记录元素和。 - -然后根据元素和和队列中元素个数计算出平均值。 - -## 代码 - -```python -class MovingAverage: - - def __init__(self, size: int): - """ - Initialize your data structure here. - """ - self.queue = [] - self.size = size - self.sum = 0 - - - def next(self, val: int) -> float: - if len(self.queue) < self.size: - self.queue.append(val) - else: - if self.queue: - self.sum -= self.queue[0] - self.queue.pop(0) - self.queue.append(val) - self.sum += val - return self.sum / len(self.queue) -``` - diff --git "a/Solutions/0347. \345\211\215 K \344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.md" "b/Solutions/0347. \345\211\215 K \344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.md" deleted file mode 100644 index b32c6193..00000000 --- "a/Solutions/0347. \345\211\215 K \344\270\252\351\253\230\351\242\221\345\205\203\347\264\240.md" +++ /dev/null @@ -1,133 +0,0 @@ -# [0347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) - -- 标签:数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 - -**要求**:返回出现频率前 $k$ 高的元素。可以按任意顺序返回答案。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $k$ 的取值范围是 $[1, \text{数组中不相同的元素的个数}]$。 -- 题目数据保证答案唯一,换句话说,数组中前 $k$ 个高频元素的集合是唯一的。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [1,1,1,2,2,3], k = 2 -输出: [1,2] -``` - -- 示例 2: - -```python -输入: nums = [1], k = 1 -输出: [1] -``` - -## 解题思路 - -### 思路 1:哈希表 + 优先队列 - -1. 使用哈希表记录下数组中各个元素的频数。 -2. 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -3. 使用二叉堆构建优先队列,优先级为元素频数。此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -4. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(log{n})$。 - - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 -5. 不断重复第 4 步,直到 $k$ 次结束。调整 $k$ 次的时间复杂度 $O(n \times \log n)$。 - -### 思路 1:代码 - -```python -class Heapq: - # 堆调整方法:调整为大顶堆 - def heapAdjust(self, nums: [int], nums_dict, index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子结点 - max_index = index - if nums_dict[nums[left]] > nums_dict[nums[max_index]]: - max_index = left - if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 将数组构建为二叉堆 - def heapify(self, nums: [int], nums_dict): - size = len(nums) - # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - # 调用调整堆函数 - self.heapAdjust(nums, nums_dict, i, size - 1) - - # 入队操作 - def heappush(self, nums: list, nums_dict, value): - nums.append(value) - size = len(nums) - i = size - 1 - # 寻找插入位置 - while (i - 1) // 2 >= 0: - cur_root = (i - 1) // 2 - # value 小于当前根节点,则插入到当前位置 - if nums_dict[nums[cur_root]] > nums_dict[value]: - break - # 继续向上查找 - nums[i] = nums[cur_root] - i = cur_root - # 找到插入位置或者到达根位置,将其插入 - nums[i] = value - - # 出队操作 - def heappop(self, nums: list, nums_dict) -> int: - size = len(nums) - nums[0], nums[-1] = nums[-1], nums[0] - # 得到最大值(堆顶元素)然后调整堆 - top = nums.pop() - if size > 0: - self.heapAdjust(nums, nums_dict, 0, size - 2) - - return top - -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - # 统计元素频数 - nums_dict = dict() - for num in nums: - if num in nums_dict: - nums_dict[num] += 1 - else: - nums_dict[num] = 1 - - # 使用 set 方法去重,得到新数组 - new_nums = list(set(nums)) - size = len(new_nums) - - heap = Heapq() - queue = [] - for num in new_nums: - heap.heappush(queue, nums_dict, num) - - res = [] - for i in range(k): - res.append(heap.heappop(queue, nums_dict)) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0349. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" "b/Solutions/0349. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" deleted file mode 100644 index 0ff90f0a..00000000 --- "a/Solutions/0349. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) - -- 标签:数组、哈希表、双指针、二分查找、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个数组 $nums1$ 和 $nums2$。 - -**要求**:返回两个数组的交集。重复元素只计算一次。 - -**说明**: - -- $1 \le nums1.length, nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2,2,1], nums2 = [2,2] -输出:[2] -示例 2: -``` - -- 示例 2: - -```python -输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] -输出:[9,4] -解释:[4,9] 也是可通过的 -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 先遍历第一个数组,利用哈希表来存放第一个数组的元素,对应字典值设为 $1$。 -2. 然后遍历第二个数组,如果哈希表中存在该元素,则将该元素加入到答案数组中,并且将该键值清空。 - -### 思路 1:代码 - -```python -class Solution: - def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: - numDict = dict() - nums = [] - for num in nums1: - if num not in numDict: - numDict[num] = 1 - for num in nums2: - if num in numDict and numDict[num] != 0: - numDict[num] -= 1 - nums.append(num) - return nums -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:分离双指针 - -1. 对数组 $nums1$、$nums2$ 先排序。 -2. 使用两个指针 $left\underline{}1$、$left\underline{}2$。$left\underline{}1$ 指向第一个数组的第一个元素,即:$left\underline{}1 = 0$,$left\underline{}2$ 指向第二个数组的第一个元素,即:$left\underline{}2 = 0$。 -3. 如果 $nums1[left_1]$ 等于 $nums2[left_2]$,则将其加入答案数组(注意去重),并将 $left\underline{}1$ 和 $left\underline{}2$ 右移。 -4. 如果 $nums1[left_1]$ 小于 $nums2[left_2]$,则将 $left\underline{}1$ 右移。 -5. 如果 $nums1[left_1]$ 大于 $nums2[left_2]$,则将 $left\underline{}2$ 右移。 -6. 最后返回答案数组。 - -### 思路 2:代码 - -```python -class Solution: - def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: - nums1.sort() - nums2.sort() - - left_1 = 0 - left_2 = 0 - res = [] - while left_1 < len(nums1) and left_2 < len(nums2): - if nums1[left_1] == nums2[left_2]: - if nums1[left_1] not in res: - res.append(nums1[left_1]) - left_1 += 1 - left_2 += 1 - elif nums1[left_1] < nums2[left_2]: - left_1 += 1 - elif nums1[left_1] > nums2[left_2]: - left_2 += 1 - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0350. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206 II.md" "b/Solutions/0350. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206 II.md" deleted file mode 100644 index 29f04591..00000000 --- "a/Solutions/0350. \344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206 II.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) - -- 标签:数组、哈希表 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个数组 `nums1` 和 `nums2`。 - -**要求**:返回两个数组的交集。可以不考虑输出结果的顺序。 - -**说明**: - -- 输出结果中,每个元素出现的次数,应该与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。 -- $1 \le nums1.length, nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 1000$。 - -**示例**: - -```python -输入:nums1 = [1,2,2,1], nums2 = [2,2] -输出:[2,2] - - -输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] -输出:[4,9] -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 先遍历第一个数组,利用字典来存放第一个数组的元素出现次数。 -2. 然后遍历第二个数组,如果字典中存在该元素,则将该元素加入到答案数组中,并减少字典中该元素出现的次数。 -3. 遍历完之后,返回答案数组。 - -### 思路 1:代码 - -```python -class Solution: - def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: - numDict = dict() - nums = [] - for num in nums1: - if num in numDict: - numDict[num] += 1 - else: - numDict[num] = 1 - for num in nums2: - if num in numDict and numDict[num] != 0: - numDict[num] -= 1 - nums.append(num) - return nums -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0351. \345\256\211\345\215\223\347\263\273\347\273\237\346\211\213\345\212\277\350\247\243\351\224\201.md" "b/Solutions/0351. \345\256\211\345\215\223\347\263\273\347\273\237\346\211\213\345\212\277\350\247\243\351\224\201.md" deleted file mode 100644 index 01872dab..00000000 --- "a/Solutions/0351. \345\256\211\345\215\223\347\263\273\347\273\237\346\211\213\345\212\277\350\247\243\351\224\201.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [0351. 安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) - -- 标签:动态规划、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:安卓系统手势解锁的界面是一个编号为 $1 \sim 9$、大小为 $3 \times 3$ 的网格。用户可以设定一个「解锁模式」,按照一定顺序经过 $k$ 个点,构成一个「解锁手势」。现在给定两个整数,分别为 $m$ 和 $n$。 - -**要求**:计算出有多少种不同且有效的解锁模式数量,其中每种解锁模式至少需要经过 $m$ 个点,但是不超过 $n$ 个点。 - -**说明**: - -- **有效的解锁模式**: - - 解锁模式中所有点不能重复。 - - 如果解锁模式中两个点是按顺序经过的,那么这两个点之间的手势轨迹不能跨过其他任何未被经过的点。 - -- 一些有效和无效解锁模式示例: - - ![](https://assets.leetcode.com/uploads/2018/10/12/android-unlock.png) - - 无效手势:$[4,1,3,6]$,连接点 $1$ 和点 $3$ 时经过了未被连接过的 $2$ 号点。 - - 无效手势:$[4,1,9,2]$,连接点 $1$ 和点 $9$ 时经过了未被连接过的 $5$ 号点。 - - 有效手势:$[2,4,1,3,6]$,连接点 $1$ 和点 $3$ 是有效的,因为虽然它经过了点 $2$,但是点 $2$ 在该手势中之前已经被连过了。 - - 有效手势:$[6,5,4,1,9,2]$,连接点 $1$ 和点 $9$ 是有效的,因为虽然它经过了按键 $5$,但是点 $5$ 在该手势中之前已经被连过了。 - -- $1 \le m, n \le 9$。 -- 如果经过的点不同或者经过点的顺序不同,表示为不同的解锁模式。 - -**示例**: - -- 示例 1: - -```python -输入:m = 1, n = 1 -输出:9 -``` - -- 示例 2: - -```python -输入:m = 1, n = 2 -输出:65 -``` - -## 解题思路 - -### 思路 1:状态压缩 + 记忆化搜索 - -因为手势解锁的界面是一个编号为 $1 \sim 9$、大小为 $3 \times 3$ 的网格,所以我们可以用一个 $9$ 位长度的二进制数 $state$ 来表示当前解锁模式中按键的选取情况。 - -因为解锁模式中两个点之间的手势轨迹不能跨过其他任何未被经过的点,所以我们可以预先使用一个哈希表 $graph$ 将手势轨迹跨过其他点的情况存储下来,便于判断当前手势轨迹是否有效。 - -接下来我们使用深度优先搜索方法,将所有有效的解锁模式统计出来,具体做法如下: - -1. 定义一个全局变量 $ans$ 用于统计所有有效的解锁模式的方案数。 -2. 定义一个深度优先搜索方法为 `def dfs(state, cur, step):`,表示当前键位选择情况为 $state$,从当前键位 $cur$ 出发,已经走了 $step$ 的有效解锁模式。 - 1. 当 $step$ 在区间 $[m, n]$ 中时,统计有效解锁模式方案数,即:令 $ans$ 加 $1$。 - 2. 当 $step$ 到达步数上限 $n$ 时,直接返回。 - 3. 遍历下一步(第 $step + 1$ 步)可选择的键位 $k$,判断键位 $k$ 是否有效。 - 4. 如果到达 $k$ 没有跨过其他键($k$ 不在 $graph[cur]$ 中),或者到达 $k$ 跨过的键位是已经经过的键 ($state >> graph[cur][k] \text{ \& } 1 == 1$),则继续调用 `dfs(state | (1 << k), k, step + 1)`,其中 `stete | (1 << k)` 表示下一步选择 $k$ 的状态。 -3. 遍历开始位置 $1 \sim 9$,从 1 ~ 9 每个数字开始出发,调用 `dfs(1 << i, i, 1)`,进行所有有效的解锁模式的统计。 -4. 最后输出 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def numberOfPatterns(self, m: int, n: int) -> int: - # 将手势轨迹跨过点的情况存入哈希表中 - graph = { - 1: {3: 2, 7: 4, 9: 5}, - 2: {8: 5}, - 3: {1: 2, 7: 5, 9: 6}, - 4: {6: 5}, - 5: {}, - 6: {4: 5}, - 7: {1: 4, 3: 5, 9: 8}, - 8: {2: 5}, - 9: {1: 5, 3: 6, 7: 8}, - } - - ans = 0 - - def dfs(state, cur, step): - nonlocal ans - if m <= step <= n: - ans += 1 - - if step == n: - return - - for k in range(1, 10): - if state >> k & 1 != 0: - continue - if k not in graph[cur] or state >> graph[cur][k] & 1: - dfs(state | (1 << k), k, step + 1) - - for i in range(1, 10): - dfs(1 << i, i, 1) # 从 1 ~ 9 每个数字开始出发 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n!)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- 【题解】[LeetCode-351. 安卓系统手势解锁 - mkdocs_blog](https://github.com/zhanguohao/mkdocs_blog/blob/mkdocs_blog/docs/problem/leetcode/LeetCode-351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md) diff --git "a/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" "b/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" deleted file mode 100644 index 93907937..00000000 --- "a/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) - -- 标签:数组、二分查找、动态规划、排序 -- 难度:困难 - -## 题目大意 - -给定一个二维整数数组 envelopes 表示信封,其中 `envelopes[i] = [wi, hi]`,表示第 i 个信封的宽度 wi 和高度 hi。 - -当一个信封的宽度和高度比另一个信封大时,则小的信封可以放进大信封里,就像俄罗斯套娃一样。 - -现在要求:计算最多能有多少个信封组成一组「俄罗斯套娃」信封。 - -注意:不允许旋转信封(也就是说宽高不能互换)。 - -## 解题思路 - -如果最多有 k 个信封可以组成「俄罗斯套娃」信封。那么这 k 个信封按照宽高关系排序一定满足: - -- $w_0 < w_1 < ... < w_{k-1}$ -- $h_0 < h_1 < ... < h_{k-1}$ - -因为原二维数组是无序的,直接暴力搜素宽高升序序列并不容易。所以我们可以先固定一个维度,将其变为升序状态。再在另一个维度上进行选择。比如固定宽度为升序,则我们的问题就变为了:在高度这一维度下,求解数组的最长递增序列的长度。就变为了经典的「最长递增序列的长度问题」。即 [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)。 - -「最长递增序列的长度问题」的思路如下: - -动态规划的状态 `dp[i]` 表示为:以第 i 个数字结尾的前 i 个元素中最长严格递增子序列的长度。 - -遍历前 i 个数字,`0 ≤ j ≤ i`: - -- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 i 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。 -- 当 `nums[j] ≥ nums[i]` 时,可以直接跳过。 - -则状态转移方程为:`dp[i] = max(dp[i], dp[j] + 1)`,`0 ≤ j ≤ i`,`nums[j] < nums[i]`。 - -最后再遍历一遍 dp 数组,求出最大值即可。 - -## 代码 - -```python -class Solution: - def maxEnvelopes(self, envelopes: List[List[int]]) -> int: - if not envelopes: - return 0 - size = len(envelopes) - envelopes.sort(key=lambda x: (x[0], -x[1])) - - dp = [1 for _ in range(size)] - - for i in range(size): - for j in range(i): - if envelopes[j][1] < envelopes[i][1]: - dp[i] = max(dp[i], dp[j] + 1) - - return max(dp) -``` - diff --git "a/Solutions/0357. \347\273\237\350\256\241\345\220\204\344\275\215\346\225\260\345\255\227\351\203\275\344\270\215\345\220\214\347\232\204\346\225\260\345\255\227\344\270\252\346\225\260.md" "b/Solutions/0357. \347\273\237\350\256\241\345\220\204\344\275\215\346\225\260\345\255\227\351\203\275\344\270\215\345\220\214\347\232\204\346\225\260\345\255\227\344\270\252\346\225\260.md" deleted file mode 100644 index 8d78168b..00000000 --- "a/Solutions/0357. \347\273\237\350\256\241\345\220\204\344\275\215\346\225\260\345\255\227\351\203\275\344\270\215\345\220\214\347\232\204\346\225\260\345\255\227\344\270\252\346\225\260.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0357. 统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) - -- 标签:数学、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:统计并返回区间 $[0, 10^n)$ 上各位数字都不相同的数字 $x$ 的个数。 - -**说明**: - -- $0 \le n \le 8$。 -- $0 \le x < 10^n$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:91 -解释:答案应为除去 11、22、33、44、55、66、77、88、99 外,在 0 ≤ x < 100 范围内的所有数字。 -``` - -- 示例 2: - -```python -输入:n = 0 -输出:1 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -题目求解区间 $[0, 10^n)$ 范围内各位数字都不相同的数字个数。则我们先将 $10^n - 1$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 - 4. 开始时没有填写数字。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), - 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 - 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 - 3. $isNum == True$ 表示 $pos$ 位选择了数字。 -6. 最后的方案数为 `dfs(0, 0, True, False) + 1`,因为之前计算时没有考虑 $0$,所以最后统计方案数时要加 $1$。 - -### 思路 1:代码 - -```python -class Solution: - def countNumbersWithUniqueDigits(self, n: int) -> int: - s = str(10 ** n - 1) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - # d 不在选择的数字集合中,即之前没有选择过 d - if (state >> d) & 1 == 0: - ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) - return ans - - return dfs(0, 0, True, False) + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 10 \times 2^{10})$。 -- **空间复杂度**:$O(n \times 2^{10})$。 - diff --git "a/Solutions/0359. \346\227\245\345\277\227\351\200\237\347\216\207\351\231\220\345\210\266\345\231\250.md" "b/Solutions/0359. \346\227\245\345\277\227\351\200\237\347\216\207\351\231\220\345\210\266\345\231\250.md" deleted file mode 100644 index ebd214a2..00000000 --- "a/Solutions/0359. \346\227\245\345\277\227\351\200\237\347\216\207\351\231\220\345\210\266\345\231\250.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [0359. 日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) - -- 标签:设计、哈希表 -- 难度:简单 - -## 题目大意 - -设计一个日志系统,可以流式接受消息和消息的时间戳。每条不重复的信息最多每 10 秒打印一次。即如果在时间 t 打印了 A 信息,则直到 t+10 的时间,才能再次打印这条信息。 - -要求实现 Logger 类: - -- `def __init__(self):` 初始化 logger 对象 -- `def shouldPrintMessage(self, timestamp: int, message: str) -> bool:` - - 如果该条消息 message 在给定时间戳 timestamp 能够打印出来,则返回 True,否则返回 False。 - -## 解题思路 - -初始化一个哈希表,用来存储消息 message 最后一次打印的时间戳。 - -当新的消息到达是,先判断之前是否出现过相同的消息,如果未出现则可打印,存储时间戳,并返回 True。 - -如果出现过,且上一次相同的消息在 10 秒之前打印的,则该消息也可打印,更新时间戳,并返回 True。 - -如果上一次相同的消息是在 10 秒内打印的,则该信息不可打印,直接返回 False。 - -## 代码 - -```python -class Logger: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.msg_dict = dict() - - - def shouldPrintMessage(self, timestamp: int, message: str) -> bool: - """ - Returns true if the message should be printed in the given timestamp, otherwise returns false. - If this method returns false, the message will not be printed. - The timestamp is in seconds granularity. - """ - if message not in self.msg_dict: - self.msg_dict[message] = timestamp - return True - if timestamp - self.msg_dict[message] >= 10: - self.msg_dict[message] = timestamp - return True - else: - return False -``` - diff --git "a/Solutions/0360. \346\234\211\345\272\217\350\275\254\345\214\226\346\225\260\347\273\204.md" "b/Solutions/0360. \346\234\211\345\272\217\350\275\254\345\214\226\346\225\260\347\273\204.md" deleted file mode 100644 index 52e09529..00000000 --- "a/Solutions/0360. \346\234\211\345\272\217\350\275\254\345\214\226\346\225\260\347\273\204.md" +++ /dev/null @@ -1,91 +0,0 @@ -# [0360. 有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) - -- 标签:数组、数学、双指针、排序 -- 难度:中等 - -## 题目大意 - -给定一个已经排好的整数数组 `nums` 和整数 `a`、`b`、`c`。 - -要求:对于数组中的每一个数 `x`,计算函数值 $f(x) = ax^2 + bx + c$,请将函数值产生的数组返回。 - -注意:返回的这个数组必须按照升序排列,并且我们所期望的解法时间复杂度为 $O(n)$。 - -## 解题思路 - -这是一道数学题。需要根据一元二次函数的性质来解决问题。因为返回的数组必须按照升序排列,并且期望的解法时间复杂度为 $O(n)$。这就不能先计算再排序了,而是要在线性时间复杂度内考虑问题。 - -我们先定义一个函数用来计算 `f(x)`。然后进行分情况讨论。 - -- 如果 `a == 0`,说明函数是一条直线。则根据 `b` 值的正负来确定数组遍历顺序。 - - 如果 `b >= 0`,说明这条直线是一条递增直线。则按照从头到尾的顺序依次计算函数值,并依次存入答案数组。 - - 如果 `b < 0`,说明这条直线是一条递减直线。则按照从尾到头的顺序依次计算函数值,并依次存入答案数组。 -- 如果 `a > 0`,说明函数是一条开口向上的抛物线,最小值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越大。则可以使用双指针从远到近,由大到小依次填入数组。具体步骤如下: - - 使用双指针 `left`、`right`,令 `left` 指向数组第一个元素位置,`right` 指向数组最后一个元素位置。再定义 `index = len(nums) - 1` 作为答案数组填入顺序的索引值。 - - 比较 `left - diad` 与 `right - diad` 的绝对值大小。大的就是目前距离 `diad` 最远的那个。 - - 如果 `abs(nums[left] - diad)` 更大,则将其填入答案数组对应位置,并令 `left += 1`。 - - 如果 `abs(nums[right] - diad)` 更大,则将其填入答案数组对应位置,并令 `right -= 1`。 - - 令 `index -= 1`。 - - 直到 `left == right`,最后将 `nums[left]` 填入答案数组对应位置。 -- 如果 `a < 0`,说明函数是一条开口向下的抛物线,最大值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越小。则可以使用双指针从远到近,由小到大一次填入数组。具体步骤如下: - - 使用双指针 `left`、`right`,令 `left` 指向数组第一个元素位置,`right` 指向数组最后一个元素位置。再定义 `index = 0` 作为答案数组填入顺序的索引值。 - - 比较 `left - diad` 与 `right - diad` 的绝对值大小。大的就是目前距离 `diad` 最远的那个。 - - 如果 `abs(nums[left] - diad)` 更大,则将其填入答案数组对应位置,并令 `left += 1`。 - - 如果 `abs(nums[right] - diad)` 更大,则将其填入答案数组对应位置,并令 `right -= 1`。 - - 令 `index += 1`。 - - 直到 `left == right`,最后将 `nums[left]` 填入答案数组对应位置。 - -## 代码 - -```python -class Solution: - def calFormula(self, x, a, b, c): - return a * x * x + b * x + c - - def sortTransformedArray(self, nums: List[int], a: int, b: int, c: int) -> List[int]: - size = len(nums) - res = [0 for _ in range(size)] - - # 直线 - if a == 0: - if b >= 0: - index = 0 - for i in range(size): - res[index] = self.calFormula(nums[i], a, b, c) - index += 1 - else: - index = 0 - for i in range(size - 1, -1, -1): - res[index] = self.calFormula(nums[i], a, b, c) - index += 1 - else: - diad = -(b / (2.0 * a)) - left, right = 0, size - 1 - - if a > 0: - index = size - 1 - while left < right: - if abs(diad - nums[left]) > abs(diad - nums[right]): - res[index] = self.calFormula(nums[left], a, b, c) - left += 1 - else: - res[index] = self.calFormula(nums[right], a, b, c) - right -= 1 - index -= 1 - res[index] = self.calFormula(nums[left], a, b, c) - else: - diad = -(b / (2.0 * a)) - left, right = 0, size - 1 - index = 0 - while left < right: - if abs(diad - nums[left]) > abs(diad - nums[right]): - res[index] = self.calFormula(nums[left], a, b, c) - left += 1 - else: - res[index] = self.calFormula(nums[right], a, b, c) - right -= 1 - index += 1 - res[index] = self.calFormula(nums[left], a, b, c) - return res -``` - diff --git "a/Solutions/0367. \346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" "b/Solutions/0367. \346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" deleted file mode 100644 index 9647072d..00000000 --- "a/Solutions/0367. \346\234\211\346\225\210\347\232\204\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [0367. 有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) - -- 标签:数学、二分查找 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个正整数 $num$。 - -**要求**:判断 num 是不是完全平方数。 - -**说明**: - -- 要求不能使用内置的库函数,如 `sqrt`。 -- $1 \le num \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:num = 16 -输出:True -解释:返回 true,因为 4 * 4 = 16 且 4 是一个整数。 -``` - -- 示例 2: - -```python -输入:num = 14 -输出:False -解释:返回 false,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -如果 $num$ 是完全平方数,则 $num = x \times x$,$x$ 为整数。问题就变为了对于正整数 $num$,是否能找到一个整数 $x$,使得 $x \times x = num$。 - -而对于 $x$,我们可以通过二分查找算法快速找到。 - -### 思路 1:代码 - -```python -class Solution: - def isPerfectSquare(self, num: int) -> bool: - left = 0 - right = num - while left < right: - mid = left + (right - left) // 2 - if mid * mid > num: - right = mid - 1 - elif mid * mid < num: - left = mid + 1 - else: - left = mid - break - return left * left == num -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$,其中 $n$ 为正整数 $num$ 的最大值。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0370. \345\214\272\351\227\264\345\212\240\346\263\225.md" "b/Solutions/0370. \345\214\272\351\227\264\345\212\240\346\263\225.md" deleted file mode 100644 index c56f94da..00000000 --- "a/Solutions/0370. \345\214\272\351\227\264\345\212\240\346\263\225.md" +++ /dev/null @@ -1,207 +0,0 @@ -# [0370. 区间加法](https://leetcode.cn/problems/range-addition/) - -- 标签:数组、前缀和 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组的长度 `length` ,初始情况下数组中所有数字均为 `0`。再给定 `k` 个更新操作。其中每个操作是一个三元组 `[startIndex, endIndex, inc]`,表示将子数组 `nums[startIndex ... endIndex]` (包括 `startIndex`、`endIndex`)上所有元素增加 `inc`。 - -**要求**:返回 `k` 次操作后的数组。 - -**示例**: - -- 示例 1: - -``` -给定 length = 5,即 nums = [0, 0, 0, 0, 0] - -操作 [1, 3, 2] -> [0, 2, 2, 2, 0] -操作 [2, 4, 3] -> [0, 2, 5, 5, 3] -操作 [0, 2, -2] -> [-2, 0, 3, 5, 3] -``` - -## 解题思路 - -### 思路 1:线段树 - -- 初始化一个长度为 `length`,值全为 `0` 的 `nums` 数组。 -- 然后根据 `nums` 数组构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。并且线段树使用延迟标记。 -- 然后遍历三元组操作,进行区间累加运算。 -- 最后从线段树中查询数组所有元素,返回该数组即可。 - -这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 - -### 思路 1:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if self.tree[index].lazy_tag is not None: - self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val - else: - self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - interval_size = (right - left + 1) # 当前节点所在区间大小 - self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val - return - - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - self.__pushdown(index) # 向下更新节点的区间值 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if lazy_tag is None: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - if self.tree[left_index].lazy_tag is not None: - self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - self.tree[left_index].lazy_tag = lazy_tag - left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) - self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag - - if self.tree[right_index].lazy_tag is not None: - self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - self.tree[right_index].lazy_tag = lazy_tag - right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) - self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag - - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 - -class Solution: - def getModifiedArray(self, length: int, updates: List[List[int]]) -> List[int]: - nums = [0 for _ in range(length)] - self.ST = SegmentTree(nums, lambda x, y: x + y) - for update in updates: - self.ST.update_interval(update[0], update[1], update[2]) - - return self.ST.get_nums() -``` - diff --git "a/Solutions/0371. \344\270\244\346\225\264\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0371. \344\270\244\346\225\264\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index f4257ed7..00000000 --- "a/Solutions/0371. \344\270\244\346\225\264\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0371. 两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) - -- 标签:位运算、数学 -- 难度:中等 - -## 题目大意 - -不使用运算符 `+` 和 `-` ,计算两整数 `a` 、`b` 之和。 - -## 解题思路 - -需要用到位运算的一些知识。 - -- 异或运算 a ^ b :可以获得 a + b 无进位的加法结果。 -- 与运算 a & b:对应位置为 1,说明 a、b 该位置上原来都为 1,则需要进位。 -- 座椅运算 a << 1:将 a 对应二进制数左移 1 位。 - -这样,通过 a^b 运算,我们可以得到相加后无进位结果,再根据 (a&b) << 1,计算进位后结果。 - -进行 a^b 和 (a&b) << 1操作之后判断进位是否为 0,若不为 0,则继续上一步操作,直到进位为 0。 - -> 注意: -> -> Python 的整数类型是无限长整数类型,负数不确定符号位是第几位。所以我们可以将输入的数字手动转为 32 位无符号整数。 -> -> 通过 a &= 0xFFFFFFFF 即可将 a 转为 32 位无符号整数。最后通过对 a 的范围判断,将其结果映射为有符号整数。 - -## 代码 - -```python -class Solution: - def getSum(self, a: int, b: int) -> int: - MAX_INT = 0x7FFFFFFF - MASK = 0xFFFFFFFF - a &= MASK - b &= MASK - while b: - carry = ((a & b) << 1) & MASK - a ^= b - b = carry - if a <= MAX_INT: - return a - else: - return ~(a ^ MASK) -``` - diff --git "a/Solutions/0374. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217.md" "b/Solutions/0374. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217.md" deleted file mode 100644 index f2eb46ad..00000000 --- "a/Solutions/0374. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [0374. 猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) - -- 标签:二分查找、交互 -- 难度:简单 - -## 题目大意 - -**描述**:猜数字游戏。给定一个整数 $n$ 和一个接口 `def guess(num: int) -> int:`,题目会从 $1 \sim n$ 中随机选取一个数 $x$。我们只能通过调用接口来判断自己猜测的数是否正确。 - -**要求**:要求返回题目选取的数字 $x$。 - -**说明**: - -- `def guess(num: int) -> int:` 返回值: - - $-1$:我选出的数字比你猜的数字小,即 $pick < num$; - - $1$:我选出的数字比你猜的数字大 $pick > num$; - - $0$:我选出的数字和你猜的数字一样。恭喜!你猜对了!$pick == num$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 10, pick = 6 -输出:6 -``` - -- 示例 2: - -```python -输入:n = 1, pick = 1 -输出:1 -``` - -## 解题思路 - -### 思路 1:二分查找 - -利用两个指针 $left$、$right$。$left$ 指向数字 $1$,$right$ 指向数字 $n$。每次从中间开始调用接口猜测是否正确。 - -- 如果猜测的数比选中的数大,则将 $right$ 向左移,令 `right = mid - 1`,继续从中间调用接口猜测; -- 如果猜测的数比选中的数小,则将 $left$ 向右移,令 `left = mid + 1`,继续从中间调用的接口猜测; -- 如果猜测正确,则直接返回该数。 - -### 思路 1:二分查找代码 - -```python -class Solution: - def guessNumber(self, n: int) -> int: - left = 1 - right = n - while left <= right: - mid = left + (right - left) // 2 - ans = guess(mid) - if ans == 1: - left = mid + 1 - elif ans == -1: - right = mid - 1 - else: - return mid - return 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 diff --git "a/Solutions/0375. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217 II.md" "b/Solutions/0375. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217 II.md" deleted file mode 100644 index 114543a3..00000000 --- "a/Solutions/0375. \347\214\234\346\225\260\345\255\227\345\244\247\345\260\217 II.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [0375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) - -- 标签:数学、动态规划、博弈 -- 难度:中等 - -## 题目大意 - -**描述**:现在两个人来玩一个猜数游戏,游戏规则如下: - -1. 对方从 $1 \sim n$ 中选择一个数字。 -2. 我们来猜对方选了哪个数字。 -3. 如果我们猜到了正确数字,就会赢得游戏。 -4. 如果我们猜错了,那么对方就会告诉我们,所选的数字比我们猜的数字更大或者更小,并且需要我们继续猜数。 -5. 每当我们猜了数字 $x$ 并且猜错了的时候,我们需要支付金额为 $x$ 的现金。如果我们花光了钱,就会输掉游戏。 - -现在给定一个特定数字 $n$。 - -**要求**:返回能够确保我们获胜的最小现金数(不管对方选择哪个数字)。 - -**说明**: - -- $1 \le n \le 200$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/09/10/graph.png) - -```python -输入:n = 10 -输出:16 -解释:制胜策略如下: -- 数字范围是 [1,10]。你先猜测数字为 7 。 - - 如果这是我选中的数字,你的总费用为 $0。否则,你需要支付 $7。 - - 如果我的数字更大,则下一步需要猜测的数字范围是 [8, 10] 。你可以猜测数字为 9。 - - 如果这是我选中的数字,你的总费用为 $7。否则,你需要支付 $9。 - - 如果我的数字更大,那么这个数字一定是 10。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16。 - - 如果我的数字更小,那么这个数字一定是 8。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16。 - - 如果我的数字更小,则下一步需要猜测的数字范围是 [1, 6]。你可以猜测数字为 3。 - - 如果这是我选中的数字,你的总费用为 $7。否则,你需要支付 $3。 - - 如果我的数字更大,则下一步需要猜测的数字范围是 [4, 6]。你可以猜测数字为 5。 - - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5。 - - 如果我的数字更大,那么这个数字一定是 6。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15。 - - 如果我的数字更小,那么这个数字一定是 4。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15。 - - 如果我的数字更小,则下一步需要猜测的数字范围是 [1, 2]。你可以猜测数字为 1。 - - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10。否则,你需要支付 $1。 - - 如果我的数字更大,那么这个数字一定是 2。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11。 -在最糟糕的情况下,你需要支付 $16。因此,你只需要 $16 就可以确保自己赢得游戏。 -``` - -- 示例 2: - -```python -输入:n = 2 -输出:1 -解释:有两个可能的数字 1 和 2 。 -- 你可以先猜 1 。 - - 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $1 。 - - 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $1 。 -最糟糕的情况下,你需要支付 $1。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -直觉上这道题应该通过二分查找来求解,但实际上并不能通过二分查找来求解。 - -因为我们可以通过二分查找方法,能够找到猜中的最小次数,但这个猜中的最小次数所对应的支付金额,并不是最小现金数。 - -也就是说,通过二分查找的策略,并不能找到确保我们获胜的最小现金数。所以我们需要转换思路。 - -我们可以用递归的方式来思考。 - -对于 $1 \sim n$ 中每一个数 $x$: - -1. 如果 $x$ 恰好是正确数字,则获胜,付出的现金数为 $0$。 -2. 如果 $x$ 不是正确数字,则付出现金数为 $x$,同时我们得知,正确数字比 $x$ 更大还是更小。 - 1. 如果正确数字比 $x$ 更小,我们只需要求出 $1 \sim x - 1$ 中能够获胜的最小现金数,再加上 $x$ 就是确保我们获胜的最小现金数。 - 2. 如果正确数字比 $x$ 更大,我们只需要求出 $x + 1 \sim n$ 中能够获胜的最小现金数,再加上 $x$ 就是确保我们获胜的最小现金数。 - 3. 因为正确数字可能比 $x$ 更小,也可能比 $x$ 更大。在考虑最坏情况下也能获胜,我们需要准备的最小现金应该为两种情况下的最小代价的最大值,再加上 $x$ 本身。 - -我们可以通过枚举 $x$,并求出所有情况下的最小值,即为确保我们获胜的最小现金数。 - -我们可以定义一个方法 $f(1)(n)$ 来表示 $1 \sim n$ 中能够获胜的最小现金数,则可以得到递推公式:$f(1)(n) = min_{x = 1}^{x = n} \lbrace max \lbrace f(1)(x - 1), f(x + 1)(n) \rbrace + x \rbrace)$。 - -将递推公式应用到 $i \sim j$ 中,可得:$f(i)(j) = min_{x = i}^{x = j} \lbrace max \lbrace f(i)(x - 1), f(x + 1)(j) \rbrace + x \rbrace)$ - -接下来我们就可以通过动态规划的方式解决这道题了。 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:数字 $i \sim j$ 中能够确保我们获胜的最小现金数。 - -###### 3. 状态转移方程 - -$dp[i][j] = min_{x = i}^{x = j} \lbrace max \lbrace dp[i][x - 1], dp[x + 1][j] \rbrace + x \rbrace)$ - -###### 4. 初始条件 - -- 默认数字 $i \sim j$ 中能够确保我们获胜的最小现金数为无穷大。 -- 当区间长度为 $1$ 时,区间中只有 $1$ 个数,肯定为正确数字,则付出最小现金数为 $0$,即 $dp[i][i] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:数字 $i \sim j$ 中能够确保我们获胜的最小现金数。所以最终结果为 $dp[1][n]$。 - -### 思路 1:代码 - -```python -class Solution: - def getMoneyAmount(self, n: int) -> int: - dp = [[0 for _ in range(n + 2)] for _ in range(n + 2)] - for l in range(2, n + 1): - for i in range(1, n + 1): - j = i + l - 1 - if j > n: - break - dp[i][j] = float('inf') - for k in range(i, j): - dp[i][j] = min(dp[i][j], max(dp[i][k - 1] + k, dp[k + 1][j] + k)) - - return dp[1][n] - -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0376. \346\221\206\345\212\250\345\272\217\345\210\227.md" "b/Solutions/0376. \346\221\206\345\212\250\345\272\217\345\210\227.md" deleted file mode 100644 index bcfcd931..00000000 --- "a/Solutions/0376. \346\221\206\345\212\250\345\272\217\345\210\227.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [0376. 摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -如果一个数组序列中,连续项之间的差值是严格的在正数、负数之间交替,则称该数组序列为「摆动序列」。第一个差值可能为正数,也可能为负数。只有一个元素或者还有两个不等元素的数组序列也可以看做是摆动序列。 - -- 例如:`[1, 7, 4, 9, 2, 5]` 是摆动序列 ,因为差值 `(6, -3, 5, -7, 3)` 是正负交替出现的。 -- 相反,`[1, 4, 7, 2, 5]` 和 `[1, 7, 4, 5, 5]` 不是摆动序列。第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 - -现在给定一个整数数组 nums,返回 nums 中作为「摆动序列」的「最长子序列长度」。 - -## 解题思路 - -我们先通过一个例子来说明如何求摆动数组最长子序列的长度。 - -下图是 `nums = [1,17,5,10,13,15,10,5,16,8]` 的图示。 - -![](http://qcdn.itcharge.cn/images/20210805131834.png) - -根据题意可知,摆动数组中连续项的差值是正负交替的,直观表现就像是一条高低起伏的山脉,或者像一把锯齿。 - -观察图像可知,貌似当我们不断交错的选择山脉的「波峰」和「波谷」作为子序列的元素,就会使摆动数组的子序列尽可能的长。例如下图选择 `[1, 17, 5, 15, 5, 16, 8]`。 - -![](http://qcdn.itcharge.cn/images/20210805131848.png) - -可是为什么选择「峰」「谷」就能使摆动数组的子序列尽可能的长?为什么我们不选择「中间元素」呢? - -其实也可以选择「中间元素」,**因为一路从波谷爬坡到波峰再到波谷,和从波谷爬坡到半山腰再回到波谷所形成的摆动数组最长子序列的长度是一样的。** - -只不过如果选择中间元素,这个中间元素两侧必有波峰和波谷,我们假设选择的序列出现顺序为:「谷 -> 中间元素 -> 谷」,则「谷」和「谷」中间的「峰」必定没有出现在选择的序列中,我们必然可以将选择的「中间元素」替换为「峰」。 - -同理,「峰 -> 中间元素 -> 峰」中选择的「中间元素」必然也可以替换为「谷」。 - -所以既然中可以替换,所以我们干脆直接选择「峰」「谷」就可以满足最长子序列的长度。 - -所以题目就变为了:统计序列中「峰」「谷」的数量。 - -记录下前一对连续项的差值、当前对连续项的差值,并判断是否是互为正负的。 - -- 如果互为正负,则为「峰」或「谷」,记录下个数,并更新前一对连续项的差值。 -- 如果符号相同,则继续向后判断。 - -## 代码 - -```python -class Solution: - def wiggleMaxLength(self, nums: List[int]) -> int: - size = len(nums) - cur_diff = 0 - pre_diff = 0 - res = 1 - for i in range(size - 1): - cur_diff = nums[i + 1] - nums[i] - if (cur_diff > 0 and pre_diff <= 0) or (pre_diff >= 0 and cur_diff < 0): - res += 1 - pre_diff = cur_diff - return res -``` - diff --git "a/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" "b/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" deleted file mode 100644 index 832a17b0..00000000 --- "a/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [0377. 组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由不同整数组成的数组 $nums$ 和一个目标整数 $target$。 - -**要求**:从 $nums$ 中找出并返回总和为 $target$ 的元素组合个数。 - -**说明**: - -- 题目数据保证答案符合 32 位整数范围。 -- $1 \le nums.length \le 200$。 -- $1 \le nums[i] \le 1000$。 -- $nums$ 中的所有元素互不相同。 -- $1 \le target \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3], target = 4 -输出:7 -解释: -所有可能的组合为: -(1, 1, 1, 1) -(1, 1, 2) -(1, 2, 1) -(1, 3) -(2, 1, 1) -(2, 2) -(3, 1) -请注意,顺序不同的序列被视作不同的组合。 -``` - -- 示例 2: - -```python -输入:nums = [9], target = 3 -输出:0 -``` - -## 解题思路 - -### 思路 1:动态规划 - -「完全背包问题求方案数」的变形。本题与「完全背包问题求方案数」不同点在于:方案中不同的物品顺序代表不同方案。 - -比如「完全背包问题求方案数」中,凑成总和为 $4$ 的方案 $[1, 3]$ 算 $1$ 种方案,但是在本题中 $[1, 3]$、$[3, 1]$ 算 $2$ 种方案数。 - -我们需要在考虑某一总和 $w$ 时,需要将 $nums$ 中所有元素都考虑到。对应到循环关系时,即将总和 $w$ 的遍历放到外侧循环,将 $nums$ 数组元素的遍历放到内侧循环,即: - -```python -for w in range(target + 1): - for i in range(1, len(nums) + 1): - xxxx -``` - -###### 1. 划分阶段 - -按照总和进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:凑成总和 $w$ 的组合数。 - -###### 3. 状态转移方程 - -凑成总和为 $w$ 的组合数 = 「不使用当前 $nums[i - 1]$,只使用之前整数凑成和为 $w$ 的组合数」+「使用当前 $nums[i - 1]$ 凑成和为 $w - nums[i - 1]$ 的方案数」。即状态转移方程为:$dp[w] = dp[w] + dp[w - nums[i - 1]]$。 - -###### 4. 初始条件 - -- 凑成总和 $0$ 的组合数为 $1$,即 $dp[0] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[w]$ 表示为:凑成总和 $w$ 的组合数。 所以最终结果为 $dp[target]$。 - -### 思路 1:代码 - -```python -class Solution: - def combinationSum4(self, nums: List[int], target: int) -> int: - size = len(nums) - dp = [0 for _ in range(target + 1)] - dp[0] = 1 - - for w in range(target + 1): - for i in range(1, size + 1): - if w >= nums[i - 1]: - dp[w] = dp[w] + dp[w - nums[i - 1]] - - return dp[target] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 为目标整数。 -- **空间复杂度**:$O(target)$。 - diff --git "a/Solutions/0378. \346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254 K \345\260\217\347\232\204\345\205\203\347\264\240.md" "b/Solutions/0378. \346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254 K \345\260\217\347\232\204\345\205\203\347\264\240.md" deleted file mode 100644 index 254620a7..00000000 --- "a/Solutions/0378. \346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254 K \345\260\217\347\232\204\345\205\203\347\264\240.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [0378. 有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) - -- 标签:数组、二分查找、矩阵、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个 `n * n` 矩阵 `matrix`,其中每行和每列元素均按升序排序。 - -要求:找到矩阵中第 `k` 小的元素。 - -注意:它是排序后的第 `k` 小元素,而不是第 `k` 个 不同的元素。 - -## 解题思路 - -已知二维矩阵 `matrix` 每行每列是按照升序排序的。那么二维矩阵的下界就是左上角元素 `matrix[0][0]`,上界就是右下角元素 `matrix[rows - 1][cols - 1]`。那么我们可以使用二分查找的方法在上界、下界之间搜索所有值,找到第 `k` 小的元素。 - -我们可以通过判断矩阵中比 `mid` 小的元素个数是否等于 `k` 来确定是否找到第 `k` 小的元素。 - -- 如果比 `mid` 小的元素个数大于等于 `k`,说明最终答案 `ans` 小于等于 `mid`。 -- 如果比 `mid` 小的元素个数小于 `k`,说明最终答案 `ans` 大于 `k`。 - -## 代码 - -```python -class Solution: - def kthSmallest(self, matrix: List[List[int]], k: int) -> int: - rows, cols = len(matrix), len(matrix[0]) - left, right = matrix[0][0], matrix[rows - 1][cols - 1] + 1 - while left < right: - mid = left + (right - left) // 2 - if self.counterKthSmallest(mid, matrix) >= k: - right = mid - else: - left = mid + 1 - return left - - def counterKthSmallest(self, mid, matrix): - rows, cols = len(matrix), len(matrix[0]) - count = 0 - j = cols - 1 - for i in range(rows): - while j >= 0 and mid < matrix[i][j]: - j -= 1 - count += j + 1 - return count -``` - diff --git "a/Solutions/0380. O(1) \346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240.md" "b/Solutions/0380. O(1) \346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240.md" deleted file mode 100644 index c59b91d3..00000000 --- "a/Solutions/0380. O(1) \346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0380. 常数时间插入、删除和获取随机元素](https://leetcode.cn/problems/insert-delete-getrandom-o1/) - -- 标签:设计、数组、哈希表、数学、随机化 -- 难度:中等 - -## 题目大意 - -设计一个数据结构 ,支持时间复杂度为 O(1) 的以下操作: - -- insert(val):当元素 val 不存在时,向集合中插入该项。 -- remove(val):元素 val 存在时,从集合中移除该项。 -- getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 - -## 解题思路 - -普通动态数组进行访问操作,需要线性时间查找解决。我们可以利用哈希表记录下每个元素的下标,这样在访问时可以做到常数时间内访问元素了。对应的插入、删除、后去随机元素需要做相应的变化。 - -- 插入操作:将元素直接插入到数组尾部,并用哈希表记录插入元素的下标位置。 -- 删除操作:使用哈希表找到待删除元素所在位置,将其与数组末尾位置元素相互交换,更新哈希表中交换后元素的下标值,并将末尾元素删除。 -- 获取随机元素:使用` random.choice` 获取。 - -## 代码 - -```python -import random - -class RandomizedSet: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.dict = dict() - self.list = list() - - - def insert(self, val: int) -> bool: - """ - Inserts a value to the set. Returns true if the set did not already contain the specified element. - """ - if val in self.dict: - return False - self.dict[val] = len(self.list) - self.list.append(val) - return True - - def remove(self, val: int) -> bool: - """ - Removes a value from the set. Returns true if the set contained the specified element. - """ - if val in self.dict: - idx = self.dict[val] - last = self.list[-1] - self.list[idx] = last - self.dict[last] = idx - self.list.pop() - self.dict.pop(val) - return True - return False - - - def getRandom(self) -> int: - """ - Get a random element from the set. - """ - return random.choice(self.list) -``` - diff --git "a/Solutions/0383. \350\265\216\351\207\221\344\277\241.md" "b/Solutions/0383. \350\265\216\351\207\221\344\277\241.md" deleted file mode 100644 index 8c1043cb..00000000 --- "a/Solutions/0383. \350\265\216\351\207\221\344\277\241.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0383. 赎金信](https://leetcode.cn/problems/ransom-note/) - -- 标签:哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -为了不在赎金信中暴露字迹,从杂志上搜索各个需要的字母,组成单词来表达意思。 - -给定一个赎金信字符串 `ransomNote` 和一个杂志字符串 `magazine`。 - -要求:判断 `ransomNote` 能不能由 `magazines` 里面的字符构成。如果可以构成,返回 `True`;否则返回 `False`。 - -注意:`magazine` 中的每个字符只能在 `ransomNote` 中使用一次。 - -## 解题思路 - -暴力做法是双重循环遍历字符串 `ransomNote` 和 `magazine`。我们可以用哈希表来减少算法的时间复杂度。具体做法如下: - -- 先用哈希表存储 `magazine` 中各个字符的个数(哈希表可用字典或数组实现)。 -- 再遍历字符串 `ransomNote` 中每个字符,对于每个字符: - - 如果在哈希表中个数为 `0`,直接返回 `False`。 - - 如果在哈希表中个数不为 `0`,将其个数减 1。 -- 遍历到最后,则说明 `ransomNote` 能由 `magazines` 里面的字符构成。返回 `True`。 - -## 代码 - -```python -class Solution: - def canConstruct(self, ransomNote: str, magazine: str) -> bool: - magazine_counts = [0 for _ in range(26)] - - for ch in magazine: - num = ord(ch) - ord('a') - magazine_counts[num] += 1 - - for ch in ransomNote: - num = ord(ch) - ord('a') - if magazine_counts[num] == 0: - return False - else: - magazine_counts[num] -= 1 - - return True -``` - diff --git "a/Solutions/0384. \346\211\223\344\271\261\346\225\260\347\273\204.md" "b/Solutions/0384. \346\211\223\344\271\261\346\225\260\347\273\204.md" deleted file mode 100644 index 27b6c0a6..00000000 --- "a/Solutions/0384. \346\211\223\344\271\261\346\225\260\347\273\204.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0384. 打乱数组](https://leetcode.cn/problems/shuffle-an-array/) - -- 标签:数组、数学、随机化 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是等可能的。 - -实现 `Solution class`: - -- `Solution(int[] nums)` 使用整数数组 $nums$ 初始化对象。 -- `int[] reset()` 重设数组到它的初始状态并返回。 -- `int[] shuffle()` 返回数组随机打乱后的结果。 - -**说明**: - -- $1 \le nums.length \le 50$。 -- $-10^6 \le nums[i] \le 10^6$。 -- $nums$ 中的所有元素都是 唯一的。 -- 最多可以调用 $10^4$ 次 `reset` 和 `shuffle`。 - -**示例**: - -- 示例 1: - -```python -输入: -["Solution", "shuffle", "reset", "shuffle"] -[[[1, 2, 3]], [], [], []] -输出: -[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]] - -解释: -Solution solution = new Solution([1, 2, 3]); -solution.shuffle(); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2] -solution.reset(); // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3] -solution.shuffle(); // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2] -``` - -## 解题思路 - -### 思路 1:洗牌算法 - -题目要求在打乱顺序后,数组的所有排列应该是等可能的。对于长度为 $n$ 的数组,我们可以把问题转换为:分别在 $n$ 个位置上,选择填入某个数的概率是相同。具体选择方法如下: - -- 对于第 $0$ 个位置,我们从 $0 \sim n - 1$ 总共 $n$ 个数中随机选择一个数,将该数与第 $0$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{1}{n}$。 -- 对于第 $1$ 个位置,我们从剩下 $n - 1$ 个数中随机选择一个数,将该数与第 $1$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{n - 1}{n} \times \frac{1}{n - 1} = \frac{1}{n}$ (第一次没选到并且第二次被选中)。 -- 对于第 $2$ 个位置,我们从剩下 $n - 2$ 个数中随机选择一个数,将该数与第 $2$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{n - 1}{n} \times \frac{n - 2}{n - 1} \times \frac{1}{n - 2} = \frac{1}{n}$ (第一次没选到、第二次没选到,并且第三次被选中)。 -- 依次类推,对于每个位置上,每个数被选中的概率都是 $\frac{1}{n}$。 - -### 思路 1:洗牌算法代码 - -```python -class Solution: - - def __init__(self, nums: List[int]): - self.nums = nums - - - def reset(self) -> List[int]: - return self.nums - - - def shuffle(self) -> List[int]: - self.shuffle_nums = self.nums.copy() - for i in range(len(self.shuffle_nums)): - swap_index = random.randrange(i, len(self.shuffle_nums)) - self.shuffle_nums[i], self.shuffle_nums[swap_index] = self.shuffle_nums[swap_index], self.shuffle_nums[i] - return self.shuffle_nums -``` - -## 参考资料 - -- 【题解】[「Python/Java/JavaScript/Go」 洗牌算法 - 打乱数组 - 力扣](https://leetcode.cn/problems/shuffle-an-array/solution/pythonjavajavascriptgo-xi-pai-suan-fa-by-k7i2/) \ No newline at end of file diff --git "a/Solutions/0386. \345\255\227\345\205\270\345\272\217\346\216\222\346\225\260.md" "b/Solutions/0386. \345\255\227\345\205\270\345\272\217\346\216\222\346\225\260.md" deleted file mode 100644 index 9c151657..00000000 --- "a/Solutions/0386. \345\255\227\345\205\270\345\272\217\346\216\222\346\225\260.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [0386. 字典序排数](https://leetcode.cn/problems/lexicographical-numbers/) - -- 标签:深度优先搜索、字典树 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:按字典序返回范围 `[1, n]` 的所有整数。并且要求时间复杂度为 `O(n)`,空间复杂度为 `o(1)`。 - -## 解题思路 - -按照字典序进行深度优先搜索。实质上算是构造一棵字典树,然后将 `[1, n]` 中的数插入到字典树中,并将遍历结果存储到列表中。 - -## 代码 - -```python -class Solution: - def dfs(self, cur, n, res): - if cur > n: - return - res.append(cur) - for i in range(10): - num = 10 * cur + i - if num > n: - return - self.dfs(num, n, res) - - def lexicalOrder(self, n: int) -> List[int]: - res = [] - for i in range(1, 10): - self.dfs(i, n, res) - return res -``` - diff --git "a/Solutions/0387. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.md" "b/Solutions/0387. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.md" deleted file mode 100644 index 334a01f6..00000000 --- "a/Solutions/0387. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254\344\270\200\344\270\252\345\224\257\344\270\200\345\255\227\347\254\246.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [0387. 字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) - -- 标签:队列、哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -给定一个只包含小写字母的字符串 `s`。 - -要求:找到第一个不重复的字符,并返回它的索引。 - -## 解题思路 - -遍历字符串,使用哈希表存储字符串中每个字符的出现次数。然后第二次遍历时,找出只出现一次的字符。 - -## 代码 - -```python -class Solution: - def firstUniqChar(self, s: str) -> int: - strDict = dict() - for i in range(len(s)): - if s[i] in strDict: - strDict[s[i]] += 1 - else: - strDict[s[i]] = 1 - - for i in range(len(s)): - if s[i] in strDict and strDict[s[i]] == 1: - return i - return -1 -``` - -- 思路 2 代码: diff --git "a/Solutions/0389. \346\211\276\344\270\215\345\220\214.md" "b/Solutions/0389. \346\211\276\344\270\215\345\220\214.md" deleted file mode 100644 index 72b6920e..00000000 --- "a/Solutions/0389. \346\211\276\344\270\215\345\220\214.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [0389. 找不同](https://leetcode.cn/problems/find-the-difference/) - -- 标签:位运算、哈希表、字符串、排序 -- 难度:简单 - -## 题目大意 - -给定两个只包含小写字母的字符串 s、t。字符串 t 是由 s 进行随机重拍之后,再在随机位置添加一个字母得到的。要求:找出字符串 t 中被添加的字母。 - -## 解题思路 - -字符串 t 比字符串 s 多了一个随机字母。可以使用哈希表存储一下字符串 s 中各个字符的数量,再遍历一遍字符串 t 中的字符,从哈希表中减去对应数量的字符,最后剩的那一个字符就是多余的字符。 - -## 代码 - -```python -class Solution: - def findTheDifference(self, s: str, t: str) -> str: - s_dict = dict() - for ch in s: - if ch in s_dict: - s_dict[ch] += 1 - else: - s_dict[ch] = 1 - - for ch in t: - if ch in s_dict and s_dict[ch] != 0: - s_dict[ch] -= 1 - else: - return ch -``` - diff --git "a/Solutions/0391. \345\256\214\347\276\216\347\237\251\345\275\242.md" "b/Solutions/0391. \345\256\214\347\276\216\347\237\251\345\275\242.md" deleted file mode 100644 index 0f472859..00000000 --- "a/Solutions/0391. \345\256\214\347\276\216\347\237\251\345\275\242.md" +++ /dev/null @@ -1,277 +0,0 @@ -# [0391. 完美矩形](https://leetcode.cn/problems/perfect-rectangle/) - -- 标签:数组、扫描线 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个数组 `rectangles`,其中 `rectangles[i] = [xi, yi, ai, bi]` 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 `(xi, yi)`,右上顶点是 `(ai, bi)`。 - -**要求**:如果所有矩形一起精确覆盖了某个矩形区域,则返回 `True`;否则,返回 `False`。 - -**说明**: - -- $1 \le rectangles.length \le 2 * 10^4$。 -- $rectangles[i].length == 4$。 -- $-10^5 \le xi, yi, ai, bi \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/03/27/perectrec1-plane.jpg) - -```python -输入:rectangles = [[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]] -输出:True -解释:5 个矩形一起可以精确地覆盖一个矩形区域。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/03/27/perfectrec2-plane.jpg) - -```python -输入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]] -输出:false -解释:两个矩形之间有间隔,无法覆盖成一个矩形。 -``` - -- 示例 3: - -![](https://assets.leetcode.com/uploads/2021/03/27/perfecrrec4-plane.jpg) - -```python -输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]] -输出:False -解释:因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。 -``` - - -## 解题思路 - -### 思路 1:线段树 - -首先我们要先判断所有小矩形的面积和是否等于外接矩形区域的面积。如果不相等,则说明出现了重叠或者空缺,明显不符合题意。 - -在两者面积相等的情况下,还可能会发生重叠的情况。接下来我们要思考如何判断重叠。 - -- 第一种思路:暴力枚举所有矩形对,两两进行比较,判断是否出现了重叠。这样的时间复杂度是 $O(n^2)$,容易超时。 -- 第二种思路: - - 如果所有小矩形可以精确覆盖某个矩形区域,那这些小矩形一定是相互挨着的,也就是说相邻两个矩形的边会重合在一起。比如说 矩形 `A` 下边刚好是 `B` 的上边,或者是 `B` 的上边的一部分。 - - 我们可以固定一个坐标轴,比如说固定 `y` 轴,然后只看水平方向上所有矩形的边。然后我们就会发现,满足题意要求的矩形区域中,纵坐标为 `y` 的平行线上,「所有上边纵坐标为 `y` 的矩形上边区间」与「所有下边纵坐标为 `y` 的矩形下边区间」是完全一样,或者说重合在一起的(除了矩形矩形最上边和最下边只有一条,不会重合之外)。 - - 这样我们就可以用扫描线的思路,建立一个线段树。然后先固定纵坐标 `y`,将「所有上边纵坐标为 `y` 的矩形上边区间」对应的区间值减 `1`,再将「所有下边纵坐标为 `y` 的矩形下边区间」对应的区间值加 `1`。然后查询整个线代树区间值,如果区间值超过 `1`,则说明发生了重叠,不符合题目要求。如果扫描完所有的纵坐标,没有发生重叠,则说明符合题意要求。 - - 因为横坐标的范围为 $[-10^5,10^5]$,但是最多只有 $2 * 10^4$ 个横坐标,所以我们可以先对所有坐标做一下离散化处理,再根据离散化之后的横坐标建立线段树。 - -具体步骤如下: - -1. 通过遍历所有小矩形,计算出所有小矩形的面积和为 `area`。同时计算出矩形区域四个顶点位置,并根据四个顶点计算出矩形区域的面积为 `total_area`。如果所有小矩形面积不等于矩形区域的面积,则直接返回 `False`。 -2. 再次遍历所有小矩形,将所有坐标点进行离散化处理,将其编号存入两个哈希表 `x_dict`、`y_dict`。 -3. 使用哈希表 `top_dict`、`bottom_dict` 分别存储每个矩阵的上下两条边。将上下两条边的横坐标 `x1`、`x2`。分别存入到 `top_dict[y_dict[y2]]`、`top_dict[y_dict[y2]]` 中。 -4. 建立区间长度为横坐标个数的线段树 `STree`。 -5. 遍历所有的纵坐标,对于纵坐标 `i`: - 1. 先遍历当前纵坐标下矩阵的上边数组,即 `top_dict[i]`,取出边的横坐标 `x1`、`x2`。令区间 `[x1, x2 - 1]` 上的值减 `1`。 - 2. 再遍历当前纵坐标下矩阵的下边数组,即 `bottom_dict[i]`,取出边的横坐标 `x1`、`x2`。令区间 `[x1, x2 - 1]` 上的值加 `1`。 - 3. 如果上下边覆盖完之后,被覆盖次数超过了 `1`,则说明出现了重叠,直接返回 `Fasle`。 -6. 如果遍历完所有的纵坐标,没有发现重叠,则返回 `True`。 - -### 思路 1:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if self.tree[index].lazy_tag is not None: - self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val - else: - self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - self.tree[index].val += val # 当前节点所在区间每个元素值增加 val - return - - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - self.__pushdown(index) # 向下更新节点的区间值 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if lazy_tag is None: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - if self.tree[left_index].lazy_tag is not None: - self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - self.tree[left_index].lazy_tag = lazy_tag - self.tree[left_index].val += lazy_tag - - if self.tree[right_index].lazy_tag is not None: - self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - self.tree[right_index].lazy_tag = lazy_tag - self.tree[right_index].val += lazy_tag - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 - - -class Solution: - def isRectangleCover(self, rectangles) -> bool: - left, right, bottom, top = math.inf, -math.inf, math.inf, -math.inf - area = 0 - x_set, y_set = set(), set() - - for rectangle in rectangles: - x1, y1, x2, y2 = rectangle - left, right = min(left, x1), max(right, x2) - bottom, top = min(bottom, y1), max(top, y2) - area += (y2 - y1) * (x2 - x1) - x_set.add(x1) - x_set.add(x2) - y_set.add(y1) - y_set.add(y2) - - total_area = (top - bottom) * (right - left) - - # 判断所有小矩形面积是否等于所有矩形顶点构成最大矩形面积,不等于则直接返回 False - if area != total_area: - return False - - # 离散化处理所有点的横坐标、纵坐标 - x_dict, y_dict = dict(), dict() - - idx = 0 - for x in sorted(list(x_set)): - x_dict[x] = idx - idx += 1 - - idy = 0 - for y in sorted(list(y_set)): - y_dict[y] = idy - idy += 1 - - # 使用哈希表 top_dict、bottom_dict 分别存储每个矩阵的上下两条边。 - bottom_dict, top_dict = collections.defaultdict(list), collections.defaultdict(list) - for i in range(len(rectangles)): - x1, y1, x2, y2 = rectangles[i] - bottom_dict[y_dict[y1]].append([x_dict[x1], x_dict[x2]]) - top_dict[y_dict[y2]].append([x_dict[x1], x_dict[x2]]) - - # 建立线段树 - self.STree = SegmentTree([0 for _ in range(len(x_set))], lambda x, y: max(x, y)) - - for i in range(idy): - for x1, x2 in top_dict[i]: - self.STree.update_interval(x1, x2 - 1, -1) - for x1, x2 in bottom_dict[i]: - self.STree.update_interval(x1, x2 - 1, 1) - cnt = self.STree.query_interval(0, len(x_set) - 1) - if cnt > 1: - return False - return True -``` - -## 参考资料 - -- 【题解】[线段树+扫描线 - 完美矩形 - 力扣](https://leetcode.cn/problems/perfect-rectangle/solution/xian-duan-shu-sao-miao-xian-by-lucifer10-raw5/) \ No newline at end of file diff --git "a/Solutions/0392. \345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0392. \345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index 0e3569b4..00000000 --- "a/Solutions/0392. \345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [0392. 判断子序列](https://leetcode.cn/problems/is-subsequence/) - -- 标签:双指针、字符串、动态规划 -- 难度:简单 - -## 题目大意 - -给定字符串 `s` 和 `t` ,判断 `s` 是否为 `t` 的子序列。 - -## 解题思路 - -双指针。 - -使用两个指针 `i`、`j` 分别指向字符串 `s` 和 `t`,然后对两个字符串进行遍历。 - -- 遇到 `s[i] == t[j]` 的情况,则 `i` 向右移。 -- 不断右移 `j`。 -- 如果超过 `s` 或 `t` 的长度则跳出。 -- 最后判断指针 `i` 是否指向了 `s` 的末尾,即:判断 `i` 是否等于 `s` 的长度。如果等于,则说明 `s` 是 `t` 的子序列,如果不等于,则不是。 - -## 代码 - -```python -class Solution: - def isSubsequence(self, s: str, t: str) -> bool: - size_s = len(s) - size_t = len(t) - i, j = 0, 0 - while i < size_s and j < size_t: - if s[i] == t[j]: - i += 1 - j += 1 - return i == size_s -``` - diff --git "a/Solutions/0394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" "b/Solutions/0394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" deleted file mode 100644 index 039d9c1b..00000000 --- "a/Solutions/0394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) - -- 标签:栈、递归、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个经过编码的字符串 `s`。 - -**要求**:返回 `s` 经过解码之后的字符串。 - -**说明**: - -- 编码规则:`k[encoded_string]`。`encoded_string` 为字符串,`k` 为整数。表示字符串 `encoded_string` 重复 `k` 次。 -- $1 \le s.length \le 30$。 -- `s` 由小写英文字母、数字和方括号 `[]` 组成。 -- `s` 保证是一个有效的输入。 -- `s` 中所有整数的取值范围为 $[1, 300]$。 - -**示例**: - -- 示例 1: - -```python -输入:s = "3[a]2[bc]" -输出:"aaabcbc" -``` - -- 示例 2: - -```python -输入:s = "3[a2[c]]" -输出:"accaccacc" -``` - -## 解题思路 - -### 思路 1:栈 - -1. 使用两个栈 `stack1`、`stack2`。`stack1` 用来保存左括号前已经解码的字符串,`stack2` 用来存储左括号前的数字。 -2. 用 `res` 存储待解码的字符串、`num` 存储当前数字。 -3. 遍历字符串。 - 1. 如果遇到数字,则累加数字到 `num`。 - 2. 如果遇到左括号,将当前待解码字符串入栈 `stack1`,当前数字入栈 `stack2`,然后将 `res`、`nums` 清空。 - 3. 如果遇到右括号,则从 `stack1` 的取出待解码字符串 `res`,从 `stack2` 中取出当前数字 `num`,将其解码拼合成字符串赋值给 `res`。 - 4. 如果遇到其他情况(遇到字母),则将当前字母加入 `res` 中。 -4. 遍历完输出解码之后的字符串 `res`。 - -### 思路 1:代码 - -```python -class Solution: - def decodeString(self, s: str) -> str: - stack1 = [] - stack2 = [] - num = 0 - res = "" - for ch in s: - if ch.isdigit(): - num = num * 10 + int(ch) - elif ch == '[': - stack1.append(res) - stack2.append(num) - res = "" - num = 0 - elif ch == ']': - cur_res = stack1.pop() - cur_num = stack2.pop() - res = cur_res + res * cur_num - else: - res += ch - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" "b/Solutions/0395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" deleted file mode 100644 index 71747a91..00000000 --- "a/Solutions/0395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) - -- 标签:哈希表、字符串、分治、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s` 和一个整数 `k`。 - -要求:找出 `s` 中的最长子串, 要求该子串中的每一字符出现次数都不少于 `k` 。返回这一子串的长度。 - -注意:`s` 仅由小写英文字母构成。 - -## 解题思路 - -这道题看起来很像是常规滑动窗口套路的问题,但是用普通滑动窗口思路无法解决问题。 - -如果窗口需要保证「各种字符出现次数都大于等于 `k`」这一性质。那么当向右移动 `right`,扩大窗口时,如果 `s[right]` 是第一次出现的元素,窗口内的字符种类数量必然会增加,此时缩小 `s[left]` 也不一定满足窗口内「各种字符出现次数都大于等于 `k`」这一性质。那么我们就无法通过这种方式来进行滑动窗口。 - -但是我们可以通过固定字符种类数的方式进行滑动窗口。因为给定字符串 `s` 仅有小写字母构成,则最长子串中的字符种类数目,最少为 `1` 种,最多为 `26` 种。我们通过枚举最长子串中可能出现的字符种类数目,从而固定窗口中出现的字符种类数目 `i (1 <= i <= 26)`,再进行滑动数组。窗口内需要保证出现的字符种类数目等于 `i`。向右移动 `right`,扩大窗口时,记录窗口内各种类字符数量。当窗口内出现字符数量大于 `i` 时,则不断右移 `right`,保证窗口内出现字符种类等于 `i`。同时,记录窗口内出现次数小于 `k` 的字符数量,当窗口中出现次数小于 `k` 的字符数量为 `0` 时,就可以记录答案,并维护答案最大值了。 - -整个算法的具体步骤如下: - -- 使用 `ans` 记录满足要求的最长子串长度。 - -- 枚举最长子串中的字符种类数目 `i`,最小为 `1` 种,最大为 `26` 种。对于给定字符种类数目 `i`: - - 使用两个指针 `left`、`right` 指向滑动窗口的左右边界。 - - 使用 `window_count` 变量来统计窗口内字符种类数目,保证窗口中的字符种类数目 `window_count` 不多于 `i`。 - - 使用 `letter_map` 哈希表记录窗口中各个字符出现的数目。使用 `less_k_count` 记录窗口内出现次数小于 `k` 次的字符数量。 - - 向右移动 `right`,将最右侧字符 `s[right]` 加入当前窗口,用 `letter_map` 记录该字符个数。 - - 如果该字符第一次出现,即 `letter_map[s[right]] == 1`,则窗口内字符种类数目 + 1,即 `window_count += 1`。同时窗口内小于 `k` 次的字符数量 + 1(等到 `letter_map[s[right]] >= k` 时再减去),即 `less_k_count += 1`。 - - 如果该字符已经出现过 `k` 次,即 `letter_map[s[right]] == k`,则窗口内小于 `k` 次的字符数量 -1,即 `less_k_count -= 1`。 - - 当窗口内字符种类数目 `window_count` 大于给定字符种类数目 `i` 时,即 `window_count > i`,则不断右移 `left`,缩小滑动窗口长度,直到 `window_count == i`。 - - 如果此时窗口内字符种类数目 `window_count` 等于给定字符种类 `i` 并且小于 `k` 次的字符数量为 `0`,即 `window_count == i and less_k_count == 0` 时,维护更新答案为 `ans = max(right - left + 1, ans)`。 -- 最后输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def longestSubstring(self, s: str, k: int) -> int: - ans = 0 - for i in range(1, 27): - left, right = 0, 0 - window_count = 0 - less_k_count = 0 - letter_map = dict() - while right < len(s): - if s[right] in letter_map: - letter_map[s[right]] += 1 - else: - letter_map[s[right]] = 1 - - if letter_map[s[right]] == 1: - window_count += 1 - less_k_count += 1 - if letter_map[s[right]] == k: - less_k_count -= 1 - - while window_count > i: - letter_map[s[left]] -= 1 - if letter_map[s[left]] == 0: - window_count -= 1 - less_k_count -= 1 - if letter_map[s[left]] == k - 1: - less_k_count += 1 - left += 1 - - if window_count == i and less_k_count == 0: - ans = max(right - left + 1, ans) - right += 1 - return ans -``` - diff --git "a/Solutions/0399. \351\231\244\346\263\225\346\261\202\345\200\274.md" "b/Solutions/0399. \351\231\244\346\263\225\346\261\202\345\200\274.md" deleted file mode 100644 index af543ba3..00000000 --- "a/Solutions/0399. \351\231\244\346\263\225\346\261\202\345\200\274.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0399. 除法求值](https://leetcode.cn/problems/evaluate-division/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图、数组、最短路 -- 难度:中等 - -## 题目大意 - -给定一个变量对数组 `equations` 和一个实数数组 `values` 作为已知条件,其中 `equations[i] = [Ai, Bi]` 和 `values[i]` 共同表示 `Ai / Bi = values[i]`。每个 `Ai` 或 `Bi` 是一个表示单个变量的字符串。 - -再给定一个表示多个问题的数组 `queries`,其中 `queries[j] = [Cj, Dj]` 表示第 `j` 个问题,要求:根据已知条件找出 `Cj / Dj = ?` 的结果作为答案。返回所有问题的答案。如果某个答案无法确定,则用 `-1.0` 代替,如果问题中出现了给定的已知条件中没有出现的表示变量的字符串,则也用 `-1.0` 代替这个答案。 - -## 解题思路 - -在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」的基础上增加了倍数关系。在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」中我们处理传递关系使用了并查集,这道题也是一样,不过在使用并查集的同时还要维护倍数关系。 - -举例说明: - -- `a / b = 2.0`:说明 `a = 2b`,`a` 和 `b` 在同一个集合。 -- `b / c = 3.0`:说明 `b = 3c`,`b` 和 `c` 在同一个集合。 - -根据上述两式可得:`a`、`b`、`c` 都在一个集合中,且 `a = 2b = 6c`。 - -我们可以将同一集合中的变量倍数关系都转换为与根节点变量的倍数关系,比如上述例子中都转变为与 `a` 的倍数关系。 - -具体操作如下: - -- 定义并查集结构,并在并查集中定义一个表示倍数关系的 `multiples` 数组。 -- 遍历 `equations` 数组、`values` 数组,将每个变量按顺序编号,并使用 `union` 将其并入相同集合。 -- 遍历 `queries` 数组,判断两个变量是否在并查集中,并且是否在同一集合。如果找到对应关系,则将计算后的倍数关系存入答案数组,否则则将 `-1` 存入答案数组。 -- 最终输出答案数组。 - -并查集中维护倍数相关方法说明: - -- `find` 方法: - - 递推寻找根节点,并将倍数累乘,然后进行路径压缩,并且更新当前节点的倍数关系。 -- `union` 方法: - - 如果两个节点属于同一集合,则直接返回。 - - 如果两个节点不属于同一个集合,合并之前当前节点的倍数关系更新,然后再进行更新。 -- `is_connect` 方法: - - 如果两个节点不属于同一集合,返回 `-1`。 - - 如果两个节点属于同一集合,则返回倍数关系。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.multiples = [1 for _ in range(n)] - - def find(self, x): - multiple = 1.0 - origin = x - while x != self.parent[x]: - multiple *= self.multiples[x] - x = self.parent[x] - self.parent[origin] = x - self.multiples[origin] = multiple - return x - - def union(self, x, y, multiple): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - self.parent[root_x] = root_y - self.multiples[root_x] = multiple * self.multiples[y] / self.multiples[x] - return - - def is_connected(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x != root_y: - return -1.0 - - return self.multiples[x] / self.multiples[y] - -class Solution: - def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: - equations_size = len(equations) - hash_map = dict() - union_find = UnionFind(2 * equations_size) - - id = 0 - for i in range(equations_size): - equation = equations[i] - var1, var2 = equation[0], equation[1] - if var1 not in hash_map: - hash_map[var1] = id - id += 1 - if var2 not in hash_map: - hash_map[var2] = id - id += 1 - union_find.union(hash_map[var1], hash_map[var2], values[i]) - - queries_size = len(queries) - res = [] - for i in range(queries_size): - query = queries[i] - var1, var2 = query[0], query[1] - if var1 not in hash_map or var2 not in hash_map: - res.append(-1.0) - else: - id1 = hash_map[var1] - id2 = hash_map[var2] - res.append(union_find.is_connected(id1, id2)) - - return res -``` - diff --git "a/Solutions/0400. \347\254\254 N \344\275\215\346\225\260\345\255\227.md" "b/Solutions/0400. \347\254\254 N \344\275\215\346\225\260\345\255\227.md" deleted file mode 100644 index ef6c5f43..00000000 --- "a/Solutions/0400. \347\254\254 N \344\275\215\346\225\260\345\255\227.md" +++ /dev/null @@ -1,134 +0,0 @@ -# [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) - -- 标签:数学、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给你一个整数 $n$。 - -**要求**:在无限的整数序列 $[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]$ 中找出并返回第 $n$ 位上的数字。 - -**说明**: - -- $1 \le n \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 3 -输出:3 -``` - -- 示例 2: - -```python -输入:n = 11 -输出:0 -解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。 -``` - -## 解题思路 - -### 思路 1:找规律 - -数字以 $0123456789101112131415…$ 的格式序列化到一个字符序列中。在这个序列中,第 $5$ 位(从下标 $0$ 开始计数)是 $5$,第 $13$ 位是 $1$,第 $19$ 位是 $4$,等等。 - -根据题意中的字符串,找数学规律: - -- $1$ 位数字有 $9$ 个,共 $9$ 位:$123456789$。 -- $2$ 位数字有 $90$ 个,共 $2 \times 90$ 位:$10111213...9899$。 -- $3$ 位数字有 $900$ 个,共 $3 \times 900$ 位:$100...999$。 -- $4$ 位数字有 $9000$ 个,共 $4 \times 9000$ 位: $1000...9999$。 -- $……$ - -则我们可以按照以下步骤解决这道题: - -1. 我们可以先找到第 $n$ 位所在整数 $number$ 所对应的位数 $digit$。 -2. 同时找到该位数 $digit$ 的起始整数 $start$。 -3. 再计算出 $n$ 所在整数 $number$。$number$ 等于从起始数字 $start$ 开始的第 $\lfloor \frac{n - 1}{digit} \rfloor$ 个数字。即 `number = start + (n - 1) // digit`。 -4. 然后确定 $n$ 对应的是数字 $number$ 中的哪一位。即 $digit\underline{}idx = (n - 1) \mod digit$。 -5. 最后返回结果。 - -### 思路 1:代码 - -```python -class Solution: - def findNthDigit(self, n: int) -> int: - digit = 1 - start = 1 - base = 9 - while n > base: - n -= base - digit += 1 - start *= 10 - base = start * digit * 9 - - number = start + (n - 1) // digit - digit_idx = (n - 1) % digit - return int(str(number)[digit_idx]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:二分查找 - -假设第 $n$ 位数字所在的整数是 $digit$ 位数,我们可以定义一个方法 $totalDigits(x)$ 用于计算所有位数不超过 $x$ 的整数的所有位数和。 - -根据题意我们可知,所有位数不超过 $digit - 1$ 的整数的所有位数和一定小于 $n$,并且所有不超过 $digit$ 的整数的所有位数和一定大于等于 $n$。 - -因为所有位数不超过 $x$ 的整数的所有位数和 $totalDigits(x)$ 是关于 $x$ 单调递增的,所以我们可以使用二分查找的方式,确定第 $n$ 位数字所在的整数的位数 $digit$。 - -$n$ 的最大值为 $2^{31} - 1$,约为 $2 \times 10^9$。而 $9$ 位数字有 $9 \times 10^8$ 个,共 $9 \times 9 \times 10^8 = 8.1 \times 10^9 > 2 \times 10 ^ 9$,所以第 $n$ 位所在整数的位数 $digit$ 最多为 $9$ 位,最小为 $1$ 位。即 $digit$ 的取值范围为 $[1, 9]$。 - -我们使用二分查找算法得到 $digit$ 之后,还可以计算出不超过 $digit - 1$ 的整数的所有位数和 $pre\underline{}digits = totalDigits(digit - 1)$,则第 $n$ 位数字所在整数在所有 $digit$ 位数中的下标是 $idx = n - pre\underline{}digits - 1$。 - -得到下标 $idx$ 后,可以计算出 $n$ 所在整数 $number$。$number$ 等于从起始数字 $10^{digit - 1}$ 开始的第 $\lfloor \frac{idx}{digit} \rfloor$ 个数字。即 `number = 10 ** (digit - 1) + idx // digit`。 - -该整数 $number$ 中第 $idx \mod digit$ 即为第 $n$ 位上的数字,将其作为答案返回即可。 - -### 思路 2:代码 - -```python -class Solution: - def totalDigits(self, x): - digits = 0 - digit, cnt = 1, 9 - while digit <= x: - digits += digit * cnt - digit += 1 - cnt *= 10 - return digits - - def findNthDigit(self, n: int) -> int: - left, right = 1, 9 - while left < right: - mid = left + (right - left) // 2 - if self.totalDigits(mid) < n: - left = mid + 1 - else: - right = mid - - digit = left - pre_digits = self.totalDigits(digit - 1) - idx = n - pre_digits - 1 - number = 10 ** (digit - 1) + idx // digit - digit_idx = idx % digit - - return int(str(number)[digit_idx]) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$\log n \times \log \log n$,位数上限 $D$ 为 $\log n$,二分查找的时间复杂度为 $\log D$,每次执行的时间复杂度为 $D$,总的时间复杂度为 $D \times \log D = O(\log n \times \log \log n)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- 【题解】[400. 第 N 位数字 - 清晰易懂的找规律解法(击败100%, 几乎双百)](https://leetcode.cn/problems/nth-digit/solutions/1129463/geekplayers-leetcode-ac-qing-xi-yi-dong-uasjy/) -- 【题解】[400. 第 N 位数字 - 方法一:二分查找](https://leetcode.cn/problems/nth-digit/solutions/1128000/di-n-wei-shu-zi-by-leetcode-solution-mdl2/) diff --git "a/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" "b/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" deleted file mode 100644 index 0788463e..00000000 --- "a/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" +++ /dev/null @@ -1,105 +0,0 @@ -# [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:一只青蛙要过河,这条河被等分为若干个单元格,每一个单元格内可能放油一块石子(也可能没有)。青蛙只能跳到有石子的单元格内,不能跳到没有石子的单元格内。 - -现在给定一个严格按照升序排序的数组 `stones`,其中 `stones[i]` 代表第 `i` 块石子所在的单元格序号。默认第 `0` 块石子序号为 `0`(即 `stones[0] == 0`)。 - -开始时,青蛙默认站在序号为 `0` 石子上(即 `stones[0]`),并且假定它第 `1` 步只能跳跃 `1` 个单位(即只能从序号为 `0` 的单元格跳到序号为 `1` 的单元格)。 - -如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。 - -**要求**:判断青蛙能否成功过河(即能否在最后一步跳到最后一块石子上)。如果能,则返回 `True`;否则,则返回 `False`。 - -**说明**: - -- $2 \le stones.length \le 2000$。 -- $0 \le stones[i] \le 2^{31} - 1$。 -- $stones[0] == 0$。 -- $stones$ 按严格升序排列。 - -**示例**: - -- 示例 1: - -```python -输入:stones = [0,1,3,5,6,8,12,17] -输出:true -解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -题目中说:如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。则下一步的状态可以由 `3` 种状态转移而来。 - -- 上一步所在石子到下一步所在石头的距离为 `k - 1`。 -- 上一步所在石子到下一步所在石头的距离为 `k`。 -- 上一步所在石子到下一步所在石头的距离为 `k + 1`。 - -则我们可以通过石子块数,跳跃距离来进行阶段划分和定义状态,以及推导状态转移方程。 - -###### 1. 划分阶段 - -按照石子块数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][k]` 表示为:青蛙能否以长度为 `k` 的距离,到达第 `i` 块石子。 - -###### 3. 状态转移方程 - -1. 外层循环遍历每一块石子 `i`,对于每一块石子 `i`,使用内层循环遍历石子 `i` 之前所有的石子 `j`。 -2. 并计算出上一步所在石子 `j` 到当前所在石子 `i` 之间的距离为 `k`。 -3. 如果上一步所在石子 `j` 通过上上一步以长度为 `k - 1`、`k` 或者 `k + 1` 的距离到达石子 `j`,那么当前步所在石子也可以通过 `k` 的距离到达石子 `i`。即通过检查 `dp[j][k - 1]`、`dp[j][k]`、`dp[j][k + 1]` 中是否至少有一个为真,即可判断 `dp[i][k]` 是否为真。 - - 即:`dp[i][k] = dp[j][k - 1] or dp[j][k] or dp[j][k + 1] `。 - -###### 4. 初始条件 - -刚开始青蛙站在序号为 `0` 石子上(即 `stones[0]`),肯定能以长度为 `0` 的距离,到达第 `0` 块石子,即 `dp[0][0] = True`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][k]` 表示为:青蛙能否以长度为 `k` 的距离,到达第 `i` 块石子。则如果 `dp[size - 1][k]` 为真,则说明青蛙能成功过河(即能在最后一步跳到最后一块石子上);否则则说明青蛙不能成功过河。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def canCross(self, stones: List[int]) -> bool: - size = len(stones) - - stone_dict = dict() - for i in range(size): - stone_dict[stones[i]] = i - - dp = [[False for _ in range(size + 1)] for _ in range(size)] - dp[0][0] = True - - for i in range(1, size): - for j in range(i): - k = stones[i] - stones[j] - if k <= 0 or k > j + 1: - continue - - dp[i][k] = dp[j][k - 1] or dp[j][k] or dp[j][k + 1] - - if dp[size - 1][k]: - return True - - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 - -## 参考资料 - -- 【题解】[【403. 青蛙过河】理解理解动态规划与dfs - 青蛙过河 - 力扣](https://leetcode.cn/problems/frog-jump/solution/403-qing-wa-guo-he-li-jie-li-jie-dong-ta-oyt9/) \ No newline at end of file diff --git "a/Solutions/0404. \345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.md" "b/Solutions/0404. \345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.md" deleted file mode 100644 index f57f06fc..00000000 --- "a/Solutions/0404. \345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.md" +++ /dev/null @@ -1,30 +0,0 @@ -# [0404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树,计算所有左叶子之和。 - -## 解题思路 - -深度优先搜索递归遍历二叉树,若当前节点不为空,且左孩子节点不为空,且左孩子节点的左右孩子节点都为空,则该节点的左孩子节点为左叶子节点。将其值累加起来,即为答案。 - -## 代码 - -```python -class Solution: - def sumOfLeftLeaves(self, root: TreeNode) -> int: - self.ans = 0 - def dfs(node): - if not node: - return None - if node.left and not node.left.left and not node.left.right: - self.ans += node.left.val - dfs(node.left) - dfs(node.right) - dfs(root) - return self.ans -``` - diff --git "a/Solutions/0405. \346\225\260\345\255\227\350\275\254\346\215\242\344\270\272\345\215\201\345\205\255\350\277\233\345\210\266\346\225\260.md" "b/Solutions/0405. \346\225\260\345\255\227\350\275\254\346\215\242\344\270\272\345\215\201\345\205\255\350\277\233\345\210\266\346\225\260.md" deleted file mode 100644 index 1c53cfbc..00000000 --- "a/Solutions/0405. \346\225\260\345\255\227\350\275\254\346\215\242\344\270\272\345\215\201\345\205\255\350\277\233\345\210\266\346\225\260.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [0405. 数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) - -- 标签:位运算、数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 `num`。 - -要求:编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用「补码运算」方法。 - -注意: - -- 十六进制中所有字母(`a` ~ `f`)都必须是小写。 -- 十六进制字符串中不能包含多余的前导零。如果要转化的数为 `0`,那么以单个字符 `0` 来表示 -- 对于其他情况,十六进制字符串中的第一个字符将不会是 `0` 字符。 -- 给定的数确保在 `32` 位有符号整数范围内。 -- 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。 - -## 解题思路 - -主要是对不同情况的处理。 - -- 当 `num` 为 0 时,直接返回 `0`。 -- 当 `num` 为负数时,对负数进行「补码运算」,转换为对应的十进制正数(将其绝对值与 $2^{32} - 1$ 异或再加 1),然后执行和 `nums` 为正数一样的操作。 -- 当 `num` 为正数时,将其对 16 取余,并转为对应的十六进制字符,并按位拼接到字符串中,再将 `num` 除以 16,继续对 16 取余,直到 `num` 变为为 0。 -- 最后将拼接好的字符串逆序返回就是答案。 - -## 代码 - -```python -class Solution: - def toHex(self, num: int) -> str: - res = '' - if num == 0: - return '0' - - if num < 0: - num = (abs(num) ^ (2 ** 32 - 1)) + 1 - - while num: - digit = num % 16 - if digit >= 10: - digit = chr(ord('a') + digit - 10) - else: - digit = str(digit) - res += digit - num >>= 4 - return res[::-1] -``` - diff --git "a/Solutions/0406. \346\240\271\346\215\256\350\272\253\351\253\230\351\207\215\345\273\272\351\230\237\345\210\227.md" "b/Solutions/0406. \346\240\271\346\215\256\350\272\253\351\253\230\351\207\215\345\273\272\351\230\237\345\210\227.md" deleted file mode 100644 index f6fd40a1..00000000 --- "a/Solutions/0406. \346\240\271\346\215\256\350\272\253\351\253\230\351\207\215\345\273\272\351\230\237\345\210\227.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [0406. 根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) - -- 标签:贪心、树状数组、线段树、数组、排序 -- 难度:中等 - -## 题目大意 - -n 个人打乱顺序排成一排,给定一个数组 people 表示队列中人的属性(顺序是打乱的)。其中 $people[i] = [h_i, k_i]$ 表示第 i 个人的身高为 $h_i$,前面正好有 $k_i$ 个身高大于或等于 $h_i$ 的人。 - -现在重新构造并返回输入数组 people 所表示的队列 queue。其中 $queue[j] = [h_j, k_j]$ 是队列中第 j 个人的信息,表示为身高为 $h_j$,前面正好有 $k_j$ 个身高大于或等于 $h_j$​ 的人。 - -## 解题思路 - -这道题目有两个维度,身高 $h_j$ 和满足条件的数量 $k_j$。进行排序的时候如果同时考虑两个维度条件,就有点复杂了。我们可以考虑固定一个维度,先排好序,再考虑另一个维度的要求。 - -我们可以先确定身高维度。将数组按身高从高到低进行排序,身高相同的则按照 k 值升序排列。这样排序之后可以确定目前对于第 j 个人来说,前面的 j - 1 个人肯定比他都高。 - -然后建立一个包含 n 个位置的空队列 queue,按照上边排好的顺序遍历,依次将其插入到第 $k_j$​ 位置上。最后返回新的队列。 - -## 代码 - -```python -class Solution: - def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]: - queue = [] - people.sort(key = lambda x: (-x[0], x[1])) - for p in people: - queue.insert(p[1], p) - return queue -``` - diff --git "a/Solutions/0409. \346\234\200\351\225\277\345\233\236\346\226\207\344\270\262.md" "b/Solutions/0409. \346\234\200\351\225\277\345\233\236\346\226\207\344\270\262.md" deleted file mode 100644 index 3d40944d..00000000 --- "a/Solutions/0409. \346\234\200\351\225\277\345\233\236\346\226\207\344\270\262.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0409. 最长回文串](https://leetcode.cn/problems/longest-palindrome/) - -- 标签:贪心、哈希表、字符串 -- 难度:简单 - -## 题目大意 - -给定一个包含大写字母和小写字母的字符串 `s`。 - -要求:找到通过这些字母构造成的最长的回文串。 - -注意: - -- 在构造过程中,请注意区分大小写。比如 `Aa` 不能当做一个回文字符串。 -- 假设字符串的长度不会超过 `1010`。 - -## 解题思路 - -这道题目是通过给定字母构造回文串,并找到最长的回文串长度。那就要先看看回文串的特点。在回文串中,最多只有一个字母出现过奇数次,其余字符都出现过偶数次。且相同字母是中心对称的。 - -则我们可以用哈希表统计字符出现次数。对于每个字符,使用尽可能多的偶数次字符作为回文串的两侧,并记录下使用的字符个数,记录到答案中。再使用一个 `flag` 标记下是否有奇数次的字符,如果有的话,最终答案再加 1。最后输出答案。 - -## 代码 - -```python -class Solution: - def longestPalindrome(self, s: str) -> int: - word_dict = dict() - for ch in s: - if ch in word_dict: - word_dict[ch] += 1 - else: - word_dict[ch] = 1 - - ans = 0 - flag = False - for value in word_dict.values(): - ans += value // 2 * 2 - if value % 2 == 1: - flag = True - - if flag: - ans += 1 - - return ans -``` - diff --git "a/Solutions/0410. \345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/0410. \345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index eb339c0c..00000000 --- "a/Solutions/0410. \345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [0410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) - -- 标签:贪心、数组、二分查找、动态规划、前缀和 -- 难度:困难 - -## 题目大意 - -给定一个非负整数数组 nums 和一个整数 m,将数组分成 m 个非空的连续子数组,要求使 m 个子数组各自和的最大值最小,并求出子数组各自和的最大值。 - -## 解题思路 - -先来理解清楚题意。题目的目的是使得 m 个连续子数组各自和的最大值最小。意思是将数组按顺序分成 m 个子数组,然后计算每个子数组的和,然后找出 m 个和中的最大值,要求使这个最大值尽可能小。最后输出这个尽可能小的和最大值。 - -可以用二分查找来找这个子数组和的最大值,我们用 ans 来表示这个值。ans 最小为数组 nums 所有元素的最大值,最大为数组 nums 所有元素的和。即 ans 范围是 [max(nums), sum(nums)]。 - -所以就确定了二分查找的两个指针位置。left 指向 max(nums),right 指向 sum(nums)。然后取中间值 mid,计算当子数组和的最大值为 mid 时,所需要分割的子数组最少个数。 - -- 如果需要分割的子数组最少个数大于 m 个,则说明子数组和的最大值取小了,不满足条件,应该继续调大,将 left 右移,从右区间继续查找。 -- 如果需要分割的子数组最少个数小于或等于 m 个,则说明子数组和的最大值满足条件,并且还可以继续调小,将 right 左移,从左区间继续查找,看是否有更小的数组和满足条件。 -- 最终,返回符合条件的最小值即可。 - -## 代码 - -```python -class Solution: - def splitArray(self, nums: List[int], m: int) -> int: - def get_count(x): - total = 0 - count = 1 - for num in nums: - if total + num > x: - count += 1 - total = num - else: - total += num - return count - - left = max(nums) - right = sum(nums) - while left < right: - mid = left + (right - left) // 2 - if get_count(mid) > m: - left = mid + 1 - else: - right = mid - return left -``` - diff --git a/Solutions/0412. Fizz Buzz.md b/Solutions/0412. Fizz Buzz.md deleted file mode 100644 index 1ee3422c..00000000 --- a/Solutions/0412. Fizz Buzz.md +++ /dev/null @@ -1,37 +0,0 @@ -# [0412. Fizz Buzz](https://leetcode.cn/problems/fizz-buzz/) - -- 标签:数学、字符串、模拟 -- 难度:简单 - -## 题目大意 - -给定一个整数 n,按照规则,输出 1~n 的字符串表示。 - -规则: - -- 如果 i 是 3 的倍数,输出 "Fizz"; -- 如果 i 是 5 的倍数,输出 "Buzz"; -- 如果 i 是 3 和 5 的倍数,则输出 "FizzBuzz"。 - -## 解题思路 - -简单题,按照题目规则输出即可。 - -## 代码 - -```python -class Solution: - def fizzBuzz(self, n: int) -> List[str]: - ans = [] - for i in range(1,n+1): - if i % 15 == 0: - ans.append("FizzBuzz") - elif i % 3 == 0: - ans.append("Fizz") - elif i % 5 == 0: - ans.append("Buzz") - else: - ans.append(str(i)) - return ans -``` - diff --git "a/Solutions/0415. \345\255\227\347\254\246\344\270\262\347\233\270\345\212\240.md" "b/Solutions/0415. \345\255\227\347\254\246\344\270\262\347\233\270\345\212\240.md" deleted file mode 100644 index 6cf66ba9..00000000 --- "a/Solutions/0415. \345\255\227\347\254\246\344\270\262\347\233\270\345\212\240.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) - -- 标签:数学、字符串、模拟 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个字符串形式的非负整数 `num1` 和`num2`。 - -**要求**:计算它们的和,并同样以字符串形式返回。 - -**说明**: - -- $1 \le num1.length, num2.length \le 10^4$。 -- $num1$ 和 $num2$ 都只包含数字 $0 \sim 9$。 -- $num1$ 和 $num2$ 都不包含任何前导零。 -- 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 - -**示例**: - -- 示例 1: - -```python -输入:num1 = "11", num2 = "123" -输出:"134" -``` - -- 示例 2: - -```python -输入:num1 = "456", num2 = "77" -输出:"533" -``` - -## 解题思路 - -### 思路 1:双指针 - -需要用字符串的形式来模拟大数加法。 - -加法的计算方式是:从个位数开始,由低位到高位,按位相加,如果相加之后超过 `10`,就需要向前进位。 - -模拟加法的做法是: - -1. 用一个数组存储按位相加后的结果,每一位对应一位数。 -2. 然后分别使用一个指针变量,对两个数 `num1`、`num2` 字符串进行反向遍历,将相加后的各个位置上的结果保存在数组中,这样计算完成之后就得到了一个按位反向的结果。 -3. 最后返回结果的时候将数组反向转为字符串即可。 - -注意需要考虑 `num1`、`num2` 不等长的情况,让短的那个字符串对应位置按 $0$ 计算即可。 - -### 思路 1:代码 - -```python -class Solution: - def addStrings(self, num1: str, num2: str) -> str: - # num1 位数 - digit1 = len(num1) - 1 - # num2 位数 - digit2 = len(num2) - 1 - - # 进位 - carry = 0 - # sum 存储反向结果 - sum = [] - # 逆序相加 - while carry > 0 or digit1 >= 0 or digit2 >= 0: - # 获取对应位数上的数字 - num1_d = int(num1[digit1]) if digit1 >= 0 else 0 - num2_d = int(num2[digit2]) if digit2 >= 0 else 0 - digit1 -= 1 - digit2 -= 1 - # 计算结果,存储,进位 - num = num1_d+num2_d+carry - sum.append('%d'%(num%10)) - carry = num // 10 - # 返回计算结果 - return "".join(sum[::-1]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(max(m + n))$。其中 $m$ 是字符串 $num1$ 的长度,$n$ 是字符串 $num2$ 的长度。 -- **空间复杂度**:$O(max(m + n))$。 \ No newline at end of file diff --git "a/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" "b/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" deleted file mode 100644 index 8fdf6be7..00000000 --- "a/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含正整数的非空数组 $nums$。 - -**要求**:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 - -**说明**: - -- $1 \le nums.length \le 200$。 -- $1 \le nums[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,5,11,5] -输出:true -解释:数组可以分割成 [1, 5, 5] 和 [11]。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,3,5] -输出:false -解释:数组不能分割成两个元素和相等的子集。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题换一种说法就是:从数组中选择一些元素组成一个子集,使子集的元素和恰好等于整个数组元素和的一半。 - -这样的话,这道题就可以转变为「0-1 背包问题」。 - -1. 把整个数组中的元素和记为 $sum$,把元素和的一半 $target = \frac{sum}{2}$ 看做是「0-1 背包问题」中的背包容量。 -2. 把数组中的元素 $nums[i]$ 看做是「0-1 背包问题」中的物品。 -3. 第 $i$ 件物品的重量为 $nums[i]$,价值也为 $nums[i]$。 -4. 因为物品的重量和价值相等,如果能装满载重上限为 $target$ 的背包,那么得到的最大价值也应该是 $target$。 - -这样问题就转变为:给定一个数组 $nums$ 代表物品,数组元素和的一半 $target = \frac{sum}{2}$ 代表背包的载重上限。其中第 $i$ 件物品的重量为 $nums[i]$,价值为 $nums[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包装载重量上限的情况下,能否将背包装满从而得到最大价值? - -###### 1. 划分阶段 - -当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $w$ 的背包中,得到的元素和最大为多少。 - -###### 3. 状态转移方程 - -$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], \quad dp[w - nums[i - 1]] + nums[i - 1] \rbrace & w \ge nums[i - 1] \end{cases}$ - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[target]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $target = \frac{sum}{2}$ 的背包中,得到的元素和最大值。 - -所以最后判断一下 $dp[target]$ 是否等于 $target$。如果 $dp[target] == target$,则说明集合中的子集刚好能够凑成总和 $target$,此时返回 `True`;否则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - # 思路 2:动态规划 + 滚动数组优化 - def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): - size = len(weight) - dp = [0 for _ in range(W + 1)] - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 逆序枚举背包装载重量(避免状态值错误) - for w in range(W, weight[i - 1] - 1, -1): - # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 - dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) - - return dp[W] - - def canPartition(self, nums: List[int]) -> bool: - sum_nums = sum(nums) - if sum_nums & 1: - return False - - target = sum_nums // 2 - return self.zeroOnePackMethod2(nums, nums, target) == target -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 是整个数组元素和的一半。 -- **空间复杂度**:$O(target)$。 - diff --git "a/Solutions/0417. \345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" "b/Solutions/0417. \345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" deleted file mode 100644 index f678adaa..00000000 --- "a/Solutions/0417. \345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0417. 太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) - -- 标签:深度优先搜索、广度优先搜索、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 `m * n` 大小的二维非负整数矩阵 `heights` 来表示一片大陆上各个单元格的高度。`heights[i][j]` 表示第 `i` 行第 `j` 列所代表的陆地高度。这个二维矩阵所代表的陆地被太平洋和大西洋所包围着。左上角是「太平洋」,右下角是「大西洋」。规定水流只能按照上、下、左、右四个方向流动,且只能从高处流到低处,或者在同等高度上流动。 - -**要求**:找出代表陆地的二维矩阵中,水流既可以从该处流动到太平洋,又可以流动到大西洋的所有坐标。以二维数组 `res` 的形式返回,其中 `res[i] = [ri, ci]` 表示雨水从单元格 `(ri, ci)` 既可流向太平洋也可流向大西洋。 - -**说明**: - -- $m == heights.length$。 -- $n == heights[r].length$。 -- $1 \le m, n \le 200$。 -- $0 \le heights[r][c] \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/06/08/waterflow-grid.jpg) - -```python -输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] -输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]] -``` - -- 示例 2: - -```python -输入: heights = [[2,1],[1,2]] -输出: [[0,0],[0,1],[1,0],[1,1]] -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -雨水由高处流向低处,如果我们根据雨水的流向搜索,来判断是否能从某一位置流向太平洋和大西洋不太容易。我们可以换个思路。 - -1. 分别从太平洋和大西洋(就是矩形边缘)出发,逆流而上,找出水流逆流能达到的地方,可以用两个二维数组 `pacific`、`atlantic` 分别记录太平洋和大西洋能到达的位置。 -2. 然后再对二维数组进行一次遍历,找出两者交集的位置,就是雨水既可流向太平洋也可流向大西洋的位置,将其加入答案数组 `res` 中。 -3. 最后返回答案数组 `res`。 - -### 思路 1:代码 - -```python -class Solution: - def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: - rows, cols = len(heights), len(heights[0]) - pacific = [[False for _ in range(cols)] for _ in range(rows)] - atlantic = [[False for _ in range(cols)] for _ in range(rows)] - - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def dfs(i, j, visited): - visited[i][j] = True - for direct in directs: - new_i = i + direct[0] - new_j = j + direct[1] - if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: - continue - if heights[new_i][new_j] >= heights[i][j] and not visited[new_i][new_j]: - dfs(new_i, new_j, visited) - - for j in range(cols): - dfs(0, j, pacific) - dfs(rows - 1, j, atlantic) - - for i in range(rows): - dfs(i, 0, pacific) - dfs(i, cols - 1, atlantic) - - res = [] - for i in range(rows): - for j in range(cols): - if pacific[i][j] and atlantic[i][j]: - res.append([i, j]) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git "a/Solutions/0421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274.md" "b/Solutions/0421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274.md" deleted file mode 100644 index 03f6e7b8..00000000 --- "a/Solutions/0421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [0421. 数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) - -- 标签:位运算、字典树、数组、哈希表 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`。 - -要求:返回 `num[i] XOR nums[j]` 的最大运算结果。其中 `0 ≤ i ≤ j < n`。 - -## 解题思路 - -最直接的想法暴力求解。两层循环计算两两之间的异或结果,记录并更新最大异或结果。 - -更好的做法可以减少一重循环。首先,要取得异或结果的最大值,那么从二进制的高位到低位,尽可能的让每一位异或结果都为 `1`。 - -将数组中所有数字的二进制形式从高位到低位依次存入字典树中。然后是利用异或运算交换律:如果 `a ^ b = max` 成立,那么 `a ^ max = b` 与 `b ^ max = a` 均成立。这样当我们知道 `a` 和 `max` 时,可以通过交换律求出 `b`。`a` 是我们遍历的每一个数,`max` 是我们想要尝试的最大值,从 `111111...` 开始,从高位到低位依次填 `1`。 - -对于 `a` 和 `max`,如果我们所求的 `b` 也在字典树中,则表示 `max` 是可以通过 `a` 和 `b` 得到的,那么 `max` 就是所求最大的异或。如果 `b` 不在字典树中,则减小 `max` 值继续判断,或者继续查询下一个 `a`。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, num: int, max_bit: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for i in range(max_bit, -1, -1): - bit = num >> i & 1 - if bit not in cur.children: - cur.children[bit] = Trie() - cur = cur.children[bit] - cur.isEnd = True - - def search(self, num: int, max_bit: int) -> int: - """ - Returns if the word is in the trie. - """ - cur = self - res = 0 - for i in range(max_bit, -1, -1): - bit = num >> i & 1 - if 1 - bit not in cur.children: - res = res * 2 - cur = cur.children[bit] - else: - res = res * 2 + 1 - cur = cur.children[1 - bit] - return res - -class Solution: - def findMaximumXOR(self, nums: List[int]) -> int: - trie_tree = Trie() - max_bit = len(format(max(nums), 'b')) - 1 - ans = 0 - for num in nums: - trie_tree.insert(num, max_bit) - ans = max(ans, trie_tree.search(num, max_bit)) - - return ans -``` - - - diff --git "a/Solutions/0424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.md" "b/Solutions/0424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.md" deleted file mode 100644 index aba387dd..00000000 --- "a/Solutions/0424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个仅由大写英文字母组成的字符串 s,以及一个整数 k。可以将任意位置上的字符替换成另外的大写字母,最多可替换 k 次。再进行上述操作后,找到包含重复字母的最长子串长度。 - -## 解题思路 - -先来考虑暴力求法。枚举字符串 s 的所有子串,对于每一个子串: - -- 统计子串中出现次数最多的字符,替换除它以外的字符 k 次。 -- 维护最长子串的长度。 - -但是这种暴力求法中,枚举子串的时间复杂度为 $O(n^2)$,统计出现次数最多的字符和替换字符时间复杂度为 $0(n)$,且两者属于平行处理,总体下来的时间复杂度为 $O(n^3)$。这样做会超时。 - -下面采用滑动窗口来做。 - -- 使用 counts 数组来统计字母频数。使用 left、right 双指针分别指向滑动窗口的首尾位置,使用 max_count 来维护最长子串的长度。 -- 不断右移 right 指针,增加滑动窗口的长度。 -- 对于当前滑动窗口的子串,如果当前窗口的间距 > 当前出现最大次数的字符的次数 + k 时,意味着替换 k 次仍不能使当前窗口中的字符全变为相同字符,则此时应该将左边界右移,同时将原先左边界的字符频次减少。 - -## 代码 - -```python -class Solution: - def characterReplacement(self, s: str, k: int) -> int: - max_count = 0 - left, right = 0, 0 - counts = [0 for _ in range(26)] - while right < len(s): - num_right = ord(s[right]) - ord('A') - counts[num_right] += 1 - max_count = max(max_count, counts[num_right]) - right += 1 - if right - left > max_count + k: - num_left = ord(s[left]) - ord('A') - counts[num_left] -= 1 - left += 1 - - return right - left -``` - diff --git "a/Solutions/0425. \345\215\225\350\257\215\346\226\271\345\235\227.md" "b/Solutions/0425. \345\215\225\350\257\215\346\226\271\345\235\227.md" deleted file mode 100644 index 5628fa35..00000000 --- "a/Solutions/0425. \345\215\225\350\257\215\346\226\271\345\235\227.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [0425. 单词方块](https://leetcode.cn/problems/word-squares/) - -- 标签:字典树、数组、字符串、回溯 -- 难度:困难 - -## 题目大意 - -给定一个单词集合 `words`(没有重复)。 - -要求:找出其中所有的单词方块 。 - -- 单词方块:指从第 `k` 行和第 `k` 列 `(0 ≤ k < max(行数, 列数))` 来看都是相同的字符串。 - -例如,单词序列 ["ball","area","lead","lady"] 形成了一个单词方块,因为每个单词从水平方向看和从竖直方向看都是相同的。 - -``` -b a l l -a r e a -l e a d -l a d y -``` - -## 解题思路 - -根据单词方块的第一个单词,可以推出下一个单词的前缀。 - -比如第一个单词是 `ball`,那么单词方块的长度是 `4 * 4`,则下一个单词(第二个单词)的前缀为 `a`。这样我们就又找到了一个以 `a` 为前缀且长度为 `4` 的单词,即 `area`,此时就变成了 `[ball, area]`。 - -那么下一个单词(第三个单词)的前缀为 `le`。这样我们就又找到了一个以 `le` 为前缀且长度为 `4` 的单词,即 `lead`。此时就变成了 `[ball, area, lead]`。 - -以此类推,就可以得到整个单词方块。 - -并且我们可以使用字典树(前缀树)来存储单词,并且通过回溯得到所有的解。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str): - """ - Returns if the word is in the trie. - """ - cur = self - res = [] - for ch in word: - if ch not in cur.children: - return res - cur = cur.children[ch] - cur.dfs(word, res) - return res - - def dfs(self, word, res): - cur = self - if cur and cur.isEnd: - res.append(word) - return - for ch in cur.children: - node = cur.children[ch] - node.dfs(word + ch, res) - - -class Solution: - - def backtrace(self, index, size, path, res, trie_tree): - if index == size: - res.append(path[:]) - return - next_prefix = "" # 下一行的前缀 - for i in range(index): - next_prefix += path[i][index] - - next_words_with_prefix = trie_tree.search(next_prefix) - for word in next_words_with_prefix: - path.append(word) - self.backtrace(index + 1, size, path, res, trie_tree) - path.pop(-1) - - - def wordSquares(self, words: List[str]) -> List[List[str]]: - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - size = len(words[0]) - res = [] - path = [] - for word in words: - path.append(word) - self.backtrace(1, size, path, res, trie_tree) - path.pop(-1) - return res -``` - diff --git "a/Solutions/0426. \345\260\206\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\345\214\226\344\270\272\346\216\222\345\272\217\347\232\204\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/Solutions/0426. \345\260\206\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\345\214\226\344\270\272\346\216\222\345\272\217\347\232\204\345\217\214\345\220\221\351\223\276\350\241\250.md" deleted file mode 100644 index f51214a0..00000000 --- "a/Solutions/0426. \345\260\206\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\345\214\226\344\270\272\346\216\222\345\272\217\347\232\204\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0426. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) - -- 标签:栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:将这棵二叉树转换为一个已排序的双向循环链表。要求不能创建新的节点,只能调整树中节点指针的指向。 - -## 解题思路 - -通过中序递归遍历可以将二叉树升序排列输出。这道题需要在中序遍历的同时,将节点的左右指向进行改变。使用 `head`、`tail` 存放双向链表的头尾节点,然后从根节点开始,进行中序递归遍历。 - -具体做法如下: - -- 如果当前节点为空,直接返回。 -- 如果当前节点不为空: - - 递归遍历左子树。 - - 如果尾节点不为空,则将尾节点与当前节点进行连接。 - - 如果尾节点为空,则初始化头节点。 - - 将当前节点标记为尾节点。 - - 递归遍历右子树。 -- 最后将头节点和尾节点进行连接。 - -## 代码 - -```python -class Solution: - def treeToDoublyList(self, root: 'Node') -> 'Node': - def dfs(node: 'Node'): - if not node: - return - - dfs(node.left) - if self.tail: - self.tail.right = node - node.left = self.tail - else: - self.head = node - self.tail = node - dfs(node.right) - - if not root: - return None - - self.head, self.tail = None, None - dfs(root) - self.head.left = self.tail - self.tail.right = self.head - return self.head -``` - diff --git "a/Solutions/0428. \345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226 N \345\217\211\346\240\221.md" "b/Solutions/0428. \345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226 N \345\217\211\346\240\221.md" deleted file mode 100644 index ce8ae971..00000000 --- "a/Solutions/0428. \345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226 N \345\217\211\346\240\221.md" +++ /dev/null @@ -1,60 +0,0 @@ -# [0428. 序列化和反序列化 N 叉树](https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、字符串 -- 难度:困难 - -## 题目大意 - -要求:设计一个序列化和反序列化 N 叉树的算法。序列化 / 反序列化算法的算法实现没有限制。你只需要保证 N 叉树可以被序列化为一个字符串并且该字符串可以被反序列化成原树结构即可。 - -- 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构。 -- N 叉树是指每个节点都有不超过 N 个孩子节点的有根树。 - -## 解题思路 - -- 序列化:通过深度优先搜索的方式,递归遍历节点,以 `root.val`、`len(root.children)`、`root.children` 的顺序生成序列化结果,并用 `-` 链接,返回结果字符串。 -- 反序列化:先将字符串按 `-` 分割成数组。然后按照 `root.val`、`len(root.children)`、`root.children` 的顺序解码,并建立对应节点。最后返回根节点。 - - - -## 代码 - -```python -class Codec: - def serialize(self, root: 'Node') -> str: - """Encodes a tree to a single string. - - :type root: Node - :rtype: str - """ - if not root: - return 'None' - - data = str(root.val) + '-' + str(len(root.children)) - for child in root.children: - data += '-' + self.serialize(child) - return data - - - def deserialize(self, data: str) -> 'Node': - """Decodes your encoded data to tree. - - :type data: str - :rtype: Node - """ - datalist = data.split('-') - return self.dfs(datalist) - - def dfs(self, datalist): - val = datalist.pop(0) - if val == 'None': - return None - root = Node(int(val)) - root.children = [] - - size = int(datalist.pop(0)) - for _ in range(size): - root.children.append(self.dfs(datalist)) - return root -``` - diff --git "a/Solutions/0429. N \345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0429. N \345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index 485de8d7..00000000 --- "a/Solutions/0429. N \345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0429. N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) - -- 标签:树、广度优先搜索 -- 难度:中等 - -## 题目大意 - -给定一个 N 叉树的根节点 `root`。 - -要求:返回其节点值的层序遍历(即从左到右,逐层遍历)。 - -树的序列化输入是用层序遍历,每组子节点都由 null 值分隔。 - -## 解题思路 - -和二叉树的层序遍历类似。广度优先搜索每次取出第 `i` 层上所有元素。具体步骤如下: - -- 根节点入队。 -- 当队列不为空时,求出当前队列长度 $size$。 - - 依次从队列中取出这 $size$ 个元素,并将元素值存入当前层级列表 `level` 中。 - - 将该层所有节点的所有孩子节点入队,遍历完之后将这层节点数组加入答案数组中,然后继续迭代。 -- 当队列为空时,结束。 - -## 代码 - -```python -class Solution: - def levelOrder(self, root: 'Node') -> List[List[int]]: - ans = [] - if not root: - return ans - - queue = [root] - - while queue: - level = [] - size = len(queue) - for _ in range(size): - cur = queue.pop(0) - level.append(cur.val) - for child in cur.children: - queue.append(child) - ans.append(level) - - return ans -``` - diff --git "a/Solutions/0430. \346\211\201\345\271\263\345\214\226\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/Solutions/0430. \346\211\201\345\271\263\345\214\226\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" deleted file mode 100644 index b472f8b1..00000000 --- "a/Solutions/0430. \346\211\201\345\271\263\345\214\226\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ /dev/null @@ -1,60 +0,0 @@ -# [0430. 扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) - -- 标签:深度优先搜索、链表、双向链表 -- 难度:中等 - -## 题目大意 - -给定一个带子链表指针 child 的双向链表,将 child 的子链表进行扁平化处理,使所有节点出现在单级双向链表中。 - -扁平化处理如下: - -``` -原链表: -1---2---3---4---5---6--NULL - | - 7---8---9---10--NULL - | - 11--12--NULL -扁平化之后: -1---2---3---7---8---11---12---9---10---4---5---6--NULL -``` - - - -## 解题思路 - -递归处理多层链表的扁平化。遍历链表,找到 child 非空的节点, 将其子链表链接到当前节点的 next 位置(自身扁平化处理)。然后继续向后遍历,不断找到 child 节点,并进行链接。直到处理到尾部位置。 - -## 代码 - -```python -class Solution: - def dfs(self, node: 'Node'): - # 找到链表的尾节点或 child 链表不为空的节点 - while node.next and not node.child: - node = node.next - tail = None - if node.child: - # 如果 child 链表不为空,将 child 链表扁平化 - tail = self.dfs(node.child) - - # 将扁平化的 child 链表链接在该节点之后 - temp = node.next - node.next = node.child - node.next.prev = node - node.child = None - tail.next = temp - if temp: - temp.prev = tail - # 链接之后,从 child 链表的尾节点继续向后处理链表 - return self.dfs(tail) - # child 链表为空,则该节点是尾节点,直接返回 - return node - def flatten(self, head: 'Node') -> 'Node': - if not head: - return head - self.dfs(head) - return head -``` - diff --git "a/Solutions/0435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" "b/Solutions/0435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" deleted file mode 100644 index 7853fba7..00000000 --- "a/Solutions/0435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0435. 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) - -- 标签:贪心、数组、动态规划、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个区间的集合 `intervals`,其中 `intervals[i] = [starti, endi]`。从集合中移除部分区间,使得剩下的区间互不重叠。 - -**要求**:返回需要移除区间的最小数量。 - -**说明**: - -- $1 \le intervals.length \le 10^5$。 -- $intervals[i].length == 2$。 -- $-5 * 10^4 \le starti < endi \le 5 * 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:intervals = [[1,2],[2,3],[3,4],[1,3]] -输出:1 -解释:移除 [1,3] 后,剩下的区间没有重叠。 -``` - -- 示例 2: - -```python -输入: intervals = [ [1,2], [1,2], [1,2] ] -输出: 2 -解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -这道题我们可以转换一下思路。原题要求保证移除区间最少,使得剩下的区间互不重叠。换个角度就是:「如何使得剩下互不重叠区间的数目最多」。那么答案就变为了:「总区间个数 - 不重叠区间的最多个数」。我们的问题也变成了求所有区间中不重叠区间的最多个数。 - -从贪心算法的角度来考虑,我们应该将区间按照结束时间排序。每次选择结束时间最早的区间,然后再在剩下的时间内选出最多的区间。 - -我们用贪心三部曲来解决这道题。 - -1. **转换问题**:将原问题转变为,当选择结束时间最早的区间之后,再在剩下的时间内选出最多的区间(子问题)。 -2. **贪心选择性质**:每次选择时,选择结束时间最早的区间。这样选出来的区间一定是原问题最优解的区间之一。 -3. **最优子结构性质**:在上面的贪心策略下,贪心选择当前时间最早的区间 + 剩下的时间内选出最多区间的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使所有区间中不重叠区间的个数最多。 - -使用贪心算法的代码解决步骤描述如下: - -1. 将区间集合按照结束坐标升序排列,然后维护两个变量,一个是当前不重叠区间的结束时间 `end_pos`,另一个是不重叠区间的个数 `count`。初始情况下,结束坐标 `end_pos` 为第一个区间的结束坐标,`count` 为 `1`。 -2. 依次遍历每段区间。对于每段区间:`intervals[i]`: - 1. 如果 `end_pos <= intervals[i][0]`,即 `end_pos` 小于等于区间起始位置,则说明出现了不重叠区间,令不重叠区间数 `count` 加 `1`,`end_pos` 更新为新区间的结束位置。 -3. 最终返回「总区间个数 - 不重叠区间的最多个数」即 `len(intervals) - count` 作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: - if not intervals: - return 0 - intervals.sort(key=lambda x: x[1]) - end_pos = intervals[0][1] - count = 1 - for i in range(1, len(intervals)): - if end_pos <= intervals[i][0]: - count += 1 - end_pos = intervals[i][1] - - return len(intervals) - count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是区间的数量。 -- **空间复杂度**:$O(\log n)$。 diff --git "a/Solutions/0437. \350\267\257\345\276\204\346\200\273\345\222\214 III.md" "b/Solutions/0437. \350\267\257\345\276\204\346\200\273\345\222\214 III.md" deleted file mode 100644 index b8be8681..00000000 --- "a/Solutions/0437. \350\267\257\345\276\204\346\200\273\345\222\214 III.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [0437. 路径总和 III](https://leetcode.cn/problems/path-sum-iii/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`,和一个整数 `sum`。 - -要求:求出该二叉树里节点值之和等于 `sum` 的路径的数目。 - -- 路径:不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 - -## 解题思路 - -直观想法是: - -以每一个节点 `node` 为起始节点,向下检测延伸的路径。递归遍历每一个节点所有可能的路径,然后将这些路径数目加起来即为答案。 - -但是这样会存在许多重复计算。我们可以定义节点的前缀和来减少重复计算。 - -- 节点的前缀和:从根节点到当前节点路径上所有节点的和。 - -有了节点的前缀和,我们就可以通过前缀和来计算两节点之间的路劲和。即:`则两节点之间的路径和 = 两节点之间的前缀和之差`。 - -为了计算符合要求的路径数量,我们用哈希表存储「前缀和的节点数量」。哈希表以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值。这样就能通过哈希表直接计算出符合要求的路径数量,从而累加到答案上。 - -整个算法的具体步骤如下: - -- 通过先序遍历方式递归遍历二叉树,计算每一个节点的前缀和 `cur_sum`。 -- 从哈希表中取出 `cur_sum - sum` 的路径数量(也就是表示存在从前缀和为 `cur_sum - sum` 所对应的节点到前缀和为 `cur_sum` 所对应的节点的路径个数)累加到答案 `res` 中。 -- 然后以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值,存入哈希表中。 -- 递归遍历二叉树,并累加答案值。 -- 恢复哈希表「当前前缀和的节点数量」,返回答案。 - -## 代码 - -```python -class Solution: - prefixsum_count = dict() - - def dfs(self, root, prefixsum_count, target_sum, cur_sum): - if not root: - return 0 - res = 0 - cur_sum += root.val - res += prefixsum_count.get(cur_sum - target_sum, 0) - prefixsum_count[cur_sum] = prefixsum_count.get(cur_sum, 0) + 1 - - res += self.dfs(root.left, prefixsum_count, target_sum, cur_sum) - res += self.dfs(root.right, prefixsum_count, target_sum, cur_sum) - - prefixsum_count[cur_sum] -= 1 - return res - - def pathSum(self, root: TreeNode, sum: int) -> int: - if not root: - return 0 - prefixsum_count = dict() - prefixsum_count[0] = 1 - return self.dfs(root, prefixsum_count, sum, 0) -``` - diff --git "a/Solutions/0438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" "b/Solutions/0438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" deleted file mode 100644 index cc7a38f1..00000000 --- "a/Solutions/0438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [0438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定两个字符串 `s` 和 `p`。 - -要求:找到 `s` 中所有 `p` 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 - -- 异位词:指由相同字母重排列形成的字符串(包括相同的字符串)。 - -## 解题思路 - -维护一个固定长度为 `len(p)` 的滑动窗口。于是问题的难点变为了如何判断 `s` 的子串和 `p` 是异位词。可以使用两个字典来分别存储 `s` 的子串中各个字符个数和 `p` 中各个字符个数。如果两个字典对应的键值全相等,则说明 `s` 的子串和 `p` 是异位词。但是这样每一次比较的操作时间复杂度是 $O(n)$,我们可以通过在滑动数组中逐字符比较的方式来减少两个字典之间相互比较的复杂度,并用 `valid` 记录经过验证的字符个数。整个算法步骤如下: - -- 使用哈希表 `need` 记录 `p` 中各个字符出现次数。使用字典 `window` 记录 `s` 的子串中各个字符出现的次数。使用数组 `res` 记录答案。使用 `valid` 记录 `s` 的子串中经过验证的字符个数。使用 `window_size` 表示窗口大小,值为 `len(p)`。使用两个指针 `left`、`right`。分别指向滑动窗口的左右边界。 -- 一开始,`left`、`right` 都指向 `0`。 -- 如果 `s[right]` 出现在 `need` 中,将最右侧字符 `s[right]` 加入当前窗口 `window` 中,记录该字符个数。并验证该字符是否和 `need` 中个对应字符个数相等。如果相等则验证的字符个数 +1,即 `valid += 1`。 -- 如果该窗口字符长度大于等于 `window_size` 个,即 `right - left + 1 >= window_size`。则不断右移 `left`,缩小滑动窗口长度。 - - 如果验证字符个数 `valid` 等于窗口长度 `window_size`,则 `s[left, right + 1]` 为 `p` 的异位词,所以将 `left` 加入到答案数组中。 - - 如果`s[left]` 在 `need` 中,则更新窗口中对应字符的个数,同时维护 `valid` 值。 -- 右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出答案数组 `res`。 - -## 代码 - -```python -class Solution: - def findAnagrams(self, s: str, p: str) -> List[int]: - need = collections.defaultdict(int) - for ch in p: - need[ch] += 1 - - window = collections.defaultdict(int) - window_size = len(p) - res = [] - left, right = 0, 0 - valid = 0 - while right < len(s): - if s[right] in need: - window[s[right]] += 1 - if window[s[right]] == need[s[right]]: - valid += 1 - - if right - left + 1 >= window_size: - if valid == len(need): - res.append(left) - if s[left] in need: - if window[s[left]] == need[s[left]]: - valid -= 1 - window[s[left]] -= 1 - left += 1 - right += 1 - return res -``` - diff --git "a/Solutions/0443. \345\216\213\347\274\251\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0443. \345\216\213\347\274\251\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 61681dde..00000000 --- "a/Solutions/0443. \345\216\213\347\274\251\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0443. 压缩字符串](https://leetcode.cn/problems/string-compression/) - -- 标签:双指针、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符数组 `chars`。请使用下述算法压缩: - -从一个空字符串 `s` 开始。对于 `chars` 中的每组连续重复字符: - -- 如果这一组长度为 `1`,则将字符追加到 `s` 中。 -- 如果这一组长度超过 `1`,则需要向 `s` 追加字符,后跟这一组的长度。 - -压缩后得到的字符串 `s` 不应该直接返回 ,需要转储到字符数组 `chars` 中。需要注意的是,如果组长度为 `10` 或 `10` 以上,则在 `chars` 数组中会被拆分为多个字符。 - -**要求**:在修改完输入数组后,返回该数组的新长度。 - -**说明**: - -- $1 \le chars.length \le 2000$。 -- `chars[i]` 可以是小写英文字母、大写英文字母、数字或符号。 -- 必须设计并实现一个只使用常量额外空间的算法来解决此问题。 - -**示例**: - -- 示例 1: - -```python -输入:chars = ["a","a","b","b","c","c","c"] -输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"] -解释:"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。 -``` - -## 解题思路 - -### 思路 1:快慢指针 - -题目要求原地修改字符串数组。我们可以使用快慢指针来解决原地修改问题,具体解决方法如下: - -- 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向压缩后的当前字符位置,`fast` 指向压缩前的当前字符位置。 -- 记录下当前待压缩字符的起始位置 `fast_start = start`,然后过滤掉连续相同的字符。 -- 将待压缩字符的起始位置的字符存入压缩后的当前字符位置,即 `chars[slow] = chars[fast_start]`,并向右移动压缩后的当前字符位置,即 `slow += 1`。 -- 判断一下待压缩字符的数目是否大于 `1`: - - 如果数量为 `1`,则不用记录该数量。 - - 如果数量大于 `1`(即 `fast - fast_start > 0`),则我们需要将对应数量存入压缩后的当前字符位置。这时候还需要判断一下数量是否大于等于 `10`。 - - 如果数量大于等于 `10`,则需要先将数字从个位到高位转为字符,存入压缩后的当前字符位置(此时数字为反,比如原数字是 `321`,则此时存入后为 `123`)。因为数字为反,所以我们需要将对应位置上的子字符串进行反转。 - - 如果数量小于 `10`,则直接将数字存入压缩后的当前字符位置,无需取反。 -- 判断完之后向右移动压缩前的当前字符位置 `fast`,然后继续压缩字符串,直到全部压缩完,则返回压缩后的当前字符位置 `slow` 即为答案。 - -### 思路 1:快慢指针代码 - -```python -class Solution: - - def compress(self, chars: List[str]) -> int: - def reverse(left, right): - while left < right: - chars[left], chars[right] = chars[right], chars[left] - left += 1 - right -= 1 - - slow, fast = 0, 0 - while fast < len(chars): - fast_start = fast - while fast + 1 < len(chars) and chars[fast + 1] == chars[fast]: - fast += 1 - - chars[slow] = chars[fast_start] - slow += 1 - - if fast - fast_start > 0: - cnt = fast - fast_start + 1 - slow_start = slow - while cnt != 0: - chars[slow] = str(cnt % 10) - slow += 1 - cnt = cnt // 10 - reverse(slow_start, slow - 1) - - fast += 1 - return slow -``` diff --git "a/Solutions/0445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" "b/Solutions/0445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" deleted file mode 100644 index b821e36a..00000000 --- "a/Solutions/0445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) - -- 标签:栈、链表、数学 -- 难度:中等 - -## 题目大意 - -给定两个非空链表的头节点 `l1` 和 `l2` 来代表两个非负整数。数字最高位位于链表开始位置。每个节点只储存一位数字。除了数字 `0` 之外,这两个链表代表的数字都不会以 `0` 开头。 - -要求:将这两个数相加会返回一个新的链表。 - -## 解题思路 - -链表中最高位位于链表开始位置,最低位位于链表结束位置。这与我们做加法的数位顺序是相反的。为了将链表逆序,从而从低位开始处理数位,我们可以借用两个栈:将链表中所有数字分别压入两个栈中,再依次取出相加。 - -同时,在相加的时候,还要考虑进位问题。具体步骤如下: - -- 将链表 `l1` 中所有节点值压入 `stack1` 栈中,再将链表 `l2` 中所有节点值压入 `stack2` 栈中。 -- 使用 `res` 存储新的结果链表,一开始指向 `None`,`carry` 记录进位。 -- 如果 `stack1` 或 `stack2` 不为空,或着进位 `carry` 不为 `0`,则: - - 从 `stack1` 中取出栈顶元素 `num1`,如果 `stack1` 为空,则 `num1 = 0`。 - - 从 `stack2` 中取出栈顶元素 `num2`,如果 `stack2` 为空,则 `num2 = 0`。 - - 计算相加结果,并计算进位。 - - 建立新节点,存储进位后余下的值,并令其指向 `res`。 - - `res` 指向新节点,继续判断。 -- 如果 `stack1`、`stack2` 都为空,并且进位 `carry` 为 `0`,则输出 `res`。 - -## 代码 - -```python -class Solution: - def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: - stack1, stack2 = [], [] - while l1: - stack1.append(l1.val) - l1 = l1.next - while l2: - stack2.append(l2.val) - l2 = l2.next - - res = None - carry = 0 - while stack1 or stack2 or carry != 0: - num1 = stack1.pop() if stack1 else 0 - num2 = stack2.pop() if stack2 else 0 - cur_sum = num1 + num2 + carry - carry = cur_sum // 10 - cur_sum %= 10 - cur_node = ListNode(cur_sum) - cur_node.next = res - res = cur_node - return res -``` - diff --git "a/Solutions/0447. \345\233\236\346\227\213\351\225\226\347\232\204\346\225\260\351\207\217.md" "b/Solutions/0447. \345\233\236\346\227\213\351\225\226\347\232\204\346\225\260\351\207\217.md" deleted file mode 100644 index 54b1419a..00000000 --- "a/Solutions/0447. \345\233\236\346\227\213\351\225\226\347\232\204\346\225\260\351\207\217.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [0447. 回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) - -- 标签:数组、哈希表、数学 -- 难度:中等 - -## 题目大意 - -给定平面上点坐标的数组 points,其中 $points[i] = [x_i, y_i]$。判断 points 中是否存在三个点 i,j,k,满足 i 和 j 之间的距离等于 i 和 k 之间的距离,即 $dist[i, j] = dist[i, k]$。找出满足上述关系的答案数量。 - -## 解题思路 - -使用哈希表记录每两个点之间的距离。然后使用两重循环遍历坐标数组,对于每两个点 i、点 j,计算两个点之间的距离,并将距离存进哈希表中。再从哈希表中选取距离相同的关系中依次选出两个,作为三个点之间的距离关系 $dist[i, j] =dist[i, k]$,因为还需考虑顺序,所以共有 $value * (value-1)$ 种情况。累加到答案中。 - -## 代码 - -```python -class Solution: - def numberOfBoomerangs(self, points: List[List[int]]) -> int: - ans = 0 - for point_i in points: - dis_dict = dict() - for point_j in points: - if point_i != point_j: - dx = point_i[0] - point_j[0] - dy = point_i[1] - point_j[1] - dis = dx * dx + dy * dy - if dis in dis_dict: - dis_dict[dis] += 1 - else: - dis_dict[dis] = 1 - for value in dis_dict.values(): - ans += value*(value-1) - return ans -``` - diff --git "a/Solutions/0450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" "b/Solutions/0450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" deleted file mode 100644 index e167afc9..00000000 --- "a/Solutions/0450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) - -- 标签:树、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉搜索树的根节点 `root`,以及一个值 `key`。 - -**要求**:从二叉搜索树中删除 key 对应的节点。并保证删除后的树仍是二叉搜索树。要求算法时间复杂度为 $0(h)$,$h$ 为树的高度。最后返回二叉搜索树的根节点。 - -**说明**: - -- 节点数的范围 $[0, 10^4]$。 -- $-10^5 \le Node.val \le 10^5$。 -- 节点值唯一。 -- `root` 是合法的二叉搜索树。 -- $-10^5 \le key \le 10^5$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2020/09/04/del_node_1.jpg) - -```python -输入:root = [5,3,6,2,4,null,7], key = 3 -输出:[5,4,6,2,null,null,7] -解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 -一个正确的答案是 [5,4,6,2,null,null,7], 如上图所示。 -另一个正确答案是 [5,2,6,null,4,null,7]。 -``` - -- 示例 2: - -```python -输入: root = [5,3,6,2,4,null,7], key = 0 -输出: [5,3,6,2,4,null,7] -解释: 二叉树不包含值为 0 的节点 -``` - -## 解题思路 - -### 思路 1:递归 - -删除分两个步骤:查找和删除。查找通过递归查找,删除的话需要考虑情况。 - -1. 从根节点 `root` 开始,递归遍历搜索二叉树。 - 1. 如果当前节点节点为空,返回当前节点。 - 2. 如果当前节点值大于 `key`,则去左子树中搜索并删除,此时 `root.left` 也要跟着递归更新,递归完成后返回当前节点。 - 3. 如果当前节点值小于 `key`,则去右子树中搜索并删除,此时 `root.right` 也要跟着递归更新,递归完成后返回当前节点。 - 4. 如果当前节点值等于 `key`,则该节点就是待删除节点。 - 1. 如果当前节点的左子树为空,则删除该节点之后,则右子树代替当前节点位置,返回右子树。 - 2. 如果当前节点的右子树为空,则删除该节点之后,则左子树代替当前节点位置,返回左子树。 - 3. 如果当前节点的左右子树都有,则将左子树转移到右子树最左侧的叶子节点位置上,然后右子树代替当前节点位置。返回右子树。 - -### 思路 1:代码 - -```python -class Solution: - def deleteNode(self, root: TreeNode, key: int) -> TreeNode: - if not root: - return root - - if root.val > key: - root.left = self.deleteNode(root.left, key) - return root - elif root.val < key: - root.right = self.deleteNode(root.right, key) - return root - else: - if not root.left: - return root.right - elif not root.right: - return root.left - else: - curr = root.right - while curr.left: - curr = curr.left - curr.left = root.left - return root.right -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0451. \346\240\271\346\215\256\345\255\227\347\254\246\345\207\272\347\216\260\351\242\221\347\216\207\346\216\222\345\272\217.md" "b/Solutions/0451. \346\240\271\346\215\256\345\255\227\347\254\246\345\207\272\347\216\260\351\242\221\347\216\207\346\216\222\345\272\217.md" deleted file mode 100644 index 26359de3..00000000 --- "a/Solutions/0451. \346\240\271\346\215\256\345\255\227\347\254\246\345\207\272\347\216\260\351\242\221\347\216\207\346\216\222\345\272\217.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) - -- 标签:哈希表、字符串、桶排序、计数、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:将字符串 `s` 里的字符按照出现的频率降序排列。如果有多个答案,返回其中任何一个。 - -**说明**: - -- $1 \le s.length \le 5 * 10^5$。 -- `s` 由大小写英文字母和数字组成。 - -**示例**: - -- 示例 1: - -```python -输入: s = "tree" -输出: "eert" -解释: 'e'出现两次,'r'和't'都只出现一次。 -因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。 -``` - -- 示例 2: - -```python -输入: s = "cccaaa" -输出: "cccaaa" -解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。 -注意"cacaca"是不正确的,因为相同的字母必须放在一起。 -``` - -## 解题思路 - -### 思路 1:优先队列 - -1. 使用哈希表 `s_dict` 统计字符频率。 -2. 然后遍历哈希表 `s_dict`,将字符以及字符频数存入优先队列中。 -3. 将优先队列中频数最高的元素依次加入答案数组中。 -4. 最后拼接答案数组为字符串,将其返回。 - -### 思路 1:代码 - -```python -import heapq - -class Solution: - def frequencySort(self, s: str) -> str: - # 统计元素频数 - s_dict = dict() - for ch in s: - if ch in s_dict: - s_dict[ch] += 1 - else: - s_dict[ch] = 1 - - priority_queue = [] - for ch in s_dict: - heapq.heappush(priority_queue, (-s_dict[ch], ch)) - - res = [] - while priority_queue: - ch = heapq.heappop(priority_queue)[-1] - times = s_dict[ch] - while times: - res.append(ch) - times -= 1 - return ''.join(res) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + k \times log_2k)$。其中 $n$ 为字符串 $s$ 的长度,$k$ 是字符串中不同字符的个数。 -- **空间复杂度**:$O(n + k)$。 - diff --git "a/Solutions/0452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" "b/Solutions/0452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" deleted file mode 100644 index 0defaeed..00000000 --- "a/Solutions/0452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" +++ /dev/null @@ -1,120 +0,0 @@ -# [0452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) - -- 标签:贪心、数组、排序 -- 难度:中等 - -## 题目大意 - -**描述**:在一个坐标系中有许多球形的气球。对于每个气球,给定气球在 x 轴上的开始坐标和结束坐标 $(x_{start}, x_{end})$。 - -同时,在 $x$ 轴的任意位置都能垂直发出弓箭,假设弓箭发出的坐标就是 x。那么如果有气球满足 $x_{start} \le x \le x_{end}$,则该气球就会被引爆,且弓箭可以无限前进,可以将满足上述要求的气球全部引爆。 - -现在给定一个数组 `points`,其中 $points[i] = [x_{start}, x_{end}]$ 代表每个气球的开始坐标和结束坐标。 - -**要求**:返回能引爆所有气球的最小弓箭数。 - -**说明**: - -- $1 \le points.length \le 10^5$。 -- $points[i].length == 2$。 -- $-2^{31} \le x_{start} < x_{end} \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:points = [[10,16],[2,8],[1,6],[7,12]] -输出:2 -解释:气球可以用 2 支箭来爆破: -- 在x = 6 处射出箭,击破气球 [2,8] 和 [1,6]。 -- 在x = 11 处发射箭,击破气球 [10,16] 和 [7,12]。 -``` - -- 示例 2: - -```python -输入:points = [[1,2],[3,4],[5,6],[7,8]] -输出:4 -解释:每个气球需要射出一支箭,总共需要 4 支箭。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -弓箭的起始位置和结束位置可以看做是一段区间,直观上来看,为了使用最少的弓箭数,可以尽量射中区间重叠最多的地方。 - -所以问题变为了:**如何寻找区间重叠最多的地方,也就是区间交集最多的地方。** - -我们将 `points` 按结束坐标升序排序(为什么按照结束坐标排序后边说)。 - -然后维护两个变量:一个是当前弓箭的坐标 `arrow_pos`、另一个是弓箭的数目 `count`。 - -为了尽可能的穿过更多的区间,所以每一支弓箭都应该尽可能的从区间的结束位置穿过,这样才能覆盖更多的区间。 - -初始情况下,第一支弓箭的坐标为第一个区间的结束位置,然后弓箭数为 $1$。然后依次遍历每段区间。 - -如果遇到弓箭坐标小于区间起始位置的情况,说明该弓箭不能引爆该区间对应的气球,需要用新的弓箭来射,所以弓箭数加 $1$,弓箭坐标也需要更新为新区间的结束位置。 - -最终返回弓箭数目。 - -再来看为什么将 `points` 按结束坐标升序排序而不是按照开始坐标升序排序? - -其实也可以,但是按开始坐标排序不如按结束坐标排序简单。 - -按开始坐标升序排序需要考虑一种情况:有交集关系的区间中,有的区间结束位置比较早。比如 `[0, 6]、[1, 2] [4, 5]`,按照开始坐标升序排序的话,就像下图一样: - -``` -[0..................6] - [1..2] - [4..5] -``` - -第一箭的位置需要进行迭代判断,取区间 `[0, 6]、[1, 2]` 中结束位置最小的位置,即 `arrow_pos = min(points[i][1], arrow_pos)`,然后再判断接下来的区间是否能够引爆。 - -而按照结束坐标排序的话,箭的位置一开始就确定了,不需要再改变和判断箭的位置,直接判断区间即可。 - -### 思路 1:代码 - -1. 按照结束位置升序排序 - -```python -class Solution: - def findMinArrowShots(self, points: List[List[int]]) -> int: - if not points: - return 0 - points.sort(key=lambda x: x[1]) - arrow_pos = points[0][1] - count = 1 - for i in range(1, len(points)): - if arrow_pos < points[i][0]: - count += 1 - arrow_pos = points[i][1] - return count -``` - -2. 按照开始位置升序排序 - -```python -class Solution: - def findMinArrowShots(self, points: List[List[int]]) -> int: - if not points: - return 0 - points.sort(key=lambda x: x[0]) - arrow_pos = points[0][1] - count = 1 - for i in range(1, len(points)): - if arrow_pos < points[i][0]: - count += 1 - arrow_pos = points[i][1] - else: - arrow_pos = min(points[i][1], arrow_pos) - return count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$, 其中 $n$ 是数组 `points` 的长度。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/0454. \345\233\233\346\225\260\347\233\270\345\212\240 II.md" "b/Solutions/0454. \345\233\233\346\225\260\347\233\270\345\212\240 II.md" deleted file mode 100644 index dee9d5b3..00000000 --- "a/Solutions/0454. \345\233\233\346\225\260\347\233\270\345\212\240 II.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0454. 四数相加 II](https://leetcode.cn/problems/4sum-ii/) - -- 标签:数组、哈希表 -- 难度:中等 - -## 题目大意 - -给定四个整数数组 nums1、nums2、nums3、nums4。计算有多少不同的(i, j, k, l)满足 nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0。 - -## 解题思路 - -直接暴力搜索的时间复杂度是 $O(n^4)$。我们可以降低一下复杂度。 - -将四个数组分为两组。nums1 和 nums2 分为一组,nums3 和 nums4 分为一组。 - -已知 $nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0$,可以得到 $nums1[i] + nums2[j] = -(nums3[k] + nums4[l])$ - -建立一个哈希表。两重循环遍历数组 nums1、nums2,先将 $nums[i] + nums[j]$ 的和个数记录到哈希表中,然后再用两重循环遍历数组 nums3、nums4。如果 $-(nums3[k] + nums4[l])$ 的结果出现在哈希表中,则将结果数累加到答案中。最终输出累加之后的答案。 - -## 代码 - -```python -class Solution: - def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int: - nums_dict = dict() - for num1 in nums1: - for num2 in nums2: - sum = num1 + num2 - if sum in nums_dict: - nums_dict[sum] += 1 - else: - nums_dict[sum] = 1 - count = 0 - for num3 in nums3: - for num4 in nums4: - sum = num3 + num4 - if -sum in nums_dict: - count += nums_dict[-sum] - - return count -``` - diff --git "a/Solutions/0455. \345\210\206\345\217\221\351\245\274\345\271\262.md" "b/Solutions/0455. \345\210\206\345\217\221\351\245\274\345\271\262.md" deleted file mode 100644 index f2397b3c..00000000 --- "a/Solutions/0455. \345\210\206\345\217\221\351\245\274\345\271\262.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0455. 分发饼干](https://leetcode.cn/problems/assign-cookies/) - -- 标签:贪心、数组、双指针、排序 -- 难度:简单 - -## 题目大意 - -**描述**:一位很棒的家长为孩子们分发饼干。对于每个孩子 `i`,都有一个胃口值 `g[i]`,即每个小孩希望得到饼干的最小尺寸值。对于每块饼干 `j`,都有一个尺寸值 `s[j]`。只有当 `s[j] > g[i]` 时,我们才能将饼干 `j` 分配给孩子 `i`。每个孩子最多只能给一块饼干。 - -现在给定代表所有孩子胃口值的数组 `g` 和代表所有饼干尺寸的数组 `j`。 - -**要求**:尽可能满足越多数量的孩子,并求出这个最大数值。 - -**说明**: - -- $1 \le g.length \le 3 * 10^4$。 -- $0 \le s.length \le 3 * 10^4$。 -- $1 \le g[i], s[j] \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:g = [1,2,3], s = [1,1] -输出:1 -解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1, 2, 3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以应该输出 1。 -``` - -- 示例 2: - -```python -输入: g = [1,2], s = [1,2,3] -输出: 2 -解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1, 2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -为了尽可能的满⾜更多的⼩孩,而且一块饼干不能掰成两半,所以我们应该尽量让胃口小的孩子吃小块饼干,这样胃口大的孩子才有大块饼干吃。 - -所以,从贪心算法的角度来考虑,我们应该按照孩子的胃口从小到大对数组 `g` 进行排序,然后按照饼干的尺寸大小从小到大对数组 `s` 进行排序,并且对于每个孩子,应该选择满足这个孩子的胃口且尺寸最小的饼干。 - -下面我们使用贪心算法三步走的方法解决这道题。 - -1. **转换问题**:将原问题转变为,当胃口最小的孩子选择完满足这个孩子的胃口且尺寸最小的饼干之后,再解决剩下孩子的选择问题(子问题)。 -2. **贪心选择性质**:对于当前孩子,用尺寸尽可能小的饼干满足这个孩子的胃口。 -3. **最优子结构性质**:在上面的贪心策略下,当前孩子的贪心选择 + 剩下孩子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得满足胃口的孩子数量达到最大。 - -使用贪心算法的代码解决步骤描述如下: - -1. 对数组 `g`、`s` 进行从小到大排序,使用变量 `index_g` 和 `index_s` 分别指向 `g`、`s` 初始位置,使用变量 `res` 保存结果,初始化为 `0`。 -2. 对比每个元素 `g[index_g]` 和 `s[index_s]`: - 1. 如果 `g[index_g] <= s[index_s]`,说明当前饼干满足当前孩子胃口,则答案数量加 `1`,并且向右移动 `index_g` 和 `index_s`。 - 2. 如果 `g[index_g] > s[index_s]`,说明当前饼干无法满足当前孩子胃口,则向右移动 `index_s`,判断下一块饼干是否可以满足当前孩子胃口。 -3. 遍历完输出答案 `res`。 - -### 思路 1:代码 - -```python -class Solution: - def findContentChildren(self, g: List[int], s: List[int]) -> int: - g.sort() - s.sort() - index_g, index_s = 0, 0 - res = 0 - while index_g < len(g) and index_s < len(s): - if g[index_g] <= s[index_s]: - res += 1 - index_g += 1 - index_s += 1 - else: - index_s += 1 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times \log m + n \times \log n)$,其中 $m$ 和 $n$ 分别是数组 $g$ 和 $s$ 的长度。 -- **空间复杂度**:$O(\log m + \log n)$。 diff --git "a/Solutions/0459. \351\207\215\345\244\215\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0459. \351\207\215\345\244\215\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index dce76e3e..00000000 --- "a/Solutions/0459. \351\207\215\345\244\215\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [0459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) - -- 标签:字符串、字符串匹配 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个非空的字符串 `s`。 - -**要求**:检查该字符串 `s` 是否可以通过由它的一个子串重复多次构成。 - -**说明**: - -- $1 \le s.length \le 10^4$。 -- `s` 由小写英文字母组成 - -**示例**: - -- 示例 1: - -```python -输入: s = "abab" -输出: true -解释: 可由子串 "ab" 重复两次构成。 -``` - -- 示例 2: - -```python -输入: s = "aba" -输出: false -``` - -## 解题思路 - -### 思路 1:KMP 算法 - -这道题我们可以使用 KMP 算法的 `next` 数组来解决。我们知道 `next[j]` 表示的含义是:**记录下标 `j` 之前(包括 `j`)的模式串 `p` 中,最长相等前后缀的长度。** - -而如果整个模式串 `p` 的最长相等前后缀长度不为 `0`,即 `next[len(p) - 1] != 0` ,则说明整个模式串 `p` 中有最长相同的前后缀,假设 `next[len(p) - 1] == k`,则说明 `p[0: k] == p[m - k: m]`。比如字符串 `"abcabcabc"`,最长相同前后缀为 `"abcabc" = "abcabc"`。 - -- 如果最长相等的前后缀是重叠的,比如之前的例子 `"abcabcabc"`。 - - 如果我们去除字符串中相同的前后缀的重叠部分,剩下两头前后缀部分(这两部分是相同的)。然后再去除剩余的后缀部分,只保留剩余的前缀部分。比如字符串 `"abcabcabc"` 去除重叠部分和剩余的后缀部分之后就是 `"abc"`。实际上这个部分就是字符串去除整个后缀部分的剩余部分。 - - 如果整个字符串可以通过子串重复构成的话,那么这部分就是最小周期的子串。 - - 我们只需要判断整个子串的长度是否是剩余部分长度的整数倍即可。也就是判断 `len(p) % (len(p) - next[size - 1]) == 0` 是否成立,如果成立,则字符串 `s` 可由 `s[0: len(p) - next[size - 1]]` 构成的子串重复构成,返回 `True`。否则返回 `False`。 -- 如果最长相等的前后缀是不重叠的,那我们可将重叠部分视为长度为 `0` 的空串,则剩余的部分其实就是去除后缀部分的剩余部分,上述结论依旧成立。  - -### 思路 1:代码 - -```python -class Solution: - def generateNext(self, p: str): - m = len(p) - next = [0 for _ in range(m)] - - left = 0 - for right in range(1, m): - while left > 0 and p[left] != p[right]: - left = next[left - 1] - if p[left] == p[right]: - left += 1 - next[right] = left - - return next - - def repeatedSubstringPattern(self, s: str) -> bool: - size = len(s) - if size == 0: - return False - next = self.generateNext(s) - if next[size - 1] != 0 and size % (size - next[size - 1]) == 0: - return True - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m)$,其中模式串 $p$ 的长度为 $m$。 -- **空间复杂度**:$O(m)$。 - diff --git "a/Solutions/0461. \346\261\211\346\230\216\350\267\235\347\246\273.md" "b/Solutions/0461. \346\261\211\346\230\216\350\267\235\347\246\273.md" deleted file mode 100644 index b66c9e42..00000000 --- "a/Solutions/0461. \346\261\211\346\230\216\350\267\235\347\246\273.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [0461. 汉明距离](https://leetcode.cn/problems/hamming-distance/) - -- 标签:位运算 -- 难度:简单 - -## 题目大意 - -给定两个整数 x 和 y,计算他们之间的汉明距离。 - -- 汉明距离:两个数字对应二进制位上不同的位置的数目 - -## 解题思路 - -先对两个数进行异或运算(相同位置上,值相同,结果为 0,值不同,结果为 1),用于记录 x 和 y 不同位置上的异同情况。 - -然后再按位统计异或结果中 1 的位数。 - -这里统计 1 的位数可以逐位移动,检查每一位是否为 1。 - -也可以借助 $n \text{ \& } (n - 1)$ 运算。这个运算刚好可以将 n 的二进制中最低位的 1 变为 0。 - -## 代码 - -1. 逐位移动 -```python -class Solution: - def hammingDistance(self, x: int, y: int) -> int: - xor = x ^ y - distance = 0 - while xor: - if xor & 1: - distance += 1 - xor >>= 1 - return distance -``` - -2. $n \text{ \& } (n - 1)$ 运算 -```python -class Solution: - def hammingDistance(self, x: int, y: int) -> int: - xor = x ^ y - distance = 0 - while xor: - distance += 1 - xor = xor & (xor - 1) - return distance -``` - diff --git "a/Solutions/0463. \345\262\233\345\261\277\347\232\204\345\221\250\351\225\277.md" "b/Solutions/0463. \345\262\233\345\261\277\347\232\204\345\221\250\351\225\277.md" deleted file mode 100644 index 6ef3c04d..00000000 --- "a/Solutions/0463. \345\262\233\345\261\277\347\232\204\345\221\250\351\225\277.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0463. 岛屿的周长](https://leetcode.cn/problems/island-perimeter/) - -- 标签:深度优先搜索、广度优先搜索、数组、矩阵 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个 `row * col` 大小的二维网格地图 `grid` ,其中:`grid[i][j] = 1` 表示陆地,`grid[i][j] = 0` 表示水域。 - -网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(多个表示陆地的格子相连组成)。 - -岛屿内部中没有「湖」(指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。 - -**要求**:计算这个岛屿的周长。 - -**说明**: - -- $row == grid.length$。 -- $col == grid[i].length$。 -- $1 <= row, col <= 100$。 -- $grid[i][j]$ 为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/island.png) - -```python -输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]] -输出:16 -解释:它的周长是上面图片中的 16 个黄色的边 -``` - -- 示例 2: - -```python -输入:grid = [[1]] -输出:4 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -1. 使用整形变量 `count` 存储周长,使用队列 `queue` 用于进行广度优先搜索。 -2. 遍历一遍二维数组 `grid`,对 `grid[row][col] == 1` 的区域进行广度优先搜索。 -3. 先将起始点 `(row, col)` 加入队列。 -4. 如果队列不为空,则取出队头坐标 `(row, col)`。先将 `(row, col)` 标记为 `2`,避免重复统计。 -5. 然后遍历上、下、左、右四个方向的相邻区域,如果遇到边界或者水域,则周长加 1。 -6. 如果相邻区域 `grid[new_row][new_col] == 1`,则将其赋值为 `2`,并将坐标加入队列。 -7. 继续执行 4 ~ 6 步,直到队列为空时返回 `count`。 - -### 思路 1:代码 - -```python -class Solution: - def bfs(self, grid, rows, cols, row, col): - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - queue = collections.deque([(row, col)]) - - count = 0 - while queue: - row, col = queue.popleft() - # 避免重复统计 - grid[row][col] = 2 - for direct in directs: - new_row = row + direct[0] - new_col = col + direct[1] - # 遇到边界或者水域,则周长加 1 - if new_row < 0 or new_row >= rows or new_col < 0 or new_col >= cols or grid[new_row][new_col] == 0: - count += 1 - # 相邻区域为陆地,则将其标记为 2,加入队列 - elif grid[new_row][new_col] == 1: - grid[new_row][new_col] = 2 - queue.append((new_row, new_col)) - # 相邻区域为 2 的情况不做处理 - return count - - def islandPerimeter(self, grid: List[List[int]]) -> int: - rows, cols = len(grid), len(grid[0]) - for row in range(rows): - for col in range(cols): - if grid[row][col] == 1: - return self.bfs(grid, rows, cols, row, col) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(n \times m)$。 - -## 参考资料 - -- 【题解】[Golang BFS 实现,性能比dfs要高 - 岛屿的周长 - 力扣](https://leetcode.cn/problems/island-perimeter/solution/golang-bfs-shi-xian-xing-neng-bi-dfsyao-nln2g/) diff --git "a/Solutions/0464. \346\210\221\350\203\275\350\265\242\345\220\227.md" "b/Solutions/0464. \346\210\221\350\203\275\350\265\242\345\220\227.md" deleted file mode 100644 index 7aef4439..00000000 --- "a/Solutions/0464. \346\210\221\350\203\275\350\265\242\345\220\227.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) - -- 标签:位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数,$maxChoosableInteger$ 表示可以选择的最大整数,$desiredTotal$ 表示累计和。现在开始玩一个游戏,两个玩家轮流从 $1 \sim maxChoosableInteger$ 中不重复的抽取一个整数,直到累积整数和大于等于 $desiredTotal$ 时,这个人就赢得比赛。假设两位玩家玩游戏时都表现最佳。 - -**要求**:判断先出手的玩家是否能够稳赢,如果能稳赢,则返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le maxChoosableInteger \le 20$。 -- $0 \le desiredTotal \le 300$。 - -**示例**: - -- 示例 1: - -```python -输入:maxChoosableInteger = 10, desiredTotal = 11 -输出:False -解释: -无论第一个玩家选择哪个整数,他都会失败。 -第一个玩家可以选择从 1 到 10 的整数。 -如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。 -第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利. -同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。 -``` - -- 示例 2: - -```python -输入:maxChoosableInteger = 10, desiredTotal = 0 -输出:True -``` - -## 解题思路 - -### 思路 1:状态压缩 + 记忆化搜索 - -$maxChoosableInteger$ 的区间范围是 $[1, 20]$,数据量不是很大,我们可以使用状态压缩来判断当前轮次中数字的选取情况。 - -题目假设两位玩家玩游戏时都表现最佳,则每个人都会尽力去赢,在每轮次中,每个人都会分析此次选择后,对后续轮次的影响,判断自己是必赢还是必输。 - -1. 如果当前轮次选择某个数之后,自己一定会赢时,才会选择这个数。 -2. 如果当前轮次无论选择哪个数,自己一定会输时,那无论选择哪个数其实都已经无所谓了。 - -这样我们可以定义一个递归函数 `dfs(state, curTotal)`,用于判断处于状态 $state$,并且当前累计和为 $curTotal$ 时,自己是否一定会赢。如果自己一定会赢,返回 `True`,否则返回 `False`。递归函数内容如下: - -1. 从 $1 \sim maxChoosableInteger$ 中选择一个之前没有选过的数 $k$。 -2. 如果选择的数 $k$ 加上当前的整数和 $curTotal$ 之后大于等于 $desiredTotal$,则自己一定会赢。 -3. 如果选择的数 $k$ 之后,对方必输(即递归调用 `dfs(state | (1 << (k - 1)), curTotal + k)` 为 `Flase` 时),则自己一定会赢。 -4. 如果无论选择哪个数,自己都赢不了,则自己必输,返回 `False`。 - -这样,我们从 $state = 0, curTotal = 0$ 开始调用递归方法 `dfs(state, curTotal)`,即可判断先出手的玩家是否能够稳赢。 - -接下来,我们还需要考虑一些边界条件。 - -1. 当 $maxChoosableInteger$ 直接大于等于 $desiredTotal$,则先手玩家无论选什么,直接就赢了,这种情况下,我们直接返回 `True`。 -2. 当 $1 \sim maxChoosableInteger$ 中所有数加起来都小于 $desiredTotal$,则先手玩家无论怎么选,都无法稳赢,题目要求我们判断先出手的玩家是否能够稳赢,既然先手无法稳赢,我们直接返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def canIWin(self, maxChoosableInteger: int, desiredTotal: int) -> bool: - @cache - def dfs(state, curTotal): - for k in range(1, maxChoosableInteger + 1): # 从 1 ~ maxChoosableInteger 中选择一个数 - if state >> (k - 1) & 1 != 0: # 如果之前选过该数则跳过 - continue - if curTotal + k >= desiredTotal: # 如果选择了 k,累积整数和大于等于 desiredTotal,则该玩家一定赢 - return True - if not dfs(state | (1 << (k - 1)), curTotal + k): # 如果当前选择了 k 之后,对手一定输,则当前玩家一定赢 - return True - return False # 以上都赢不了的话,当前玩家一定输 - - # maxChoosableInteger 直接大于等于 desiredTotal,则先手玩家一定赢 - if maxChoosableInteger >= desiredTotal: - return True - - # 1 ~ maxChoosableInteger 所有数加起来都不够 desiredTotal,则先手玩家一定输 - if (1 + maxChoosableInteger) * maxChoosableInteger // 2 < desiredTotal: - return False - return dfs(0, 0) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为 $maxChoosableInteger$。 -- **空间复杂度**:$O(2^n)$。 diff --git "a/Solutions/0467. \347\216\257\347\273\225\345\255\227\347\254\246\344\270\262\344\270\255\345\224\257\344\270\200\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0467. \347\216\257\347\273\225\345\255\227\347\254\246\344\270\262\344\270\255\345\224\257\344\270\200\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index d054977d..00000000 --- "a/Solutions/0467. \347\216\257\347\273\225\345\255\227\347\254\246\344\270\262\344\270\255\345\224\257\344\270\200\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [0467. 环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -把字符串 `s` 看作是 `abcdefghijklmnopqrstuvwxyz` 的无限环绕字符串,所以 `s` 看起来是这样的:`...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....`。 - -给定一个字符串 `p`。 - -要求:你需要的是找出 `s` 中有多少个唯一的 `p` 的非空子串,尤其是当你的输入是字符串 `p` ,你需要输出字符串 `s` 中 `p` 的不同的非空子串的数目。 - -注意: `p` 仅由小写的英文字母组成,`p` 的大小可能超过 `10000`。 - -## 解题思路 - -字符串 `s` 是个 `a` ~ `z` 无限循环的字符串,题目要求计算字符串 `s` 和字符串 `p` 中有多少个相等的非空子串。发现以该字符结尾的连续子串的长度,就等于以该字符结尾的相等子串的个数。所以我们可以按以下步骤求解: - -- 记录以每个字符结尾的字符串最长长度。 -- 将其累加起来就是最终答案。 - -## 代码 - -```python -class Solution: - def findSubstringInWraproundString(self, p: str) -> int: - dp = collections.defaultdict(int) - dp[p[0]] = 1 - max_len = 1 - for i in range(1, len(p)): - if (ord(p[i]) - ord(p[i - 1])) % 26 == 1: - max_len += 1 - else: - max_len = 1 - dp[p[i]] = max(dp[p[i]], max_len) - - ans = 0 - for key, value in dp.items(): - ans += value - return ans -``` - diff --git "a/Solutions/0468. \351\252\214\350\257\201IP\345\234\260\345\235\200.md" "b/Solutions/0468. \351\252\214\350\257\201IP\345\234\260\345\235\200.md" deleted file mode 100644 index f9ca8b55..00000000 --- "a/Solutions/0468. \351\252\214\350\257\201IP\345\234\260\345\235\200.md" +++ /dev/null @@ -1,108 +0,0 @@ -# [0468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) - -- 标签:字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `queryIP`。 - -**要求**:如果是有效的 IPv4 地址,返回 `"IPv4"`;如果是有效的 IPv6 地址,返回 `"IPv6"`;如果不是上述类型的 IP 地址,返回 `"Neither"`。 - -**说明**: - -- **有效的 IPv4 地址**:格式为 `"x1.x2.x3.x4"` 形式的 IP 地址。 其中: - - $0 \le xi \le 255$。 - - $xi$ 不能包含前导零。 - -- 例如: `"192.168.1.1"` 、 `"192.168.1.0"` 为有效 IPv4 地址,`"192.168.01.1"` 为无效 IPv4 地址,`"192.168.1.00"` 、 `"192.168@1.1"` 为无效 IPv4 地址。 -- **有效的 IPv6 地址**: 格式为`"x1:x2:x3:x4:x5:x6:x7:x8"` 的 IP 地址,其中: - - $1 \le xi.length \le 4$。 - - $xi$ 是一个十六进制字符串,可以包含数字、小写英文字母(`'a'` 到 `'f'`)和大写英文字母(`'A'` 到 `'F'`)。 - - 在 $xi$ 中允许前导零。 -- 例如:`"2001:0db8:85a3:0000:0000:8a2e:0370:7334"` 和 `"2001:db8:85a3:0:0:8A2E:0370:7334"` 是有效的 IPv6 地址,而 `"2001:0db8:85a3::8A2E:037j:7334"` 和 `"02001:0db8:85a3:0000:0000:8a2e:0370:7334"` 是无效的 IPv6 地址。 -- `queryIP` 仅由英文字母,数字,字符 `'.'` 和 `':'` 组成。 - -**示例**: - -- 示例 1: - -```python -输入:queryIP = "172.16.254.1" -输出:"IPv4" -解释:有效的 IPv4 地址,返回 "IPv4" -``` - -- 示例 2: - -```python -输入:queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334" -输出:"IPv6" -解释:有效的 IPv6 地址,返回 "IPv6" -``` - -## 解题思路 - -### 思路 1:模拟 - -根据题意以及有效的 IPV4 地址规则、有效的 IPv6 地址规则,我们可以分两步来做:第一步,验证是否为有效的 IPV4 地址。第二步,验证是否为有效的 IPv6 地址。 - -#### 1. 验证是否为有效的 IPv4 地址 - -1. 将字符串按照 `'.'` 进行分割,将不同分段存入数组 `path` 中。 -2. 如果分段数组 `path` 长度等于 $4$,则说明该字符串为 IPv4 地址,接下里验证是否为有效的 IPv4 地址。 -3. 遍历分段数组 `path`,去验证每个分段 `sub`。 - 1. 如果当前分段 `sub` 为空,或者不是纯数字,则返回 `"Neither"`。 - 2. 如果当前分段 `sub` 有前导 $0$,并且长度不为 $1$,则返回 `"Neither"`。 - 3. 如果当前分段 `sub` 对应的值不在 $0 \sim 255$ 范围内,则返回 `"Neither"`。 -4. 遍历完分段数组 `path`,扔未发现问题,则该字符串为有效的 IPv4 地址,返回 `IPv4`。 - -#### 2. 验证是否为有效的 IPv6 地址 - -1. 将字符串按照 `':'` 进行分割,将不同分段存入数组 `path` 中。 -2. 如果分段数组 `path` 长度等于 $8$,则说明该字符串为 IPv6 地址,接下里验证是否为有效的 IPv6 地址。 -3. 定义一个代表十六进制不同字符的字符串 `valid = "0123456789abcdefABCDEF"`,用于验证分段的每一位是否为 $16$ 进制数。 -4. 遍历分段数组 `path`,去验证每个分段 `sub`。 - 1. 如果当前分段 `sub` 为空,则返回 `"Neither"`。 - 2. 如果当前分段 `sub` 长度超过 $4$,则返回 `"Neither"`。 - 3. 如果当前分段 `sub` 对应的每一位的值不在 `valid` 内,则返回 `"Neither"`。 -5. 遍历完分段数组 `path`,扔未发现问题,则该字符串为有效的 IPv6 地址,返回 `IPv6`。 - -如果通过上面两步验证,该字符串既不是有效的 IPv4 地址,也不是有效的 IPv6 地址,则返回 `"Neither"`。 - -### 思路 1:代码 - -```python -class Solution: - def validIPAddress(self, queryIP: str) -> str: - path = queryIP.split('.') - if len(path) == 4: - for sub in path: - if not sub or not sub.isdecimal(): - return "Neither" - if sub[0] == '0' and len(sub) != 1: - return "Neither" - if int(sub) > 255: - return "Neither" - return "IPv4" - - path = queryIP.split(':') - if len(path) == 8: - valid = "0123456789abcdefABCDEF" - for sub in path: - if not sub: - return "Neither" - if len(sub) > 4: - return "Neither" - for digit in sub: - if digit not in valid: - return "Neither" - return "IPv6" - - return "Neither" -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 `queryIP` 的长度。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0473. \347\201\253\346\237\264\346\213\274\346\255\243\346\226\271\345\275\242.md" "b/Solutions/0473. \347\201\253\346\237\264\346\213\274\346\255\243\346\226\271\345\275\242.md" deleted file mode 100644 index 3ab3ddb1..00000000 --- "a/Solutions/0473. \347\201\253\346\237\264\346\213\274\346\255\243\346\226\271\345\275\242.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [0473. 火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) - -- 标签:位运算、数组、动态规划、回溯、状态压缩 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个表示火柴长度的数组 `matchsticks`,其中 `matchsticks[i]` 表示第 `i` 根火柴的长度。 - -**要求**:找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以将火柴连接起来,并且每根火柴都要用到。如果能拼成正方形,则返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le matchsticks.length \le 15$。 -- $1 \le matchsticks[i] \le 10^8$。 - -**示例**: - -- 示例 1: - -```python -输入: matchsticks = [1,1,2,2,2] -输出: True -解释: 能拼成一个边长为 2 的正方形,每边两根火柴。 -``` - -- 示例 2: - -```python -输入: matchsticks = [3,3,3,3,4] -输出: False -解释: 不能用所有火柴拼成一个正方形。 -``` - -## 解题思路 - -### 思路 1:回溯算法 - -1. 先排除数组为空和火柴总长度不是 `4` 的倍数的情况,直接返回 `False`。 -2. 然后将火柴按照从大到小排序。用数组 `sums` 记录四个边长分组情况。 -3. 将火柴分为 `4` 组,把每一根火柴依次向 `4` 条边上放。 -4. 直到放置最后一根,判断能否构成正方形,若能构成正方形,则返回 `True`,否则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def dfs(self, index, sums, matchsticks, size, side_len): - if index == size: - return True - - for i in range(4): - # 如果两条边的情况相等,只需要计算一次,没必要多次重复计算 - if i > 0 and sums[i] == sums[i - 1]: - continue - sums[i] += matchsticks[index] - if sums[i] <= side_len and self.dfs(index + 1, sums, matchsticks, size, side_len): - return True - sums[i] -= matchsticks[index] - - return False - - def makesquare(self, matchsticks: List[int]) -> bool: - if not matchsticks: - return False - size = len(matchsticks) - sum_len = sum(matchsticks) - if sum_len % 4 != 0: - return False - - side_len = sum_len // 4 - matchsticks.sort(reverse=True) - - sums = [0 for _ in range(4)] - return self.dfs(0, sums, matchsticks, size, side_len) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(4^n)$。$n$ 是火柴的数目。 -- **空间复杂度**:$O(n)$。递归栈的空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0474. \344\270\200\345\222\214\351\233\266.md" "b/Solutions/0474. \344\270\200\345\222\214\351\233\266.md" deleted file mode 100644 index 3fe15544..00000000 --- "a/Solutions/0474. \344\270\200\345\222\214\351\233\266.md" +++ /dev/null @@ -1,98 +0,0 @@ -# [0474. 一和零](https://leetcode.cn/problems/ones-and-zeroes/) - -- 标签:数组、字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二进制字符串数组 $strs$,以及两个整数 $m$ 和 $n$。 - -**要求**:找出并返回 $strs$ 的最大子集的大小,该子集中最多有 $m$ 个 $0$ 和 $n$ 个 $1$。 - -**说明**: - -- 如果 $x$ 的所有元素也是 $y$ 的元素,集合 $x$ 是集合 $y$ 的子集。 -- $1 \le strs.length \le 600$。 -- $1 \le strs[i].length \le 100$。 -- $strs[i]$ 仅由 `'0'` 和 `'1'` 组成。 -- $1 \le m, n \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 -输出:4 -解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 -其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3。 -``` - -- 示例 2: - -```python -输入:strs = ["10", "0", "1"], m = 1, n = 1 -输出:2 -解释:最大的子集是 {"0", "1"} ,所以答案是 2。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题可以转换为「二维 0-1 背包问题」来做。 - -把 $0$ 的个数和 $1$ 的个数视作一个二维背包的容量。每一个字符串都当做是一件物品,其成本为字符串中 $1$ 的数量和 $0$ 的数量,每个字符串的价值为 $1$。 - -###### 1. 划分阶段 - -按照物品的序号、当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:最多有 $i$ 个 $0$ 和 $j$ 个 $1$ 的字符串 $strs$ 的最大子集的大小。 - -###### 3. 状态转移方程 - -填满最多由 $i$ 个 $0$ 和 $j$ 个 $1$ 构成的二维背包的最多物品数为下面两种情况中的最大值: - -- 使用之前字符串填满容量为 $i - zero\underline{}num$、$j - one\underline{}num$ 的背包的物品数 + 当前字符串价值 -- 选择之前字符串填满容量为 $i$、$j$ 的物品数。 - -则状态转移方程为:$dp[i][j] = max(dp[i][j], dp[i - zero\underline{}num][j - one\underline{}num] + 1)$。 - -###### 4. 初始条件 - -- 无论有多少个 $0$,多少个 $1$,只要不选 $0$,也不选 $1$,则最大子集的大小为 $0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:最多有 $i$ 个 $0$ 和 $j$ 个 $1$ 的字符串 $strs$ 的最大子集的大小。所以最终结果为 $dp[m][n]$。 - -### 思路 1:代码 - -```python -class Solution: - def findMaxForm(self, strs: List[str], m: int, n: int) -> int: - dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] - - for str in strs: - one_num = 0 - zero_num = 0 - for ch in str: - if ch == '0': - zero_num += 1 - else: - one_num += 1 - for i in range(m, zero_num - 1, -1): - for j in range(n, one_num - 1, -1): - dp[i][j] = max(dp[i][j], dp[i - zero_num][j - one_num] + 1) - - return dp[m][n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(l \times m \times n)$,其中 $l$ 为字符串 $strs$ 的长度。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0480. \346\273\221\345\212\250\347\252\227\345\217\243\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0480. \346\273\221\345\212\250\347\252\227\345\217\243\344\270\255\344\275\215\346\225\260.md" deleted file mode 100644 index d89ae6bc..00000000 --- "a/Solutions/0480. \346\273\221\345\212\250\347\252\227\345\217\243\344\270\255\344\275\215\346\225\260.md" +++ /dev/null @@ -1,112 +0,0 @@ -# [0480. 滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) - -- 标签:数组、哈希表、滑动窗口、堆(优先队列) -- 难度:困难 - -## 题目大意 - -给定一个数组 `nums`,有一个长度为 `k` 的窗口从最左端滑动到最右端。窗口中有 `k` 个数,每次窗口向右移动 `1` 位。 - -要求:找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。 - -- 中位数:有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。 - -例如: - -- `[2, 3, 4]`,中位数是 `3`。 -- `[2, 3]`,中位数是 `(2 + 3) / 2 = 2.5`。 - -## 解题思路 - -题目要求动态维护长度为 `k` 的窗口中元素的中位数。如果对窗口元素进行排序,时间复杂度一般是 $O(k * log_2k)$。如果对每个区间都进行排序,那时间复杂度就更大了,肯定会超时。 - -我们需要借助一个内部有序的数据结构,来降低取窗口中位数的时间复杂度。`Python` 可以借助 `heapq` 构建大顶堆和小顶堆。通过 `k` 的奇偶性和堆顶元素来获取中位数。 - -接下来还要考虑几个问题:初始化问题、取中位数问题、窗口滑动中元素的添加删除操作。接下来一一解决。 - -初始化问题: - -我们将所有大于中位数的元素放到 `heap_max`(小顶堆)中,并且元素个数向上取整。然后再将所有小于等于中位数的元素放到 `heap_min`(大顶堆)中,并且元素个数向下取整。这样当 `k` 为奇数时,`heap_max` 比 `heap_min` 多一个元素,中位数就是 `heap_max` 堆顶元素。当 `k` 为偶数时,`heap_max` 和 `heap_min` 中的元素个数相同,中位数就是 `heap_min` 堆顶元素和 `heap_max` 堆顶元素的平均数。这个过程操作如下: - -- 先将数组中前 `k` 个元素放到 `heap_max` 中。 -- 再从 `heap_max` 中取出 `k // 2` 个堆顶元素放到 `heap_min` 中。 - -取中位数问题(上边提到过): - -- 当 `k` 为奇数时,中位数就是 `heap_max` 堆顶元素。当 `k` 为偶数时,中位数就是 `heap_max` 堆顶元素和 `heap_min` 堆顶元素的平均数。 - -窗口滑动过程中元素的添加和删除问题: - -- 删除:每次滑动将窗口左侧元素删除。由于 `heapq` 没有提供删除中间特定元素相对应的方法。所以我们使用「延迟删除」的方式先把待删除的元素标记上,等到待删除的元素出现在堆顶时,再将其移除。我们使用 `removes` (哈希表)来记录待删除元素个数。 - - 将窗口左侧元素删除的操作为:`removes[nums[left]] += 1`。 -- 添加:每次滑动在窗口右侧添加元素。需要根据上一步删除的结果来判断需要添加到哪一个堆上。我们用 `banlance` 记录 `heap_max` 和 `heap_min` 元素个数的差值。 - - 如果窗口左边界 `nums[left]`小于等于 `heap_max` 堆顶元素 ,则说明上一步删除的元素在 `heap_min` 上,则让 `banlance -= 1`。 - - 如果窗口左边界 `nums[left]` 大于 `heap_max` 堆顶元素,则说明上一步删除的元素在 `heap_max` 上,则上 `banlance += 1`。 - - 如果窗口右边界 `nums[right]` 小于等于 `heap_max` 堆顶元素,则说明待添加元素需要添加到 `heap_min` 上,则让 `banlance += 1`。 - - 如果窗口右边界 `nums[right]` 大于 `heap_max` 堆顶元素,则说明待添加元素需要添加到 `heap_max` 上,则让 `banlance -= 1`。 -- 经过上述操作,`banlance` 的取值为 `0`、`-2`、`2` 中的一种。需要经过调整使得 `banlance == 0`。 - - 如果 `banlance == 0`,已经平衡,不需要再做操作。 - - 如果 `banlance == -2`,则说明 `heap_min` 比 `heap_max` 的元素多了两个。则从 `heap_min` 中取出堆顶元素添加到 `heap_max` 中。 - - 如果 `banlance == 2`,则说明 `heap_max` 比 `heap_min` 的元素多了两个。则从 `heap_max` 中取出堆顶元素添加到 `heap_min` 中。 -- 调整完之后,分别检查 `heap_max` 和 `heap_min` 的堆顶元素。 - - 如果 `heap_max` 堆顶元素恰好为待删除元素,即 `removes[-heap_max[0]] > 0`,则弹出 `heap_max` 堆顶元素。 - - 如果 `heap_min` 堆顶元素恰好为待删除元素,即 `removes[heap_min[0]] > 0`,则弹出 `heap_min` 堆顶元素。 -- 最后取中位数放入答案数组中,然后继续滑动窗口。 - -## 代码 - -```python -import collections -import heapq - -class Solution: - def median(self, heap_max, heap_min, k): - if k % 2 == 1: - return -heap_max[0] - else: - return (-heap_max[0] + heap_min[0]) / 2 - - def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]: - heap_max, heap_min = [], [] - removes = collections.Counter() - - for i in range(k): - heapq.heappush(heap_max, -nums[i]) - for i in range(k // 2): - heapq.heappush(heap_min, -heapq.heappop(heap_max)) - - res = [self.median(heap_max, heap_min, k)] - - for i in range(k, len(nums)): - banlance = 0 - left, right = i - k, i - removes[nums[left]] += 1 - if heap_max and nums[left] <= -heap_max[0]: - banlance -= 1 - else: - banlance += 1 - - if heap_max and nums[right] <= -heap_max[0]: - heapq.heappush(heap_max, -nums[i]) - banlance += 1 - else: - banlance -= 1 - heapq.heappush(heap_min, nums[i]) - - if banlance == -2: - heapq.heappush(heap_max, -heapq.heappop(heap_min)) - if banlance == 2: - heapq.heappush(heap_min, -heapq.heappop(heap_max)) - - while heap_max and removes[-heap_max[0]] > 0: - removes[-heapq.heappop(heap_max)] -= 1 - while heap_min and removes[heap_min[0]] > 0: - removes[heapq.heappop(heap_min)] -= 1 - res.append(self.median(heap_max, heap_min, k)) - - return res -``` - -## 参考资料 - -- 【题解】[《风 险 对 冲》:双堆对顶,大堆小堆同时维护,44ms - 滑动窗口中位数 - 力扣](https://leetcode.cn/problems/sliding-window-median/solution/feng-xian-dui-chong-shuang-dui-dui-ding-hq1dt/) diff --git "a/Solutions/0485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260.md" "b/Solutions/0485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index d4423e93..00000000 --- "a/Solutions/0485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [0485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二进制数组 $nums$, 数组中只包含 $0$ 和 $1$。 - -**要求**:计算其中最大连续 $1$ 的个数。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $nums[i]$ 不是 $0$ 就是 $1$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,0,1,1,1] -输出:3 -解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3. -``` - -- 示例 2: - -```python -输入:nums = [1,0,1,1,0,1] -输出:2 -``` - -## 解题思路 - -### 思路 1:一次遍历 - -1. 使用两个变量 $cnt$ 和 $ans$。$cnt$ 用于存储当前连续 $1$ 的个数,$ans$ 用于存储最大连续 $1$ 的个数。 -2. 然后进行一次遍历,统计当前连续 $1$ 的个数,并更新最大的连续 $1$ 个数。 -3. 最后返回 $ans$ 作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def findMaxConsecutiveOnes(self, nums: List[int]) -> int: - ans = 0 - cnt = 0 - for num in nums: - if num == 1: - cnt += 1 - ans = max(ans, cnt) - else: - cnt = 0 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0486. \351\242\204\346\265\213\350\265\242\345\256\266.md" "b/Solutions/0486. \351\242\204\346\265\213\350\265\242\345\256\266.md" deleted file mode 100644 index 3b7d4ca5..00000000 --- "a/Solutions/0486. \351\242\204\346\265\213\350\265\242\345\256\266.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0486. 预测赢家](https://leetcode.cn/problems/predict-the-winner/) - -- 标签:递归、数组、数学、动态规划、博弈 -- 难度:中等 - -## 题目大意 - -**描述**:给定搞一个整数数组 $nums$。玩家 $1$ 和玩家 $2$ 基于这个数组设计了一个游戏。 - -玩家 $1$ 和玩家 $2$ 轮流进行自己的回合,玩家 $1$ 先手。 - -开始时,两个玩家的初始分值都是 $0$。每一回合,玩家从数组的任意一端取一个数字(即 $nums[0]$ 或 $nums[nums.length - 1]$),取到的数字将会从数组中移除(数组长度减 $1$)。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。 - -**要求**:如果玩家 $1$ 能成为赢家,则返回 `True`。否则返回 `False`。如果两个玩家得分相等,同样认为玩家 $1$ 是游戏的赢家,也返回 `True`。假设每个玩家的玩法都会使他的分数最大化。 - -**说明**: - -- $1 \le nums.length \le 20$。 -- $0 \le nums[i] \le 10^7$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,5,2] -输出:False -解释:一开始,玩家 1 可以从 1 和 2 中进行选择。 -如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。 -所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。 -因此,玩家 1 永远不会成为赢家,返回 False。 -``` - -- 示例 2: - -```python -输入:nums = [1,5,233,7] -输出:True -解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 5 和 7 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。 -最终,玩家 1(234 分)比玩家 2(12 分)获得更多的分数,所以返回 True,表示玩家 1 可以成为赢家。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:玩家 $1$ 与玩家 $2$ 在 $nums[i]...nums[j]$ 之间互相选取,玩家 $1$ 比玩家 $2$ 多的最大分数。 - -###### 3. 状态转移方程 - -根据状态的定义,只有在 $i \le j$ 时才有意义,所以当 $i > j$ 时,$dp[i][j] = 0$。 - -1. 当 $i == j$ 时,当前玩家只能拿取 $nums[i]$,因此对于所有 $0 \le i < nums.length$,都有:$dp[i][i] = nums[i]$。 -2. 当 $i < j$ 时,当前玩家可以选择 $nums[i]$ 或 $nums[j]$,并是自己的分数最大化,然后换另一位玩家从剩下部分选取数字。则转移方程为:$dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])$。 - -###### 4. 初始条件 - -- 当 $i > j$ 时,$dp[i][j] = 0$。 -- 当 $i == j$ 时,$dp[i][j] = nums[i]$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:玩家 $1$ 与玩家 $2$ 在 $nums[i]...nums[j]$ 之间互相选取,玩家 $1$ 比玩家 $2$ 多的最大分数。则如果玩家 $1$ 想要赢,则 $dp[0][size - 1]$ 必须大于等于 $0$。所以最终结果为 $dp[0][size - 1] >= 0$。 - -### 思路 1:代码 - -```python -class Solution: - def PredictTheWinner(self, nums: List[int]) -> bool: - size = len(nums) - dp = [[0 for _ in range(size)] for _ in range(size)] - - for l in range(1, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - if l == 1: - dp[i][j] = nums[i] - else: - dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]) - return dp[0][size - 1] >= 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0487. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 II.md" "b/Solutions/0487. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 II.md" deleted file mode 100644 index 53d2438a..00000000 --- "a/Solutions/0487. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 II.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0487. 最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) - -- 标签:数组、动态规划、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个二进制数组,可以最多将 `1` 个 `0` 翻转为 `1`。 - -要求:找出其中最大连续 `1` 的个数。 - -## 解题思路 - -暴力做法是尝试将每个位置的 `0` 分别变为 `1`,然后统计最大连续 `1` 的个数。但这样复杂度就太高了。 - -我们可以使用滑动窗口来解决问题。保证滑动窗口内最多有 `1` 个 `0`。具体做法如下: - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证滑动窗口内最多有 `1` 个 `0`。使用 `zero_count` 统计窗口内 `1` 的个数。使用 `ans` 记录答案。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 如果 `nums[right] == 0`,则窗口内 `1` 的个数 + 1。 -- 如果该窗口中 `1` 的个数多于 `1` 个,即 `zero_count > 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中 `1` 的个数,直到 `zero_count <= 1`。 -- 维护更新最大连续 `1` 的个数。然后右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出最大连续 `1` 的个数。 - -## 代码 - -```python -class Solution: - def findMaxConsecutiveOnes(self, nums: List[int]) -> int: - left, right = 0, 0 - ans = 0 - zero_count = 0 - - while right < len(nums): - if nums[right] == 0: - zero_count += 1 - while zero_count > 1: - if nums[left] == 0: - zero_count -= 1 - left += 1 - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - diff --git "a/Solutions/0491. \351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0491. \351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index ad9bee70..00000000 --- "a/Solutions/0491. \351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0491. 递增子序列](https://leetcode.cn/problems/increasing-subsequences/) - -- 标签:位运算、数组、哈希表、回溯 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`,找出并返回该数组的所有递增子序列,递增子序列的长度至少为 2。 - -## 解题思路 - -可以利用回溯算法求解。 - -建立两个数组 res、path。res 用于存放所有递增子序列,path 用于存放当前的递增子序列。 - -定义回溯方法,从 `start_index = 0` 的位置开始遍历。 - -- 如果当前子序列的长度大于等于 2,则将当前递增子序列添加到 res 数组中(注意:不用返回,因为还要继续向下查找) -- 对数组 `[start_index, len(nums) - 1]` 范围内的元素进行取值,判断当前元素是否在本层出现过。如果出现过则跳出循环。 - - 将 `nums[i]` 标记为使用过。 - - 将 `nums[i]` 加入到当前 path 中。 - - 继续从 `i + 1` 开发遍历下一节点。 - - 进行回退操作。 -- 最终返回 res 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, nums: List[int], start_index): - if len(self.path) > 1: - self.res.append(self.path[:]) - - num_set = set() - for i in range(start_index, len(nums)): - if self.path and nums[i] < self.path[-1] or nums[i] in num_set: - continue - - num_set.add(nums[i]) - self.path.append(nums[i]) - self.backtrack(nums, i + 1) - self.path.pop() - - def findSubsequences(self, nums: List[int]) -> List[List[int]]: - self.res.clear() - self.path.clear() - self.backtrack(nums, 0) - return self.res -``` - diff --git "a/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" "b/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" deleted file mode 100644 index b8c42663..00000000 --- "a/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" +++ /dev/null @@ -1,178 +0,0 @@ -# [0494. 目标和](https://leetcode.cn/problems/target-sum/) - -- 标签:数组、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个整数 $target$。数组长度不超过 $20$。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 - -**要求**:返回通过上述方法构造的、运算结果等于 $target$ 的不同表达式数目。 - -**说明**: - -- $1 \le nums.length \le 20$。 -- $0 \le nums[i] \le 1000$。 -- $0 \le sum(nums[i]) \le 1000$。 -- $-1000 \le target \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,1,1,1,1], target = 3 -输出:5 -解释:一共有 5 种方法让最终目标和为 3。 --1 + 1 + 1 + 1 + 1 = 3 -+1 - 1 + 1 + 1 + 1 = 3 -+1 + 1 - 1 + 1 + 1 = 3 -+1 + 1 + 1 - 1 + 1 = 3 -+1 + 1 + 1 + 1 - 1 = 3 -``` - -- 示例 2: - -```python -输入:nums = [1], target = 1 -输出:1 -``` - -## 解题思路 - -### 思路 1:深度优先搜索(超时) - -使用深度优先搜索对每位数字进行 `+` 或者 `-`,具体步骤如下: - -1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 -2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 $i$ 到达最后一个位置 $size$: - 1. 如果和 $cur\underline{}sum$ 等于目标和 $target$,则返回方案数 $1$。 - 2. 如果和 $cur\underline{}sum$ 不等于目标和 $target$,则返回方案数 $0$。 -4. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum - nums[i]$ 的方案数。 -5. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum + nums[i]$ 的方案数。 -6. 将 4 ~ 5 两个方案数加起来就是当前位置 $i$、和为 $cur\underline{}sum$ 的方案数,返回该方案数。 -7. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - size = len(nums) - - def dfs(i, cur_sum): - if i == size: - if cur_sum == target: - return 1 - else: - return 0 - ans = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) - return ans - - return dfs(0, 0) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 - -### 思路 2:记忆化搜索 - -在思路 1 中我们单独使用深度优先搜索对每位数字进行 `+` 或者 `-` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。 - -这里我们使用哈希表 $$table$$ 记录遍历过的位置 $i$ 及所得到的的当前和 $cur\underline{}sum$ 下的方案数,来避免重复搜索。具体步骤如下: - -1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 -2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 $i$ 遍历完所有位置: - 1. 如果和 $cur\underline{}sum$ 等于目标和 $target$,则返回方案数 $1$。 - 2. 如果和 $cur\underline{}sum$ 不等于目标和 $target$,则返回方案数 $0$。 -4. 如果当前位置 $i$、和为 $cur\underline{}sum$ 之前记录过(即使用 $table$ 记录过对应方案数),则返回该方案数。 -5. 如果当前位置 $i$、和为 $cur\underline{}sum$ 之前没有记录过,则: - 1. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum - nums[i]$ 的方案数。 - 2. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum + nums[i]$ 的方案数。 - 3. 将上述两个方案数加起来就是当前位置 $i$、和为 $cur\underline{}sum$ 的方案数,将其记录到哈希表 $table$ 中,并返回该方案数。 -6. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 - -### 思路 2:代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - size = len(nums) - table = dict() - - def dfs(i, cur_sum): - if i == size: - if cur_sum == target: - return 1 - else: - return 0 - - if (i, cur_sum) in table: - return table[(i, cur_sum)] - - cnt = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) - table[(i, cur_sum)] = cnt - return cnt - - return dfs(0, 0) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 - -### 思路 3:动态规划 - -假设数组中所有元素和为 $sum$,数组中所有符号为 `+` 的元素为 $sum\underline{}x$,符号为 `-` 的元素和为 $sum\underline{}y$。则 $target = sum\underline{}x - sum\underline{}y$。 - -而 $sum\underline{}x + sum\underline{}y = sum$。根据两个式子可以求出 $2 \times sum\underline{}x = target + sum$,即 $sum\underline{}x = (target + sum) / 2$。 - -那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 $(target + sum) / 2$。这就变为了「0-1 背包问题」中求装满背包的方案数问题。 - -###### 1. 定义状态 - -定义状态 $dp[i]$ 表示为:填满容量为 $i$ 的背包,有 $dp[i]$ 种方法。 - -###### 2. 状态转移方程 - -填满容量为 $i$ 的背包的方法数来源于: - -1. 不使用当前 $num$:只使用之前元素填满容量为 $i$ 的背包的方法数。 -2. 使用当前 $num$:填满容量 $i - num$ 的包的方法数,再填入 $num$ 的方法数。 - -则动态规划的状态转移方程为:$dp[i] = dp[i] + dp[i - num]$。 - -###### 3. 初始化 - -初始状态下,默认填满容量为 $0$ 的背包有 $1$ 种办法(什么也不装)。即 $dp[i] = 1$。 - -###### 4. 最终结果 - -根据状态定义,最后输出 $dp[sise]$(即填满容量为 $size$ 的背包,有 $dp[size]$ 种方法)即可,其中 $size$ 为数组 $nums$ 的长度。 - -### 思路 3:代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - sum_nums = sum(nums) - if abs(target) > abs(sum_nums) or (target + sum_nums) % 2 == 1: - return 0 - size = (target + sum_nums) // 2 - dp = [0 for _ in range(size + 1)] - dp[0] = 1 - for num in nums: - for i in range(size, num - 1, -1): - dp[i] = dp[i] + dp[i - num] - return dp[size] -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0496. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 I.md" "b/Solutions/0496. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 I.md" deleted file mode 100644 index 775d6efa..00000000 --- "a/Solutions/0496. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 I.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) - -- 标签:栈、数组、哈希表、单调栈 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个没有重复元素的数组 `nums1` 和 `nums2` ,其中 `nums1` 是 `nums2` 的子集。 - -**要求**:找出 `nums1` 中每个元素在 `nums2` 中的下一个比其大的值。 - -**说明**: - -- `nums1` 中数字 `x` 的下一个更大元素是指: `x` 在 `nums2` 中对应位置的右边的第一个比 `x` 大的元素。如果不存在,对应位置输出 `-1`。 -- $1 \le nums1.length \le nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 10^4$。 -- $nums1$ 和 $nums2$ 中所有整数互不相同。 -- $nums1$ 中的所有整数同样出现在 $nums2$ 中。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. -输出:[-1,3,-1] -解释:nums1 中每个值的下一个更大元素如下所述: -- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 -- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。 -- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 -``` - -- 示例 2: - -```python -输入:nums1 = [2,4], nums2 = [1,2,3,4]. -输出:[3,-1] -解释:nums1 中每个值的下一个更大元素如下所述: -- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。 -- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。 -``` - -## 解题思路 - -最直接的思路是根据题意直接暴力求解。遍历 `nums1` 中的每一个元素。对于 `nums1` 的每一个元素 `nums1[i]`,再遍历一遍 `nums2`,查找 `nums2` 中对应位置右边第一个比 `nums1[i]` 大的元素。这种解法的时间复杂度是 $O(n^2)$。 - -另一种思路是单调栈。 - -### 思路 1:单调栈 - -因为 `nums1` 是 `nums2` 的子集,所以我们可以先遍历一遍 `nums2`,并构造单调递增栈,求出 `nums2` 中每个元素右侧下一个更大的元素。然后将其存储到哈希表中。然后再遍历一遍 `nums1`,从哈希表中取出对应结果,存放到答案数组中。这种解法的时间复杂度是 $O(n)$。具体做法如下: - -1. 使用数组 `res` 存放答案。使用 `stack` 表示单调递增栈。使用哈希表 `num_map` 用于存储 `nums2` 中下一个比当前元素大的数值,映射关系为 `当前元素值:下一个比当前元素大的数值`。 - -2. 遍历数组 `nums2`,对于当前元素: - 1. 如果当前元素值较小,则直接让当前元素值入栈。 - 2. 如果当前元素值较大,则一直出栈,直到当前元素值小于栈顶元素。 - 1. 出栈时,第一个大于栈顶元素值的元素,就是当前元素。则将其映射到 `num_map` 中。 -3. 遍历完数组 `nums2`,建立好所有元素下一个更大元素的映射关系之后,再遍历数组 `nums1`。 -4. 从 `num_map` 中取出对应的值,将其加入到答案数组中。 -5. 最终输出答案数组 `res`。 - -### 思路 1:代码 - -```python -class Solution: - def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: - res = [] - stack = [] - num_map = dict() - for num in nums2: - while stack and num > stack[-1]: - num_map[stack[-1]] = num - stack.pop() - stack.append(num) - - for num in nums1: - res.append(num_map.get(num, -1)) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0498. \345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.md" "b/Solutions/0498. \345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.md" deleted file mode 100644 index 39f88ad0..00000000 --- "a/Solutions/0498. \345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) - -- 标签:数组、矩阵、模拟 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个大小为 $m \times n$ 的矩阵 $mat$ 。 - -**要求**:以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。 - -**说明**: - -- $m == mat.length$。 -- $n == mat[i].length$。 -- $1 \le m, n \le 10^4$。 -- $1 \le m \times n \le 10^4$。 -- $-10^5 \le mat[i][j] \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/04/10/diag1-grid.jpg) - -```python -输入:mat = [[1,2,3],[4,5,6],[7,8,9]] -输出:[1,2,4,7,5,3,6,8,9] -``` - -- 示例 2: - -```python -输入:mat = [[1,2],[3,4]] -输出:[1,2,3,4] -``` - -## 解题思路 - -### 思路 1:找规律 + 考虑边界问题 - -这道题的关键是「找规律」和「考虑边界问题」。 - -找规律: - -1. 当「行号 + 列号」为偶数时,遍历方向为从左下到右上。可以记为右上方向 $(-1, +1)$,即行号减 $1$,列号加 $1$。 -2. 当「行号 + 列号」为奇数时,遍历方向为从右上到左下。可以记为左下方向 $(+1, -1)$,即行号加 $1$,列号减 $1$。 - -边界情况: - -1. 向右上方向移动时: - 1. 如果在最后一列,则向下方移动,即 `x += 1`。 - 2. 如果在第一行,则向右方移动,即 `y += 1`。 - 3. 其余情况想右上方向移动,即 `x -= 1`、`y += 1`。 -2. 向左下方向移动时: - 1. 如果在最后一行,则向右方移动,即 `y += 1`。 - 2. 如果在第一列,则向下方移动,即 `x += 1`。 - 3. 其余情况向左下方向移动,即 `x += 1`、`y -= 1`。 - -### 思路 1:代码 - -```python -class Solution: - def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]: - rows = len(mat) - cols = len(mat[0]) - count = rows * cols - x, y = 0, 0 - ans = [] - - for i in range(count): - ans.append(mat[x][y]) - - if (x + y) % 2 == 0: - # 最后一列 - if y == cols - 1: - x += 1 - # 第一行 - elif x == 0: - y += 1 - # 右上方向 - else: - x -= 1 - y += 1 - else: - # 最后一行 - if x == rows - 1: - y += 1 - # 第一列 - elif y == 0: - x += 1 - # 左下方向 - else: - x += 1 - y -= 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$、$n$ 分别为二维矩阵的行数、列数。 -- **空间复杂度**:$O(m \times n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(m \times n)$。不算上则空间复杂度为 $O(1)$。 - -## 参考资料 - -- 【题解】[「498. 对角线遍历」最简单易懂! - 对角线遍历 - 力扣(LeetCode)](https://leetcode.cn/problems/diagonal-traverse/solution/498-dui-jiao-xian-bian-li-zui-jian-dan-y-ibu3/) - diff --git "a/Solutions/0501. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\274\227\346\225\260.md" "b/Solutions/0501. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\274\227\346\225\260.md" deleted file mode 100644 index 7508b705..00000000 --- "a/Solutions/0501. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\274\227\346\225\260.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [0501. 二叉搜索树中的众数](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个有相同值的二叉搜索树(BST),要求找出 BST 中所有众数(出现频率最高的元素)。 - -二叉搜索树定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -## 解题思路 - -中序递归遍历二叉搜索树所得到的结果是一个有序数组,所以问题就变为了如何统计有序数组的众数。 - -定义几个变量。`count` 用来统计当前元素值对应的节点个数,`max_count` 用来元素出现次数最多的次数。数组 `res` 用来存储所有众数结果(因为众数可能不止一个)。 - -因为中序递归遍历二叉树,比较的元素肯定是相邻节点,所以需要再使用一个变量 `pre` 来指向前一节点。下面就开始愉快的递归了。 - -- 如果当前节点为空,直接返回。 -- 递归遍历左子树。 -- 比较当前节点和前一节点: - - 如果前一节点为空,则当前元素频率赋值为 1。 - - 如果前一节点值与当前节点值相同,则当前元素频率 + 1。 - - 如果前一节点值与当前节点值不同,则重新计算当前元素频率,将当前元素频率赋值为 1。 -- 判断当前元素频率和最高频率关系: - - 如果当前元素频率和最高频率值相等,则将对应元素值加入 res 数组。 - - 如果当前元素频率大于最高频率值,则更新最高频率值,并清空原 res 数组,将当前元素加入 res 数组。 -- 递归遍历右子树。 - -最终得到的 res 数组即为所求的众数。 - -## 代码 - -```python -class Solution: - res = [] - count = 0 - max_count = 0 - pre = None - def search(self, cur: TreeNode): - if not cur: - return - self.search(cur.left) - if not self.pre: - self.count = 1 - elif self.pre.val == cur.val: - self.count += 1 - else: - self.count = 1 - - self.pre = cur - - if self.count == self.max_count: - self.res.append(cur.val) - elif self.count > self.max_count: - self.max_count = self.count - self.res.clear() - self.res.append(cur.val) - - self.search(cur.right) - return - - def findMode(self, root: TreeNode) -> List[int]: - self.count = 0 - self.max_count = 0 - self.res.clear() - self.pre = None - self.search(root) - return self.res -``` - diff --git "a/Solutions/0503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II.md" "b/Solutions/0503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II.md" deleted file mode 100644 index 4e420c24..00000000 --- "a/Solutions/0503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) - -- 标签:栈、数组、单调栈 -- 难度:中等 - -## 题目大意 - -给定一个循环数组 `nums`(最后一个元素的下一个元素是数组的第一个元素)。 - -要求:输出每个元素的下一个更大元素。如果不存在,则输出 `-1`。 - -- 数字 `x` 的下一个更大的元素:按数组遍历顺序,这个数字之后的第一个比它更大的数。这意味着你应该循环地搜索它的下一个更大的数。 - -## 解题思路 - -第一种思路是根据题意直接暴力求解。遍历 `nums` 中的每一个元素。对于 `nums` 的每一个元素 `nums[i]`,查找 `nums[i]` 右边第一个比 `nums1[i]` 大的元素。这种解法的时间复杂度是 $O(n^2)$。 - -第二种思路是使用单调递增栈。遍历数组 `nums`,构造单调递增栈,求出 `nums` 中每个元素右侧下一个更大的元素。然后将其存储到答案数组中。这种解法的时间复杂度是 $O(n)$。 - -而循环数组的求解方法可以将 `nums` 复制一份到末尾,生成长度为 `len(nums) * 2` 的数组,或者通过取模运算将下标映射到 `0` ~ `len(nums) * 2 - 1` 之间。 - -具体做法如下: - -- 使用数组 `res` 存放答案,初始值都赋值为 `-1`。使用变量 `stack` 表示单调递增栈。 -- 遍历数组 `nums`,对于当前元素: - - 如果当前元素值小于栈顶元素,则说明当前元素「下一个更大元素」与栈顶元素的「下一个更大元素」相同。应该直接让当前元素的下标入栈。 - - 如果当前元素值大于栈顶元素,则说明当前元素是之前元素的「下一个更大元素」,则不断将栈顶元素出栈。直到当前元素值小于栈顶元素值。 - - 出栈时,出栈元素的「下一个更大元素」是当前元素。则将当前元素值存入到答案数组 `res` 中出栈元素所对应的位置中。 -- 最终输出答案数组 `res`。 - -## 代码 - -```python -size = len(nums) - res = [-1 for _ in range(size)] - stack = [] - for i in range(size * 2): - while stack and nums[i % size] > nums[stack[-1]]: - index = stack.pop() - res[index] = nums[i % size] - stack.append(i % size) - - return res -``` - diff --git "a/Solutions/0504. \344\270\203\350\277\233\345\210\266\346\225\260.md" "b/Solutions/0504. \344\270\203\350\277\233\345\210\266\346\225\260.md" deleted file mode 100644 index a80870fb..00000000 --- "a/Solutions/0504. \344\270\203\350\277\233\345\210\266\346\225\260.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [0504. 七进制数](https://leetcode.cn/problems/base-7/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 num,将其转换为 7 进制数,并以字符串形式输出。 - -## 解题思路 - -对 num 不断取余整除,然后将取到的余数进行拼接成字符串即可。 - -## 代码 - -```python -class Solution: - def convertToBase7(self, num: int) -> str: - if num == 0: - return "0" - if num < 0: - return "-" + self.convertToBase7(-num) - ans = "" - while num: - ans = str(num % 7) + ans - num //= 7 - return ans -``` - diff --git "a/Solutions/0506. \347\233\270\345\257\271\345\220\215\346\254\241.md" "b/Solutions/0506. \347\233\270\345\257\271\345\220\215\346\254\241.md" deleted file mode 100644 index 17e0fb47..00000000 --- "a/Solutions/0506. \347\233\270\345\257\271\345\220\215\346\254\241.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0506. 相对名次](https://leetcode.cn/problems/relative-ranks/) - -- 标签:数组、排序、堆(优先队列) -- 难度:简单 - -## 题目大意 - -**描述**:给定一个长度为 $n$ 的数组 $score$。其中 $score[i]$ 表示第 $i$ 名运动员在比赛中的成绩。所有成绩互不相同。 - -**要求**:找出他们的相对名次,并授予前三名对应的奖牌。前三名运动员将会被分别授予「金牌(`"Gold Medal"`)」,「银牌(`"Silver Medal"`)」和「铜牌(`"Bronze Medal"`)」。 - -**说明**: - -- $n == score.length$。 -- $1 \le n \le 10^4$。 -- $0 \le score[i] \le 10^6$。 -- $score$ 中的所有值互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:score = [5,4,3,2,1] -输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"] -解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。 -``` - -- 示例 2: - -```python -输入:score = [10,3,8,9,4] -输出:["Gold Medal","5","Bronze Medal","Silver Medal","4"] -解释:名次为 [1st, 5th, 3rd, 2nd, 4th] 。 -``` - -## 解题思路 - -### 思路 1:排序 - -1. 先对数组 $score$ 进行排序。 -2. 再将对应前三个位置上的元素替换成对应的字符串:`"Gold Medal"`, `"Silver Medal"`, `"Bronze Medal"`。 - -### 思路 1:代码 - -```python -class Solution: - def shellSort(self, arr): - size = len(arr) - gap = size // 2 - - while gap > 0: - for i in range(gap, size): - temp = arr[i] - j = i - while j >= gap and arr[j - gap] < temp: - arr[j] = arr[j - gap] - j -= gap - arr[j] = temp - gap = gap // 2 - return arr - - def findRelativeRanks(self, score: List[int]) -> List[str]: - nums = score.copy() - nums = self.shellSort(nums) - score_map = dict() - for i in range(len(nums)): - score_map[nums[i]] = i + 1 - - res = [] - for i in range(len(score)): - if score[i] == nums[0]: - res.append("Gold Medal") - elif score[i] == nums[1]: - res.append("Silver Medal") - elif score[i] == nums[2]: - res.append("Bronze Medal") - else: - res.append(str(score_map[score[i]])) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。因为采用了时间复杂度为 $O(n \times \log n)$ 的希尔排序。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0509. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.md" "b/Solutions/0509. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.md" deleted file mode 100644 index 093e3255..00000000 --- "a/Solutions/0509. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) - -- 标签:递归、记忆化搜索、数学、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:计算第 $n$ 个斐波那契数。 - -**说明**: - -- 斐波那契数列的定义如下: - - $f(0) = 0, f(1) = 1$。 - - $f(n) = f(n - 1) + f(n - 2)$,其中 $n > 1$。 -- $0 \le n \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:1 -解释:F(2) = F(1) + F(0) = 1 + 0 = 1 -``` - -- 示例 2: - -```python -输入:n = 3 -输出:2 -解释:F(3) = F(2) + F(1) = 1 + 1 = 2 -``` - -## 解题思路 - -### 思路 1:递归算法 - -根据我们的递推三步走策略,写出对应的递归代码。 - -1. 写出递推公式:$f(n) = f(n - 1) + f(n - 2)$。 -2. 明确终止条件:$f(0) = 0, f(1) = 1$。 -3. 翻译为递归代码: - 1. 定义递归函数:`fib(self, n)` 表示输入参数为问题的规模 $n$,返回结果为第 $n$ 个斐波那契数。 - 2. 书写递归主体:`return self.fib(n - 1) + self.fib(n - 2)`。 - 3. 明确递归终止条件: - 1. `if n == 0: return 0` - 2. `if n == 1: return 1` - -### 思路 1:代码 - -```python -class Solution: - def fib(self, n: int) -> int: - if n == 0: - return 0 - if n == 1: - return 1 - return self.fib(n - 1) + self.fib(n - 2) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。具体证明方法参考 [递归求斐波那契数列的时间复杂度,不要被网上的答案误导了 - 知乎](https://zhuanlan.zhihu.com/p/256344121)。 -- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 - -### 思路 2:动态规划算法 - -###### 1. 划分阶段 - -我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 为:第 $i$ 个斐波那契数。 - -###### 3. 状态转移方程 - -根据题目中所给的斐波那契数列的定义 $f(n) = f(n - 1) + f(n - 2)$,则直接得出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 - -###### 4. 初始条件 - -根据题目中所给的初始条件 $f(0) = 0, f(1) = 1$ 确定动态规划的初始条件,即 $dp[0] = 0, dp[1] = 1$。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 $dp[n]$,即第 $n$ 个斐波那契数为 $dp[n]$。 - -### 思路 2:代码 - -```python -class Solution: - def fib(self, n: int) -> int: - if n <= 1: - return n - - dp = [0 for _ in range(n + 1)] - dp[0] = 0 - dp[1] = 1 - for i in range(2, n + 1): - dp[i] = dp[i - 2] + dp[i - 1] - - return dp[n] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 diff --git "a/Solutions/0513. \346\211\276\346\240\221\345\267\246\344\270\213\350\247\222\347\232\204\345\200\274.md" "b/Solutions/0513. \346\211\276\346\240\221\345\267\246\344\270\213\350\247\222\347\232\204\345\200\274.md" deleted file mode 100644 index 18e46a61..00000000 --- "a/Solutions/0513. \346\211\276\346\240\221\345\267\246\344\270\213\350\247\222\347\232\204\345\200\274.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [0513. 找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:找出该二叉树 「最底层」的「最左边」节点的值。 - -**说明**: - -- 假设二叉树中至少有一个节点。 -- 二叉树的节点个数的范围是 $[1,10^4]$。 -- $-2^{31} \le Node.val \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:[1,2,3,4,null,5,6,null,null,7] -输出:7 -``` - -![](https://assets.leetcode.com/uploads/2020/12/14/tree2.jpg) - -## 解题思路 - -### 思路 1:层序遍历 - -这个问题可以拆分为两个问题: - -1. 如何找到「最底层」。 -2. 在「最底层」如何找到最左边的节点。 - -第一个问题,我们可以通过层序遍历直接确定最底层节点。而第二个问题可以通过改变层序遍历的左右节点访问顺序从而找到「最底层」的「最左边节点」。具体方法如下: - -1. 对二叉树进行层序遍历。每层元素先访问右节点,再访问左节点。 -2. 当遍历到最后一个元素时,此时最后一个元素就是「最底层」的「最左边」节点,即左下角的节点,将该节点的值返回即可。 - -### 思路 1:层序遍历代码 - -```python -import collections -class Solution: - def findBottomLeftValue(self, root: TreeNode) -> int: - if not root: - return -1 - queue = collections.deque() - queue.append(root) - while queue: - cur = queue.popleft() - if cur.right: - queue.append(cur.right) - if cur.left: - queue.append(cur.left) - return cur.val -``` - diff --git "a/Solutions/0515. \345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.md" "b/Solutions/0515. \345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 54cc5d1a..00000000 --- "a/Solutions/0515. \345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,21 +0,0 @@ -# [0515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:找出二叉树中每一层的最大值。 - -## 解题思路 - -利用队列进行层序遍历,并记录下每一层的最大值,将其存入答案数组中。 - -## 代码 - -```python - -``` - diff --git "a/Solutions/0516. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0516. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index ef62c6eb..00000000 --- "a/Solutions/0516. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [0516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:找出其中最长的回文子序列,并返回该序列的长度。 - -**说明**: - -- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 -- $1 \le s.length \le 1000$。 -- $s$ 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "bbbab" -输出:4 -解释:一个可能的最长回文子序列为 "bbbb"。 -``` - -- 示例 2: - -```python -输入:s = "cbbd" -输出:2 -解释:一个可能的最长回文子序列为 "bb"。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 - -###### 3. 状态转移方程 - -我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: - -1. 如果 $s[i] = s[j]$,则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + $2$,即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 -2. 如果 $s[i] \ne s[j]$,则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: - 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 - 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 - -则状态转移方程为: - -$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ - -###### 4. 初始条件 - -- 单个字符的最长回文序列是 $1$,即 $dp[i][i] = 1$。 - -###### 5. 最终结果 - -由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1]$,所以我们应该按照从下到上、从左到右的顺序进行遍历。 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def longestPalindromeSubseq(self, s: str) -> int: - size = len(s) - dp = [[0 for _ in range(size)] for _ in range(size)] - for i in range(size): - dp[i][i] = 1 - - for i in range(size - 1, -1, -1): - for j in range(i + 1, size): - if s[i] == s[j]: - dp[i][j] = dp[i + 1][j - 1] + 2 - else: - dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) - - return dp[0][size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" "b/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" deleted file mode 100644 index 0a737f80..00000000 --- "a/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" +++ /dev/null @@ -1,89 +0,0 @@ -# [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $coins$ 表示不同面额的硬币,另给一个整数 $amount$ 表示总金额。 - -**要求**:计算并返回可以凑成总金额的硬币方案数。如果无法凑出总金额,则返回 $0$。 - -**说明**: - -- 每一种面额的硬币枚数为无限个。 -- $1 \le coins.length \le 300$。 -- $1 \le coins[i] \le 5000$。 -- $coins$ 中的所有值互不相同。 -- $0 \le amount \le 5000$。 - -**示例**: - -- 示例 1: - -```python -输入:amount = 5, coins = [1, 2, 5] -输出:4 -解释:有四种方式可以凑成总金额: -5=5 -5=2+2+1 -5=2+1+1+1 -5=1+1+1+1+1 -``` - -- 示例 2: - -```python -输入:amount = 3, coins = [2] -输出:0 -解释:只用面额 2 的硬币不能凑成总金额 3。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问凑成总金额为 $amount$ 的背包,一共有多少种方案? - -这就变成了完全背包问题。「[322. 零钱兑换](https://leetcode.cn/problems/coin-change/)」中计算的是凑成总金额的最少硬币个数,而这道题计算的是凑成总金额的方案数。 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 - -###### 3. 状态转移方程 - -凑成总金额为 $i$ 的方案数 = 「不使用当前 $coin$,只使用之前硬币凑成金额 $i$ 的方案数」+「使用当前 $coin$ 凑成金额 $i - coin$ 的方案数」。即状态转移方程为:$dp[i] = dp[i] + dp[i - coin]$。 - -###### 4. 初始条件 - -- 凑成总金额为 $0$ 的方案数为 $1$,即 $dp[0] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 所以最终结果为 $dp[amount]$。 - -### 思路 1:代码 - -```python -class Solution: - def change(self, amount: int, coins: List[int]) -> int: - - dp = [0 for _ in range(amount + 1)] - dp[0] = 1 - for coin in coins: - for i in range(coin, amount + 1): - dp[i] += dp[i - coin] - - return dp[amount] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times amount)$,其中 $n$ 为数组 $coins$ 的元素个数,$amount$ 为总金额。 -- **空间复杂度**:$O(amount)$。 - diff --git "a/Solutions/0525. \350\277\236\347\273\255\346\225\260\347\273\204.md" "b/Solutions/0525. \350\277\236\347\273\255\346\225\260\347\273\204.md" deleted file mode 100644 index 6b35d6a1..00000000 --- "a/Solutions/0525. \350\277\236\347\273\255\346\225\260\347\273\204.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [0525. 连续数组](https://leetcode.cn/problems/contiguous-array/) - -- 标签:数组、哈希表、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个二进制数组 `nums`。 - -要求:找到含有相同数量 `0` 和 `1` 的最长连续子数组,并返回该子数组的长度。 - -## 解题思路 - -「`0` 和 `1` 数量相同」等价于「`1` 的数量减去 `0` 的数量等于 `0`」。 - -我们可以使用一个变量 `pre_diff` 来记录下前 `i` 个数中,`1` 的数量比 `0` 的数量多多少个。我们把这个 `pre_diff`叫做「`1` 和 `0` 数量差」,也可以理解为变种的前缀和。 - -然后我们再用一个哈希表 `pre_dic` 来记录「`1` 和 `0` 数量差」第一次出现的下标。 - -那么,如果我们在遍历的时候,发现 `pre_diff` 相同的数量差已经在之前出现过了,则说明:这两段之间相减的 `1` 和 `0` 数量差为 `0`。 - -什么意思呢? - -比如说:`j < i`,前 `j` 个数中第一次出现 `pre_diff == 2` ,然后前 `i` 个数中个第二次又出现了 `pre_diff == 2`。那么这两段形成的子数组 `nums[j + 1: i]` 中 `1` 比 `0` 多 `0` 个,则 `0` 和 `1` 数量相同的子数组长度为 `i - j`。 - -而第二次之所以又出现 `pre_diff == 2` ,是因为前半段子数组 `nums[0: j]` 贡献了相同的差值。 - -接下来还有一个小问题,如何计算「`1` 和 `0` 数量差」? - -我们可以把数组中的 `1` 记为贡献 `+1`,`0` 记为贡献 `-1`。然后使用一个变量 `count`,只要出现 `1` 就让 `count` 加上 `1`,意思是又多出了 `1` 个 `1`。只要出现 `0`,将让 `count` 减去 `1`,意思是 `0` 和之前累积的 `1` 个 `1` 相互抵消掉了。这样遍历完数组,也就计算出了对应的「`1` 和 `0` 数量差」。 - -整个思路的具体做法如下: - -- 创建一个哈希表,键值对关系为「`1` 和 `0` 的数量差:最早出现的下标 `i`」。 -- 使用变量 `pre_diff` 来计算「`1` 和 `0` 数量差」,使用变量 `count` 来记录 `0` 和 `1` 数量相同的连续子数组的最长长度,然后遍历整个数组。 -- 如果 `nums[i] == 1`,则让 `pre_diff += 1`;如果 `nums[i] == 0`,则让 `pre_diff -= 1`。 -- 如果在哈希表中发现了相同的 `pre_diff`,则计算相应的子数组长度,与 `count` 进行比较并更新 `count` 值。 -- 如果在哈希表中没有发现相同的 `pre_diff`,则在哈希表中记录下第一次出现 `pre_diff` 的下标 `i`。 -- 最后遍历完输出 `count`。 - -> 注意:初始化哈希表为:`pre_dic = {0: -1}`,意思为空数组时,默认「`1` 和 `0` 数量差」为 `0`,且第一次出现的下标为 `-1`。 -> -> 之所以这样做,是因为在遍历过程中可能会直接出现 `pre_diff == 0` 的情况,这种情况下说明 `nums[0: i]` 中 `0` 和 `1` 数量相同,如果像上边这样初始化后,就可以直接计算出此时子数组长度为 `i - (-1) = i + 1`。 - -## 代码 - -```python -class Solution: - def findMaxLength(self, nums: List[int]) -> int: - pre_dic = {0: -1} - count = 0 - pre_sum = 0 - for i in range(len(nums)): - if nums[i]: - pre_sum += 1 - else: - pre_sum -= 1 - if pre_sum in pre_dic: - count = max(count, i - pre_dic[pre_sum]) - else: - pre_dic[pre_sum] = i - return count -``` - diff --git "a/Solutions/0526. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.md" "b/Solutions/0526. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.md" deleted file mode 100644 index 27390c6a..00000000 --- "a/Solutions/0526. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.md" +++ /dev/null @@ -1,216 +0,0 @@ -# [0526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) - -- 标签:位运算、数组、动态规划、回溯、状态压缩 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回可以构造的「优美的排列」的数量。 - -**说明**: - -- **优美的排列**:假设有 $1 \sim n$ 的 $n$ 个整数。如果用这些整数构造一个数组 $perm$(下标从 $1$ 开始),使得数组第 $i$ 位元素 $perm[i]$ 满足下面两个条件之一,则该数组就是一个「优美的排列」: - - $perm[i]$ 能够被 $i$ 整除; - - $i$ 能够被 $perm[i]$ 整除。 - -- $1 \le n \le 15$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:2 -解释: -第 1 个优美的排列是 [1,2]: - - perm[1] = 1 能被 i = 1 整除 - - perm[2] = 2 能被 i = 2 整除 -第 2 个优美的排列是 [2,1]: - - perm[1] = 2 能被 i = 1 整除 - - i = 2 能被 perm[2] = 1 整除 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:1 -``` - -## 解题思路 - -### 思路 1:回溯算法 - -这道题可以看做是「[0046. 全排列](https://leetcode.cn/problems/permutations/)」的升级版。 - -1. 通过回溯算法我们可以将数组的所有排列情况列举出来。 -2. 因为只有满足第 $i$ 位元素能被 $i$ 整除,或者满足 $i$ 能整除第 $i$ 位元素的条件下才符合要求,所以我们可以进行剪枝操作,不再考虑不满足要求的情况。 -3. 最后回溯完输出方案数。 - -### 思路 1:代码 - -```python -class Solution: - def countArrangement(self, n: int) -> int: - ans = 0 - visited = set() - - def backtracking(index): - nonlocal ans - if index == n + 1: - ans += 1 - return - - for i in range(1, n + 1): - if i in visited: - continue - if i % index == 0 or index % i == 0: - visited.add(i) - backtracking(index + 1) - visited.remove(i) - - backtracking(1) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n!)$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(n)$,递归栈空间大小为 $O(n)$。 - -### 思路 2:状态压缩 DP - -因为 $n$ 最大只有 $15$,所以我们可以考虑使用「状态压缩」。 - -「状态压缩」指的是使用一个 $n$ 位的二进制数来表示排列中数的选取情况。 - -举个例子: - -1. $n = 4, state = (1001)_2$,表示选择了数字 $1, 4$,剩余数字 $2$ 和 $3$ 未被选择。 -2. $n = 6, state = (011010)_2$,表示选择了数字 $2, 4, 5$,剩余数字 $1, 3, 6$ 未被选择。 - -这样我们就可以使用 $n$ 位的二进制数 $state$ 来表示当前排列中数的选取情况。 - -如果我们需要检查值为 $k$ 的数字是否被选择时,可以通过判断 $(state \text{ >} \text{> } (k - 1)) \text{ \& } 1$ 是否为 $1$ 来确定。 - -如果为 $1$,则表示值为 $k$ 的数字被选择了,如果为 $0$,则表示值为 $k$ 的数字没有被选择。 - -###### 1. 划分阶段 - -按照排列的数字个数、数字集合的选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][state]$ 表示为:考虑前 $i$ 个数,且当数字集合的选择情况为 $state$ 时的方案数。 - -###### 3. 状态转移方程 - -假设 $dp[i][state]$ 中第 $i$ 个位置所选数字为 $k$,则:$state$ 中第 $k$ 位为 $1$,且 $k \mod i == 0$ 或者 $i \mod k == 0$。 - -那么 $dp[i][state]$ 肯定是由考虑前 $i - 1$ 个位置,且 $state$ 第 $k$ 位为 $0$ 的状态而来,即:$dp[i - 1][state \& (\neg(1 \text{ <}\text{< } (k - 1)))]$。 - -所以状态转移方程为:$dp[i][state] = \sum_{k = 1}^n dp[i - 1][state \text{ \& } (\neg(1 \text{ <} \text{< } (k - 1)))]$。 - -###### 4. 初始条件 - -- 不考虑任何数($i = 0, state = 0$)的情况下,方案数为 $1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][state]$ 表示为:考虑前 $i$ 个数,且当数字集合的选择情况为 $state$ 时的方案数。所以最终结果为 $dp[i][states - 1]$,其中 $states = 1 \text{ <} \text{< } n$。 - -### 思路 2:代码 - -```python -class Solution: - def countArrangement(self, n: int) -> int: - states = 1 << n - dp = [[0 for _ in range(states)] for _ in range(n + 1)] - dp[0][0] = 1 - - for i in range(1, n + 1): # 枚举第 i 个位置 - for state in range(states): # 枚举所有状态 - one_num = bin(state).count("1") # 计算当前状态中选择了多少个数字(即统计 1 的个数) - if one_num != i: # 只有 i 与选择数字个数相同时才能计算 - continue - for k in range(1, n + 1): # 枚举第 i 个位置(最后 1 位)上所选的数字 - if state >> (k - 1) & 1 == 0: # 只有 state 第 k 个位置上为 1 才表示选了该数字 - continue - if k % i == 0 or i % k == 0: # 只有满足整除关系才符合要求 - # dp[i][state] 由前 i - 1 个位置,且 state 第 k 位为 0 的状态而来 - dp[i][state] += dp[i - 1][state & (~(1 << (k - 1)))] - - return dp[i][states - 1] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2 \times 2^n)$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(n \times 2^n)$。 - -### 思路 3:状态压缩 DP + 优化 - -通过二维的「状态压缩 DP」可以看出,当我们在考虑第 $i$ 个位置时,其选择数字个数也应该为 $i$。 - -而我们可以根据 $state$ 中 $1$ 的个数来判断当前选择的数字个数,这样我们就可以减少用于枚举第 $i$ 个位置的循环,改用统计 $state$ 中 $1$ 的个数来判断前选择的数字个数或者说当前正在考虑的元素位置。 - -而这样,我们还可以进一步优化状态的定义,将二维的状态优化为一维的状态。具体做法如下: - -###### 1. 划分阶段 - -按照数字集合的选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[state]$ 表示为:当数字集合的选择情况为 $state$ 时的方案数。 - -###### 3. 状态转移方程 - -对于状态 $state$,先统计出 $state$ 中选择的数字个数(即统计二进制中 $1$ 的个数)$one_num$。 - -则 $dp[state]$ 表示选择了前 $one\underline{}num$ 个数字,且选择情况为 $state$ 时的方案数。 - -$dp[state]$ 的状态肯定是由前 $one\underline{}num - 1$ 个数字,且 $state$ 第 $k$ 位为 $0$ 的状态而来对应状态转移而来,即:$dp[state \oplus (1 << (k - 1))]$。 - -所以状态转移方程为:$dp[state] = \sum_{k = 1}^n dp[state \oplus (1 << (k - 1))]$ - -###### 4. 初始条件 - -- 不考虑任何数的情况下,方案数为 $1$,即:$dp[0] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当数字集合选择状态为 $state$ 时的方案数。所以最终结果为 $dp[states - 1]$,其中 $states = 1 << n$。 - -### 思路 3:代码 - -```python -class Solution: - def countArrangement(self, n: int) -> int: - states = 1 << n - dp = [0 for _ in range(states)] - dp[0] = 1 - - for state in range(states): # 枚举所有状态 - one_num = bin(state).count("1") # 计算当前状态中选择了多少个数字(即统计 1 的个数) - for k in range(1, n + 1): # 枚举最后 1 位上所选的数字 - if state >> (k - 1) & 1 == 0: # 只有 state 第 k 个位置上为 1 才表示选了该数字 - continue - if one_num % k == 0 or k % one_num == 0: # 只有满足整除关系才符合要求 - # dp[state] 由前 one_num - 1 个位置,且 state 第 k 位为 0 的状态而来 - dp[state] += dp[state ^ (1 << (k - 1))] - - return dp[states - 1] -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(2^n)$。 - -## 参考资料 - -- 【题解】[【宫水三叶】详解两种状态压缩 DP 思路 - 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/solution/gong-shui-san-xie-xiang-jie-liang-chong-vgsia/) diff --git "a/Solutions/0530. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\345\260\217\347\273\235\345\257\271\345\267\256.md" "b/Solutions/0530. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\345\260\217\347\273\235\345\257\271\345\267\256.md" deleted file mode 100644 index 729c52cd..00000000 --- "a/Solutions/0530. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\345\260\217\347\273\235\345\257\271\345\267\256.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0530. 二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 -- 难度: - -## 题目大意 - -给定一棵所有节点都为非负值的二叉搜索树,计算树中任意两节点的差的绝对值的最小值。 - -## 解题思路 - -先来看二叉搜索树的定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -题目要求二叉搜索树上任意两节点的差的绝对值的最小值。 - -二叉树的中序遍历顺序是:左 -> 根 -> 右,二叉搜索树的中序遍历最终得到就是一个升序数组。而升序数组中绝对值差的最小值就是比较相邻两节点差值的绝对值,找出其中最小值。 - -那么我们就可以在中序遍历的同时,比较搜索二叉树相邻节点的差值绝对值大小。这就需要维护两个变量:`ans` 和 `pre`。`ans` 用于存储差的绝对值的最小值,`pre`用于保存上一节点的值,用于和当前节点计算差值的绝对值。 - -## 代码 - -```python -class Solution: - ans = 999 - pre = 999 - def dfs(self, root: TreeNode): - if not root: - return - self.dfs(root.left) - self.ans = min(self.ans, abs(self.pre - root.val)) - self.pre = root.val - self.dfs(root.right) - - def getMinimumDifference(self, root: TreeNode) -> int: - self.dfs(root) - return self.ans -``` - diff --git "a/Solutions/0538. \346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.md" "b/Solutions/0538. \346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.md" deleted file mode 100644 index 90e0cd93..00000000 --- "a/Solutions/0538. \346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [0538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树(BST)的根节点,且二叉搜索树的节点值各不相同。要求将其转化为「累加树」,使其每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和。 - -二叉搜索树的定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -## 解题思路 - -题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 - -题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 - -二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 - -当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 - -## 代码 - -```python -class Solution: - pre = 0 - def createBinaryTree(self, root: TreeNode): - if not root: - return - self.createBinaryTree(root.right) - root.val += self.pre - self.pre = root.val - self.createBinaryTree(root.left) - - def convertBST(self, root: TreeNode) -> TreeNode: - self.pre = 0 - self.createBinaryTree(root) - return root -``` - diff --git "a/Solutions/0539. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" "b/Solutions/0539. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" deleted file mode 100644 index 5a101c64..00000000 --- "a/Solutions/0539. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [0539. 最小时间差](https://leetcode.cn/problems/minimum-time-difference/) - -- 标签:数组、数学、字符串、排序 -- 难度:中等 - -## 题目大意 - -给定一个 24 小时制形式(小时:分钟 "HH:MM")的时间列表 `timePoints`。 - -要求:找出列表中任意两个时间的最小时间差并以分钟数表示。 - -## 解题思路 - -- 遍历时间列表 `timePoints`,将每个时间转换为以分钟计算的整数形式,比如时间 `14:20`,将其转换为 `14 * 60 + 20 = 860`,存放到新的时间列表 `times` 中。 -- 为了处理最早时间、最晚时间之间的时间间隔,我们将 `times` 中最小时间添加到列表末尾一起进行排序。 -- 然后将新的时间列表 `times` 按照升序排列。 -- 遍历排好序的事件列表 `times` ,找出相邻两个时间的最小间隔值即可。 - -## 代码 - -```python -class Solution: - def changeTime(self, timePoint: str): - hours, minutes = timePoint.split(':') - return int(hours) * 60 + int(minutes) - - def findMinDifference(self, timePoints: List[str]) -> int: - if not timePoints or len(timePoints) > 24 * 60: - return 0 - - times = sorted(self.changeTime(time) for time in timePoints) - times.append(times[0] + 24 * 60) - res = times[-1] - for i in range(1, len(times)): - res = min(res, times[i] - times[i - 1]) - return res -``` - diff --git "a/Solutions/0542. 01 \347\237\251\351\230\265.md" "b/Solutions/0542. 01 \347\237\251\351\230\265.md" deleted file mode 100644 index 6b0ad43b..00000000 --- "a/Solutions/0542. 01 \347\237\251\351\230\265.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [0542. 01 矩阵](https://leetcode.cn/problems/01-matrix/) - -- 标签:广度优先搜索、数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个 $m * n$ 大小的、由 `0` 和 `1` 组成的矩阵 $mat$。 - -**要求**:输出一个大小相同的矩阵 $res$,其中 $res[i][j]$ 表示对应位置元素(即 $mat[i][j]$)到最近的 $0$ 的距离。 - -**说明**: - -- 两个相邻元素间的距离为 $1$。 -- $m == mat.length$。 -- $n == mat[i].length$。 -- $1 \le m, n \le 10^4$。 -- $1 \le m * n \le 10^4$。 -- $mat[i][j] === 0$ 或者 $mat[i][j] == 1$。 -- $mat$ 中至少有一个 $0$。 - -**示例**: - -- 示例 1: - -![](https://pic.leetcode-cn.com/1626667201-NCWmuP-image.png) - -```python -输入:mat = [[0,0,0],[0,1,0],[0,0,0]] -输出:[[0,0,0],[0,1,0],[0,0,0]] -``` - -- 示例 2: - -![](https://pic.leetcode-cn.com/1626667205-xFxIeK-image.png) - -```python -输入:mat = [[0,0,0],[0,1,0],[1,1,1]] -输出:[[0,0,0],[0,1,0],[1,2,1]] -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -题目要求的是每个 `1` 到 `0`的最短曼哈顿距离。 - -比较暴力的做法是,从每个 `1` 开始进行广度优先搜索,每一步累积距离,当搜索到第一个 `0`,就是离这个 `1` 最近的 `0`,我们更新对应 `1` 位置上的答案距离。然后从下一个 `1` 开始进行广度优先搜索。 - -这样做每次进行广度优先搜索的时间复杂度为 $O(m \times n)$。对于 $m \times n$ 个节点来说,每个节点可能都要进行一次广度优先搜索,总的时间复杂度为 $O(m^2 \times n^2)$。时间复杂度太高了。 - -我们可以换个角度:求每个 `0` 到 `1` 的最短曼哈顿距离(和求每个 `1` 到 `0` 是等价的)。 - -我们将所有值为 `0` 的元素位置保存到队列中,然后对所有值为 `0` 的元素开始进行广度优先搜索,每搜一步距离加 `1`,当每次搜索到 `1` 时,就可以得到 `0` 到这个 `1` 的最短距离,也就是当前离这个 `1` 最近的 `0` 的距离。 - -这样对于所有节点来说,总共需要进行一次广度优先搜索就可以了,时间复杂度为 $O(m \times n)$。 - -具体步骤如下: - -1. 使用一个集合变量 `visited` 存储所有值为 `0` 的元素坐标。使用队列变量 `queue` 存储所有值为 `0` 的元素坐标。使用二维数组 `res` 存储对应位置元素(即 $mat[i][j]$)到最近的 $0$ 的距离。 -2. 我们从所有为如果队列 `queue` 不为空,则从队列中依次取出值为 `0` 的元素坐标,遍历其上、下、左、右位置。 -3. 如果相邻区域未被访问过(说明遇到了值为 `1` 的元素),则更新相邻位置的距离值,并把相邻位置坐标加入队列 `queue` 和访问集合 `visited` 中。 -4. 继续执行 2 ~ 3 步,直到队列为空时,返回 `res`。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: - rows, cols = len(mat), len(mat[0]) - res = [[0 for _ in range(cols)] for _ in range(rows)] - visited = set() - - for i in range(rows): - for j in range(cols): - if mat[i][j] == 0: - visited.add((i, j)) - - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - queue = collections.deque(visited) - - while queue: - i, j = queue.popleft() - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - if 0 <= new_i < rows and 0 <= new_j < cols and (new_i, new_j) not in visited: - res[new_i][new_j] = res[i][j] + 1 - queue.append((new_i, new_j)) - visited.add((new_i, new_j)) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/0543. \344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.md" "b/Solutions/0543. \344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.md" deleted file mode 100644 index a683d40f..00000000 --- "a/Solutions/0543. \344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给一个二叉树的根节点 $root$。 - -**要求**:计算该二叉树的直径长度。 - -**说明**: - -- **二叉树的直径长度**:二叉树中任意两个节点路径长度中的最大值。 -- 两节点之间的路径长度是以它们之间边的数目表示。 -- 这条路径可能穿过也可能不穿过根节点。 - -**示例**: - -- 示例 1: - -```python -给定二叉树: - 1 - / \ - 2 3 - / \ - 4 5 -输出:3 -解释:该二叉树的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -这道题重点是理解直径长度的定义。「二叉树的直径长度」的定义为:二叉树中任意两个节点路径长度中的最大值。并且这条路径可能穿过也可能不穿过根节点。 - -对于根为 $root$ 的二叉树来说,其直径长度并不简单等于「左子树高度」加上「右子树高度」。 - -根据路径是否穿过根节点,我们可以将二叉树分为两种: - -1. 直径长度所对应的路径穿过根节点。 -2. 直径长度所对应的路径不穿过根节点。 - -我们来看下图中的两个例子。 - -![](https://qcdn.itcharge.cn/images/20230427111005.png) - -如图所示,左侧这棵二叉树就是一棵常见的平衡二叉树,其直径长度所对应的路径是穿过根节点的($D\rightarrow B \rightarrow A \rightarrow C$)。这种情况下:$\text{二叉树的直径} = \text{左子树高度} + \text{右子树高度}$。 - -而右侧这棵特殊的二叉树,其直径长度所对应的路径是没有穿过根节点的($F \rightarrow D \rightarrow B \rightarrow E \rightarrow G$)。这种情况下:$\text{二叉树的直径} = \text{所有子树中最大直径长度}$。 - -也就是说根为 $root$ 的二叉树的直径长度可能来自于 $\text{左子树高度} + \text{右子树高度}$,也可能来自于 $\text{子树中的最大直径}$,即 $\text{二叉树的直径} = max(\text{左子树高度} + \text{右子树高度}, \quad \text{所有子树中最大直径长度})$。 - -那么现在问题就变成为如何求「子树的高度」和「子树中的最大直径」。 - -1. 子树的高度:我们可以利用深度优先搜索方法,递归遍历左右子树,并分别返回左右子树的高度。 -2. 子树中的最大直径:我们可以在递归求解子树高度的时候维护一个 $ans$ 变量,用于记录所有 $\text{左子树高度} + \text{右子树高度}$ 中的最大值。 - -最终 $ans$ 就是我们所求的该二叉树的最大直径,将其返回即可。 - -### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def __init__(self): - self.ans = 0 - - def dfs(self, node): - if not node: - return 0 - left_height = self.dfs(node.left) # 左子树高度 - right_height = self.dfs(node.right) # 右子树高度 - self.ans = max(self.ans, left_height + right_height) # 维护所有路径中的最大直径 - return max(left_height, right_height) + 1 # 返回该节点的高度 = 左右子树最大高度 + 1 - - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - self.dfs(root) - return self.ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 - diff --git "a/Solutions/0546. \347\247\273\351\231\244\347\233\222\345\255\220.md" "b/Solutions/0546. \347\247\273\351\231\244\347\233\222\345\255\220.md" deleted file mode 100644 index 82cf3ed5..00000000 --- "a/Solutions/0546. \347\247\273\351\231\244\347\233\222\345\255\220.md" +++ /dev/null @@ -1,106 +0,0 @@ -# [0546. 移除盒子](https://leetcode.cn/problems/remove-boxes/) - -- 标签:记忆化搜索、数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个代表不同颜色盒子的正数数组 $boxes$,盒子的颜色由不同正数组成,其中 $boxes[i]$ 表示第 $i$ 个盒子的颜色。 - -我们将经过若干轮操作去去掉盒子,直到所有盒子都去掉为止。每一轮我们可以移除具有相同颜色的连续 $k$ 个盒子($k \ge 1$),这样一轮之后,我们将获得 $k \times k$ 个积分。 - -**要求**:返回我们能获得的最大积分和。 - -**说明**: - -- $1 \le boxes.length \le 100$。 -- $1 \le boxes[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:boxes = [1,3,2,2,2,3,4,3,1] -输出:23 -解释: -[1, 3, 2, 2, 2, 3, 4, 3, 1] -----> [1, 3, 3, 4, 3, 1] (3*3=9 分) -----> [1, 3, 3, 3, 1] (1*1=1 分) -----> [1, 1] (3*3=9 分) -----> [] (2*2=4 分) -``` - -- 示例 2: - -```python -输入:boxes = [1,1,1] -输出:9 -``` - -## 解题思路 - -### 思路 1:动态规划 - -对于每个盒子, - -如果使用二维状态 $dp[i][j]$ 表示为:移除区间 $[i, j]$ 之间的盒子,所能够得到的最大积分和。但实际上,移除区间 $[i, j]$ 之间盒子,所能得到的最大积分和,并不只依赖于子区间,也依赖于之前移除其他区间对当前区间的影响。比如当前区间的某个值和其他区间的相同值连起来可以获得更高的额分数。 - -因此,我们需要再二维状态的基础上,增加更多维数的状态。 - -对于当前区间 $[i, j]$,我们需要凑一些尽可能长的同色盒子一起消除,从而获得更高的分数。我们不妨每次都选择消除区间 $[i, j]$ 中最后一个盒子 $boxes[j]$,并且记录 $boxes[j]$ 之后与 $boxes[j]$ 颜色相同的盒子数量。 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j][k]$ 表示为:移除区间 $[i, j]$ 之间的盒子,并且区间右侧有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分和。 - -###### 3. 状态转移方程 - -- 当区间长度为 $1$ 时,当前区间只有一个盒子,区间末尾有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分为 $(k + 1) \times (k + 1)$。 -- 当区间长度大于 $1$ 时,对于区间末尾的 $k$ 个与 $boxes[j]$ 颜色相同的盒子,有两种处理方式: - - 将末尾的盒子移除,所能够得到的最大积分为:移除末尾盒子之前能够获得的最大积分和,再加上本轮移除末尾盒子能够获得的积分和,即:$dp[i][j - 1][0] + (k + 1) \times (k + 1)$。 - - 在区间中找到一个位置 $t$,使得第 $t$ 个盒子与第 $j$ 个盒子颜色相同,先将区间 $[t + 1, j - 1]$ 的盒子消除,然后继续凑同色盒子,即:$dp[t + 1][j - 1][0] + dp[i][t][k + 1]$。 - -###### 4. 初始条件 - -- 区间长度为 $1$ 时,当前区间只有一个盒子,区间末尾有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分为 $(k + 1) \times (k + 1)$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j][k]$ 表示为:移除区间 $[i, j]$ 之间的盒子,并且区间右侧有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分和。所以最终结果为 $dp[0][size - 1][0]$。 - -### 思路 1:代码 - -```python -class Solution: - def removeBoxes(self, boxes: List[int]) -> int: - size = len(boxes) - - dp = [[[0 for _ in range(size)] for _ in range(size)] for _ in range(size)] - for l in range(1, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - - for k in range(size - j): - if l == 1: - dp[i][j][k] = max(dp[i][j][k], (k + 1) * (k + 1)) - else: - dp[i][j][k] = max(dp[i][j][k], dp[i][j - 1][0] + (k + 1) * (k + 1)) - for t in range(i, j): - if boxes[t] == boxes[j]: - dp[i][j][k] = max(dp[i][j][k], dp[t + 1][j - 1][0] + dp[i][t][k + 1]) - - return dp[0][size - 1][0] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^4)$,其中 $n$ 为数组 $boxes$ 的元素个数。 -- **空间复杂度**:$O(n^3)$。 - diff --git "a/Solutions/0547. \347\234\201\344\273\275\346\225\260\351\207\217.md" "b/Solutions/0547. \347\234\201\344\273\275\346\225\260\351\207\217.md" deleted file mode 100644 index 215cb992..00000000 --- "a/Solutions/0547. \347\234\201\344\273\275\346\225\260\351\207\217.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [0547. 省份数量](https://leetcode.cn/problems/number-of-provinces/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -**描述**:有 `n` 个城市,其中一些彼此相连,另一些没有相连。如果城市 `a` 与城市 `b` 直接相连,且城市 `b` 与城市 `c` 直接相连,那么城市 `a` 与城市 `c` 间接相连。 - -「省份」是由一组直接或间接链接的城市组成,组内不含有其他没有相连的城市。 - -现在给定一个 `n * n` 的矩阵 `isConnected` 表示城市的链接关系。其中 `isConnected[i][j] = 1` 表示第 `i` 个城市和第 `j` 个城市直接相连,`isConnected[i][j] = 0` 表示第 `i` 个城市和第 `j` 个城市没有相连。 - -**要求**:根据给定的城市关系,返回「省份」的数量。 - -**说明**: - -- $1 \le n \le 200$。 -- $n == isConnected.length$。 -- $n == isConnected[i].length$。 -- $isConnected[i][j]$ 为 $1$ 或 $0$。 -- $isConnected[i][i] == 1$。 -- $isConnected[i][j] == isConnected[j][i]$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/12/24/graph1.jpg) - -```python -输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] -输出:2 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/12/24/graph2.jpg) - -```python -输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] -输出:3 -``` - -## 解题思路 - -### 思路 1:并查集 - -1. 遍历矩阵 `isConnected`。如果 `isConnected[i][j] == 1`,将 `i` 节点和 `j` 节点相连。 -2. 然后判断每个城市节点的根节点,然后统计不重复的根节点有多少个,即为「省份」的数量。 - -### 思路 1:代码 - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) - -class Solution: - def findCircleNum(self, isConnected: List[List[int]]) -> int: - size = len(isConnected) - union_find = UnionFind(size) - for i in range(size): - for j in range(i + 1, size): - if isConnected[i][j] == 1: - union_find.union(i, j) - - res = set() - for i in range(size): - res.add(union_find.find(i)) - return len(res) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 \times \alpha(n))$。其中 $n$ 是城市的数量,$\alpha$ 是反 `Ackerman` 函数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0557. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215 III.md" "b/Solutions/0557. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215 III.md" deleted file mode 100644 index 1f7aea38..00000000 --- "a/Solutions/0557. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215 III.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0557. 反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:将字符串中每个单词的字符顺序进行反装,同时仍保留空格和单词的初始顺序。 - -**说明**: - -- $1 \le s.length \le 5 * 10^4$。 -- `s` 包含可打印的 ASCII 字符。 -- `s` 不包含任何开头或结尾空格。 -- `s` 里至少有一个词。 -- `s` 中的所有单词都用一个空格隔开。 - -**示例**: - -- 示例 1: - -```python -输入:s = "Let's take LeetCode contest" -输出:"s'teL ekat edoCteeL tsetnoc" -``` - -- 示例 2: - -```python -输入: s = "God Ding" -输出:"doG gniD" -``` - -## 解题思路 - -### 思路 1:使用额外空间 - -因为 Python 的字符串是不可变的,所以在原字符串空间上进行切换顺序操作肯定是不可行的了。但我们可以利用切片方法。 - -1. 将字符串按空格进行分割,分割成一个个的单词。 -2. 再将每个单词进行反转。 -3. 最后将每个单词连接起来。 - -### 思路 1:代码 - -```python -class Solution: - def reverseWords(self, s: str) -> str: - return " ".join(word[::-1] for word in s.split(" ")) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 128a0cc8..00000000 --- "a/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) - -- 标签:数组、哈希表、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums` 和一个整数 `k`。 - -要求:找到该数组中和为 `k` 的连续子数组的个数。 - -## 解题思路 - -看到题目的第一想法是通过滑动窗口求解。但是做下来发现有些数据样例无法通过。发现这道题目中的整数不能保证都为正数,则无法通过滑动窗口进行求解。 - -先考虑暴力做法,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: - -```python -for i in range(len(nums)): - for j in range(i + 1): - sum = countSum(i, j) -``` - -这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 - -先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 - -但是还是超时了。。 - -由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 - -`pre_sum[i]` 的定义是前 `i` 个元素和,则 `pre_sum[i]` 可以由 `pre_sum[i - 1]` 递推而来,即:`pre_sum[i] = pre_sum[i - 1] + num[i]`。 `[j..i]` 子数组和为 `k` 可以转换为:`pre_sum[i] - pre_sum[j - 1] == k`。 - -综合一下,可得:`pre_sum[j - 1] == pre_sum[i] - k `。 - -所以,当我们考虑以 `i` 结尾和为 `k` 的连续子数组个数时,只需要统计有多少个前缀和为 `pre_sum[i] - k` (即 `pre_sum[j - 1]`)的个数即可。具体做法如下: - -- 使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。 -- 使用哈希表 `pre_dic` 记录 `pre_sum[i]` 出现的次数。键值对为 `pre_sum[i] : pre_sum_count`。 -- 从左到右遍历数组,计算当前前缀和 `pre_sum`。 -- 如果 `pre_sum - k` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum - k]`。 -- 如果 `pre_sum` 在哈希表中,则前缀和个数累加 1,即 `pre_dic[pre_sum] += 1`。 -- 最后输出答案个数。 - -## 代码 - -```python -class Solution: - def subarraySum(self, nums: List[int], k: int) -> int: - pre_dic = {0: 1} - pre_sum = 0 - count = 0 - for num in nums: - pre_sum += num - if pre_sum - k in pre_dic: - count += pre_dic[pre_sum - k] - if pre_sum in pre_dic: - pre_dic[pre_sum] += 1 - else: - pre_dic[pre_sum] = 1 - return count -``` - diff --git "a/Solutions/0561. \346\225\260\347\273\204\346\213\206\345\210\206.md" "b/Solutions/0561. \346\225\260\347\273\204\346\213\206\345\210\206.md" deleted file mode 100644 index c99f7286..00000000 --- "a/Solutions/0561. \346\225\260\347\273\204\346\213\206\345\210\206.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0561. 数组拆分](https://leetcode.cn/problems/array-partition/) - -- 标签:贪心、数组、计数排序、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个长度为 $2 \times n$ 的整数数组 $nums$。 - -**要求**:将数组中的数拆分成 $n$ 对,每对数求最小值,求 $n$ 对数最小值的最大总和是多少。 - -**说明**: - -- $1 \le n \le 10^4$。 -- $nums.length == 2 * n$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,4,3,2] -输出:4 -解释:所有可能的分法(忽略元素顺序)为: -1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3 -2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3 -3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4 -所以最大总和为 4 -``` -- 示例 2: - -```python -输入:nums = [6,2,6,5,1,2] -输出:9 -解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9 -``` - -## 解题思路 - -### 思路 1:计数排序 - -因为 $nums[i]$ 的范围为 $[-10^4, 10^4]$,范围不是很大,所以我们可以使用计数排序算法先将数组 $nums$ 进行排序。 - -要想每对数最小值的总和最大,就得使每对数的最小值尽可能大。只有让较大的数与较大的数一起组合,较小的数与较小的数一起结合,才能才能使总和最大。所以,排序完之后将相邻两个元素的最小值进行相加,即得到结果。 - -### 思路 1:代码 - -```python -class Solution: - def countingSort(self, nums: [int]) -> [int]: - # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min - nums_min, nums_max = min(nums), max(nums) - # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 - size = nums_max - nums_min + 1 - counts = [0 for _ in range(size)] - - # 统计值为 num 的元素出现的次数 - for num in nums: - counts[num - nums_min] += 1 - - # 生成累积计数数组 - for i in range(1, size): - counts[i] += counts[i - 1] - - # 反向填充目标数组 - res = [0 for _ in range(len(nums))] - for i in range(len(nums) - 1, -1, -1): - num = nums[i] - # 根据累积计数数组,将 num 放在数组对应位置 - res[counts[num - nums_min] - 1] = num - # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 - counts[nums[i] - nums_min] -= 1 - - return res - - def arrayPairSum(self, nums: List[int]) -> int: - nums = self.countingSort(nums) - return sum(nums[::2]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + k)$,其中 $k$ 代表数组 $nums$ 的值域。 -- **空间复杂度**:$O(k)$。 - -### 思路 2:排序 - -要想每对数最小值的总和最大,就得使每对数的最小值尽可能大。只有让较大的数与较大的数一起组合,较小的数与较小的数一起结合,才能才能使总和最大。 - -1. 对 $nums$ 进行排序。 -2. 将相邻两个元素的最小值进行相加,即得到结果。 - -### 思路 1:代码 - -```python -class Solution: - def arrayPairSum(self, nums: List[int]) -> int: - nums.sort() - return sum(nums[::2]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" "b/Solutions/0567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" deleted file mode 100644 index 0f0ae7d5..00000000 --- "a/Solutions/0567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) - -- 标签:哈希表、双指针、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定两个字符串 `s1` 和 `s2` 。 - -要求:判断 `s2` 是否包含 `s1` 的排列。如果包含,返回 `True`;否则,返回 `False`。 - -## 解题思路 - -题目要求判断 `s2` 是否包含 `s1` 的排列,则 `s2` 的子串长度等于 `s1` 的长度。我们可以维护一个长度为字符串 `s1` 长度的固定长度的滑动窗口。 - -先统计出字符串 `s1` 中各个字符的数量,我们用 `s1_count` 来表示。这个过程可以用字典、数组来实现,也可以直接用 `collections.Counter()` 实现。再统计 `s2` 对应窗口内的字符数量 `window_count`,然后不断向右滑动,然后进行比较。如果对应字符数量相同,则返回 `True`,否则继续滑动。直到末尾时,返回 `False`。整个解题步骤具体如下: - -1. `s1_count` 用来统计 `s1` 中各个字符数量。`window_count` 用来维护窗口中 `s2` 对应子串的各个字符数量。`window_size` 表示固定窗口的长度,值为 `len(s1)`。 -2. 先统计出 `s1` 中各个字符数量。 -3. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -4. 向右移动 `right`,先将 `len(s1)` 个元素填入窗口中。 -5. 当窗口元素个数为 `window_size` 时,即:`right - left + 1 >= window_size` 时,判断窗口内各个字符数量 `window_count` 是否等于 `s1 ` 中各个字符数量 `s1_count`。 - 1. 如果等于,直接返回 `True`。 - 2. 如果不等于,则向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `window_size`。 -6. 重复 4 ~ 5 步,直到 `right` 到达数组末尾。返回 `False`。 - -## 代码 - -```python -import collections - -class Solution: - def checkInclusion(self, s1: str, s2: str) -> bool: - left, right = 0, 0 - s1_count = collections.Counter(s1) - window_count = collections.Counter() - window_size = len(s1) - - while right < len(s2): - window_count[s2[right]] += 1 - - if right - left + 1 >= window_size: - if window_count == s1_count: - return True - window_count[s2[left]] -= 1 - if window_count[s2[left]] == 0: - del window_count[s2[left]] - left += 1 - right += 1 - return False -``` - diff --git "a/Solutions/0575. \345\210\206\347\263\226\346\236\234.md" "b/Solutions/0575. \345\210\206\347\263\226\346\236\234.md" deleted file mode 100644 index 9327d624..00000000 --- "a/Solutions/0575. \345\210\206\347\263\226\346\236\234.md" +++ /dev/null @@ -1,28 +0,0 @@ -# [0575. 分糖果](https://leetcode.cn/problems/distribute-candies/) - -- 标签:数组、哈希表 -- 难度:简单 - -## 题目大意 - -给定一个偶数长度为 `n` 的数组,其中不同的数字代表不同种类的糖果,每一个数字代表一个糖果。 - -要求:将这些糖果按种类平均分为一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。 - -## 解题思路 - -`n` 个糖果分为两个人,每个人最多只能得到 `n // 2` 个糖果。假设糖果种数为 `m`。则如果糖果种类数大于糖果总数的一半,即 `m > n // 2`,则返回糖果数量的一半就好,也就说糖果总数一半的糖果都可以是不同种类的糖果。妹妹能获得最多 `n // 2` 种糖果。而如果让给种类数小于等于糖果总数的一半,即 `m <= n // 2`,则返回种类数,也就是说妹妹可以最多获得 `m` 种糖果。 - -综合这两种情况,其最终结果就是 `ans = min(m, n // 2)`。 - -计算糖果种类可以用 set 集合来做。 - -## 代码 - -```python -class Solution: - def distributeCandies(self, candyType: List[int]) -> int: - candy_set = set(candyType) - return min(len(candyType) // 2, len(candy_set)) -``` - diff --git "a/Solutions/0576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260.md" "b/Solutions/0576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260.md" deleted file mode 100644 index 9ab2e6c3..00000000 --- "a/Solutions/0576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [0576. 出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) - -- 标签:动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:有一个大小为 $m \times n$ 的网络和一个球。球的起始位置为 $(startRow, startColumn)$。你可以将球移到在四个方向上相邻的单元格内(可以穿过网格边界到达网格之外)。最多可以移动 $maxMove$ 次球。 - -现在给定五个整数 $m$、$n$、$maxMove$、$startRow$ 以及 $startColumn$。 - -**要求**:找出并返回可以将球移出边界的路径数量。因为答案可能非常大,返回对 $10^9 + 7$ 取余后的结果。 - -**说明**: - -- $1 \le m, n \le 50$。 -- $0 \le maxMove \le 50$。 -- $0 \le startRow < m$。 -- $0 \le startColumn < n$。 - -**示例**: - -- 示例 1: - -```python -输入:m = 2, n = 2, maxMove = 2, startRow = 0, startColumn = 0 -输出:6 -``` - -![](https://assets.leetcode.com/uploads/2021/04/28/out_of_boundary_paths_1.png) - -## 解题思路 - -### 思路 1:记忆化搜索 - -1. 问题的状态定义为:从位置 $(i, j)$ 出发,最多使用 $moveCount$ 步,可以将球移出边界的路径数量。 -2. 定义一个 $m \times n \times (maxMove + 1)$ 的三维数组 $memo$ 用于记录已经计算过的路径数量。 -3. 定义递归函数 $dfs(i, j, moveCount)$ 用于计算路径数量。 - 1. 如果 $(i, j)$ 已经出界,则说明找到了一条路径,返回方案数为 $1$。 - 2. 如果没有移动次数了,则返回方案数为 $0$。 - 3. 定义方案数 $ans$,遍历四个方向,递归计算四个方向的方案数,累积到 $ans$ 中,并进行取余。 - 4. 返回方案数 $ans$。 -4. 调用递归函数 $dfs(startRow, startColumn, maxMove)$,并将其返回值作为答案进行返回。 - -### 思路 1:代码 - -```python -class Solution: - def findPaths(self, m: int, n: int, maxMove: int, startRow: int, startColumn: int) -> int: - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - mod = 10 ** 9 + 7 - - memo = [[[-1 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] - - def dfs(i, j, moveCount): - if i < 0 or i >= m or j < 0 or j >= n: - return 1 - - if moveCount == 0: - return 0 - - if memo[i][j][moveCount] != -1: - return memo[i][j][moveCount] - - ans = 0 - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - ans += dfs(new_i, new_j, moveCount - 1) - ans %= mod - - memo[i][j][moveCount] = ans - return ans - - return dfs(startRow, startColumn, maxMove) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n \times maxMove)$。 -- **空间复杂度**:$O(m \times n \times maxMove)$。 - -### 思路 2:动态规划 - -我们需要统计从 $(startRow, startColumn)$ 位置出发,最多移动 $maxMove$ 次能够穿过边界的所有路径数量。则我们可以根据位置和移动步数来划分阶段和定义状态。 - -###### 1. 划分阶段 - -按照位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j][k]$ 表示为:从位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量。 - -###### 3. 状态转移方程 - -因为球可以在上下左右四个方向上进行移动,所以对于位置 $(i, j)$,最多移动 $k$ 次最终穿过边界的所有路径数量取决于周围四个方向上最多经过 $k - 1$ 次穿过对应位置上的所有路径数量和。 - -即:$dp[i][j][k] = dp[i - 1][j][k - 1] + dp[i + 1][j][k - 1] + dp[i][j - 1][k - 1] + dp[i][j + 1][k - 1]$。 - -###### 4. 初始条件 - -如果位置 $[i, j]$ 已经处于边缘,只差一步就穿过边界。则此时位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量取决于有相邻多少个方向是边界。也可以通过对上面 $(i - 1, j)$、$(i + 1, j)$、$(i, j - 1)$、$(i, j + 1)$ 是否已经穿过边界进行判断(每一个方向穿过一次,就累积一次),来计算路径数目。然后将其作为初始条件。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j][k]$ 表示为:从位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量。则最终答案为 $dp[startRow][startColumn][maxMove]$。 - -### 思路 2:动态规划代码 - -```python -class Solution: - def findPaths(self, m: int, n: int, maxMove: int, startRow: int, startColumn: int) -> int: - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - mod = 10 ** 9 + 7 - - dp = [[[0 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] - for i in r - for k in range(1, maxMove + 1): - for i in range(m): - for j in range(n): - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - if 0 <= new_i < m and 0 <= new_j < n: - dp[i][j][k] = (dp[i][j][k] + dp[new_i][new_j][k - 1]) % mod - else: - dp[i][j][k] = (dp[i][j][k] + 1) % mod - - return dp[startRow][startColumn][maxMove] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(m \times n \times maxMove)$。三重循环遍历的时间复杂度为 $O(m \times n \times maxMove)$。 -- **空间复杂度**:$O(m \times n \times maxMove)$。使用了三维数组保存状态,所以总体空间复杂度为 $O(m \times n \times maxMove)$。 diff --git "a/Solutions/0583. \344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.md" "b/Solutions/0583. \344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.md" deleted file mode 100644 index ca6f3a48..00000000 --- "a/Solutions/0583. \344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -给定两个单词 `word1` 和 `word2`,找到使得 `word1` 和 `word2` 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 - -## 解题思路 - -动态规划求解。 - -先定义状态 `dp[i][j]` 为以 `i - 1` 为结尾的字符串 `word1` 和以 `j - 1` 字结尾的字符串 `word2` 想要达到相等,所需要删除元素的最少次数。 - -然后确定状态转移方程。 - -- 如果 `word1[i - 1] == word2[j - 1]`,`dp[i][j]` 取源于以 `i - 2` 结尾结尾的字符串 `word1` 和以 `j - 1` 结尾的字符串 `word2`,即 `dp[i][j] = dp[i - 1][j - 1]`。 -- 如果 `word1[i - 1] != word2[j - 1]`,`dp[i][j]` 取源于以下三种情况中的最小情况: - - 删除 `word1[i - 1]`,最少操作次数为:`dp[i - 1][j] + 1`。 - - 删除 `word2[j - 1]`,最少操作次数为:`dp[i][j - 1] + 1`。 - - 同时删除 `word1[i - 1]`、`word2[j - 1]`,最少操作次数为 `dp[i - 1][j - 1] + 2`。 - -然后确定一下边界条件。 - -- 当 `word1` 为空字符串,以 `j - 1` 结尾的字符串 `word2` 要删除 `j` 个字符才能和 `word1` 相同,即 `dp[0][j] = j`。 -- 当 `word2` 为空字符串,以 `i - 1` 结尾的字符串 `word1` 要删除 `i` 个字符才能和 `word2` 相同,即 `dp[i][0] = i`。 - -最后递推求解,最终输出 `dp[size1][size2]` 为答案。 - -## 代码 - -```python -class Solution: - def minDistance(self, word1: str, word2: str) -> int: - size1 = len(word1) - size2 = len(word2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - - for i in range(size1 + 1): - dp[i][0] = i - for j in range(size2 + 1): - dp[0][j] = j - - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if word1[i - 1] == word2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] - else: - dp[i][j] = min(dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1) - - return dp[size1][size2] -``` - diff --git "a/Solutions/0589. N \345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0589. N \345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index 4fa13006..00000000 --- "a/Solutions/0589. N \345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [0589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) - -- 标签:栈、树、深度优先搜索 -- 难度:简单 - -## 题目大意 - -给定一棵 N 叉树的根节点 `root`。 - -要求:返回其节点值的前序遍历。 - -进阶:使用迭代法完成。 - -## 解题思路 - -递归法很好写。迭代法需要借助于栈。 - -- 用栈保存根节点 `root`。然后遍历栈。 -- 循环判断栈是否为空。 -- 如果栈不为空,取出栈顶节点,将节点值加入答案数组。 -- 逆序遍历栈顶节点的子节点,将其依次放入栈中(逆序保证取出顺序为正)。 -- 然后继续第 2 ~ 4 步,直到栈为空。 - -最后输出答案数组。 - -## 代码 - -```python -class Solution: - def preorder(self, root: 'Node') -> List[int]: - res = [] - stack = [] - if not root: - return res - stack.append(root) - while stack: - node = stack.pop() - res.append(node.val) - for i in range(len(node.children) - 1, -1, -1): - if node.children[i]: - stack.append(node.children[i]) - return res -``` - diff --git "a/Solutions/0590. N \345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" "b/Solutions/0590. N \345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" deleted file mode 100644 index cb6d2415..00000000 --- "a/Solutions/0590. N \345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) - -- 标签:栈、树、深度优先搜索 -- 难度:简单 - -## 题目大意 - -给定一个 N 叉树的根节点 `root`。 - -要求:返回其节点值的后序遍历。 - -## 解题思路 - -N 叉树的后序遍历顺序为:子节点顺序递归遍历 -> 根节点。 - -一个取巧的方法是先按照:根节点 -> 子节点逆序递归遍历 的顺序将遍历顺序存储到答案数组。 - -然后再将其进行翻转就变为了后序遍历顺序。具体操作如下: - -- 用栈保存根节点 `root`。然后遍历栈。 -- 循环判断栈是否为空。 -- 如果栈不为空,取出栈顶节点,将节点值加入答案数组。 -- 顺序遍历栈顶节点的子节点,将其依次放入栈中(顺序遍历保证取出顺序为逆序)。 -- 然后继续第 2 ~ 4 步,直到栈为空。 - -最后将答案数组逆序返回。 - -## 代码 - -```python -class Solution: - def postorder(self, root: 'Node') -> List[int]: - res = [] - stack = [] - if not root: - return res - - stack.append(root) - while stack: - node = stack.pop() - res.append(node.val) - for child in node.children: - stack.append(child) - - return res[::-1] -``` - diff --git "a/Solutions/0599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214.md" "b/Solutions/0599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214.md" deleted file mode 100644 index f17cd76b..00000000 --- "a/Solutions/0599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [0599. 两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) - -- 标签:数组、哈希表、字符串 -- 难度:简单 - -## 题目大意 - -Andy 和 Doris 都有一个表示最喜欢餐厅的列表 list1、list2,每个餐厅的名字用字符串表示。 - -找出他们共同喜爱的餐厅,要求两个餐厅在列表中的索引和最小,如果答案不唯一,则输出所有答案。 - -## 解题思路 - -遍历 list1,建立一个哈希表 list1_dict,以 list1[i] : i 键值对的方式,将 list1 的下标存储起来。 - -然后遍历 list2,判断 list2[i] 是否在哈希表中,如果在,则根据 i + list1_dict[i] 和 min_sum 的比较,判断是否需要更新最小索引和。如果 i + list1_dict[i] < min_sum,则更新最小索引和,并清空答案数据,添加新的答案。如果 i + list1_dict[i] == min_sum,则更新最小索引和,并添加答案。 - -## 代码 - -```python -class Solution: - def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]: - list1_dict = dict() - len1 = len(list1) - len2 = len(list2) - for i in range(len1): - list1_dict[list1[i]] = i - - min_sum = len1 + len2 - res = [] - for i in range(len2): - if list2[i] in list1_dict: - sum = i + list1_dict[list2[i]] - if sum < min_sum: - res = [list2[i]] - min_sum = sum - elif sum == min_sum: - res.append(list2[i]) - return res -``` - diff --git "a/Solutions/0600. \344\270\215\345\220\253\350\277\236\347\273\2551\347\232\204\351\235\236\350\264\237\346\225\264\346\225\260.md" "b/Solutions/0600. \344\270\215\345\220\253\350\277\236\347\273\2551\347\232\204\351\235\236\350\264\237\346\225\264\346\225\260.md" deleted file mode 100644 index 8e982082..00000000 --- "a/Solutions/0600. \344\270\215\345\220\253\350\277\236\347\273\2551\347\232\204\351\235\236\350\264\237\346\225\264\346\225\260.md" +++ /dev/null @@ -1,104 +0,0 @@ -# [0600. 不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) - -- 标签:动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个正整数 $n$。 - -**要求**:统计在 $[0, n]$ 范围的非负整数中,有多少个整数的二进制表示中不存在连续的 $1$。 - -**说明**: - -- $1 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入: n = 5 -输出: 5 -解释: -下面列出范围在 [0, 5] 的非负整数与其对应的二进制表示: -0 : 0 -1 : 1 -2 : 10 -3 : 11 -4 : 100 -5 : 101 -其中,只有整数 3 违反规则(有两个连续的 1 ),其他 5 个满足规则。 -``` - -- 示例 2: - -```python -输入: n = 1 -输出: 2 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, pre, isLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。其中: - -1. $pos$ 表示当前枚举的数位位置。 -2. $pre$ 表示前一位是否为 $1$,用于过滤连续 $1$ 的不合法方案。 -3. $isLimit$ 表示前一位数位是否等于上界,用于限制本次搜索的数位范围。 - -接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, False, True)` 开始递归。 `dfs(0, False, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 开始时前一位不为 $1$。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,当前为合法方案,此时:直接返回方案数 $1$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 因为不需要考虑前导 $0$,所以当前所能选择的最小数字 $minX$ 为 $0$。 -5. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 -6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 -7. 如果前一位为 $1$ 并且当前为 $d$ 也为 $1$,则说明当前方案出现了连续的 $1$,则跳过。 -8. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, d == 1, isLimit and d == maxX)`。 - 1. `d == 1` 表示下一位 $pos - 1$ 的前一位 $pos$ 是否为 $1$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 -9. 最后的方案数为 `dfs(0, False, True)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def findIntegers(self, n: int) -> int: - # 将 n 的二进制转换为字符串 s - s = str(bin(n))[2:] - - @cache - # pos: 第 pos 个数位 - # pre: 第 pos - 1 位是否为 1 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - def dfs(pos, pre, isLimit): - if pos == len(s): - return 1 - - ans = 0 - # 不需要考虑前导 0,则最小可选择数字为 0 - minX = 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 1。 - maxX = int(s[pos]) if isLimit else 1 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - if pre and d == 1: - continue - ans += dfs(pos + 1, d == 1, isLimit and d == maxX) - - return ans - - return dfs(0, False, True) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 diff --git "a/Solutions/0611. \346\234\211\346\225\210\344\270\211\350\247\222\345\275\242\347\232\204\344\270\252\346\225\260.md" "b/Solutions/0611. \346\234\211\346\225\210\344\270\211\350\247\222\345\275\242\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index 9130eb48..00000000 --- "a/Solutions/0611. \346\234\211\346\225\210\344\270\211\350\247\222\345\275\242\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0611. 有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) - -- 标签:贪心、数组、双指针、二分查找、排序 -- 难度:中等 - -## 题目大意 - -给定一个包含非负整数的数组 `nums`,其中 `nums[i]` 表示第 `i` 条边的边长。 - -要求:统计数组中可以组成三角形三条边的三元组个数。 - -## 解题思路 - -构成三角形的条件为:任意两边和大于第三边,或者任意两边差小于第三边。只要满足这两个条件之一就可以构成三角形。以任意两边和大于第三边为例,如果用 `a`、`b`、`c` 来表示的话,应该同时满足 `a + b > c`、`a + c > b`、`b + c > a`。如果我们将三条边升序排序,假设 `a <= b <= c`,则如果满足 `a + b > c`,则 `a + c > b` 和 `b + c > a` 一定成立。 - -所以我们可以先对 `nums` 进行排序。然后固定最大边 `i`,利用对撞指针 `left`、`right` 查找较小的两条边。然后判断是否构成三角形并统计三元组个数。 - -为了避免重复计算和漏解,要严格保证三条边的序号关系为:`left < right < i`。具体做法如下: - -- 对数组从小到大排序,使用 `ans` 记录三元组个数。 -- 从 `i = 2` 开始遍历数组的每一条边,`i` 作为最大边。 -- 使用双指针 `left`、`right`。`left` 指向 `0`,`right` 指向 `i - 1`。 - - 如果 `nums[left] + nums[right] <= nums[i]`,说明第一条边太短了,可以增加第一条边长度,所以将 `left` 右移,即 `left += 1`。 - - 如果 `nums[left] + nums[right] > nums[i]`,说明可以构成三角形,并且第二条边固定为 `right` 边的话,第一条边可以在 `[left, right - 1]` 中任意选择。所以三元组个数要加上 `right - left`。即 `ans += (right - left)`。 -- 直到 `left == right` 跳出循环,输出三元组个数 `ans`。 - -## 代码 - -```python -class Solution: - def triangleNumber(self, nums: List[int]) -> int: - nums.sort() - size = len(nums) - ans = 0 - - for i in range(2, size): - left = 0 - right = i - 1 - while left < right: - if nums[left] + nums[right] <= nums[i]: - left += 1 - else: - ans += (right - left) - right -= 1 - return ans -``` - diff --git "a/Solutions/0616. \347\273\231\345\255\227\347\254\246\344\270\262\346\267\273\345\212\240\345\212\240\347\262\227\346\240\207\347\255\276.md" "b/Solutions/0616. \347\273\231\345\255\227\347\254\246\344\270\262\346\267\273\345\212\240\345\212\240\347\262\227\346\240\207\347\255\276.md" deleted file mode 100644 index a2a9b5c9..00000000 --- "a/Solutions/0616. \347\273\231\345\255\227\347\254\246\344\270\262\346\267\273\345\212\240\345\212\240\347\262\227\346\240\207\347\255\276.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [0616. 给字符串添加加粗标签](https://leetcode.cn/problems/add-bold-tag-in-string/) - -- 标签:字典树、数组、哈希表、字符串、字符串匹配 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s` 和一个字符串列表 `words`。 - -要求:如果 `s` 的子串在字符串列表 `words` 中出现过,则在该子串前后添加加粗闭合标签 `` 和 ``。如果两个子串有重叠部分,则将它们一起用一对闭合标签包围起来。同理,如果两个子字符串连续被加粗,那么你也需要把它们合起来用一对加粗标签包围。最后返回添加加粗标签后的字符串 `s`。 - -## 解题思路 - -构建字典树,将字符串列表 `words` 中所有字符串添加到字典树中。 - -然后遍历字符串 `s`,从每一个位置开始查询字典树。在第一个符合要求的单词前面添加 ``。在连续符合要求的单词中的最后一个单词后面添加 ``。 - -最后返回添加加粗标签后的字符串 `s`。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - - -class Solution: - def addBoldTag(self, s: str, words: List[str]) -> str: - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - size = len(s) - bold_left, bold_right = -1, -1 - ans = "" - for i in range(size): - cur = trie_tree - if s[i] in cur.children: - bold_left = i - while bold_left < size and s[bold_left] in cur.children: - cur = cur.children[s[bold_left]] - bold_left += 1 - if cur.isEnd: - if bold_right == -1: - ans += "" - bold_right = max(bold_left, bold_right) - if i == bold_right: - ans += "" - bold_right = -1 - ans += s[i] - if bold_right >= 0: - ans += "" - return ans -``` - diff --git "a/Solutions/0617. \345\220\210\345\271\266\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0617. \345\220\210\345\271\266\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 6a038e28..00000000 --- "a/Solutions/0617. \345\220\210\345\271\266\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [0617. 合并二叉树](https://leetcode.cn/problems/merge-two-binary-trees/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定两个二叉树,将两个二叉树合并成一个新的二叉树。合并规则如下: - -- 如果两个二叉树对应节点重叠,则将两个节点的值相加并作为新的二叉树节点。 -- 如果两个二叉树对应节点其中一个为空,另一个不为空,则将不为空的节点左心新的二叉树节点。 - -最终返回新的二叉树的根节点。 - -## 解题思路 - -利用前序遍历二叉树,并按照规则递归建立二叉树。将其对应节点值相加或者取其中不为空的节点做为新节点。 - -## 代码 - -```python -class Solution: - def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: - if not root1: - return root2 - if not root2: - return root1 - - merged = TreeNode(root1.val + root2.val) - merged.left = self.mergeTrees(root1.left, root2.left) - merged.right = self.mergeTrees(root1.right, root2.right) - return merged - -``` - diff --git "a/Solutions/0621. \344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.md" "b/Solutions/0621. \344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.md" deleted file mode 100644 index 73af9abe..00000000 --- "a/Solutions/0621. \344\273\273\345\212\241\350\260\203\345\272\246\345\231\250.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0621. 任务调度器](https://leetcode.cn/problems/task-scheduler/) - -- 标签:贪心、数组、哈希表、计数、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个字符数组 tasks 表示 CPU 需要执行的任务列表。tasks 中每个字母表示一种不同种类的任务。任务可以按任意顺序执行,并且每个任务执行时间为 1 个单位时间。在任何一个单位时间,CPU 可以完成一个任务,或者也可以处于待命状态。 - -但是两个相同种类的任务之间需要 n 个单位时间的冷却时间,所以不能在连续的 n 个单位时间内执行相同的任务。 - -要求计算出完成 tasks 中所有任务所需要的「最短时间」。 - -## 解题思路 - -因为相同种类的任务之间最少需要 n 个单位时间间隔,所以为了最短时间,应该优先考虑任务出现此次最多的任务。 - -先找出出现次数最多的任务,然后中间间隔的单位来安排别的任务,或者处于待命状态。 - -然后将第二出现次数最多的任务,按照 n 个时间间隔安排起来。如果第二出现次数最多的任务跟第一出现次数最多的任务出现次数相同,则最短时间就会加一。 - -最后我们会发现:最短时间跟出现次数最多的任务正相关。 - -假设出现次数最多的任务为 "A"。与 "A" 出现次数相同的任务数为 count。则: - -- `最短时间 = (A 出现次数 - 1)* (n + 1)+ count`。 - -最后还应该比较一下总的任务个数跟计算出的最短时间答案。如果最短时间比总的任务个数还少,说明间隔中放不下所有的任务,会有任务「溢出」。则应该将多余任务插入间隔中,则答案应为总的任务个数。 - -## 代码 - -```python -class Solution: - def leastInterval(self, tasks: List[str], n: int) -> int: - # 记录每个任务出现的次数 - tasks_counts = [0 for _ in range(26)] - for i in range(len(tasks)): - num = ord(tasks[i]) - ord('A') - tasks_counts[num] += 1 - max_task_count = max(tasks_counts) - # 统计多少个出现最多次的任务 - count = 0 - for task_count in tasks_counts: - if task_count == max_task_count: - count += 1 - - # 如果结果比任务数量少,则返回总任务数 - return max((max_task_count - 1) * (n + 1) + count, len(tasks)) -``` - diff --git "a/Solutions/0622. \350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.md" "b/Solutions/0622. \350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.md" deleted file mode 100644 index dae2d590..00000000 --- "a/Solutions/0622. \350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.md" +++ /dev/null @@ -1,114 +0,0 @@ -# [0622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) - -- 标签:设计、队列、数组、链表 -- 难度:中等 - -## 题目大意 - -**要求**:设计实现一个循环队列,支持以下操作: - -- `MyCircularQueue(k)`: 构造器,设置队列长度为 `k`。 -- `Front`: 从队首获取元素。如果队列为空,返回 `-1`。 -- `Rear`: 获取队尾元素。如果队列为空,返回 `-1`。 -- `enQueue(value)`: 向循环队列插入一个元素。如果成功插入则返回真。 -- `deQueue()`: 从循环队列中删除一个元素。如果成功删除则返回真。 -- `isEmpty()`: 检查循环队列是否为空。 -- `isFull()`: 检查循环队列是否已满。 - -**说明**: - -- 所有的值都在 `0` 至 `1000` 的范围内。 -- 操作数将在 `1` 至 `1000` 的范围内。 -- 请不要使用内置的队列库。 - -**示例**: - -- 示例 1: - -```python -MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3 -circularQueue.enQueue(1);  // 返回 true -circularQueue.enQueue(2);  // 返回 true -circularQueue.enQueue(3);  // 返回 true -circularQueue.enQueue(4);  // 返回 false,队列已满 -circularQueue.Rear();  // 返回 3 -circularQueue.isFull();  // 返回 true -circularQueue.deQueue();  // 返回 true -circularQueue.enQueue(4);  // 返回 true -circularQueue.Rear();  // 返回 4 -``` - -## 解题思路 - -这道题可以使用数组,也可以使用链表来实现循环队列。 - -### 思路 1:使用数组模拟 - -建立一个容量为 `k + 1` 的数组 `queue`。并保存队头指针 `front`、队尾指针 `rear`,队列容量 `capacity` 为 `k + 1`(这里之所以用了 `k + 1` 的容量,是为了判断空和满,需要空出一个)。 - -然后实现循环队列的各个接口: - -1. `MyCircularQueue(k)`: - 1. 将数组 `queue` 初始化大小为 `k + 1` 的数组。 - 2. `front`、`rear` 初始化为 `0`。 -2. `Front`: - 1. 先检测队列是否为空。如果队列为空,返回 `-1`。 - 2. 如果不为空,则返回队头元素。 -3. `Rear`: - 1. 先检测队列是否为空。如果队列为空,返回 `-1`。 - 2. 如果不为空,则返回队尾元素。 -4. `enQueue(value)`: - 1. 如果队列已满,则无法插入,返回 `False`。 - 2. 如果队列未满,则将队尾指针 `rear` 向右循环移动一位,并进行插入操作。然后返回 `True`。 -5. `deQueue()`: - 1. 如果队列为空,则无法删除,返回 `False`。 - 2. 如果队列不空,则将队头指针 `front` 指向元素赋值为 `None`,并将 `front` 向右循环移动一位。然后返回 `True`。 -6. `isEmpty()`: 如果 `rear` 等于 `front`,则说明队列为空,返回 `True`。否则,队列不为空,返回 `False`。 -7. `isFull()`: 如果 `(rear + 1) % capacity` 等于 `front`,则说明队列已满,返回 `True`。否则,队列未满,返回 `False`。 - -### 思路 1:代码 - -```python -class MyCircularQueue: - - def __init__(self, k: int): - self.capacity = k + 1 - self.queue = [0 for _ in range(k + 1)] - self.front = 0 - self.rear = 0 - - def enQueue(self, value: int) -> bool: - if self.isFull(): - return False - self.rear = (self.rear + 1) % self.capacity - self.queue[self.rear] = value - return True - - def deQueue(self) -> bool: - if self.isEmpty(): - return False - self.front = (self.front + 1) % self.capacity - return True - - def Front(self) -> int: - if self.isEmpty(): - return -1 - return self.queue[(self.front + 1) % self.capacity] - - def Rear(self) -> int: - if self.isEmpty(): - return -1 - return self.queue[self.rear] - - def isEmpty(self) -> bool: - return self.front == self.rear - - def isFull(self) -> bool: - return (self.rear + 1) % self.capacity == self.front -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。初始化和每项操作的时间复杂度均为 $O(1)$。 -- **空间复杂度**:$O(k)$。其中 $k$ 为给定队列的元素数目。 - diff --git "a/Solutions/0633. \345\271\263\346\226\271\346\225\260\344\271\213\345\222\214.md" "b/Solutions/0633. \345\271\263\346\226\271\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index 01f7954d..00000000 --- "a/Solutions/0633. \345\271\263\346\226\271\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [0633. 平方数之和](https://leetcode.cn/problems/sum-of-square-numbers/) - -- 标签:数学、双指针、二分查找 -- 难度:中等 - -## 题目大意 - -给定一个非负整数 c,判断是否存在两个整数 a 和 b,使得 $a^2 + b^2 = c$,如果存在则返回 True,不存在返回 False。 - -## 解题思路 - -最直接的办法就是枚举 a、b 所有可能。这样遍历下来的时间复杂度为 $O(c^2)$。但是没必要进行二重遍历。可以只遍历 a,然后去判断 $\sqrt{c - b^2}$ 是否为整数,并且 a 只需遍历到 $\sqrt{c}$ 即可,时间复杂度为 $O(\sqrt{c})$。 - -另一种方法是双指针。定义两个指针 left,right 分别指向 0 和 $\sqrt{c}$。判断 $left^2 + right^2$ 与 c 之间的关系。 - -- 如果 $a^2 + b^2 == c$,则返回 True。 -- 如果 $a^2 + b^2 < c$,则将 a 值加一,继续查找。 -- 如果 $a^2 + b^2 > c$,则将 b 值减一,继续查找。 -- 当 $a == b$ 时,结束查找。如果此时仍没有找到满足 $a^2 + b^2 == c$ 的 a、b 值,则返回 False。 - -## 代码 - -```python -class Solution: - def judgeSquareSum(self, c: int) -> bool: - a, b = 0, int(c ** 0.5) - while a <= b: - sum = a*a + b*b - if sum == c: - return True - elif sum < c: - a += 1 - else: - b -= 1 - return False -``` - diff --git "a/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" "b/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" deleted file mode 100644 index 7ebd61e3..00000000 --- "a/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [0639. 解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) - -- 标签:字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个包含数字和字符 `'*'` 的字符串 `s`。该字符串已经按照下面的映射关系进行了编码: - -- `A` 映射为 `1`。 -- `B` 映射为 `2`。 -- ... -- `Z` 映射为 `26`。 - -除了上述映射方法,字符串 `s` 中可能包含字符 `'*'`,可以表示 `1` ~ `9` 的任一数字(不包括 `0`)。例如字符串 `"1*"` 可以表示为 `"11"`、`"12"`、…、`"18"`、`"19"` 中的任何一个编码。 - -基于上述映射的方法,现在对字符串 `s` 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: - -- `"AAJF"`,将消息分组为 `(1 1 10 6)`。 -- `"KJF"`,将消息分组为 `(11 10 6)`。 - -**要求**:计算出共有多少种可能的解码方案。 - -**说明**: - -- $1 \le s.length \le 100$。 -- `s` 只包含数字,并且可能包含前导零。 -- 题目数据保证答案肯定是一个 `32` 位的整数。 - -```python -输入:s = "*" -输出:9 -解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。因此,"*" 总共有 9 种解码方法。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -这道题是「[91. 解码方法 - 力扣](https://leetcode.cn/problems/decode-ways/)」的升级版,其思路是相似的,只不过本题的状态转移方程的条件和公式不太容易想全。 - -###### 1. 划分阶段 - -按照字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。 - -###### 3. 状态转移方程 - -`dp[i]` 的来源有两种情况: - -1. 使用了一个字符,对 `s[i]` 进行翻译: - 1. 如果 `s[i] == '*'`,则 `s[i]` 可以视作区间 `[1, 9]` 上的任意一个数字,可以被翻译为 `A` ~ `I`。此时当前位置上的方案数为 `9`,即 `dp[i] = dp[i - 1] * 9`。 - 2. 如果 `s[i] == '0'`,则无法被翻译,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 1] * 0`。 - 3. 如果是其他情况(即 `s[i]` 是区间 `[1, 9]` 上某一个数字),可以被翻译为 `A` ~ `I` 对应位置上的某个字母。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 1] * 1`。 - -2. 使用了两个字符,对 `s[i - 1]` 和 `s[i]` 进行翻译: - 1. 如果 `s[i - 1] == '*'` 并且 `s[i] == '*'`,则 `s[i]` 可以视作区间 `[11, 19]` 或者 `[21, 26]` 上的任意一个数字。此时当前位置上的方案数为 `15`,即 `dp[i] = dp[i - 2] * 15`。 - 2. 如果 `s[i - 1] == '*'` 并且 `s[i] != '*'`,则: - 1. 如果 `s[i]` 在区间 `[1, 6]` 内,`s[i - 1]` 可以选择 `1` 或 `2`。此时当前位置上的方案数为 `2`,即 `dp[i] = dp[i - 2] * 2`。 - 2. 如果 `s[i]` 不在区间 `[1, 6]` 内,`s[i - 1]` 只能选择 `1`。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 - - 3. 如果 `s[i - 1] == '1'` 并且 `s[i] == '*'`,`s[i]` 可以视作区间 `[1, 9]` 上任意一个数字。此时当前位置上的方案数为 `9`,即 `dp[i] = dp[i - 2] * 9`。 - 4. 如果 `s[i - 1] == '1'` 并且 `s[i] != '*'`,`s[i]` 可以视作区间 `[1, 9]` 上的某一个数字。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 - 5. 如果 `s[i - 1] == '2'` 并且 `s[i] == '*'`,`s[i]` 可以视作区间 `[1, 6]` 上任意一个数字。此时当前位置上的方案数为 `6`,即 `dp[i] = dp[i - 2] * 6`。 - 6. 如果 `s[i - 1] == '2'` 并且 `s[i] != '*'`,则: - 1. 如果 `s[i]` 在区间 `[1, 6]` 内,此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 - 2. 如果 `s[i]` 不在区间 `[1, 6]` 内,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 2] * 0`。 - - 7. 其他情况下(即 `s[i - 1]` 在区间 `[3, 9]` 内),则无法被翻译,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 2] * 0`。 - - -在进行转移的时候,需要将使用一个字符的翻译方案数与使用两个字符的翻译方案数进行相加。同时还要注意对 $10^9 + 7$ 的取余。 - -这里我们可以单独写两个方法 `,分别来表示「单个字符 `s[i]` 的翻译方案数」和「两个字符 `s[i - 1]` 和 `s[i]` 的翻译方案数」,这样代码逻辑会更加清晰。 - -###### 4. 初始条件 - -- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 `dp[0] = 1`。 -- 字符串只有一个字符时,单个字符 `s[i]` 的翻译方案数为转移条件的第一种求法,即`dp[1] = self.parse1(s[0])`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。则最终结果为 `dp[size]`,`size` 为字符串长度。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def parse1(self, ch): - if ch == '*': - return 9 - if ch == '0': - return 0 - return 1 - - def parse2(self, ch1, ch2): - if ch1 == '*' and ch2 == '*': - return 15 - if ch1 == '*' and ch2 != '*': - return 2 if ch2 <= '6' else 1 - - if ch1 == '1' and ch2 == '*': - return 9 - if ch1 == '1' and ch2 != '*': - return 1 - - if ch1 == '2' and ch2 == '*': - return 6 - if ch1 == '2' and ch2 != '*': - return 1 if ch2 <= '6' else 0 - - return 0 - - def numDecodings(self, s: str) -> int: - mod = 10 ** 9 + 7 - size = len(s) - - dp = [0 for _ in range(size + 1)] - dp[0] = 1 - dp[1] = self.parse1(s[0]) - - for i in range(2, size + 1): - dp[i] += dp[i - 1] * self.parse1(s[i - 1]) - dp[i] += dp[i - 2] * self.parse2(s[i - 2], s[i - 1]) - dp[i] %= mod - - return dp[size] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 \ No newline at end of file diff --git "a/Solutions/0642. \350\256\276\350\256\241\346\220\234\347\264\242\350\207\252\345\212\250\350\241\245\345\205\250\347\263\273\347\273\237.md" "b/Solutions/0642. \350\256\276\350\256\241\346\220\234\347\264\242\350\207\252\345\212\250\350\241\245\345\205\250\347\263\273\347\273\237.md" deleted file mode 100644 index cc64cafd..00000000 --- "a/Solutions/0642. \350\256\276\350\256\241\346\220\234\347\264\242\350\207\252\345\212\250\350\241\245\345\205\250\347\263\273\347\273\237.md" +++ /dev/null @@ -1,125 +0,0 @@ -# [0642. 设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) - -- 标签:设计、字典树、字符串、数据流 -- 难度:困难 - -## 题目大意 - -要求:设计一个搜索自动补全系统。用户会输入一条语句(最少包含一个字母,以特殊字符 `#` 结尾)。除 `#` 以外用户输入的每个字符,返回历史中热度前三并以当前输入部分为前缀的句子。下面是详细规则: - -- 一条句子的热度定义为历史上用户输入这个句子的总次数。 -- 返回前三的句子需要按照热度从高到低排序(第一个是最热门的)。如果有多条热度相同的句子,请按照 ASCII 码的顺序输出(ASCII 码越小排名越前)。 -- 如果满足条件的句子个数少于 3,将它们全部输出。 -- 如果输入了特殊字符,意味着句子结束了,请返回一个空集合。 - -你的工作是实现以下功能: - -- 构造函数: `AutocompleteSystem(String[] sentences, int[] times):` - - 输入历史数据。 `sentences` 是之前输入过的所有句子,`times` 是每条句子输入的次数,你的系统需要记录这些历史信息。 - -- 输入函数(用户输入一条新的句子,下面的函数会提供用户输入的下一个字符):`List input(char c):` - - 其中 `c` 是用户输入的下一个字符。字符只会是小写英文字母(`a` 到 `z` ),空格(` `)和特殊字符(`#`)。输出历史热度前三的具有相同前缀的句子。 - -## 解题思路 - -使用字典树来保存输入过的所有句子 `sentences`,并且在字典树中维护每条句子的输入次数 `times`。 - -构造函数中: - -- 将所有句子及对应输入次数插入到字典树中。 - -输入函数中: - -- 使用 `path` 变量保存当前输入句子的前缀。 -- 如果遇到 `#`,则将当前句子插入到字典树中。 -- 如果遇到其他字符,用 `path` 保存当前字符 `c`。并在字典树中搜索以 `path` 为前缀的节点的所有分支,将每个分支对应的单词 `path` 和它们出现的次数 `times` 存入数组中。然后借助 `heapq` 进行堆排序,根据出现次数和 ASCII 码大小排序,找出 `times` 最多的前三个单词。 - -## 代码 - -```python -import heapq - -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.times = 0 - - - def insert(self, word: str, times=1) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.times += times - - - def search(self, word: str): - """ - Returns if the word is in the trie. - """ - cur = self - - for ch in word: - if ch not in cur.children: - return [] - cur = cur.children[ch] - - res = [] - path = [word] - cur.dfs(res, path) - return res - - - def dfs(self, res, path): - cur = self - if cur.isEnd: - res.append((-cur.times, ''.join(path))) - for ch in cur.children: - node = cur.children[ch] - path.append(ch) - node.dfs(res, path) - path.pop() - - -class AutocompleteSystem: - - def __init__(self, sentences: List[str], times: List[int]): - self.path = '' - self.exists = True - self.trie_tree = Trie() - for i in range(len(sentences)): - self.trie_tree.insert(sentences[i], times[i]) - - - def input(self, c: str) -> List[str]: - if c == '#': - self.trie_tree.insert(self.path, 1) - self.path = '' - self.exists = True - return [] - else: - self.path += c - if not self.exists: - return [] - words = self.trie_tree.search(self.path) - if words: - heapq.heapify(words) - res = [] - while words and len(res) < 3: - res.append(heapq.heappop(words)[1]) - return res - else: - self.exists = False - return [] -``` - diff --git "a/Solutions/0643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I.md" "b/Solutions/0643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I.md" deleted file mode 100644 index ff842a2d..00000000 --- "a/Solutions/0643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [0643. 子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) - -- 标签:数组、滑动窗口 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个由 $n$ 个元素组成的整数数组 $nums$ 和一个整数 $k$。 - -**要求**:找出平均数最大且长度为 $k$ 的连续子数组,并输出该最大平均数。 - -**说明**: - -- 任何误差小于 $10^{-5}$ 的答案都将被视为正确答案。 -- $n == nums.length$。 -- $1 \le k \le n \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,12,-5,-6,50,3], k = 4 -输出:12.75 -解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75 -``` - -- 示例 2: - -```python -输入:nums = [5], k = 1 -输出:5.00000 -``` - -## 解题思路 - -### 思路 1:滑动窗口(固定长度) - -这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 $k$。具体做法如下: - -1. $ans$ 用来维护子数组最大平均数,初始值为负无穷,即 `float('-inf')`。$window\underline{}total$ 用来维护窗口中元素的和。 -2. $left$ 、$right$ 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 向右移动 $right$,先将 $k$ 个元素填入窗口中。 -4. 当窗口元素个数为 $k$ 时,即:$right - left + 1 >= k$ 时,计算窗口内的元素和平均值,并维护子数组最大平均数。 -5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 -6. 重复 $4 \sim 5$ 步,直到 $right$ 到达数组末尾。 -7. 最后输出答案 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def findMaxAverage(self, nums: List[int], k: int) -> float: - left = 0 - right = 0 - window_total = 0 - ans = float('-inf') - while right < len(nums): - window_total += nums[right] - - if right - left + 1 >= k: - ans = max(window_total / k, ans) - window_total -= nums[left] - left += 1 - - # 向右侧增大窗口 - right += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为数组 $nums$ 的元素个数。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0647. \345\233\236\346\226\207\345\255\220\344\270\262.md" "b/Solutions/0647. \345\233\236\346\226\207\345\255\220\344\270\262.md" deleted file mode 100644 index b83ebd0b..00000000 --- "a/Solutions/0647. \345\233\236\346\226\207\345\255\220\344\270\262.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [0647. 回文子串](https://leetcode.cn/problems/palindromic-substrings/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`,计算 `s` 中有多少个回文子串。 - -## 解题思路 - -动态规划求解。 - -先定义状态 `dp[i][j]` 表示为区间 `[i, j]` 的子串是否为回文子串,如果是,则 `dp[i][j] = True`,如果不是,则 `dp[i][j] = False`。 - -接下来确定状态转移共识: - -如果 `s[i] == s[j]`,分为以下几种情况: - -- `i == j`,单字符肯定是回文子串,`dp[i][j] == True`。 -- `j - i == 1`,比如 `aa` 肯定也是回文子串,`dp[i][j] = True`。 -- 如果 `j - i > 1`,则需要看 `[i + 1, j - 1]` 区间是不是回文子串,`dp[i][j] = dp[i + 1][j - 1]`。 - -如果 `s[i] != s[j]`,那肯定不是回文子串,`dp[i][j] = False`。 - -下一步确定遍历方向。 - -由于 `dp[i][j]` 依赖于 `dp[i + 1][j - 1]`,所以我们可以从左下角向右上角遍历。 - -同时,在递推过程中记录下 `dp[i][j] == True` 的个数,即为最后结果。 - -## 代码 - -```python -class Solution: - def countSubstrings(self, s: str) -> int: - size = len(s) - dp = [[False for _ in range(size)] for _ in range(size)] - res = 0 - for i in range(size - 1, -1, -1): - for j in range(i, size): - if s[i] == s[j]: - if j - i <= 1: - dp[i][j] = True - else: - dp[i][j] = dp[i + 1][j - 1] - else: - dp[i][j] = False - if dp[i][j]: - res += 1 - return res -``` - diff --git "a/Solutions/0648. \345\215\225\350\257\215\346\233\277\346\215\242.md" "b/Solutions/0648. \345\215\225\350\257\215\346\233\277\346\215\242.md" deleted file mode 100644 index ac1ed6fa..00000000 --- "a/Solutions/0648. \345\215\225\350\257\215\346\233\277\346\215\242.md" +++ /dev/null @@ -1,106 +0,0 @@ -# [0648. 单词替换](https://leetcode.cn/problems/replace-words/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由许多词根组成的字典列表 `dictionary`,以及一个句子字符串 `sentence`。 - -**要求**:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。 - -**说明**: - -- $1 \le dictionary.length \le 1000$。 -- $1 \le dictionary[i].length \le 100$。 -- `dictionary[i]` 仅由小写字母组成。 -- $1 \le sentence.length \le 10^6$。 -- `sentence` 仅由小写字母和空格组成。 -- `sentence` 中单词的总量在范围 $[1, 1000]$ 内。 -- `sentence` 中每个单词的长度在范围 $[1, 1000]$ 内。 -- `sentence` 中单词之间由一个空格隔开。 -- `sentence` 没有前导或尾随空格。 - -**示例**: - -- 示例 1: - -```python -输入:dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" -输出:"the cat was rat by the bat" -``` - -- 示例 2: - -```python -输入:dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" -输出:"a a b c" -``` - -## 解题思路 - -### 思路 1:字典树 - -1. 构造一棵字典树。 -2. 将所有的词根存入到前缀树(字典树)中。 -3. 然后在树上查找每个单词的最短词根。 - -### 思路 1:代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> str: - """ - Returns if the word is in the trie. - """ - cur = self - index = 0 - for ch in word: - if ch not in cur.children: - return word - cur = cur.children[ch] - index += 1 - if cur.isEnd: - break - return word[:index] - - -class Solution: - def replaceWords(self, dictionary: List[str], sentence: str) -> str: - trie_tree = Trie() - for word in dictionary: - trie_tree.insert(word) - - words = sentence.split(" ") - size = len(words) - for i in range(size): - word = words[i] - words[i] = trie_tree.search(word) - return ' '.join(words) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(|dictionary| + |sentence|)$。其中 $|dictionary|$ 是字符串数组 `dictionary` 中的字符总数,$|sentence|$ 是字符串 `sentence` 的字符总数。 -- **空间复杂度**:$O(|dictionary| + |sentence|)$。 \ No newline at end of file diff --git "a/Solutions/0650. \345\217\252\346\234\211\344\270\244\344\270\252\351\224\256\347\232\204\351\224\256\347\233\230.md" "b/Solutions/0650. \345\217\252\346\234\211\344\270\244\344\270\252\351\224\256\347\232\204\351\224\256\347\233\230.md" deleted file mode 100644 index d3641158..00000000 --- "a/Solutions/0650. \345\217\252\346\234\211\344\270\244\344\270\252\351\224\256\347\232\204\351\224\256\347\233\230.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0650. 只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) - -- 标签:数学、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:最初记事本上只有一个字符 `'A'`。你每次可以对这个记事本进行两种操作: - -- **Copy All(复制全部)**:复制这个记事本中的所有字符(不允许仅复制部分字符)。 -- **Paste(粘贴)**:粘贴上一次复制的字符。 - -现在,给定一个数字 $n$,需要使用最少的操作次数,在记事本上输出恰好 $n$ 个 `'A'` 。 - -**要求**:返回能够打印出 $n$ 个 `'A'` 的最少操作次数。 - -**说明**: - -- $1 \le n \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:3 -输出:3 -解释 -最初, 只有一个字符 'A'。 -第 1 步, 使用 Copy All 操作。 -第 2 步, 使用 Paste 操作来获得 'AA'。 -第 3 步, 使用 Paste 操作来获得 'AAA'。 -``` - -- 示例 2: - -```python -输入:n = 1 -输出:0 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照字符 `'A'` 的个数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 - -###### 3. 状态转移方程 - -1. 对于 $i$ 个字符 `'A'`,如果 $i$ 可以被一个小于 $i$ 的整数 $j$ 除尽($j$ 是 $i$ 的因子),则说明 $j$ 个字符 `'A'` 可以通过「复制」+「粘贴」总共 $\frac{i}{j}$ 次得到 $i$ 个字符 `'A'`。 -2. 而得到 $j$ 个字符 `'A'`,最少需要的操作数可以通过 $dp[j]$ 获取。 - -则我们可以枚举 $i$ 的因子,从中找到在满足 $j$ 能够整除 $i$ 的条件下,最小的 $dp[j] + \frac{i}{j}$,即为 $dp[i]$,即 $dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j})$。 - -由于 $j$ 能够整除 $i$,则 $j$ 与 $\frac{i}{j}$ 都是 $i$ 的因子,两者中必有一个因子是小于等于 $\sqrt{i}$ 的,所以在枚举 $i$ 的因子时,我们只需要枚举区间 $[1, \sqrt{i}]$ 即可。 - -综上所述,状态转移方程为:$dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j}, dp[\frac{i}{j}] + j)$。 - -###### 4. 初始条件 - -- 当 $i$ 为 $1$ 时,最少需要的操作数为 $0$。所以 $dp[1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 所以最终结果为 $dp[n]$。 - -### 思路 1:动态规划代码 - -```python -import math - -class Solution: - def minSteps(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - for i in range(2, n + 1): - dp[i] = float('inf') - for j in range(1, int(math.sqrt(n)) + 1): - if i % j == 0: - dp[i] = min(dp[i], dp[j] + i // j, dp[i // j] + j) - - return dp[n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \sqrt{n})$。外层循环遍历的时间复杂度是 $O(n)$,内层循环遍历的时间复杂度是 $O(\sqrt{n})$,所以总体时间复杂度为 $O(n \sqrt{n})$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 diff --git "a/Solutions/0652. \345\257\273\346\211\276\351\207\215\345\244\215\347\232\204\345\255\220\346\240\221.md" "b/Solutions/0652. \345\257\273\346\211\276\351\207\215\345\244\215\347\232\204\345\255\220\346\240\221.md" deleted file mode 100644 index 30e2f0ff..00000000 --- "a/Solutions/0652. \345\257\273\346\211\276\351\207\215\345\244\215\347\232\204\345\255\220\346\240\221.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [0652. 寻找重复的子树](https://leetcode.cn/problems/find-duplicate-subtrees/) - -- 标签:树、深度优先搜索、哈希表、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树,返回所有重复的子树。对于重复的子树,只需返回其中任意一棵的根节点。 - -## 解题思路 - -对二叉树进行先序遍历,对遍历的所有的子树进行序列化处理,将序列化处理后的字符串作为哈希表的键,记录每棵子树出现的次数。 - -当出现第二次时,则说明该子树是重复的子树,将其加入答案数组。最后返回答案数组即可。 - -## 代码 - -```python -class Solution: - def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]: - tree_dict = dict() - res = [] - def preorder(node): - if not node: - return '#' - sub_tree = str(node.val) + ',' + preorder(node.left) + ',' + preorder(node.right) - if sub_tree in tree_dict: - tree_dict[sub_tree] += 1 - else: - tree_dict[sub_tree] = 1 - if tree_dict[sub_tree] == 2: - res.append(node) - return sub_tree - preorder(root) - return res -``` - diff --git "a/Solutions/0653. \344\270\244\346\225\260\344\271\213\345\222\214 IV - \350\276\223\345\205\245\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0653. \344\270\244\346\225\260\344\271\213\345\222\214 IV - \350\276\223\345\205\245\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index bdf2d17b..00000000 --- "a/Solutions/0653. \344\270\244\346\225\260\344\271\213\345\222\214 IV - \350\276\223\345\205\245\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [0653. 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉搜索树的根节点 `root` 和一个整数 `k`。 - -要求:判断该二叉搜索树是否存在两个节点值的和等于 `k`。如果存在,则返回 `True`,不存在则返回 `False`。 - -## 解题思路 - -二叉搜索树中序遍历的结果是从小到大排序,所以我们可以先对二叉搜索树进行中序遍历,将中序遍历结果存储到列表中。再使用左右指针查找节点值和为 `k` 的两个节点。 - -## 代码 - -```python -class Solution: - def inOrder(self, root, nums): - if not root: - return - self.inOrder(root.left, nums) - nums.append(root.val) - self.inOrder(root.right, nums) - - def findTarget(self, root: TreeNode, k: int) -> bool: - nums = [] - self.inOrder(root, nums) - left, right = 0, len(nums) - 1 - while left < right: - sum = nums[left] + nums[right] - if sum == k: - return True - elif sum < k: - left += 1 - else: - right -= 1 - return False -``` - diff --git "a/Solutions/0654. \346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0654. \346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 0ece737f..00000000 --- "a/Solutions/0654. \346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [0654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) - -- 标签:栈、树、数组、分治、二叉树、单调栈 -- 难度:中等 - -## 题目大意 - -给定一个不含重复元素的整数数组 `nums`。一个以此数组构建的最大二叉树定义如下: - -- 二叉树的根是数组中的最大元素。 -- 左子树是通过数组中最大值左边部分构造出的最大二叉树。 -- 右子树是通过数组中最大值右边部分构造出的最大二叉树。 - -要求通过给定的数组构建最大二叉树,并且输出这个树的根节点。 - -## 解题思路 - -根据题意可知,数组中最大元素位置为根节点,最大元素位置左右部分可分别作为左右子树。则我们可以通过递归的方式构建最大二叉树。 - -- 定义 left、right 分别表示当前数组的左右边界位置,定义 `max_value_index` 为当前数组中最大值位置。 -- 遍历当前数组,找到最大值位置 `max_value_index`,并建立根节点 `root`,将数组 `nums` 分为 `[left, max_value_index]` 和 `[max_value_index, right]` 两部分,并分别递归建树。 -- 将其赋值给 `root` 的左右子节点,最后返回 root 节点。 - -## 代码 - -```python -class Solution: - def createBinaryTree(self, nums: List[int], left: int, right: int) -> TreeNode: - if left >= right: - return None - max_value_index = left - for i in range(left + 1, right): - if nums[i] > nums[max_value_index]: - max_value_index = i - - root = TreeNode(nums[max_value_index]) - root.left = self.createBinaryTree(nums, left, max_value_index) - root.right = self.createBinaryTree(nums, max_value_index + 1, right) - - return root - - def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: - return self.createBinaryTree(nums, 0, len(nums)) -``` - diff --git "a/Solutions/0658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" "b/Solutions/0658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" deleted file mode 100644 index 39afcba5..00000000 --- "a/Solutions/0658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [0658. 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) - -- 标签:数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个有序数组 arr,以及两个整数 k、x。从数组中找到最靠近 x(两数之差最小)的 k 个数。返回包含这 k 个数的有序数组。 - -## 解题思路 - -数组的区间为 [0, n-1],查找的子区间长度为 k。我们可以通过查找子区间左端点位置,从而确定子区间。 - -查找子区间左端点可以通过二分查找来降低复杂度。 - -因为子区间为 k,所以左端点最多取到 n-k 的位置。 - -设定两个指针 left,right。left 指向 0,right 指向 n-k。 - -每次取 left 和 right 中间位置,判断 x 与左右边界的差值。x 与左边的差值为 x - arr[mid],x 与右边界的差值为 arr[mid + k] - x。 - -- 如果 x 与左边界的差值 > x 与右边界的差值,即 x - arr[mid] > arr[mid + k] - x,将 left 右移,left = mid + 1,从右侧继续查找。 -- 如果 x 与左边界的差值 <= x 与右边界的差值, 即 x - arr[mid] <= arr[mid + k] - x,则将 right 向左侧靠拢,right = mid,从左侧继续查找。 - -最后返回 arr[left, left + k] 即可。 - -## 代码 - -```python -class Solution: - def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: - n = len(arr) - left = 0 - right = n - k - while left < right: - mid = left + (right - left) // 2 - if x - arr[mid] > arr[mid + k] - x: - left = mid + 1 - else: - right = mid - return arr[left: left + k] -``` - diff --git "a/Solutions/0662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246.md" "b/Solutions/0662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246.md" deleted file mode 100644 index 680711f1..00000000 --- "a/Solutions/0662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给你一棵二叉树的根节点 `root`。 - -**要求**:返回树的最大宽度。 - -**说明**: - -- **每一层的宽度**:为该层最左和最右的非空节点(即两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 `null` 节点,这些 `null` 节点也计入长度。 -- **树的最大宽度**:是所有层中最大的宽度。 -- 题目数据保证答案将会在 32 位带符号整数范围内。 -- 树中节点的数目范围是 $[1, 3000]$。 -- $-100 \le Node.val \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/05/03/width1-tree.jpg) - -```python -输入:root = [1,3,2,5,3,null,9] -输出:4 -解释:最大宽度出现在树的第 3 层,宽度为 4 (5,3,null,9)。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2022/03/14/maximum-width-of-binary-tree-v3.jpg) - -```python -输入:root = [1,3,2,5,null,null,9,6,null,7] -输出:7 -解释:最大宽度出现在树的第 4 层,宽度为 7 (6,null,null,null,null,null,7) 。 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -最直观的做法是,求出每一层的宽度,然后求出所有层高度的最大值。 - -在计算每一层宽度时,根据题意,两端点之间的 `null` 节点也计入长度,所以我们可以对包括 `null` 节点在内的该二叉树的所有节点进行编号。 - -也就是满二叉树的编号规则:如果当前节点的编号为 $i$,则左子节点编号记为 $i \times 2 + 1$,则右子节点编号为 $i \times 2 + 2$。 - -接下来我们使用广度优先搜索方法遍历每一层的节点,在向队列中添加节点时,将该节点与该节点对应的编号一同存入队列中。 - -这样在计算每一层节点的宽度时,我们可以通过队列中队尾节点的编号与队头节点的编号,快速计算出当前层的宽度。并计算出所有层宽度的最大值。 - -### 思路 1:代码 - -```python -class Solution: - def widthOfBinaryTree(self, root: Optional[TreeNode]) -> int: - if not root: - return False - - queue = collections.deque([[root, 0]]) - ans = 0 - while queue: - ans = max(ans, queue[-1][1] - queue[0][1] + 1) - size = len(queue) - for _ in range(size): - cur, index = queue.popleft() - if cur.left: - queue.append([cur.left, index * 2 + 1]) - if cur.right: - queue.append([cur.right, index * 2 + 2]) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272.md" "b/Solutions/0664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272.md" deleted file mode 100644 index 86a5bdfc..00000000 --- "a/Solutions/0664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0664. 奇怪的打印机](https://leetcode.cn/problems/strange-printer/) - -- 标签:字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:有一台奇怪的打印机,有以下两个功能: - -1. 打印机每次只能打印由同一个字符组成的序列,比如:`"aaaa"`、`"bbb"`。 -2. 每次可以从起始位置到结束的任意为止打印新字符,并且会覆盖掉原有字符。 - -现在给定一个字符串 $s$。 - -**要求**:计算这个打印机打印出字符串 $s$ 需要的最少打印次数。 - -**说明**: - -- $1 \le s.length \le 100$。 -- $s$ 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "aaabbb" -输出:2 -解释:首先打印 "aaa" 然后打印 "bbb"。 -``` - -- 示例 2: - -```python -输入:s = "aba" -输出:2 -解释:首先打印 "aaa" 然后在第二个位置打印 "b" 覆盖掉原来的字符 'a'。 -``` - -## 解题思路 - -对于字符串 $s$,我们可以先考虑区间 $[i, j]$ 上的子字符串需要的最少打印次数。 - -1. 如果区间 $[i, j]$ 内只有 $1$ 种字符,则最少打印次数为 $1$,即:$dp[i][i] = 1$。 -2. 如果区间 $[i, j]$ 内首尾字符相同,即 $s[i] == s[j]$,则我们在打印 $s[i]$ 的同时我们可以顺便打印 $s[j]$,这样我们可以忽略 $s[j]$,只考虑剩下区间 $[i, j - 1]$ 的打印情况,即:$dp[i][j] = dp[i][j - 1]$。 -3. 如果区间 $[i, j]$ 上首尾字符不同,即 $s[i] \ne s[j]$,则枚举分割点 $k$,将区间 $[i, j]$ 分为区间 $[i, k]$ 与区间 $[k + 1, j]$,使得 $dp[i][k] + dp[k + 1][j]$ 的值最小即为 $dp[i][j]$。 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:打印第 $i$ 个字符到第 $j$ 个字符需要的最少打印次数。 - -###### 3. 状态转移方程 - -1. 如果 $s[i] == s[j]$,则我们在打印 $s[i]$ 的同时我们可以顺便打印 $s[j]$,这样我们可以忽略 $s[j]$,只考虑剩下区间 $[i, j - 1]$ 的打印情况,即:$dp[i][j] = dp[i][j - 1]$。 -2. 如果 $s[i] \ne s[j]$,则枚举分割点 $k$,将区间 $[i, j]$ 分为区间 $[i, k]$ 与区间 $[k + 1, j]$,使得 $dp[i][k] + dp[k + 1][j]$ 的值最小即为 $dp[i][j]$,即:$dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])$。 - -###### 4. 初始条件 - -- 初始时,打印单个字符的最少打印次数为 $1$,即 $dp[i][i] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:打印第 $i$ 个字符到第 $j$ 个字符需要的最少打印次数。 所以最终结果为 $dp[0][size - 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def strangePrinter(self, s: str) -> int: - size = len(s) - dp = [[float('inf') for _ in range(size)] for _ in range(size)] - for i in range(size): - dp[i][i] = 1 - - for l in range(2, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - if s[i] == s[j]: - dp[i][j] = dp[i][j - 1] - else: - for k in range(i, j): - dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]) - - return dp[0][size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为字符串 $s$ 的长度。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/0665. \351\235\236\351\200\222\345\207\217\346\225\260\345\210\227.md" "b/Solutions/0665. \351\235\236\351\200\222\345\207\217\346\225\260\345\210\227.md" deleted file mode 100644 index 22977eee..00000000 --- "a/Solutions/0665. \351\235\236\351\200\222\345\207\217\346\225\260\345\210\227.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [0665. 非递减数列](https://leetcode.cn/problems/non-decreasing-array/) - -- 标签:数组 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 nums,问能否在最多改变 1 个元素的条件下,使数组变为非递减序列。若能,返回 True,不能则返回 False。 - -## 解题思路 - -循环遍历数组,寻找 nums[i] > nums[i+1] 的情况,一旦这种情况出现超过 2 次,则不可能最多改变 1 个元素,直接返回 False。 - -遇到 nums[i] > nums[i+1] 的情况,应该手动调节某位置上元素使数组有序。此时,有两种选择: - -- 将 nums[i] 调低,与 nums[i-1] 持平 -- 将 nums[i+1] 调高,与 nums[i] 持平 - -若选择第一种调节方式,如果调节前 nums[i-1] > nums[i+1],那么调节完 nums[i] 之后,nums[i-1] 还是比 nums[i+1] 大,不可取。 - -所以应选择第二种调节方式,如果调节前 nums[i-1] > nums[i+1],那么调节完 nums[i+1] 之后 nums[i-1] < nums[i] <= nums[i+1],满足非递减要求。 - -最终如果最多调整过一次,且 nums[i] > nums[i+1] 的情况也最多出现过一次,则返回 True。 - -## 代码 - -```python -class Solution: - def checkPossibility(self, nums: List[int]) -> bool: - count = 0 - for i in range(len(nums)-1): - if nums[i] > nums[i+1]: - count += 1 - if count > 1: - return False - if i > 0 and nums[i-1] > nums[i+1]: - nums[i+1] = nums[i] - - return True -``` - diff --git "a/Solutions/0669. \344\277\256\345\211\252\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0669. \344\277\256\345\211\252\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index e3278f7b..00000000 --- "a/Solutions/0669. \344\277\256\345\211\252\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0669. 修剪二叉搜索树](https://leetcode.cn/problems/trim-a-binary-search-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root`,同时给定最小边界 `low` 和最大边界 `high`。通过修建二叉搜索树,使得所有节点值都在 `[low, high]` 中。修剪树不应该改变保留在树中的元素的相对结构(即如果没有移除节点,则该节点的父节点关系、子节点关系都应当保留)。 - -现在要求返回修建过后的二叉树的根节点。 - -## 解题思路 - -递归修剪,函数返回值为修剪之后的树。 - -- 如果当前根节点为空,则直接返回 None。 -- 如果当前根节点的值小于 `low`,则该节点左子树全部都小于最小边界,则删除左子树,然后递归遍历右子树,在右子树中寻找符合条件的节点。 -- 如果当前根节点的值大于 `hight`,则该节点右子树全部都大于最大边界,则删除右子树,然后递归遍历左子树,在左子树中寻找符合条件的节点。 -- 如果在最小边界和最大边界的区间内,则分别从左右子树寻找符合条件的节点作为根的左右子树。 - -## 代码 - -```python -class Solution: - def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode: - if not root: - return None - if root.val < low: - right = self.trimBST(root.right, low, high) - return right - if root.val > high: - left = self.trimBST(root.left, low, high) - return left - - root.left = self.trimBST(root.left, low, high) - root.right = self.trimBST(root.right, low, high) - return root -``` - diff --git "a/Solutions/0673. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260.md" "b/Solutions/0673. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index e7e634f3..00000000 --- "a/Solutions/0673. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,221 +0,0 @@ -# [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) - -- 标签:树状数组、线段树、数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个未排序的整数数组 `nums`。 - -**要求**:返回最长递增子序列的个数。 - -**说明**: - -- 子数列必须是严格递增的。 -- $1 \le nums.length \le 2000$。 -- $-10^6 \le nums[i] \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:[1,3,5,4,7] -输出:2 -解释:有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -可以先做题目 [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)。 - -动态规划的状态 `dp[i]` 表示为:以第 `i` 个数字结尾的前 `i` 个元素中最长严格递增子序列的长度。 - -两重循环遍历前 `i` 个数字,对于 $0 \le j \le i$: - -- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 `i` 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。 -- 当 `nums[j] ≥ nums[i]` 时,可以直接跳过。 - -则状态转移方程为:`dp[i] = max(dp[i], dp[j] + 1)`,`0 ≤ j ≤ i`,`nums[j] < nums[i]`。 - -最后再遍历一遍 dp 数组,求出最大值即为最长递增子序列的长度。 - -现在求最长递增子序列的个数。则需要在求解的过程中维护一个 `count` 数组,用来保存以 `nums[i]` 结尾的最长递增子序列的个数。 - -对于 $0 \le j \le i$: - -- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 > dp[i]` 时,说明第一次找到 `dp[j] + 1`长度且以`nums[i]`结尾的最长递增子序列,则以 `nums[i]` 结尾的最长递增子序列的组合数就等于以 `nums[j]` 结尾的组合数,即 `count[i] = count[j]`。 -- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 == dp[i]` 时,说明以 `nums[i]` 结尾且长度为 `dp[j] + 1` 的递增序列已找到过一次了,则以 `nums[i]` 结尾的最长递增子序列的组合数要加上以 `nums[j]` 结尾的组合数,即 `count[i] += count[j]`。 - -- 然后根据遍历 dp 数组得到的最长递增子序列的长度 max_length,然后再一次遍历 dp 数组,将所有 `dp[i] == max_length` 情况下的组合数 `coun[i]` 累加起来,即为最长递增序列的个数。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def findNumberOfLIS(self, nums: List[int]) -> int: - size = len(nums) - dp = [1 for _ in range(size)] - count = [1 for _ in range(size)] - for i in range(size): - for j in range(i): - if nums[j] < nums[i]: - if dp[j] + 1 > dp[i]: - dp[i] = dp[j] + 1 - count[i] = count[j] - elif dp[j] + 1 == dp[i]: - count[i] += count[j] - - max_length = max(dp) - res = 0 - for i in range(size): - if dp[i] == max_length: - res += count[i] - return res -``` - -### 思路 2:线段树 - -题目中 `nums` 的长度 为 $[1, 2000]$,值域为 $[-10^6, 10^6]$。 - -值域范围不是特别大,我们可以直接用线段树保存整个值域区间。但因为数组的长度只有 `2000`,所以算法效率更高的做法是先对数组进行离散化处理。把数组中的元素按照大小依次映射到 `[0, len(nums) - 1]` 这个区间。 - -1. 构建一棵长度为 `len(nums)` 的线段树,其中每个线段树的节点保存一个二元组。这个二元组 `val = [length, count]` 用来表示:以当前节点为结尾的子序列所能达到的最长递增子序列长度 `length` 和最长递增子序列对应的数量 `count`。 -2. 顺序遍历数组 `nums`。对于当前元素 `nums[i]`: -3. 查找 `[0, nums[i - 1]]` 离散化后对应区间节点的二元组,也就是查找以区间 `[0, nums[i - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量,即 `val = [length, count]`。 - - 如果所能达到的最长递增子序列长度为 `0`,则加入 `nums[i]` 之后最长递增子序列长度变为 `1`,且数量也变为 `1`。 - - 如果所能达到的最长递增子序列长度不为 `0`,则加入 `nums[i]` 之后最长递增子序列长度 +1,但数量不变。 -4. 根据上述计算的 `val` 值更新 `nums[i]` 对应节点的 `val` 值。 -5. 然后继续向后遍历,重复进行第 `3` ~ `4` 步操作。 -6. 最后查询以区间 `[0, nums[len(nums) - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量。返回对应的数量即为答案。 - -### 思路 2:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=[0, 1]): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, size): - self.size = size - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.__update_point(i, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = [0, 0] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - - self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值 - - # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == i and right == i: - self.tree[index].val = self.merge(self.tree[index].val, val) - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值 - - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return [0, 0] - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = [0, 0] - res_right = [0, 0] - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - # 返回合并结果 - return self.merge(res_left, res_right) - - # 向上合并实现方法 - def merge(self, val1, val2): - val = [0, 0] - if val1[0] == val2[0]: # 递增子序列长度一致,则合并后最长递增子序列个数为之前两者之和 - val = [val1[0], val1[1] + val2[1]] - elif val1[0] < val2[0]: # 如果递增子序列长度不一致,则合并后最长递增子序列个数取较长一方的个数 - val = [val2[0], val2[1]] - else: - val = [val1[0], val1[1]] - return val - -class Solution: - def findNumberOfLIS(self, nums: List[int]) -> int: - - # 离散化处理 - num_dict = dict() - nums_sort = sorted(nums) - for i in range(len(nums_sort)): - num_dict[nums_sort[i]] = i - - # 构造线段树 - self.STree = SegmentTree(len(nums_sort)) - - for num in nums: - index = num_dict[num] - # 查询 [0, nums[index - 1]] 区间上以 nums[index - 1] 结尾的子序列所能达到的最长递增子序列长度和对应数量 - val = self.STree.query_interval(0, index - 1) - # 如果当前最长递增子序列长度为 0,则加入 num 之后最长递增子序列长度为 1,且数量为 1 - # 如果当前最长递增子序列长度不为 0,则加入 num 之后最长递增子序列长度 +1,但数量不变 - if val[0] == 0: - val = [1, 1] - else: - val = [val[0] + 1, val[1]] - self.STree.update_point(index, val) - return self.STree.query_interval(0, len(nums_sort) - 1)[1] -``` - diff --git "a/Solutions/0674. \346\234\200\351\225\277\350\277\236\347\273\255\351\200\222\345\242\236\345\272\217\345\210\227.md" "b/Solutions/0674. \346\234\200\351\225\277\350\277\236\347\273\255\351\200\222\345\242\236\345\272\217\345\210\227.md" deleted file mode 100644 index 8f4a5e28..00000000 --- "a/Solutions/0674. \346\234\200\351\225\277\350\277\236\347\273\255\351\200\222\345\242\236\345\272\217\345\210\227.md" +++ /dev/null @@ -1,123 +0,0 @@ -# [0674. 最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个未经排序的数组 $nums$。 - -**要求**:找到最长且连续递增的子序列,并返回该序列的长度。 - -**说明**: - -- **连续递增的子序列**:可以由两个下标 $l$ 和 $r$($l < r$)确定,如果对于每个 $l \le i < r$,都有 $nums[i] < nums[i + 1] $,那么子序列 $[nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]$ 就是连续递增子序列。 -- $1 \le nums.length \le 10^4$。 -- $-10^9 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,3,5,4,7] -输出:3 -解释:最长连续递增序列是 [1,3,5], 长度为 3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 -``` - -- 示例 2: - -```python -输入:nums = [2,2,2,2,2] -输出:1 -解释:最长连续递增序列是 [2], 长度为 1。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 定义状态 - -定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长且连续递增的子序列长度。 - -###### 2. 状态转移方程 - -因为求解的是连续子序列,所以只需要考察相邻元素的状态转移方程。 - -如果一个较小的数右侧相邻元素为一个较大的数,则会形成一个更长的递增子序列。 - -对于相邻的数组元素 $nums[i - 1]$ 和 $nums[i]$ 来说: - -- 如果 $nums[i - 1] < nums[i]$,则 $nums[i]$ 可以接在 $nums[i - 1]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[i - 1]$ 结尾的最长递增子序列长度」的基础上加 $1$,即 $dp[i] = dp[i - 1] + 1$。 - -- 如果 $nums[i - 1] >= nums[i]$,则 $nums[i]$ 不可以接在 $nums[i - 1]$ 后面,可以直接跳过。 - -综上,我们的状态转移方程为:$dp[i] = dp[i - 1] + 1$,$nums[i - 1] < nums[i]$。 - -###### 3. 初始条件 - -默认状态下,把数组中的每个元素都作为长度为 $1$ 的最长且连续递增的子序列长度。即 $dp[i] = 1$。 - -###### 4. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长且连续递增的子序列长度。则为了计算出最大值,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def findLengthOfLCIS(self, nums: List[int]) -> int: - size = len(nums) - dp = [1 for _ in range(size)] - - for i in range(1, size): - if nums[i - 1] < nums[i]: - dp[i] = dp[i - 1] + 1 - - return max(dp) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 - -### 思路 2:滑动窗口(不定长度) - -1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内为连续递增序列。使用 $window\underline{}len$ 存储当前窗口大小,使用 $max\underline{}len$ 维护最大窗口长度。 -2. 一开始,$left$、$right$ 都指向 $0$。 -3. 将最右侧元素 $nums[right]$ 加入当前连续递增序列中,即当前窗口长度加 $1$(`window_len += 1`)。 -4. 判断当前元素 $nums[right]$ 是否满足连续递增序列。 -5. 如果 $right > 0$ 并且 $nums[right - 1] \ge nums[right]$ ,说明不满足连续递增序列,则将 $left$ 移动到窗口最右侧,重置当前窗口长度为 $1$(`window_len = 1`)。 -6. 记录当前连续递增序列的长度,并更新最长连续递增序列的长度。 -7. 继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -8. 输出最长连续递增序列的长度 $max\underline{}len$。 - -### 思路 2:代码 - -```python -class Solution: - def findLengthOfLCIS(self, nums: List[int]) -> int: - size = len(nums) - left, right = 0, 0 - window_len = 0 - max_len = 0 - - while right < size: - window_len += 1 - - if right > 0 and nums[right - 1] >= nums[right]: - left = right - window_len = 1 - - max_len = max(max_len, window_len) - right += 1 - - return max_len -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270.md" "b/Solutions/0676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270.md" deleted file mode 100644 index 560f7c0a..00000000 --- "a/Solutions/0676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270.md" +++ /dev/null @@ -1,122 +0,0 @@ -# [0676. 实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**要求**:设计一个使用单词表进行初始化的数据结构。单词表中的单词互不相同。如果给出一个单词,要求判定能否将该单词中的一个字母替换成另一个字母,是的所形成的新单词已经在够构建的单词表中。 - -实现 MagicDictionary 类: - -- `MagicDictionary()` 初始化对象。 -- `void buildDict(String[] dictionary)` 使用字符串数组 `dictionary` 设定该数据结构,`dictionary` 中的字符串互不相同。 -- `bool search(String searchWord)` 给定一个字符串 `searchWord`,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 `True`;否则,返回 `False`。 - -**说明**: - -- $1 \le dictionary.length \le 100$。 -- $1 \le dictionary[i].length \le 100$。 -- `dictionary[i]` 仅由小写英文字母组成。 -- `dictionary` 中的所有字符串互不相同。 -- $1 \le searchWord.length \le 100$。 -- `searchWord` 仅由小写英文字母组成。 -- `buildDict` 仅在 `search` 之前调用一次。 -- 最多调用 $100$ 次 `search`。 - -**示例**: - -- 示例 1: - -```python -输入 -["MagicDictionary", "buildDict", "search", "search", "search", "search"] -[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]] -输出 -[null, null, false, true, false, false] - -解释 -MagicDictionary magicDictionary = new MagicDictionary(); -magicDictionary.buildDict(["hello", "leetcode"]); -magicDictionary.search("hello"); // 返回 False -magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True -magicDictionary.search("hell"); // 返回 False -magicDictionary.search("leetcoded"); // 返回 False -``` - -## 解题思路 - -### 思路 1:字典树 - -1. 构造一棵字典树。 -2. `buildDict` 方法中将所有单词存入字典树中。 -3. `search` 方法中替换 `searchWord` 每一个位置上的字符,然后在字典树中查询。 - -### 思路 1:代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - - -class MagicDictionary: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.trie_tree = Trie() - - - def buildDict(self, dictionary: List[str]) -> None: - for word in dictionary: - self.trie_tree.insert(word) - - - def search(self, searchWord: str) -> bool: - size = len(searchWord) - for i in range(size): - for j in range(26): - new_ch = chr(ord('a') + j) - if searchWord[i] != new_ch: - new_word = searchWord[:i] + new_ch + searchWord[i + 1:] - if self.trie_tree.search(new_word): - return True - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:初始化操作是 $O(1)$。构建操作是 $O(|dictionary|)$,搜索操作是 $O(|searchWord| \times |\sum|)$。其中 $|dictionary|$ 是字符串数组 `dictionary` 中的字符个数,$|searchWord|$ 是查询操作中字符串的长度,$|\sum|$ 是字符集的大小。 -- **空间复杂度**:$O(|dicitonary|)$。 \ No newline at end of file diff --git "a/Solutions/0677. \351\224\256\345\200\274\346\230\240\345\260\204.md" "b/Solutions/0677. \351\224\256\345\200\274\346\230\240\345\260\204.md" deleted file mode 100644 index acca4c9d..00000000 --- "a/Solutions/0677. \351\224\256\345\200\274\346\230\240\345\260\204.md" +++ /dev/null @@ -1,119 +0,0 @@ -# [0677. 键值映射](https://leetcode.cn/problems/map-sum-pairs/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**要求**:实现一个 MapSum 类,支持两个方法,`insert` 和 `sum`: - -- `MapSum()` 初始化 MapSum 对象。 -- `void insert(String key, int val)` 插入 `key-val` 键值对,字符串表示键 `key`,整数表示值 `val`。如果键 `key` 已经存在,那么原来的键值对将被替代成新的键值对。 -- `int sum(string prefix)` 返回所有以该前缀 `prefix` 开头的键 `key` 的值的总和。 - -**说明**: - -- $1 \le key.length, prefix.length \le 50$。 -- `key` 和 `prefix` 仅由小写英文字母组成。 -- $1 \le val \le 1000$。 -- 最多调用 $50$ 次 `insert` 和 `sum`。 - -**示例**: - -- 示例 1: - -```python -输入: -["MapSum", "insert", "sum", "insert", "sum"] -[[], ["apple", 3], ["ap"], ["app", 2], ["ap"]] -输出: -[null, null, 3, null, 5] - -解释: -MapSum mapSum = new MapSum(); -mapSum.insert("apple", 3); -mapSum.sum("ap"); // 返回 3 (apple = 3) -mapSum.insert("app", 2); -mapSum.sum("ap"); // 返回 5 (apple + app = 3 + 2 = 5) -``` - -## 解题思路 - -### 思路 1:字典树 - -可以构造前缀树(字典树)解题。 - -- 初始化时,构建一棵前缀树(字典树),并增加 `val` 变量。 - -- 调用插入方法时,用字典树存储 `key`,并在对应字母节点存储对应的 `val`。 -- 在调用查询总和方法时,先查找该前缀 `prefix` 对应的前缀树节点,从该节点开始,递归遍历该节点的子节点,并累积子节点的 `val`,进行求和,并返回求和累加结果。 - -### 思路 1:代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.value = 0 - - - def insert(self, word: str, value: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.value = value - - - def search(self, word: str) -> int: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return 0 - cur = cur.children[ch] - return self.dfs(cur) - - def dfs(self, root) -> int: - if not root: - return 0 - res = root.value - for node in root.children.values(): - res += self.dfs(node) - return res - - - -class MapSum: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.trie_tree = Trie() - - - def insert(self, key: str, val: int) -> None: - self.trie_tree.insert(key, val) - - - def sum(self, prefix: str) -> int: - return self.trie_tree.search(prefix) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:`insert` 操作的时间复杂度为 $O(|key|)$。其中 $|key|$ 是每次插入字符串 `key` 的长度。`sum` 操作的时间复杂度是 $O(|prefix|)$,其中 $O(| prefix |)$ 是查询字符串 `prefix` 的长度。 -- **空间复杂度**:$O(|T| \times m)$。其中 $|T|$ 表示字符串 `key` 的最大长度,$m$ 表示 `key - val` 的键值数目。 \ No newline at end of file diff --git "a/Solutions/0678. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0678. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 1be9cb0e..00000000 --- "a/Solutions/0678. \346\234\211\346\225\210\347\232\204\346\213\254\345\217\267\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,145 +0,0 @@ -# [0678. 有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) - -- 标签:栈、贪心、字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含三种字符的字符串:`(` ,`)` 和 `*`。有效的括号字符串具有如下规则: - -1. 任何左括号 `(` 必须有相应的右括号 `)`。 -2. 任何右括号 `)` 必须有相应的左括号 `(`。 -3. 左括号 `(` 必须在对应的右括号之前 `)`。 -4. `*` 可以被视为单个右括号 `)`,或单个左括号 `(`,或一个空字符串。 -5. 一个空字符串也被视为有效字符串。 - -**要求**:验证这个字符串是否为有效字符串。如果是,则返回 `True`;否则,则返回 `False`。 - -**说明**: - -- 字符串大小将在 `[1, 100]` 范围内。 - -**示例**: - -- 示例 1: - -```python -输入:"(*)" -输出:True -``` - -## 解题思路 - -### 思路 1:动态规划(时间复杂度为 $O(n^3)$) - -###### 1. 划分阶段 - -按照子串的起始位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:从下标 `i` 到下标 `j` 的子串是否为有效的括号字符串,其中 ($0 \le i < j < size$,$size$ 为字符串长度)。如果是则 `dp[i][j] = True`,否则,`dp[i][j] = False`。 - -###### 3. 状态转移方程 - -长度大于 `2` 时,我们需要根据 `s[i]` 和 `s[j]` 的情况,以及子串中间的有效字符串情况来判断 `dp[i][j]`。 - -- 如果 `s[i]`、`s[j]` 分别表示左括号和右括号,或者为 `'*'`(此时 `s[i]`、`s[j]` 可以分别看做是左括号、右括号)。则如果 `dp[i + 1][j - 1] == True` 时,`dp[i][j] = True`。 -- 如果可以将从下标 `i` 到下标 `j` 的子串从中间分开为两个有效字符串,则 `dp[i][j] = True`。即如果存在 $i \le k < j$,使得 `dp[i][k] == True` 并且 `dp[k + 1][j] == True`,则 `dp[i][j] = True`。 - -###### 4. 初始条件 - -- 当子串的长度为 `1`,并且该字符串为 `'*'` 时,子串可看做是空字符串,此时子串是有效的括号字符串。 -- 当子串的长度为 `2` 时,如果两个字符可以分别看做是左括号和右括号,子串可以看做是 `"()"`,此时子串是有效的括号字符串。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:从下标 `i` 到下标 `j` 的子串是否为有效的括号字符串。则最终结果为 `dp[0][size - 1]`。 - -### 思路 1:动态规划(时间复杂度为 $O(n^3)$)代码 - -```python -class Solution: - def checkValidString(self, s: str) -> bool: - size = len(s) - dp = [[False for _ in range(size)] for _ in range(size)] - - for i in range(size): - if s[i] == '*': - dp[i][i] = True - - for i in range(1, size): - if (s[i - 1] == '(' or s[i - 1] == '*') and (s[i] == ')' or s[i] == '*'): - dp[i - 1][i] = True - - for i in range(size - 3, -1, -1): - for j in range(i + 2, size): - if (s[i] == '(' or s[i] == '*') and (s[j] == ')' or s[j] == '*'): - dp[i][j] = dp[i + 1][j - 1] - for k in range(i, j): - if dp[i][j]: - break - dp[i][j] = dp[i][k] and dp[k + 1][j] - - return dp[0][size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$。三重循环遍历的时间复杂度是 $O(n^3)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 - -### 思路 2:动态规划(时间复杂度为 $O(n^2)$) - -###### 1. 划分阶段 - -按照字符串的结束位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:前 `i` 个字符能否通过补齐 `j` 个右括号成为有效的括号字符串。 - -###### 3. 状态转移方程 - -1. 如果 `s[i] == '('`,则如果前 `i - 1` 个字符通过补齐 `j - 1` 个右括号成为有效的括号字符串,则前 `i` 个字符就能通过补齐 `j` 个右括号成为有效的括号字符串(比前 `i - 1` 个字符需要多补一个右括号)。也就是说,如果 `s[i] == '('` 并且 `dp[i - 1][j - 1] == True`,则 `dp[i][j] = True`。 -2. 如果 `s[i] == ')'`,则如果前 `i - 1` 个字符通过补齐 `j + 1` 个右括号成为有效的括号字符串,则前 `i` 个字符就能通过补齐 `j` 个右括号成为有效的括号字符串(比前 `i - 1` 个字符需要少补一个右括号)。也就是说,如果 `s[i] == ')'` 并且 `dp[i - 1][j + 1] == True`,则 `dp[i][j] = True`。 -3. 如果 `s[i] == '*'`,而 `'*'` 可以表示空字符串、左括号或者右括号,则 `dp[i][j]` 取决于这三种情况,只要有一种情况为 `True`,则 `dp[i][j] = True`。也就是说,如果 `s[i] == '*'`,则 `dp[i][j] = dp[i - 1][j] or dp[i - 1][j - 1]`。 - -###### 4. 初始条件 - -- `0` 个字符可以通过补齐 `0` 个右括号成为有效的括号字符串(空字符串),即 `dp[0][0] = 0`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:前 `i` 个字符能否通过补齐 `j` 个右括号成为有效的括号字符串。。则最终结果为 `dp[size][0]`。 - -### 思路 2:动态规划(时间复杂度为 $O(n^2)$)代码 - -```python -class Solution: - def checkValidString(self, s: str) -> bool: - size = len(s) - dp = [[False for _ in range(size + 1)] for _ in range(size + 1)] - dp[0][0] = True - for i in range(1, size + 1): - for j in range(i + 1): - if s[i - 1] == '(': - if j > 0: - dp[i][j] = dp[i - 1][j - 1] - elif s[i - 1] == ')': - if j < i: - dp[i][j] = dp[i - 1][j + 1] - else: - dp[i][j] = dp[i - 1][j] - if j > 0: - dp[i][j] = dp[i][j] or dp[i - 1][j - 1] - if j < i: - dp[i][j] = dp[i][j] or dp[i - 1][j + 1] - - return dp[size][0] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$。 -- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 \ No newline at end of file diff --git "a/Solutions/0680. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262 II.md" "b/Solutions/0680. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262 II.md" deleted file mode 100644 index 322b623c..00000000 --- "a/Solutions/0680. \351\252\214\350\257\201\345\233\236\346\226\207\344\270\262 II.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [0680. 验证回文串 II](https://leetcode.cn/problems/valid-palindrome-ii/) - -- 标签:贪心、双指针、字符串 -- 难度:简单 - -## 题目大意 - -给定一个非空字符串 `s`。 - -要求:判断如果最多从字符串中删除一个字符能否得到一个回文字符串。 - -## 解题思路 - -题目要求在最多删除一个字符的情况下是否能得到一个回文字符串。最直接的思路是遍历各个字符,判断将该字符删除之后,剩余字符串是否是回文串。但是这种思路的时间复杂度是 $O(n^2)$,解答的话会超时。 - -我们可以通过双指针 + 贪心算法来减少时间复杂度。具体做法如下: - -- 使用两个指针变量 `left`、`right` 分别指向字符串的开始和结束位置。 - -- 判断 `s[left]` 是否等于 `s[right]`。 - - 如果等于,则 `left` 右移、`right`左移。 - - 如果不等于,则判断 `s[left: right - 1]` 或 `s[left + 1, right]` 是为回文串。 - - 如果是则返回 `True`。 - - 如果不是则返回 `False`,然后继续判断。 -- 如果 `right >= left`,则说明字符串 `s` 本身就是回文串,返回 `True`。 - -## 代码 - -```python -class Solution: - def checkPalindrome(self, s: str, left: int, right: int): - i, j = left, right - while i < j: - if s[i] != s[j]: - return False - i += 1 - j -= 1 - return True - - def validPalindrome(self, s: str) -> bool: - left, right = 0, len(s) - 1 - while left < right: - if s[left] == s[right]: - left += 1 - right -= 1 - else: - return self.checkPalindrome(s, left + 1, right) or self.checkPalindrome(s, left, right - 1) - return True -``` - diff --git "a/Solutions/0683. K \344\270\252\345\205\263\351\227\255\347\232\204\347\201\257\346\263\241.md" "b/Solutions/0683. K \344\270\252\345\205\263\351\227\255\347\232\204\347\201\257\346\263\241.md" deleted file mode 100644 index fd3acc74..00000000 --- "a/Solutions/0683. K \344\270\252\345\205\263\351\227\255\347\232\204\347\201\257\346\263\241.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [0683. K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) - -- 标签:树状数组、数组、有序集合、滑动窗口 -- 难度:困难 - -## 题目大意 - -`n` 个灯泡排成一行,编号从 `1` 到 `n`。最初,所有灯泡都关闭。每天只打开一个灯泡,直到 `n` 天后所有灯泡都打开。 - -给定一个长度为 `n` 的灯泡数组 `blubs`,其中 `bulls[i] = x` 意味着在第`i + 1` 天,我们会把在位置 `x` 的灯泡打开,其中 `i` 从 `0` 开始,`x` 从 `1` 开始。 - -再给定一个整数 `k`。 - -要求:输出在第几天恰好有两个打开的灯泡,使得它们中间正好有 `k` 个灯泡且这些灯泡全部是关闭的 。如果不存在这种情况,则返回 `-1`。如果有多天都出现这种情况,请返回最小的天数 。 - -## 解题思路 - -`blubs[i]` 记录的是第 `i + 1` 天开灯的位置。我们将其转换一下,使用另一个数组 `days` 来存储每个灯泡的开灯时间,其中 `days[i]` 表示第 `i` 个位置上的灯泡的开灯时间。 - -- 使用 `ans` 记录最小满足条件的天数。维护一个窗口 `left`、`right`。其中 `right = left + k + 1`。使得区间 `(left, right)` 中所有灯泡(总共为 `k` 个)开灯时间都晚于 `days[left]` 和 `days[right]`。 -- 对于区间 `[left, right]`,`left < i < right`: - - 如果出现 `days[i] < days[left]` 或者 `days[i] < days[right]`,说明不符合要求。将 `left`、`right` 移动到 `[i, i + k + 1]`,继续进行判断。 - - 如果对于 `left < i < right` 中所有的 `i`,都满足 `days[i] >= days[left]` 并且 `days[i] >= days[right]`,说明此时满足要求。将当前答案与 `days[left]` 和 `days[right]` 中的较大值作比较。如果比当前答案更小,则更新答案。同时将窗口向右移动 `k `位。继续检测新的不相交间隔 `[right, right + k + 1]`。 - - 注意:之所以检测新的不相交间隔,是因为如果检测的是相交间隔,原来的 `right` 位置元素仍在区间中,肯定会出现 `days[right] < days[right_new]`,不满足要求。所以此时相交的区间可以直接跳过,直接检测不相交的间隔。 -- 直到 `right >= len(days)` 时跳出循环,判断是否有符合要求的答案,并返回答案 `ans`。 - -## 代码 - -```python -class Solution: - def kEmptySlots(self, bulbs: List[int], k: int) -> int: - size = len(bulbs) - days = [0 for _ in range(size)] - for i in range(size): - days[bulbs[i] - 1] = i + 1 - - left, right = 0, k + 1 - ans = float('inf') - while right < size: - check_flag = True - for i in range(left + 1, right): - if days[i] < days[left] or days[i] < days[right]: - left, right = i, i + k + 1 - check_flag = False - break - if check_flag: - ans = min(ans, max(days[left], days[right])) - left, right = right, right + k + 1 - - if ans != float('inf'): - return ans - else: - return -1 -``` - diff --git "a/Solutions/0684. \345\206\227\344\275\231\350\277\236\346\216\245.md" "b/Solutions/0684. \345\206\227\344\275\231\350\277\236\346\216\245.md" deleted file mode 100644 index 06987762..00000000 --- "a/Solutions/0684. \345\206\227\344\275\231\350\277\236\346\216\245.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -**描述**:一个 `n` 个节点的树(节点值为 `1~n`)添加一条边后就形成了图,添加的这条边不属于树中已经存在的边。图的信息记录存储与长度为 `n` 的二维数组 `edges`,`edges[i] = [ai, bi]` 表示图中在 `ai` 和 `bi` 之间存在一条边。 - -现在给定代表边信息的二维数组 `edges`。 - -**要求**:找到一条可以山区的边,使得删除后的剩余部分是一个有着 `n` 个节点的树。如果有多个答案,则返回数组 `edges` 中最后出现的边。 - -**说明**: - -- $n == edges.length$。 -- $3 \le n \le 1000$。 -- $edges[i].length == 2$。 -- $1 \le ai < bi \le edges.length$。 -- $ai ≠ bi$。 -- $edges$ 中无重复元素。 -- 给定的图是连通的。 - -**示例**: - -- 示例 1: - -![img](https://pic.leetcode-cn.com/1626676174-hOEVUL-image.png) - -```python -输入: edges = [[1,2], [1,3], [2,3]] -输出: [2,3] -``` - -- 示例 2: - -![img](https://pic.leetcode-cn.com/1626676179-kGxcmu-image.png) - -```python -输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]] -输出: [1,4] -``` - -## 解题思路 - -### 思路 1:并查集 - -树可以看做是无环的图,这道题就是要找出那条添加边之后成环的边。可以考虑用并查集来做。 - -1. 从前向后遍历每一条边。 -2. 如果边的两个节点不在同一个集合,就加入到一个集合(链接到同一个根节点)。 -3. 如果边的节点已经出现在同一个集合里,说明边的两个节点已经连在一起了,再加入这条边一定会出现环,则这条边就是所求答案。 - -### 思路 1:代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - self.parent[root_x] = root_y - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: - size = len(edges) - union_find = UnionFind(size + 1) - - for edge in edges: - if union_find.is_connected(edge[0], edge[1]): - return edge - union_find.union(edge[0], edge[1]) - - return None -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是图中的节点个数,$\alpha$ 是反 `Ackerman` 函数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0686. \351\207\215\345\244\215\345\217\240\345\212\240\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" "b/Solutions/0686. \351\207\215\345\244\215\345\217\240\345\212\240\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" deleted file mode 100644 index 07c06506..00000000 --- "a/Solutions/0686. \351\207\215\345\244\215\345\217\240\345\212\240\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [0686. 重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) - -- 标签:字符串、字符串匹配 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个字符串 `a` 和 `b`。 - -**要求**:寻找重复叠加字符串 `a` 的最小次数,使得字符串 `b` 成为叠加后的字符串 `a` 的子串,如果不存在则返回 `-1`。 - -**说明**: - -- 字符串 `"abc"` 重复叠加 `0` 次是 `""`,重复叠加 `1` 次是 `"abc"`,重复叠加 `2` 次是 `"abcabc"`。 -- $1 \le a.length \le 10^4$。 -- $1 \le b.length \le 10^4$。 -- `a` 和 `b` 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:a = "abcd", b = "cdabcdab" -输出:3 -解释:a 重复叠加三遍后为 "abcdabcdabcd", 此时 b 是其子串。 -``` - -- 示例 2: - -```python -输入:a = "a", b = "aa" -输出:2 -``` - -## 解题思路 - -### 思路 1:KMP 算法 - -假设字符串 `a` 的长度为 `n`,`b` 的长度为 `m`。 - -把 `b` 看做是模式串,把字符串 `a` 叠加后的字符串看做是文本串,这道题就变成了单模式串匹配问题。 - -我们可以模拟叠加字符串 `a` 后进行单模式串匹配问题。模拟叠加字符串可以通过在遍历字符串匹配时对字符串 `a` 的长度 `n` 取余来实现。 - -那么问题关键点就变为了如何高效的进行单模式串匹配,以及字符串循环匹配的退出条件是什么。 - -**单模式串匹配问题**:可以用 KMP 算法来做。 - -**循环匹配退出条件问题**:假设我们用 `i` 遍历 `a` 叠加后字符串,用 `j` 遍历字符串 `b`。如果字符串 `b` 是 `a` 叠加后字符串的子串,那么 `b` 有两种可能: - -1. `b` 直接是原字符串 `a` 的子串:这种情况下,最多遍历到 `len(a)`。 -2. `b` 是 `a` 叠加后的字符串的子串: - 1. 最多遍历到 `len(a) + len(b)`,可以写为 `while i < len(a) + len(b):`,当 `i == len(a) + len(b)` 时跳出循环。 - 2. 也可以写为 `while i - j < len(a):`,这种写法中 `i - j ` 表示的是字符匹配开始的位置,如果匹配到 `len(a)` 时(即 `i - j == len(a)` 时)最开始位置的字符仍没有匹配,那么 `b` 也不可能是 `a` 叠加后的字符串的子串了,此时跳出循环。 - -最后我们需要计算一下重复叠加字符串 `a` 的最小次数。假设 `index` 使我们求出的匹配位置。 - -1. 如果 `index == -1`,则说明 `b` 不可能是 `a` 叠加后的字符串的子串,返回 `False`。 -2. 如果 `len(a) - index >= len(b)`,则说明匹配位置未超过字符串 `a` 的长度,叠加 `1` 次(字符串 `a` 本身)就可以匹配。 -3. 如果 `len(a) - index < len(b)`,则说明需要叠加才能匹配。此时最小叠加次数为 $\lfloor \frac{index + len(b) - 1}{len(a)} \rfloor + 1$。其中 `index` 代笔匹配开始前的字符串长度,加上 `len(b)` 后就是匹配到字符串 `b` 结束时最少需要的字符数,再 `-1` 是为了向下取整。 除以 `len(a)` 表示至少需要几个 `a`, 因为是向下取整,所以最后要加上 `1`。写成代码就是:`(index + len(b) - 1) // len(a) + 1`。 - -### 思路 1:代码 - -```python -class Solution: - # KMP 匹配算法,T 为文本串,p 为模式串 - def kmp(self, T: str, p: str) -> int: - n, m = len(T), len(p) - - next = self.generateNext(p) - - i, j = 0, 0 - while i - j < n: - while j > 0 and T[i % n] != p[j]: - j = next[j - 1] - if T[i % n] == p[j]: - j += 1 - if j == m: - return i - m + 1 - i += 1 - return -1 - - def generateNext(self, p: str): - m = len(p) - next = [0 for _ in range(m)] - - left = 0 - for right in range(1, m): - while left > 0 and p[left] != p[right]: - left = next[left - 1] - if p[left] == p[right]: - left += 1 - next[right] = left - - return next - - def repeatedStringMatch(self, a: str, b: str) -> int: - len_a = len(a) - len_b = len(b) - index = self.kmp(a, b) - if index == -1: - return -1 - if len_a - index >= len_b: - return 1 - return (index + len(b) - 1) // len(a) + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中文本串 $a$ 的长度为 $n$,模式串 $b$ 的长度为 $m$。 -- **空间复杂度**:$O(m)$。 - diff --git "a/Solutions/0687. \346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204.md" "b/Solutions/0687. \346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204.md" deleted file mode 100644 index d684f153..00000000 --- "a/Solutions/0687. \346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204.md" +++ /dev/null @@ -1,115 +0,0 @@ -# [0687. 最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 $root$。 - -**要求**:返回二叉树中最长的路径的长度,该路径中每个节点具有相同值。 这条路径可以经过也可以不经过根节点。 - -**说明**: - -- 树的节点数的范围是 $[0, 10^4]$。 -- $-1000 \le Node.val \le 1000$。 -- 树的深度将不超过 $1000$。 -- 两个节点之间的路径长度:由它们之间的边数表示。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/10/13/ex1.jpg) - -```python -输入:root = [5,4,5,1,1,5] -输出:2 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/10/13/ex2.jpg) - -```python -输入:root = [1,4,5,4,4,5] -输出:2 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -这道题如果先不考虑「路径中每个节点具有相同值」这个条件,那么这道题就是在求「二叉树的直径长度(最长路径的长度)」。 - -「二叉树的直径长度」的定义为:二叉树中任意两个节点路径长度中的最大值。并且这条路径可能穿过也可能不穿过根节点。 - -对于根为 $root$ 的二叉树来说,其直径长度并不简单等于「左子树高度」加上「右子树高度」。 - -根据路径是否穿过根节点,我们可以将二叉树分为两种: - -1. 直径长度所对应的路径穿过根节点,这种情况下:$\text{二叉树的直径} = \text{左子树高度} + \text{右子树高度}$。 -2. 直径长度所对应的路径不穿过根节点,这种情况下:$\text{二叉树的直径} = \text{所有子树中最大直径长度}$。 - -也就是说根为 $root$ 的二叉树的直径长度可能来自于 $\text{左子树高度} + \text{右子树高度}$,也可能来自于 $\text{子树中的最大直径}$,即 $\text{二叉树的直径} = max(\text{左子树高度} + \text{右子树高度}, \quad \text{所有子树中最大直径长度})$。 - -那么现在问题就变成为如何求「子树的高度」和「子树中的最大直径」。 - -1. 子树的高度:我们可以利用深度优先搜索方法,递归遍历左右子树,并分别返回左右子树的高度。 -2. 子树中的最大直径:我们可以在递归求解子树高度的时候维护一个 $ans$ 变量,用于记录所有 $\text{左子树高度} + \text{右子树高度$ 中的最大值。 - -最终 $ans$ 就是我们所求的该二叉树的最大直径。 - -接下来我们再来加上「路径中每个节点具有相同值」这个限制条件。 - -1. 「左子树高度」应变为「左子树最长同值路径长度」。 -2. 「右子树高度」应变为「右子树最长同值路径长度」。 -3. 题目变为求「二叉树的最长同值路径长度」,式子为:$\text{二叉树的最长同值路径长度} = max(\text{左子树最长同值路径长度} + \text{右子树最长同值路径长度}, \quad \text{所有子树中最长同值路径长度})$。 - -在递归遍历的时候,我们还需要当前节点与左右子节点的值的相同情况,来维护更新「包含当前节点的最长同值路径长度」。 - -1. 在递归遍历左子树时,如果当前节点与左子树的值相同,则:$\text{包含当前节点向左的最长同值路径长度} = \text{左子树最长同值路径长度} + 1$,否则为 $0$。 -2. 在递归遍历左子树时,如果当前节点与左子树的值相同,则:$\text{包含当前节点向右的最长同值路径长度} = \text{右子树最长同值路径长度} + 1$,否则为 $0$。 - -则:$\text{包含当前节点向左的最长同值路径长度} = max(\text{包含当前节点向左的最长同值路径长度}, \quad \text{包含当前节点向右的最长同值路径长度})$。 - -### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def __init__(self): - self.ans = 0 - - def dfs(self, node): - if not node: - return 0 - - left_len = self.dfs(node.left) # 左子树高度 - right_len = self.dfs(node.right) # 右子树高度 - if node.left and node.left.val == node.val: - left_len += 1 - else: - left_len = 0 - if node.right and node.right.val == node.val: - right_len += 1 - else: - right_len = 0 - self.ans = max(self.ans, left_len + right_len) - return max(left_len, right_len) - - def longestUnivaluePath(self, root: Optional[TreeNode]) -> int: - self.dfs(root) - - return self.ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点个数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0688. \351\252\221\345\243\253\345\234\250\346\243\213\347\233\230\344\270\212\347\232\204\346\246\202\347\216\207.md" "b/Solutions/0688. \351\252\221\345\243\253\345\234\250\346\243\213\347\233\230\344\270\212\347\232\204\346\246\202\347\216\207.md" deleted file mode 100644 index 6c9f331a..00000000 --- "a/Solutions/0688. \351\252\221\345\243\253\345\234\250\346\243\213\347\233\230\344\270\212\347\232\204\346\246\202\347\216\207.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0688. 骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) - -- 标签:动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:在一个 `n * n` 的国际象棋棋盘上,一个骑士从单元格 `(row, column)` 开始,尝试进行 `k` 次 移动。行和列是从 `0` 开始的,左上角的单元格是 `(0, 0)`,右下角的单元格是 `(n - 1, n - 1)`。 - -象棋骑士有 `8` 种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。 - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/knight.png) - -每次骑士要移动时,它都会随机从 `8` 种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。骑士继续移动,直到它走了 `k` 步或离开了棋盘。 - -现在给定代表棋盘大小的整数 `n`、代表骑士移动次数的整数 `k`,以及代表骑士初始位置的坐标 `row` 和 `column`。 - -**要求**:返回骑士在棋盘停止移动后仍留在棋盘上的概率。 - -**说明**: - -- $1 \le n \le 25$。 -- $0 \le k \le 100$。 -- $0 \le row, column \le n$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 3, k = 2, row = 0, column = 0 -输出:0.0625 -解释:有两步(到(1,2),(2,1))可以让骑士留在棋盘上。在每一个位置上,也有两种移动可以让骑士留在棋盘上。骑士留在棋盘上的总概率是 0.0625。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照骑士所在位置和所走步数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j][p]` 表示为:从位置 `(i, j)` 出发,移动不超过 `p` 步的情况下,最后仍留在棋盘内的概率。 - -###### 3. 状态转移方程 - -根据象棋骑士的 `8` 种可能的走法,`dp[i][j][p]` 的来源有八个方向(超出棋盘的无需再考虑): - -- 假设下一步的落点为 `(new_i, new_j)`。从当前步选择 `8` 个方向其中之一作为下一步方向的概率为 $\frac{1}{8}$。 -- 而每个方向上落点仍在棋盘内的概率为 `dp[new_i][new_j][p - 1]`。所以从 `(i, j)` 走到 `(new_i, new_j)` 的可能性为 $dp[new_i][new_j] \times \frac{1}{8}$。 - -最终 $dp[i][j][p]$ 来源为 `8` 个方向上落点的概率之和,即:$dp[i][j][p] = \sum{ dp[new_i][new_j] \times \frac{1}{8} }$。 - -###### 4. 初始条件 - -- 从位置 `(i, j)` 出发,移动不超过 `0` 步的情况下,最后仍留在棋盘内的概率为 `1`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j][p]` 表示为:从位置 `(i, j)` 出发,移动不超过 `p` 步的情况下,最后仍留在棋盘内的概率。则最终结果为 `dp[row][column][k]`。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def knightProbability(self, n: int, k: int, row: int, column: int) -> float: - dp = [[[0 for _ in range(k + 1)] for _ in range(n)] for _ in range(n)] - for i in range(n): - for j in range(n): - dp[i][j][0] = 1 - - directions = {(-1, -2), (-1, 2), (1, -2), (1, 2), (-2, -1), (-2, 1), (2, -1), (2, 1)} - - for p in range(1, k + 1): - for i in range(n): - for j in range(n): - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - if 0 <= new_i < n and 0 <= new_j < n: - dp[i][j][p] += dp[new_i][new_j][p - 1] / 8 - - return dp[row][column][k] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 * k)$。外三层循环的时间复杂度为 $O(n^2 * k)$,内层关于 `directions` 的循环每次执行 `8` 次,可以看做是常数级时间复杂度。 -- **空间复杂度**:$O(n^2 * k)$。用到了三维数组保存状态。 diff --git "a/Solutions/0690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247.md" "b/Solutions/0690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247.md" deleted file mode 100644 index 2b32105d..00000000 --- "a/Solutions/0690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [0690. 员工的重要性](https://leetcode.cn/problems/employee-importance/) - -- 标签:深度优先搜索、广度优先搜索、哈希表 -- 难度:中等 - -## 题目大意 - -给定一个公司的所有员工信息。其中每个员工信息包含:该员工 id,该员工重要度,以及该员工的所有下属 id。 - -再给定一个员工 id,要求返回该员工和他所有下属的重要度之和。 - -## 解题思路 - -利用哈希表,以「员工 id: 员工数据结构」的形式将员工信息存入哈希表中。然后深度优先搜索该员工以及下属员工。在搜索的同时,计算重要度之和,最终返回结果即可。 - -## 代码 - -```python -class Solution: - def getImportance(self, employees: List['Employee'], id: int) -> int: - employee_dict = dict() - for employee in employees: - employee_dict[employee.id] = employee - - def dfs(index: int) -> int: - total = employee_dict[index].importance - for sub_index in employee_dict[index].subordinates: - total += dfs(sub_index) - return total - - return dfs(id) -``` - diff --git "a/Solutions/0691. \350\264\264\347\272\270\346\213\274\350\257\215.md" "b/Solutions/0691. \350\264\264\347\272\270\346\213\274\350\257\215.md" deleted file mode 100644 index 2d7c3aee..00000000 --- "a/Solutions/0691. \350\264\264\347\272\270\346\213\274\350\257\215.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0691. 贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) - -- 标签:位运算、数组、字符串、动态规划、回溯、状态压缩 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个字符串数组 $stickers$ 表示不同的贴纸,其中 $stickers[i]$ 表示第 $i$ 张贴纸上的小写英文单词。再给定一个字符串 $target$。为了拼出给定字符串 $target$,我们需要从贴纸中切割单个字母并重新排列它们。贴纸的数量是无限的,可以重复多次使用。 - -**要求**:返回需要拼出 $target$ 的最小贴纸数量。如果任务不可能,则返回 $-1$。 - -**说明**: - -- 在所有的测试用例中,所有的单词都是从 $1000$ 个最常见的美国英语单词中随机选择的,并且 $target$ 被选择为两个随机单词的连接。 -- $n == stickers.length$。 -- $1 \le n \le 50$。 -- $1 \le stickers[i].length \le 10$。 -- $1 \le target.length \le 15$。 -- $stickers[i]$ 和 $target$ 由小写英文单词组成。 - -**示例**: - -- 示例 1: - -```python -输入:stickers = ["with","example","science"], target = "thehat" -输出:3 -解释: -我们可以使用 2 个 "with" 贴纸,和 1 个 "example" 贴纸。 -把贴纸上的字母剪下来并重新排列后,就可以形成目标 “thehat“ 了。 -此外,这是形成目标字符串所需的最小贴纸数量。 -``` - -- 示例 2: - -```python -输入:stickers = ["notice","possible"], target = "basicbasic" -输出:-1 -解释:我们不能通过剪切给定贴纸的字母来形成目标“basicbasic”。 -``` - -## 解题思路 - -### 思路 1:状态压缩 DP + 广度优先搜索 - -根据题意,$target$ 的长度最大为 $15$,所以我们可以使用一个长度最多为 $15$ 位的二进制数 $state$ 来表示 $target$ 的某个子序列,如果 $state$ 第 $i$ 位二进制值为 $1$,则说明 $target$ 的第 $i$ 个字母被选中。 - -然后我们从初始状态 $state = 0$(没有选中 $target$ 中的任何字母)开始进行广度优先搜索遍历。 - -在广度优先搜索过程中,对于当前状态 $cur\underline{}state$,我们遍历所有贴纸的所有字母,如果当前字母可以拼到 $target$ 中的某个位置上,则更新状态 $next\underline{}state$ 为「选中 $target$ 中对应位置上的字母」。 - -为了得到最小最小贴纸数量,我们可以使用动态规划的方法,定义 $dp[state]$ 表示为到达 $state$ 状态需要的最小贴纸数量。 - -那么在广度优先搜索中,在更新状态时,同时进行状态转移,即 $dp[next\underline{}state] = dp[cur\underline{}state] + 1$。 - -> 注意:在进行状态转移时,要跳过 $dp[next\underline{}state]$ 已经有值的情况。 - -这样在到达状态 $1 \text{ <}\text{< } len(target) - 1$ 时,所得到的 $dp[1 \text{ <}\text{< } len(target) - 1]$ 即为答案。 - -如果最终到达不了 $dp[1 \text{ <}\text{< } len(target) - 1]$,则说明无法完成任务,返回 $-1$。 - -### 思路 1:代码 - -```python -class Solution: - def minStickers(self, stickers: List[str], target: str) -> int: - size = len(target) - states = 1 << size - dp = [0 for _ in range(states)] - - queue = collections.deque([0]) - - while queue: - cur_state = queue.popleft() - for sticker in stickers: - next_state = cur_state - cnts = [0 for _ in range(26)] - for ch in sticker: - cnts[ord(ch) - ord('a')] += 1 - for i in range(size): - if cnts[ord(target[i]) - ord('a')] and next_state & (1 << i) == 0: - next_state |= (1 << i) - cnts[ord(target[i]) - ord('a')] -= 1 - - if dp[next_state] or next_state == 0: - continue - - queue.append(next_state) - dp[next_state] = dp[cur_state] + 1 - if next_state == states - 1: - return dp[next_state] - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n \times \sum_{i = 0}^{m - 1} len(stickers[i]) \times n$,其中 $n$ 为 $target$ 的长度,$m$ 为 $stickers$ 的元素个数。 -- **空间复杂度**:$O(2^n)$。 - diff --git "a/Solutions/0695. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" "b/Solutions/0695. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" deleted file mode 100644 index 878d399f..00000000 --- "a/Solutions/0695. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" +++ /dev/null @@ -1,124 +0,0 @@ -# [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个只包含 $0$、$1$ 元素的二维数组,$1$ 代表岛屿,$0$ 代表水。一座岛的面积就是上下左右相邻的 $1$ 所组成的连通块的数目。 - -**要求**:计算出最大的岛屿面积。 - -**说明**: - -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 50$。 -- $grid[i][j]$ 为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg) - -```python -输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] -输出:6 -解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。 -``` - -- 示例 2: - -```python -输入:grid = [[0,0,0,0,0,0,0,0]] -输出:0 -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -1. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: - 1. 将该位置上的值置为 $0$(防止二次重复计算)。 - 2. 递归搜索该位置上下左右四个位置,并统计搜到值为 $1$ 的元素个数。 - 3. 返回值为 $1$ 的元素个数(即为该岛的面积)。 -2. 维护并更新最大的岛面积。 -3. 返回最大的到面积。 - -### 思路 1:代码 - -```python -class Solution: - def dfs(self, grid, i, j): - n = len(grid) - m = len(grid[0]) - if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == 0: - return 0 - ans = 1 - grid[i][j] = 0 - ans += self.dfs(grid, i + 1, j) - ans += self.dfs(grid, i, j + 1) - ans += self.dfs(grid, i - 1, j) - ans += self.dfs(grid, i, j - 1) - return ans - - def maxAreaOfIsland(self, grid: List[List[int]]) -> int: - ans = 0 - for i in range(len(grid)): - for j in range(len(grid[0])): - if grid[i][j] == 1: - ans = max(ans, self.dfs(grid, i, j)) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(n \times m)$。 - -### 思路 2:广度优先搜索 - -1. 使用 $ans$ 记录最大岛屿面积。 -2. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: - 1. 将该元素置为 $0$。并使用队列 $queue$ 存储该节点位置。使用 $temp\underline{}ans$ 记录当前岛屿面积。 - 2. 然后从队列 $queue$ 中取出第一个节点位置 $(i, j)$。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 $0$(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`。 - 3. 不断重复上一步骤,直到队列 $queue$ 为空。 - 4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`。 -3. 将 $ans$ 作为答案返回。 - -### 思路 2:代码 - -```python -import collections - -class Solution: - def maxAreaOfIsland(self, grid: List[List[int]]) -> int: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - rows, cols = len(grid), len(grid[0]) - ans = 0 - for i in range(rows): - for j in range(cols): - if grid[i][j] == 1: - grid[i][j] = 0 - temp_ans = 1 - q = collections.deque([(i, j)]) - while q: - i, j = q.popleft() - for direct in directs: - new_i = i + direct[0] - new_j = j + direct[1] - if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols or grid[new_i][new_j] == 0: - continue - grid[new_i][new_j] = 0 - q.append((new_i, new_j)) - temp_ans += 1 - - ans = max(ans, temp_ans) - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(n \times m)$。 diff --git "a/Solutions/0698. \345\210\222\345\210\206\344\270\272k\344\270\252\347\233\270\347\255\211\347\232\204\345\255\220\351\233\206.md" "b/Solutions/0698. \345\210\222\345\210\206\344\270\272k\344\270\252\347\233\270\347\255\211\347\232\204\345\255\220\351\233\206.md" deleted file mode 100644 index 3d3569a6..00000000 --- "a/Solutions/0698. \345\210\222\345\210\206\344\270\272k\344\270\252\347\233\270\347\255\211\347\232\204\345\255\220\351\233\206.md" +++ /dev/null @@ -1,132 +0,0 @@ -# [0698. 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) - -- 标签:位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$ 和一个正整数 $k$。 - -**要求**:找出是否有可能把这个数组分成 $k$ 个非空子集,其总和都相等。 - -**说明**: - -- $1 \le k \le len(nums) \le 16$。 -- $0 < nums[i] < 10000$。 -- 每个元素的频率在 $[1, 4]$ 范围内。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 -输出: True -说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。 -``` - -- 示例 2: - -```python -输入: nums = [1,2,3,4], k = 3 -输出: False -``` - -## 解题思路 - -### 思路 1:状态压缩 DP - -根据题目要求,我们可以将几种明显不符合要求的情况过滤掉,比如:元素个数小于 $k$、元素总和不是 $k$ 的倍数、数组 $nums$ 中最大元素超过 $k$ 等分的目标和这几种情况。 - -然后再来考虑一般情况下,如何判断是否符合要求。 - -因为题目给定数组 $nums$ 的长度最多为 $16$,所以我们可以使用一个长度为 $16$ 位的二进制数来表示数组子集的选择状态。我们可以定义 $dp[state]$ 表示为当前选择状态下,是否可行。如果 $dp[state] == True$,表示可行;如果 $dp[state] == False$,则表示不可行。 - -接下来使用动态规划方法,进行求解。具体步骤如下: - -###### 1. 划分阶段 - -按照数组元素选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[state]$ 表示为:当数组元素选择情况为 $state$ 时,是否存在一种方案,使得方案中的数字必定能分割成 $p(0 \le p \le k)$ 组恰好数字和等于目标和 $target$ 的集合和至多 $1$ 组数字和小于目标和 $target$ 的集合。 - -###### 3. 状态转移方程 - -对于当前状态 $state$,如果: - -1. 当数组元素选择情况为 $state$ 时可行,即 $dp[state] == True$; -2. 第 $i$ 位数字没有被使用; -3. 加上第 $i$ 位元素后的状态为 $next\underline{}state$; -4. 加上第 $i$ 位元素后没有超出目标和。 - -则:$dp[next\underline{}state] = True$。 - -###### 4. 初始条件 - -- 当不选择任何元素时,可按照题目要求 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当数组元素选择情况为 $state$ 时,是否存在一种方案,使得方案中的数字必定能分割成 $p(0 \le p \le k)$ 组恰好数字和等于目标和 $target$ 的集合和至多 $1$ 组数字和小于目标和 $target$ 的集合。 - -所以当 $state == 1 << n - 1$ 时,状态就变为了:当数组元素都选上的情况下,是否存在一种方案,使得方案中的数字必定能分割成 $k$ 组恰好数字和等于目标和 $target$ 的集合。 - -这里之所以是 $k$ 组恰好数字和等于目标和 $target$ 的集合,是因为一开我们就限定了 $total \mod k == 0$ 这个条件,所以只能是 $k$ 组恰好数字和等于目标和 $target$ 的集合。 - -所以最终结果为 $dp[states - 1]$,其中 $states = 1 << n$。 - -### 思路 1:代码 - -```python -class Solution: - def canPartitionKSubsets(self, nums: List[int], k: int) -> bool: - size = len(nums) - if size < k: # 元素个数小于 k - return False - - total = sum(nums) - if total % k != 0: # 元素总和不是 k 的倍数 - return False - - target = total // k - if nums[-1] > target: # 最大元素超过 k 等分的目标和 - return False - - nums.sort() - states = 1 << size # 子集选择状态总数 - cur_sum = [0 for _ in range(states)] - dp = [False for _ in range(states)] - dp[0] = True - - for state in range(states): - if not dp[state]: # 基于 dp[state] == True 前提下进行转移 - continue - for i in range(size): - if state & (1 << i) != 0: # 当前数字已被使用 - continue - - if cur_sum[state] % target + nums[i] > target: - break # 如果加入当前数字超出目标和,则后续不用继续遍历 - - next_state = state | (1 << i) # 加入当前数字 - if dp[next_state]: # 如果新状态能划分,则跳过继续 - continue - - cur_sum[next_state] = cur_sum[state] + nums[i] # 更新新状态下子集和 - dp[next_state] = True # 更新新状态 - if dp[states - 1]: # 找到一个符合要求的划分方案,提前返回 - return True - - return dp[states - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(2^n)$。 - -## 参考资料 - -- 【题解】[状态压缩的定义理解 - 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/solution/zhuang-tai-ya-suo-de-ding-yi-li-jie-by-c-fo1b/) diff --git "a/Solutions/0700. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.md" "b/Solutions/0700. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.md" deleted file mode 100644 index e9674346..00000000 --- "a/Solutions/0700. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) - -- 标签:树、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个二叉搜索树和一个值 `val`。 - -**要求**:在二叉搜索树中查找节点值等于 `val` 的节点,并返回该节点。 - -**说明**: - -- 数中节点数在 $[1, 5000]$ 范围内。 -- $1 \le Node.val \le 10^7$。 -- `root` 是二叉搜索树。 -- $1 \le val \le 10^7$。 - -**示例**: - -- 示例 1: - -![img](https://assets.leetcode.com/uploads/2021/01/12/tree1.jpg) - -```python -输入:root = [4,2,7,1,3], val = 2 -输出:[2,1,3] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/01/12/tree2.jpg) - -```python -输入:root = [4,2,7,1,3], val = 5 -输出:[] -``` - -## 解题思路 - -### 思路 1:递归 - -1. 从根节点 `root` 开始向下递归遍历。 - 1. 如果 `val` 等于当前节点的值,即 `val == root.val`,则返回 `root`; - 2. 如果 `val` 小于当前节点的值 ,即 `val < root.val`,则递归遍历左子树,继续查找; - 3. 如果 `val` 大于当前节点的值 ,即 `val > root.val`,则递归遍历右子树,继续查找。 -2. 如果遍历到最后也没有找到,则返回空节点。 - -### 思路 1:代码 - -```python -class Solution: - def searchBST(self, root: TreeNode, val: int) -> TreeNode: - if not root or val == root.val: - return root - if val < root.val: - return self.searchBST(root.left, val) - else: - return self.searchBST(root.right, val) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0701. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.md" "b/Solutions/0701. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.md" deleted file mode 100644 index bc147561..00000000 --- "a/Solutions/0701. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) - -- 标签:树、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉搜索树的根节点和要插入树中的值 `val`。 - -**要求**:将 `val` 插入到二叉搜索树中,返回新的二叉搜索树的根节点。 - -**说明**: - -- 树中的节点数将在 $[0, 10^4]$ 的范围内。 -- $-10^8 \le Node.val \le 10^8$ -- 所有值 `Node.val` 是独一无二的。 -- $-10^8 \le val \le 10^8$。 -- **保证** $val$ 在原始 BST 中不存在。 - -**示例**: - -- 示例 1: - -```python -输入:root = [4,2,7,1,3], val = 5 -输出:[4,2,7,1,3,5] -解释:另一个满足题目要求可以通过的树是: -``` - -- 示例 2: - -```python -输入:root = [40,20,60,10,30,50,70], val = 25 -输出:[40,20,60,10,30,50,70,null,null,25] -``` - -## 解题思路 - -### 思路 1:递归 - -已知搜索二叉树的性质: - -- 左子树上任意节点值均小于根节点,即 `root.left.val < root.val`。 -- 右子树上任意节点值均大于根节点,即 `root.left.val > root.val`。 - -那么根据 `val` 和当前节点的大小关系,则可以确定将 `val` 插入到当前节点的哪个子树上。具体步骤如下: - -1. 从根节点 `root` 开始向下递归遍历。根据 `val` 值和当前子树节点 `cur` 的大小关系: - 1. 如果 `val < cur.val`,则应在当前节点的左子树继续遍历判断。 - 1. 如果左子树为空,则新建节点,赋值为 `val`。链接到该子树的父节点上。并停止遍历。 - 2. 如果左子树不为空,则继续向左子树移动。 - 2. 如果 `val >= cur.val`,则应在当前节点的右子树继续遍历判断。 - 1. 如果右子树为空,则新建节点,赋值为 `val`。链接到该子树的父节点上。并停止遍历。 - 2. 如果右子树不为空,则继续向左子树移动。 -2. 遍历完返回根节点 `root`。 - -### 思路 1:代码 - -```python -class Solution: - def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: - if not root: - return TreeNode(val) - - cur = root - while cur: - if val < cur.val: - if not cur.left: - cur.left = TreeNode(val) - break - else: - cur = cur.left - else: - if not cur.right: - cur.right = TreeNode(val) - break - else: - cur = cur.right - return root -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0702. \346\220\234\347\264\242\351\225\277\345\272\246\346\234\252\347\237\245\347\232\204\346\234\211\345\272\217\346\225\260\347\273\204.md" "b/Solutions/0702. \346\220\234\347\264\242\351\225\277\345\272\246\346\234\252\347\237\245\347\232\204\346\234\211\345\272\217\346\225\260\347\273\204.md" deleted file mode 100644 index 10ad0626..00000000 --- "a/Solutions/0702. \346\220\234\347\264\242\351\225\277\345\272\246\346\234\252\347\237\245\347\232\204\346\234\211\345\272\217\346\225\260\347\273\204.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0702. 搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) - -- 标签:数组、二分查找、交互 -- 难度:中等 - -## 题目大意 - -给定一个升序数组 nums,但是数组的大小是未知的,只能通过接口 `reader.get(k)` 来获取数组 nums 中第 k 个元素值。如果数组访问越界,则接口返回 `2147483647`。再给定一个数字 target。要求从 nums 中找出 target,并返回下标,如果 nums 中不存在 target,则返回 -1。 - -## 解题思路 - -这道题的关键点在于找到数组的大小,以便确定查找的右边界位置。右边界可以通过倍增的方式快速查找。在查找右边界的同时,也能将左边界的范围进一步缩小。等确定了左右边界,就可以使用二分查找算法快速查找 target。 - -## 代码 - -```python -class Solution: - def binarySearch(self, reader, left, right, target): - while left < right: - mid = left + (right - left) // 2 - if target > reader.get(mid): - left = mid + 1 - else: - right = mid - if reader.get(left) == target: - return left - else: - return -1 - - def search(self, reader, target): - left = 0 - right = 1 - while reader.get(right) < target: - left = right - right <<= 1 - - return self.binarySearch(reader, left, right, target) -``` - diff --git "a/Solutions/0703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240.md" "b/Solutions/0703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240.md" deleted file mode 100644 index e41e37da..00000000 --- "a/Solutions/0703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [0703. 数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) - -- 标签:树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) -- 难度:简单 - -## 题目大意 - -设计一个 ` KthLargest` 类,用于找到数据流中第 `k` 大元素。 - -- `KthLargest(int k, int[] nums)`:使用整数 k 和整数流 nums 初始化对象。 -- `int add(int val)`:将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。 - -## 解题思路 - -- 建立大小为 `k` 的大顶堆,堆中元素保证不超过 k 个。 -- 每次 `add` 操作时,将新元素压入堆中,如果堆中元素超出了 `k` 个,则将堆中最小元素(堆顶)移除。 -- 此时堆中最小元素(堆顶)就是整个数据流中的第 `k` 大元素。 - -## 代码 - -```python -import heapq - -class KthLargest: - - def __init__(self, k: int, nums: List[int]): - self.min_heap = [] - self.k = k - for num in nums: - heapq.heappush(self.min_heap, num) - if len(self.min_heap) > k: - heapq.heappop(self.min_heap) - - def add(self, val: int) -> int: - heapq.heappush(self.min_heap, val) - if len(self.min_heap) > self.k: - heapq.heappop(self.min_heap) - return self.min_heap[0] -``` - diff --git "a/Solutions/0704. \344\272\214\345\210\206\346\237\245\346\211\276.md" "b/Solutions/0704. \344\272\214\345\210\206\346\237\245\346\211\276.md" deleted file mode 100644 index 12d13769..00000000 --- "a/Solutions/0704. \344\272\214\345\210\206\346\237\245\346\211\276.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [0704. 二分查找](https://leetcode.cn/problems/binary-search/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个升序的数组 $nums$,和一个目标值 $target$。 - -**要求**:返回 $target$ 在数组中的位置,如果找不到,则返回 -1。 - -**说明**: - -- 你可以假设 $nums$ 中的所有元素是不重复的。 -- $n$ 将在 $[1, 10000]$之间。 -- $nums$ 的每个元素都将在 $[-9999, 9999]$之间。 - -**示例**: - -- 示例 1: - -```python -输入: nums = [-1,0,3,5,9,12], target = 9 -输出: 4 -解释: 9 出现在 nums 中并且下标为 4 -``` - -- 示例 2: - -```python -输入: nums = [-1,0,3,5,9,12], target = 2 -输出: -1 -解释: 2 不存在 nums 中因此返回 -1 -``` - -## 解题思路 - -### 思路 1:二分查找 - -设定左右节点为数组两端,即 `left = 0`,`right = len(nums) - 1`,代表待查找区间为 $[left, right]$(左闭右闭)。 - -取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 - -- 如果 $target == nums[mid]$,则返回中心位置。 -- 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 -- 如果中心位置值 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 - -### 思路 1:代码 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - # 在区间 [left, right] 内查找 target - while left <= right: - # 取区间中间节点 - mid = (left + right) // 2 - # 如果找到目标值,则直接返回中心位置 - if nums[mid] == target: - return mid - # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 - elif nums[mid] < target: - left = mid + 1 - # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 - else: - right = mid - 1 - # 未搜索到元素,返回 -1 - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0705. \350\256\276\350\256\241\345\223\210\345\270\214\351\233\206\345\220\210.md" "b/Solutions/0705. \350\256\276\350\256\241\345\223\210\345\270\214\351\233\206\345\220\210.md" deleted file mode 100644 index b0aa8c9c..00000000 --- "a/Solutions/0705. \350\256\276\350\256\241\345\223\210\345\270\214\351\233\206\345\220\210.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) - -- 标签:设计、数组、哈希表、链表、哈希函数 -- 难度:简单 - -## 题目大意 - -要求不使用内建的哈希表库,自行实现一个哈希集合(HashSet)。 - -满足以下操作: - -- void add(key) 向哈希集合中插入值 key 。 -- bool contains(key) 返回哈希集合中是否存在这个值 key 。 -- void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 - -## 解题思路 - -可以利用「数组+链表」的方式实现哈希集合。 - -定义一个一维长度为 buckets 的二维数组 table。第一维度用于计算哈希函数,为 key 分桶。第二个维度用于寻找 key 存放的具体位置。第二维度的数组会根据 key 值动态增长,模拟真正的链表。 - -## 代码 - -```python -class MyHashSet: - - def __init__(self): - self.buckets = 1003 - self.table = [[] for _ in range(self.buckets)] - - - def hash(self, key): - return key % self.buckets - - - def add(self, key: int) -> None: - hash_key = self.hash(key) - if key in self.table[hash_key]: - return - self.table[hash_key].append(key) - - - def remove(self, key: int) -> None: - hash_key = self.hash(key) - if key not in self.table[hash_key]: - return - self.table[hash_key].remove(key) - - - def contains(self, key: int) -> bool: - hash_key = self.hash(key) - return key in self.table[hash_key] -``` - diff --git "a/Solutions/0706. \350\256\276\350\256\241\345\223\210\345\270\214\346\230\240\345\260\204.md" "b/Solutions/0706. \350\256\276\350\256\241\345\223\210\345\270\214\346\230\240\345\260\204.md" deleted file mode 100644 index 4665b1cf..00000000 --- "a/Solutions/0706. \350\256\276\350\256\241\345\223\210\345\270\214\346\230\240\345\260\204.md" +++ /dev/null @@ -1,104 +0,0 @@ -# [0706. 设计哈希映射](https://leetcode.cn/problems/design-hashmap/) - -- 标签:设计、数组、哈希表、链表、哈希函数 -- 难度:简单 - -## 题目大意 - -**要求**:不使用任何内建的哈希表库设计一个哈希映射(`HashMap`)。 - -需要满足以下操作: - -- `MyHashMap()` 用空映射初始化对象。 -- `void put(int key, int value) 向 HashMap` 插入一个键值对 `(key, value)` 。如果 `key` 已经存在于映射中,则更新其对应的值 `value`。 -- `int get(int key)` 返回特定的 `key` 所映射的 `value`;如果映射中不包含 `key` 的映射,返回 `-1`。 -- `void remove(key)` 如果映射中存在 key 的映射,则移除 `key` 和它所对应的 `value` 。 - -**说明**: - -- $0 \le key, value \le 10^6$。 -- 最多调用 $10^4$ 次 `put`、`get` 和 `remove` 方法。 - -**示例**: - -- 示例 1: - -```python -输入: -["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] -[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] -输出: -[null, null, null, 1, -1, null, 1, null, -1] - -解释: -MyHashMap myHashMap = new MyHashMap(); -myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] -myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] -myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] -myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] -myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) -myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] -myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] -myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] -``` - -## 解题思路 - -### 思路 1:链地址法 - -和 [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) 类似。这里我们使用「链地址法」来解决哈希冲突。即利用「数组 + 链表」的方式实现哈希集合。 - -1. 定义哈希表长度 `buckets` 为 `1003`。 -2. 定义一个一维长度为 `buckets` 的二维数组 `table`。其中第一维度用于计算哈希函数,为关键字 `key` 分桶。第二个维度用于存放 `key` 和对应的 `value`。第二维度的数组会根据 `key` 值动态增长,用数组模拟真正的链表。 -3. 定义一个 `hash(key)` 的方法,将 `key` 转换为对应的地址 `hash_key`。 -4. 进行 `put` 操作时,根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 - 1. 如果找到与 `key` 值相同的元素,则更改该元素对应的 `value` 值。 - 2. 如果没找到与 `key` 值相同的元素,则在第二维数组 `table[hask_key]` 中增加元素,元素为 `(key, value)` 组成的元组。 - -5. 进行 `get` 操作跟 `put` 操作差不多。根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 - 1. 如果找到与 `key` 值相同的元素,则返回该元素对应的 `value`。 - 2. 如果没找到与 `key` 值相同的元素,则返回 `-1`。 - -### 思路 1:代码 - -```python -class MyHashMap: - - def __init__(self): - self.buckets = 1003 - self.table = [[] for _ in range(self.buckets)] - - - def hash(self, key): - return key % self.buckets - - - def put(self, key: int, value: int) -> None: - hash_key = self.hash(key) - for item in self.table[hash_key]: - if key == item[0]: - item[1] = value - return - self.table[hash_key].append([key, value]) - - - def get(self, key: int) -> int: - hash_key = self.hash(key) - for item in self.table[hash_key]: - if key == item[0]: - return item[1] - return -1 - - - def remove(self, key: int) -> None: - hash_key = self.hash(key) - for i, item in enumerate(self.table[hash_key]): - if key == item[0]: - self.table[hash_key].pop(i) - return -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\frac{n}{b})$。其中 $n$ 为哈希表中元素数量,$b$ 为链表的数量。 -- **空间复杂度**:$O(n + b)$。 \ No newline at end of file diff --git "a/Solutions/0707. \350\256\276\350\256\241\351\223\276\350\241\250.md" "b/Solutions/0707. \350\256\276\350\256\241\351\223\276\350\241\250.md" deleted file mode 100644 index 50973c2c..00000000 --- "a/Solutions/0707. \350\256\276\350\256\241\351\223\276\350\241\250.md" +++ /dev/null @@ -1,245 +0,0 @@ -# [0707. 设计链表](https://leetcode.cn/problems/design-linked-list/) - -- 标签:设计、链表 -- 难度:中等 - -## 题目大意 - -**要求**:设计实现一个链表,需要支持以下操作: - -- `get(index)`:获取链表中第 `index` 个节点的值。如果索引无效,则返回 `-1`。 -- `addAtHead(val)`:在链表的第一个元素之前添加一个值为 `val` 的节点。插入后,新节点将成为链表的第一个节点。 -- `addAtTail(val)`:将值为 `val` 的节点追加到链表的最后一个元素。 -- `addAtIndex(index, val)`:在链表中的第 `index` 个节点之前添加值为 `val` 的节点。如果 `index` 等于链表的长度,则该节点将附加到链表的末尾。如果 `index` 大于链表长度,则不会插入节点。如果 `index` 小于 `0`,则在头部插入节点。 -- `deleteAtIndex(index)`:如果索引 `index` 有效,则删除链表中的第 `index` 个节点。 - -**说明**: - -- 所有`val`值都在 $[1, 1000]$ 之内。 -- 操作次数将在 $[1, 1000]$ 之内。 -- 请不要使用内置的 `LinkedList` 库。 - -**示例**: - -- 示例 1: - -```python -MyLinkedList linkedList = new MyLinkedList(); -linkedList.addAtHead(1); -linkedList.addAtTail(3); -linkedList.addAtIndex(1,2); // 链表变为 1 -> 2 -> 3 -linkedList.get(1); // 返回 2 -linkedList.deleteAtIndex(1); // 现在链表是 1-> 3 -linkedList.get(1); // 返回 3 -``` - -## 解题思路 - -### 思路 1:单链表 - -新建一个带有 `val` 值 和 `next` 指针的链表节点类, 然后按照要求对节点进行操作。 - -### 思路 1:代码 - -```python -class ListNode: - def __init__(self, x): - self.val = x - self.next = None - - -class MyLinkedList: - def __init__(self): - """ - Initialize your data structure here. - """ - self.size = 0 - self.head = ListNode(0) - - - def get(self, index: int) -> int: - """ - Get the value of the index-th node in the linked list. If the index is invalid, return -1. - """ - if index < 0 or index >= self.size: - return -1 - - curr = self.head - for _ in range(index + 1): - curr = curr.next - return curr.val - - - def addAtHead(self, val: int) -> None: - """ - Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. - """ - self.addAtIndex(0, val) - - - def addAtTail(self, val: int) -> None: - """ - Append a node of value val to the last element of the linked list. - """ - self.addAtIndex(self.size, val) - - - def addAtIndex(self, index: int, val: int) -> None: - """ - Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. - """ - if index > self.size: - return - - if index < 0: - index = 0 - - self.size += 1 - pre = self.head - for _ in range(index): - pre = pre.next - - add_node = ListNode(val) - add_node.next = pre.next - pre.next = add_node - - - def deleteAtIndex(self, index: int) -> None: - """ - Delete the index-th node in the linked list, if the index is valid. - """ - if index < 0 or index >= self.size: - return - - self.size -= 1 - pre = self.head - for _ in range(index): - pre = pre.next - - pre.next = pre.next.next -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**: - - `addAtHead(val)`:$O(1)$。 - - `get(index)`、`addAtTail(val)`、`del eteAtIndex(index)`:$O(k)$。$k$ 指的是元素的索引。 - - `addAtIndex(index, val)`:$O(n)$。$n$ 指的是链表的元素个数。 - -- **空间复杂度**:$O(1)$。 - -### 思路 2:双链表 - -新建一个带有 `val` 值和 `next` 指针、`prev` 指针的链表节点类,然后按照要求对节点进行操作。 - -### 思路 2:代码 - -```python -class ListNode: - def __init__(self, x): - self.val = x - self.next = None - self.prev = None - -class MyLinkedList: - def __init__(self): - """ - Initialize your data structure here. - """ - self.size = 0 - self.head = ListNode(0) - self.tail = ListNode(0) - self.head.next = self.tail - self.tail.prev = self.head - - - def get(self, index: int) -> int: - """ - Get the value of the index-th node in the linked list. If the index is invalid, return -1. - """ - if index < 0 or index >= self.size: - return -1 - - if index + 1 < self.size - index: - curr = self.head - for _ in range(index + 1): - curr = curr.next - else: - curr = self.tail - for _ in range(self.size - index): - curr = curr.prev - return curr.val - - - def addAtHead(self, val: int) -> None: - """ - Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. - """ - self.addAtIndex(0, val) - - - def addAtTail(self, val: int) -> None: - """ - Append a node of value val to the last element of the linked list. - """ - self.addAtIndex(self.size, val) - - - def addAtIndex(self, index: int, val: int) -> None: - """ - Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. - """ - if index > self.size: - return - - if index < 0: - index = 0 - - if index < self.size - index: - prev = self.head - for _ in range(index): - prev = prev.next - next = prev.next - else: - next = self.tail - for _ in range(self.size - index): - next = next.prev - prev = next.prev - - self.size += 1 - add_node = ListNode(val) - add_node.prev = prev - add_node.next = next - prev.next = add_node - next.prev = add_node - - def deleteAtIndex(self, index: int) -> None: - """ - Delete the index-th node in the linked list, if the index is valid. - """ - if index < 0 or index >= self.size: - return - - if index < self.size - index: - prev = self.head - for _ in range(index): - prev = prev.next - next = prev.next.next - else: - next = self.tail - for _ in range(self.size - index - 1): - next = next.prev - prev = next.prev.prev - - self.size -= 1 - prev.next = next - next.prev = prev -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**: - - `addAtHead(val)`、`addAtTail(val)`:$O(1)$。 - - `get(index)`、`addAtIndex(index, val)`、`del eteAtIndex(index)`:$O(min(k, n - k))$。$n$ 指的是链表的元素个数,$k$ 指的是元素的索引。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0708. \345\276\252\347\216\257\346\234\211\345\272\217\345\210\227\350\241\250\347\232\204\346\217\222\345\205\245.md" "b/Solutions/0708. \345\276\252\347\216\257\346\234\211\345\272\217\345\210\227\350\241\250\347\232\204\346\217\222\345\205\245.md" deleted file mode 100644 index 58c35e17..00000000 --- "a/Solutions/0708. \345\276\252\347\216\257\346\234\211\345\272\217\345\210\227\350\241\250\347\232\204\346\217\222\345\205\245.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [0708. 循环有序列表的插入](https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/) - -- 标签:链表 -- 难度:中等 - -## 题目大意 - -给定循环升序链表中的一个节点 `head` 和一个整数 `insertVal`。 - -要求:将整数 `insertVal` 插入循环升序链表中,并且满足链表仍为循环升序链表。最终返回原先给定的节点。 - -## 解题思路 - -- 先判断所给节点 `head` 是否为空,为空直接创建一个值为 `insertVal` 的新节点,并指向自己,返回即可。 - -- 如果 `head` 不为空,把 `head` 赋值给 `node` ,方便最后返回原节点 `head`。 -- 然后遍历 `node`,判断插入值 `insertVal` 与 `node.val` 和 `node.next.val` 的关系,找到插入位置,具体判断如下: - - 如果新节点值在两个节点值中间, 即 `node.val <= insertVal <= node.next.val`。则说明新节点值在最大值最小值中间,应将新节点插入到当前位置,则应将 `insertVal` 插入到这个位置。 - - 如果新节点值比当前节点值和当前节点下一节点值都大,并且当前节点值比当前节点值的下一节点值大,即 `node.next.val < node.val <= insertVal`,则说明 `insertVal` 比链表最大值都大,应插入最大值后边。 - - 如果新节点值比当前节点值和当前节点下一节点值都小,并且当前节点值比当前节点值的下一节点值大,即 `insertVal < node.next.val < node.val`,则说明 `insertVal` 比链表中最小值都小,应插入最小值前边。 -- 找到插入位置后,跳出循环,在插入位置插入值为 `insertVal` 的新节点。 - -## 代码 - -```python -class Solution: - def insert(self, head: 'Node', insertVal: int) -> 'Node': - if not head: - node = Node(insertVal) - node.next = node - return node - - node = head - while node.next != head: - if node.val <= insertVal <= node.next.val: - break - elif node.next.val < node.val <= insertVal: - break - elif insertVal < node.next.val < node.val: - break - else: - node = node.next - - insert_node = Node(insertVal) - insert_node.next = node.next - node.next = insert_node - return head -``` - diff --git "a/Solutions/0709. \350\275\254\346\215\242\346\210\220\345\260\217\345\206\231\345\255\227\346\257\215.md" "b/Solutions/0709. \350\275\254\346\215\242\346\210\220\345\260\217\345\206\231\345\255\227\346\257\215.md" deleted file mode 100644 index fe43b437..00000000 --- "a/Solutions/0709. \350\275\254\346\215\242\346\210\220\345\260\217\345\206\231\345\255\227\346\257\215.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [0709. 转换成小写字母](https://leetcode.cn/problems/to-lower-case/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $s$。 - -**要求**:将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。 - -**说明**: - -- $1 \le s.length \le 100$。 -- $s$ 由 ASCII 字符集中的可打印字符组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "Hello" -输出:"hello" -``` - -- 示例 2: - -```python -输入:s = "LOVELY" -输出:"lovely" -``` - -## 解题思路 - -### 思路 1:直接模拟 - -- 大写字母 $A \sim Z$ 的 ASCII 码范围为 $[65, 90]$。 -- 小写字母 $a \sim z$ 的 ASCII 码范围为 $[97, 122]$。 - -将大写字母的 ASCII 码加 $32$,就得到了对应的小写字母,则解决步骤如下: - -1. 使用一个字符串变量 $ans$ 存储最终答案字符串。 -2. 遍历字符串 $s$,对于当前字符 $ch$: - 1. 如果 $ch$ 的 ASCII 码范围在 $[65, 90]$,则说明 $ch$ 为大写字母。将 $ch$ 的 ASCII 码增加 $32$,再转换为对应的字符,存入字符串 $ans$ 的末尾。 - 2. 如果 $ch$ 的 ASCII 码范围不在 $[65, 90]$,则说明 $ch$ 为小写字母。直接将 $ch$ 存入字符串 $ans$ 的末尾。 -3. 遍历完字符串 $s$,返回答案字符串 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def toLowerCase(self, s: str) -> str: - ans = "" - for ch in s: - if ord('A') <= ord(ch) <= ord('Z'): - ans += chr(ord(ch) + 32) - else: - ans += ch - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 - -### 思路 2:使用 API - -Python 语言中自带大写字母转小写字母的 API:`lower()`,用 API 转换完成之后,直接返回新的字符串。 - -### 思路 2:代码 - -```python -class Solution: - def toLowerCase(self, s: str) -> str: - return s.lower() -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 \ No newline at end of file diff --git "a/Solutions/0713. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0713. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index b3a676e3..00000000 --- "a/Solutions/0713. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) - -- 标签:数组、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个正整数数组 $nums$ 和整数 $k$。 - -**要求**:找出该数组内乘积小于 $k$ 的连续的子数组的个数。 - -**说明**: - -- $1 \le nums.length \le 3 * 10^4$。 -- $1 \le nums[i] \le 1000$。 -- $0 \le k \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [10,5,2,6], k = 100 -输出:8 -解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。 -``` - -- 示例 2: - -```python -输入:nums = [1,2,3], k = 0 -输出:0 -``` - -## 解题思路 - -### 思路 1:滑动窗口(不定长度) - -1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 $window\underline{}product$ 都小于 $k$。使用 $window\underline{}product$ 记录窗口中的乘积值,使用 $count$ 记录符合要求的子数组个数。 -2. 一开始,$left$、$right$ 都指向 $0$。 -3. 向右移动 $right$,将最右侧元素加入当前子数组乘积 $window\underline{}product$ 中。 -4. 如果 $window\underline{}product \ge k$,则不断右移 $left$,缩小滑动窗口长度,并更新当前乘积值 $window\underline{}product$ 直到 $window\underline{}product < k$。 -5. 记录累积答案个数加 $1$,继续右移 $right$,直到 $right \ge len(nums)$ 结束。 -6. 输出累积答案个数。 - -### 思路 1:代码 - -```python -class Solution: - def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int: - if k <= 1: - return 0 - - size = len(nums) - left = 0 - right = 0 - window_product = 1 - - count = 0 - - while right < size: - window_product *= nums[right] - - while window_product >= k: - window_product /= nums[left] - left += 1 - - count += (right - left + 1) - right += 1 - - return count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0714. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.md" "b/Solutions/0714. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.md" deleted file mode 100644 index f22375c1..00000000 --- "a/Solutions/0714. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [0714. 买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `prices`,其中第 `i` 个元素代表了第 `i` 天的股票价格 ;整数 `fee` 代表了交易股票的手续费用。 - -你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 - -最后要求返回获得利润的最大值。 - -## 解题思路 - -这道题的解题思路和「[0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)」类似,同样可以买卖多次。122 题是在跌入谷底的时候买入,在涨到波峰的时候卖出,这道题多了手续费,则在判断波峰波谷的时候还要考虑手续费。贪心策略如下: - -- 当股票价格小于当前最低股价时,更新最低股价,不卖出。 -- 当股票价格大于最小价格 + 手续费时,累积股票利润(实质上暂未卖出,等到波峰卖出),同时最低股价减去手续费,以免重复计算。 - -## 代码 - -```python -class Solution: - def maxProfit(self, prices: List[int], fee: int) -> int: - res = 0 - min_price = prices[0] - - for i in range(1, len(prices)): - if prices[i] < min_price: - min_price = prices[i] - elif prices[i] > min_price + fee: - res += prices[i] - min_price - fee - min_price = prices[i] - fee - return res -``` - diff --git "a/Solutions/0715. Range \346\250\241\345\235\227.md" "b/Solutions/0715. Range \346\250\241\345\235\227.md" deleted file mode 100644 index 941a57c0..00000000 --- "a/Solutions/0715. Range \346\250\241\345\235\227.md" +++ /dev/null @@ -1,133 +0,0 @@ -# [0715. Range 模块](https://leetcode.cn/problems/range-module/) - -- 标签:设计、线段树、有序集合 -- 难度:困难 - -## 题目大意 - -**描述**:`Range` 模块是跟踪数字范围的模块。 - -**要求**: - -- 设计一个数据结构来跟踪查询半开区间 `[left, right)` 内的数字是否被跟踪。 -- 实现 `RangeModule` 类: - - `RangeModule()` 初始化数据结构的对象。 - - `void addRange(int left, int right)` 添加半开区间 `[left, right)`,跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 `[left, right)` 中尚未跟踪的任何数字到该区间中。 - - `boolean queryRange(int left, int right)` 只有在当前正在跟踪区间 `[left, right)` 中的每一个实数时,才返回 `True` ,否则返回 `False`。 - - `void removeRange(int left, int right)` 停止跟踪半开区间 `[left, right)` 中当前正在跟踪的每个实数。 - -**说明**: - -- $1 \le left < right \le 10^9$。 - -**示例**: - -- 示例 1: - -``` -rangeModule = RangeModule() -> null -rangeModule.addRange(10, 20) -> null -rangeModule.removeRange(14, 16) -> null -rangeModule.queryRange(10, 14) -> True -rangeModule.queryRange(13, 15) -> False -rangeModule.queryRange(16, 17) -> True -``` - -## 解题思路 - -### 思路 1:线段树 - -这道题可以使用线段树来做,但是效率比较差。 - -区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。题目要求的是半开区间 `[left, right)` ,而线段树中常用的是闭合区间。但是我们可以将半开区间 `[left, right)` 转为 `[left, right - 1]` 的闭合空间。 - -这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 - -## 代码 - -### 思路 1 代码: - -```python -# 线段树的节点类 -class TreeNode: - def __init__(self, left, right, val=False, lazy_tag=None, letNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = (left + right) >> 1 - self.leftNode = letNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 - - -class RangeModule: - - def __init__(self): - self.tree = TreeNode(0, int(1e9)) - - # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.leftNode and node.rightNode: - node.val = node.leftNode.val and node.rightNode.val - else: - node.val = False - - # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if not node.leftNode: - node.leftNode = TreeNode(node.left, node.mid) - if not node.rightNode: - node.rightNode = TreeNode(node.mid + 1, node.right) - if node.lazy_tag is not None: - node.leftNode.lazy_tag = node.lazy_tag # 更新左子节点懒惰标记 - node.leftNode.val = node.lazy_tag # 左子节点每个元素值增加 lazy_tag - - node.rightNode.lazy_tag = node.lazy_tag # 更新右子节点懒惰标记 - node.rightNode.val = node.lazy_tag # 右子节点每个元素值增加 lazy_tag - - node.lazy_tag = None # 更新当前节点的懒惰标记 - - # 区间更新 - def __update_interval(self, q_left, q_right, val, node): - if q_left <= node.left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - node.lazy_tag = val # 将当前节点的延迟标记增加 val - node.val = val # 当前节点所在区间每个元素值增加 val - return - - self.__pushdown(node) - - if q_left <= node.mid: - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if q_left <= node.left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - - # 需要向下更新节点所在区间的左右子节点的值和懒惰标记 - self.__pushdown(node) - - if q_right <= node.mid: - return self.__query_interval(q_left, q_right, node.leftNode) - if q_left > node.mid: - return self.__query_interval(q_left, q_right, node.rightNode) - - return self.__query_interval(q_left, q_right, node.leftNode) and self.__query_interval(q_left, q_right, node.rightNode) # 返回左右子树元素值的聚合计算结果 - - - def addRange(self, left: int, right: int) -> None: - self.__update_interval(left, right - 1, True, self.tree) - - - def queryRange(self, left: int, right: int) -> bool: - return self.__query_interval(left, right - 1, self.tree) - - - def removeRange(self, left: int, right: int) -> None: - self.__update_interval(left, right - 1, False, self.tree) -``` - diff --git "a/Solutions/0718. \346\234\200\351\225\277\351\207\215\345\244\215\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0718. \346\234\200\351\225\277\351\207\215\345\244\215\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 59fe557e..00000000 --- "a/Solutions/0718. \346\234\200\351\225\277\351\207\215\345\244\215\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,188 +0,0 @@ -# [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) - -- 标签:数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数数组 $nums1$、$nums2$。 - -**要求**:计算两个数组中公共的、长度最长的子数组长度。 - -**说明**: - -- $1 \le nums1.length, nums2.length \le 1000$。 -- $0 \le nums1[i], nums2[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] -输出:3 -解释:长度最长的公共子数组是 [3,2,1] 。 -``` - -- 示例 2: - -```python -输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] -输出:5 -``` - -## 解题思路 - -### 思路 1:暴力(超时) - -1. 枚举数组 $nums1$ 和 $nums2$ 的子数组开始位置 $i$、$j$。 -2. 如果遇到相同项,即 $nums1[i] == nums2[j]$,则以 $nums1[i]$、$nums2[j]$ 为前缀,同时向后遍历,计算当前的公共子数组长度 $subLen$ 最长为多少。 -3. 直到遇到超出数组范围或者 $nums1[i + subLen] == nums2[j + subLen]$ 情况时,停止遍历,并更新答案。 -4. 继续执行 $1 \sim 3$ 步,直到遍历完,输出答案。 - -### 思路 1:代码 - -```python -class Solution: - def findLength(self, nums1: List[int], nums2: List[int]) -> int: - size1, size2 = len(nums1), len(nums2) - ans = 0 - for i in range(size1): - for j in range(size2): - if nums1[i] == nums2[j]: - subLen = 1 - while i + subLen < size1 and j + subLen < size2 and nums1[i + subLen] == nums2[j + subLen]: - subLen += 1 - ans = max(ans, subLen) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m \times min(n, m))$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:滑动窗口 - -暴力方法中,因为子数组在两个数组中的位置不同,所以会导致子数组之间会进行多次比较。 - -我们可以将两个数组分别看做是两把直尺。然后将数组 $nums1$ 固定, 让 $nums2$ 的尾部与 $nums1$ 的头部对齐,如下所示。 - -```python -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] -``` - -然后逐渐向右移动直尺 $nums2$,比较 $nums1$ 与 $nums2$ 重叠部分中的公共子数组的长度,直到直尺 $nums2$ 的头部移动到 $nums1$ 的尾部。 - -```python -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] - -nums1 = [1, 2, 3, 2, 1] -nums2 = [3, 2, 1, 4, 7] -``` - -在这个过程中求得的 $nums1$ 与 $nums2$ 重叠部分中的最大的公共子数组的长度就是 $nums1$ 与 $nums2$ 数组中公共的、长度最长的子数组长度。 - -### 思路 2:代码 - -```python -class Solution: - def findMaxLength(self, nums1, nums2, i, j): - size1, size2 = len(nums1), len(nums2) - max_len = 0 - cur_len = 0 - while i < size1 and j < size2: - if nums1[i] == nums2[j]: - cur_len += 1 - max_len = max(max_len, cur_len) - else: - cur_len = 0 - i += 1 - j += 1 - return max_len - - def findLength(self, nums1: List[int], nums2: List[int]) -> int: - size1, size2 = len(nums1), len(nums2) - res = 0 - for i in range(size1): - res = max(res, self.findMaxLength(nums1, nums2, i, 0)) - - for i in range(size2): - res = max(res, self.findMaxLength(nums1, nums2, 0, i)) - - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n + m) \times min(n, m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 -- **空间复杂度**:$O(1)$。 - -### 思路 3:动态规划 - -###### 1. 划分阶段 - -按照子数组结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。 - -###### 3. 状态转移方程 - -1. 如果 $nums1[i - 1] = nums2[j - 1]$,则当前元素可以构成公共子数组,此时 $dp[i][j] = dp[i - 1][j - 1] + 1$。 -2. 如果 $nums1[i - 1] \ne nums2[j - 1]$,则当前元素不能构成公共子数组,此时 $dp[i][j] = 0$。 - -###### 4. 初始条件 - -- 当 $i = 0$ 时,$nums1[0]...nums1[i - 1]$ 表示的是空数组,空数组与 $nums2[0]...nums2[j - 1]$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 -- 当 $j = 0$ 时,$nums2[0]...nums2[j - 1]$ 表示的是空数组,空数组与 $nums1[0]...nums1[i - 1]$ 的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 - -###### 5. 最终结果 - -- 根据状态定义, $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。在遍历过程中,我们可以使用 $res$ 记录下所有 $dp[i][j]$ 中最大值即为答案。 - -### 思路 3:代码 - -```python -class Solution: - def findLength(self, nums1: List[int], nums2: List[int]) -> int: - size1 = len(nums1) - size2 = len(nums2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - res = 0 - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if nums1[i - 1] == nums2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - if dp[i][j] > res: - res = dp[i][j] - - return res -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 -- **空间复杂度**:$O(n \times m)$。 \ No newline at end of file diff --git "a/Solutions/0719. \346\211\276\345\207\272\347\254\254 K \345\260\217\347\232\204\346\225\260\345\257\271\350\267\235\347\246\273.md" "b/Solutions/0719. \346\211\276\345\207\272\347\254\254 K \345\260\217\347\232\204\346\225\260\345\257\271\350\267\235\347\246\273.md" deleted file mode 100644 index 5a9e7d84..00000000 --- "a/Solutions/0719. \346\211\276\345\207\272\347\254\254 K \345\260\217\347\232\204\346\225\260\345\257\271\350\267\235\347\246\273.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [0719. 找出第 k 小的距离对](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) - -- 标签:数组、双指针、二分查找、排序 -- 难度:困难 - -## 题目大意 - -给定一个整数数组 nums,对于数组中不同的数 nums[i]、nums[j] 之间的距离定义为 nums[i] 和 nums[j] 的绝对差值,即 `dist(nums[i], nums[j]) = abs(nums[i] - nums[j])`。求所有数对之间第 k 个最小距离。 - -## 解题思路 - -一般来说 topK 问题都可以用堆排序来解决。但是这道题使用堆排序超时了。所以需要换其他方法。 - -先来考虑第 k 个最小距离的范围。这个范围一定在 `[0, max(nums) - min(nums)]` 之间。 - -我们可以对 nums 先进行排序,然后得到最小距离为 0,最大距离为 `nums[-1] - nums[0]`。我们可以在这个区间上进行二分,对于二分的位置 mid,统计距离小于等于 mid 的距离对数,并根据它和 k 的关系调整区间上下界。 - -统计对数可以使用双指针来计算出所有小于等于 mid 的距离对数目。维护两个指针 left、right。left、right 都指向数组开头位置。然后不断移动 right,计算 nums[right] 和 nums[left] 之间的距离,如果大于 mid,则 left 向右移动,直到 距离小于等于 mid 时,统计当前距离对数为 right - left。最终将这些符合要求的距离对数累加,就得到了所有小于等于 mid 的距离对数目。 - -## 代码 - -```python -class Solution: - def smallestDistancePair(self, nums: List[int], k: int) -> int: - def get_count(dist): - left, count = 0, 0 - for right in range(1, len(nums)): - while nums[right] - nums[left] > dist: - left += 1 - count += (right - left) - return count - - nums.sort() - left, right = 0, nums[-1] - nums[0] - while left < right: - mid = left + (right - left) // 2 - if get_count(mid) >= k: - right = mid - else: - left = mid + 1 - return left -``` - diff --git "a/Solutions/0720. \350\257\215\345\205\270\344\270\255\346\234\200\351\225\277\347\232\204\345\215\225\350\257\215.md" "b/Solutions/0720. \350\257\215\345\205\270\344\270\255\346\234\200\351\225\277\347\232\204\345\215\225\350\257\215.md" deleted file mode 100644 index a27274ac..00000000 --- "a/Solutions/0720. \350\257\215\345\205\270\344\270\255\346\234\200\351\225\277\347\232\204\345\215\225\350\257\215.md" +++ /dev/null @@ -1,71 +0,0 @@ -# [0720. 词典中最长的单词](https://leetcode.cn/problems/longest-word-in-dictionary/) - -- 标签:字典树、数组、哈希表、字符串、排序 -- 难度:中等 - -## 题目大意 - -给出一个字符串数组 `words` 组成的一本英语词典。 - -要求:从中找出最长的一个单词,该单词是由 `words` 词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。 - -## 解题思路 - -使用字典树存储每一个单词。再在字典树中查找每一个单词,查找的时候判断是否有以当前单词为前缀的单词。如果有,则该单词可以由前缀构成的单词逐步添加字母获得。此时,如果该单词比答案单词更长,则维护更新答案单词。 - -最后输出答案单词。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children or not cur.children[ch].isEnd: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def longestWord(self, words: List[str]) -> str: - - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - ans = "" - for word in words: - if trie_tree.search(word): - if len(word) > len(ans): - ans = word - elif len(word) == len(ans) and word < ans: - ans = word - return ans -``` - diff --git "a/Solutions/0724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207.md" "b/Solutions/0724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207.md" deleted file mode 100644 index 14b036fe..00000000 --- "a/Solutions/0724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) - -- 标签:数组、前缀和 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $nums$。 - -**要求**:找到「左侧元素和」与「右侧元素和相等」的位置,若找不到,则返回 $-1$。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $-1000 \le nums[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1, 7, 3, 6, 5, 6] -输出:3 -解释: -中心下标是 3 。 -左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11, -右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11,二者相等。 -``` - -- 示例 2: - -```python -输入:nums = [1, 2, 3] -输出:-1 -解释: -数组中不存在满足此条件的中心下标。 -``` - -## 解题思路 - -### 思路 1:两次遍历 - -两次遍历,第一次遍历先求出数组全部元素和。第二次遍历找到左侧元素和恰好为全部元素和一半的位置。 - -### 思路 1:代码 - -```python -class Solution: - def pivotIndex(self, nums: List[int]) -> int: - sum = 0 - for i in range(len(nums)): - sum += nums[i] - curr_sum = 0 - for i in range(len(nums)): - if curr_sum * 2 + nums[i] == sum: - return i - curr_sum += nums[i] - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。两次遍历的时间复杂度为 $O(2 \times n)$ ,$O(2 \times n) == O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0727. \346\234\200\345\260\217\347\252\227\345\217\243\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0727. \346\234\200\345\260\217\347\252\227\345\217\243\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index 593093f4..00000000 --- "a/Solutions/0727. \346\234\200\345\260\217\347\252\227\345\217\243\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [0727. 最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) - -- 标签:字符串、动态规划、滑动窗口 -- 难度:困难 - -## 题目大意 - -给定字符串 `s1` 和 `s2`。 - -要求:找出 `s1` 中最短的(连续)子串 `w`,使得 `s2` 是 `w` 的子序列 。如果 `s1` 中没有窗口可以包含 `s2` 中的所有字符,返回空字符串 `""`。如果有不止一个最短长度的窗口,返回开始位置最靠左的那个。 - -## 解题思路 - -这道题跟「[76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)」有点类似。但这道题中字符的相对顺序需要保持一致。求解的思路如下: - -- 向右扩大窗口,匹配字符,直到匹配完 `s2` 的最后一个字符。 -- 当满足条件时,缩小窗口,并更新最小窗口的起始位置和最短长度。 -- 缩小窗口到不满足条件为止。 - -这道题的难点在于第二步中如何缩小窗口。当匹配到一个子序列时,可以采用逆向匹配的方式,从 `s2` 的最后一位字符匹配到 `s2` 的第一位字符。找到符合要求的最大下标,即是窗口的左边界。 - -整个算法的解题步骤如下: - -- 使用两个指针 `left`、`right` 代表窗口的边界,一开始都指向 `0` 。`min_len` 用来记录最小子序列的长度。`i`、`j` 作为索引,用于遍历字符串 `s1` 和 `s2`,一开始都为 `0`。 -- 遍历字符串 `s1` 的每一个字符,如果 `s1[i] == s2[j]`,则说明 `s2` 中第 `j` 个字符匹配了,向右移动 `j`,即 `j += 1`,然后继续匹配。 -- 如果 `j == len(s2)`,则说明 `s2` 中所有字符都匹配了。 - - 此时确定了窗口的右边界 `right = i`,并令 `j` 指向 `s2` 最后一个字符位置。 - - 从右至左逆向匹配字符串,找到窗口的左边界。 - - 判断当前窗口长度和窗口的最短长度,并更新最小窗口的起始位置和最短长度。 - - 令 `j = 0`,重新继续匹配 `s2`。 -- 向右移动 `i`,继续匹配。 -- 遍历完输出窗口的最短长度(需要判断是否有解)。 - -## 代码 - -```python -class Solution: - def minWindow(self, s1: str, s2: str) -> str: - i, j = 0, 0 - min_len = float('inf') - left, right = 0, 0 - while i < len(s1): - if s1[i] == s2[j]: - j += 1 - # 完成了匹配 - if j == len(s2): - right = i - j -= 1 - while j >= 0: - if s1[i] == s2[j]: - j -= 1 - i -= 1 - i += 1 - if right - i + 1 < min_len: - left = i - min_len = right - left + 1 - j = 0 - i += 1 - if min_len != float('inf'): - return s1[left: left + min_len] - return "" -``` - -## 参考资料 - -- 【题解】[c++ 简单好理解的 滑动窗口解法 和 动态规划解法 - 最小窗口子序列 - 力扣](https://leetcode.cn/problems/minimum-window-subsequence/solution/c-jian-dan-hao-li-jie-de-hua-dong-chuang-wguk/) -- 【题解】[727. 最小窗口子序列 C++ 滑动窗口 - 最小窗口子序列 - 力扣](https://leetcode.cn/problems/minimum-window-subsequence/solution/727-zui-xiao-chuang-kou-zi-xu-lie-c-hua-dong-chuan/) - diff --git "a/Solutions/0729. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 I.md" "b/Solutions/0729. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 I.md" deleted file mode 100644 index 84b0f164..00000000 --- "a/Solutions/0729. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 I.md" +++ /dev/null @@ -1,191 +0,0 @@ -# [0729. 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) - -- 标签:设计、线段树、二分查找、有序集合 -- 难度:中等 - -## 题目大意 - -**要求**:实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的日程安排不会造成重复预订 ,则可以存储这个新的日程安排。 - -日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 - -`MyCalendar` 类: - -- `MyCalendar()` 初始化日历对象。 -- `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 `True` 。否则,返回 `False` 并且不要将该日程安排添加到日历中。 - -**说明**: - -- 重复预订:当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订 。 -- $0 \le start < end \le 10^9$ -- 每个测试用例,调用 `book` 方法的次数最多不超过 `1000` 次。 - -**示例**: - -- 示例 1: - -```python -输入: -["MyCalendar", "book", "book", "book"] -[[], [10, 20], [15, 25], [20, 30]] - -输出: -[null, true, false, true] - -解释: -MyCalendar myCalendar = new MyCalendar(); -myCalendar.book(10, 20); // return True -myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。 -myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。 -``` - -## 解题思路 - -### 思路 1:线段树 - -这道题可以使用线段树来做。 - -因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 - -- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 - -- 在 `book` 方法中,从线段树中查询 `[start, end - 1]` 区间上保存的日程区间个数。 - - 如果日程区间个数大于等于 `1`,则说明该日程添加到日历中会导致重复预订,则直接返回 `False`。 - - 如果日程区间个数小于 `1`,则说明该日程添加到日历中不会导致重复预定,则在线段树中将区间 `[start, end - 1]` 的日程区间个数 + 1,然后返回 `True`。 - -### 思路 1:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = leftNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, function): - self.tree = SegTreeNode(0, int(1e9)) - self.function = function # function 是一个函数,左右区间的聚合方法 - - # 单点更新,将 nums[i] 更改为 val - def update_point(self, i, val): - self.__update_point(i, val, self.tree) - - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间查询,查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self, length): - nums = [0 for _ in range(length)] - for i in range(length): - nums[i] = self.query_interval(i, i) - return nums - - - # 以下为内部实现方法 - - # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] - def __update_point(self, i, val, node): - if node.left == node.right: - node.val = val # 叶子节点,节点值修改为 val - return - - if i <= node.mid: # 在左子树中更新节点值 - self.__update_point(i, val, node.leftNode) - else: # 在右子树中更新节点值 - self.__update_point(i, val, node.rightNode) - self.__pushup(node) # 向上更新节点的区间值 - - # 区间更新 - def __update_interval(self, q_left, q_right, val, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if node.lazy_tag is not None: - node.lazy_tag += val # 将当前节点的延迟标记增加 val - else: - node.lazy_tag = val # 将当前节点的延迟标记增加 val - node.val += val # 当前节点所在区间每个元素值增加 val - return - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - if q_left <= node.mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, node.rightNode) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.leftNode and node.rightNode: - node.val = self.function(node.leftNode.val, node.rightNode.val) - - # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if node.leftNode is None: - node.leftNode = SegTreeNode(node.left, node.mid) - if node.rightNode is None: - node.rightNode = SegTreeNode(node.mid + 1, node.right) - - lazy_tag = node.lazy_tag - if node.lazy_tag is None: - return - - if node.leftNode.lazy_tag is not None: - node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 - node.leftNode.val += lazy_tag # 左子节点每个元素值增加 lazy_tag - - if node.rightNode.lazy_tag is not None: - node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 - node.rightNode.val += lazy_tag # 右子节点每个元素值增加 lazy_tag - - node.lazy_tag = None # 更新当前节点的懒惰标记 - -class MyCalendar: - - def __init__(self): - self.STree = SegmentTree(lambda x, y: max(x, y)) - - - def book(self, start: int, end: int) -> bool: - if self.STree.query_interval(start, end - 1) >= 1: - return False - self.STree.update_interval(start, end - 1, 1) - return True -``` diff --git "a/Solutions/0731. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 II.md" "b/Solutions/0731. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 II.md" deleted file mode 100644 index b993b11c..00000000 --- "a/Solutions/0731. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 II.md" +++ /dev/null @@ -1,190 +0,0 @@ -# [731. 我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) - -- 标签:设计、线段树、二分查找、有序集合 -- 难度:中等 - -## 题目大意 - -**要求**:实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。 - -日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 - -`MyCalendar` 类: - -- `MyCalendar()` 初始化日历对象。 -- `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 `True` 。否则,返回 `False` 并且不要将该日程安排添加到日历中。 - -**说明**: - -- 三重预定:当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订 。 -- $0 \le start < end \le 10^9$。 -- 每个测试用例,调用 `book` 方法的次数最多不超过 `1000` 次。 - -**示例**: - -- 示例 1: - -```python -输入: -["MyCalendar", "book", "book", "book"] -[[], [10, 20], [15, 25], [20, 30]] -输出: -[null, true, false, true] - -解释: -MyCalendar myCalendar = new MyCalendar(); -myCalendar.book(10, 20); // return True -myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。 -myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。 -``` - -## 解题思路 - -### 思路 1:线段树 - -这道题可以使用线段树来做。 - -因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 - -- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 - -- 在 `book` 方法中,从线段树中查询 `[start, end - 1]` 区间上保存的日程区间个数。 - - 如果日程区间个数大于等于 `2`,则说明该日程添加到日历中会导致三重预订,则直接返回 `False`。 - - 如果日程区间个数小于 `2`,则说明该日程添加到日历中不会导致三重预订,则在线段树中将区间 `[start, end - 1]` 的日程区间个数 + 1,然后返回 `True`。 - -### 思路 1:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = leftNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, function): - self.tree = SegTreeNode(0, int(1e9)) - self.function = function # function 是一个函数,左右区间的聚合方法 - - # 单点更新,将 nums[i] 更改为 val - def update_point(self, i, val): - self.__update_point(i, val, self.tree) - - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间查询,查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self, length): - nums = [0 for _ in range(length)] - for i in range(length): - nums[i] = self.query_interval(i, i) - return nums - - - # 以下为内部实现方法 - - # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] - def __update_point(self, i, val, node): - if node.left == node.right: - node.val = val # 叶子节点,节点值修改为 val - return - - if i <= node.mid: # 在左子树中更新节点值 - self.__update_point(i, val, node.leftNode) - else: # 在右子树中更新节点值 - self.__update_point(i, val, node.rightNode) - self.__pushup(node) # 向上更新节点的区间值 - - # 区间更新 - def __update_interval(self, q_left, q_right, val, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if node.lazy_tag is not None: - node.lazy_tag += val # 将当前节点的延迟标记增加 val - else: - node.lazy_tag = val # 将当前节点的延迟标记增加 val - node.val += val # 当前节点所在区间增加 val - return - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - if q_left <= node.mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, node.rightNode) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.leftNode and node.rightNode: - node.val = self.function(node.leftNode.val, node.rightNode.val) - - # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if node.leftNode is None: - node.leftNode = SegTreeNode(node.left, node.mid) - if node.rightNode is None: - node.rightNode = SegTreeNode(node.mid + 1, node.right) - - lazy_tag = node.lazy_tag - if node.lazy_tag is None: - return - - if node.leftNode.lazy_tag is not None: - node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 - node.leftNode.val += lazy_tag # 左子节点区间增加 lazy_tag - - if node.rightNode.lazy_tag is not None: - node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 - node.rightNode.val += lazy_tag # 右子节点区间增加 lazy_tag - - node.lazy_tag = None # 更新当前节点的懒惰标记 - -class MyCalendarTwo: - - def __init__(self): - self.STree = SegmentTree(lambda x, y: max(x, y)) - - - def book(self, start: int, end: int) -> bool: - if self.STree.query_interval(start, end - 1) >= 2: - return False - self.STree.update_interval(start, end - 1, 1) - return True -``` \ No newline at end of file diff --git "a/Solutions/0732. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 III.md" "b/Solutions/0732. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 III.md" deleted file mode 100644 index 06ee93e3..00000000 --- "a/Solutions/0732. \346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250 III.md" +++ /dev/null @@ -1,199 +0,0 @@ -# [0732. 我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) - -- 标签:设计、线段树、二分查找、有序集合 -- 难度:困难 - -## 题目大意 - -**要求**:实现一个 `MyCalendarThree` 类来存放你的日程安排,你可以一直添加新的日程安排。 - -日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 - -`MyCalendarThree` 类: - -- `MyCalendarThree()` 初始化对象。 -- `int book(int start, int end)` 返回一个整数 `k`,表示日历中存在的 `k` 次预订的最大值。 - -**说明**: - -- `k` 次预定:当 `k` 个日程安排有一些时间上的交叉时(例如 `k` 个日程安排都在同一时间内),就会产生 `k` 次预订。 -- $0 \le start < end \le 10^9$ -- 每个测试用例,调用 `book` 函数最多不超过 `400` 次。 - -**示例**: - -- 示例 1: - -```python -输入 -["MyCalendarThree", "book", "book", "book", "book", "book", "book"] -[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]] -输出 -[null, 1, 1, 2, 3, 3, 3] - -解释 -MyCalendarThree myCalendarThree = new MyCalendarThree(); -myCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。 -myCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。 -myCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。 -myCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。 -myCalendarThree.book(5, 10); // 返回 3 -myCalendarThree.book(25, 55); // 返回 3 -``` - -## 解题思路 - -### 思路 1:线段树 - -这道题可以使用线段树来做。 - -因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 - -- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 - -- 在 `book` 方法中,在线段树中更新 `[start, end - 1]` 的交叉日程区间个数,即令其区间值整体加 `1`。 - -- 然后从线段树中查询区间 $[0, 10^9]$ 上保存的交叉日程区间个数,并返回。 - - -### 思路 1:代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = leftNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, function): - self.tree = SegTreeNode(0, int(1e9)) - self.function = function # function 是一个函数,左右区间的聚合方法 - - # 单点更新,将 nums[i] 更改为 val - def update_point(self, i, val): - self.__update_point(i, val, self.tree) - - # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间查询,查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self, length): - nums = [0 for _ in range(length)] - for i in range(length): - nums[i] = self.query_interval(i, i) - return nums - - - # 以下为内部实现方法 - - # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] - def __update_point(self, i, val, node): - if node.left == node.right: - node.val = val # 叶子节点,节点值修改为 val - return - - if i <= node.mid: # 在左子树中更新节点值 - self.__update_point(i, val, node.leftNode) - else: # 在右子树中更新节点值 - self.__update_point(i, val, node.rightNode) - self.__pushup(node) # 向上更新节点的区间值 - - # 区间更新 - def __update_interval(self, q_left, q_right, val, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if node.lazy_tag is not None: - node.lazy_tag += val # 将当前节点的延迟标记增加 val - else: - node.lazy_tag = val # 将当前节点的延迟标记增加 val - node.val += val # 当前节点所在区间增加 val - return - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - if q_left <= node.mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, node.rightNode) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.leftNode and node.rightNode: - node.val = self.function(node.leftNode.val, node.rightNode.val) - - # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if node.leftNode is None: - node.leftNode = SegTreeNode(node.left, node.mid) - if node.rightNode is None: - node.rightNode = SegTreeNode(node.mid + 1, node.right) - - lazy_tag = node.lazy_tag - if node.lazy_tag is None: - return - - if node.leftNode.lazy_tag is not None: - node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 - node.leftNode.val += lazy_tag # 左子节点区间增加 lazy_tag - - if node.rightNode.lazy_tag is not None: - node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 - node.rightNode.val += lazy_tag # 右子节点区间增加 lazy_tag - - node.lazy_tag = None # 更新当前节点的懒惰标记 - - -class MyCalendarThree: - - def __init__(self): - self.STree = SegmentTree(lambda x, y: max(x, y)) - - - def book(self, start: int, end: int) -> int: - self.STree.update_interval(start, end - 1, 1) - return self.STree.query_interval(0, int(1e9)) - - - -# Your MyCalendarThree object will be instantiated and called as such: -# obj = MyCalendarThree() -# param_1 = obj.book(start,end) -``` diff --git "a/Solutions/0733. \345\233\276\345\203\217\346\270\262\346\237\223.md" "b/Solutions/0733. \345\233\276\345\203\217\346\270\262\346\237\223.md" deleted file mode 100644 index 4987c96f..00000000 --- "a/Solutions/0733. \345\233\276\345\203\217\346\270\262\346\237\223.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [0733. 图像渲染](https://leetcode.cn/problems/flood-fill/) - -- 标签:深度优先搜索、广度优先搜索、数组、矩阵 -- 难度:简单 - -## 题目大意 - -给定一个二维数组 image 表示图画,数组的每个元素值表示该位置的像素值大小。再给定一个坐标 (sr, sc) 表示图像渲染开始的位置。然后再给定一个新的颜色值 newColor。现在要求:将坐标 (sr, sc) 以及 (sr, sc) 相连的上下左右区域上与 (sr, sc) 原始颜色相同的区域染色为 newColor。返回染色后的二维数组。 - - - -## 解题思路 - -从起点开始,对上下左右四个方向进行广度优先搜索。每次搜索到一个位置时,如果该位置上的像素值与初始位置像素值相同,则更新该位置像素值,并将该位置加入队列中。最后将二维数组返回。 - -- 注意:如果起点位置初始颜色和新颜色值 newColor 相同,则不需要染色,直接返回原数组即可。 - -## 代码 - -```python -import collections - -class Solution: - def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: - if newColor == image[sr][sc]: - return image - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - queue = collections.deque([(sr, sc)]) - oriColor = image[sr][sc] - while queue: - point = queue.popleft() - image[point[0]][point[1]] = newColor - for direction in directions: - new_i = point[0] + direction[0] - new_j = point[1] + direction[1] - if 0 <= new_i < len(image) and 0 <= new_j < len(image[0]) and image[new_i][new_j] == oriColor: - queue.append((new_i, new_j)) - return image -``` - diff --git "a/Solutions/0735. \350\241\214\346\230\237\347\242\260\346\222\236.md" "b/Solutions/0735. \350\241\214\346\230\237\347\242\260\346\222\236.md" deleted file mode 100644 index ff0314c0..00000000 --- "a/Solutions/0735. \350\241\214\346\230\237\347\242\260\346\222\236.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0735. 行星碰撞](https://leetcode.cn/problems/asteroid-collision/) - -- 标签:栈、数组 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `asteroids`,表示在同一行的小行星。 - -数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。小行星按照下面的规则发生碰撞。 - -- 碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 - -要求:找出碰撞后剩下的所有小行星,将答案存入数组并返回。 - -## 解题思路 - -用栈模拟小行星碰撞,具体步骤如下: - -- 遍历数组 `asteroids`。 -- 如果栈为空或者当前元素 `asteroid` 为正数,将其压入栈。 -- 如果当前栈不为空并且当前元素 `asteroid` 为负数: - - 与栈中元素发生碰撞,判断当前元素和栈顶元素的大小和方向,如果栈顶元素为正数,并且当前元素的绝对值大于栈顶元素,则将栈顶元素弹出,并继续与栈中元素发生碰撞。 - - 碰撞完之后,如果栈为空并且栈顶元素为负数,则将当前元素 `asteroid` 压入栈,表示碰撞完剩下了 `asteroid`。 - - 如果栈顶元素恰好与当前元素值大小相等、方向相反,则弹出栈顶元素,表示碰撞完两者都爆炸了。 -- 最后返回栈作为答案。 - -## 代码 - -```python -class Solution: - def asteroidCollision(self, asteroids: List[int]) -> List[int]: - stack = [] - for asteroid in asteroids: - if not stack or asteroid > 0: - stack.append(asteroid) - else: - while stack and 0 < stack[-1] < -asteroid: - stack.pop() - if not stack or stack[-1] < 0: - stack.append(asteroid) - elif stack[-1] == -asteroid: - stack.pop() - - return stack -``` - diff --git "a/Solutions/0738. \345\215\225\350\260\203\351\200\222\345\242\236\347\232\204\346\225\260\345\255\227.md" "b/Solutions/0738. \345\215\225\350\260\203\351\200\222\345\242\236\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index febff1cc..00000000 --- "a/Solutions/0738. \345\215\225\350\260\203\351\200\222\345\242\236\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [0738. 单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) - -- 标签:贪心、数学 -- 难度:中等 - -## 题目大意 - -给定一个非负整数 n,找出小于等于 n 的最大整数,同时该整数需要满足其各个位数上的数字是单调递增的。 - -## 解题思路 - -为了方便操作,我们先将整数 n 转为 list 数组,即 n_list。 - -题目要求这个整数尽可能的大,那么这个数从高位开始,就应该尽可能的保持不变。那么我们需要从高位到低位,找到第一个满足 `n_list[i - 1] > n_list[i]` 的位置,然后把 `n_list[i] - 1`,再把剩下的低位都变为 9。 - -## 代码 - -```python -class Solution: - def monotoneIncreasingDigits(self, n: int) -> int: - n_list = list(str(n)) - size = len(n_list) - start_i = size - for i in range(size - 1, 0, -1): - if n_list[i - 1] > n_list[i]: - start_i = i - n_list[i - 1] = chr(ord(n_list[i - 1]) - 1) - - for i in range(start_i, size, 1): - n_list[i] = '9' - res = int(''.join(n_list)) - return res -``` - diff --git "a/Solutions/0739. \346\257\217\346\227\245\346\270\251\345\272\246.md" "b/Solutions/0739. \346\257\217\346\227\245\346\270\251\345\272\246.md" deleted file mode 100644 index 702971c3..00000000 --- "a/Solutions/0739. \346\257\217\346\227\245\346\270\251\345\272\246.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) - -- 标签:栈、数组、单调栈 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个列表 `temperatures`,`temperatures[i]` 表示第 `i` 天的气温。 - -**要求**:输出一个列表,列表上每个位置代表「如果要观测到更高的气温,至少需要等待的天数」。如果之后的气温不再升高,则用 `0` 来代替。 - -**说明**: - -- $1 \le temperatures.length \le 10^5$。 -- $30 \le temperatures[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入: temperatures = [73,74,75,71,69,72,76,73] -输出: [1,1,4,2,1,1,0,0] -``` - -- 示例 2: - -```python -输入: temperatures = [30,40,50,60] -输出: [1,1,1,0] -``` - -## 解题思路 - -题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置右侧找到第一个比当前元素更大的元素。求「该元素」与「右侧第一个比当前元素更大的元素」之间的距离,将所有距离保存为数组返回结果。 - -最简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 - -更好的方式使用「单调递增栈」,栈中保存元素的下标。 - -### 思路 1:单调栈 - -1. 首先,将答案数组 `ans` 全部赋值为 0。然后遍历数组每个位置元素。 -2. 如果栈为空,则将当前元素的下标入栈。 -3. 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 -4. 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组 `ans` 中保存起来,判断栈顶元素。 -5. 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 -6. 最后输出答案数组 `ans`。 - -### 思路 1:代码 - -```python -class Solution: - def dailyTemperatures(self, T: List[int]) -> List[int]: - n = len(T) - stack = [] - ans = [0 for _ in range(n)] - for i in range(n): - while stack and T[i] > T[stack[-1]]: - index = stack.pop() - ans[index] = (i-index) - stack.append(i) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0744. \345\257\273\346\211\276\346\257\224\347\233\256\346\240\207\345\255\227\346\257\215\345\244\247\347\232\204\346\234\200\345\260\217\345\255\227\346\257\215.md" "b/Solutions/0744. \345\257\273\346\211\276\346\257\224\347\233\256\346\240\207\345\255\227\346\257\215\345\244\247\347\232\204\346\234\200\345\260\217\345\255\227\346\257\215.md" deleted file mode 100644 index bd9312de..00000000 --- "a/Solutions/0744. \345\257\273\346\211\276\346\257\224\347\233\256\346\240\207\345\255\227\346\257\215\345\244\247\347\232\204\346\234\200\345\260\217\345\255\227\346\257\215.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [0744. 寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -**描述**:给你一个字符数组 $letters$,该数组按非递减顺序排序,以及一个字符 $target$。$letters$ 里至少有两个不同的字符。 - -**要求**:找出 $letters$ 中大于 $target$ 的最小的字符。如果不存在这样的字符,则返回 $letters$ 的第一个字符。 - -**说明**: - -- $2 \le letters.length \le 10^4$。 -- $letters[i]$$ 是一个小写字母。 -- $letters$ 按非递减顺序排序。 -- $letters$ 最少包含两个不同的字母。 -- $target$ 是一个小写字母。 - -**示例**: - -- 示例 1: - -```python -输入: letters = ["c", "f", "j"],target = "a" -输出: "c" -解释:letters 中字典上比 'a' 大的最小字符是 'c'。 -``` - -- 示例 2: - -```python -输入: letters = ["c","f","j"], target = "c" -输出: "f" -解释:letters 中字典顺序上大于 'c' 的最小字符是 'f'。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -利用二分查找,找到比 $target$ 大的字母。注意 $target$ 可能大于 $letters$ 的所有字符,此时应返回 $letters$ 的第一个字母。 - -我们可以假定 $target$ 的取值范围为 $[0, len(letters)]$。当 $target$ 取到 $len(letters)$ 时,说明 $target$ 大于 $letters$ 的所有字符,对 $len(letters)$ 取余即可得到 $letters[0]$。 - -### 思路 1:代码 - -```python -class Solution: - def nextGreatestLetter(self, letters: List[str], target: str) -> str: - n = len(letters) - left = 0 - right = n - while left < right: - mid = left + (right - left) // 2 - if letters[mid] <= target: - left = mid + 1 - else: - right = mid - return letters[left % n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 为字符数组 $letters$ 的长度。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.md" "b/Solutions/0746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.md" deleted file mode 100644 index 199ca660..00000000 --- "a/Solutions/0746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [0746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) - -- 标签:数组、动态规划 -- 难度:简单 - -## 题目大意 - -给定一个数组 `cost` 代表一段楼梯,`cost[i]` 代表爬上第 `i` 阶楼梯醒酒药花费的体力值(下标从 `0` 开始)。 - -每爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 - -要求:找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 `0` 或 `1` 的元素作为初始阶梯。 - -## 解题思路 - -使用动态规划方法。 - -状态 `dp[i]` 表示为:到达第 `i` 个台阶所花费的最少体⼒。 - -则状态转移方程为: `dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]`。 - -表示为:到达第 `i` 个台阶所花费的最少体⼒ = 到达第 `i - 1` 个台阶所花费的最小体力 与 到达第 `i - 2` 个台阶所花费的最小体力中的最小值 + 到达第 `i` 个台阶所需要花费的体力值。 - -## 代码 - -```python -class Solution: - def minCostClimbingStairs(self, cost: List[int]) -> int: - size = len(cost) - dp = [0 for _ in range(size + 1)] - for i in range(2, size+1): - dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]) - return dp[size] -``` - diff --git "a/Solutions/0752. \346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.md" "b/Solutions/0752. \346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.md" deleted file mode 100644 index ddd572bb..00000000 --- "a/Solutions/0752. \346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [0752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) - -- 标签:广度优先搜索、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:有一把带有四个数字的密码锁,每个位置上有 `0` ~ `9` 共 `10` 个数字。每次只能将其中一个位置上的数字转动一下。可以向上转,也可以向下转。比如:`1 -> 2`、`2 -> 1`。 - -密码锁的初始数字为:`0000`。现在给定一组表示死亡数字的字符串数组 `deadends`,和一个带有四位数字的目标字符串 `target`。 - -如果密码锁转动到 `deadends` 中任一字符串状态,则锁就会永久锁定,无法再次旋转。 - -**要求**:给出使得锁的状态由 `0000` 转动到 `target` 的最小的选择次数。如果无论如何不能解锁,返回 `-1` 。 - -**说明**: - -- $1 \le deadends.length \le 500$ - $deadends[i].length == 4$ - $target.length == 4$ - $target$ 不在 $deadends$ 之中 - $target$ 和 $deadends[i]$ 仅由若干位数字组成。 - -**示例**: - -- 示例 1: - -```python -输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202" -输出:6 -解释: -可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。 -注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的, -因为当拨动到 "0102" 时这个锁就会被锁定。 -``` - -- 示例 2: - -```python -输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888" -输出:-1 -解释:无法旋转到目标数字且不被锁定。 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -1. 定义 `visited` 为标记访问节点的 set 集合变量,`queue` 为存放节点的队列。 -2. 将`0000` 状态标记为访问,并将其加入队列 `queue`。 -3. 将当前队列中的所有状态依次出队,判断这些状态是否为死亡字符串。 - 1. 如果为死亡字符串,则跳过该状态,否则继续执行。 - 2. 如果为目标字符串,则返回当前路径长度,否则继续执行。 - -4. 枚举当前状态所有位置所能到达的所有状态(通过向上或者向下旋转),并判断是否访问过该状态。 -5. 如果之前出现过该状态,则继续执行,否则将其存入队列,并标记访问。 -6. 遍历完步骤 3 中当前队列中的所有状态,令路径长度加 `1`,继续执行 3 ~ 5 步,直到队列为空。 -7. 如果队列为空,也未能到达目标状态,则返回 `-1`。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - def openLock(self, deadends: List[str], target: str) -> int: - queue = collections.deque(['0000']) - visited = set(['0000']) - deadset = set(deadends) - level = 0 - while queue: - size = len(queue) - for _ in range(size): - cur = queue.popleft() - if cur in deadset: - continue - if cur == target: - return level - for i in range(len(cur)): - up = self.upward_adjust(cur, i) - if up not in visited: - queue.append(up) - visited.add(up) - down = self.downward_adjust(cur, i) - if down not in visited: - queue.append(down) - visited.add(down) - level += 1 - return -1 - - def upward_adjust(self, s, i): - s_list = list(s) - if s_list[i] == '9': - s_list[i] = '0' - else: - s_list[i] = chr(ord(s_list[i]) + 1) - return "".join(s_list) - - def downward_adjust(self, s, i): - s_list = list(s) - if s_list[i] == '0': - s_list[i] = '9' - else: - s_list[i] = chr(ord(s_list[i]) - 1) - return "".join(s_list) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(10^d \times d^2 + m \times d)$。其中 $d$ 是数字的位数,$m$ 是数组 $deadends$ 的长度。 -- **空间复杂度**:$O(10^D \times d + m)$。 diff --git "a/Solutions/0758. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\212\240\347\262\227\345\215\225\350\257\215.md" "b/Solutions/0758. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\212\240\347\262\227\345\215\225\350\257\215.md" deleted file mode 100644 index 927abaec..00000000 --- "a/Solutions/0758. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\212\240\347\262\227\345\215\225\350\257\215.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0758. 字符串中的加粗单词](https://leetcode.cn/problems/bold-words-in-string/) - -- 标签:字典树、数组、哈希表、字符串、字符串匹配 -- 难度:中等 - -## 题目大意 - -给定一个关键词集合 `words` 和一个字符串 `s`。 - -要求:在所有 `s` 中出现的关键词前后位置上添加加粗闭合标签 `` 和 ``。如果两个子串有重叠部分,则将它们一起用一对闭合标签包围起来。同理,如果两个子字符串连续被加粗,那么你也需要把它们合起来用一对加粗标签包围。最后返回添加加粗标签后的字符串 `s`。 - -## 解题思路 - -构建字典树,将字符串列表 `words` 中所有字符串添加到字典树中。 - -然后遍历字符串 `s`,从每一个位置开始查询字典树。在第一个符合要求的单词前面添加 ``。在连续符合要求的单词中的最后一个单词后面添加 ``。 - -最后返回添加加粗标签后的字符串 `s`。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def boldWords(self, words: List[str], s: str) -> str: - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - size = len(s) - bold_left, bold_right = -1, -1 - ans = "" - for i in range(size): - cur = trie_tree - if s[i] in cur.children: - bold_left = i - while bold_left < size and s[bold_left] in cur.children: - cur = cur.children[s[bold_left]] - bold_left += 1 - if cur.isEnd: - if bold_right == -1: - ans += "" - bold_right = max(bold_left, bold_right) - if i == bold_right: - ans += "" - bold_right = -1 - ans += s[i] - if bold_right >= 0: - ans += "" - return ans -``` - diff --git "a/Solutions/0763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" "b/Solutions/0763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" deleted file mode 100644 index 864a6762..00000000 --- "a/Solutions/0763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0763. 划分字母区间](https://leetcode.cn/problems/partition-labels/) - -- 标签:贪心、哈希表、双指针、字符串 -- 难度:中等 - -## 题目大意 - -给定一个由小写字母组成的字符串 `s`。要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 - -要求:返回一个表示每个字符串片段的长度的列表。 - -## 解题思路 - -因为同一字母最多出现在一个片段中,则同一字母第一次出现的下标位置和最后一次出现的下标位置肯定在同一个片段中。 - -我们先遍历一遍字符串,用哈希表 letter_map 存储下每一个字母最后一次出现的下标位置。 - -为了得到尽可能的片段,我们使用贪心的思想: - -- 从头开始遍历字符串,遍历同时维护当前片段的开始位置 start 和结束位置 end。 -- 对于字符串中的每个字符 `s[i]`,得到当前字母的最后一次出现的下标位置 `letter_map[s[i]]`,则当前片段的结束位置一定不会早于 `letter_map[s[i]]`,所以更新 end 值为 `end = max(end, letter_map[s[i]])`。 -- 当访问到 `i == end` 时,当前片段访问结束,当前片段的下标范围为 `[start, end]`,长度为 `end - start + 1`,将其长度加入答案数组,并更新 start 值为 `i + 1`,继续遍历。 -- 最终返回答案数组。 - -## 代码 - -```python -class Solution: - def partitionLabels(self, s: str) -> List[int]: - letter_map = dict() - for i in range(len(s)): - letter_map[s[i]] = i - res = [] - start, end = 0, 0 - for i in range(len(s)): - end = max(end, letter_map[s[i]]) - if i == end: - res.append(end - start + 1) - start = i + 1 - return res -``` - diff --git "a/Solutions/0765. \346\203\205\344\276\243\347\211\265\346\211\213.md" "b/Solutions/0765. \346\203\205\344\276\243\347\211\265\346\211\213.md" deleted file mode 100644 index 49149139..00000000 --- "a/Solutions/0765. \346\203\205\344\276\243\347\211\265\346\211\213.md" +++ /dev/null @@ -1,97 +0,0 @@ -# [0765. 情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) - -- 标签:贪心、深度优先搜索、广度优先搜索、并查集、图 -- 难度:困难 - -## 题目大意 - -**描述**:$n$ 对情侣坐在连续排列的 $2 \times n$ 个座位上,想要牵对方的手。人和座位用 $0 \sim 2 \times n - 1$ 的整数表示。情侣按顺序编号,第一对是 $(0, 1)$,第二对是 $(2, 3)$,以此类推,最后一对是 $(2 \times n - 2, 2 \times n - 1)$。 - -给定代表情侣初始座位的数组 `row`,`row[i]` 表示第 `i` 个座位上的人的编号。 - -**要求**:计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。每一次交换可以选择任意两人,让他们互换座位。 - -**说明**: - -- $2 \times n == row.length$。 -- $2 \le n \le 30$。 -- $n$ 是偶数。 -- $0 \le row[i] < 2 \times n$。 -- $row$ 中所有元素均无重复。 - -**示例**: - -- 示例 1: - -```python -输入: row = [0,2,1,3] -输出: 1 -解释: 只需要交换row[1]和row[2]的位置即可。 -``` - -- 示例 2: - -```python -输入: row = [3,2,0,1] -输出: 0 -解释: 无需交换座位,所有的情侣都已经可以手牵手了。 -``` - -## 解题思路 - -### 思路 1:并查集 - -先观察一下可以直接牵手的情侣特点: - -- 编号一定相邻。 -- 编号为一个奇数一个偶数。 -- 偶数 + 1 = 奇数。 - -将每对情侣的编号 `(0, 1) (2, 3) (4, 5) ...` 除以 `2` 可以得到 `(0, 0) (1, 1) (2, 2) ...`,这样相同编号就代表是一对情侣。 - -1. 按照 `2` 个一组的顺序,遍历一下所有编号。 - 1. 如果相邻的两人编号除以 `2` 相同,则两人是情侣,将其合并到一个集合中。 - 2. 如果相邻的两人编号不同,则将其合并到同一个集合中,而这两个人分别都有各自的对象,所以在后续遍历中两个人各自的对象和他们同组上的另一个人一定都会并到统一集合中,最终形成一个闭环。比如 `(0, 1) (1, 3) (2, 0) (3, 2)`。假设闭环对数为 `k`,最少需要交换 `k - 1` 次才能让情侣牵手。 -2. 假设 `n` 对情侣中有 `m` 个闭环,则 `至少交换次数 = (n1 - 1) + (n2 - 1) + ... + (nn - 1) = n - m`。 - -### 思路 1:代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return False - self.parent[root_x] = root_y - return True - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def minSwapsCouples(self, row: List[int]) -> int: - size = len(row) - n = size // 2 - count = n - union_find = UnionFind(n) - for i in range(0, size, 2): - if union_find.union(row[i] // 2, row[i + 1] // 2): - count -= 1 - return n - count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是数组 $row$ 长度,$\alpha$ 是反 `Ackerman` 函数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" "b/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" deleted file mode 100644 index 21393b6e..00000000 --- "a/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [0766. 托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) - -- 标签:数组、矩阵 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的矩阵 $matrix$。 - -**要求**:如果 $matrix$ 是托普利茨矩阵,则返回 `True`;否则返回 `False`。 - -**说明**: - -- **托普利茨矩阵**:矩阵上每一条由左上到右下的对角线上的元素都相同。 -- $m == matrix.length$。 -- $n == matrix[i].length$。 -- $1 \le m, n \le 20$。 -- $0 \le matrix[i][j] \le 99$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2020/11/04/ex1.jpg) - -```python -输入:matrix = [[1,2,3,4],[5,1,2,3],[9,5,1,2]] -输出:true -解释: -在上述矩阵中, 其对角线为: -"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。 -各条对角线上的所有元素均相同, 因此答案是 True。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2020/11/04/ex2.jpg) - -```python -输入:matrix = [[1,2],[2,2]] -输出:false -解释: -对角线 "[1, 2]" 上的元素不同。 -``` - -## 解题思路 - -### 思路 1:简单模拟 - -1. 两层循环遍历矩阵,依次判断矩阵当前位置 $(i, j)$ 上的值 $matrix[i][j]$ 与其左上角位置 $(i - 1, j - 1)$ 位置上的值 $matrix[i - 1][j - 1]$ 是否相等。 -2. 如果不相等,则返回 `False`。 -3. 遍历完,则返回 `True`。 - -### 思路 1:代码 - -```python -class Solution: - def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool: - for i in range(1, len(matrix)): - for j in range(1, len(matrix[0])): - if matrix[i][j] != matrix[i - 1][j - 1]: - return False - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别是矩阵 $matrix$ 的行数、列数。 -- **空间复杂度**:$O(m \times n)$。 diff --git "a/Solutions/0771. \345\256\235\347\237\263\344\270\216\347\237\263\345\244\264.md" "b/Solutions/0771. \345\256\235\347\237\263\344\270\216\347\237\263\345\244\264.md" deleted file mode 100644 index f65b4a50..00000000 --- "a/Solutions/0771. \345\256\235\347\237\263\344\270\216\347\237\263\345\244\264.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0771. 宝石与石头](https://leetcode.cn/problems/jewels-and-stones/) - -- 标签:哈希表、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $jewels$ 代表石头中宝石的类型,再给定一个字符串 $stones$ 代表你拥有的石头。$stones$ 中每个字符代表了一种你拥有的石头的类型。 - -**要求**:计算出拥有的石头中有多少是宝石。 - -**说明**: - -- 字母区分大小写,因此 $a$ 和 $A$ 是不同类型的石头。 -- $1 \le jewels.length, stones.length \le 50$。 -- $jewels$ 和 $stones$ 仅由英文字母组成。 -- $jewels$ 中的所有字符都是唯一的。 - -**示例**: - -- 示例 1: - -```python -输入:jewels = "aA", stones = "aAAbbbb" -输出:3 -``` - -- 示例 2: - -```python -输入:jewels = "z", stones = "ZZ" -输出:0 -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 用 $count$ 来维护石头中的宝石个数。 -2. 先使用哈希表或者集合存储宝石。 -3. 再遍历数组 $stones$,并统计每块石头是否在哈希表中或集合中。 - 1. 如果当前石头在哈希表或集合中,则令 $count$ 加 $1$。 - 2. 如果当前石头不在哈希表或集合中,则不统计。 -4. 最后返回 $count$。 - -### 思路 1:代码 - -```python -class Solution: - def numJewelsInStones(self, jewels: str, stones: str) -> int: - jewel_dict = dict() - for jewel in jewels: - jewel_dict[jewel] = 1 - count = 0 - for stone in stones: - if stone in jewel_dict: - count += 1 - return count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$,其中 $m$ 是字符串 $jewels$ 的长度,$n$ 是 $stones$ 的长度。 -- **空间复杂度**:$O(m)$,其中 $m$ 是字符串 $jewels$ 的长度。 - diff --git "a/Solutions/0778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263.md" "b/Solutions/0778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263.md" deleted file mode 100644 index 49b242d8..00000000 --- "a/Solutions/0778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0778. 水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) -- 难度:困难 - -## 题目大意 - -给定一个 `n * n` 大小的二维数组 `grid`,每一个方格的值 `grid[i][j]` 表示为位置 `(i, j)` 的高度。 - -现在要从左上角 `(0, 0)` 位置出发,经过方格的一些点,到达右下角 `(n - 1, n - 1)` 位置上。其中所经过路径的花费为这条路径上所有位置的最大高度。 - -现在要求:计算从 `(0, 0)` 位置到 `(n - 1, n - 1)` 的最优路径的花费。 - -最优路径指的路径上最大高度最小的那条路径。 - -## 解题思路 - -将整个网络抽象为一个无向图,每个点与相邻的点(上下左右)之间都存在一条无向边,边的权重为两个点之间的最大高度。 - -我们要找到左上角到右下角的最优路径,可以遍历所有的点,将所有的边存储到数组中,每条边的存储格式为 `[x, y, h]`,意思是编号 `x` 的点和编号为 `y` 的点之间的权重为 `h`。 - -然后按照权重从小到大的顺序,对所有边进行排序。 - -再按照权重大小遍历所有边,将其依次加入并查集中。并且每次都需要判断 `(0, 0)` 点和 `(n - 1, n - 1)` 点是否连通。 - -如果连通,则该边的权重即为答案。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def swimInWater(self, grid: List[List[int]]) -> int: - row_size = len(grid) - col_size = len(grid[0]) - size = row_size * col_size - edges = [] - for row in range(row_size): - for col in range(col_size): - if row < row_size - 1: - x = row * col_size + col - y = (row + 1) * col_size + col - h = max(grid[row][col], grid[row + 1][col]) - edges.append([x, y, h]) - if col < col_size - 1: - x = row * col_size + col - y = row * col_size + col + 1 - h = max(grid[row][col], grid[row][col + 1]) - edges.append([x, y, h]) - - edges.sort(key=lambda x: x[2]) - - union_find = UnionFind(size) - - for edge in edges: - x, y, h = edge[0], edge[1], edge[2] - union_find.union(x, y) - if union_find.is_connected(0, size - 1): - return h - return 0 -``` - diff --git "a/Solutions/0779. \347\254\254K\344\270\252\350\257\255\346\263\225\347\254\246\345\217\267.md" "b/Solutions/0779. \347\254\254K\344\270\252\350\257\255\346\263\225\347\254\246\345\217\267.md" deleted file mode 100644 index 259ebaee..00000000 --- "a/Solutions/0779. \347\254\254K\344\270\252\350\257\255\346\263\225\347\254\246\345\217\267.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0779. 第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) - -- 标签:位运算、递归、数学 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数 $n$ 和 $k$​。我们可以按照下面的规则来生成字符串: - -- 第一行写上一个 $0$。 -- 从第二行开始,每一行将上一行的 $0$ 替换成 $01$,$1$ 替换为 $10$。 - -**要求**:输出第 $n$ 行字符串中的第 $k$ 个字符。 - -**说明**: - -- $1 \le n \le 30$。 -- $1 \le k \le 2^{n - 1}$。 - -**示例**: - -- 示例 1: - -```python -输入: n = 2, k = 1 -输出: 0 -解释: -第一行: 0 -第二行: 01 -``` - -- 示例 2: - -```python -输入: n = 4, k = 4 -输出: 0 -解释: -第一行:0 -第二行:01 -第三行:0110 -第四行:01101001 -``` - -## 解题思路 - -### 思路 1:递归算法 + 找规律 - -每一行都是由上一行生成的。我们可以将多行写到一起找下规律。 - -可以发现:第 $k$ 个数字是由上一位对应位置上的数字生成的。 - -- $k$ 在奇数位时,由上一行 $(k + 1) / 2$ 位置的值生成。且与上一行 $(k + 1) / 2$ 位置的值相同; -- $k$ 在偶数位时,由上一行 $k / 2$ 位置的值生成。且与上一行 $k / 2$ 位置的值相反。 - -接下来就是递归求解即可。 - -### 思路 1:代码 - -```python -class Solution: - def kthGrammar(self, n: int, k: int) -> int: - if n == 0: - return 0 - if k % 2 == 1: - return self.kthGrammar(n - 1, (k + 1) // 2) - else: - return abs(self.kthGrammar(n - 1, k // 2) - 1) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" "b/Solutions/0784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" deleted file mode 100644 index 89798748..00000000 --- "a/Solutions/0784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0784. 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) - -- 标签:位运算、字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串`s`,通过将字符串`s`中的每个字母转变大小写,我们可以获得一个新的字符串。 - -要求:返回所有可能得到的字符串集合。 - -## 解题思路 - -回溯算法进行求解。具体解法如下: - -- `i` 代表当前要处理的字符在字符串 `s` 中的下标,`path` 表示当前路径,`ans` 表示答案数组。 -- 如果处理到 `i == len(s)` 时,将当前路径存入答案数组中返回,否则进行递归处理。 - - 不修改当前字符,直接递归处理第 `i + 1` 个字符。 - - 如果当前字符是小写字符,则变为大写字符之后,递归处理第 `i + 1` 个字符。 - - 如果当前字符是大写字符,则变为小写字符之后,递归处理第 `i + 1` 个字符。 - -## 代码 - -```python -class Solution: - def dfs(self, s, path, i, ans): - if i == len(s): - ans.append(path) - return - - self.dfs(s, path + s[i], i + 1, ans) - if ord('a') <= ord(s[i]) <= ord('z'): - self.dfs(s, path + s[i].upper(), i + 1, ans) - elif ord('A') <= ord(s[i]) <= ord('Z'): - self.dfs(s, path + s[i].lower(), i + 1, ans) - - def letterCasePermutation(self, s: str) -> List[str]: - ans, path = [], "" - self.dfs(s, path, 0, ans) - return ans -``` - diff --git "a/Solutions/0785. \345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.md" "b/Solutions/0785. \345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.md" deleted file mode 100644 index 6da4c9b5..00000000 --- "a/Solutions/0785. \345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [0785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -给定一个代表 n 个节点的无向图的二维数组 `graph`,其中 `graph[u]` 是一个节点数组,由节点 `u` 的邻接节点组成。对于 `graph[u]` 中的每个 `v`,都存在一条位于节点 `u` 和节点 `v` 之间的无向边。 - -该无向图具有以下属性: - -- 不存在自环(`graph[u]` 不包含 `u`)。 -- 不存在平行边(`graph[u]` 不包含重复值)。 -- 如果 `v` 在 `graph[u]` 内,那么 `u` 也应该在 `graph[v]` 内(该图是无向图)。 -- 这个图可能不是连通图,也就是说两个节点 `u` 和 `v` 之间可能不存在一条连通彼此的路径。 - -要求:判断该图是否是二分图,如果是二分图,则返回 `True`;否则返回 `False`。 - -- 二分图:如果能将一个图的节点集合分割成两个独立的子集 `A` 和 `B`,并使图中的每一条边的两个节点一个来自 `A` 集合,一个来自 `B` 集合,就将这个图称为 二分图 。 - -## 解题思路 - -对于图中的任意节点 `u` 和 `v`,如果 `u` 和 `v` 之间有一条无向边,那么 `u` 和 `v` 必然属于不同的集合。 - -我们可以通过在深度优先搜索中对邻接点染色标记的方式,来识别该图是否是二分图。具体做法如下: - -- 找到一个没有染色的节点 `u`,将其染成红色。 -- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 -- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 -- 如果所有节点都顺利染上色,则说明该图为二分图,返回 `True`。否则,如果在途中不能顺利染色,则返回 `False`。 - -## 代码 - -```python -class Solution: - def dfs(self, graph, colors, i, color): - colors[i] = color - for j in graph[i]: - if colors[j] == colors[i]: - return False - if colors[j] == 0 and not self.dfs(graph, colors, j, -color): - return False - return True - - def isBipartite(self, graph: List[List[int]]) -> bool: - size = len(graph) - colors = [0 for _ in range(size)] - for i in range(size): - if colors[i] == 0 and not self.dfs(graph, colors, i, 1): - return False - return True -``` - diff --git "a/Solutions/0788. \346\227\213\350\275\254\346\225\260\345\255\227.md" "b/Solutions/0788. \346\227\213\350\275\254\346\225\260\345\255\227.md" deleted file mode 100644 index 5ef0e5b1..00000000 --- "a/Solutions/0788. \346\227\213\350\275\254\346\225\260\345\255\227.md" +++ /dev/null @@ -1,134 +0,0 @@ -# [0788. 旋转数字](https://leetcode.cn/problems/rotated-digits/) - -- 标签:数学、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定搞一个正整数 $n$。 - -**要求**:计算从 $1$ 到 $n$ 中有多少个数 $x$ 是好数。 - -**说明**: - -- **好数**:如果一个数 $x$ 的每位数字逐个被旋转 180 度之后,我们仍可以得到一个有效的,且和 $x$ 不同的数,则成该数为好数。 -- 如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。$0$、$1$ 和 $8$ 被旋转后仍然是它们自己;$2$ 和 $5$ 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,$2$ 和 $5$ 互为镜像);$6$ 和 $9$ 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。 -- $n$ 的取值范围是 $[1, 10000]$。 - -**示例**: - -- 示例 1: - -```python -输入: 10 -输出: 4 -解释: -在 [1, 10] 中有四个好数: 2, 5, 6, 9。 -注意 1 和 10 不是好数, 因为他们在旋转之后不变。 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -根据题目描述,一个数满足:数中没有出现 $3$、$4$、$7$,并且至少出现一次 $2$、$5$、$6$ 或 $9$,就是好数。 - -因此,我们可以枚举 $[1, n]$ 中的每一个正整数 $x$,并判断该正整数 $x$ 的数位中是否满足没有出现 $3$、$4$、$7$,并且至少一次出现了 $2$、$5$、$6$ 或 $9$,如果满足,则该正整数 $x$ 位好数,否则不是好数。 - -最后统计好数的方案个数并将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def rotatedDigits(self, n: int) -> int: - check = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] - ans = 0 - for i in range(1, n + 1): - flag = False - num = i - while num: - digit = num % 10 - num //= 10 - if check[digit] == 1: - flag = True - elif check[digit] == -1: - flag = False - break - if flag: - ans += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(\log n)$。 - -### 思路 2:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, hasDiff, isLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。其中: - -1. $pos$ 表示当前枚举的数位位置。 -2. $hasDiff$ 表示当前是否用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字。 -3. $isLimit$ 表示前一位数位是否等于上界,用于限制本次搜索的数位范围。 - -接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, False, True)` 开始递归。 `dfs(0, False, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $hasDiff == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $hasDiff == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 因为不需要考虑前导 $0$,所以当前所能选择的最小数字 $minX$ 为 $0$。 -5. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 -6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 -7. 如果当前数位与之前数位没有出现 $3$、$4$、$7$,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, hasDiff or check[d], isLimit and d == maxX)`。 - 1. `hasDiff or check[d]` 表示当前是否用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字或者没有用到 $3$、$4$、$7$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 -8. 最后的方案数为 `dfs(0, False, True)`,将其返回即可。 - -### 思路 2:代码 - -```python -class Solution: - def rotatedDigits(self, n: int) -> int: - check = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] - - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # hasDiff: 之前选过的数字是否包含 2,5,6,9 中至少一个。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - def dfs(pos, hasDiff, isLimit): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(hasDiff) - - ans = 0 - # 不需要考虑前导 0,则最小可选择数字为 0 - minX = 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - # d 不在选择的数字集合中,即之前没有选择过 d - if check[d] != -1: - ans += dfs(pos + 1, hasDiff or check[d], isLimit and d == maxX) - return ans - - return dfs(0, False, True) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/0795. \345\214\272\351\227\264\345\255\220\346\225\260\347\273\204\344\270\252\346\225\260.md" "b/Solutions/0795. \345\214\272\351\227\264\345\255\220\346\225\260\347\273\204\344\270\252\346\225\260.md" deleted file mode 100644 index 5f373daa..00000000 --- "a/Solutions/0795. \345\214\272\351\227\264\345\255\220\346\225\260\347\273\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [0795. 区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) - -- 标签:数组、双指针 -- 难度:中等 - -## 题目大意 - -给定一个元素都是正整数的数组`A` ,正整数 `L` 以及 `R` (`L <= R`)。 - -求连续、非空且其中最大元素满足大于等于`L` 小于等于`R`的子数组个数。 - -## 解题思路 - -最大元素满足大于等于`L` 小于等于`R`的子数组个数 = 最大元素小于等于 `R` 的子数组个数 - 最大元素小于 `L` 的子数组个数。 - -其中「最大元素小于 `L` 的子数组个数」也可以转变为「最大元素小于等于 `L - 1` 的子数组个数」。那么现在的问题就变为了如何计算最大元素小于等于 `k` 的子数组个数。 - -我们使用 `count` 记录 小于等于 `k` 的连续元素数量,遍历一遍数组,如果遇到 `nums[i] <= k` 时,`count` 累加,表示在此位置上结束的有效子数组数量为 `count + 1`。如果遇到 `nums[i] > k` 时,`count` 重新开始计算。每次遍历完将有效子数组数量累加到答案中。 - -## 代码 - -```python -class Solution: - def numSubarrayMaxK(self, nums, k): - ans = 0 - count = 0 - for i in range(len(nums)): - if nums[i] <= k: - count += 1 - else: - count = 0 - ans += count - return ans - - def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int: - return self.numSubarrayMaxK(nums, right) - self.numSubarrayMaxK(nums, left - 1) -``` - diff --git "a/Solutions/0796. \346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0796. \346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index e7213c4b..00000000 --- "a/Solutions/0796. \346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,92 +0,0 @@ -# [0796. 旋转字符串](https://leetcode.cn/problems/rotate-string/) - -- 标签:字符串、字符串匹配 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个字符串 `s` 和 `goal`。 - -**要求**:如果 `s` 在若干次旋转之后,能变为 `goal`,则返回 `True`,否则返回 `False`。 - -**说明**: - -- `s` 的旋转操作:将 `s` 最左侧的字符移动到最右边。 - - 比如:`s = "abcde"`,在旋转一次之后结果就是 `s = "bcdea"`。 -- $1 \le s.length, goal.length \le 100$。 -- `s` 和 `goal` 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入: s = "abcde", goal = "cdeab" -输出: true -``` - -- 示例 2: - -```python -输入: s = "abcde", goal = "abced" -输出: false -``` - -## 解题思路 - -### 思路 1:KMP 算法 - -其实将两个字符串 `s` 拼接在一起,就包含了所有从 `s` 进行旋转后的字符串。那么我们只需要判断一下 `goal` 是否为 `s + s` 的子串即可。可以用 KMP 算法来做。 - -1. 先排除掉几种不可能的情况,比如 `s` 为空串的情况,`goal` 为空串的情况,`len(s) != len(goal)` 的情况。 -2. 然后使用 KMP 算法计算出 `goal` 在 `s + s` 中的下标位置 `index`(`s + s` 可用取余运算模拟)。 -3. 如果 `index == -1`,则说明 `s` 在若干次旋转之后,不能能变为 `goal`,则返回 `False`。 -4. 如果 `index != -1`,则说明 `s` 在若干次旋转之后,能变为 `goal`,则返回 `True`。 - -### 思路 1:代码 - -```python -class Solution: - def kmp(self, T: str, p: str) -> int: - n, m = len(T), len(p) - - next = self.generateNext(p) - - i, j = 0, 0 - while i - j < n: - while j > 0 and T[i % n] != p[j]: - j = next[j - 1] - if T[i % n] == p[j]: - j += 1 - if j == m: - return i - m + 1 - i += 1 - return -1 - - def generateNext(self, p: str): - m = len(p) - next = [0 for _ in range(m)] - - left = 0 - for right in range(1, m): - while left > 0 and p[left] != p[right]: - left = next[left - 1] - if p[left] == p[right]: - left += 1 - next[right] = left - - return next - - def rotateString(self, s: str, goal: str) -> bool: - if not s or not goal or len(s) != len(goal): - return False - index = self.kmp(s, goal) - if index == -1: - return False - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中文本串 $s$ 的长度为 $n$,模式串 $goal$ 的长度为 $m$。 -- **空间复杂度**:$O(m)$。 diff --git "a/Solutions/0797. \346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.md" "b/Solutions/0797. \346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index 295cc188..00000000 --- "a/Solutions/0797. \346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) - -- 标签:深度优先搜索、广度优先搜索、图、回溯 -- 难度:中等 - -## 题目大意 - -给定一个有 `n` 个节点的有向无环图(DAG),用二维数组 `graph` 表示。 - -要求:找出所有从节点 `0` 到节点 `n - 1` 的路径并输出(不要求按特定顺序)。 - -二维数组 `graph` 的第 `i` 个数组 `graph[i]` 中的单元都表示有向图中 `i` 号节点所能到达的下一个节点,如果为空就是没有下一个结点了。 - -## 解题思路 - -从第 `0` 个节点开始进行深度优先搜索遍历。在遍历的同时,通过回溯来寻找所有路径。具体做法如下: - -- 使用 `ans` 数组存放所有答案路径,使用 `path` 数组记录当前路径。 -- 从第 `0` 个节点开始进行深度优先搜索遍历。 - - 如果当前开始节点 `start` 等于目标节点 `target`。则将当前路径 `path` 添加到答案数组 `ans` 中,并返回。 - - 然后遍历当前节点 `start` 所能达到的下一个节点。 - - 将下一个节点加入到当前路径中。 - - 从该节点出发进行深度优先搜索遍历。 - - 然后将下一个节点从当前路径中移出,进行回退操作。 -- 最后返回答案数组 `ans`。 - -## 代码 - -```python -class Solution: - def dfs(self, graph, start, target, path, ans): - if start == target: - ans.append(path[:]) - return - for end in graph[start]: - path.append(end) - self.dfs(graph, end, target, path, ans) - path.remove(end) - - def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: - path = [0] - ans = [] - self.dfs(graph, 0, len(graph) - 1, path, ans) - return ans -``` - diff --git "a/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" "b/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" deleted file mode 100644 index a636822a..00000000 --- "a/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [0800. 相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) - -- 标签:数学、字符串、枚举 -- 难度:简单 - -## 题目大意 - -**描述**:RGB 颜色 `"#AABBCC"` 可以简写成 `"#ABC"` 。例如,`"#1155cc"` 可以简写为 `"#15c"`。现在给定一个按 `"#ABCDEF"` 形式定义的字符串 `color` 表示 RGB 颜色。 - -**要求**:返回一个与 `color` 相似度最大并且可以简写的颜色。 - -**说明**: - -- 两个颜色 `"#ABCDEF"` 和 `"#UVWXYZ"` 的相似度计算公式为:$-(AB - UV)^2 - (CD - WX)^2 - (EF - YZ)^2$。 - -**示例**: - -- 示例 1: - -```python -输入 color = "#09f166" -输出 "#11ee66" -解释: 因为相似度计算得出 -(0x09 - 0x11)^2 -(0xf1 - 0xee)^2 - (0x66 - 0x66)^2 = -64 -9 -0 = -73,这是所有可以简写的颜色中与 color 最相似的颜色 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -所有可以简写的颜色范围是 `"#000"` ~ `"#fff"`,共 $16^3 = 4096$ 种颜色。因此,我们可以枚举这些可以简写的颜色,并计算出其与 `color`的相似度,从而找出与 `color` 最相似的颜色。具体做法如下: - -- 将 `color` 转换为十六进制数,即 `hex_color = int(color[1:], 16)`。 -- 三重循环遍历 `R`、`G`、`B` 三个通道颜色,每一重循环范围为 `0` ~ `15`。 -- 计算出每一种可以简写的颜色对应的十六进制,即 `17 * R * (1 << 16) + 17 * G * (1 << 8) + 17 * B`,`17` 是 `0x11 = 16 + 1 = 17`,`(1 << 16)` 为 `R` 左移的位数,`17 * R * (1 << 16)` 就表示 `R` 通道上对应的十六进制数。`(1 << 8)` 为 `G` 左移的位数,`17 * G * (1 << 8)` 就表示 `G` 通道上对应的十六进制数。`17 * B` 就表示 `B` 通道上对应的十六进制数。 -- 然后我们根据 `color` 的十六进制数,与每一个可以简写的颜色对应的十六进制数,计算出相似度,并找出大相似对应的颜色。将其转换为字符串,并输出。 - -### 思路 1:枚举算法代码 - -```python -class Solution: - def similar(self, hex1, hex2): - r1, g1, b1 = hex1 >> 16, (hex1 >> 8) % 256, hex1 % 256 - r2, g2, b2 = hex2 >> 16, (hex2 >> 8) % 256, hex2 % 256 - return - (r1 - r2) ** 2 - (g1 - g2) ** 2 - (b1 - b2) ** 2 - - def similarRGB(self, color: str) -> str: - ans = 0 - hex_color = int(color[1:], 16) - for r in range(16): - for g in range(16): - for b in range(16): - hex_cur = 17 * r * (1 << 16) + 17 * g * (1 << 8) + 17 * b - if self.similar(hex_color, hex_cur) > self.similar(hex_color, ans): - ans = hex_cur - - return "#{:06x}".format(ans) -``` diff --git "a/Solutions/0801. \344\275\277\345\272\217\345\210\227\351\200\222\345\242\236\347\232\204\346\234\200\345\260\217\344\272\244\346\215\242\346\254\241\346\225\260.md" "b/Solutions/0801. \344\275\277\345\272\217\345\210\227\351\200\222\345\242\236\347\232\204\346\234\200\345\260\217\344\272\244\346\215\242\346\254\241\346\225\260.md" deleted file mode 100644 index edb756bb..00000000 --- "a/Solutions/0801. \344\275\277\345\272\217\345\210\227\351\200\222\345\242\236\347\232\204\346\234\200\345\260\217\344\272\244\346\215\242\346\254\241\346\225\260.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0801. 使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -给定两个长度相等的整形数组 A 和 B。可以交换两个数组相同位置上的元素,比如 A[i] 与 B[i] 交换,可以交换多个位置,但要保证交换之后保证数组 A和数组 B 是严格递增的。 - -要求:返回使得数组 A和数组 B 保持严格递增状态的最小交换次数。假设给定的输入一定有效。 - -## 解题思路 - -可以用动态规划来做。 - -对于两个数组每一个位置上的元素 A[i] 和 B[i] 来说,只有两种情况:换或者不换。 - -动态规划的状态 `dp[i][j]` 表示为:第 i 个位置元素,不交换(j = 0)、交换(j = 1)状态时的最小交换次数。 - -如果数组元素个数只有一个,则: - -- `dp[0][0] = 0` ,第 0 个元素不做交换,交换次数为 0。 -- `dp[0][1] = 1`,第 0 个元素做交换,交换次数为 1。 - -如果有 2 个元素,为了保证两个数组中的相邻元素都为递增元素,则第 2 个元素交换与否与第 1 个元素有关。同理如果有多个元素,那么第 i 个元素交换与否,只与第 i - 1 个元素有关。现在来考虑第 i 个元素与第 i - 1 的元素的情况。 - -先按原本数组当前是否满足递增关系来划分,可以划分为: - -- 原本数组都满足递增关系,即 `A[i - 1] < A[i]` 并且 `B[i - 1] < B[i]`。 -- 不满足上述递增关系的情况,即 `A[i - 1] >= A[i]` 或者 `B[i - 1] >= B[i]`。 - -可以看出,不满足递增关系的情况下是肯定要交换的。只需要考虑交换第 i 位元素,还是第 i - 1 位元素。 - -- `dp[i][0] = dp[i - 1][1]`,第 i 位若不交换,则第 i - 1 位必须交换。 -- `dp[i][1] = dp[i - 1][0] + 1`,第 i 位交换,则第 i - 1 位不能交换。 - -下面再来考虑原本数组都满足递增关系的情况。考虑两个数组间相邻元素的关系。 - -- `A[i - 1] < B[i]` 并且 `B[i - 1] < A[i]`。 -- `A[i - 1] >= B[i]` 或者 `B[i - 1] >= A[i]`。 - -如果是 `A[i - 1] < B[i]` 并且 `B[i - 1] < A[i]` 情况下,第 i 位交换,与第 i - 1 位交换与否无关,则 `dp[i][j]` 只需取 `dp[i-1][j]` 上较小结果进行计算即可,即: - -- `dp[i][0] = min(dp[i-1][0], dp[i-1][1])` -- `dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + 1` - -如果是 `A[i - 1] >= B[i]` 或者 `B[i - 1] >= A[i]` 情况下,则如果第 i 位交换,则第 i - 1 位必须跟着交换。如果第 i 位不交换,则第 i - 1 为也不能交换,即: - -- `dp[i][0] = dp[i - 1][0]`,如果第 i 位不交换,则第 i - 1 位也不交换。 -- `dp[i][1] = dp[i - 1][1] + 1`,如果第 i 位交换,则第 i - 1 位也必须交换。 - -这样就考虑了所有的情况,最终返回最后一个元素,(交换、不交换)状态下的最小值即可。 - -## 代码 - -```python -class Solution: - def minSwap(self, nums1: List[int], nums2: List[int]) -> int: - size = len(nums1) - dp = [[0 for _ in range(size)] for _ in range(size)] - dp[0][1] = 1 - for i in range(1, size): - if nums1[i - 1] < nums1[i] and nums2[i - 1] < nums2[i]: - if nums1[i - 1] < nums2[i] and nums2[i - 1] < nums1[i]: - # 第 i 位交换,与第 i - 1 位交换与否无关 - dp[i][0] = min(dp[i-1][0], dp[i-1][1]) - dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + 1 - else: - # 如果第 i 位不交换,则第 i - 1 位也不交换 - # 如果第 i 位交换,则第 i - 1 位也必须交换 - dp[i][0] = dp[i - 1][0] - dp[i][1] = dp[i - 1][1] + 1 - else: - dp[i][0] = dp[i - 1][1] # 如果第 i 位若不交换,则第 i - 1 位必须交换 - dp[i][1] = dp[i - 1][0] + 1 # 如果第 i 位交换,则第 i - 1 位不能交换 - return min(dp[size - 1][0], dp[size - 1][1]) -``` - diff --git "a/Solutions/0802. \346\211\276\345\210\260\346\234\200\347\273\210\347\232\204\345\256\211\345\205\250\347\212\266\346\200\201.md" "b/Solutions/0802. \346\211\276\345\210\260\346\234\200\347\273\210\347\232\204\345\256\211\345\205\250\347\212\266\346\200\201.md" deleted file mode 100644 index 9d27f751..00000000 --- "a/Solutions/0802. \346\211\276\345\210\260\346\234\200\347\273\210\347\232\204\345\256\211\345\205\250\347\212\266\346\200\201.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [0802. 找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个有向图 $graph$,其中 $graph[i]$ 是与节点 $i$ 相邻的节点列表,意味着从节点 $i$ 到节点 $graph[i]$ 中的每个节点都有一条有向边。 - -**要求**:找出图中所有的安全节点,将其存入数组作为答案返回,答案数组中的元素应当按升序排列。 - -**说明**: - -- **终端节点**:如果一个节点没有连出的有向边,则它是终端节点。或者说,如果没有出边,则节点为终端节点。 -- **安全节点**:如果从该节点开始的所有可能路径都通向终端节点,则该节点为安全节点。 -- $n == graph.length$。 -- $1 \le n \le 10^4$。 -- $0 \le graph[i].length \le n$。 -- $0 \le graph[i][j] \le n - 1$。 -- $graph[i]$ 按严格递增顺序排列。 -- 图中可能包含自环。 -- 图中边的数目在范围 $[1, 4 \times 10^4]$ 内。 - -**示例**: - -- 示例 1: - -![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/03/17/picture1.png) - -```python -输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]] -输出:[2,4,5,6] -解释:示意图如上。 -节点 5 和节点 6 是终端节点,因为它们都没有出边。 -从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6。 -``` - -- 示例 2: - -```python -输入:graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]] -输出:[4] -解释: -只有节点 4 是终端节点,从节点 4 开始的所有路径都通向节点 4。 -``` - -## 解题思路 - -### 思路 1:拓扑排序 - -1. 根据题意可知,安全节点所对应的终点,一定是出度为 $0$ 的节点。而安全节点一定能在有限步内到达终点,则说明安全节点一定不在「环」内。 -2. 我们可以利用拓扑排序来判断顶点是否在环中。 -3. 为了找出安全节点,可以采取逆序建图的方式,将所有边进行反向。这样出度为 $0$ 的终点就变为了入度为 $0$ 的点。 -4. 然后通过拓扑排序不断移除入度为 $0$ 的点之后,如果不在「环」中的点,最后入度一定为 $0$,这些点也就是安全节点。而在「环」中的点,最后入度一定不为 $0$。 -5. 最后将所有安全的起始节点存入数组作为答案返回。 - -### 思路 1:代码 - -```python -class Solution: - # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) - def topologicalSortingKahn(self, graph: dict): - indegrees = {u: 0 for u in graph} # indegrees 用于记录所有节点入度 - for u in graph: - for v in graph[u]: - indegrees[v] += 1 # 统计所有节点入度 - - # 将入度为 0 的顶点存入集合 S 中 - S = collections.deque([u for u in indegrees if indegrees[u] == 0]) - - while S: - u = S.pop() # 从集合中选择一个没有前驱的顶点 0 - for v in graph[u]: # 遍历顶点 u 的邻接顶点 v - indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 - if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 - S.append(v) # 将其放入集合 S 中 - - res = [] - for u in indegrees: - if indegrees[u] == 0: - res.append(u) - - return res - - def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]: - graph_dict = {u: [] for u in range(len(graph))} - - for u in range(len(graph)): - for v in graph[u]: - graph_dict[v].append(u) # 逆序建图 - - return self.topologicalSortingKahn(graph_dict) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 是图中节点数目,$m$ 是图中边数目。 -- **空间复杂度**:$O(n + m)$。 - diff --git "a/Solutions/0803. \346\211\223\347\240\226\345\235\227.md" "b/Solutions/0803. \346\211\223\347\240\226\345\235\227.md" deleted file mode 100644 index 7435ac99..00000000 --- "a/Solutions/0803. \346\211\223\347\240\226\345\235\227.md" +++ /dev/null @@ -1,121 +0,0 @@ -# [0803. 打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) - -- 标签:并查集、数组、矩阵 -- 难度:困难 - -## 题目大意 - -给定一个 `m * n` 大小的二元网格,其中 `1` 表示砖块,`0` 表示空白。砖块稳定(不会掉落)的前提是: - -- 一块砖直接连接到网格的顶部。 -- 或者至少有一块相邻(4 个方向之一)砖块稳定不会掉落时。 - -再给定一个数组 `hits`,这是需要依次消除砖块的位置。每当消除 `hits[i] = (row_i, col_i)` 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。 - -要求:返回一个数组 `result`,其中 `result[i]` 表示第 `i` 次消除操作对应掉落的砖块数目。 - -注意:消除可能指向是没有砖块的空白位置,如果发生这种情况,则没有砖块掉落。 - -## 解题思路 - -一个很直观的想法: - -- 将所有砖块放入一个集合中。 -- 根据 `hits` 数组的顺序,每敲掉一块砖。则将这块砖与相邻(4 个方向)的砖块断开集合。 -- 然后判断哪些砖块会掉落,从集合中删除会掉落的砖块,并统计掉落砖块的数量。 - - `掉落砖块的数目 = 击碎砖块之前与屋顶相连的砖块数目 - 击碎砖块之后与屋顶相连的砖块数目 - 1` 。 - -涉及集合问题,很容易想到用并查集来做。但是并查集主要用于合并查找集合,不适合断开集合。我们可以反向思考问题: - -- 先将 `hits` 中的所有位置上的砖块敲掉。 -- 将剩下的砖块建立并查集。 -- 逆序填回被敲掉的砖块,并与相邻(4 个方向)的砖块合并。这样问题就变为了 `补上砖块会新增多少个砖块粘到屋顶`。 - -整个算法步骤具体如下: - -- 先将二维数组 `grid` 复制一份到二维数组 `copy_gird` 上。这是因为遍历 `hits` 元素时需要判断原网格是空白还是被打碎的砖块。 -- 在 `copy_grid` 中将 `hits` 中打碎的砖块赋值为 `0`。 -- 建立并查集,将房顶上的砖块合并到一个集合中。 -- 逆序遍历 `hits`,将 `hits` 中的砖块补到 `copy_grid` 中,并计算每一步中有多少个砖块粘到屋顶上(与屋顶砖块在一个集合中),并存入答案数组对应位置。 -- 最后输出答案数组。 - -## 代码 - -```python -class UnionFind: - def __init__(self, n): - self.parent = [i for i in range(n)] - self.size = [1 for _ in range(n)] - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return False - self.parent[root_x] = root_y - self.size[root_y] += self.size[root_x] - return True - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - - def get_size(self, x): - root_x = self.find(x) - return self.size[root_x] - -class Solution: - def hitBricks(self, grid: List[List[int]], hits: List[List[int]]) -> List[int]: - directions = {(0, 1), (1, 0), (-1, 0), (0, -1)} - rows, cols = len(grid), len(grid[0]) - - def is_area(x, y): - return 0 <= x < rows and 0 <= y < cols - - def get_index(x, y): - return x * cols + y - - copy_grid = [[grid[i][j] for j in range(cols)] for i in range(rows)] - - for hit in hits: - copy_grid[hit[0]][hit[1]] = 0 - - union_find = UnionFind(rows * cols + 1) - - for j in range(cols): - if copy_grid[0][j] == 1: - union_find.union(j, rows * cols) - - for i in range(1, rows): - for j in range(cols): - if copy_grid[i][j] == 1: - if copy_grid[i - 1][j] == 1: - union_find.union(get_index(i - 1, j), get_index(i, j)) - if j > 0 and copy_grid[i][j - 1] == 1: - union_find.union(get_index(i, j - 1), get_index(i, j)) - - size_hits = len(hits) - res = [0 for _ in range(size_hits)] - for i in range(size_hits - 1, -1, -1): - x, y = hits[i][0], hits[i][1] - if grid[x][y] == 0: - continue - origin = union_find.get_size(rows * cols) - if x == 0: - union_find.union(y, rows * cols) - for direction in directions: - new_x = x + direction[0] - new_y = y + direction[1] - if is_area(new_x, new_y) and copy_grid[new_x][new_y] == 1: - union_find.union(get_index(x, y), get_index(new_x, new_y)) - curr = union_find.get_size(rows * cols) - res[i] = max(0, curr - origin - 1) - copy_grid[x][y] = 1 - return res -``` - diff --git "a/Solutions/0806. \345\206\231\345\255\227\347\254\246\344\270\262\351\234\200\350\246\201\347\232\204\350\241\214\346\225\260.md" "b/Solutions/0806. \345\206\231\345\255\227\347\254\246\344\270\262\351\234\200\350\246\201\347\232\204\350\241\214\346\225\260.md" deleted file mode 100644 index f7cd6248..00000000 --- "a/Solutions/0806. \345\206\231\345\255\227\347\254\246\344\270\262\351\234\200\350\246\201\347\232\204\350\241\214\346\225\260.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0806. 写字符串需要的行数](https://leetcode.cn/problems/number-of-lines-to-write-string/) - -- 标签:数组、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $widths$,其中 $words[0]$ 代表 `'a'` 需要的单位,$words[1]$ 代表 `'b'` 需要的单位,…,$words[25]$ 代表 `'z'` 需要的单位。再给定一个字符串 $s$,现在需要将字符串 $s$ 从左到右写到每一行上,每一行的最大宽度为 $100$ 个单位,如果在写某个字符的时候使改行超过了 $100$ 个单位,那么我们应该将这个字母写到下一行。 - -**要求**:计算出能放下 $s$ 的最少行数,以及最后一行使用的宽度单位。 - -**说明**: - -- 字符串 $s$ 的长度在 $[1, 1000]$ 的范围。 -- $s$ 只包含小写字母。 -- $widths$ 是长度为 $26$ 的数组。 -- $widths[i]$ 值的范围在 $[2, 10]$。 - -**示例**: - -- 示例 1: - -```python -输入: -widths = [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10] -S = "abcdefghijklmnopqrstuvwxyz" -输出: [3, 60] -解释: -所有的字符拥有相同的占用单位10。所以书写所有的26个字母, -我们需要2个整行和占用60个单位的一行。 -``` - -- 示例 2: - -```python -输入: -widths = [4,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10] -S = "bbbcccdddaaa" -输出: [2, 4] -解释: -除去字母'a'所有的字符都是相同的单位10,并且字符串 "bbbcccdddaa" 将会覆盖 9 * 10 + 2 * 4 = 98 个单位. -最后一个字母 'a' 将会被写到第二行,因为第一行只剩下2个单位了。 -所以,这个答案是2行,第二行有4个单位宽度。 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 使用变量 $line\underline{}cnt$ 记录行数,使用变量 $last\underline{}cnt$ 记录最后一行使用的单位数。 -2. 遍历字符串,如果当前最后一行使用的单位数 + 当前字符需要的单位超过了 $100$,则: - 1. 另起一行填充字符。(即行数加 $1$,最后一行使用的单位数为当前字符宽度)。 -3. 如果当前最后一行使用的单位数 + 当前字符需要的单位没有超过 $100$,则: - 1. 在当前行填充字符。(即最后一行使用的单位数累加上当前字符宽度)。 - -### 思路 1:代码 - -```python -class Solution: - def numberOfLines(self, widths: List[int], s: str) -> List[int]: - line_cnt, last_cnt = 1, 0 - for ch in s: - width = widths[ord(ch) - ord('a')] - if last_cnt + width > 100: - line_cnt += 1 - last_cnt = width - else: - last_cnt += width - - return [line_cnt, last_cnt] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0811. \345\255\220\345\237\237\345\220\215\350\256\277\351\227\256\350\256\241\346\225\260.md" "b/Solutions/0811. \345\255\220\345\237\237\345\220\215\350\256\277\351\227\256\350\256\241\346\225\260.md" deleted file mode 100644 index 4b1f8a85..00000000 --- "a/Solutions/0811. \345\255\220\345\237\237\345\220\215\350\256\277\351\227\256\350\256\241\346\225\260.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [0811. 子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) - -- 标签:数组、哈希表、字符串、计数 -- 难度:中等 - -## 题目大意 - -**描述**:网站域名是由多个子域名构成的。 - -- 例如 `"discuss.leetcode.com"` 的顶级域名为 `"com"`,二级域名为 `"leetcode.com"`,三级域名为 `"discuss.leetcode.com"`。 - -当访问 `"discuss.leetcode.com"` 时,也会隐式访问其父域名 `"leetcode.com"` 以及 `"com"`。 - -计算机配对域名的格式为 `"rep d1.d2.d3"` 或 `"rep d1.d2"`。其中 `rep` 表示访问域名的次数,`d1.d2.d3` 或 `d1.d2` 为域名本身。 - -- 例如:`"9001 discuss.leetcode.com"` 就是一个 计数配对域名 ,表示 `discuss.leetcode.com` 被访问了 `9001` 次。 - -现在给定一个由计算机配对域名组成的数组 `cpdomains`。 - -**要求**:解析每一个计算机配对域名,计算出所有域名的访问次数,并以数组形式返回。可以按任意顺序返回答案。 - -## 解题思路 - -这道题求解的是不同层级的域名的次数汇总,很容易想到使用哈希表。我们可以使用哈希表来统计不同层级的域名访问次数。具体做如下: - -1. 如果数组 `cpdomains` 为空,直接返回空数组。 -2. 使用哈希表 `times_dict` 存储不同层级的域名访问次数。 -3. 遍历数组 `cpdomains`。对于每一个计算机配对域名 `cpdomain`: - 1. 先将计算机配对域名的访问次数 `times` 和域名 `domain` 进行分割。 - 2. 然后将域名转为子域名数组 `domain_list`,逆序拼接不同等级的子域名 `sub_domain`。 - 3. 如果子域名 `sub_domain` 没有出现在哈希表 `times_dict` 中,则在哈希表中存入 `sub_domain` 和访问次数 `times` 的键值对。 - 4. 如果子域名 `sub_domain` 曾经出现在哈希表 `times_dict` 中,则在哈希表对应位置加上 `times`。 -4. 遍历完之后,遍历哈希表 `times_dict`,将所有域名和访问次数拼接为字符串,存入答案数组中。 -5. 最后返回答案数组。 - -## 代码 - -```python -class Solution: - def subdomainVisits(self, cpdomains: List[str]) -> List[str]: - if not cpdomains: - return [] - - times_dict = dict() - for cpdomain in cpdomains: - tiems, domain = cpdomain.split() - tiems = int(tiems) - - domain_list = domain.split('.') - for i in range(len(domain_list) - 1, -1, -1): - sub_domain = '.'.join(domain_list[i:]) - if sub_domain not in times_dict: - times_dict[sub_domain] = tiems - else: - times_dict[sub_domain] += tiems - - res = [] - for key in times_dict.keys(): - res.append(str(times_dict[key]) + ' ' + key) - return res -``` - diff --git "a/Solutions/0814. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" "b/Solutions/0814. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" deleted file mode 100644 index a3bb1db0..00000000 --- "a/Solutions/0814. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [0814. 二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`,树的每个节点值要么是 `0`,要么是 `1`。 - -要求:剪除该二叉树中所有节点值为 `0` 的子树。 - -- 节点 `node` 的子树为: `node` 本身,以及所有 `node` 的后代。 - -## 解题思路 - -定义辅助方法 `containsOnlyZero(root)` 递归判断以 `root` 为根的子树中是否只包含 `0`。如果子树中只包含 `0`,则返回 `True`。如果子树中含有 `1`,则返回 `False`。当 `root` 为空时,也返回 `True`。 - -然后递归遍历二叉树,判断当前节点 `root` 是否只包含 `0`。如果只包含 `0`,则将其置空,返回 `None`。否则递归遍历左右子树,并设置对应的左右指针。 - -最后返回根节点 `root`。 - -## 代码 - -```python -class Solution: - def containsOnlyZero(self, root: TreeNode): - if not root: - return True - if root.val == 1: - return False - return self.containsOnlyZero(root.left) and self.containsOnlyZero(root.right) - - def pruneTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if not root: - return root - if self.containsOnlyZero(root): - return None - - root.left = self.pruneTree(root.left) - root.right = self.pruneTree(root.right) - return root -``` - diff --git "a/Solutions/0819. \346\234\200\345\270\270\350\247\201\347\232\204\345\215\225\350\257\215.md" "b/Solutions/0819. \346\234\200\345\270\270\350\247\201\347\232\204\345\215\225\350\257\215.md" deleted file mode 100644 index 9b6c794c..00000000 --- "a/Solutions/0819. \346\234\200\345\270\270\350\247\201\347\232\204\345\215\225\350\257\215.md" +++ /dev/null @@ -1,91 +0,0 @@ -# [0819. 最常见的单词](https://leetcode.cn/problems/most-common-word/) - -- 标签:哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $paragraph$ 表示段落,再给定搞一个禁用单词列表 $banned$。 - -**要求**:返回出现次数最多,同时不在禁用列表中的单词。 - -**说明**: - -- 题目保证至少有一个词不在禁用列表中,而且答案唯一。 -- 禁用列表 $banned$ 中的单词用小写字母表示,不含标点符号。 -- 段落 $paragraph$ 只包含字母、空格和下列标点符号`!?',;.` -- 段落中的单词不区分大小写。 -- $1 \le \text{段落长度} \le 1000$。 -- $0 \le \text{禁用单词个数} \le 100$。 -- $1 \le \text{禁用单词长度} \le 10$。 -- 答案是唯一的,且都是小写字母(即使在 $paragraph$ 里是大写的,即使是一些特定的名词,答案都是小写的)。 -- 不存在没有连字符或者带有连字符的单词。 -- 单词里只包含字母,不会出现省略号或者其他标点符号。 - -**示例**: - -- 示例 1: - -```python -输入: -paragraph = "Bob hit a ball, the hit BALL flew far after it was hit." -banned = ["hit"] -输出: "ball" -解释: -"hit" 出现了3次,但它是一个禁用的单词。 -"ball" 出现了2次 (同时没有其他单词出现2次),所以它是段落里出现次数最多的,且不在禁用列表中的单词。 -注意,所有这些单词在段落里不区分大小写,标点符号需要忽略(即使是紧挨着单词也忽略, 比如 "ball,"), -"hit"不是最终的答案,虽然它出现次数更多,但它在禁用单词列表中。 -``` - -- 示例 2: - -```python -输入: -paragraph = "a." -banned = [] -输出:"a" -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 将禁用词列表转为集合 $banned\underline{}set$。 -2. 遍历段落 $paragraph$,获取段落中的所有单词。 -3. 判断当前单词是否在禁用词集合中,如果不在禁用词集合中,则使用哈希表对该单词进行计数。 -4. 遍历完,找出哈希表中频率最大的单词,将该单词作为答案进行返回。 - -### 思路 1:代码 - -```python -class Solution: - def mostCommonWord(self, paragraph: str, banned: List[str]) -> str: - banned_set = set(banned) - cnts = Counter() - - word = "" - for ch in paragraph: - if ch.isalpha(): - word += ch.lower() - else: - if word and word not in banned_set: - cnts[word] += 1 - word = "" - if word and word not in banned_set: - cnts[word] += 1 - - max_cnt, ans = 0, "" - for word, cnt in cnts.items(): - if cnt > max_cnt: - max_cnt = cnt - ans = word - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 为段落 $paragraph$ 的长度,$m$ 是禁用词 $banned$ 的长度。 -- **空间复杂度**:$O(n + m)$。 - diff --git "a/Solutions/0820. \345\215\225\350\257\215\347\232\204\345\216\213\347\274\251\347\274\226\347\240\201.md" "b/Solutions/0820. \345\215\225\350\257\215\347\232\204\345\216\213\347\274\251\347\274\226\347\240\201.md" deleted file mode 100644 index 56e8ea1e..00000000 --- "a/Solutions/0820. \345\215\225\350\257\215\347\232\204\345\216\213\347\274\251\347\274\226\347\240\201.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0820. 单词的压缩编码](https://leetcode.cn/problems/short-encoding-of-words/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -给定一个单词数组 `words`。要求对 `words` 进行编码成一个助记字符串,用来帮助记忆。`words` 中拥有相同字符后缀的单词可以合并成一个单词,比如`time` 和 `me` 可以合并成 `time`。同时每个不能再合并的单词末尾以 `#` 为结束符,将所有合并后的单词排列起来就是一个助记字符串。 - -要求:返回对 `words` 进行编码的最小助记字符串 `s` 的长度。 - -## 解题思路 - -构建一个字典树。然后对字符串长度进行从小到大排序。 - -再依次将去重后的所有单词插入到字典树中。如果出现比当前单词更长的单词,则将短单词的结尾置为 `False`,意为替换掉短单词。 - -然后再依次在字典树中查询所有单词,「单词长度 + 1」就是当前不能在合并的单词,累加起来就是答案。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = False - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def minimumLengthEncoding(self, words: List[str]) -> int: - trie_tree = Trie() - words = list(set(words)) - words.sort(key=lambda i: len(i)) - - ans = 0 - for word in words: - trie_tree.insert(word[::-1]) - - for word in words: - if trie_tree.search(word[::-1]): - ans += len(word) + 1 - - return ans -``` - diff --git "a/Solutions/0821. \345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\350\267\235\347\246\273.md" "b/Solutions/0821. \345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\350\267\235\347\246\273.md" deleted file mode 100644 index d4932c85..00000000 --- "a/Solutions/0821. \345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\350\267\235\347\246\273.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0821. 字符的最短距离](https://leetcode.cn/problems/shortest-distance-to-a-character/) - -- 标签:数组、双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $s$ 和一个字符 $c$,并且 $c$ 是字符串 $s$ 中出现过的字符。 - -**要求**:返回一个长度与字符串 $s$ 想通的整数数组 $answer$,其中 $answer[i]$ 是字符串 $s$ 中从下标 $i$ 到离下标 $i$ 最近的字符 $c$ 的距离。 - -**说明**: - -- 两个下标 $i$ 和 $j$ 之间的 **距离** 为 $abs(i - j)$ ,其中 $abs$ 是绝对值函数。 -- $1 \le s.length \le 10^4$。 -- $s[i]$ 和 $c$ 均为小写英文字母 -- 题目数据保证 $c$ 在 $s$ 中至少出现一次。 - -**示例**: - -- 示例 1: - -```python -输入:s = "loveleetcode", c = "e" -输出:[3,2,1,0,1,0,0,1,2,2,1,0] -解释:字符 'e' 出现在下标 3、5、6 和 11 处(下标从 0 开始计数)。 -距下标 0 最近的 'e' 出现在下标 3,所以距离为 abs(0 - 3) = 3。 -距下标 1 最近的 'e' 出现在下标 3,所以距离为 abs(1 - 3) = 2。 -对于下标 4,出现在下标 3 和下标 5 处的 'e' 都离它最近,但距离是一样的 abs(4 - 3) == abs(4 - 5) = 1。 -距下标 8 最近的 'e' 出现在下标 6,所以距离为 abs(8 - 6) = 2。 -``` - -- 示例 2: - -```python -输入:s = "aaab", c = "b" -输出:[3,2,1,0] -``` - -## 解题思路 - -### 思路 1:两次遍历 - -第一次从左到右遍历,记录每个 $i$ 左边最近的 $c$ 的位置,并将其距离记录到 $answer[i]$ 中。 - -第二次从右到左遍历,记录每个 $i$ 右侧最近的 $c$ 的位置,并将其与第一次遍历左侧最近的 $c$ 的位置相比较,并将较小的距离记录到 $answer[i]$ 中。 - -### 思路 1:代码 - -```python -class Solution: - def shortestToChar(self, s: str, c: str) -> List[int]: - size = len(s) - ans = [size + 1 for _ in range(size)] - - pos = -1 - for i in range(size): - if s[i] == c: - pos = i - if pos != -1: - ans[i] = i - pos - - pos = -1 - for i in range(size - 1, -1, -1): - if s[i] == c: - pos = i - if pos != -1: - ans[i] = min(ans[i], pos - i) - - return ans - -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0824. \345\261\261\347\276\212\346\213\211\344\270\201\346\226\207.md" "b/Solutions/0824. \345\261\261\347\276\212\346\213\211\344\270\201\346\226\207.md" deleted file mode 100644 index 5f9d43ad..00000000 --- "a/Solutions/0824. \345\261\261\347\276\212\346\213\211\344\270\201\346\226\207.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [0824. 山羊拉丁文](https://leetcode.cn/problems/goat-latin/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个由若干单词组成的句子 $sentence$,单词之间由空格分隔。每个单词仅由大写和小写字母组成。 - -**要求**:将句子转换为「山羊拉丁文(Goat Latin)」,并返回将 $sentence$ 转换为山羊拉丁文后的句子。 - -**说明**: - -- 山羊拉丁文的规则如下: - - 如果单词以元音开头(`a`,`e`,`i`,`o`,`u`),在单词后添加 `"ma"`。 - - 例如,单词 `"apple"` 变为 `"applema"`。 - - - 如果单词以辅音字母开头(即,非元音字母),移除第一个字符并将它放到末尾,之后再添加 `"ma"`。 - - 例如,单词 `"goat"` 变为 `"oatgma"`。 - - - 根据单词在句子中的索引,在单词最后添加与索引相同数量的字母 `a`,索引从 $1$ 开始。 - - 例如,在第一个单词后添加 `"a"` ,在第二个单词后添加 `"aa"`,以此类推。 - -- $1 \le sentence.length \le 150$。 -- $sentence$ 由英文字母和空格组成。 -- $sentence$ 不含前导或尾随空格。 -- $sentence$ 中的所有单词由单个空格分隔。 - -**示例**: - -- 示例 1: - -```python -输入:sentence = "I speak Goat Latin" -输出:"Imaa peaksmaaa oatGmaaaa atinLmaaaaa" -``` - -- 示例 2: - -```python -输入:sentence = "The quick brown fox jumped over the lazy dog" -输出:"heTmaa uickqmaaa rownbmaaaa oxfmaaaaa umpedjmaaaaaa overmaaaaaaa hetmaaaaaaaa azylmaaaaaaaaa ogdmaaaaaaaaaa" -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 使用集合 $vowels$ 存储元音字符,然后将 $sentence$ 按照空格分隔成单词数组 $words$。 -2. 遍历单词数组 $words$,对于当前单词 $word$,根据山羊拉丁文的规则,将其转为山羊拉丁文的单词,并存入答案数组 $res$ 中。 -3. 遍历完之后将答案数组拼接为字符串并返回。 - -### 思路 1:代码 - -```python -class Solution: - def toGoatLatin(self, sentence: str) -> str: - vowels = set(['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']) - words = sentence.split(' ') - res = [] - for i in range(len(words)): - word = words[i] - ans = "" - if word[0] in vowels: - ans += word + "ma" - else: - ans += word[1:] + word[0] + "ma" - ans += 'a' * (i + 1) - res.append(ans) - - return " ".join(res) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0830. \350\276\203\345\244\247\345\210\206\347\273\204\347\232\204\344\275\215\347\275\256.md" "b/Solutions/0830. \350\276\203\345\244\247\345\210\206\347\273\204\347\232\204\344\275\215\347\275\256.md" deleted file mode 100644 index 119e9ea5..00000000 --- "a/Solutions/0830. \350\276\203\345\244\247\345\210\206\347\273\204\347\232\204\344\275\215\347\275\256.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [0830. 较大分组的位置](https://leetcode.cn/problems/positions-of-large-groups/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定由小写字母构成的字符串 $s$。字符串 $s$ 包含一些连续的相同字符所构成的分组。 - -**要求**:找到每一个较大分组的区间,按起始位置下标递增顺序排序后,返回结果。 - -**说明**: - -- **较大分组**:我们称所有包含大于或等于三个连续字符的分组为较大分组。 - -**示例**: - -- 示例 1: - -```python -输入:s = "abbxxxxzzy" -输出:[[3,6]] -解释:"xxxx" 是一个起始于 3 且终止于 6 的较大分组。 -``` - -- 示例 2: - -```python -输入:s = "abc" -输出:[] -解释:"a","b" 和 "c" 均不是符合要求的较大分组。 -``` - -## 解题思路 - -### 思路 1:简单模拟 - -遍历字符串 $s$,统计出所有大于等于 $3$ 个连续字符的子字符串的开始位置与结束位置。具体步骤如下: - -1. 令 $cnt = 1$,然后从下标 $1$ 位置开始遍历字符串 $s$。 - 1. 如果 $s[i - 1] == s[i]$,则令 $cnt$ 加 $1$。 - 2. 如果 $s[i - 1] \ne s[i]$,说明出现了不同字符,则判断之前连续字符个数 $cnt$ 是否大于等于 $3$。 - 3. 如果 $cnt \ge 3$,则将对应包含 $cnt$ 个连续字符的子字符串的开始位置与结束位置存入答案数组中。 - 4. 令 $cnt = 1$,重新开始记录连续字符个数。 -2. 遍历完字符串 $s$,输出答案数组。 - -### 思路 1:代码 - -```python -class Solution: - def largeGroupPositions(self, s: str) -> List[List[int]]: - res = [] - cnt = 1 - size = len(s) - for i in range(1, size): - if s[i] == s[i - 1]: - cnt += 1 - else: - if cnt >= 3: - res.append([i - cnt, i - 1]) - cnt = 1 - if cnt >= 3: - res.append([size - cnt, size - 1]) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0832. \347\277\273\350\275\254\345\233\276\345\203\217.md" "b/Solutions/0832. \347\277\273\350\275\254\345\233\276\345\203\217.md" deleted file mode 100644 index 378fd3e2..00000000 --- "a/Solutions/0832. \347\277\273\350\275\254\345\233\276\345\203\217.md" +++ /dev/null @@ -1,28 +0,0 @@ -# [0832. 翻转图像](https://leetcode.cn/problems/flipping-an-image/) - -- 标签:数组、双指针、矩阵、模拟 -- 难度:简单 - -## 题目大意 - -给定一个二进制矩阵 `A` 代表图像,先将矩阵进行水平翻转,再进行翻转(将 0 变为 1,1 变为 0)。 - -## 解题思路 - -两重 for 循环,第二层 for 循环遍历到一半即可。对于 `image[i][j]`、`image[i][n-1-j]` 先水平翻转操作,再进行翻转。 - -## 代码 - -```python -class Solution: - def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]: - n = len(image) - for i in range(n): - for j in range((n+1)//2): - image[i][j], image[i][n-1-j] = image[i][n-1-j], image[i][j] - image[i][j] = 0 if image[i][j] == 1 else 1 - if j != n-1-j: - image[i][n-1-j] = 0 if image[i][n-1-j] == 1 else 1 - return image -``` - diff --git "a/Solutions/0834. \346\240\221\344\270\255\350\267\235\347\246\273\344\271\213\345\222\214.md" "b/Solutions/0834. \346\240\221\344\270\255\350\267\235\347\246\273\344\271\213\345\222\214.md" deleted file mode 100644 index 4876ebe4..00000000 --- "a/Solutions/0834. \346\240\221\344\270\255\350\267\235\347\246\273\344\271\213\345\222\214.md" +++ /dev/null @@ -1,108 +0,0 @@ -# [0834. 树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) - -- 标签:树、深度优先搜索、图、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个无向、连通的树。树中有 $n$ 个标记为 $0 \sim n - 1$ 的节点以及 $n - 1$ 条边 。 - -给定整数 $n$ 和数组 $edges$,其中 $edges[i] = [ai, bi]$ 表示树中的节点 $ai$ 和 $bi$ 之间有一条边。 - -**要求**:返回长度为 $n$ 的数组 $answer$,其中 $answer[i]$ 是树中第 $i$ 个节点与所有其他节点之间的距离之和。 - -**说明**: - -- $1 \le n \le 3 \times 10^4$。 -- $edges.length == n - 1$。 -- $edges[i].length == 2$。 -- $0 \le ai, bi < n$。 -- $ai \ne bi$。 -- 给定的输入保证为有效的树。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/07/23/lc-sumdist1.jpg) - -```python -输入: n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]] -输出: [8,12,6,10,10,10] -解释: 树如图所示。 -我们可以计算出 dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5) -也就是 1 + 1 + 2 + 2 + 2 = 8。 因此,answer[0] = 8,以此类推。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/07/23/lc-sumdist3.jpg) - -```python -输入: n = 2, edges = [[1,0]] -输出: [1,1] -``` - -## 解题思路 - -### 思路 1:树形 DP + 二次遍历换根法 - -最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点进行树形 DP。 - -对于节点 $u$,定义 $dp[u]$ 为:以节点 $u$ 为根节点的树,它的所有子节点到它的距离之和。 - -然后进行一轮深度优先搜索,在搜索的过程中得到以节点 $v$ 为根节点的树,节点 $v$ 与所有其他子节点之间的距离之和 $dp[v]$。还能得到子树的节点个数 $sizes[v]$。 - -对于节点 $v$ 来说,其对 $dp[u]$ 的贡献为:节点 $v$ 与所有其他子节点之间的距离之和,再加上需要经过 $u \rightarrow v$ 这条边的节点个数,即 $dp[v] + sizes[v]$。 - -可得到状态转移方程为:$dp[u] = \sum_{v \in graph[u]}(dp[v] + sizes[v])$。 - -这样,对于 $n$ 个节点来说,需要进行 $n$ 次树形 DP,这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 3 \times 10^4]$,这样做会导致超时,因此需要进行优化。 - -我们可以使用「二次遍历换根法」进行优化,从而在 $O(n)$ 的时间复杂度内解决这道题。 - -以编号为 $0$ 的节点为根节点,进行两次深度优先搜索。 - -1. 第一次遍历:从编号为 $0$ 的根节点开始,自底向上地计算出节点 $0$ 到其他的距离之和,记录在 $ans[0]$ 中。并且统计出以子节点为根节点的子树节点个数 $sizes[v]$。 -2. 第二次遍历:从编号为 $0$ 的根节点开始,自顶向下地枚举每个点,计算出将每个点作为新的根节点时,其他节点到根节点的距离之和。如果当前节点为 $v$,其父节点为 $u$,则自顶向下计算出 $ans[u]$ 之后,我们将根节点从 $u$ 换为节点 $v$,子树上的点到新根节点的距离比原来都小了 $1$,非子树上剩下所有点到新根节点的距离比原来都大了 $1$。则可以据此计算出节点 $v$ 与其他节点的距离和为:$ans[v] = ans[u] + n - 2 \times sizes[u]$。 - -### 思路 1:代码 - -```python -class Solution: - def sumOfDistancesInTree(self, n: int, edges: List[List[int]]) -> List[int]: - graph = [[] for _ in range(n)] - - for u, v in edges: - graph[u].append(v) - graph[v].append(u) - - - ans = [0 for _ in range(n)] - - sizes = [1 for _ in range(n)] - def dfs(u, fa, depth): - ans[0] += depth - for v in graph[u]: - if v == fa: - continue - dfs(v, u, depth + 1) - sizes[u] += sizes[v] - - def reroot(u, fa): - for v in graph[u]: - if v == fa: - continue - ans[v] = ans[u] + n - 2 * size[v] - reroot(v, u) - - dfs(0, -1, 0) - reroot(0, -1) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为树的节点个数。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0836. \347\237\251\345\275\242\351\207\215\345\217\240.md" "b/Solutions/0836. \347\237\251\345\275\242\351\207\215\345\217\240.md" deleted file mode 100644 index d0e10522..00000000 --- "a/Solutions/0836. \347\237\251\345\275\242\351\207\215\345\217\240.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [0836. 矩形重叠](https://leetcode.cn/problems/rectangle-overlap/) - -- 标签:几何、数学 -- 难度:简单 - -## 题目大意 - -给定两个矩形的左下角、右上角坐标:[x1, y1, x2, y2]。[x1, y1] 表示左下角坐标,[x2, y2] 表示右上角坐标。如果两个矩形相交面积大于 0,则称两矩形重叠。 - -要求:根据给定的矩形 rec1 和 rec2 的左下角、右上角坐标,如果重叠,则返回 True,否则返回 False。 - -## 解题思路 - -如果两个矩形重叠,则两个矩形的水平边投影到 x 轴上的线段会有交集,同理竖直边投影到 y 轴上的线段也会有交集。因此我们可以把问题看做是:判断两条线段是否有交集。 - -矩形 rec1 和 rec2 水平边投影到 x 轴上的线段为 `(rec1[0], rec1[2])` 和 `(rec2[0], rec2[2])`。如果两条线段有交集,则 `min(rec1[2], rec2[2]) > max(rec1[0], rec2[0])`。 - -矩形 rec1 和 rec2 竖直边投影到 y 轴上的线段为 `(rec1[1], rec1[3])` 和 `(rec2[1], rec2[3])`。如果两条线段有交集,则 `min(rec1[3], rec2[3]) > max(rec1[1], rec2[1])`。 - -判断是否满足上述条件,若满足则说明两个矩形重叠,返回 True,若不满足则返回 False。 - -## 代码 - -```python -class Solution: - def isRectangleOverlap(self, rec1: List[int], rec2: List[int]) -> bool: - return min(rec1[2], rec2[2]) > max(rec1[0], rec2[0]) and min(rec1[3], rec2[3]) > max(rec1[1], rec2[1]) -``` - diff --git "a/Solutions/0841. \351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.md" "b/Solutions/0841. \351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.md" deleted file mode 100644 index 278b7ffc..00000000 --- "a/Solutions/0841. \351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [0841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) - -- 标签:深度优先搜索、广度优先搜索、图 -- 难度:中等 - -## 题目大意 - -**描述**:有 `n` 个房间,编号为 `0` ~ `n - 1`,每个房间都有若干把钥匙,每把钥匙上都有一个编号,可以开启对应房间号的门。最初,除了 `0` 号房间外其他房间的门都是锁着的。 - -现在给定一个二维数组 `rooms`,`rooms[i][j]` 表示第 `i` 个房间的第 `j` 把钥匙所能开启的房间号。 - -**要求**:判断是否能开启所有房间的门。如果能开启,则返回 `True`。否则返回 `False`。 - -**说明**: - -- $n == rooms.length$。 -- $2 \le n \le 1000$。 -- $0 \le rooms[i].length \le 1000$。 -- $1 \le sum(rooms[i].length) \le 3000$。 -- $0 \le rooms[i][j] < n$。 -- 所有 $rooms[i]$ 的值互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:rooms = [[1],[2],[3],[]] -输出:True -解释: -我们从 0 号房间开始,拿到钥匙 1。 -之后我们去 1 号房间,拿到钥匙 2。 -然后我们去 2 号房间,拿到钥匙 3。 -最后我们去了 3 号房间。 -由于我们能够进入每个房间,我们返回 true。 -``` - -- 示例 2: - -```python -输入:rooms = [[1,3],[3,0,1],[2],[0]] -输出:False -解释:我们不能进入 2 号房间。 -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -当 `x` 号房间有 `y` 号房间的钥匙时,就可以认为我们可以通过 `x` 号房间去往 `y` 号房间。现在把 `n` 个房间看做是拥有 `n` 个节点的图,则上述关系可以看做是 `x` 与 `y` 点之间有一条有向边。 - -那么问题就变为了给定一张有向图,从 `0` 节点开始出发,问是否能到达所有的节点。 - -我们可以使用深度优先搜索的方式来解决这道题,具体做法如下: - -1. 使用 set 集合变量 `visited` 来统计遍历到的节点个数。 -2. 从 `0` 节点开始,使用深度优先搜索的方式遍历整个图。 -3. 将当前节点 `x` 加入到集合 `visited` 中,遍历当前节点的邻接点。 - 1. 如果邻接点不再集合 `visited` 中,则继续递归遍历。 -4. 最后深度优先搜索完毕,判断一下遍历到的节点个数是否等于图的节点个数(即集合 `visited` 中的元素个数是否等于节点个数)。 - 1. 如果等于,则返回 `True` - 2. 如果不等于,则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def canVisitAllRooms(self, rooms: List[List[int]]) -> bool: - def dfs(x): - visited.add(x) - for key in rooms[x]: - if key not in visited: - dfs(key) - visited = set() - dfs(0) - return len(visited) == len(rooms) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 是房间的数量,$m$ 是所有房间中的钥匙数量的总数。 -- **空间复杂度**:$O(n)$,递归调用的栈空间深度不超过 $n$。 \ No newline at end of file diff --git "a/Solutions/0844. \346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.md" "b/Solutions/0844. \346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index e1c38a2f..00000000 --- "a/Solutions/0844. \346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,106 +0,0 @@ -# [0844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) - -- 标签:栈、双指针、字符串、模拟 -- 难度:简单 - -## 题目大意 - -给定 `s` 和 `t` 两个字符串。字符串中的 `#` 代表退格字符。 - -要求:当它们分别被输入到空白的文本编辑器后,判断二者是否相等。如果相等,返回 `True`;否则,返回 `False`。 - -注意:如果对空文本输入退格字符,文本继续为空。 - -## 解题思路 - -这道题的第一个思路是用栈,第二个思路是使用分离双指针。 - -思路一:栈。 - -- 定义一个构建方法,用来将含有退格字符串构建为删除退格的字符串。构建方法如下。 - - 使用一个栈存放删除退格的字符串。 - - 遍历字符串,如果遇到的字符不是 `#`,则将其插入到栈中。 - - 如果遇到的字符是 `#`,且当前栈不为空,则将当前栈顶元素弹出。 -- 分别使用构建方法处理字符串 `s` 和 `t`,如果处理完的字符串 `s` 和 `t` 相等,则返回 `True`,否则返回 `False`。 - -思路二:分离双指针。 - -由于 `#` 会消除左侧字符,而不会影响右侧字符,所以我们选择从字符串尾端遍历 `s`、`t` 字符串。具体做法如下: - -- 使用分离双指针 `left_1`、`left_2`。`left_1` 指向字符串 `s` 末尾,`left_2` 指向字符串 `t` 末尾。使用 `sign_1`、`sign_2` 标记字符串 `s`、`t` 中当前退格字符个数。 -- 从后到前遍历字符串 `s`、`t`。 - - 先来循环处理字符串 `s` 尾端 `#` 的影响,具体如下: - - 如果当前字符是 `#`,则更新 `s` 当前退格字符个数,即 `sign_1 += 1`。同时将 `left_1` 左移。 - - 如果 `s` 当前退格字符个数大于 `0`,则退格数减一,即 `sign_1 -= 1`。同时将 `left_1` 左移。 - - 如果 `s` 当前为普通字符,则跳出循环。 - - 同理再来处理字符串 `t` 尾端 `#` 的影响,具体如下: - - 如果当前字符是 `#`,则更新 `t` 当前退格字符个数,即 `sign_2 += 1`。同时将 `left_2` 左移。 - - 如果 `t` 当前退格字符个数大于 `0`,则退格数减一,即 `sign_2 -= 1`。同时将 `left_2` 左移。 - - 如果 `t` 当前为普通字符,则跳出循环。 - - 处理完,如果两个字符串为空,则说明匹配,直接返回 `True`。 - - 再先排除长度不匹配的情况,直接返回 `False`。 - - 最后判断 `s[left_1]` 是否等于 `s[left_2]`。不等于则直接返回 `False`,等于则令 `left_1`、`left_2` 左移,继续遍历。 -- 遍历完没有出现不匹配的情况,则返回 `True`。 - -## 代码 - -- 思路一: - -```python -class Solution: - def build(self, s: str): - stack = [] - for ch in s: - if ch != '#': - stack.append(ch) - elif stack: - stack.pop() - return stack - - def backspaceCompare(self, s: str, t: str) -> bool: - return self.build(s) == self.build(t) -``` - -- 思路二: - -```python -class Solution: - def backspaceCompare(self, s: str, t: str) -> bool: - left_1, left_2 = len(s) - 1, len(t) - 1 - sign_1, sign_2 = 0, 0 - while left_1 >= 0 or left_2 >= 0: - while left_1 >= 0: - if s[left_1] == '#': - sign_1 += 1 - left_1 -= 1 - elif sign_1 > 0: - sign_1 -= 1 - left_1 -= 1 - else: - break - - while left_2 >= 0: - if t[left_2] == '#': - sign_2 += 1 - left_2 -= 1 - elif sign_2 > 0: - sign_2 -= 1 - left_2 -= 1 - else: - break - - if left_1 < 0 and left_2 < 0: - return True - if left_1 >= 0 and left_2 < 0: - return False - if left_1 < 0 and left_2 >= 0: - return False - if s[left_1] != t[left_2]: - return False - - left_1 -= 1 - left_2 -= 1 - - return True -``` - diff --git "a/Solutions/0845. \346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\351\225\277\345\261\261\350\204\211.md" "b/Solutions/0845. \346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\351\225\277\345\261\261\350\204\211.md" deleted file mode 100644 index 4212a8fd..00000000 --- "a/Solutions/0845. \346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\351\225\277\345\261\261\350\204\211.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [0845. 数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) - -- 标签:数组、双指针、动态规划、枚举 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `arr`。 - -要求:返回最长「山脉」长度。如果不含有 「山脉」 则返回 0。 - -- 山脉:数组`arr` 中满足 `arr[i - a] < ... < arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[i + b]` 的连续子数组。 - -## 解题思路 - -- 使用变量 `ans` 保存最长山脉长度。 -- 遍历数组,假定当前节点为山峰。 -- 使用双指针 `left`、`right` 分别向左、向右查找山脉的长度。 -- 如果当前山脉的长度比最长山脉长度更长,则更新最长山脉长度。 -- 最后输出 `ans`。 - -## 代码 - -```python -class Solution: - def longestMountain(self, arr: List[int]) -> int: - size = len(arr) - res = 0 - for i in range(1, size - 1): - if arr[i] > arr[i - 1] and arr[i] > arr[i + 1]: - left = i - 1 - right = i + 1 - - while left > 0 and arr[left - 1] < arr[left]: - left -= 1 - while right < size - 1 and arr[right + 1] < arr[right]: - right += 1 - if right - left + 1 > res: - res = right - left + 1 - return res -``` - diff --git "a/Solutions/0846. \344\270\200\346\211\213\351\241\272\345\255\220.md" "b/Solutions/0846. \344\270\200\346\211\213\351\241\272\345\255\220.md" deleted file mode 100644 index e4d38fd1..00000000 --- "a/Solutions/0846. \344\270\200\346\211\213\351\241\272\345\255\220.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [0846. 一手顺子](https://leetcode.cn/problems/hand-of-straights/) - -- 标签:贪心、数组、哈希表、排序 -- 难度:中等 - -## 题目大意 - -**描述**:`Alice` 手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌都是顺子(即由连续的牌构成),并且每一组的牌数都是 `groupSize`。现在给定一个整数数组 `hand`,其中 `hand[i]` 是表示第 `i` 张牌的数值,和一个整数 `groupSize`。 - -**要求**:如果 `Alice` 能将这些牌重新排列成若干组、并且每组都是 `goupSize` 张牌的顺子,则返回 `True`;否则,返回 `False`。 - -**说明**: - -- $1 \le hand.length \le 10^4$。 -- $0 \le hand[i] \le 10^9$。 -- $1 \le groupSize \le hand.length$。 - -**示例**: - -- 示例 1: - -```python -输入:hand = [1,2,3,6,2,3,4,7,8], groupSize = 3 -输出:True -解释:Alice 手中的牌可以被重新排列为 [1,2,3],[2,3,4],[6,7,8]。 -``` - -## 解题思路 - -### 思路 1:哈希表 + 排序 - -1. 使用哈希表存储每个数出现的次数。 -2. 将哈希表中每个键从小到大排序。 -3. 从哈希表中最小的数开始,以它作为当前顺子的开头,然后依次判断顺子里的数是否在哈希表中,如果在的话,则将哈希表中对应数的数量减 `1`。不在的话,说明无法满足题目要求,直接返回 `False`。 -4. 重复执行 2 ~ 3 步,直到哈希表为空。最后返回 `True`。 - -### 思路 1:哈希表 + 排序代码 - -```python -class Solution: - def isPossibleDivide(self, nums: List[int], k: int) -> bool: - hand_map = collections.defaultdict(int) - for i in range(len(nums)): - hand_map[nums[i]] += 1 - for key in sorted(hand_map.keys()): - value = hand_map[key] - if value == 0: - continue - count = 0 - for i in range(k): - hand_map[key + count] -= value - if hand_map[key + count] < 0: - return False - count += 1 - return True -``` diff --git "a/Solutions/0847. \350\256\277\351\227\256\346\211\200\346\234\211\350\212\202\347\202\271\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" "b/Solutions/0847. \350\256\277\351\227\256\346\211\200\346\234\211\350\212\202\347\202\271\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" deleted file mode 100644 index bc73779f..00000000 --- "a/Solutions/0847. \350\256\277\351\227\256\346\211\200\346\234\211\350\212\202\347\202\271\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [0847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) - -- 标签:位运算、广度优先搜索、图、动态规划、状态压缩 -- 难度:困难 - -## 题目大意 - -**描述**:存在一个由 $n$ 个节点组成的无向连通图,图中节点编号为 $0 \sim n - 1$。现在给定一个数组 $graph$ 表示这个图。其中,$graph[i]$ 是一个列表,由所有与节点 $i$ 直接相连的节点组成。 - -**要求**:返回能够访问所有节点的最短路径长度。可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。 - -**说明**: - -- $n == graph.length$。 -- $1 \le n \le 12$。 -- $0 \le graph[i].length < n$。 -- $graph[i]$ 不包含 $i$。 -- 如果 $graph[a]$ 包含 $b$,那么 $graph[b]$ 也包含 $a$。 -- 输入的图总是连通图。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/05/12/shortest1-graph.jpg) - -```python -输入:graph = [[1,2,3],[0],[0],[0]] -输出:4 -解释:一种可能的路径为 [1,0,2,0,3] -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/05/12/shortest2-graph.jpg) - -```python -输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]] -输出:4 -解释:一种可能的路径为 [0,1,4,2,3] -``` - -## 解题思路 - -### 思路 1:状态压缩 + 广度优先搜索 - - 题目需要求解的是「能够访问所有节点的最短路径长度」,并且每个节点都可以作为起始点。 - -如果对于一个特定的起点,我们可以将该起点放入队列中,然后对其进行广度优先搜索,并使用访问数组 $visited$ 标记访问过的节点,直到所有节点都已经访问过时,返回路径长度即为「从某点开始出发,所能够访问所有节点的最短路径长度」。 - -而本题中,每个节点都可以作为起始点,则我们可以直接将所有节点放入队列中,然后对所有节点进行广度优先搜索。 - -因为本题中节点数目 $n$ 的范围为 $[1, 12]$,所以我们可以采用「状态压缩」的方式,标记节点的访问情况。每个点的初始状态可以表示为 `(u, 1 << u)`。当状态 $state == 1 \text{ <}\text{< } n - 1$ 时,表示所有节点都已经访问过了,此时返回其对应路径长度即为「能够访问所有节点的最短路径长度」。 - -为了方便在广度优先搜索的同事,记录当前的「路径长度」以及「节点的访问情况」。我们可以使用一个三元组 $(u, state, dist)$ 来表示当前节点情况,其中: - -- $u$:表示当前节点编号。 -- $state$:一个 $n$ 位的二进制数,表示 $n$ 个节点的访问情况。$state$ 第 $i$ 位为 $0$ 时表示未访问过,$state$ 第 $i$ 位为 $1$ 时表示访问过。 -- $dist$ 表示当前的「路径长度」。 - -同时为了避免重复搜索同一个节点 $u$ 以及相同节点的访问情况,我们可以使用集合记录 $(u, state)$ 是否已经被搜索过。 - -整个算法步骤如下: - -1. 将所有节点的 `(节点编号, 起始状态, 路径长度)` 作为三元组存入队列,并使用集合 $visited$ 记录所有节点的访问情况。 -2. 对所有点开始进行广度优先搜索: - 1. 从队列中弹出队头节点。 - 2. 判断节点的当前状态,如果所有节点都已经访问过,则返回答案。 - 3. 如果没有全访问过,则遍历当前节点的邻接节点。 - 4. 将邻接节点的访问状态标记为访问过。 - 5. 如果节点即当前路径没有访问过,则加入队列继续遍历,并标记为访问过。 -3. 重复进行第 $2$ 步,直到队列为空。 - -### 思路 1:代码 - -```python -import collections - - -class Solution: - def shortestPathLength(self, graph: List[List[int]]) -> int: - size = len(graph) - - queue = collections.deque([]) - visited = set() - for u in range(size): - queue.append((u, 1 << u, 0)) # 将 (节点编号, 起始状态, 路径长度) 存入队列 - visited.add((u, 1 << u)) # 标记所有节点的节点编号,以及当前状态 - - while queue: # 对所有点开始进行广度优先搜索 - u, state, dist = queue.popleft() # 弹出队头节点 - if state == (1 << size) - 1: # 所有节点都访问完,返回答案 - return dist - for v in graph[u]: # 遍历邻接节点 - next_state = state | (1 << v) # 标记邻接节点的访问状态 - if (v, next_state) not in visited: # 如果节点即当前路径没有访问过,则加入队列继续遍历,并标记为访问过 - queue.append((v, next_state, dist + 1)) - visited.add((v, next_state)) - - return 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 \times 2^n)$,其中 $n$ 为图的节点数量。 -- **空间复杂度**:$O(n \times 2^n)$。 - diff --git "a/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" "b/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" deleted file mode 100644 index fc881a04..00000000 --- "a/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" +++ /dev/null @@ -1,162 +0,0 @@ -# [0850. 矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) - -- 标签:线段树、数组、有序集合、扫描线 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个二维矩形列表 `rectangles`,其中 `rectangle[i] = [x1, y1, x2, y2]` 表示第 `i` 个矩形,`(x1, y1)` 是第 `i` 个矩形左下角的坐标,`(x2, y2)` 是第 `i` 个矩形右上角的坐标。。 - -**要求**:计算 `rectangles` 中所有矩形所覆盖的总面积,并返回总面积。 - -**说明**: - -- 任何被两个或多个矩形覆盖的区域应只计算一次 。 -- 因为答案可能太大,返回 $10^9 + 7$ 的模。 -- $1 \le rectangles.length \le 200$。 -- $rectanges[i].length = 4$。 -- $0 \le x_1, y_1, x_2, y_2 \le 10^9$。 -- 矩形叠加覆盖后的总面积不会超越 $2^63 - 1$,这意味着可以用一个 $64$ 位有符号整数来保存面积结果。 - -**示例**: - -- 示例 1: - -![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/06/06/rectangle_area_ii_pic.png) - -```python -输入:rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]] -输出:6 -解释:如图所示,三个矩形覆盖了总面积为6的区域。 -从 (1,1) 到 (2,2),绿色矩形和红色矩形重叠。 -从 (1,0) 到 (2,3),三个矩形都重叠。 -``` - -## 解题思路 - -### 思路 1:扫描线 + 动态开点线段树 - - - -### 思路 1:扫描线 + 动态开点线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, left=-1, right=-1, cnt=0, height=0, leftNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = leftNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.cnt = cnt # 节点值(区间值) - self.height = height # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self): - self.tree = SegTreeNode(0, int(1e9)) - - # 区间更新接口:将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - - # 以下为内部实现方法 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, node): - - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - node.cnt += val # 当前节点所在区间每个元素值改为 val - self.__pushup(node) - return - - - self.__pushdown(node) - - if q_left <= node.mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.height # 直接返回节点值 - - self.__pushdown(node) - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, node.mid, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - res_right = self.__query_interval(node.mid + 1, q_right, node.rightNode) - - - return res_left + res_right # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新 node 节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.cnt > 0: - node.height = node.right - node.left + 1 - else: - if node.leftNode and node.rightNode: - node.height = node.leftNode.height + node.rightNode.height - else: - node.height = 0 - - # 向下更新实现方法:更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if node.leftNode is None: - node.leftNode = SegTreeNode(node.left, node.mid) - if node.rightNode is None: - node.rightNode = SegTreeNode(node.mid + 1, node.right) - -class Solution: - def rectangleArea(self, rectangles) -> int: - # lines 存储每个矩阵的上下两条边 - lines = [] - - for rectangle in rectangles: - x1, y1, x2, y2 = rectangle - lines.append([x1, y1 + 1, y2, 1]) - lines.append([x2, y1 + 1, y2, -1]) - - lines.sort(key=lambda line: line[0]) - - # 建立线段树 - self.STree = SegmentTree() - - ans = 0 - mod = 10 ** 9 + 7 - prev_x = lines[0][0] - for i in range(len(lines)): - x, y1, y2, val = lines[i] - height = self.STree.query_interval(0, int(1e9)) - ans += height * (x - prev_x) - ans %= mod - self.STree.update_interval(y1, y2, val) - prev_x = x - - return ans -``` - -## 参考资料 - -- 【文章】[【hdu1542】线段树求矩形面积并 - 拦路雨偏似雪花](https://www.cnblogs.com/KonjakJuruo/p/6024266.html) diff --git "a/Solutions/0851. \345\226\247\351\227\271\345\222\214\345\257\214\346\234\211.md" "b/Solutions/0851. \345\226\247\351\227\271\345\222\214\345\257\214\346\234\211.md" deleted file mode 100644 index 45ee7ba8..00000000 --- "a/Solutions/0851. \345\226\247\351\227\271\345\222\214\345\257\214\346\234\211.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0851. 喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) - -- 标签:深度优先搜索、图、拓扑排序、数组 -- 难度:中等 - -## 题目大意 - -**描述**:有一组 `n` 个人作为实验对象,从 `0` 到 `n - 1` 编号,其中每个人都有不同数目的钱,以及不同程度的安静值 `quietness`。 - -现在给定一个数组 `richer`,其中 `richer[i] = [ai, bi]` 表示第 `ai` 个人比第 `bi` 个人更有钱。另给你一个整数数组 `quiet`,其中 `quiet[i]` 是第 `i` 个人的安静值。数组 `richer` 中所给出的数据逻辑自洽(也就是说,在第 `ai` 个人比第 `bi` 个人更有钱的同时,不会出现第 `bi` 个人比第 `ai` 个人更有钱的情况 )。 - -**要求**:返回一个长度为 `n` 的整数数组 `answer` 作为答案,其中 `answer[i]` 表示在所有比第 `i` 个人更有钱或者和他一样有钱的人中,安静值最小的那个人的编号。 - -**说明**: - -- $n == quiet.length$ -- $1 \le n \le 500$。 -- $0 \le quiet[i] \le n$。 -- $quiet$ 的所有值互不相同。 -- $0 \le richer.length \le n * (n - 1) / 2$。 -- $0 \le ai, bi < n$。 -- $ai != bi$。 -- $richer$ 中的所有数对 互不相同。 -- 对 $richer$ 的观察在逻辑上是一致的。 - -**示例**: - -- 示例 1: - -```python -输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0] -输出:[5,5,2,5,4,5,6,7] - -解释: -answer[0] = 5, -person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。 -唯一较为安静(有较低的安静值 quiet[x])的人是 person 7, -但是目前还不清楚他是否比 person 0 更有钱。 -answer[7] = 7, -在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3,4,5,6 以及 7), -最安静(有较低安静值 quiet[x])的人是 person 7。 -其他的答案也可以用类似的推理来解释。 -``` - -## 解题思路 - -### 思路 1:拓扑排序 - -对于第 `i` 个人,我们要求解的是比第 `i` 个人更有钱或者和他一样有钱的人中,安静值最小的那个人的编号。 - -我们可以建立一张有向无环图,由富人指向穷人。这样,对于任意一点来说(比如 `x`),通过有向边链接的点(比如 `y`),拥有的钱都没有 `x` 多。则我们可以根据 `answer[x]` 去更新所有 `x` 能连接到的点的 `answer` 值。 - -我们可以先将数组 `answer` 元素初始化为当前元素编号。然后对建立的有向无环图进行拓扑排序,按照拓扑排序的顺序去更新 `x` 能连接到的点的 `answer` 值。 - -### 思路 1:拓扑排序代码 - -```python -import collections - -class Solution: - def loudAndRich(self, richer: List[List[int]], quiet: List[int]) -> List[int]: - - size = len(quiet) - indegrees = [0 for _ in range(size)] - edges = collections.defaultdict(list) - - for x, y in richer: - edges[x].append(y) - indegrees[y] += 1 - - res = [i for i in range(size)] - queue = collections.deque([]) - for i in range(size): - if not indegrees[i]: - queue.append(i) - - while queue: - x = queue.popleft() - size -= 1 - for y in edges[x]: - if quiet[res[x]] < quiet[res[y]]: - res[y] = res[x] - indegrees[y] -= 1 - if not indegrees[y]: - queue.append(y) - return res -``` diff --git "a/Solutions/0852. \345\261\261\350\204\211\346\225\260\347\273\204\347\232\204\345\263\260\351\241\266\347\264\242\345\274\225.md" "b/Solutions/0852. \345\261\261\350\204\211\346\225\260\347\273\204\347\232\204\345\263\260\351\241\266\347\264\242\345\274\225.md" deleted file mode 100644 index b519793b..00000000 --- "a/Solutions/0852. \345\261\261\350\204\211\346\225\260\347\273\204\347\232\204\345\263\260\351\241\266\347\264\242\345\274\225.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [0852. 山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:给定由整数组成的山脉数组 $arr$。 - -**要求**:返回任何满足 $arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[len(arr) - 1] $ 的下标 $i$。 - -**说明**: - -- **山脉数组**:满足以下属性的数组: - 1. $len(arr) \ge 3$; - 2. 存在 $i$($0 < i < len(arr) - 1$),使得: - 1. $arr[0] < arr[1] < ... arr[i-1] < arr[i]$; - 2. $arr[i] > arr[i+1] > ... > arr[len(arr) - 1]$。 -- $3 <= arr.length <= 105$ -- $0 <= arr[i] <= 106$ -- 题目数据保证 $arr$ 是一个山脉数组 - -**示例**: - -- 示例 1: - -```python -输入:arr = [0,1,0] -输出:1 -``` - -- 示例 2: - -```python -输入:arr = [0,2,1,0] -输出:1 -``` - -## 解题思路 - -### 思路 1:二分查找 - -1. 使用两个指针 $left$、$right$ 。$left$ 指向数组第一个元素,$right$ 指向数组最后一个元素。 -2. 取区间中间节点 $mid$,并比较 $nums[mid]$ 和 $nums[mid + 1]$ 的值大小。 - 1. 如果 $nums[mid]< nums[mid + 1]$,则右侧存在峰值,令 `left = mid + 1`。 - 2. 如果 $nums[mid] \ge nums[mid + 1]$,则左侧存在峰值,令 `right = mid`。 -3. 最后,当 $left == right$ 时,跳出循环,返回 $left$。 - -### 思路 1:代码 - -```python -class Solution: - def peakIndexInMountainArray(self, arr: List[int]) -> int: - left = 0 - right = len(arr) - 1 - while left < right: - mid = left + (right - left) // 2 - if arr[mid] < arr[mid + 1]: - left = mid + 1 - else: - right = mid - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0860. \346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.md" "b/Solutions/0860. \346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.md" deleted file mode 100644 index 1d2ae6fd..00000000 --- "a/Solutions/0860. \346\237\240\346\252\254\346\260\264\346\211\276\351\233\266.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0860. 柠檬水找零](https://leetcode.cn/problems/lemonade-change/) - -- 标签:贪心、数组 -- 难度:简单 - -## 题目大意 - -**描述**:一杯柠檬水的售价是 $5$ 美元。现在有 $n$ 个顾客排队购买柠檬水,每人只能购买一杯。顾客支付的钱面额有 $5$ 美元、$10$ 美元、$20$ 美元。必须给每个顾客正确找零(就是每位顾客需要向你支付 $5$ 美元,多出的钱要找还回顾客)。 - -现在给定 $n$ 个顾客支付的钱币面额数组 `bills`。 - -**要求**:如果能给每位顾客正确找零,则返回 `True`,否则返回 `False`。 - -**说明**: - -- 一开始的时候手头没有任何零钱。 -- $1 \le bills.length \le 10^5$。 -- `bills[i]` 不是 $5$ 就是 $10$ 或是 $20$。 - -**示例**: - -- 示例 1: - -```python -输入:bills = [5,5,5,10,20] -输出:True -解释: -前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 -第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 -第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 -由于所有客户都得到了正确的找零,所以我们输出 True。 -``` - -- 示例 2: - -```python -输入:bills = [5,5,10,10,20] -输出:False -解释: -前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 -对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 -对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 -由于不是每位顾客都得到了正确的找零,所以答案是 False。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -由于顾客只能给我们 $5$、$10$、$20$ 三种面额的钞票,且一开始我们手头没有任何钞票,所以我们手中所能拥有的钞票面额只能是 $5$、$10$、$20$。因此可以采取下面的策略: - -1. 如果顾客支付 $5$ 美元,直接收下。 -2. 如果顾客支付 $10$ 美元,如果我们手头有 $5$ 美元面额的钞票,则找给顾客,否则无法正确找零,返回 `False`。 -3. 如果顾客支付 $20$ 美元,如果我们手头有 $1$ 张 $10$ 美元和 $1$ 张 $5$ 美元的钞票,或者有 $3$ 张 $5$ 美元的钞票,则可以找给顾客。如果两种组合方式同时存在,倾向于第 $1$ 种方式找零,因为使用 $5$ 美元的场景比使用 $10$ 美元的场景多,要尽可能的保留 $5$ 美元的钞票。如果这两种组合方式都不通知,则无法正确找零,返回 `False`。 - -所以,我们可以使用两个变量 `five` 和 `ten` 来维护手中 $5$ 美元、$10$ 美团的钞票数量, 然后遍历一遍根据上述条件分别判断即可。 - -### 思路 1:代码 - -```python -class Solution: - def lemonadeChange(self, bills: List[int]) -> bool: - five, ten, twenty = 0, 0, 0 - for bill in bills: - if bill == 5: - five += 1 - if bill == 10: - if five <= 0: - return False - ten += 1 - five -= 1 - if bill == 20: - if five > 0 and ten > 0: - five -= 1 - ten -= 1 - twenty += 1 - elif five >= 3: - five -= 3 - twenty += 1 - else: - return False - - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `bill` 的长度。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0861. \347\277\273\350\275\254\347\237\251\351\230\265\345\220\216\347\232\204\345\276\227\345\210\206.md" "b/Solutions/0861. \347\277\273\350\275\254\347\237\251\351\230\265\345\220\216\347\232\204\345\276\227\345\210\206.md" deleted file mode 100644 index 29a38e5f..00000000 --- "a/Solutions/0861. \347\277\273\350\275\254\347\237\251\351\230\265\345\220\216\347\232\204\345\276\227\345\210\206.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [0861. 翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) - -- 标签:贪心、位运算、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二维矩阵 `A`,其中每个元素的值为 `0` 或 `1`。 - -我们可以选择任一行或列,并转换该行或列中的每一个值:将所有 `0` 都更改为 `1`,将所有 `1` 都更改为 `0`。 - -在做出任意次数的移动后,将该矩阵的每一行都按照二进制数来解释,矩阵的得分就是这些数字的总和。 - -**要求**:返回尽可能高的分数。 - -**说明**: - -- $1 \le A.length \le 20$。 -- $1 \le A[0].length \le 20$。 -- `A[i][j]` 值为 `0` 或 `1`。 - -**示例**: - -- 示例 1: - -```python -输入:[[0,0,1,1],[1,0,1,0],[1,1,0,0]] -输出:39 -解释: -转换为 [[1,1,1,1],[1,0,0,1],[1,1,1,1]] -0b1111 + 0b1001 + 0b1111 = 15 + 9 + 15 = 39 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -对于一个二进制数来说,应该优先保证高位(靠前的列)尽可能的大,也就是保证高位尽可能值为 `1`。 - -- 我们先来看矩阵的第一列数,只要第一列的某一行为 `0`,则将这一行的值进行翻转。这样就保证了最高位一定为 `1`。 -- 接下来,我们再来关注除了第一列的其他列,这里因为有最高位限制,所以我们不能随意再将某一行的值进行翻转,只能选择某一列进行翻转。 -- 为了保证当前位上有尽可能多的 `1`。我们可以用两个变量 `one_cnt`、`zeo_cnt` 来记录当前列上 `1` 的个数和 `0` 的个数。如果 `0` 的个数多于 `1` 的个数,那么我们就将当前列进行翻转。从而保证当前位上有尽可能多的 `1`。 -- 当所有列都遍历完成后,我们会得到加和最大的情况。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def matrixScore(self, grid: List[List[int]]) -> int: - zero_cnt, one_cnt = 0, 0 - res = 0 - rows, cols = len(grid), len(grid[0]) - - for col in range(cols): - for row in range(rows): - if col == 0 and grid[row][col] == 0: - for j in range(cols): - grid[row][j] = 1 - grid[row][j] - else: - if grid[row][col] == 1: - one_cnt += 1 - else: - zero_cnt += 1 - if zero_cnt > one_cnt: - for row in range(rows): - grid[row][col] = 1 - grid[row][col] - - for row in range(rows): - if grid[row][col] == 1: - res += pow(2, cols - col - 1) - zero_cnt = 0 - one_cnt = 0 - return res -``` diff --git "a/Solutions/0867. \350\275\254\347\275\256\347\237\251\351\230\265.md" "b/Solutions/0867. \350\275\254\347\275\256\347\237\251\351\230\265.md" deleted file mode 100644 index c5135c6a..00000000 --- "a/Solutions/0867. \350\275\254\347\275\256\347\237\251\351\230\265.md" +++ /dev/null @@ -1,27 +0,0 @@ -# [0867. 转置矩阵](https://leetcode.cn/problems/transpose-matrix/) - -- 标签:数组、矩阵、模拟 -- 难度:简单 - -## 题目大意 - -给定一个二维数组 matrix。返回 matrix 的转置矩阵。 - -## 解题思路 - -直接模拟求解即可。先求出 matrix 的规模。若 matrix 是 m * n 的矩阵。则创建一个 n * m 大小的矩阵 transposed。根据转置的规则对 transposed 的每个元素进行赋值。最终返回 transposed。 - -## 代码 - -```python -class Solution: - def transpose(self, matrix: List[List[int]]) -> List[List[int]]: - m = len(matrix) - n = len(matrix[0]) - transposed = [[0 for _ in range(m)] for _ in range(n)] - for i in range(m): - for j in range(n): - transposed[j][i] = matrix[i][j] - return transposed -``` - diff --git "a/Solutions/0872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.md" "b/Solutions/0872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.md" deleted file mode 100644 index b55a3318..00000000 --- "a/Solutions/0872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [0872. 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -将一棵二叉树树上所有的叶子,按照从左到右的顺序排列起来就形成了一个「叶值序列」。如果两棵二叉树的叶值序列是相同的,我们就认为它们是叶相似的。 - -现在给定两棵二叉树的根节点 `root1`、`root2`。如果两棵二叉是叶相似的,则返回 `True`,否则返回 `False`。 - -## 解题思路 - -分别 DFS 遍历两棵树,得到对应的叶值序列,判断两个叶值序列是否相等。 - -## 代码 - -```python -class Solution: - def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool: - def dfs(node: TreeNode, res: List[int]): - if not node: - return - if not node.left and not node.right: - res.append(node.val) - dfs(node.left, res) - dfs(node.right, res) - - res1 = [] - dfs(root1, res1) - res2 = [] - dfs(root2, res2) - return res1 == res2 -``` - diff --git "a/Solutions/0873. \346\234\200\351\225\277\347\232\204\346\226\220\346\263\242\351\202\243\345\245\221\345\255\220\345\272\217\345\210\227\347\232\204\351\225\277\345\272\246.md" "b/Solutions/0873. \346\234\200\351\225\277\347\232\204\346\226\220\346\263\242\351\202\243\345\245\221\345\255\220\345\272\217\345\210\227\347\232\204\351\225\277\345\272\246.md" deleted file mode 100644 index 0db68bfd..00000000 --- "a/Solutions/0873. \346\234\200\351\225\277\347\232\204\346\226\220\346\263\242\351\202\243\345\245\221\345\255\220\345\272\217\345\210\227\347\232\204\351\225\277\345\272\246.md" +++ /dev/null @@ -1,196 +0,0 @@ -# [0873. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) - -- 标签:数组、哈希表、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个严格递增的正整数数组 $arr$。 - -**要求**:从数组 $arr$ 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 0。 - -**说明**: - -- **斐波那契式序列**:如果序列 $X_1, X_2, ..., X_n$ 满足: - - - $n \ge 3$; - - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 - - 则称该序列为斐波那契式序列。 - -- **斐波那契式子序列**:从序列 $A$ 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:$A = [3, 4, 5, 6, 7, 8]$。则 $[3, 5, 8]$ 是 $A$ 的一个斐波那契式子序列。 - -- $3 \le arr.length \le 1000$。 - -- $1 \le arr[i] < arr[i + 1] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入: arr = [1,2,3,4,5,6,7,8] -输出: 5 -解释: 最长的斐波那契式子序列为 [1,2,3,5,8]。 -``` - -- 示例 2: - -```python -输入: arr = [1,3,7,11,12,14,18] -输出: 3 -解释: 最长的斐波那契式子序列有 [1,11,12]、[3,11,14] 以及 [7,11,18]。 -``` - -## 解题思路 - -### 思路 1: 暴力枚举(超时) - -假设 $arr[i]$、$arr[j]$、$arr[k]$ 是序列 $arr$ 中的 $3$ 个元素,且满足关系:$arr[i] + arr[j] == arr[k]$,则 $arr[i]$、$arr[j]$、$arr[k]$ 就构成了 $arr$ 的一个斐波那契式子序列。 - -通过 $arr[i]$、$arr[j]$,我们可以确定下一个斐波那契式子序列元素的值为 $arr[i] + arr[j]$。 - -因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 $arr[i]$、$arr[j]$,则可以顺着 $arr$ 序列,从第 $j + 1$ 的元素开始,查找值为 $arr[i] + arr[j]$ 的元素 。找到 $arr[i] + arr[j]$ 之后,然后再顺着查找子序列的下一个元素。 - -简单来说,就是确定了 $arr[i]$、$arr[j]$,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 $arr[i]$、$arr[j]$,统计不同的斐波那契式子序列的长度。 - -最后将这些长度进行比较,其中最长的长度就是答案。 - -### 思路 1:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - k = j + 1 - while k < size: - if arr[temp_i] + arr[temp_j] == arr[k]: - temp_ans += 1 - temp_i = temp_j - temp_j = k - k += 1 - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:哈希表 - -对于 $arr[i]$、$arr[j]$,要查找的元素 $arr[i] + arr[j]$ 是否在 $arr$ 中,我们可以预先建立一个反向的哈希表。键值对关系为 $value : idx$,这样就能在 $O(1)$ 的时间复杂度通过 $arr[i] + arr[j]$ 的值查找到对应的 $arr[k]$,而不用像原先一样线性查找 $arr[k]$ 了。 - -### 思路 2:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - idx_map = dict() - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - while arr[temp_i] + arr[temp_j] in idx_map: - temp_ans += 1 - k = idx_map[arr[temp_i] + arr[temp_j]] - temp_i = temp_j - temp_j = k - - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - -### 思路 3:动态规划 + 哈希表 - -###### 1. 划分阶段 - -按照斐波那契式子序列相邻两项的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。 - -###### 3. 状态转移方程 - -以 $arr[j]$、$arr[k]$ 结尾的斐波那契式子序列的最大长度 = 满足 $arr[i] + arr[j] = arr[k]$ 条件下,以 $arr[i]$、$arr[j]$ 结尾的斐波那契式子序列的最大长度加 $1$。即状态转移方程为:$dp[j][k] = max_{(A[i] + A[j] = A[k], i < j < k)}(dp[i][j] + 1)$。 - -###### 4. 初始条件 - -默认状态下,数组中任意相邻两项元素都可以作为长度为 $2$ 的斐波那契式子序列,即 $dp[i][j] = 2$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。那为了计算出最大的最长递增子序列长度,则需要在进行状态转移时,求出最大值 $ans$ 即为最终结果。 - -因为题目定义中,斐波那契式中 $n \ge 3$,所以只有当 $ans \ge 3$ 时,返回 $ans$。如果 $ans < 3$,则返回 $0$。 - -> **注意**:在进行状态转移的同时,我们应和「思路 2:哈希表」一样采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 - -### 思路 3:代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - - dp = [[0 for _ in range(size)] for _ in range(size)] - ans = 0 - - # 初始化 dp - for i in range(size): - for j in range(i + 1, size): - dp[i][j] = 2 - - idx_map = {} - # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - if arr[i] + arr[j] in idx_map: - # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 - k = idx_map[arr[i] + arr[j]] - - dp[j][k] = max(dp[j][k], dp[i][j] + 1) - ans = max(ans, dp[j][k]) - - if ans >= 3: - return ans - return 0 -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0875. \347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.md" "b/Solutions/0875. \347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.md" deleted file mode 100644 index f942c661..00000000 --- "a/Solutions/0875. \347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -给定一个数组 `piles` 代表 `n` 堆香蕉。其中 `piles[i]` 表示第 `i` 堆香蕉的个数。再给定一个整数 `h` ,表示最多可以在 `h` 小时内吃完所有香蕉。珂珂决定以速度每小时 `k`(未知)根的速度吃香蕉。每一个小时,她讲选择其中一堆香蕉,从中吃掉 `k` 根。如果这堆香蕉少于 `k` 根,珂珂将在这一小时吃掉这堆的所有香蕉,并且这一小时不会再吃其他堆的香蕉。 - -要求:返回珂珂可以在 `h` 小时内吃掉所有香蕉的最小速度 `k`(`k` 为整数)。 - -## 解题思路 - - 先来看 `k` 的取值范围,因为 `k` 是整数,且速度肯定不能为 `0` 吧,为 `0` 的话就永远吃不完了。所以`k` 的最小值可以取 `1`。`k` 的最大值根香蕉中最大堆的香蕉个数有关,因为 `1` 个小时内只能选择一堆吃,不能再吃其他堆的香蕉,则 `k` 的最大值取香蕉堆的最大值即可。即 `k` 的最大值为 `max(piles)`。 - -我们的目标是求出 `h` 小时内吃掉所有香蕉的最小速度 `k`。现在有了区间「`[1, max(piles)]`」,有了目标「最小速度 `k`」。接下来使用二分查找算法来查找「最小速度 `k`」。至于计算 `h` 小时内能否以 `k` 的速度吃完香蕉,我们可以再写一个方法 `canEat` 用于判断。如果能吃完就返回 `True`,不能吃完则返回 `False`。下面说一下算法的具体步骤。 - -- 使用两个指针 `left`、`right`。令 `left` 指向 `1`,`right` 指向 `max(piles)`。代表待查找区间为 `[left, right]` - -- 取两个节点中心位置 `mid`,判断是否能在 `h` 小时内以 `k` 的速度吃完香蕉。 - - 如果不能吃完,则将区间 `[left, mid]` 排除掉,继续在区间 `[mid + 1, right]` 中查找。 - - 如果能吃完,说明 `k` 还可以继续减小,则继续在区间 `[left, mid]` 中查找。 -- 当 `left == right` 时跳出循环,返回 `left`。 - -## 代码 - -```python -class Solution: - def canEat(self, piles, hour, speed): - time = 0 - for pile in piles: - time += (pile + speed - 1) // speed - return time <= hour - - def minEatingSpeed(self, piles: List[int], h: int) -> int: - left, right = 1, max(piles) - - while left < right: - mid = left + (right - left) // 2 - if not self.canEat(piles, h, mid): - left = mid + 1 - else: - right = mid - - return left -``` - diff --git "a/Solutions/0876. \351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.md" "b/Solutions/0876. \351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.md" deleted file mode 100644 index 7effbd71..00000000 --- "a/Solutions/0876. \351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [0876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) - -- 标签:链表、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个单链表的头节点 `head`。 - -**要求**:返回链表的中间节点。如果有两个中间节点,则返回第二个中间节点。 - -**说明**: - -- 给定链表的结点数介于 `1` 和 `100` 之间。 - -**示例**: - -- 示例 1: - -```python -输入:[1,2,3,4,5] -输出:此列表中的结点 3 (序列化形式:[3,4,5]) -解释:返回的结点值为 3 。 -注意,我们返回了一个 ListNode 类型的对象 ans,这样: -ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. -``` - -- 示例 2: - -```python -输入:[1,2,3,4,5,6] -输出:此列表中的结点 4 (序列化形式:[4,5,6]) -解释:由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 -``` - -## 解题思路 - -### 思路 1:单指针 - -先遍历一遍链表,统计一下节点个数为 `n`,再遍历到 `n / 2` 的位置,返回中间节点。 - -### 思路 1:代码 - -```python -class Solution: - def middleNode(self, head: ListNode) -> ListNode: - n = 0 - curr = head - while curr: - n += 1 - curr = curr.next - k = 0 - curr = head - while k < n // 2: - k += 1 - curr = curr.next - return curr -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:快慢指针 - -使用步长不一致的快慢指针进行一次遍历找到链表的中间节点。具体做法如下: - -1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 -2. 在循环体中将快、慢指针同时向右移动。其中慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 -3. 等到快指针移动到链表尾部(即 `fast == Node`)时跳出循环体,此时 `slow` 指向链表中间位置。 -4. 返回 `slow` 指针。 - -### 思路 2:代码 - -```python -class Solution: - def middleNode(self, head: ListNode) -> ListNode: - fast = head - slow = head - while fast and fast.next: - slow = slow.next - fast = fast.next.next - return slow -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0877. \347\237\263\345\255\220\346\270\270\346\210\217.md" "b/Solutions/0877. \347\237\263\345\255\220\346\270\270\346\210\217.md" deleted file mode 100644 index 9feacac2..00000000 --- "a/Solutions/0877. \347\237\263\345\255\220\346\270\270\346\210\217.md" +++ /dev/null @@ -1,30 +0,0 @@ -# [0877. 石子游戏](https://leetcode.cn/problems/stone-game/) - -- 标签:数组、数学、动态规划、博弈 -- 难度:中等 - -## 题目大意 - -亚历克斯和李在玩石子游戏。总共有偶数堆石子,每堆都有正整数颗石子 `piles[i]`,总共的石子数为奇数 。每回合,玩家从开始位置或者结束位置取走一整堆石子。直到没有石子堆为止结束游戏,最终手中石子颗数多的玩家获胜。假设亚历克斯和李每回合都能发挥出最佳水平,并且亚历克斯先开始。 - -给定代表每个位置石子颗数的数组 `piles`。 - -要求:判断亚历克斯是否能赢得比赛。如果亚历克斯赢得比赛,则返回 `True`。如果李赢得比赛返回 `False`。 - -## 解题思路 - -能取的次数是偶数个,总数是奇数个。 - -- 如果亚历克斯开始取了开始偶数位置 `0`,那么李只能取奇数位置 `1` 或者末尾位置 `len(piles) - 1`。然后亚历克斯可以j接着取偶数位。 -- 或者亚历克斯开始取了最后奇数位置 `len(piles) - 1`,那么李只能取偶数位置 `0` 或 `len(piles) - 2`。然后亚历克斯可以接着取奇数位。 -- 这样亚历克斯只要一开始计算好奇数位置上的石子总数多,还是偶数位置上的石子总数多,然后就可以选择一开始取奇数位置还是偶数位置。所以最后肯定会赢 -- 游戏一开始,其实就没李啥事了。。。 - -## 代码 - -```python -class Solution: - def stoneGame(self, piles: List[int]) -> bool: - return True -``` - diff --git "a/Solutions/0881. \346\225\221\347\224\237\350\211\207.md" "b/Solutions/0881. \346\225\221\347\224\237\350\211\207.md" deleted file mode 100644 index 37000d17..00000000 --- "a/Solutions/0881. \346\225\221\347\224\237\350\211\207.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0881. 救生艇](https://leetcode.cn/problems/boats-to-save-people/) - -- 标签:贪心、数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `people` 代表每个人的体重,其中第 `i` 个人的体重为 `people[i]`。再给定一个整数 `limit`,代表每艘船可以承载的最大重量。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 `limit`。 - -**要求**:返回载到每一个人所需的最小船数(保证每个人都能被船载)。 - -**说明**: - -- $1 \le people.length \le 5 \times 10^4$。 -- $1 \le people[i] \le limit \le 3 \times 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:people = [1,2], limit = 3 -输出:1 -解释:1 艘船载 (1, 2) -``` - -- 示例 2: - -```python -输入:people = [3,2,2,1], limit = 3 -输出:3 -解释:3 艘船分别载 (1, 2), (2) 和 (3) -``` - -## 解题思路 - -### 思路 1:贪心算法 + 双指针 - -暴力枚举的时间复杂度为 $O(n^2)$。使用双指针可以减少循环内的时间复杂度。 - -我们可以利用贪心算法的思想,让最重的和最轻的人一起走。这样一只船就可以尽可能的带上两个人。 - -具体做法如下: - -1. 先对数组进行升序排序,使用 `ans` 记录所需最小船数。 -2. 使用两个指针 `left`、`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 -3. 判断 `people[left]` 和 `people[right]` 加一起是否超重。 - 1. 如果 `people[left] + people[right] > limit`,则让重的人上船,船数量 + 1,令 `right` 左移,继续判断。 - 2. 如果 `people[left] + people[right] <= limit`,则两个人都上船,船数量 + 1,并令 `left` 右移,`right` 左移,继续判断。 -4. 如果 `lefft == right`,则让最后一个人上船,船数量 + 1。并返回答案。 - -### 思路 1:代码 - -```python -class Solution: - def numRescueBoats(self, people: List[int], limit: int) -> int: - people.sort() - size = len(people) - left, right = 0, size - 1 - ans = 0 - while left < right: - if people[left] + people[right] > limit: - right -= 1 - else: - left += 1 - right -= 1 - ans += 1 - if left == right: - ans += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是数组 `people` 的长度。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/0884. \344\270\244\345\217\245\350\257\235\344\270\255\347\232\204\344\270\215\345\270\270\350\247\201\345\215\225\350\257\215.md" "b/Solutions/0884. \344\270\244\345\217\245\350\257\235\344\270\255\347\232\204\344\270\215\345\270\270\350\247\201\345\215\225\350\257\215.md" deleted file mode 100644 index 3cd7c522..00000000 --- "a/Solutions/0884. \344\270\244\345\217\245\350\257\235\344\270\255\347\232\204\344\270\215\345\270\270\350\247\201\345\215\225\350\257\215.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [0884. 两句话中的不常见单词](https://leetcode.cn/problems/uncommon-words-from-two-sentences/) - -- 标签:哈希表、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个字符串 $s1$ 和 $s2$ ,分别表示两个句子。 - -**要求**:返回所有不常用单词的列表。返回列表中单词可以按任意顺序组织。 - -**说明**: - -- **句子**:是一串由空格分隔的单词。 -- **单词**:仅由小写字母组成的子字符串。 -- **不常见单词**:如果某个单词在其中一个句子中恰好出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的。 -- $1 \le s1.length, s2.length \le 200$。 -- $s1$ 和 $s2$ 由小写英文字母和空格组成。 -- $s1$ 和 $s2$ 都不含前导或尾随空格。 -- $s1$ 和 $s2$ 中的所有单词间均由单个空格分隔。 - -**示例**: - -- 示例 1: - -```python -输入:s1 = "this apple is sweet", s2 = "this apple is sour" -输出:["sweet","sour"] -``` - -- 示例 2: - -```python -输入:s1 = "apple apple", s2 = "banana" -输出:["banana"] -``` - -## 解题思路 - -### 思路 1:哈希表 - -题目要求找出在其中一个句子中恰好出现一次,在另一个句子中却没有出现的单词,其实就是找出在两个句子中只出现过一次的单词,我们可以用哈希表统计两个句子中每个单词的出现频次,然后将出现频次为 $1$ 的单词就是不常见单词,将其加入答案数组即可。 - -具体步骤如下: - -1. 遍历字符串 $s1$、$s2$,使用哈希表 $table$ 统计字符串 $s1$、$s2$ 各个单词的出现频次。 -2. 遍历哈希表,找出出现频次为 $1$ 的单词,将其加入答案数组 $res$ 中。 -3. 遍历完返回答案数组 $res$。 - -### 思路 1:代码 - -```python -class Solution: - def uncommonFromSentences(self, s1: str, s2: str) -> List[str]: - table = dict() - for word in s1.split(' '): - if word not in table: - table[word] = 1 - else: - table[word] += 1 - - for word in s2.split(' '): - if word not in table: - table[word] = 1 - else: - table[word] += 1 - - res = [] - for word in table: - if table[word] == 1: - res.append(word) - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$,其中 $m$、$n$ 分别为字符串 $s1$、$s2$ 的长度。 -- **空间复杂度**:$O(m + n)$。 diff --git "a/Solutions/0886. \345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.md" "b/Solutions/0886. \345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.md" deleted file mode 100644 index a165ac31..00000000 --- "a/Solutions/0886. \345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [0886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -把 n 个人(编号为 1, 2, ... , n)分为任意大小的两组。每个人都可能不喜欢其他人,那么他们不应该属于同一组。 - -给定表示不喜欢关系的数组 `dislikes`,其中 `dislikes[i] = [a, b]` 表示 `a` 和 `b` 互相不喜欢,不允许将编号 `a` 和 `b` 的人归入同一组。 - -要求:如果可以以这种方式将所有人分为两组,则返回 `True`;如果不能则返回 `False`。 - -## 解题思路 - -先构建图,对于 `dislikes[i] = [a, b]`,在节点 `a` 和 `b` 之间建立一条无向边,然后判断该图是否为二分图。具体做法如下: - -- 找到一个没有染色的节点 `u`,将其染成红色。 -- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 -- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 -- 如果所有节点都顺利染上色,则说明该图为二分图,可以将所有人分为两组,返回 `True`。否则,如果在途中不能顺利染色,不能将所有人分为两组,则返回 `False`。 - -## 代码 - -```python -class Solution: - def dfs(self, graph, colors, i, color): - colors[i] = color - for j in graph[i]: - if colors[j] == colors[i]: - return False - if colors[j] == 0 and not self.dfs(graph, colors, j, -color): - return False - return True - - def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool: - graph = [[] for _ in range(n + 1)] - colors = [0 for _ in range(n + 1)] - - for x, y in dislikes: - graph[x].append(y) - graph[y].append(x) - - for i in range(1, n + 1): - if colors[i] == 0 and not self.dfs(graph, colors, i, 1): - return False - return True -``` - diff --git "a/Solutions/0887. \351\270\241\350\233\213\346\216\211\350\220\275.md" "b/Solutions/0887. \351\270\241\350\233\213\346\216\211\350\220\275.md" deleted file mode 100644 index c3ebb86e..00000000 --- "a/Solutions/0887. \351\270\241\350\233\213\346\216\211\350\220\275.md" +++ /dev/null @@ -1,262 +0,0 @@ -# [0887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) - -- 标签:数学、二分查找、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 `k` 和整数 `n`,分别代表 `k` 枚鸡蛋和可以使用的一栋从第 `1` 层到第 `n` 层楼的建筑。 - -已知存在楼层 `f`,满足 `0 <= f <= n`,任何从高于 `f` 的楼层落下的鸡蛋都会碎,从 `f` 楼层或比它低的楼层落下的鸡蛋都不会碎。 - -每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 `x` 扔下(满足 `1 <= x <= n`),如果鸡蛋碎了,就不能再次使用它。如果鸡蛋没碎,则可以再次使用。 - -**要求**:计算并返回要确定 `f` 确切值的最小操作次数是多少。 - -**说明**: - -- $1 \le k \le 100$。 -- $1 \le n \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:k = 1, n = 2 -输入:2 -解释:鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0。否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1。如果它没碎,那么肯定能得出 f = 2。因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 -``` - -## 解题思路 - -这道题目的题意不是很容易理解,我们先把题目简化一下,忽略一些限制条件,理解简单情况下的题意。然后再一步步增加限制条件,从而弄明白这道题目的意思,以及思考清楚这道题的解题思路。 - -我们先忽略 `k` 个鸡蛋这个条件,假设有无限个鸡蛋。 - -现在有 `1` ~ `n` 一共 `n` 层楼。已知存在楼层 `f`,低于等于 `f` 层的楼层扔下去的鸡蛋都不会碎,高于 `f` 的楼层扔下去的鸡蛋都会碎。 - -当然这个楼层 `f` 的确切值题目没有给出,需要我们一次次去测试鸡蛋最高会在哪一层不会摔碎。 - -在每次操作中,我们可以选定一个楼层,将鸡蛋扔下去: - -- 如果鸡蛋没摔碎,则可以继续选择其他楼层进行测试。 -- 如果鸡蛋摔碎了,则该鸡蛋无法继续测试。 - -现在题目要求:**已知有 `n` 层楼,无限个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来?** - -最简单且直观的想法: - -1. 从第 `1` 楼开始扔鸡蛋。`1` 楼不碎,再去 `2` 楼扔。 -2. `2` 楼还不碎,就去 `3` 楼扔。 -3. …… -4. 直到鸡蛋碎了,也就找到了鸡蛋不会摔碎的最高层 `f`。 - -用这种方法,最坏情况下,鸡蛋在第 `n` 层也没摔碎。这种情况下我们总共试了 `n` 次才确定鸡蛋不会摔碎的最高楼层 `f`。 - -下面再来说一下比 `n` 次要少的情况。 - -如果我们可以通过二分查找的方法,先从 `1` ~ `n` 层的中间层开始扔鸡蛋。 - -- 如果鸡蛋碎了,则从第 `1` 层到中间层这个区间中去扔鸡蛋。 -- 如果鸡蛋没碎,则从中间层到第 `n` 层这个区间中去扔鸡蛋。 - -每次扔鸡蛋都从区间的中间层去扔,这样每次都能排除当前区间一半的答案,从而最终确定鸡蛋不会摔碎的最高楼层 `f`。 - -通过这种二分查找的方法,可以优化到 $\log n$ 次就能确定鸡蛋不会摔碎的最高楼层 `f`。 - -因为 $\log n \le n$,所以通过二分查找的方式,「至少」比线性查找的次数要少。 - -同样,我们还可以通过三分查找、五分查找等等方式减少次数。 - -这是在不限制鸡蛋个数的情况下,现在我们来限制一下鸡蛋个数为 `k`。 - -现在题目要求:**已知有 `n` 层楼,`k` 个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来?** - -如果鸡蛋足够多(大于等于 $\log_2 n$ 个),可以通过二分查找的方法来测试。如果鸡蛋不够多,可能二分查找过程中,鸡蛋就用没了,则不能通过二分查找的方法来测试。 - -那么这时候为了找出 `f` ,我们应该如何求出最少的扔鸡蛋次数? - -### 思路 1:动态规划(超时) - -可以这样考虑。题目限定了 `n` 层楼,`k` 个鸡蛋。 - -如果我们尝试在 `1` ~ `n` 层中的任意一层 `x` 扔鸡蛋: - -1. 如果鸡蛋没碎,则说明 `1` ~ `x` 层都不用再考虑了,我们需要用 `k` 个鸡蛋去考虑剩下的 `n - x` 层,问题就从 `(n, k)` 转变为了 `(n - x, k)`。 -2. 如果鸡蛋碎了,则说明 `x + 1` ~ `n` 层都不用再考虑了,我们需要去剩下的 `k - 1` 个鸡蛋考虑剩下的 `x - 1` 层,问题就从 `(n, k)` 转变为了 `(x - 1, k - 1)`。 - -这样一来,我们就可以根据上述关系使用动态规划方法来解决这道题目了。具体步骤如下: - -###### 1. 划分阶段 - -按照楼层数量、剩余鸡蛋个数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:一共有 `i` 层楼,`j` 个鸡蛋的条件下,为了找出 `f` ,最坏情况下的最少扔鸡蛋次数。 - -###### 3. 状态转移方程 - -根据之前的描述,`dp[i][j]` 有两个来源,其状态转移方程为: - -$dp[i][j] = min_{1 \le x \le n} (max(dp[i - x][j], dp[x - 1][j - 1])) + 1$ - -###### 4. 初始条件 - -给定鸡蛋 `k` 的取值范围为 `[1, 100]`,`f` 值取值范围为 `[0, n]`,初始化时,可以考虑将所有值设置为当前拥有的楼层数。 - -- 当鸡蛋数为 `1` 时,`dp[i][1] = i`。这是如果唯一的蛋碎了,则无法测试了。只能从低到高,一步步进行测试,最终最少测试数为当前拥有的楼层数(如果刚开始初始化时已经将所有值设置为当前拥有的楼层数,其实这一步可省略)。 -- 当楼层为 `1` 时,在 `1` 层扔鸡蛋,`dp[1][j] = 1`。这是因为: - - 如果在 `1` 层扔鸡蛋碎了,则 `f < 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 - - 如果在 `1` 层扔鸡蛋没碎,则 `f >= 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:一共有 `i` 层楼,`j` 个鸡蛋的条件下,为了找出 `f` ,最坏情况下的最少扔鸡蛋次数。则最终结果为 `dp[n][k]`。 - -### 思路 1:代码 - -```python -class Solution: - def superEggDrop(self, k: int, n: int) -> int: - dp = [[0 for _ in range(k + 1)] for i in range(n + 1)] - - for i in range(1, n + 1): - for j in range(1, k + 1): - dp[i][j] = i - - # for i in range(1, n + 1): - # dp[i][1] = i - - for j in range(1, k + 1): - dp[1][j] = 1 - - for i in range(2, n + 1): - for j in range(2, k + 1): - for x in range(1, i + 1): - dp[i][j] = min(dp[i][j], max(dp[i - x][j], dp[x - 1][j - 1]) + 1) - - return dp[n][k] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 \times k)$。三重循环的时间复杂度为 $O(n^2 \times k)$。 -- **空间复杂度**:$O(n \times k)$。 - -### 思路 2:动态规划优化 - -上一步中时间复杂度为 $O(n^2 \times k)$。根据 $n$ 的规模,提交上去不出意外的超时了。 - -我们可以观察一下上面的状态转移方程:$dp[i][j] = min_{1 \le x \le n} (max(dp[i - x][j], dp[x - 1][j - 1])) + 1$ 。 - -这里最外两层循环的 `i`、`j` 分别为状态的阶段,可以先将 `i`、`j` 看作固定值。最里层循环的 `x` 代表选择的任意一层 `x` ,值从 `1` 遍历到 `i`。 - -此时我们把 `dp[i - x][j]` 和 `dp[x - 1][j - 1]` 分别单独来看。可以看出: - -- 对于 `dp[i - x][j]`:当 `x` 增加时,`i - x` 的值减少,`dp[i - x][j]` 的值跟着减小。自变量 `x` 与函数 `dp[i - x][j]` 是一条单调非递增函数。 -- 对于 `dp[x - 1][j - 1]`:当 `x` 增加时, `x - 1` 的值增加,`dp[x - 1][j - 1]` 的值跟着增加。自变量 `x` 与函数 `dp[x - 1][j - 1]` 是一条单调非递减函数。 - -两条函数的交点处就是两个函数较大值的最小值位置。即 `dp[i][j]` 所取位置。而这个位置可以通过二分查找满足 `dp[x - 1][j - 1] >= dp[i - x][j]` 最大的那个 `x`。这样时间复杂度就从 $O(n^2 \times k)$ 优化到了 $O(n \log n \times k)$。 - -### 思路 2:代码 - -```python -class Solution: - def superEggDrop(self, k: int, n: int) -> int: - dp = [[0 for _ in range(k + 1)] for i in range(n + 1)] - - for i in range(1, n + 1): - for j in range(1, k + 1): - dp[i][j] = i - - # for i in range(1, n + 1): - # dp[i][1] = i - - for j in range(1, k + 1): - dp[1][j] = 1 - - for i in range(2, n + 1): - for j in range(2, k + 1): - left, right = 1, i - while left < right: - mid = left + (right - left) // 2 - if dp[mid - 1][j - 1] < dp[i - mid][j]: - left = mid + 1 - else: - right = mid - dp[i][j] = max(dp[left - 1][j - 1], dp[i - left][j]) + 1 - - return dp[n][k] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \log n \times k)$。两重循环的时间复杂度为 $O(n \times k)$,二分查找的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(n \times k)$。 - -### 思路 3:动态规划 + 逆向思维 - -再看一下我们现在的题目要求:已知有 `n` 层楼,`k` 个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来? - -我们可以逆向转换一下思维,将题目转变为:**已知有 `k` 个鸡蛋,最多扔 `x` 次鸡蛋(碎没碎都算 `1` 次),求最多可以检测的多少层?** - -我们把未知条件「扔鸡蛋的次数」变为了已知条件,将「检测的楼层个数」变为了未知条件。 - -这样如果求出来的「检测的楼层个数」大于等于 `n`,则说明 `1` ~ `n` 层楼都考虑全了,`f` 值也就明确了。我们只需要从符合条件的情况中,找出「扔鸡蛋次数」最少的次数即可。 - -动态规划的具体步骤如下: - -###### 1. 划分阶段 - -按照鸡蛋个数、扔鸡蛋的次数进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:一共有 `i` 个鸡蛋,最多扔 `j` 次鸡蛋(碎没碎都算 `1` 次)的条件下,最多可以检测的楼层个数。 - -###### 3. 状态转移方程 - -我们现在有 `i` 个鸡蛋,`j` 次扔鸡蛋的机会,现在尝试在 `1` ~ `n` 层中的任意一层 `x` 扔鸡蛋: - -1. 如果鸡蛋没碎,剩下 `i` 个鸡蛋,还有 `j - 1` 次扔鸡蛋的机会,最多可以检测 `dp[i][j - 1]` 层楼层。 -2. 如果鸡蛋碎了,剩下 `i - 1` 个鸡蛋,还有 `j - 1` 次扔鸡蛋的机会,最多可以检测 `dp[i - 1][j - 1]` 层楼层。 -3. 再加上我们扔鸡蛋的第 `x` 层,`i` 个鸡蛋,`j` 次扔鸡蛋的机会最多可以检测 `dp[i][j - 1] + dp[i - 1][j - 1] + 1` 层。 - -则状态转移方程为:$dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1$。 - -###### 4. 初始条件 - -- 当鸡蛋数为 `1` 时,只有 `1` 次扔鸡蛋的机会时,最多可以检测 `1` 层,即 `dp[1][1] = 1`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i][j]` 表示为:一共有 `i` 个鸡蛋,最多扔 `j` 次鸡蛋(碎没碎都算 `1` 次)的条件下,最多可以检测的楼层个数。则我们需要从满足 `i == k` 并且 `dp[i][j] >= n`(即 `k` 个鸡蛋,`j` 次扔鸡蛋,一共检测出 `n` 层楼)的情况中,找出最小的 ` j`,将其返回。 - -### 思路 3:代码 - -```python -class Solution: - def superEggDrop(self, k: int, n: int) -> int: - dp = [[0 for _ in range(n + 1)] for i in range(k + 1)] - dp[1][1] = 1 - - for i in range(1, k + 1): - for j in range(1, n + 1): - dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1 - if i == k and dp[i][j] >= n: - return j - return n -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n \times k)$。两重循环的时间复杂度为 $O(n \times k)$。 -- **空间复杂度**:$O(n \times k)$。 - -## 参考资料 - -- 【题解】[题目理解 + 基本解法 + 进阶解法 - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/ji-ben-dong-tai-gui-hua-jie-fa-by-labuladong/) -- 【题解】[动态规划(只解释官方题解方法一)(Java) - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang/) -- 【题解】[动态规划 & 记忆化搜索 2000ms -> 32ms 的过程 - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/python-dong-tai-gui-hua-ji-yi-hua-sou-su-hnj9/) diff --git "a/Solutions/0889. \346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0889. \346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index ef6e60e6..00000000 --- "a/Solutions/0889. \346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [0889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) - -- 标签:树、数组、哈希表、分治、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一棵无重复值二叉树的前序遍历结果 `preorder` 和后序遍历结果 `postorder`。 - -**要求**:构造出该二叉树并返回其根节点。如果存在多个答案,则可以返回其中任意一个。 - -**说明**: - -- $1 \le preorder.length \le 30$。 -- $1 \le preorder[i] \le preorder.length$。 -- `preorder` 中所有值都不同。 -- `postorder.length == preorder.length`。 -- $1 \le postorder[i] \le postorder.length$。 -- `postorder` 中所有值都不同。 -- 保证 `preorder` 和 `postorder` 是同一棵二叉树的前序遍历和后序遍历。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/07/24/lc-prepost.jpg) - -```python -输入:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1] -输出:[1,2,3,4,5,6,7] -``` - -- 示例 2: - -```python -输入: preorder = [1], postorder = [1] -输出: [1] -``` - -## 解题思路 - -### 思路 1:递归 - -如果已知二叉树的前序遍历序列和后序遍历序列,是不能唯一地确定一棵二叉树的。这是因为没有中序遍历序列无法确定左右部分,也就无法进行子序列的分割。 - -只有二叉树中每个节点度为 `2` 或者 `0` 的时候,已知前序遍历序列和后序遍历序列,才能唯一地确定一颗二叉树,如果二叉树中存在度为 `1` 的节点时是无法唯一地确定一棵二叉树的,这是因为我们无法判断该节点是左子树还是右子树。 - -而这道题说明了,如果存在多个答案,则可以返回其中任意一个。 - -我们可以默认指定前序遍历序列的第 `2` 个值为左子树的根节点,由此递归划分左右子序列。具体操作步骤如下: - -1. 从前序遍历序列中可知当前根节点的位置在 `preorder[0]`。 - -2. 前序遍历序列的第 `2` 个值为左子树的根节点,即 `preorder[1]`。通过在后序遍历中查找上一步根节点对应的位置 `postorder[k]`(该节点右侧为右子树序列),从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 - -3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 - -4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -### 思路 1:代码 - -```python -class Solution: - def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode: - def createTree(preorder, postorder, n): - if n == 0: - return None - node = TreeNode(preorder[0]) - if n == 1: - return node - k = 0 - while postorder[k] != preorder[1]: - k += 1 - node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1) - node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2) - return node - return createTree(preorder, postorder, len(preorder)) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n^2)$。 \ No newline at end of file diff --git "a/Solutions/0897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221.md" "b/Solutions/0897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index d247017d..00000000 --- "a/Solutions/0897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0897. 递增顺序搜索树](https://leetcode.cn/problems/increasing-order-search-tree/) - -- 标签:栈、树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root`。 - -要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 - -## 解题思路 - -可以分为两步: - -1. 中序遍历二叉搜索树,将节点先存储到列表中。 -2. 将列表中的节点构造成一棵递增顺序搜索树。 - -中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 - -构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 - -最后返回 `head.right` 即可。 - -## 代码 - -```python -class Solution: - def inOrder(self, root, res): - if not root: - return - self.inOrder(root.left, res) - res.append(root) - self.inOrder(root.right, res) - - def increasingBST(self, root: TreeNode) -> TreeNode: - res = [] - self.inOrder(root, res) - - if not res: - return - head = TreeNode(-1) - cur = head - for node in res: - node.left = node.right = None - cur.right = node - cur = cur.right - return head.right -``` - - - diff --git "a/Solutions/0901. \350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.md" "b/Solutions/0901. \350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.md" deleted file mode 100644 index 7f1c2cc8..00000000 --- "a/Solutions/0901. \350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) - -- 标签:栈、设计、数据流、单调栈 -- 难度:中等 - -## 题目大意 - -要求:编写一个 `StockSpanner` 类,用于收集某些股票的每日报价,并返回该股票当日价格的跨度。 - -- 今天股票价格的跨度:股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。 - -例如:如果未来 7 天股票的价格是 `[100, 80, 60, 70, 60, 75, 85]`,那么股票跨度将是 `[1, 1, 1, 2, 1, 4, 6]`。 - -## 解题思路 - -「求解小于或等于今天价格的最大连续日」等价于「求出左侧第一个比当前股票价格大的股票,并计算距离」。求出左侧第一个比当前股票价格大的股票我们可以使用「单调递减栈」来做。具体步骤如下: - -- 初始化方法:初始化一个空栈,即 `self.stack = []` - -- 求解今天股票价格的跨度: - - - 初始化跨度 `span` 为 `1`。 - - 如果今日股票价格 `price` 大于等于栈顶元素 `self.stack[-1][0]`,则: - - 将其弹出,即 `top = self.stack.pop()`。 - - 跨度累加上弹出栈顶元素的跨度,即 `span += top[1]`。 - - 继续判断,直到遇到一个今日股票价格 `price` 小于栈顶元素的元素位置,再将 `[price, span]` 压入栈中。 - - 如果今日股票价格 `price` 小于栈顶元素 `self.stack[-1][0]`,则直接将 `[price, span]` 压入栈中。 - - - 最后输出今天股票价格的跨度 `span`。 - -## 代码 - -```python -class StockSpanner: - - def __init__(self): - self.stack = [] - - def next(self, price: int) -> int: - span = 1 - while self.stack and price >= self.stack[-1][0]: - top = self.stack.pop() - span += top[1] - self.stack.append([price, span]) - return span -``` - diff --git "a/Solutions/0902. \346\234\200\345\244\247\344\270\272 N \347\232\204\346\225\260\345\255\227\347\273\204\345\220\210.md" "b/Solutions/0902. \346\234\200\345\244\247\344\270\272 N \347\232\204\346\225\260\345\255\227\347\273\204\345\220\210.md" deleted file mode 100644 index 18021e54..00000000 --- "a/Solutions/0902. \346\234\200\345\244\247\344\270\272 N \347\232\204\346\225\260\345\255\227\347\273\204\345\220\210.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [0902. 最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) - -- 标签:数组、数学、字符串、二分查找、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个按非递减序列排列的数字数组 $digits$。我们可以使用任意次数的 $digits[i]$ 来写数字。例如,如果 `digits = ["1", "3", "5"]`,我们可以写数字,如 `"13"`, `"551"`, 和 `"1351315"`。 - -**要求**:返回可以生成的小于等于给定整数 $n$ 的正整数个数。 - -**说明**: - -- $1 \le digits.length \le 9$。 -- $digits[i].length == 1$。 -- $digits[i]$ 是从 `'1'` 到 `'9'` 的数。 -- $digits$ 中的所有值都不同。 -- $digits$ 按非递减顺序排列。 -- $1 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:digits = ["1","3","5","7"], n = 100 -输出:20 -解释: -可写出的 20 个数字是: -1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77。 -``` - -- 示例 2: - -```python -输入:digits = ["1","4","9"], n = 1000000000 -输出:29523 -解释: -我们可以写 3 个一位数字,9 个两位数字,27 个三位数字, -81 个四位数字,243 个五位数字,729 个六位数字, -2187 个七位数字,6561 个八位数字和 19683 个九位数字。 -总共,可以使用D中的数字写出 29523 个整数。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -数位 DP 模板的应用。因为这道题目中可以使用任意次数的 $digits[i]$,所以不需要用状态压缩的方式来表示数字集合。 - -这道题的具体步骤如下: - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, True, False)` 开始递归。 `dfs(0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 开始时受到数字 $n$ 对应最高位数位的约束。 - 3. 开始时没有填写数字。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 - 2. 然后枚举 $digits$ 数组中所有能够填入的数字 $d$。 - 3. 如果 $d$ 超过了所能选择的最大数字 $maxX$ 则直接跳出循环。 - 4. 如果 $d$ 是合法数字,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, isLimit and d == maxX, True)`。 - 1. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 - 2. $isNum == True$ 表示 $pos$ 位选择了数字。 -6. 最后的方案数为 `dfs(0, True, False)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def atMostNGivenDigitSet(self, digits: List[str], n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, False, False) - - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = s[pos] if isLimit else '9' - - # 枚举可选择的数字 - for d in digits: - if d > maxX: - break - ans += dfs(pos + 1, isLimit and d == maxX, True) - - return ans - - return dfs(0, True, False) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times \log n)$,其中 $m$ 是数组 $digits$ 的长度,$\log n$ 是 $n$ 转为字符串之后的位数长度。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/0904. \346\260\264\346\236\234\346\210\220\347\257\256.md" "b/Solutions/0904. \346\260\264\346\236\234\346\210\220\347\257\256.md" deleted file mode 100644 index 9dcf0110..00000000 --- "a/Solutions/0904. \346\260\264\346\236\234\346\210\220\347\257\256.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [0904. 水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) - -- 标签:数组、哈希表、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个数组 `fruits`。其中 `fruits[i]` 表示第 `i` 棵树会产生 `fruits[i]` 型水果。 - -你可以从你选择的任何树开始,然后重复执行以下步骤: - -- 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。 -- 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。 -- 请注意,在选择一棵树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。 - -你有 `2` 个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。 - -要求:返回你能收集的水果树的最大总量。 - -## 解题思路 - -只有 `2` 个篮子,要求在连续子数组中装最多 `2` 种不同水果。可以理解为维护一个水果种类数为 `2` 的滑动数组,求窗口中最大的水果树数目。具体做法如下: - -- 用滑动窗口 `window` 来维护不同种类水果树数目。`window` 为哈希表类型。`ans` 用来维护能收集的水果树的最大总量。设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中水果种类数不超过 `2` 种。 -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧数组元素 `fruits[right]` 加入当前窗口 `window` 中,该水果树数目 +1。 -- 如果该窗口中该水果树种类多于 `2` 种,即 `len(window) > 2`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应水果树的个数,直到 `len(window) <= 2`。 -- 维护更新能收集的水果树的最大总量。然后右移 `right`,直到 `right >= len(fruits)` 结束。 -- 输出能收集的水果树的最大总量。 - -## 代码 - -```python -class Solution: - def totalFruit(self, fruits: List[int]) -> int: - window = dict() - window_size = 2 - ans = 0 - left, right = 0, 0 - while right < len(fruits): - if fruits[right] in window: - window[fruits[right]] += 1 - else: - window[fruits[right]] = 1 - - while len(window) > window_size: - window[fruits[left]] -= 1 - if window[fruits[left]] == 0: - del window[fruits[left]] - left += 1 - ans = max(ans, right - left + 1) - right += 1 - return ans -``` - diff --git "a/Solutions/0908. \346\234\200\345\260\217\345\267\256\345\200\274 I.md" "b/Solutions/0908. \346\234\200\345\260\217\345\267\256\345\200\274 I.md" deleted file mode 100644 index 54130626..00000000 --- "a/Solutions/0908. \346\234\200\345\260\217\345\267\256\345\200\274 I.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [0908. 最小差值 I](https://leetcode.cn/problems/smallest-range-i/) - -- 标签:数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 `nums`,和一个整数 `k`。给数组中的每个元素 `nums[i]` 都加上一个任意数字 `x` (`-k <= x <= k`),从而得到一个新数组 `result`。 - -**要求**:返回数组 `result` 的最大值和最小值之间可能存在的最小差值。 - -**说明**: - -- $1 \le nums.length \le 10^4$。 -- $0 \le nums[i] \le 10^4$。 -- $0 \le k \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1], k = 0 -输出:0 -解释:分数是 max(nums) - min(nums) = 1 - 1 = 0。 -``` - -- 示例 2: - -```python -输入:nums = [0,10], k = 2 -输出:6 -解释:将 nums 改为 [2,8]。分数是 max(nums) - min(nums) = 8 - 2 = 6。 -``` - -## 解题思路 - -### 思路 1:数学 - -`nums` 中的每个元素可以波动 `[-k, k]`。最小的差值就是「最大值减去 `k`」和「最小值加上 `k`」之间的差值。而如果差值小于 `0`,则说明每个数字都可以波动成相等的数字,此时直接返回 `0` 即可。 - -### 思路 1:代码 - -```python -class Solution: - def smallestRangeI(self, nums: List[int], k: int) -> int: - return max(0, max(nums) - min(nums) - 2*k) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0912. \346\216\222\345\272\217\346\225\260\347\273\204.md" "b/Solutions/0912. \346\216\222\345\272\217\346\225\260\347\273\204.md" deleted file mode 100644 index 4b9a0812..00000000 --- "a/Solutions/0912. \346\216\222\345\272\217\346\225\260\347\273\204.md" +++ /dev/null @@ -1,594 +0,0 @@ -# [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) - -- 标签:数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:将该数组升序排列。 - -**说明**: - -- $1 \le nums.length \le 5 * 10^4$。 -- $-5 * 10^4 \le nums[i] \le 5 * 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [5,2,3,1] -输出:[1,2,3,5] -``` - -- 示例 2: - -```python -输入:nums = [5,1,1,2,0,0] -输出:[0,0,1,1,2,5] -``` - -## 解题思路 - -这道题是一道用来复习排序算法,测试算法时间复杂度的好题。我试过了十种排序算法。得到了如下结论: - -- 超时算法(时间复杂度为 $O(n^2)$):冒泡排序、选择排序、插入排序。 -- 通过算法(时间复杂度为 $O(n \times \log n)$):希尔排序、归并排序、快速排序、堆排序。 -- 通过算法(时间复杂度为 $O(n)$):计数排序、桶排序。 -- 解答错误算法(普通基数排序只适合非负数):基数排序。 - -### 思路 1:冒泡排序(超时) - -> **冒泡排序(Bubble Sort)基本思想**:经过多次迭代,通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 - -假设数组的元素个数为 $n$ 个,则冒泡排序的算法步骤如下: - -1. 第 $1$ 趟「冒泡」:对前 $n$ 个元素执行「冒泡」,从而使第 $1$ 个值最大的元素放置在正确位置上。 - 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换。 - 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,如果前者大于后者,则两者交换位置,否则不交换。 - 3. 依次类推,直到第 $n - 1$ 个元素与第 $n$ 个元素比较(或交换)为止。 - 4. 经过第 $1$ 趟排序,使得 $n$ 个元素中第 $i$ 个值最大元素被安置在第 $n$ 个位置上。 -2. 第 $2$ 趟「冒泡」:对前 $n - 1$ 个元素执行「冒泡」,从而使第 $2$ 个值最大的元素放置在正确位置上。 - 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换。 - 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,若前者大于后者,则两者交换位置,否则不交换。 - 3. 依次类推,直到第 $n - 2$ 个元素与第 $n - 1$ 个元素比较(或交换)为止。 - 4. 经过第 $2$ 趟排序,使得数组中第 $2$ 个值最大元素被安置在第 $n$ 个位置上。 -3. 依次类推,重复上述「冒泡」过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 - -### 思路 1:代码 - -```python -class Solution: - def bubbleSort(self, nums: [int]) -> [int]: - # 第 i 趟「冒泡」 - for i in range(len(nums) - 1): - flag = False # 是否发生交换的标志位 - # 对数组未排序区间 [0, n - i - 1] 的元素执行「冒泡」 - for j in range(len(nums) - i - 1): - # 相邻两个元素进行比较,如果前者大于后者,则交换位置 - if nums[j] > nums[j + 1]: - nums[j], nums[j + 1] = nums[j + 1], nums[j] - flag = True - if not flag: # 此趟遍历未交换任何元素,直接跳出 - break - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.bubbleSort(nums) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:选择排序(超时) - ->**选择排序(Selection Sort)基本思想**:将数组分为两个区间,左侧为已排序区间,右侧为未排序区间。每趟从未排序区间中选择一个值最小的元素,放到已排序区间的末尾,从而将该元素划分到已排序区间。 - -假设数组的元素个数为 $n$ 个,则选择排序的算法步骤如下: - -1. 初始状态下,无已排序区间,未排序区间为 $[0, n - 1]$。 -2. 第 $1$ 趟选择: - 1. 遍历未排序区间 $[0, n - 1]$,使用变量 $min\underline{}i$ 记录区间中值最小的元素位置。 - 2. 将 $min\underline{}i$ 与下标为 $0$ 处的元素交换位置。如果下标为 $0$ 处元素就是值最小的元素位置,则不用交换。 - 3. 此时,$[0, 0]$ 为已排序区间,$[1, n - 1]$(总共 $n - 1$ 个元素)为未排序区间。 -3. 第 $2$ 趟选择: - 1. 遍历未排序区间 $[1, n - 1]$,使用变量 $min\underline{}i$ 记录区间中值最小的元素位置。 - 2. 将 $min\underline{}i$ 与下标为 $1$ 处的元素交换位置。如果下标为 $1$ 处元素就是值最小的元素位置,则不用交换。 - 3. 此时,$[0, 1]$ 为已排序区间,$[2, n - 1]$(总共 $n - 2$ 个元素)为未排序区间。 -4. 依次类推,对剩余未排序区间重复上述选择过程,直到所有元素都划分到已排序区间,排序结束。 - -### 思路 2:代码 - -```python -class Solution: - def selectionSort(self, nums: [int]) -> [int]: - for i in range(len(nums) - 1): - # 记录未排序区间中最小值的位置 - min_i = i - for j in range(i + 1, len(nums)): - if nums[j] < nums[min_i]: - min_i = j - # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 - if i != min_i: - nums[i], nums[min_i] = nums[min_i], nums[i] - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.selectionSort(nums) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 3:插入排序(超时) - ->**插入排序(Insertion Sort)基本思想**:将数组分为两个区间,左侧为有序区间,右侧为无序区间。每趟从无序区间取出一个元素,然后将其插入到有序区间的适当位置。 - -假设数组的元素个数为 $n$ 个,则插入排序的算法步骤如下: - -1. 初始状态下,有序区间为 $[0, 0]$,无序区间为 $[1, n - 1]$。 -2. 第 $1$ 趟插入: - 1. 取出无序区间 $[1, n - 1]$ 中的第 $1$ 个元素,即 $nums[1]$。 - 2. 从右到左遍历有序区间中的元素,将比 $nums[1]$ 小的元素向后移动 $1$ 位。 - 3. 如果遇到大于或等于 $nums[1]$ 的元素时,说明找到了插入位置,将 $nums[1]$ 插入到该位置。 - 4. 插入元素后有序区间变为 $[0, 1]$,无序区间变为 $[2, n - 1]$。 -3. 第 $2$ 趟插入: - 1. 取出无序区间 $[2, n - 1]$ 中的第 $1$ 个元素,即 $nums[2]$。 - 2. 从右到左遍历有序区间中的元素,将比 $nums[2]$ 小的元素向后移动 $1$ 位。 - 3. 如果遇到大于或等于 $nums[2]$ 的元素时,说明找到了插入位置,将 $nums[2]$ 插入到该位置。 - 4. 插入元素后有序区间变为 $[0, 2]$,无序区间变为 $[3, n - 1]$。 -4. 依次类推,对剩余无序区间中的元素重复上述插入过程,直到所有元素都插入到有序区间中,排序结束。 - -### 思路 3:代码 - -```python -class Solution: - def insertionSort(self, nums: [int]) -> [int]: - # 遍历无序区间 - for i in range(1, len(nums)): - temp = nums[i] - j = i - # 从右至左遍历有序区间 - while j > 0 and nums[j - 1] > temp: - # 将有序区间中插入位置右侧的所有元素依次右移一位 - nums[j] = nums[j - 1] - j -= 1 - # 将该元素插入到适当位置 - nums[j] = temp - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.insertionSort(nums) -``` - -### 思路 3:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 4:希尔排序(通过) - -> **希尔排序(Shell Sort)基本思想**:将整个数组切按照一定的间隔取值划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 $1$,对整个数组进行插入排序。 - -假设数组的元素个数为 $n$ 个,则希尔排序的算法步骤如下: - -1. 确定一个元素间隔数 $gap$。 -2. 将参加排序的数组按此间隔数从第 $1$ 个元素开始一次分成若干个子数组,即分别将所有位置相隔为 $gap$ 的元素视为一个子数组。 -3. 在各个子数组中采用某种排序算法(例如插入排序算法)进行排序。 -4. 减少间隔数,并重新将整个数组按新的间隔数分成若干个子数组,再分别对各个子数组进行排序。 -5. 依次类推,直到间隔数 $gap$ 值为 $1$,最后进行一次排序,排序结束。 - -### 思路 4:代码 - -```python -class Solution: - def shellSort(self, nums: [int]) -> [int]: - size = len(nums) - gap = size // 2 - # 按照 gap 分组 - while gap > 0: - # 对每组元素进行插入排序 - for i in range(gap, size): - # temp 为每组中无序数组第 1 个元素 - temp = nums[i] - j = i - # 从右至左遍历每组中的有序数组元素 - while j >= gap and nums[j - gap] > temp: - # 将每组有序数组中插入位置右侧的元素依次在组中右移一位 - nums[j] = nums[j - gap] - j -= gap - # 将该元素插入到适当位置 - nums[j] = temp - # 缩小 gap 间隔 - gap = gap // 2 - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.shellSort(nums) -``` - -### 思路 4:复杂度分析 - -- **时间复杂度**:介于 $O(n \times \log n)$ 与 $O(n^2)$ 之间。 -- **空间复杂度**:$O(1)$。 - -### 思路 5:归并排序(通过) - -> **归并排序(Merge Sort)基本思想**:采用经典的分治策略,先递归地将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。 - -假设数组的元素个数为 $n$ 个,则归并排序的算法步骤如下: - -1. **分解过程**:先递归地将当前数组平均分成两半,直到子数组长度为 $1$。 - 1. 找到数组中心位置 $mid$,从中心位置将数组分成左右两个子数组 $left\underline{}nums$、$right\underline{}nums$。 - 2. 对左右两个子数组 $left\underline{}nums$、$right\underline{}nums$ 分别进行递归分解。 - 3. 最终将数组分解为 $n$ 个长度均为 $1$ 的有序子数组。 -2. **归并过程**:从长度为 $1$ 的有序子数组开始,依次将有序数组两两合并,直到合并成一个长度为 $n$ 的有序数组。 - 1. 使用数组变量 $nums$ 存放合并后的有序数组。 - 2. 使用两个指针 $left\underline{}i$、$right\underline{}i$ 分别指向两个有序子数组 $left\underline{}nums$、$right\underline{}nums$ 的开始位置。 - 3. 比较两个指针指向的元素,将两个有序子数组中较小元素依次存入到结果数组 $nums$ 中,并将指针移动到下一位置。 - 4. 重复步骤 $3$,直到某一指针到达子数组末尾。 - 5. 将另一个子数组中的剩余元素存入到结果数组 $nums$ 中。 - 6. 返回合并后的有序数组 $nums$。 - -### 思路 5:代码 - -```python -class Solution: - # 合并过程 - def merge(self, left_nums: [int], right_nums: [int]): - nums = [] - left_i, right_i = 0, 0 - while left_i < len(left_nums) and right_i < len(right_nums): - # 将两个有序子数组中较小元素依次插入到结果数组中 - if left_nums[left_i] < right_nums[right_i]: - nums.append(left_nums[left_i]) - left_i += 1 - else: - nums.append(right_nums[right_i]) - right_i += 1 - - # 如果左子数组有剩余元素,则将其插入到结果数组中 - while left_i < len(left_nums): - nums.append(left_nums[left_i]) - left_i += 1 - - # 如果右子数组有剩余元素,则将其插入到结果数组中 - while right_i < len(right_nums): - nums.append(right_nums[right_i]) - right_i += 1 - - # 返回合并后的结果数组 - return nums - - # 分解过程 - def mergeSort(self, nums: [int]) -> [int]: - # 数组元素个数小于等于 1 时,直接返回原数组 - if len(nums) <= 1: - return nums - - mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 - left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 - right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 - return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 - - def sortArray(self, nums: [int]) -> [int]: - return self.mergeSort(nums) -``` - -### 思路 5:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 6:快速排序(通过) - -> **快速排序(Quick Sort)基本思想**:采用经典的分治策略,选择数组中某个元素作为基准数,通过一趟排序将数组分为独立的两个子数组,一个子数组中所有元素值都比基准数小,另一个子数组中所有元素值都比基准数大。然后再按照同样的方式递归的对两个子数组分别进行快速排序,以达到整个数组有序。 - -假设数组的元素个数为 $n$ 个,则快速排序的算法步骤如下: - -1. **哨兵划分**:选取一个基准数,将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。 - 1. 从当前数组中找到一个基准数 $pivot$(这里以当前数组第 $1$ 个元素作为基准数,即 $pivot = nums[low]$)。 - 2. 使用指针 $i$ 指向数组开始位置,指针 $j$ 指向数组末尾位置。 - 3. 从右向左移动指针 $j$,找到第 $1$ 个小于基准值的元素。 - 4. 从左向右移动指针 $i$,找到第 $1$ 个大于基准数的元素。 - 5. 交换指针 $i$、指针 $j$ 指向的两个元素位置。 - 6. 重复第 $3 \sim 5$ 步,直到指针 $i$ 和指针 $j$ 相遇时停止,最后将基准数放到两个子数组交界的位置上。 -2. **递归分解**:完成哨兵划分之后,对划分好的左右子数组分别进行递归排序。 - 1. 按照基准数的位置将数组拆分为左右两个子数组。 - 2. 对每个子数组分别重复「哨兵划分」和「递归分解」,直到各个子数组只有 $1$ 个元素,排序结束。 - -### 思路 6:代码 - -```python -import random - -class Solution: - # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 - def randomPartition(self, nums: [int], low: int, high: int) -> int: - # 随机挑选一个基准数 - i = random.randint(low, high) - # 将基准数与最低位互换 - nums[i], nums[low] = nums[low], nums[i] - # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 - return self.partition(nums, low, high) - - # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 - def partition(self, nums: [int], low: int, high: int) -> int: - # 以第 1 位元素为基准数 - pivot = nums[low] - - i, j = low, high - while i < j: - # 从右向左找到第 1 个小于基准数的元素 - while i < j and nums[j] >= pivot: - j -= 1 - # 从左向右找到第 1 个大于基准数的元素 - while i < j and nums[i] <= pivot: - i += 1 - # 交换元素 - nums[i], nums[j] = nums[j], nums[i] - - # 将基准数放到正确位置上 - nums[j], nums[low] = nums[low], nums[j] - return j - - def quickSort(self, nums: [int], low: int, high: int) -> [int]: - if low < high: - # 按照基准数的位置,将数组划分为左右两个子数组 - pivot_i = self.partition(nums, low, high) - # 对左右两个子数组分别进行递归快速排序 - self.quickSort(nums, low, pivot_i - 1) - self.quickSort(nums, pivot_i + 1, high) - - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.quickSort(nums, 0, len(nums) - 1) -``` - -### 思路 6:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 7:堆排序(通过) - -> **堆排序(Heap sort)基本思想**:借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 - -假设数组的元素个数为 $n$ 个,则堆排序的算法步骤如下: - -1. **构建初始大顶堆**: - 1. 定义一个数组实现的堆结构,将原始数组的元素依次存入堆结构的数组中(初始顺序不变)。 - 2. 从数组的中间位置开始,从右至左,依次通过「下移调整」将数组转换为一个大顶堆。 - -2. **交换元素,调整堆**: - 1. 交换堆顶元素(第 $1$ 个元素)与末尾(最后 $1$ 个元素)的位置,交换完成后,堆的长度减 $1$。 - 2. 交换元素之后,由于堆顶元素发生了改变,需要从根节点开始,对当前堆进行「下移调整」,使其保持堆的特性。 - -3. **重复交换和调整堆**: - 1. 重复第 $2$ 步,直到堆的大小为 $1$ 时,此时大顶堆的数组已经完全有序。 - -### 思路 7:代码 - -```python -class Solution: - # 调整为大顶堆 - def heapify(self, arr, index, end): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if arr[left] > arr[max_index]: - max_index = left - if right <= end and arr[right] > arr[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - arr[index], arr[max_index] = arr[max_index], arr[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(self, arr): - size = len(arr) - # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - self.heapify(arr, i, size - 1) - return arr - - # 升序堆排序,思路如下: - # 1. 先建立大顶堆 - # 2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值 - # 3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值 - # 4. 以此类推,直到最后一个元素交换之后完毕。 - def maxHeapSort(self, arr): - self.buildMaxHeap(arr) - size = len(arr) - for i in range(size): - arr[0], arr[size-i-1] = arr[size-i-1], arr[0] - self.heapify(arr, 0, size-i-2) - return arr - - def sortArray(self, nums: List[int]) -> List[int]: - return self.maxHeapSort(nums) -``` - -### 思路 7:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 8:计数排序(通过) - -> **计数排序(Counting Sort)基本思想**:通过统计数组中每个元素在数组中出现的次数,根据这些统计信息将数组元素有序的放置到正确位置,从而达到排序的目的。 - -假设数组的元素个数为 $n$ 个,则计数排序的算法步骤如下: - -1. **计算排序范围**:遍历数组,找出待排序序列中最大值元素 $nums\underline{}max$ 和最小值元素 $nums\underline{}min$,计算出排序范围为 $nums\underline{}max - nums\underline{}min + 1$。 -2. **定义计数数组**:定义一个大小为排序范围的计数数组 $counts$,用于统计每个元素的出现次数。其中: - 1. 数组的索引值 $num - nums\underline{}min$ 表示元素的值为 $num$。 - 2. 数组的值 $counts[num - nums\underline{}min]$ 表示元素 $num$ 的出现次数。 - -3. **对数组元素进行计数统计**:遍历待排序数组 $nums$,对每个元素在计数数组中进行计数,即将待排序数组中「每个元素值减去最小值」作为索引,将「对计数数组中的值」加 $1$,即令 $counts[num - nums\underline{}min]$ 加 $1$。 -4. **生成累积计数数组**:从 $counts$ 中的第 $1$ 个元素开始,每一项累家前一项和。此时 $counts[num - nums\underline{}min]$ 表示值为 $num$ 的元素在排序数组中最后一次出现的位置。 -5. **逆序填充目标数组**:逆序遍历数组 $nums$,将每个元素 $num$ 填入正确位置。 - 6. 将其填充到结果数组 $res$ 的索引 $counts[num - nums\underline{}min]$ 处。 - 7. 放入后,令累积计数数组中对应索引减 $1$,从而得到下个元素 $num$ 的放置位置。 - -### 思路 8:代码 - -```python -class Solution: - def countingSort(self, nums: [int]) -> [int]: - # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min - nums_min, nums_max = min(nums), max(nums) - # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 - size = nums_max - nums_min + 1 - counts = [0 for _ in range(size)] - - # 统计值为 num 的元素出现的次数 - for num in nums: - counts[num - nums_min] += 1 - - # 生成累积计数数组 - for i in range(1, size): - counts[i] += counts[i - 1] - - # 反向填充目标数组 - res = [0 for _ in range(len(nums))] - for i in range(len(nums) - 1, -1, -1): - num = nums[i] - # 根据累积计数数组,将 num 放在数组对应位置 - res[counts[num - nums_min] - 1] = num - # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 - counts[nums[i] - nums_min] -= 1 - - return res - - def sortArray(self, nums: [int]) -> [int]: - return self.countingSort(nums) -``` - -### 思路 8:复杂度分析 - -- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序序列的值域。 -- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。 - -### 思路 9:桶排序(通过) - -> **桶排序(Bucket Sort)基本思想**:将待排序数组中的元素分散到若干个「桶」中,然后对每个桶中的元素再进行单独排序。 - -假设数组的元素个数为 $n$ 个,则桶排序的算法步骤如下: - -1. **确定桶的数量**:根据待排序数组的值域范围,将数组划分为 $k$ 个桶,每个桶可以看做是一个范围区间。 -2. **分配元素**:遍历待排序数组元素,将每个元素根据大小分配到对应的桶中。 -3. **对每个桶进行排序**:对每个非空桶内的元素单独排序(使用插入排序、归并排序、快排排序等算法)。 -4. **合并桶内元素**:将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。 - -### 思路 9:代码 - -```python -class Solution: - def insertionSort(self, nums: [int]) -> [int]: - # 遍历无序区间 - for i in range(1, len(nums)): - temp = nums[i] - j = i - # 从右至左遍历有序区间 - while j > 0 and nums[j - 1] > temp: - # 将有序区间中插入位置右侧的元素依次右移一位 - nums[j] = nums[j - 1] - j -= 1 - # 将该元素插入到适当位置 - nums[j] = temp - - return nums - - def bucketSort(self, nums: [int], bucket_size=5) -> [int]: - # 计算待排序序列中最大值元素 nums_max、最小值元素 nums_min - nums_min, nums_max = min(nums), max(nums) - # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 - bucket_count = (nums_max - nums_min) // bucket_size + 1 - # 定义桶数组 buckets - buckets = [[] for _ in range(bucket_count)] - - # 遍历待排序数组元素,将每个元素根据大小分配到对应的桶中 - for num in nums: - buckets[(num - nums_min) // bucket_size].append(num) - - # 对每个非空桶内的元素单独排序,排序之后,按照区间顺序依次合并到 res 数组中 - res = [] - for bucket in buckets: - self.insertionSort(bucket) - res.extend(bucket) - - # 返回结果数组 - return res - - def sortArray(self, nums: [int]) -> [int]: - return self.bucketSort(nums) -``` - -### 思路 9:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 - -### 思路 10:基数排序(提交解答错误,普通基数排序只适合非负数) - -> **基数排序(Radix Sort)基本思想**:将整数按位数切割成不同的数字,然后从低位开始,依次到高位,逐位进行排序,从而达到排序的目的。 - -我们以最低位优先法为例,讲解一下基数排序的算法步骤。 - -1. **确定排序的最大位数**:遍历数组元素,获取数组最大值元素,并取得对应位数。 -2. **从最低位(个位)开始,到最高位为止,逐位对每一位进行排序**: - 1. 定义一个长度为 $10$ 的桶数组 $buckets$,每个桶分别代表 $0 \sim 9$ 中的 $1$ 个数字。 - 2. 按照每个元素当前位上的数字,将元素放入对应数字的桶中。 - 3. 清空原始数组,然后按照桶的顺序依次取出对应元素,重新加入到原始数组中。 - -### 思路 10:代码 - -```python -class Solution: - def radixSort(self, nums: [int]) -> [int]: - # 桶的大小为所有元素的最大位数 - size = len(str(max(nums))) - - # 从最低位(个位)开始,逐位遍历每一位 - for i in range(size): - # 定义长度为 10 的桶数组 buckets,每个桶分别代表 0 ~ 9 中的 1 个数字。 - buckets = [[] for _ in range(10)] - # 遍历数组元素,按照每个元素当前位上的数字,将元素放入对应数字的桶中。 - for num in nums: - buckets[num // (10 ** i) % 10].append(num) - # 清空原始数组 - nums.clear() - # 按照桶的顺序依次取出对应元素,重新加入到原始数组中。 - for bucket in buckets: - for num in bucket: - nums.append(num) - - # 完成排序,返回结果数组 - return nums - - def sortArray(self, nums: [int]) -> [int]: - return self.radixSort(nums) -``` - -### 思路 10:复杂度分析 - -- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 -- **空间复杂度**:$O(n + k)$。 - diff --git "a/Solutions/0918. \347\216\257\345\275\242\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" "b/Solutions/0918. \347\216\257\345\275\242\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" deleted file mode 100644 index 5846d299..00000000 --- "a/Solutions/0918. \347\216\257\345\275\242\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) - -- 标签:队列、数组、分治、动态规划、单调队列 -- 难度:中等 - -## 题目大意 - -给定一个环形整数数组 nums,数组 nums 的尾部和头部是相连状态。求环形数组 nums 的非空子数组的最大和(子数组中每个位置元素最多出现一次)。 - -## 解题思路 - -构成环形整数数组 nums 的非空子数组的最大和的子数组有两种情况: - -- 最大和的子数组为一个子区间:$nums[i] + nums[i+1] + nums[i+2] + ... + num[j]$。 -- 最大和的子数组为首尾的两个子区间:$(nums[0] + nums[1] + ... + nums[i]) + (nums[j] + nums[j+1] + ... + num[N-1])$。 - -第一种情况其实就是无环情况下的整数数组的非空子数组最大和问题,跟「[53. 最大子序和](https://leetcode.cn/problems/maximum-subarray/)」问题是一致的,我们假设求解结果为 `max_num`。 - -下来来思考第二种情况,第二种情况下,要使首尾两个子区间的和尽可能的大,则中间的子区间的和应该尽可能的小。 - -使得中间子区间的和尽可能小的问题,可以转变为求解:整数数组 nums 的非空子数组最小和问题。求解思路跟上边是相似的,只不过最大变为了最小。我们假设求解结果为 `min_num`。 - -而首尾两个区间和尽可能大的结果为数组 nums 的和减去中间最小子数组和,即 `sum(nums) - min_num`。 - - 最终的结果就是比较 `sum(nums) - min_num` 和 `max_num`的大小,返回较大值即可。 - -## 代码 - -```python -class Solution: - def maxSubarraySumCircular(self, nums: List[int]) -> int: - size = len(nums) - - dp_max, dp_min = nums[0], nums[0] - max_num, min_num = nums[0], nums[0] - for i in range(1, size): - dp_max = max(dp_max + nums[i], nums[i]) - dp_min = min(dp_min + nums[i], nums[i]) - max_num = max(dp_max, max_num) - min_num = min(dp_min, min_num) - sum_num = sum(nums) - if max_num < 0: - return max_num - return max(sum_num - min_num, max_num) -``` - diff --git "a/Solutions/0919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.md" "b/Solutions/0919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.md" deleted file mode 100644 index 49ced4d9..00000000 --- "a/Solutions/0919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [0919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) - -- 标签:树、广度优先搜索、设计、二叉树 -- 难度:中等 - -## 题目大意 - -要求:设计一个用完全二叉树初始化的数据结构 `CBTInserter`,并支持以下几种操作: - -- `CBTInserter(TreeNode root)` 使用根节点为 `root` 的给定树初始化该数据结构; -- `CBTInserter.insert(int v)` 向树中插入一个新节点,节点类型为 `TreeNode`,值为 `v`。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值; -- `CBTInserter.get_root()` 返回树的根节点。 - -## 解题思路 - -使用数组标记完全二叉树中节点的序号,初始化数组为 `[None]`。完全二叉树中节点的序号从 `1` 开始,对于序号为 `k` 的节点,其左子节点序号为 `2k`,右子节点的序号为 `2k + 1`,其父节点的序号为 `k // 2`。 - -然后在初始化和插入节点的同时,按顺序向数组中插入节点。 - -## 代码 - -```python -class CBTInserter: - - def __init__(self, root: TreeNode): - self.queue = [root] - self.nodelist = [None] - - while self.queue: - node = self.queue.pop(0) - self.nodelist.append(node) - if node.left: - self.queue.append(node.left) - if node.right: - self.queue.append(node.right) - - - def insert(self, v: int) -> int: - self.nodelist.append(TreeNode(v)) - index = len(self.nodelist) - 1 - father = self.nodelist[index // 2] - if index % 2 == 0: - father.left = self.nodelist[-1] - else: - father.right = self.nodelist[-1] - return father.val - - - def get_root(self) -> TreeNode: - return self.nodelist[1] -``` - diff --git "a/Solutions/0921. \344\275\277\346\213\254\345\217\267\346\234\211\346\225\210\347\232\204\346\234\200\345\260\221\346\267\273\345\212\240.md" "b/Solutions/0921. \344\275\277\346\213\254\345\217\267\346\234\211\346\225\210\347\232\204\346\234\200\345\260\221\346\267\273\345\212\240.md" deleted file mode 100644 index 30ecc6b7..00000000 --- "a/Solutions/0921. \344\275\277\346\213\254\345\217\267\346\234\211\346\225\210\347\232\204\346\234\200\345\260\221\346\267\273\345\212\240.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [0921. 使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) - -- 标签:栈、贪心、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个括号字符串 `s`,可以在字符串的任何位置插入一个括号。 - -**要求**:返回为使结果字符串 `s` 有效而必须添加的最少括号数。 - -**说明**: - -- $1 \le s.length \le 1000$。 -- `s` 只包含 `'('` 和 `')'` 字符。 - -只有满足下面几点之一,括号字符串才是有效的: - -- 它是一个空字符串,或者 -- 它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者 -- 它可以被写作 (A),其中 A 是有效字符串。 - -例如,如果 `s = "()))"`,你可以插入一个开始括号为 `"(()))"` 或结束括号为 `"())))"`。 - -**示例**: - -- 示例 1: - -```python -输入:s = "())" -输出:1 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -为了最终添加的最少括号数,我们应该尽可能将当前能够匹配的括号先进行配对。则剩余的未完成配对的括号数量就是答案。 - -我们使用变量 `left_cnt` 来记录当前左括号的数量。使用 `res` 来记录添加的最少括号数量。 - -- 遍历字符串,判断当前字符。 -- 如果当前字符为左括号 `(`,则令 `left_cnt` 加 `1`。 -- 如果当前字符为右括号 `)`,则令 `left_cnt` 减 `1`。如果 `left_cnt` 减到 `-1`,说明当前有右括号不能完成匹配,则答案数量 `res` 加 `1`,并令 `left_cnt` 重新赋值为 `0`。 -- 遍历完之后,令 `res` 加上剩余不匹配的 `left_cnt` 数量。 -- 最后输出 `res`。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def minAddToMakeValid(self, s: str) -> int: - res = 0 - left_cnt = 0 - for ch in s: - if ch == '(': - left_cnt += 1 - elif ch == ')': - left_cnt -= 1 - if left_cnt == -1: - left_cnt = 0 - res += 1 - res += left_cnt - return res -``` diff --git "a/Solutions/0925. \351\225\277\346\214\211\351\224\256\345\205\245.md" "b/Solutions/0925. \351\225\277\346\214\211\351\224\256\345\205\245.md" deleted file mode 100644 index e7a65a90..00000000 --- "a/Solutions/0925. \351\225\277\346\214\211\351\224\256\345\205\245.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [0925. 长按键入](https://leetcode.cn/problems/long-pressed-name/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:你的朋友正在使用键盘输入他的名字 $name$。偶尔,在键入字符时,按键可能会被长按,而字符可能被输入 $1$ 次或多次。 - -现在给定代表名字的字符串 $name$,以及实际输入的字符串 $typed$。 - -**要求**:检查键盘输入的字符 $typed$。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),就返回 `True`。否则返回 `False`。 - -**说明**: - -- $1 \le name.length, typed.length \le 1000$。 -- $name$ 和 $typed$ 的字符都是小写字母。 - -**示例**: - -- 示例 1: - -```python -输入:name = "alex", typed = "aaleex" -输出:true -解释:'alex' 中的 'a' 和 'e' 被长按。 -``` - -- 示例 2: - -```python -输入:name = "saeed", typed = "ssaaedd" -输出:false -解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。 -``` - -## 解题思路 - -### 思路 1:分离双指针 - -这道题目的意思是在 $typed$ 里边匹配 $name$,同时要考虑字符重复问题,以及不匹配的情况。可以使用分离双指针来做。具体做法如下: - -1. 使用两个指针 $left\underline{}1$、$left\underline{}2$,$left\underline{}1$ 指向字符串 $name$ 开始位置,$left\underline{}2$ 指向字符串 $type$ 开始位置。 -2. 如果 $name[left\underline{}1] == name[left\underline{}2]$,则将 $left\underline{}1$、$left\underline{}2$ 同时右移。 -3. 如果 $nmae[left\underline{}1] \ne name[left\underline{}2]$,则: - 1. 如果 $typed[left\underline{}2]$ 和前一个位置元素 $typed[left\underline{}2 - 1]$ 相等,则说明出现了重复元素,将 $left\underline{}2$ 右移,过滤重复元素。 - 2. 如果 $typed[left\underline{}2]$ 和前一个位置元素 $typed[left\underline{}2 - 1]$ 不等,则说明出现了多余元素,不匹配。直接返回 `False` 即可。 - -4. 当 $left\underline{}1 == len(name)$ 或者 $left\underline{}2 == len(typed)$ 时跳出循环。然后过滤掉 $typed$ 末尾的重复元素。 -5. 最后判断,如果 $left\underline{}1 == len(name)$ 并且 $left\underline{}2 == len(typed)$,则说明匹配,返回 `True`,否则返回 `False`。 - -### 思路 1:代码 - -```python -class Solution: - def isLongPressedName(self, name: str, typed: str) -> bool: - left_1, left_2 = 0, 0 - - while left_1 < len(name) and left_2 < len(typed): - if name[left_1] == typed[left_2]: - left_1 += 1 - left_2 += 1 - elif left_2 > 0 and typed[left_2 - 1] == typed[left_2]: - left_2 += 1 - else: - return False - while 0 < left_2 < len(typed) and typed[left_2] == typed[left_2 - 1]: - left_2 += 1 - - if left_1 == len(name) and left_2 == len(typed): - return True - else: - return False -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$。其中 $n$、$m$ 分别为字符串 $name$、$typed$ 的长度。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/0932. \346\274\202\344\272\256\346\225\260\347\273\204.md" "b/Solutions/0932. \346\274\202\344\272\256\346\225\260\347\273\204.md" deleted file mode 100644 index 9c99e7a7..00000000 --- "a/Solutions/0932. \346\274\202\344\272\256\346\225\260\347\273\204.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [0932. 漂亮数组](https://leetcode.cn/problems/beautiful-array/) - -- 标签:数组、数学、分治 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回长度为 $n$ 的任一漂亮数组。 - -**说明**: - -- **漂亮数组**(长度为 $n$ 的数组 $nums$ 满足下述条件): - - $nums$ 是由范围 $[1, n]$ 的整数组成的一个排列。 - - 对于每个 $0 \le i < j < n$,均不存在下标 $k$($i < k < j$)使得 $2 \times nums[k] == nums[i] + nums[j]$。 -- $1 \le n \le 1000$。 -- 本题保证对于给定的 $n$ 至少存在一个有效答案。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:[2,1,4,3] -``` - -- 示例 2: - -```python -输入:n = 5 -输出:[3,1,2,5,4] -``` - -## 解题思路 - -### 思路 1:分治算法 - -根据题目要求,我们可以得到以下信息: - -1. 题目要求 $2 \times nums[k] == nums[i] + nums[j], (0 \le i < k < j < n)$ 不能成立,可知:等式左侧必为偶数,只要右侧和为奇数则等式不成立。 -2. 已知:奇数 + 偶数 = 奇数,则令 $nums[i]$ 和 $nums[j]$ 其中一个为奇数,另一个为偶数,即可保证 $nums[i] + nums[j]$ 一定为奇数。这里我们不妨令 $nums[i]$ 为奇数,令 $nums[j]$ 为偶数。 -3. 如果数组 $nums$ 是漂亮数组,那么对数组 $nums$ 的每一位元素乘以一个常数或者加上一个常数之后,$nums$ 仍是漂亮数组。 - - 即如果 $[a_1, a_2, ..., a_n]$ 是一个漂亮数组,那么 $[k \times a_1 + b, k \times a_2 + b, ..., k \times a_n + b]$ 也是漂亮数组。 - -那么,我们可以按照下面的规则构建长度为 $n$ 的漂亮数组。 - -1. 当 $n = 1$ 时,返回 $[1]$。此时数组 $nums$ 中仅有 $1$ 个元素,并且满足漂亮数组的条件。 -2. 当 $n > 1$ 时,我们将 $nums$ 分解为左右两个部分:`left_nums`、`right_nums`。如果左右两个部分满足: - 1. 数组 `left_nums` 中元素全为奇数(可以通过 `nums[i] * 2 - 1` 将 `left_nums` 中元素全部映射为奇数)。 - 2. 数组 `right_nums` 中元素全为偶数(可以通过 `nums[i] * 2` 将 `right_nums` 中元素全部映射为偶数)。 - 3. `left_nums` 和 `right_nums` 都是漂亮数组。 -3. 那么 `left_nums + right_nums` 构成的数组一定也是漂亮数组,即 $nums$ 为漂亮数组,将 $nums$ 返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def beautifulArray(self, n: int) -> List[int]: - if n == 1: - return [1] - - nums = [0 for _ in range(n)] - left_cnt = (n + 1) // 2 - right_cnt = n - left_cnt - left_nums = self.beautifulArray(left_cnt) - right_nums = self.beautifulArray(right_cnt) - - for i in range(left_cnt): - nums[i] = 2 * left_nums[i] - 1 - - for i in range(right_cnt): - nums[left_cnt + i] = 2 * right_nums[i] - - return nums -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n \times \log n)$。 diff --git "a/Solutions/0933. \346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.md" "b/Solutions/0933. \346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.md" deleted file mode 100644 index 3578a7b0..00000000 --- "a/Solutions/0933. \346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [0933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) - -- 标签:设计、队列、数据流 -- 难度:简单 - -## 题目大意 - -要求:实现一个用来计算特定时间范围内的最近请求的 `RecentCounter` 类: - -- `RecentCounter()` 初始化计数器,请求数为 0 。 -- `int ping(int t)` 在时间 `t` 时添加一个新请求,其中 `t` 表示以毫秒为单位的某个时间,并返回在 `[t-3000, t]` 内发生的请求数。 - -## 解题思路 - -使用一个队列,用于存储 `[t - 3000, t]` 范围内的请求。 - -获取请求数时,将队首所有小于 `t - 3000` 时间的请求将其从队列中移除,然后返回队列的长度即可。 - -## 代码 - -```python -class RecentCounter: - - def __init__(self): - self.queue = [] - - - def ping(self, t: int) -> int: - self.queue.append(t) - while self.queue[0] < t - 3000: - self.queue.pop(0) - return len(self.queue) -``` - diff --git "a/Solutions/0935. \351\252\221\345\243\253\346\213\250\345\217\267\345\231\250.md" "b/Solutions/0935. \351\252\221\345\243\253\346\213\250\345\217\267\345\231\250.md" deleted file mode 100644 index 9b20ed21..00000000 --- "a/Solutions/0935. \351\252\221\345\243\253\346\213\250\345\217\267\345\231\250.md" +++ /dev/null @@ -1,108 +0,0 @@ -# [0935. 骑士拨号器](https://leetcode.cn/problems/knight-dialer/) - -- 标签:动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:象棋骑士可以垂直移动两个方格,水平移动一个方格,或者水平移动两个方格,垂直移动一个方格(两者都形成一个 $L$ 的形状),如下图所示。 - -![](https://assets.leetcode.com/uploads/2020/08/18/chess.jpg) - -现在我们有一个象棋其实和一个电话垫,如下图所示,骑士只能站在一个数字单元格上($0 \sim 9$)。 - -![](https://assets.leetcode.com/uploads/2020/08/18/phone.jpg) - -现在给定一个整数 $n$。 - -**要求**:返回我们可以拨多少个长度为 $n$ 的不同电话号码。因为答案可能很大,所以最终答案需要对 $10^9 + 7$ 进行取模。 - -**说明**: - -- 可以将骑士放在任何数字单元格上,然后执行 $n - 1$ 次移动来获得长度为 $n$ 的电话号码。 -- $1 \le n \le 5000$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 1 -输出:10 -解释:我们需要拨一个长度为1的数字,所以把骑士放在10个单元格中的任何一个数字单元格上都能满足条件。 -``` - -- 示例 2: - -```python -输入:n = 2 -输出:20 -解释:我们可以拨打的所有有效号码为[04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94] -``` - -## 解题思路 - -### 思路 1:动态规划 - -根据象棋骑士的跳跃规则,以及电话键盘的样式,我们可以预先处理一下象棋骑士当前位置与下一步能跳跃到的位置关系,将其存入哈希表中,方便查询。 - -接下来我们可以用动态规划的方式,计算出跳跃 $n - 1$ 次总共能得到多少个长度为 $n$ 的不同电话号码。 - -###### 1. 划分阶段 - -按照步数、所处数字位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][v]$ 表示为:第 $i$ 步到达键位 $u$ 总共能到的长度为 $i + 1$ 的不同电话号码个数。 - -###### 3. 状态转移方程 - -第 $i$ 步到达键位 $v$ 所能得到的不同电话号码个数,取决于 $i - 1$ 步中所有能到达 $v$ 的键位 $u$ 的不同电话号码个数总和。 - -呢状态转移方程为:$dp[i][v] = \sum dp[i - 1][u]$(可以从 $u$ 跳到 $v$)。 - -###### 4. 初始条件 - -- 第 $0$ 步(位于开始位置)所能得到的电话号码个数为 $1$,因为开始时可以将骑士放在任何数字单元格上,所以所有的 $dp[0][v] = 1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][v]$ 表示为:第 $i$ 步到达键位 $u$ 总共能到的长度为 $i + 1$ 的不同电话号码个数。 所以最终结果为第 $n - 1$ 行所有的 $dp[n - 1][v]$ 的总和。 - -### 思路 1:代码 - -```python -class Solution: - def knightDialer(self, n: int) -> int: - graph = { - 0: [4, 6], - 1: [6, 8], - 2: [7, 9], - 3: [4, 8], - 4: [0, 3, 9], - 5: [], - 6: [0, 1, 7], - 7: [2, 6], - 8: [1, 3], - 9: [2, 4] - } - - MOD = 10 ** 9 + 7 - dp = [[0 for _ in range(10)] for _ in range(n)] - for v in range(10): - dp[0][v] = 1 - - for i in range(1, n): - for u in range(10): - for v in graph[u]: - dp[i][v] = (dp[i][v] + dp[i - 1][u]) % MOD - - return sum(dp[n - 1]) % MOD -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 10)$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(n \times 10)$。 - diff --git "a/Solutions/0938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214.md" "b/Solutions/0938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214.md" deleted file mode 100644 index 6c4a5e37..00000000 --- "a/Solutions/0938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [0938. 二叉搜索树的范围和](https://leetcode.cn/problems/range-sum-of-bst/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉搜索树,和一个范围 [low, high]。求范围 [low, high] 之间所有节点的值的和。 - -## 解题思路 - -二叉搜索树的定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -这道题求解 [low, high] 之间所有节点的值的和,需要递归求解。 - -- 当前节点为 None 时返回 0; -- 当前节点值 val > high 时,则返回左子树之和; -- 当前节点值 val < low 时,则返回右子树之和; -- 当前节点 val <= high,且 val >= low 时,则返回当前节点值 + 左子树之和 + 右子树之和。 - -## 代码 - -```python -class Solution: - def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int: - if not root: - return 0 - if root.val > high: - return self.rangeSumBST(root.left, low, high) - if root.val < low: - return self.rangeSumBST(root.right, low, high) - return root.val + self.rangeSumBST(root.left, low, high) + self.rangeSumBST(root.right, low, high) -``` - diff --git "a/Solutions/0946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227.md" "b/Solutions/0946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227.md" deleted file mode 100644 index b089a4df..00000000 --- "a/Solutions/0946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [0946. 验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) - -- 标签:栈、数组、模拟 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个整数序列 `pushed` 和 `popped`,每个序列中的值都不重复。 - -**要求**:如果第一个序列为空栈的压入顺序,而第二个序列 `popped` 为该栈的压出序列,则返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le pushed.length \le 1000$。 -- $0 \le pushed[i] \le 1000$。 -- $pushed$ 的所有元素互不相同。 -- $popped.length == pushed.length$。 -- $popped$ 是 $pushed$ 的一个排列。 - -**示例**: - -- 示例 1: - -```python -输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1] -输出:true -解释:我们可以按以下顺序执行: -push(1), push(2), push(3), push(4), pop() -> 4, -push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1 -``` - -- 示例 2: - -```python -输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2] -输出:false -解释:1 不能在 2 之前弹出。 -``` - -## 解题思路 - -### 思路 1:栈 - -借助一个栈来模拟压入、压出的操作。检测最后是否能模拟成功。 - -### 思路 1:代码 - -```python -class Solution: - def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: - stack = [] - index = 0 - for item in pushed: - stack.append(item) - while (stack and stack[-1] == popped[index]): - stack.pop() - index += 1 - - return len(stack) == 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0947. \347\247\273\351\231\244\346\234\200\345\244\232\347\232\204\345\220\214\350\241\214\346\210\226\345\220\214\345\210\227\347\237\263\345\244\264.md" "b/Solutions/0947. \347\247\273\351\231\244\346\234\200\345\244\232\347\232\204\345\220\214\350\241\214\346\210\226\345\220\214\345\210\227\347\237\263\345\244\264.md" deleted file mode 100644 index c101339d..00000000 --- "a/Solutions/0947. \347\247\273\351\231\244\346\234\200\345\244\232\347\232\204\345\220\214\350\241\214\346\210\226\345\220\214\345\210\227\347\237\263\345\244\264.md" +++ /dev/null @@ -1,120 +0,0 @@ -# [0947. 移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) - -- 标签:深度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -**描述**:二维平面中有 `n` 块石头,每块石头都在整数坐标点上,且每个坐标点上最多只能有一块石头。如果一块石头的同行或者同列上有其他石头存在,那么就可以移除这块石头。 - -给你一个长度为 `n` 的数组 `stones` ,其中 `stones[i] = [xi, yi]` 表示第 `i` 块石头的位置。 - -**要求**:返回可以移除的石子的最大数量。 - -**说明**: - -- $1 \le stones.length \le 1000$。 -- $0 \le xi, yi \le 10^4$。 -- 不会有两块石头放在同一个坐标点上。 - -**示例**: - -- 示例 1: - -```python -输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]] -输出:5 -解释:一种移除 5 块石头的方法如下所示: -1. 移除石头 [2,2] ,因为它和 [2,1] 同行。 -2. 移除石头 [2,1] ,因为它和 [0,1] 同列。 -3. 移除石头 [1,2] ,因为它和 [1,0] 同行。 -4. 移除石头 [1,0] ,因为它和 [0,0] 同列。 -5. 移除石头 [0,1] ,因为它和 [0,0] 同行。 -石头 [0,0] 不能移除,因为它没有与另一块石头同行/列。 -``` - -- 示例 2: - -```python -输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]] -输出:3 -解释:一种移除 3 块石头的方法如下所示: -1. 移除石头 [2,2] ,因为它和 [2,0] 同行。 -2. 移除石头 [2,0] ,因为它和 [0,0] 同列。 -3. 移除石头 [0,2] ,因为它和 [0,0] 同行。 -石头 [0,0] 和 [1,1] 不能移除,因为它们没有与另一块石头同行/列。 -``` - -## 解题思路 - -### 思路 1:并查集 - -题目「求最多可以移走的石头数目」也可以换一种思路:「求最少留下的石头数目」。 - -- 如果两个石头 `A`、`B` 处于同一行或者同一列,我们就可以删除石头 `A` 或 `B`,最少留下 `1` 个石头。 -- 如果三个石头 `A`、`B`、`C`,其中 `A`、`B` 处于同一行,`B`、`C` 处于同一列,则我们可以先删除石头 `A`,再删除石头 `C`,最少留下 `1` 个石头。 -- 如果有 `n` 个石头,其中每个石头都有一个同行或者同列的石头,则我们可以将 `n - 1` 个石头都删除,最少留下 `1` 个石头。 - -通过上面的分析,我们可以利用并查集,将同行、同列的石头都加入到一个集合中。这样「最少可以留下的石头」就是并查集中集合的个数。 - -则答案为:**最多可以移走的石头数目 = 所有石头个数 - 最少可以留下的石头(并查集的集合个数)**。 - -因为石子坐标是二维的,在使用并查集的时候要区分横纵坐标,因为 $0 <= xi, yi <= 10^4$,可以取 $n = 10010$,将纵坐标映射到 $[n, n + 10000]$ 的范围内,这样就可以得到所有节点的标号。 - -最后计算集合个数,可以使用 set 集合去重,然后统计数量。 - -整体步骤如下: - -1. 定义一个 $10010 \times 2$ 大小的并查集。 -2. 遍历每块石头的横纵坐标: - 1. 将纵坐标映射到 $[10010, 10010 + 10000]$ 的范围内。 - 2. 然后将当前石头的横纵坐标相连接(加入到并查集中)。 -3. 建立一个 set 集合,查找每块石头横坐标所在集合对应的并查集编号,将编号加入到 set 集合中。 -4. 最后,返回「所有石头个数 - 并查集集合个数」即为答案。 - -### 思路 1:代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def removeStones(self, stones: List[List[int]]) -> int: - size = len(stones) - n = 10010 - union_find = UnionFind(n * 2) - for i in range(size): - union_find.union(stones[i][0], stones[i][1] + n) - - stones_set = set() - for i in range(size): - stones_set.add(union_find.find(stones[i][0])) - - return size - len(stones_set) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是石子个数。$\alpha$ 是反 `Ackerman` 函数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0953. \351\252\214\350\257\201\345\244\226\346\230\237\350\257\255\350\257\215\345\205\270.md" "b/Solutions/0953. \351\252\214\350\257\201\345\244\226\346\230\237\350\257\255\350\257\215\345\205\270.md" deleted file mode 100644 index 6cd0bb9e..00000000 --- "a/Solutions/0953. \351\252\214\350\257\201\345\244\226\346\230\237\350\257\255\350\257\215\345\205\270.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [0953. 验证外星语词典](https://leetcode.cn/problems/verifying-an-alien-dictionary/) - -- 标签:数组、哈希表、字符串 -- 难度:简单 - -## 题目大意 - -给定一组用外星语书写的单词字符串数组 `words`,以及表示外星字母表的顺序的字符串 `order` 。 - -要求:判断 `words` 中的单词是否都是按照 `order` 来排序的。如果是,则返回 `True`,否则返回 `False`。 - -## 解题思路 - -如果所有单词是按照 `order` 的规则升序排列,则所有单词都符合规则。而判断所有单词是升序排列,只需要两两比较相邻的单词即可。所以我们可以先用哈希表存储所有字母的顺序,然后对所有相邻单词进行两两比较,如果最终是升序排列,则符合要求。具体步骤如下: - -- 使用哈希表 `order_map` 存储字母的顺序。 -- 遍历单词数组 `words`,比较相邻单词 `word1` 和 `word2` 中所有字母在 `order_map` 中的下标,看是否满足 `word1 <= word2`。 -- 如果全部满足,则返回 `True`。如果有不满足的情况,则直接返回 `False`。 - -## 代码 - -```python -class Solution: - def isAlienSorted(self, words: List[str], order: str) -> bool: - order_map = dict() - for i in range(len(order)): - order_map[order[i]] = i - for i in range(len(words) - 1): - word1 = words[i] - word2 = words[i + 1] - - flag = True - - for j in range(min(len(word1), len(word2))): - if word1[j] != word2[j]: - if order_map[word1[j]] > order_map[word2[j]]: - return False - else: - flag = False - break - - if flag and len(word1) > len(word2): - return False - return True -``` - diff --git "a/Solutions/0958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" "b/Solutions/0958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" deleted file mode 100644 index 644892ec..00000000 --- "a/Solutions/0958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二叉树的根节点 `root`。 - -**要求**:判断该二叉树是否是一个完全二叉树。 - -**说明**: - -- **完全二叉树**: -- 树的结点数在范围 $[1, 100]$ 内。 -- $1 \le Node.val \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-1.png) - -```python -输入:root = [1,2,3,4,5,6] -输出:true -解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-2.png) - -```python -输入:root = [1,2,3,4,5,null,7] -输出:false -解释:值为 7 的结点没有尽可能靠向左侧。 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -对于一个完全二叉树,按照「层序遍历」的顺序进行广度优先搜索,在遇到第一个空节点之后,整个完全二叉树的遍历就已结束了。不应该在后续遍历过程中再次出现非空节点。 - -如果在遍历过程中在遇到第一个空节点之后,又出现了非空节点,则该二叉树不是完全二叉树。 - -利用这一点,我们可以在广度优先搜索的过程中,维护一个布尔变量 `is_empty` 用于标记是否遇见了空节点。 - -### 思路 1:代码 - -```python -class Solution: - def isCompleteTree(self, root: Optional[TreeNode]) -> bool: - if not root: - return False - - queue = collections.deque([root]) - is_empty = False - while queue: - size = len(queue) - for _ in range(size): - cur = queue.popleft() - if not cur: - is_empty = True - else: - if is_empty: - return False - queue.append(cur.left) - queue.append(cur.right) - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0959. \347\224\261\346\226\234\346\235\240\345\210\222\345\210\206\345\214\272\345\237\237.md" "b/Solutions/0959. \347\224\261\346\226\234\346\235\240\345\210\222\345\210\206\345\214\272\345\237\237.md" deleted file mode 100644 index 1c9a60cc..00000000 --- "a/Solutions/0959. \347\224\261\346\226\234\346\235\240\345\210\222\345\210\206\345\214\272\345\237\237.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [0959. 由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -在由 `1 * 1` 方格组成的 `n * n` 网格 `grid` 中,每个 `1 * 1` 方块由 `/`、`\` 或空格构成。这些字符会将方块划分为一些共边的区域。 - -注意:反斜杠字符是转义的,因此 `\` 用 `\\` 表示。 - -现在给定代表网格的二维数组 `grid`,要求:返回区域的数目。 - -## 解题思路 - -我们把一个 `1 * 1` 的单元格分割成逻辑上的 4 个部分,则 空格、`/`、`\\` 可以将 `1 * 1` 的方格分割为以下三种形态: - -![](http://qcdn.itcharge.cn/images/20210827142447.png) - -在进行遍历的时候,需要将联通的部分进行合并,并统计出联通的块数。这就需要用到了并查集。 - -遍历二维数组 `gird`,然后在「单元格内」和「单元格间」进行合并。 - -现在我们为单元格的每个小三角部分按顺时针方向都编上编号,起始位置为左边。然后单元格间的编号按照从左到右,从上到下的位置进行编号,如下图所示: - -![](http://qcdn.itcharge.cn/images/20210827143836.png) - -假设当前单元格的起始位置为 `index`,则合并策略如下: - -- 如果是单元格内: - - 如果是空格:合并 `index`、`index + 1`、`index + 2`、`index + 3`。 - - 如果是 `/`:合并 `index` 和 `index + 1`,合并 `index + 2` 和 `index + 3`。 - - 如果是 `\\`:合并 `index` 和 `index + 3`,合并 `index + 1` 和 `index + 2`。 -- 如果是单元格间,则向下向右进行合并: - - 向下:合并 `index + 3` 和 `index + 4 * size + 1 `。 - - 向右:合并 `index + 2` 和 `index + 4`。 - -最后合并完成之后,统计并查集中连通分量个数即为答案。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def regionsBySlashes(self, grid: List[str]) -> int: - size = len(grid) - m = 4 * size * size - union_find = UnionFind(m) - for i in range(size): - for j in range(size): - index = 4 * (i * size + j) - ch = grid[i][j] - if ch == '/': - union_find.union(index, index + 1) - union_find.union(index + 2, index + 3) - elif ch == '\\': - union_find.union(index, index + 3) - union_find.union(index + 1, index + 2) - else: - union_find.union(index, index + 1) - union_find.union(index + 1, index + 2) - union_find.union(index + 2, index + 3) - if j + 1 < size: - union_find.union(index + 2, index + 4) - if i + 1 < size: - union_find.union(index + 3, index + 4 * size + 1) - - return union_find.count -``` - diff --git "a/Solutions/0968. \347\233\221\346\216\247\344\272\214\345\217\211\346\240\221.md" "b/Solutions/0968. \347\233\221\346\216\247\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index cd0269f5..00000000 --- "a/Solutions/0968. \347\233\221\346\216\247\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [0968. 监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) - -- 标签:树、深度优先搜索、动态规划、二叉树 -- 难度:困难 - -## 题目大意 - -给定一个二叉树,需要在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父节点、自身及其直接子节点。 - -计算监控树的所有节点所需的最小摄像头数量。 - -- 示例 1: - - - -![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/bst_cameras_01.png) - -``` -输入:[0,0,null,0,0] -输出:1 -解释:如图所示,一台摄像头足以监控所有节点。 -``` - -- 示例 2: - -![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/bst_cameras_02.png) - -``` -输入:[0,0,null,0,null,0,null,null,0] -输出:2 -解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。 -``` - -## 解题思路 - -根据题意可知,一个摄像头的有效范围为 3 层:父节点、自身及其直接子节点。而约是下层的节点就越多,所以摄像头应该优先满足下层节点。可以使用后序遍历的方式遍历二叉树的节点,这样就可以优先遍历叶子节点。 - -对于每个节点,利用贪心思想,可以确定三种状态: - -- 第一种状态:该节点无覆盖 -- 第二种状态:该节点已经装上了摄像头 -- 第三种状态:该节点已经覆盖 - -为了让摄像头数量最少,我们要尽量让叶⼦节点的⽗节点安装摄像头,这样才能摄像头的数量最少。对此我们应当分析当前节点和左右两侧子节点的覆盖情况。 - -先来考虑空节点,空节点应该算作已经覆盖状态。 - -再来考虑左右两侧子覆盖情况: - -- 如果左节点或者右节点都无覆盖,则当前节点需要装上摄像头,答案 res 需要 + 1。 -- 如果左节点已经覆盖或者右节点已经装上了摄像头,则当前节点已经覆盖。 -- 如果左节点右节点都已经覆盖,则当前节点无覆盖。 - -根据以上条件就可以写出对应的后序遍历代码。 - -## 代码 - -```python -class Solution: - res = 0 - def traversal(self, cur: TreeNode) -> int: - if not cur: - return 3 - - left = self.traversal(cur.left) - right = self.traversal(cur.right) - - if left == 1 or right == 1: - self.res += 1 - return 2 - - if left == 2 or right == 2: - return 3 - - if left == 3 and right == 3: - return 1 - return -1 - - def minCameraCover(self, root: TreeNode) -> int: - self.res = 0 - if self.traversal(root) == 1: - self.res += 1 - return self.res -``` - diff --git "a/Solutions/0973. \346\234\200\346\216\245\350\277\221\345\216\237\347\202\271\347\232\204 K \344\270\252\347\202\271.md" "b/Solutions/0973. \346\234\200\346\216\245\350\277\221\345\216\237\347\202\271\347\232\204 K \344\270\252\347\202\271.md" deleted file mode 100644 index b6363d2b..00000000 --- "a/Solutions/0973. \346\234\200\346\216\245\350\277\221\345\216\237\347\202\271\347\232\204 K \344\270\252\347\202\271.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [0973. 最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) - -- 标签:几何、数组、数学、分治、快速选择、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个由由平面上的点组成的列表 `points`,再给定一个整数 `K`。 - -要求:从中找出 `K` 个距离原点` (0, 0)` 最近的点。(这里,平面上两点之间的距离是欧几里德距离。)可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。 - -## 解题思路 - -1. 使用二叉堆构建优先队列,优先级为距离原点的距离。此时堆顶元素即为距离原点最近的元素。 -2. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(log{n})$。 - - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 -3. 不断重复第 2 步,直到 `K` 次结束。 - -## 代码 - -```python -class Heapq: - def compare(self, a, b): - dist_a = a[0] * a[0] + a[1] * a[1] - dist_b = b[0] * b[0] + b[1] * b[1] - if dist_a < dist_b: - return -1 - elif dist_a == dist_b: - return 0 - else: - return 1 - # 堆调整方法:调整为小顶堆 - def heapAdjust(self, nums: [int], index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子结点 - max_index = index - if self.compare(nums[left], nums[max_index]) == -1: - max_index = left - if right <= end and self.compare(nums[right], nums[max_index]) == -1: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 将数组构建为二叉堆 - def heapify(self, nums: [int]): - size = len(nums) - # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - # 调用调整堆函数 - self.heapAdjust(nums, i, size - 1) - - # 入队操作 - def heappush(self, nums: list, value): - nums.append(value) - size = len(nums) - i = size - 1 - # 寻找插入位置 - while (i - 1) // 2 >= 0: - cur_root = (i - 1) // 2 - # value 大于当前根节点,则插入到当前位置 - if self.compare(nums[cur_root], value) == -1: - break - # 继续向上查找 - nums[i] = nums[cur_root] - i = cur_root - # 找到插入位置或者到达根位置,将其插入 - nums[i] = value - - # 出队操作 - def heappop(self, nums: list) -> int: - size = len(nums) - nums[0], nums[-1] = nums[-1], nums[0] - # 得到最小值(堆顶元素)然后调整堆 - top = nums.pop() - if size > 0: - self.heapAdjust(nums, 0, size - 2) - - return top - - # 升序堆排序 - def heapSort(self, nums: [int]): - self.heapify(nums) - size = len(nums) - for i in range(size): - nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] - self.heapAdjust(nums, 0, size - i - 2) - return nums - -class Solution: - def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]: - heap = Heapq() - queue = [] - for point in points: - heap.heappush(queue, point) - - res = [] - for i in range(k): - res.append(heap.heappop(queue)) - - return res -``` - diff --git "a/Solutions/0974. \345\222\214\345\217\257\350\242\253 K \346\225\264\351\231\244\347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0974. \345\222\214\345\217\257\350\242\253 K \346\225\264\351\231\244\347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 37176f61..00000000 --- "a/Solutions/0974. \345\222\214\345\217\257\350\242\253 K \346\225\264\351\231\244\347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [974. 和可被 K 整除的子数组](https://leetcode.cn/problems/subarray-sums-divisible-by-k/) - -- 标签:数组、哈希表、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums` 和一个整数 `k`。 - -要求:返回其中元素之和可被 `k` 整除的(连续、非空)子数组的数目。 - -## 解题思路 - -先考虑暴力计算子数组和,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: - -```python -for i in range(len(nums)): - for j in range(i + 1): - sum = countSum(i, j) -``` - -这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 - -先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 - -由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 - -`pre_sum[i]` 的定义是前 `i` 个元素和,则 `[j..i]` 子数组和可以被 `k` 整除可以转换为:`(pre_sum[i] - pre_sum[j - 1])% k == 0`。再转换一下:`pre_sum[i] % k == pre_sum[j - 1] % k`。 - -所以,我们只需要统计满足 `pre_sum[i] % k == pre_sum[j - 1] % k` 条件的组合个数。具体做法如下: - -使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。使用哈希表 `pre_dic` 记录 `pre_sum[i] % k` 出现的次数。键值对为 `pre_sum[i] : count`。 - -- 从左到右遍历数组,计算当前前缀和并对 `k` 取余,即 `pre_sum = (pre_sum + nums[i]) % k`。 - - 如果 `pre_sum` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum]`。同时 `pre_sum` 个数累加 1,即 `pre_dic[pre_sum] += 1`。 - - 如果 `pre_sum` 不在哈希表中,则 `pre_sum` 个数记为 1,即 `pre_dic[pre_sum] += 1`。 -- 最后输出答案个数。 - -## 代码 - -```python -class Solution: - def subarraysDivByK(self, nums: List[int], k: int) -> int: - pre_sum = 0 - ans = 0 - nums_dict = {0: 1} - for i in range(len(nums)): - pre_sum = (pre_sum + nums[i]) % k - if pre_sum < 0: - pre_sum += k - if pre_sum in nums_dict: - ans += nums_dict[pre_sum] - nums_dict[pre_sum] += 1 - else: - nums_dict[pre_sum] = 1 - return ans -``` - diff --git "a/Solutions/0976. \344\270\211\350\247\222\345\275\242\347\232\204\346\234\200\345\244\247\345\221\250\351\225\277.md" "b/Solutions/0976. \344\270\211\350\247\222\345\275\242\347\232\204\346\234\200\345\244\247\345\221\250\351\225\277.md" deleted file mode 100644 index aff797be..00000000 --- "a/Solutions/0976. \344\270\211\350\247\222\345\275\242\347\232\204\346\234\200\345\244\247\345\221\250\351\225\277.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [0976. 三角形的最大周长](https://leetcode.cn/problems/largest-perimeter-triangle/) - -- 标签:贪心、数组、数学、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一些由正数(代表长度)组成的数组 `nums`。 - -**要求**:返回由其中 `3` 个长度组成的、面积不为 `0` 的三角形的最大周长。如果不能形成任何面积不为 `0` 的三角形,则返回 `0`。 - -**说明**: - -- $3 \le nums.length \le 10^4$。 -- $1 \le nums[i] \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,1,2] -输出:5 -解释:长度为 2, 1, 2 的边组成的三角形周长为 5,为最大周长 -``` - -## 解题思路 - -### 思路 1: - -要想三角形的周长最大,则每一条边都要尽可能的长,并且还要满足三角形的边长条件,即 `a + b > c`,其中 `a`、`b`、`c` 分别是三角形的 `3` 条边长。 - -所以,我们可以先对所有边长进行排序。然后倒序枚举最长边 `nums[i]`,判断前两个边长相加是否大于最长边,即 `nums[i - 2] + nums[i - 1] > nums[i]`。如果满足,则返回 `3` 条边长的和,否则的话继续枚举最长边。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def largestPerimeter(self, nums: List[int]) -> int: - nums.sort() - for i in range(len(nums) - 1, 1, -1): - if nums[i - 2] + nums[i - 1] > nums[i]: - return nums[i - 2] + nums[i - 1] + nums[i] - return 0 -``` - diff --git "a/Solutions/0977. \346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.md" "b/Solutions/0977. \346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.md" deleted file mode 100644 index ede1cb29..00000000 --- "a/Solutions/0977. \346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.md" +++ /dev/null @@ -1,102 +0,0 @@ -# [0977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) - -- 标签:数组、双指针、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给你一个按「非递减顺序」排序的整数数组 `nums`。 - -**要求**:返回「每个数字的平方」组成的新数组,要求也按「非递减顺序」排序。 - -## 解题思路 - -### 思路 1:双指针 - -原数组是按「非递减顺序」排序的,可能会存在负数元素。但是无论是否存在负数,数字的平方最大值一定在原数组的两端。题目要求返回的新数组也要按照「非递减顺序」排序。那么,我们可以利用双指针,从两端向中间移动,然后不断将数的平方最大值填入数组。具体做法如下: - -- 使用两个指针 `left`、`right`。`left` 指向数组第一个元素位置,`right` 指向数组最后一个元素位置。再定义 `index = len(nums) - 1` 作为答案数组填入顺序的索引值。`res` 作为答案数组。 - -- 比较 `nums[left]` 与 `nums[right]` 的绝对值大小。大的就是平方最大的的那个数。 - - - 如果 `abs(nums[right])` 更大,则将其填入答案数组对应位置,并令 `right -= 1`。 - - - 如果 `abs(nums[left])` 更大,则将其填入答案数组对应位置,并令 `left += 1`。 - - - 令 `index -= 1`。 - -- 直到 `left == right`,最后将 `nums[left]` 填入答案数组对应位置。 - -返回答案数组 `res`。 - -## 思路 2:排序算法 - -可以通过各种排序算法来对平方后的数组进行排序。以快速排序为例,具体步骤如下: - -1. 遍历数组,将数组中各个元素变为平方项。 -2. 从数组中找到一个基准数。 -3. 然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧,从而把数组拆分为左右两个部分。 -4. 再对左右两个部分分别重复第 2、3 步,直到各个部分只有一个数,则排序结束。 - -## 代码 - -- 双指针: - -```python -class Solution: - def sortedSquares(self, nums: List[int]) -> List[int]: - size = len(nums) - left, right = 0, size - 1 - index = size - 1 - res = [0 for _ in range(size)] - - while left < right: - if abs(nums[left]) < abs(nums[right]): - res[index] = nums[right] * nums[right] - right -= 1 - else: - res[index] = nums[left] * nums[left] - left += 1 - index -= 1 - res[index] = nums[left] * nums[left] - - return res -``` - -- 排序算法 - -```python -import random - -class Solution: - def randomPartition(self, arr: [int], low: int, high: int): - i = random.randint(low, high) - arr[i], arr[high] = arr[high], arr[i] - return self.partition(arr, low, high) - - def partition(self, arr: [int], low: int, high: int): - i = low - 1 - pivot = arr[high] - - for j in range(low, high): - if arr[j] <= pivot: - i += 1 - arr[i], arr[j] = arr[j], arr[i] - arr[i + 1], arr[high] = arr[high], arr[i + 1] - return i + 1 - - def quickSort(self, arr, low, high): - if low < high: - pi = self.randomPartition(arr, low, high) - self.quickSort(arr, low, pi - 1) - self.quickSort(arr, pi + 1, high) - - return arr - - def sortedSquares(self, nums: List[int]) -> List[int]: - for i in range(len(nums)): - nums[i] = nums[i] * nums[i] - - return self.quickSort(nums, 0, len(nums) - 1) -``` - diff --git "a/Solutions/0978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 6a5a55b1..00000000 --- "a/Solutions/0978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [0978. 最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) - -- 标签:数组、动态规划、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个数组 `arr`。当 `arr` 的子数组 `arr[i]`,`arr[i + 1]`,`...`, `arr[j]` 满足下列条件时,我们称其为湍流子数组: - -- 若 `i <= k < j`,当 `k` 为奇数时, `arr[k] > arr[k + 1]`,且当 `k` 为偶数时,`arr[k] < arr[k + 1]`; -- 或若 `i <= k < j`,当 `k` 为偶数时,`arr[k] > arr[k + 1]` ,且当 `k` 为奇数时,`arr[k] < arr[k + 1]`。 -- 也就是说,如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组。 - -要求:返回给定数组 `arr` 的最大湍流子数组的长度。 - -## 解题思路 - -湍流子数组实际上像波浪一样,比如 `arr[i - 2] > arr[i - 1] < arr[i] > arr[i + 1] < arr[i + 2]`。所以我们可以使用双指针的做法。具体做法如下: - -- 使用两个指针 `left`、`right`。`left` 指向湍流子数组的左端,`right` 指向湍流子数组的右端。 -- 如果 `arr[right - 1] == arr[right]`,则更新 `left = right`,重新开始计算最长湍流子数组大小。 -- 如果 `arr[right - 2] < arr[right - 1] < arr[right]`,此时为递增数组,则 `left` 从 `right - 1` 开始重新计算最长湍流子数组大小。 -- 如果 `arr[right - 2] > arr[right - 1] > arr[right]`,此时为递减数组,则 `left` 从 `right - 1` 开始重新计算最长湍流子数组大小。 -- 其他情况(即 `arr[right - 2] < arr[right - 1] > arr[right]` 或 `arr[right - 2] > arr[right - 1] < arr[right]`)时,不用更新 `left`值。 -- 更新最大湍流子数组的长度,并向右移动 `right`。直到 `right >= len(arr)` 时,返回答案 `ans`。 - -## 代码 - -```python -class Solution: - def maxTurbulenceSize(self, arr: List[int]) -> int: - left, right = 0, 1 - ans = 1 - - while right < len(arr): - if arr[right - 1] == arr[right]: - left = right - elif right != 1 and arr[right - 2] < arr[right - 1] and arr[right - 1] < arr[right]: - left = right - 1 - elif right != 1 and arr[right - 2] > arr[right - 1] and arr[right - 1] > arr[right]: - left = right - 1 - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - diff --git "a/Solutions/0982. \346\214\211\344\275\215\344\270\216\344\270\272\351\233\266\347\232\204\344\270\211\345\205\203\347\273\204.md" "b/Solutions/0982. \346\214\211\344\275\215\344\270\216\344\270\272\351\233\266\347\232\204\344\270\211\345\205\203\347\273\204.md" deleted file mode 100644 index a3c4caa3..00000000 --- "a/Solutions/0982. \346\214\211\344\275\215\344\270\216\344\270\272\351\233\266\347\232\204\344\270\211\345\205\203\347\273\204.md" +++ /dev/null @@ -1,153 +0,0 @@ -# [0982. 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) - -- 标签:位运算、数组、哈希表 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:返回其中「按位与三元组」的数目。 - -**说明**: - -- **按位与三元组**:由下标 $(i, j, k)$ 组成的三元组,并满足下述全部条件: - - $0 \le i < nums.length$。 - - $0 \le j < nums.length$。 - - $0 \le k < nums.length$。 - - $nums[i] \text{ \& } nums[j] \text{ \& } nums[k] == 0$ ,其中 $\text{ \& }$ 表示按位与运算符。 - -- $1 \le nums.length \le 1000$。 -- $0 \le nums[i] < 2^{16}$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,1,3] -输出:12 -解释:可以选出如下 i, j, k 三元组: -(i=0, j=0, k=1) : 2 & 2 & 1 -(i=0, j=1, k=0) : 2 & 1 & 2 -(i=0, j=1, k=1) : 2 & 1 & 1 -(i=0, j=1, k=2) : 2 & 1 & 3 -(i=0, j=2, k=1) : 2 & 3 & 1 -(i=1, j=0, k=0) : 1 & 2 & 2 -(i=1, j=0, k=1) : 1 & 2 & 1 -(i=1, j=0, k=2) : 1 & 2 & 3 -(i=1, j=1, k=0) : 1 & 1 & 2 -(i=1, j=2, k=0) : 1 & 3 & 2 -(i=2, j=0, k=1) : 3 & 2 & 1 -(i=2, j=1, k=0) : 3 & 1 & 2 -``` - -- 示例 2: - -```python -输入:nums = [0,0,0] -输出:27 -``` - -## 解题思路 - -### 思路 1:枚举 - -最直接的方法是使用三重循环直接枚举 $(i, j, k)$,然后再判断 $nums[i] \text{ \& } nums[j] \text{ \& } nums[k]$ 是否为 $0$。但是这样做的时间复杂度为 $O(n^3)$。 - -从题目中可以看出 $nums[i]$ 的值域范围为 $[0, 2^{16}]$,而 $2^{16} = 65536$。所以我们可以按照下面步骤优化时间复杂度: - -1. 先使用两重循环枚举 $(i, j)$,计算出 $nums[i] \text{ \& } nums[j]$ 的值,将其存入一个大小为 $2^{16}$ 的数组或者哈希表 $cnts$ 中,并记录每个 $nums[i] \text{ \& } nums[j]$ 值出现的次数。 -2. 然后遍历该数组或哈希表,再使用一重循环遍历 $k$,找出所有满足 $nums[k] \text{ \& } x == 0$ 的 $x$,并将其对应数量 $cnts[x]$ 累积到答案 $ans$ 中。 -3. 最后返回答案 $ans$ 即可。 - -### 思路 1:代码 - -```python -class Solution: - def countTriplets(self, nums: List[int]) -> int: - states = 1 << 16 - cnts = [0 for _ in range(states)] - - for num_x in nums: - for num_y in nums: - cnts[num_x & num_y] += 1 - - ans = 0 - for num in nums: - for x in range(states): - if num & x == 0: - ans += cnts[x] - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 + 2^{16} \times n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(2^{16})$。 - -### 思路 2:枚举 + 优化 - -第一步跟思路 1 一样,我们先使用两重循环枚举 $(i, j)$,计算出 $nums[i] \text{ \& } nums[j]$ 的值,将其存入一个大小为 $2^{16}$ 的数组或者哈希表 $cnts$ 中,并记录每个 $nums[i] \text{ \& } nums[j]$ 值出现的次数。 - -接下来我们对思路 1 中的第二步进行优化,在思路 1 中,我们是通过枚举数组或哈希表的方式得到 $x$ 的,这里我们换一种方法。 - -使用一重循环遍历 $k$,对于 $nums[k]$,我们先计算出 $nums[k]$ 的补集,即将 $nums[k]$ 与 $2^{16} - 1$(二进制中 $16$ 个 $1$)进行按位异或操作,得到 $nums[k]$ 的补集 $com$。如果 $nums[k] \text{ \& } x == 0$,则 $x$ 一定是 $com$ 的子集。 - -换句话说,$x$ 中 $1$ 的位置一定与 $nums[k]$ 中 $1$ 的位置不同,如果 $nums[k]$ 中第 $m$ 位为 $1$,则 $x$ 中第 $m$ 位一定为 $0$。 - -接下来我们通过下面的方式来枚举子集: - -1. 定义子集为 $sub$,初始时赋值为 $com$,即:$sub = com$。 -2. 令 $sub$ 减 $1$,然后与 $com$ 做按位与操作,得到下一个子集,即:$sub = (sub - 1) \text{ \& } com$。 -3. 不断重复第 $2$ 步,直到 $sub$ 为空集时为止。 - -这种方法能枚举子集的原理是:$sub$ 减 $1$ 会将最低位的 $1$ 改为 $0$,而比这个 $1$ 更低位的 $0$ 都改为了 $1$。此时再与 $com$ 做按位与操作,就会过保留原本高位上的 $1$,滤掉当前最低位的 $1$,并且保留比这个 $1$ 更低位上的原有的 $1$,也就得到嘞下一个子集。 - -举个例子,比如补集 $com$ 为 $(00010110)_2$: - -1. 初始 $sub = (00010110)_2$。 -2. 令其减 $1$ 后为 $(00010101)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010100)_2$,即:$(00010101)_2 \text{ \& } (00010110)_2$)。 -3. 令其减 $1$ 后为 $(00010011)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010010)_2$,即: $(00010011)_2 \text{ \& } (00010110)_2$。 -4. 令其减 $1$ 后为 $(00010001)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010000)_2$,即:$(00010001)_2 \text{ \& } (00010110)_2$。 -5. 令其减 $1$ 后为 $(00001111)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000110)_2$,即:$(00001111)_2 \text{ \& } (00010110)_2$。 -6. 令其减 $1$ 后为 $(00000101)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000100)_2$,即:$(00000101)_2 \text{ \& } (00010110)_2$。 -7. 令其减 $1$ 后为 $(00000011)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000010)_2$,即:$(00000011)_2 \text{ \& } (00010110)_2$。 -8. 令其减 $1$ 后为 $(00000001)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000000)_2$,即:$(00000001)_2 \text{ \& } (00010110)_2$。 -9. $sub$ 变为了空集。 - -### 思路 2:代码 - -```python -class Solution: - def countTriplets(self, nums: List[int]) -> int: - states = 1 << 16 - cnts = [0 for _ in range(states)] - - for num_x in nums: - for num_y in nums: - cnts[num_x & num_y] += 1 - - ans = 0 - for num in nums: - com = num ^ 0xffff # com: num 的补集 - sub = com # sub: 子集 - while True: - ans += cnts[sub] - if sub == 0: - break - sub = (sub - 1) & com - - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2 + 2^{16} \times n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(2^{16})$。 - -## 参考资料 - -- 【题解】[按位与为零的三元组 - 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/solution/an-wei-yu-wei-ling-de-san-yuan-zu-by-lee-gjud/) -- 【题解】[有技巧的枚举 + 常数优化(Python/Java/C++/Go) - 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/solution/you-ji-qiao-de-mei-ju-chang-shu-you-hua-daxit/) diff --git "a/Solutions/0990. \347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.md" "b/Solutions/0990. \347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.md" deleted file mode 100644 index 71aeff51..00000000 --- "a/Solutions/0990. \347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [0990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) - -- 标签:并查集、图、数组、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由字符串方程组成的数组 `equations`,每个字符串方程 `equations[i]` 的长度为 `4`,有以下两种形式组成:`a==b` 或 `a!=b`。`a` 和 `b` 是小写字母,表示单字母变量名。 - -**要求**:判断所有的字符串方程是否能同时满足,如果能同时满足,返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le equations.length \le 500$。 -- $equations[i].length == 4$。 -- $equations[i][0]$ 和 $equations[i][3]$ 是小写字母。 -- $equations[i][1]$ 要么是 `'='`,要么是 `'!'`。 -- `equations[i][2]` 是 `'='`。 - -**示例**: - -- 示例 1: - -```python -输入:["a==b","b!=a"] -输出:False -解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。 -``` - -## 解题思路 - -### 思路 1:并查集 - -字符串方程只有 `==` 或者 `!=`,可以考虑将相等的遍历划分到相同集合中,然后再遍历所有不等式方程,看方程的两个变量是否在之前划分的相同集合中,如果在则说明不满足。 - -这就需要用到并查集,具体操作如下: - -- 遍历所有等式方程,将等式两边的单字母变量顶点进行合并。 -- 遍历所有不等式方程,检查不等式两边的单字母遍历是不是在一个连通分量中,如果在则返回 `False`,否则继续扫描。如果所有不等式检查都没有矛盾,则返回 `True`。 - -### 思路 1:并查集代码 - -```python -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - - def __find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.__find(x) - root_y = self.__find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.__find(x) == self.__find(y) - -class Solution: - def equationsPossible(self, equations: List[str]) -> bool: - union_find = UnionFind(26) - for eqation in equations: - if eqation[1] == "=": - index1 = ord(eqation[0]) - 97 - index2 = ord(eqation[3]) - 97 - union_find.union(index1, index2) - - for eqation in equations: - if eqation[1] == "!": - index1 = ord(eqation[0]) - 97 - index2 = ord(eqation[3]) - 97 - if union_find.is_connected(index1, index2): - return False - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + C \times \log C)$。其中 $n$ 是方程组 $equations$ 中的等式数量。$C$ 是字母变量的数量。本题中变量都是小写字母,即 $C \le 26$。 -- **空间复杂度**:$O(C)$。 \ No newline at end of file diff --git "a/Solutions/0992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 6d187b3e..00000000 --- "a/Solutions/0992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,60 +0,0 @@ -# [0992. K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) - -- 标签:数组、哈希表、计数、滑动窗口 -- 难度:困难 - -## 题目大意 - -给定一个正整数数组 `nums`,再给定一个整数 `k`。如果 `nums` 的某个子数组中不同整数的个数恰好为 `k`,则称 `nums` 的这个连续、不一定不同的子数组为「好子数组」。 - -- 例如,`[1, 2, 3, 1, 2]` 中有 3 个不同的整数:`1`,`2` 以及 `3`。 - -要求:返回 `nums` 中好子数组的数目。 - -## 解题思路 - -这道题转换一下思路会更简单。 - -恰好包含 `k` 个不同整数的连续子数组数量 = 包含小于等于 `k` 个不同整数的连续子数组数量 - 包含小于等于 `k - 1` 个不同整数的连续子数组数量 - -可以专门写一个方法计算包含小于等于 `k` 个不同整数的连续子数组数量。 - -计算包含小于等于 `k` 个不同整数的连续子数组数量的方法具体步骤如下: - -用滑动窗口 `windows` 来记录不同的整数个数,`windows` 为哈希表类型。 - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内不超过 `k` 个不同整数。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧整数 `nums[right]` 加入当前窗口 `windows` 中,记录该整数个数。 -- 如果该窗口中该整数的个数多于 `k` 个,即 `len(windows) > k`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应整数的个数,直到 `len(windows) <= k`。 -- 维护更新包含小于等于 `k` 个不同整数的连续子数组数量。每次累加数量为 `right - left + 1`,表示以 `nums[right]` 为结尾的小于等于 `k` 个不同整数的连续子数组数量。 -- 然后右移 `right`,直到 `right >= len(nums)` 结束。 -- 返回包含小于等于 `k` 个不同整数的连续子数组数量。 - -## 代码 - -```python -class Solution: - def subarraysMostKDistinct(self, nums, k): - windows = dict() - left, right = 0, 0 - ans = 0 - while right < len(nums): - if nums[right] in windows: - windows[nums[right]] += 1 - else: - windows[nums[right]] = 1 - while len(windows) > k: - windows[nums[left]] -= 1 - if windows[nums[left]] == 0: - del windows[nums[left]] - left += 1 - ans += right - left + 1 - right += 1 - return ans - - def subarraysWithKDistinct(self, nums: List[int], k: int) -> int: - return self.subarraysMostKDistinct(nums, k) - self.subarraysMostKDistinct(nums, k - 1) -``` - diff --git "a/Solutions/0993. \344\272\214\345\217\211\346\240\221\347\232\204\345\240\202\345\205\204\345\274\237\350\212\202\347\202\271.md" "b/Solutions/0993. \344\272\214\345\217\211\346\240\221\347\232\204\345\240\202\345\205\204\345\274\237\350\212\202\347\202\271.md" deleted file mode 100644 index 8d9e7bc6..00000000 --- "a/Solutions/0993. \344\272\214\345\217\211\346\240\221\347\232\204\345\240\202\345\205\204\345\274\237\350\212\202\347\202\271.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [0993. 二叉树的堂兄弟节点](https://leetcode.cn/problems/cousins-in-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树,和两个值 x,y。从二叉树中找出 x 和 y 对应的节点 node_x,node_y。如果两个节点是堂兄弟节点,则返回 True,否则返回 False。 - -- 堂兄弟节点:两个节点的深度相同,父节点不同。 - -## 解题思路 - -广度优先搜索或者深度优先搜索都可。以深度优先搜索为例,递归遍历查找节点值为 x,y 的两个节点。在递归的同时,需要传入递归函数当前节点的深度和父节点信息。若找到对应的节点,则保存两节点对应深度和父节点信息。最后判断两个节点是否是深度相同,父节点不同。如果是,则返回 True,不是则返回 False。 - -## 代码 - -```python -class Solution: - def isCousins(self, root: TreeNode, x: int, y: int) -> bool: - depths = [0, 0] - parents = [None, None] - - def dfs(node, depth, parent): - if not node: - return - if node.val == x: - depths[0] = depth - parents[0] = parent - elif node.val == y: - depths[1] = depth - parents[1] = parent - dfs(node.left, depth+1, node) - dfs(node.right, depth+1, node) - - dfs(root, 0, None) - return depths[0] == depths[1] and parents[0] != parents[1] -``` - diff --git "a/Solutions/0995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260.md" "b/Solutions/0995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260.md" deleted file mode 100644 index f14dde3f..00000000 --- "a/Solutions/0995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260.md" +++ /dev/null @@ -1,60 +0,0 @@ -# [0995. K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) - -- 标签:位运算、队列、数组、前缀和、滑动窗口 -- 难度:困难 - -## 题目大意 - -给定一个仅包含 `0` 和 `1` 的数组 `nums`,再给定一个整数 `k`。进行一次 `k` 位翻转包括选择一个长度为 `k` 的(连续)子数组,同时将子数组中的每个 `0` 更改为 `1`,而每个 `1` 更改为 `0`。 - -要求:返回所需的 `k` 位翻转的最小次数,以便数组没有值为 `0` 的元素。如果不可能,返回 `-1`。 - -## 解题思路 - -每次需要翻转的起始位置肯定是遇到第一个元素为 `0` 的位置开始反转,如果能够使得整个数组不存在 `0`,即返回 `ans` 作为反转次数。 - -同时我们还可以发现: - -- 如果某个元素反转次数为奇数次,元素会由 `0 -> 1`,`1 -> 0`。 -- 如果某个元素反转次数为偶数次,元素不会发生变化。 - -每个第 `i` 位置上的元素只会被前面 `[i - k + 1, i - 1]` 的元素影响。所以我们只需要知道前面 `k - 1` 个元素翻转次数的奇偶性就可以了。 - -同时如果我们知道了前面 `k - 1` 个元素的翻转次数就可以直接修改 `nums[i]` 了。 - -我们使用 `flip_count` 记录第 `i` 个元素之前 `k - 1` 个位置总共被反转了多少次,或者 `flip_count` 是大小为 `k - 1` 的滑动窗口。 - -- 如果前面第 `k - 1` 个元素翻转了奇数次,则如果 `nums[i] == 1`,则 `nums[i]` 也被翻转成了 `0`,需要再翻转 `1` 次。 -- 如果前面第 `k - 1` 个元素翻转了偶数次,则如果 `nums[i] == 0`,则 `nums[i]` 也被翻转成为了 `0`,需要再翻转 `1` 次。 - -这两句写成判断语句可以写为:`if (flip_count + nums[i]) % 2 == 0:`。 - -因为 `0 <= nums[i] <= 1`,所以我们可以用 `0` 和 `1` 以外的数,比如 `2` 来标记第 `i` 个元素发生了翻转,即 `nums[i] = 2`。这样在遍历到第 `i` 个元素时,如果有 `nums[i - k] == 2`,则说明 `nums[i - k]` 发生了翻转。同时根据 `flip_count` 和 `nums[i]` 来判断第 `i` 位是否需要进行翻转。 - -整个算法的具体步骤如下: - -- 使用 `res` 记录最小翻转次数。使用 `flip_count` 记录窗口内前 `k - 1 ` 位元素的翻转次数。 -- 遍历数组 `nums`,对于第 `i` 位元素: - - 如果 `i - k >= 0`,并且 `nums[i - k] == 2`,需要缩小窗口,将翻转次数减一。(此时窗口范围为 `[i - k + 1, i - 1]`)。 - - 如果 `(flip_count + nums[i]) % 2 == 0`,则说明 `nums[i]` 还需要再翻转一次,将 `nums[i]` 标记为 `2`,同时更新窗口内翻转次数 `flip_count` 和答案最小翻转次数 `ans`。 -- 遍历完之后,返回 `res`。 - -## 代码 - -```python -class Solution: - def minKBitFlips(self, nums: List[int], k: int) -> int: - ans = 0 - flip_count = 0 - for i in range(len(nums)): - if i - k >= 0 and nums[i - k] == 2: - flip_count -= 1 - if (flip_count + nums[i]) % 2 == 0: - if i + k > len(nums): - return -1 - nums[i] = 2 - flip_count += 1 - ans += 1 - return ans -``` - diff --git "a/Solutions/1000. \345\220\210\345\271\266\347\237\263\345\244\264\347\232\204\346\234\200\344\275\216\346\210\220\346\234\254.md" "b/Solutions/1000. \345\220\210\345\271\266\347\237\263\345\244\264\347\232\204\346\234\200\344\275\216\346\210\220\346\234\254.md" deleted file mode 100644 index 192acf44..00000000 --- "a/Solutions/1000. \345\220\210\345\271\266\347\237\263\345\244\264\347\232\204\346\234\200\344\275\216\346\210\220\346\234\254.md" +++ /dev/null @@ -1,196 +0,0 @@ -# [1000. 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) - -- 标签:数组、动态规划、前缀和 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个代表 $n$ 堆石头的整数数组 $stones$,其中 $stones[i]$ 代表第 $i$ 堆中的石头个数。再给定一个整数 $k$, 每次移动需要将连续的 $k$ 堆石头合并为一堆,而这次移动的成本为这 $k$ 堆中石头的总数。 - -**要求**:返回把所有石头合并成一堆的最低成本。如果无法合并成一堆,则返回 $-1$。 - -**说明**: - -- $n == stones.length$。 -- $1 \le n \le 30$。 -- $1 \le stones[i] \le 100$。 -- $2 \le k \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入:stones = [3,2,4,1], K = 2 -输出:20 -解释: -从 [3, 2, 4, 1] 开始。 -合并 [3, 2],成本为 5,剩下 [5, 4, 1]。 -合并 [4, 1],成本为 5,剩下 [5, 5]。 -合并 [5, 5],成本为 10,剩下 [10]。 -总成本 20,这是可能的最小值。 -``` - -- 示例 2: - -```python -输入:stones = [3,5,1,2,6], K = 3 -输出:25 -解释: -从 [3, 5, 1, 2, 6] 开始。 -合并 [5, 1, 2],成本为 8,剩下 [3, 8, 6]。 -合并 [3, 8, 6],成本为 17,剩下 [17]。 -总成本 25,这是可能的最小值。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 前缀和 - -每次将 $k$ 堆连续的石头合并成 $1$ 堆,石头堆数就会减少 $k - 1$ 堆。总共有 $n$ 堆石子,则: - -1. 当 $(n - 1) \mod (k - 1) == 0$ 时,一定可以经过 $\frac{n - 1}{k - 1}$ 次合并,将 $n$ 堆石头合并为 $1$ 堆。 -2. 当 $(n - 1) \mod (k - 1) \ne 0$ 时,则无法将所有的石头合并成一堆。 - -根据以上情况,我们可以先将无法将所有的石头合并成一堆的情况排除出去,接下来只考虑合法情况。 - -由于每次合并石头的成本为合并的 $k$ 堆的石子总数,即数组 $stones$ 中长度为 $k$ 的连续子数组和,因此为了快速计算数组 $stones$ 的连续子数组和,我们可以使用「前缀和」的方式,预先计算出「前 $i$ 堆的石子总数」,从而可以在 $O(1)$ 的时间复杂度内得到数组 $stones$ 的连续子数组和。 - -$k$ 堆石头合并为 $1$ 堆石头的过程,可以看做是长度为 $k$ 的连续子数组合并为长度为 $1$ 的子数组的过程,也可以看做是将长度为 $k$ 的区间合并为长度为 $1$ 的区间。 - -接下来我们就可以按照「区间 DP 问题」的基本思路来做。 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 - -###### 3. 状态转移方程 - -我们将区间 $[i, j]$ 的石堆合并成 $m$ 堆,可以枚举 $i \le n \le j$,将区间 $[i, j]$ 拆分为两个区间 $[i, n]$ 和 $[n + 1, j]$。然后将 $[i, n]$ 中的石头合并为 $1$ 堆,将 $[n + 1, j]$ 中的石头合并成 $m - 1$ 堆。最后将 $1$ 堆石头和 $m - 1$ 堆石头合并成 $1$ 堆,这样就可以将 $[i, j]$ 的石堆合并成 $k$ 堆。则状态转移方程为:$dp[i][j][m] = min_{i \le n < j} \lbrace dp[i][n][1] + dp[n + 1][j][m - 1] \rbrace$。 - -我们再将区间 $[i, j]$ 的 $k$ 堆石头合并成 $1$ 堆,其成本为 区间 $[i, j]$ 的石堆合并成 $k$ 堆的成本,加上将这 $k$ 堆石头合并成 $1$ 堆的成本,即状态转移方程为:$dp[i][j][1] = dp[i][j][k] + \sum_{t = i}^{t = j} stones[t]$。 - -###### 4. 初始条件 - -- 长度为 $1$ 的区间 $[i, i]$ 合并为 $1$ 堆成本为 $0$,即:$dp[i][i][1] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 所以最终结果为 $dp[1][size][1]$,其中 $size$ 为数组 $stones$ 的长度。 - -### 思路 1:代码 - -```python -class Solution: - def mergeStones(self, stones: List[int], k: int) -> int: - size = len(stones) - if (size - 1) % (k - 1) != 0: - return -1 - - prefix = [0 for _ in range(size + 1)] - for i in range(1, size + 1): - prefix[i] = prefix[i - 1] + stones[i - 1] - - dp = [[[float('inf') for _ in range(k + 1)] for _ in range(size)] for _ in range(size)] - - for i in range(size): - dp[i][i][1] = 0 - - for l in range(2, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - for m in range(2, k + 1): - for n in range(i, j, k - 1): - dp[i][j][m] = min(dp[i][j][m], dp[i][n][1] + dp[n + 1][j][m - 1]) - dp[i][j][1] = dp[i][j][k] + prefix[j + 1] - prefix[i] - - return dp[0][size - 1][1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3 \times k)$,其中 $n$ 是数组 $stones$ 的长度。 -- **空间复杂度**:$O(n^2 \times k)$。 - -### 思路 2:动态规划 + 状态优化 - -在思路 1 中,我们使用定义状态 $dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 - -事实上,对于固定区间 $[i, j]$,初始时堆数为 $j - i + 1$,每次合并都会减少 $k - 1$ 堆,合并到无法合并时的堆数固定为 $(j - i) \mod (k - 1) + 1$。 - -所以,我们可以直接定义状态 $dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。 - -具体步骤如下: - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。 - -###### 3. 状态转移方程 - -枚举 $i \le n \le j$,将区间 $[i, j]$ 拆分为两个区间 $[i, n]$ 和 $[n + 1, j]$。然后将区间 $[i, n]$ 合并成 $1$ 堆,$[n + 1, j]$ 合并成 $m$ 堆。 - -$dp[i][j] = min_{i \le n < j} \lbrace dp[i][n] + dp[n + 1][j] \rbrace$。 - -如果 $(j - i) \mod (k - 1) == 0$,则说明区间 $[i, j]$ 能狗合并为 1 堆,则加上区间子数组和,即 $dp[i][j] += prefix[j + 1] - prefix[i]$。 - -###### 4. 初始条件 - -- 长度为 $1$ 的区间 $[i, i]$ 合并到无法合并时的最低成本为 $0$,即:$dp[i][i] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。所以最终结果为 $dp[0][size - 1]$,其中 $size$ 为数组 $stones$ 的长度。 - -### 思路 2:代码 - -```python -class Solution: - def mergeStones(self, stones: List[int], k: int) -> int: - size = len(stones) - if (size - 1) % (k - 1) != 0: - return -1 - - prefix = [0 for _ in range(size + 1)] - for i in range(1, size + 1): - prefix[i] = prefix[i - 1] + stones[i - 1] - - dp = [[float('inf') for _ in range(size)] for _ in range(size)] - - for i in range(size): - dp[i][i] = 0 - - for l in range(2, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - # 遍历每一个可以组成 k 堆石子的分割点 n,每次递增 k - 1 个 - for n in range(i, j, k - 1): - # 判断 [i, n] 到 [n + 1, j] 是否比之前花费小 - dp[i][j] = min(dp[i][j], dp[i][n] + dp[n + 1][j]) - # 如果 [i, j] 能狗合并为 1 堆,则加上区间子数组和 - if (l - 1) % (k - 1) == 0: - dp[i][j] += prefix[j + 1] - prefix[i] - - return dp[0][size - 1] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 是数组 $stones$ 的长度。 -- **空间复杂度**:$O(n^2)$。 - -## 参考资料 - -- 【题解】[一题一解:动态规划(区间 DP)+ 前缀和(清晰题解) - 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/solution/python3javacgo-yi-ti-yi-jie-dong-tai-gui-lr9q/) diff --git "a/Solutions/1002. \346\237\245\346\211\276\345\205\261\347\224\250\345\255\227\347\254\246.md" "b/Solutions/1002. \346\237\245\346\211\276\345\205\261\347\224\250\345\255\227\347\254\246.md" deleted file mode 100644 index 93a235eb..00000000 --- "a/Solutions/1002. \346\237\245\346\211\276\345\205\261\347\224\250\345\255\227\347\254\246.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [1002. 查找共用字符](https://leetcode.cn/problems/find-common-characters/) - -- 标签:数组、哈希表、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串数组 $words$。 - -**要求**:找出所有在 $words$ 的每个字符串中都出现的公用字符(包括重复字符),并以数组形式返回。可以按照任意顺序返回答案。 - -**说明**: - -- $1 \le words.length \le 100$。 -- $1 \le words[i].length \le 100$。 -- $words[i]$ 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:words = ["bella","label","roller"] -输出:["e","l","l"] -``` - -- 示例 2: - -```python -输入:words = ["cool","lock","cook"] -输出:["c","o"] -``` - -## 解题思路 - -### 思路 1:哈希表 - -如果某个字符 $ch$ 在所有字符串中都出现了 $k$ 次以上,则最终答案中需要包含 $k$ 个 $ch$。因此,我们可以使用哈希表 $minfreq[ch]$ 记录字符 $ch$ 在所有字符串中出现的最小次数。具体步骤如下: - -1. 定义长度为 $26$ 的哈希表 $minfreq$,初始化所有字符出现次数为无穷大,$minfreq[ch] = float('inf')$。 -2. 遍历字符串数组中的所有字符串 $word$,对于字符串 $word$: - 1. 记录 $word$ 中所有字符串的出现次数 $freq[ch]$。 - 2. 取 $freq[ch]$ 与 $minfreq[ch]$ 中的较小值更新 $minfreq[ch]$。 -3. 遍历完之后,再次遍历 $26$ 个字符,将所有最小出现次数大于零的字符按照出现次数存入答案数组中。 -4. 最后将答案数组返回。 - -### 思路 1:代码 - -```python -class Solution: - def commonChars(self, words: List[str]) -> List[str]: - minfreq = [float('inf') for _ in range(26)] - for word in words: - freq = [0 for _ in range(26)] - for ch in word: - freq[ord(ch) - ord('a')] += 1 - for i in range(26): - minfreq[i] = min(minfreq[i], freq[i]) - - res = [] - for i in range(26): - while minfreq[i]: - res.append(chr(i + ord('a'))) - minfreq[i] -= 1 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times (|\sum| + m))$,其中 $n$ 为字符串数组 $words$ 的长度,$m$ 为每个字符串的平均长度,$|\sum|$ 为字符集。 -- **空间复杂度**:$O(|\sum|)$。 - diff --git "a/Solutions/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III.md" "b/Solutions/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III.md" deleted file mode 100644 index 856c9979..00000000 --- "a/Solutions/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III.md" +++ /dev/null @@ -1,71 +0,0 @@ -# [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) - -- 标签:数组、二分查找、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个由 $0$、$1$ 组成的数组 $nums$,再给定一个整数 $k$。最多可以将 $k$ 个值从 $0$ 变到 $1$。 - -**要求**:返回仅包含 $1$ 的最长连续子数组的长度。 - -**说明**: - -- $1 \le nums.length \le 10^5$。 -- $nums[i]$ 不是 $0$ 就是 $1$。 -- $0 \le k \le nums.length$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], K = 2 -输出:6 -解释:[1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1] -将 nums[5]、nums[10] 从 0 翻转到 1,最长的子数组长度为 6。 -``` - -- 示例 2: - -```python -输入:nums = [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1], K = 3 -输出:10 -解释:[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1] -将 nums[4]、nums[5]、nums[9] 从 0 翻转到 1,最长的子数组长度为 10。 -``` - -## 解题思路 - -### 思路 1:滑动窗口(不定长度) - -1. 使用两个指针 $left$、$right$ 指向数组开始位置。使用 $max\underline{}count$ 来维护仅包含 $1$ 的最长连续子数组的长度。 -2. 不断右移 $right$ 指针,扩大滑动窗口范围,并统计窗口内 $0$ 元素的个数。 -3. 直到 $0$ 元素的个数超过 $k$ 时将 $left$ 右移,缩小滑动窗口范围,并减小 $0$ 元素的个数,同时维护 $max\underline{}count$。 -4. 最后输出最长连续子数组的长度 $max\underline{}count$。 - -### 思路 1:代码 - -```python -class Solution: - def longestOnes(self, nums: List[int], k: int) -> int: - max_count = 0 - zero_count = 0 - left, right = 0, 0 - while right < len(nums): - if nums[right] == 0: - zero_count += 1 - right += 1 - if zero_count > k: - if nums[left] == 0: - zero_count -= 1 - left += 1 - max_count = max(max_count, right - left) - return max_count -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1005. K \346\254\241\345\217\226\345\217\215\345\220\216\346\234\200\345\244\247\345\214\226\347\232\204\346\225\260\347\273\204\345\222\214.md" "b/Solutions/1005. K \346\254\241\345\217\226\345\217\215\345\220\216\346\234\200\345\244\247\345\214\226\347\232\204\346\225\260\347\273\204\345\222\214.md" deleted file mode 100644 index 757da097..00000000 --- "a/Solutions/1005. K \346\254\241\345\217\226\345\217\215\345\220\216\346\234\200\345\244\247\345\214\226\347\232\204\346\225\260\347\273\204\345\222\214.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [1005. K 次取反后最大化的数组和](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) - -- 标签:贪心、数组、排序 -- 难度:简单 - -## 题目大意 - -给定一个整数数组 nums 和一个整数 k。只能用下面的方法修改数组: - -- 将数组上第 i 个位置上的值取相反数,即将 `nums[i]` 变为 `-nums[i]`。 - -用这种方式进行 K 次修改(可以多次修改同一个位置 i) 后,返回数组可能的最大和。 - -## 解题思路 - -- 先将数组按绝对值大小进行排序 -- 从绝对值大的数开始遍历数组,如果 nums[i] < 0,并且 k > 0: - - 则对 nums[i] 取相反数,并将 k 值 -1。 -- 如果最后 k 还有余值,则判断奇偶性: - - 若 k 为奇数,则将数组绝对值最小的数进行取反。 - - 若 k 为偶数,则说明可将某一位数进行偶数次取反,和原数值一致,则不需要进行操作。 -- 最后返回数组和。 - -## 代码 - -```python -class Solution: - def largestSumAfterKNegations(self, nums: List[int], k: int) -> int: - nums.sort(key=lambda x: abs(x), reverse = True) - for i in range(len(nums)): - if nums[i] < 0 and k > 0: - nums[i] *= -1 - k -= 1 - if k % 2 == 1: - nums[-1] *= -1 - return sum(nums) -``` - diff --git "a/Solutions/1008. \345\211\215\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/1008. \345\211\215\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index d6e9a7e8..00000000 --- "a/Solutions/1008. \345\211\215\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [1008. 前序遍历构造二叉搜索树](https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/) - -- 标签:栈、树、二叉搜索树、数组、二叉树、单调栈 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树的前序遍历结果 `preorder`。 - -要求:返回与给定前序遍历 `preorder` 相匹配的二叉搜索树的根节点。题目保证,对于给定的测试用例,总能找到满足要求的二叉搜索树。 - -## 解题思路 - -二叉搜索树的中序遍历是升序序列。而题目又给了我们二叉搜索树的前序遍历,那么通过对前序遍历结果的排序,我们也可以得到二叉搜索树的中序遍历结果。这样就能根据二叉树的前序、中序遍历序列构造二叉树了。就变成了了「[0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)」题。 - -此外,我们还有另一种方法求解。前序遍历的顺序是:根 -> 左 -> 右。并且在二叉搜索树中,左子树的值小于根节点,右子树的值大于根节点。 - -根据以上性质,我们可以递归地构造二叉搜索树。 - -首先,以前序遍历的开始位置元素构造为根节点。从开始位置的下一个位置开始,找到序列中第一个大于等于根节点值的位置 `mid`。该位置左侧的值都小于根节点,右侧的值都大于等于根节点。以此位置为中心,递归的构造左子树和右子树。 - -最后再将根节点进行返回。 - -## 代码 - -```python -class Solution: - def buildTree(self, preorder, start, end): - if start == end: - return None - root = preorder[start] - mid = start + 1 - while mid < end and preorder[mid] < root: - mid += 1 - node = TreeNode(root) - node.left = self.buildTree(preorder, start + 1, mid) - node.right = self.buildTree(preorder, mid, end) - return node - - def bstFromPreorder(self, preorder: List[int]) -> TreeNode: - return self.buildTree(preorder, 0, len(preorder)) -``` - diff --git "a/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" "b/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" deleted file mode 100644 index a082410f..00000000 --- "a/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [1009. 十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) - -- 标签:位运算 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个十进制数 $n$。 - -**要求**:返回其二进制表示的反码对应的十进制整数。 - -**说明**: - -- $0 \le N < 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:5 -输出:2 -解释:5 的二进制表示为 "101",其二进制反码为 "010",也就是十进制中的 2 。 -``` - -- 示例 2: - -```python -输入:7 -输出:0 -解释:7 的二进制表示为 "111",其二进制反码为 "000",也就是十进制中的 0 。 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 将十进制数 $n$ 转为二进制 $binary$。 -2. 遍历二进制 $binary$ 的每一个数位 $digit$。 - 1. 如果 $digit$ 为 $0$,则将其转为 $1$,存入答案 $res$ 中。 - 2. 如果 $digit$ 为 $1$,则将其转为 $0$,存入答案 $res$ 中。 -3. 返回答案 $res$。 - -### 思路 1:代码 - -```python -class Solution: - def bitwiseComplement(self, n: int) -> int: - binary = "" - while n: - binary += str(n % 2) - n //= 2 - if binary == "": - binary = "0" - else: - binary = binary[::-1] - res = 0 - for digit in binary: - if digit == '0': - res = res * 2 + 1 - else: - res = res * 2 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(len(n))$,其中 $len(n)$ 为 $n$ 对应二进制的长度。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1011. \345\234\250 D \345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.md" "b/Solutions/1011. \345\234\250 D \345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.md" deleted file mode 100644 index 448a73c7..00000000 --- "a/Solutions/1011. \345\234\250 D \345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -**描述**:传送带上的包裹必须在 $D$ 天内从一个港口运送到另一个港口。给定所有包裹的重量数组 $weights$,货物必须按照给定的顺序装运。且每天船上装载的重量不会超过船的最大运载重量。 - -**要求**:求能在 $D$ 天内将所有包裹送达的船的最低运载量。 - -**说明**: - -- $1 \le days \le weights.length \le 5 * 10^4$。 -- $1 \le weights[i] \le 500$。 - -**示例**: - -- 示例 1: - -```python -输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5 -输出:15 -解释: -船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示: -第 1 天:1, 2, 3, 4, 5 -第 2 天:6, 7 -第 3 天:8 -第 4 天:9 -第 5 天:10 -请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 -``` - -- 示例 2: - -```python -输入:weights = [3,2,2,4,1,4], days = 3 -输出:6 -解释: -船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示: -第 1 天:3, 2 -第 2 天:2, 4 -第 3 天:1, 4 -``` - -## 解题思路 - -### 思路 1:二分查找 - -船最小的运载能力,最少也要等于或大于最重的那件包裹,即 $max(weights)$。最多的话,可以一次性将所有包裹运完,即 $sum(weights)$。船的运载能力介于 $[max(weights), sum(weights)]$ 之间。 - -我们现在要做的就是从这个区间内,找到满足可以在 $D$ 天内运送完所有包裹的最小载重量。 - -可以通过二分查找的方式,找到满足要求的最小载重量。 - -### 思路 1:代码 - -```python -class Solution: - def shipWithinDays(self, weights: List[int], D: int) -> int: - left = max(weights) - right = sum(weights) - - while left < right: - mid = (left + right) >> 1 - days = 1 - cur = 0 - for weight in weights: - if cur + weight > mid: - days += 1 - cur = 0 - cur += weight - - if days <= D: - right = mid - else: - left = mid + 1 - return left -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 -- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 - diff --git "a/Solutions/1012. \350\207\263\345\260\221\346\234\211 1 \344\275\215\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" "b/Solutions/1012. \350\207\263\345\260\221\346\234\211 1 \344\275\215\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 22338620..00000000 --- "a/Solutions/1012. \350\207\263\345\260\221\346\234\211 1 \344\275\215\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [1012. 至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) - -- 标签:数学、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个正整数 $n$。 - -**要求**:返回在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数。 - -**说明**: - -- $1 \le n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 20 -输出:1 -解释:具有至少 1 位重复数字的正数(<= 20)只有 11。 -``` - -- 示例 2: - -```python -输入:n = 100 -输出:10 -解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -正向求解在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数不太容易,我们可以反向思考,先求解出在 $[1, n]$ 范围内各位数字都不重复的正整数的个数 $ans$,然后 $n - ans$ 就是题目答案。 - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 - 4. 开始时没有填写数字。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), - 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 - 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 - 3. $isNum == True$ 表示 $pos$ 位选择了数字。 -6. 最后的方案数为 `n - dfs(0, 0, True, False)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def numDupDigitsAtMostN(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNumb 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - # d 不在选择的数字集合中,即之前没有选择过 d - if (state >> d) & 1 == 0: - ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) - return ans - - return n - dfs(0, 0, True, False) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$。 -- **空间复杂度**:$O(\log n \times 2^{10})$。 - diff --git "a/Solutions/1014. \346\234\200\344\275\263\350\247\202\345\205\211\347\273\204\345\220\210.md" "b/Solutions/1014. \346\234\200\344\275\263\350\247\202\345\205\211\347\273\204\345\220\210.md" deleted file mode 100644 index 74760419..00000000 --- "a/Solutions/1014. \346\234\200\344\275\263\350\247\202\345\205\211\347\273\204\345\220\210.md" +++ /dev/null @@ -1,27 +0,0 @@ -# [1014. 最佳观光组合](https://leetcode.cn/problems/best-sightseeing-pair/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给你一个正整数数组 `values`,其中 `values[i]` 表示第 `i` 个观光景点的评分,并且两个景点 `i` 和 `j` 之间的距离 为 `j - i`。一对景点(`i < j`)组成的观光组合的得分为 `values[i] + values[j] + i - j`,也就是景点的评分之和减去它们两者之间的距离。 - -要求:返回一对观光景点能取得的最高分。 - -## 解题思路 - -求解的是 `ans = max(values[i] + values[j] + i - j)`。对于当前第 `j` 个位置上的元素来说,`values[j] - j` 的值是固定的,求解 `ans` 就是在求解 `values[i] + i` 的最大值。我们使用一个变量 `max_score` 来存储当前第 `j` 个位置元素之前 `values[i] + i` 的最大值。然后遍历数组,求出每一个元素位置之前 `values[i] + i` 的最大值,并找出其中最大的 `ans`。 - -## 代码 - -```python -class Solution: - def maxScoreSightseeingPair(self, values: List[int]) -> int: - ans = 0 - max_score = values[0] - for i in range(1, len(values)): - ans = max(ans, max_score + values[i] - i) - max_score = max(max_score, values[i] + i) - return ans -``` diff --git "a/Solutions/1020. \351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" "b/Solutions/1020. \351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" deleted file mode 100644 index 0eaa0ce6..00000000 --- "a/Solutions/1020. \351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二维数组 `grid`,每个单元格为 `0`(代表海)或 `1`(代表陆地)。我们可以从一个陆地走到另一个陆地上(朝四个方向之一),然后从边界上的陆地离开网络的边界。 - -**要求**:返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。 - -**说明**: - -- $m == grid.length$。 -- $n == grid[i].length$。 -- $1 \le m, n \le 500$。 -- $grid[i][j]$ 的值为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/18/enclaves1.jpg) - -```python -输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]] -输出:3 -解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/02/18/enclaves2.jpg) - -```python -输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]] -输出:0 -解释:所有 1 都在边界上或可以到达边界。 -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -与四条边界相连的陆地单元是肯定能离开网络边界的。 - -我们可以先通过深度优先搜索将与四条边界相关的陆地全部变为海(赋值为 `0`)。 - -然后统计网格中 `1` 的数量,即为答案。 - -### 思路 1:代码 - -```python -class Solution: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def dfs(self, grid, i, j): - rows = len(grid) - cols = len(grid[0]) - if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == 0: - return - grid[i][j] = 0 - - for direct in self.directs: - new_i = i + direct[0] - new_j = j + direct[1] - self.dfs(grid, new_i, new_j) - - def numEnclaves(self, grid: List[List[int]]) -> int: - rows = len(grid) - cols = len(grid[0]) - for i in range(rows): - if grid[i][0] == 1: - self.dfs(grid, i, 0) - if grid[i][cols - 1] == 1: - self.dfs(grid, i, cols - 1) - - for j in range(cols): - if grid[0][j] == 1: - self.dfs(grid, 0, j) - if grid[rows - 1][j] == 1: - self.dfs(grid, rows - 1, j) - - ans = 0 - for i in range(rows): - for j in range(cols): - if grid[i][j] == 1: - ans += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git "a/Solutions/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" "b/Solutions/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" deleted file mode 100644 index 0e1a1b70..00000000 --- "a/Solutions/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [1023. 驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) - -- 标签:字典树、双指针、字符串、字符串匹配 -- 难度:中等 - -## 题目大意 - -**描述**:给定待查询列表 `queries`,和模式串 `pattern`。如果我们可以将小写字母(0 个或多个)插入模式串 `pattern` 中间(任意位置)得到待查询项 `queries[i]`,那么待查询项与给定模式串匹配。如果匹配,则对应答案为 `True`,否则为 `False`。 - -**要求**:将匹配结果存入由布尔值组成的答案列表中,并返回。 - -**说明**: - -- $1 \le queries.length \le 100$。 -- $1 \le queries[i].length \le 100$。 -- $1 \le pattern.length \le 100$。 -- 所有字符串都仅由大写和小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FB" -输出:[true,false,true,true,false] -示例: -"FooBar" 可以这样生成:"F" + "oo" + "B" + "ar"。 -"FootBall" 可以这样生成:"F" + "oot" + "B" + "all". -"FrameBuffer" 可以这样生成:"F" + "rame" + "B" + "uffer". -``` - -- 示例 2: - -```python -输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBa" -输出:[true,false,true,false,false] -解释: -"FooBar" 可以这样生成:"Fo" + "o" + "Ba" + "r". -"FootBall" 可以这样生成:"Fo" + "ot" + "Ba" + "ll". -``` - -## 解题思路 - -### 思路 1:字典树 - -构建一棵字典树,将 `pattern` 存入字典树中。 - -1. 对于 `queries[i]` 中的每个字符串。逐个字符与 `pattern` 进行匹配。 - 1. 如果遇见小写字母,直接跳过。 - 2. 如果遇见大写字母,但是不能匹配,返回 `False`。 - 3. 如果遇见大写字母,且可以匹配,继续查找。 - 4. 如果到达末尾仍然匹配,则返回 `True`。 -2. 最后将所有结果存入答案数组中返回。 - -### 思路 1:代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ord(ch) > 96: - if ch not in cur.children: - continue - else: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def camelMatch(self, queries: List[str], pattern: str) -> List[bool]: - trie_tree = Trie() - trie_tree.insert(pattern) - res = [] - for query in queries: - res.append(trie_tree.search(query)) - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times |T| + |pattern|)$。其中 $n$ 是待查询项的数目,$|T|$ 是最长的待查询项的字符串长度,$|pattern|$ 是字符串 `pattern` 的长度。 -- **空间复杂度**:$O(|pattern|)$。 - diff --git "a/Solutions/1025. \351\231\244\346\225\260\345\215\232\345\274\210.md" "b/Solutions/1025. \351\231\244\346\225\260\345\215\232\345\274\210.md" deleted file mode 100644 index 300dc2bd..00000000 --- "a/Solutions/1025. \351\231\244\346\225\260\345\215\232\345\274\210.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [1025. 除数博弈](https://leetcode.cn/problems/divisor-game/) - -- 标签:脑筋急转弯、数学、动态规划、博弈 -- 难度:简单 - -## 题目大意 - -爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。最初,黑板上有一个数字 `n`。在每个玩家的回合,玩家需要执行以下操作: - -- 选出任一 `x`,满足 `0 < x < n` 且 `n % x == 0`。 -- 用 `n - x` 替换黑板上的数字 `n` 。 -- 如果玩家无法执行这些操作,就会输掉游戏。 - -只有在爱丽丝在游戏中取得胜利时才返回 `True`,否则返回 `False`。假设两个玩家都以最佳状态参与游戏。 - -## 解题思路 - -- 如果 `n` 为奇数,则 `n` 的约数必然都是奇数;如果 `n` 为偶数,则 `n` 的约数可能为奇数也可能为偶数。 -- 无论 `n` 为奇数还是偶数,都可以选择 `1` 作为约数。 -- 无论 `n` 初始为多大的数,游戏到最终只能到 `n == 2` 结束,只要谁先到 `n == 2`,谁就赢得胜利。 -- 当初始 `n` 为偶数时,爱丽丝只要一直选 `1`,那么鲍勃必然会一直面临 `n` 为奇数的情况,这样最后爱丽丝肯定能先到 `n == 2`,稳赢。 -- 当初始 `n` 为奇数时,因为奇数的约数只能是奇数,奇数 - 奇数 必然是偶数,所以给鲍勃的数一定是偶数,鲍勃只需一直选 `1` 就会稳赢,此时爱丽丝稳输。 - -所以,当 `n` 为偶数时,爱丽丝稳赢。当 `n` 为奇数时,爱丽丝稳输。 - -## 代码 - -```python -class Solution: - def divisorGame(self, n: int) -> bool: - return n & 1 == 0 -``` - diff --git "a/Solutions/1028. \344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.md" "b/Solutions/1028. \344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 8c4bb1d8..00000000 --- "a/Solutions/1028. \344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [1028. 从先序遍历还原二叉树](https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/) - -- 标签:树、深度优先搜索、字符串、二叉树 -- 难度:困难 - -## 题目大意 - -对一棵二叉树进行深度优先搜索。在遍历的过程中,遇到节点,先输出与该节点深度相同数量的短线,再输出该节点的值。如果节点深度为 `D`,则子节点深度为 `D + 1`。根节点的深度为 `0`。如果节点只有一个子节点,则该子节点一定为左子节点。 - -现在给定深度优先搜索输出的字符串 `traversal`。 - -要求:还原二叉树,并返回其根节点 `root`。 - -## 解题思路 - -用栈存储需要构建子树的节点。并记录下上一节点深度和当前节点深度。 - -然后遍历深度优先搜索的输出字符串。 - -- 先将开始部分的数字作为根节点值,构建一个根节点 `root`,并将根节点插入到栈中。 -- 如果遇到 `-`,则更新当前节点深度。 - -- 然后如果遇到数字,则将数字逐位转为整数。并且在最后进行判断。 - - 如果当前节点深度 > 前一节点深度: - - 将栈顶节点出栈。 - - 构建一个新节点,值为当前整数。将新节点插入到栈顶节点的左子树上。 - - 将当前节点和新节点插入到栈中。 - - 如果当前节点深度 <= 前一节点深度: - - 将当前节点深度个数的节点从栈中弹出。 - - 构建一个新节点,值为当前整数。并将新节点插入到最后弹出节点的右子树上。 - - 将当前节点和新节点插入到栈中。 -- 最后输出根节点 `root`。 - -## 代码 - -```python -class Solution: - def recoverFromPreorder(self, traversal: str) -> Optional[TreeNode]: - stack = [] - - index, num = 0, 0 - pre_level, cur_level = 0, 0 - - size = len(traversal) - while index < size and traversal[index] != '-': - num = num * 10 + ord(traversal[index]) - ord('0') - index += 1 - - root = TreeNode(num) - stack.append(root) - - while index < size: - if traversal[index] == '-': - cur_level += 1 - index += 1 - else: - num = 0 - while index < size and traversal[index] != '-': - num = num * 10 + ord(traversal[index]) - ord('0') - index += 1 - - if cur_level > pre_level: - node = stack.pop() - node.left = TreeNode(num) - stack.append(node) - stack.append(node.left) - pre_level = cur_level - cur_level = 0 - else: - while len(stack) > cur_level: - stack.pop() - node = stack.pop() - node.right = TreeNode(num) - stack.append(node) - stack.append(node.right) - pre_level = cur_level - cur_level = 0 - return root -``` - diff --git "a/Solutions/1029. \344\270\244\345\234\260\350\260\203\345\272\246.md" "b/Solutions/1029. \344\270\244\345\234\260\350\260\203\345\272\246.md" deleted file mode 100644 index bfd47bb5..00000000 --- "a/Solutions/1029. \344\270\244\345\234\260\350\260\203\345\272\246.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [1029. 两地调度](https://leetcode.cn/problems/two-city-scheduling/) - -- 标签:贪心、数组、排序 -- 难度:中等 - -## 题目大意 - -**描述**:公司计划面试 `2 * n` 人。给你一个数组 `costs`,其中 `costs[i] = [aCosti, bCosti]`,表示第 `i` 人飞往 `a` 市的费用为 `aCosti` ,飞往 `b` 市的费用为 `bCosti`。 - -**要求**:返回将每个人都飞到 `a`、`b` 中某座城市的最低费用,要求每个城市都有 `n` 人抵达。 - -**说明**: - -- $2 * n == costs.length$。 -- $2 \le costs.length \le 100$。 -- $costs.length$ 为偶数。 -- $1 \le aCosti, bCosti \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:costs = [[10,20],[30,200],[400,50],[30,20]] -输出:110 -解释: -第一个人去 a 市,费用为 10。 -第二个人去 a 市,费用为 30。 -第三个人去 b 市,费用为 50。 -第四个人去 b 市,费用为 20。 - -最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -我们先假设所有人都去了城市 `a`。然后令一半的人再去城市 `b`。现在的问题就变成了,让一半的人改变城市去向,从原本的 `a` 城市改成 `b` 城市的最低费用为多少。 - -已知第 `i` 个人更换去向的费用为「去城市 `b` 的费用 - 去城市 `a` 的费用」。所以我们可以根据「去城市 `b` 的费用 - 去城市 `a` 的费用」对数组 `costs` 进行排序,让前 `n` 个改变方向去城市 `b`,后 `n` 个人去城市 `a`。 - -最后统计所有人员的费用,将其返回即可。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def twoCitySchedCost(self, costs: List[List[int]]) -> int: - costs.sort(key=lambda x:x[1] - x[0]) - cost = 0 - size = len(costs) // 2 - for i in range(size): - cost += costs[i][ 1] - cost += costs[i + size][0] - - return cost -``` diff --git "a/Solutions/1034. \350\276\271\347\225\214\347\235\200\350\211\262.md" "b/Solutions/1034. \350\276\271\347\225\214\347\235\200\350\211\262.md" deleted file mode 100644 index 201c6cea..00000000 --- "a/Solutions/1034. \350\276\271\347\225\214\347\235\200\350\211\262.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [1034. 边界着色](https://leetcode.cn/problems/coloring-a-border/) - -- 标签:深度优先搜索、广度优先搜索、数组、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个二维整数矩阵 `grid`,其中 `grid[i][j]` 表示矩阵第 `i` 行、第 `j` 列上网格块的颜色值。再给定一个起始位置 `(row, col)`,以及一个目标颜色 `color`。 - -要求:对起始位置 `(row, col)` 所在的连通分量边界填充颜色为 `color`。并返回最终的二维整数矩阵 `grid`。 - -- 连通分量:当两个相邻(上下左右四个方向上)网格块的颜色值相同时,它们属于同一连通分量。 -- 连通分量边界:当前连通分量最外圈的所有网格块,这些网格块与连通分量的颜色相同,与其他周围网格块颜色不同。边界上的网格块也是连通分量边界。 - -## 解题思路 - -深度优先搜索。使用二维数组 `visited` 标记访问过的节点。遍历上、下、左、右四个方向上的点。如果下一个点位置越界,或者当前位置与下一个点位置颜色不一样,则对该节点进行染色。 - -在遍历的过程中注意使用 `visited` 标记访问过的节点,以免重复遍历。 - -## 代码 - -```python -class Solution: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def dfs(self, grid, i, j, origin_color, color, visited): - rows, cols = len(grid), len(grid[0]) - - for direct in self.directs: - new_i = i + direct[0] - new_j = j + direct[1] - - # 下一个位置越界,则当前点在边界,对其进行着色 - if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: - grid[i][j] = color - continue - - # 如果访问过,则跳过 - if visited[new_i][new_j]: - continue - - # 如果下一个位置颜色与当前颜色相同,则继续搜索 - if grid[new_i][new_j] == origin_color: - visited[new_i][new_j] = True - self.dfs(grid, new_i, new_j, origin_color, color, visited) - # 下一个位置颜色与当前颜色不同,则当前位置为连通区域边界,对其进行着色 - else: - grid[i][j] = color - - - def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]: - if not grid: - return grid - - rows, cols = len(grid), len(grid[0]) - visited = [[False for _ in range(cols)] for _ in range(rows)] - visited[row][col] = True - - self.dfs(grid, row, col, grid[row][col], color, visited) - - return grid -``` - diff --git "a/Solutions/1035. \344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.md" "b/Solutions/1035. \344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.md" deleted file mode 100644 index a1c4496b..00000000 --- "a/Solutions/1035. \344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [1035. 不相交的线](https://leetcode.cn/problems/uncrossed-lines/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -有两条独立平行的水平线,按照给定的顺序写下 `nums1` 和 `nums2` 的整数。 - -现在,我们可以绘制一些直线,只要满足以下要求: - -- `nums1[i] == nums2[j]`。 -- 绘制的直线不与其他任何直线相交。 - -例如:![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/04/28/142.png) - -现在要求:计算出能绘制的最大直线数目。 - -## 解题思路 - -动态规划求解。 - -定义状态 `dp[i][j]` 表示:`nums1` 中前 `i` 个数与 `nums2` 中前 `j` 个数的最大连接数,则: - -状态转移方程为: - -- 如果 `nums1[i] == nums[j]`,则 `nums1[i]` 与 `nums2[j]` 可连线,此时 `dp[i][j] = dp[i - 1][j - 1] + 1`。 -- 如果 `nums1[i] != nums[j]`,则 `nums1[i]` 与 `nums2[j]` 不可连线,此时最大连线数取决于 `dp[i - 1][j]` 和 `dp[i][j - 1]` 的较大值,即:`dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])`。 - -最后输出 `dp[size1][size2]` 即可。 - -## 代码 - -```python -class Solution: - def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int: - size1 = len(nums1) - size2 = len(nums2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if nums1[i - 1] == nums2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - else: - dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - return dp[size1][size2] -``` - diff --git "a/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" "b/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" deleted file mode 100644 index 2e701bfc..00000000 --- "a/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [1037. 有效的回旋镖](https://leetcode.cn/problems/valid-boomerang/) - -- 标签:几何、数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $points$,其中 $points[i] = [xi, yi]$ 表示平面上的一个点。 - -**要求**:如果这些点构成一个回旋镖,则返回 `True`,否则,则返回 `False`。 - -**说明**: - -- **回旋镖**:定义为一组三个点,这些点各不相同且不在一条直线上。 -- $points.length == 3$。 -- $points[i].length == 2$。 -- $0 \le xi, yi \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:points = [[1,1],[2,3],[3,2]] -输出:True -``` - -- 示例 2: - -```python -输入:points = [[1,1],[2,2],[3,3]] -输出:False -``` - -## 解题思路 - -### 思路 1: - -设三点坐标为 $A = (x1, y1)$,$B = (x2, y2)$,$C = (x3, y3)$,则向量 $\overrightarrow{AB} = (x2 - x1, y2 - y1)$,$\overrightarrow{BC} = (x3 - x2, y3 - y2)$。 - -如果三点共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) = 0$。 - -如果三点不共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) \ne 0$。 - -### 思路 1:代码 - -```python -class Solution: - def isBoomerang(self, points: List[List[int]]) -> bool: - x1, y1 = points[0] - x2, y2 = points[1] - x3, y3 = points[2] - cross1 = (x2 - x1) * (y3 - y2) - cross2 = (x3 - x2) * (y2 - y1) - return cross1 - cross2 != 0 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.md" "b/Solutions/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.md" deleted file mode 100644 index fc981c0f..00000000 --- "a/Solutions/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树(BST)的根节点,且二叉搜索树的节点值各不相同。 - -要求:将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 - -二叉搜索树的定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -## 解题思路 - -题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 - -题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 - -二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 - -当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 - -## 代码 - -```python -class Solution: - pre = 0 - - def createBinaryTree(self, root: TreeNode): - if not root: - return - self.createBinaryTree(root.right) - root.val += self.pre - self.pre = root.val - self.createBinaryTree(root.left) - - def bstToGst(self, root: TreeNode) -> TreeNode: - self.pre = 0 - self.createBinaryTree(root) - return root -``` - diff --git "a/Solutions/1039. \345\244\232\350\276\271\345\275\242\344\270\211\350\247\222\345\211\226\345\210\206\347\232\204\346\234\200\344\275\216\345\276\227\345\210\206.md" "b/Solutions/1039. \345\244\232\350\276\271\345\275\242\344\270\211\350\247\222\345\211\226\345\210\206\347\232\204\346\234\200\344\275\216\345\276\227\345\210\206.md" deleted file mode 100644 index 0d916bd3..00000000 --- "a/Solutions/1039. \345\244\232\350\276\271\345\275\242\344\270\211\350\247\222\345\211\226\345\210\206\347\232\204\346\234\200\344\275\216\345\276\227\345\210\206.md" +++ /dev/null @@ -1,107 +0,0 @@ -# [1039. 多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:有一个凸的 $n$ 边形,其每个顶点都有一个整数值。给定一个整数数组 $values$,其中 $values[i]$ 是第 $i$ 个顶点的值(即顺时针顺序)。 - -现在要将 $n$ 边形剖分为 $n - 2$ 个三角形,对于每个三角形,该三角形的值是顶点标记的乘积,$n$ 边形三角剖分的分数是进行三角剖分后所有 $n - 2$ 个三角形的值之和。 - -**要求**:返回多边形进行三角剖分可以得到的最低分。 - -**说明**: - -- $n == values.length$。 -- $3 \le n \le 50$。 -- $1 \le values[i] \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/02/25/shape1.jpg) - -```python -输入:values = [1,2,3] -输出:6 -解释:多边形已经三角化,唯一三角形的分数为 6。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/02/25/shape2.jpg) - -```python -输入:values = [3,7,4,5] -输出:144 -解释:有两种三角剖分,可能得分分别为:3*7*5 + 4*5*7 = 245,或 3*4*5 + 3*4*7 = 144。最低分数为 144。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -对于 $0 \sim n - 1$ 个顶点组成的凸多边形进行三角剖分,我们可以在 $[0, n - 1]$ 中任选 $1$ 个点 $k$,从而将凸多边形划分为: - -1. 顶点 $0 \sim k$ 组成的凸多边形。 -2. 顶点 $0$、$k$、$n - 1$ 组成的三角形。 -3. 顶点 $k \sim n - 1$ 组成的凸多边形。 - -对于顶点 $0$、$k$、$n - 1$ 组成的三角形,我们可以直接计算对应的三角剖分分数为 $values[0] \times values[k] \times values[n - 1]$。 - -而对于顶点 $0 \sim k$ 组成的凸多边形和顶点 $k \sim n - 1$ 组成的凸多边形,我们可以利用递归或者动态规划的思想,定义一个 $dp[i][j]$ 用于计算顶点 $i$ 到顶点 $j$ 组成的多边形三角剖分的最小分数。 - -具体做法如下: - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:区间 $[i, j]$ 内三角剖分后的最小分数。 - -###### 3. 状态转移方程 - -对于区间 $[i, j]$,枚举分割点 $k$,最小分数为 $min(dp[i][k] + dp[k][j] + values[i] \times values[k] \times values[j])$,即:$dp[i][j] = min(dp[i][k] + dp[k][j] + values[i] \times values[k] \times values[j])$。 - -###### 4. 初始条件 - -- 默认情况下,所有区间 $[i, j]$ 的最小分数为无穷大。 -- 当区间 $[i, j]$ 长度小于 $3$ 时,无法进行三角剖分,其最小分数为 $0$。 -- 当区间 $[i, j]$ 长度等于 $3$ 时,其三角剖分的最小分数为 $values[i] * values[i + 1] * values[i + 2]$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:区间 $[i, j]$ 内三角剖分后的最小分数。。 所以最终结果为 $dp[0][size - 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def minScoreTriangulation(self, values: List[int]) -> int: - size = len(values) - dp = [[float('inf') for _ in range(size)] for _ in range(size)] - for l in range(1, size + 1): - for i in range(size): - j = i + l - 1 - if j >= size: - break - if l < 3: - dp[i][j] = 0 - elif l == 3: - dp[i][j] = values[i] * values[i + 1] * values[i + 2] - else: - for k in range(i + 1, j): - dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + values[i] * values[j] * values[k]) - - return dp[0][size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 为顶点个数。 -- **空间复杂度**:$O(n^2)$。 - diff --git "a/Solutions/1047. \345\210\240\351\231\244\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\211\200\346\234\211\347\233\270\351\202\273\351\207\215\345\244\215\351\241\271.md" "b/Solutions/1047. \345\210\240\351\231\244\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\211\200\346\234\211\347\233\270\351\202\273\351\207\215\345\244\215\351\241\271.md" deleted file mode 100644 index adce2874..00000000 --- "a/Solutions/1047. \345\210\240\351\231\244\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\211\200\346\234\211\347\233\270\351\202\273\351\207\215\345\244\215\351\241\271.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) - -- 标签:栈、字符串 -- 难度:简单 - -## 题目大意 - -给定一个全部由小写字母组成的字符串 S,重复的删除相邻且相同的字母,直到相邻字母不再有相同的。 - -比如 "abbaca"。先删除相邻且相同的字母 "bb",变为 "aaca",再删除相邻且相同的字母 "aa",变为 "ca",无相邻且相同的字母,即 "ca" 为最终结果。 - -## 解题思路 - -跟括号匹配有点类似。我们可以利用「栈」来做这道题。遍历字符串,如果当前字符与栈顶字符相同,则将栈顶所有相同字符删除,否则就将当前字符入栈。 - -## 代码 - -```python -class Solution: - def removeDuplicates(self, S: str) -> str: - stack = [] - for ch in S: - if stack and stack[-1] == ch: - stack.pop() - else: - stack.append(ch) - return "".join(stack) -``` - diff --git "a/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" "b/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" deleted file mode 100644 index 415844ee..00000000 --- "a/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" +++ /dev/null @@ -1,95 +0,0 @@ -# [1049. 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:有一堆石头,用整数数组 $stones$ 表示,其中 $stones[i]$ 表示第 $i$​ 块石头的重量。每一回合,从石头中选出任意两块石头,将这两块石头一起粉碎。假设石头的重量分别为 $x$ 和 $y$。且 $x \le y$,则结果如下: - -- 如果 $x = y$,则两块石头都会被完全粉碎; -- 如果 $x < y$,则重量为 $x$ 的石头被完全粉碎,而重量为 $y$ 的石头新重量为 $y - x$。 - -**要求**:最后,最多只会剩下一块石头,返回此石头的最小可能重量。如果没有石头剩下,则返回 $0$。 - -**说明**: - -- $1 \le stones.length \le 30$。 -- $1 \le stones[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:stones = [2,7,4,1,8,1] -输出:1 -解释: -组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], -组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], -组合 2 和 1,得到 1,所以数组转化为 [1,1,1], -组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。 -``` - -- 示例 2: - -```python -输入:stones = [31,26,33,21,40] -输出:5 -``` - -## 解题思路 - -### 思路 1:动态规划 - -选取两块石头,重新放回去的重量是两块石头的差值绝对值。重新放回去的石头还会进行选取,然后进行粉碎,直到最后只剩一块或者不剩石头。 - -这个问题其实可以转化为:把一堆石头尽量平均的分成两对,求两堆石头重量差的最小值。 - -这就和「[0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)」有点相似。两堆石头的重量要尽可能的接近数组总数量和的一半。 - -进一步可以变为:「0-1 背包问题」。 - -1. 假设石头总重量和为 $sum$,将一堆石头放进载重上限为 $sum / 2$ 的背包中,获得的最大价值为 $max\underline{}weight$(即其中一堆石子的重量)。另一堆石子的重量为 $sum - max\underline{}weight$。 -2. 则两者的差值为 $sum - 2 \times max\underline{}weight$,即为答案。 - -###### 1. 划分阶段 - -按照石头的序号进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值。 - -###### 3. 状态转移方程 - -$dp[w] = max \lbrace dp[w], dp[w - stones[i - 1]] + stones[i - 1] \rbrace$。 - -###### 4. 初始条件 - -- 无论背包载重上限为多少,只要不选择石头,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值,即第一堆石头的价值为 $dp[size]$,第二堆石头的价值为 $sum - dp[size]$,最终答案为两者的差值,即 $sum - dp[size] \times 2$。 - -### 思路 1:代码 - -```python -class Solution: - def lastStoneWeightII(self, stones: List[int]) -> int: - W = 1500 - size = len(stones) - dp = [0 for _ in range(W + 1)] - target = sum(stones) // 2 - for i in range(1, size + 1): - for w in range(target, stones[i - 1] - 1, -1): - dp[w] = max(dp[w], dp[w - stones[i - 1]] + stones[i - 1]) - - return sum(stones) - dp[target] * 2 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times W)$,其中 $n$ 为数组 $stones$ 的元素个数,$W$ 为数组 $stones$ 中元素和的一半。 -- **空间复杂度**:$O(W)$。 diff --git "a/Solutions/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277.md" "b/Solutions/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277.md" deleted file mode 100644 index 910ae357..00000000 --- "a/Solutions/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [1052. 爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) - -- 标签:数组、滑动窗口 -- 难度:中等 - -## 题目大意 - -书店老板有一家店打算试营业 `len(customers)` 分钟。每一分钟都有一些顾客 `customers[i]` 会进入书店,这些顾客会在这一分钟结束后离开。 - -在某些时候,书店老板会生气。如果书店老板在第 `i` 分钟生气,则 `grumpy[i] = 1`,如果第 `i` 分钟不生气,则 `grumpy[i] = 0`。当书店老板生气时,这一分钟的顾客会不满意。当书店老板不生气时,这一分钟的顾客是满意的。 - -假设老板知道一个秘密技巧,能保证自己连续 `minutes` 分钟不生气,但只能使用一次。 - -现在给定代表每分钟进入书店的顾客数量的数组 `customes`,和代表老板生气状态的数组 `grumpy`,以及老板保证连续不生气的分钟数 `minutes`。 - -要求:计算出试营业下来,最多有多少客户能够感到满意。 - -## 解题思路 - -固定长度的滑动窗口题目。我们可以维护一个窗口大小为 `minutes` 的滑动窗口。使用 `window_count` 记录当前窗口内生气的顾客人数。然后滑动求出窗口中最大顾客数,然后累加上老板未生气时的顾客数,就是答案。具体做法如下: - -1. `ans` 用来维护答案数目。`window_count` 用来维护窗口中生气的顾客人数。 -2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 如果书店老板生气,则将这一分钟的顾客数量加入到 `window_count` 中,然后向右移动 `right`。 -4. 当窗口元素个数大于 `minutes` 时,即:`right - left + 1 > count` 时,如果最左侧边界老板处于生气状态,则向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为小于 `minutes`。 -5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 -6. 然后累加上老板未生气时的顾客数,最后输出答案。 - -## 代码 - -```python -class Solution: - def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int: - left = 0 - right = 0 - window_count = 0 - ans = 0 - - while right < len(customers): - if grumpy[right] == 1: - window_count += customers[right] - - if right - left + 1 > minutes: - if grumpy[left] == 1: - window_count -= customers[left] - left += 1 - - right += 1 - ans = max(ans, window_count) - - for i in range(len(customers)): - if grumpy[i] == 0: - ans += customers[i] - return ans -``` - diff --git "a/Solutions/1065. \345\255\227\347\254\246\344\270\262\347\232\204\347\264\242\345\274\225\345\257\271.md" "b/Solutions/1065. \345\255\227\347\254\246\344\270\262\347\232\204\347\264\242\345\274\225\345\257\271.md" deleted file mode 100644 index 0fcc7238..00000000 --- "a/Solutions/1065. \345\255\227\347\254\246\344\270\262\347\232\204\347\264\242\345\274\225\345\257\271.md" +++ /dev/null @@ -1,71 +0,0 @@ -# [1065. 字符串的索引对](https://leetcode.cn/problems/index-pairs-of-a-string/) - -- 标签:字典树、数组、字符串、排序 -- 难度:简单 - -## 题目大意 - -给定字符串 `text` 和单词列表 `words`。 - -要求:在 `text` 中找出所有属于单词列表 `words` 中的单词,并返回该单词在 `text` 中的索引对位置 `[i, j]`。将所有索引对存入列表中返回,并且返回的索引对可以交叉。 - -## 解题思路 - -构建字典树,将所有单词存入字典树中。 - -然后一重循环遍历 `text`,表示从第 `i` 位置开始的字符串 `text[i:]`。然后在字符串前缀中搜索对应的单词,将所有符合要求的单词末尾位置存入列表中,返回所有位置列表。对于列表中每个单词末尾位置 `index` 和 `text` 来说,每个 `[i, i + index]` 都构成了单词在 `text` 中的索引对位置,将其存入答案数组并返回即可。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, text: str) -> list: - """ - Returns if the word is in the trie. - """ - cur = self - res = [] - for i in range(len(text)): - ch = text[i] - if ch not in cur.children: - return res - cur = cur.children[ch] - if cur.isEnd: - res.append(i) - - return res - -class Solution: - def indexPairs(self, text: str, words: List[str]) -> List[List[int]]: - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - res = [] - for i in range(len(text)): - for index in trie_tree.search(text[i:]): - res.append([i, i + index]) - return res -``` - diff --git "a/Solutions/1079. \346\264\273\345\255\227\345\215\260\345\210\267.md" "b/Solutions/1079. \346\264\273\345\255\227\345\215\260\345\210\267.md" deleted file mode 100644 index 4371f155..00000000 --- "a/Solutions/1079. \346\264\273\345\255\227\345\215\260\345\210\267.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [1079. 活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) - -- 标签:哈希表、字符串、回溯、计数 -- 难度:中等 - -## 题目大意 - -给定一个代表活字字模的字符串 `tiles`,其中 `tiles[i]` 表示第 `i` 个字模上刻的字母。 - -要求:返回你可以印出的非空字母序列的数目。 - -**注意:**本题中,每个活字字模只能使用一次。 - -## 解题思路 - -使用哈希表存储每个字符的个数。然后依次从哈希表中取出对应字符,统计排列个数,并进行回溯。如果当前字符个数为 `0`,则不再进行回溯。回溯之后将状态回退。 - -## 代码 - -```python -class Solution: - ans = 0 - def backtrack(self, tile_map): - for key, value in tile_map.items(): - if value == 0: - continue - self.ans += 1 - tile_map[key] -= 1 - self.backtrack(tile_map) - tile_map[key] += 1 - - def numTilePossibilities(self, tiles: str) -> int: - tile_map = dict() - for tile in tiles: - if tile not in tile_map: - tile_map[tile] = 1 - else: - tile_map[tile] += 1 - - self.backtrack(tile_map) - - return self.ans -``` - diff --git "a/Solutions/1081. \344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227.md" "b/Solutions/1081. \344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index f24281be..00000000 --- "a/Solutions/1081. \344\270\215\345\220\214\345\255\227\347\254\246\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [1081. 不同字符的最小子序列](https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/) - -- 标签:栈、贪心、字符串、单调栈 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`。 - -**要求**:去除字符串中重复的字母,使得每个字母只出现一次。需要保证 **「返回结果的字典序最小(要求不能打乱其他字符的相对位置)」**。 - -**说明**: - -- $1 \le s.length \le 10^4$。 -- `s` 由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "bcabc" -输出:"abc" -``` - -- 示例 2: - -```python -输入:s = "cbacdcbc" -输出:"acdb" -``` - -## 解题思路 - -### 思路 1:哈希表 + 单调栈 - -针对题目的三个要求:去重、不能打乱其他字符顺序、字典序最小。我们来一一分析。 - -1. **去重**:可以通过 **「使用哈希表存储字母出现次数」** 的方式,将每个字母出现的次数统计起来,再遍历一遍,去除重复的字母。 -2. **不能打乱其他字符顺序**:按顺序遍历,将非重复的字母存储到答案数组或者栈中,最后再拼接起来,就能保证不打乱其他字符顺序。 -3. **字典序最小**:意味着字典序小的字母应该尽可能放在前面。 - 1. 对于第 `i` 个字符 `s[i]` 而言,如果第 `0` ~ `i - 1` 之间的某个字符 `s[j]` 在 `s[i]` 之后不再出现了,那么 `s[j]` 必须放到 `s[i]` 之前。 - 2. 而如果 `s[j]` 在之后还会出现,并且 `s[j]` 的字典序大于 `s[i]`,我们则可以先舍弃 `s[j]`,把 `s[i]` 尽可能的放到前面。后边再考虑使用 `s[j]` 所对应的字符。 - - -要满足第 3 条需求,我们可以使用 **「单调栈」** 来解决。我们使用单调栈存储 `s[i]` 之前出现的非重复、并且字典序最小的字符序列。整个算法步骤如下: - -1. 先遍历一遍字符串,用哈希表 `letter_counts` 统计出每个字母出现的次数。 -2. 然后使用单调递减栈保存当前字符之前出现的非重复、并且字典序最小的字符序列。 -3. 当遍历到 `s[i]` 时,如果 `s[i]` 没有在栈中出现过: - 1. 比较 `s[i]` 和栈顶元素 `stack[-1]` 的字典序。如果 `s[i]` 的字典序小于栈顶元素 `stack[-1]`,并且栈顶元素之后的出现次数大于 `0`,则将栈顶元素弹出。 - 2. 然后继续判断 `s[i]` 和栈顶元素 `stack[-1]`,并且知道栈顶元素出现次数为 `0` 时停止弹出。此时将 `s[i]` 添加到单调栈中。 -4. 从哈希表 `letter_counts` 中减去 `s[i]` 出现的次数,继续遍历。 -5. 最后将单调栈中的字符依次拼接为答案字符串,并返回。 - -### 思路 1:代码 - -```python -class Solution: - def removeDuplicateLetters(self, s: str) -> str: - stack = [] - letter_counts = dict() - for ch in s: - if ch in letter_counts: - letter_counts[ch] += 1 - else: - letter_counts[ch] = 1 - - for ch in s: - if ch not in stack: - while stack and ch < stack[-1] and stack[-1] in letter_counts and letter_counts[stack[-1]] > 0: - stack.pop() - stack.append(ch) - letter_counts[ch] -= 1 - - return ''.join(stack) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 为字符集合,$|\sum|$ 为字符种类个数。由于栈中字符不能重复,因此栈中最多有 $|\sum|$ 个字符。 - -## 参考资料 - -- 【题解】[去除重复数组 - 去除重复字母 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicate-letters/solution/qu-chu-zhong-fu-shu-zu-by-lu-shi-zhe-sokp/) \ No newline at end of file diff --git "a/Solutions/1089. \345\244\215\345\206\231\351\233\266.md" "b/Solutions/1089. \345\244\215\345\206\231\351\233\266.md" deleted file mode 100644 index 8954cedb..00000000 --- "a/Solutions/1089. \345\244\215\345\206\231\351\233\266.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [1089. 复写零](https://leetcode.cn/problems/duplicate-zeros/) - -- 标签:数组、双指针 -- 难度:简单 - -## 题目大意 - -**描述**:给定搞一个长度固定的整数数组 $arr$。 - -**要求**:键改改数组中出现的每一个 $0$ 都复写一遍,并将其余的元素向右平移。 - -**说明**: - -- 注意:不要在超过该数组长度的位置写上元素。请对输入的数组就地进行上述修改,不要从函数返回任何东西。 -- $1 \le arr.length \le 10^4$。 -- $0 \le arr[i] \le 9$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [1,0,2,3,0,4,5,0] -输出:[1,0,0,2,3,0,0,4] -解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4] -``` - -- 示例 2: - -```python -输入:arr = [1,2,3] -输出:[1,2,3] -解释:调用函数后,输入的数组将被修改为:[1,2,3] -``` - -## 解题思路 - -### 思路 1:两次遍历 + 快慢指针 - -因为数组中出现的 $0$ 需要复写为 $00$,占用空间从一个单位变成两个单位空间,那么右侧必定会有一部分元素丢失。我们可以先遍历一遍数组,找出复写后需要保留的有效数字部分与需要丢失部分的分界点。则从分界点开始,分界点右侧的元素都可以丢失。 - -我们再次逆序遍历数组, - -1. 使用两个指针 $slow$、$fast$,$slow$ 表示当前有效字符位置,$fast$ 表示当前遍历字符位置。一开始 $slow$ 和 $fast$ 都指向数组开始位置。 -2. 正序扫描数组: - 1. 如果遇到 $arr[slow] == 0$,则让 $fast$ 指针多走一步。 - 2. 然后 $fast$、$slow$ 各自向右移动 $1$ 位,直到 $fast$ 指针移动到数组末尾。此时 $slow$ 左侧数字 $arr[0]... arr[slow - 1]$ 为需要保留的有效数字部分, $arr[slow]...arr[fast - 1]$ 为需要丢失部分。 -3. 令 $slow$、$fast$ 分别左移 $1$ 位,此时 $slow$ 指向最后一个有效数字,$fast$ 指向丢失部分的最后一个数字。此时 $fast$ 可能等于 $size - 1$,也可能等于 $size$(比如输入 $[0, 0, 0]$)。 -4. 逆序遍历数组: - 1. 将 $slow$ 位置元素移动到 $fast$ 位置。 - 2. 如果遇到 $arr[slow] == 0$,则令 $fast$ 减 $1$,然后再复制 $1$ 个 $0$ 到 $fast$ 位置。 - 3. 令 $slow$、$fast$ 分别左移 $1$ 位。 - -### 思路 1:代码 - -```python -class Solution: - def duplicateZeros(self, arr: List[int]) -> None: - """ - Do not return anything, modify arr in-place instead. - """ - size = len(arr) - slow, fast = 0, 0 - while fast < size: - if arr[slow] == 0: - fast += 1 - slow += 1 - fast += 1 - - slow -= 1 # slow 指向最后一个有效数字 - fast -= 1 # fast 指向丢失部分的最后一个数字(可能在减 1 之后为 size,比如输入 [0, 0, 0]) - - while slow >= 0: - if fast < size: # 防止 fast 越界 - arr[fast] = arr[slow] # 将 slow 位置元素移动到 fast 位置 - if arr[slow] == 0 and fast >= 0: # 遇见 0 则复制 0 到 fast - 1 位置 - fast -= 1 - arr[fast] = arr[slow] - fast -= 1 - slow -= 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $arr$ 中的元素个数。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1091. \344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" "b/Solutions/1091. \344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" deleted file mode 100644 index 6ac4cb8b..00000000 --- "a/Solutions/1091. \344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [1091. 二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) - -- 标签:广度优先搜索、数组、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `n * n` 的二进制矩阵 `grid`。 `grid` 中只含有 `0` 或者 `1`。`grid` 中的畅通路径是一条从左上角 `(0, 0)` 位置上到右下角 `(n - 1, n - 1)`位置上的路径。该路径同时满足以下要求: - -- 路径途径的所有单元格的值都是 `0`。 -- 路径中所有相邻的单元格应该在 `8` 个方向之一上连通(即相邻两单元格之间彼此不同且共享一条边或者一个角)。 -- 畅通路径的长度是该路径途径的单元格总数。 - -要求:计算出矩阵中最短畅通路径的长度。如果不存在这样的路径,返回 `-1`。 - -## 解题思路 - -使用广度优先搜索查找最短路径。具体做法如下: - -1. 使用队列 `queue` 存放当前节点位置,使用 set 集合 `visited` 存放遍历过的节点位置。使用 `count` 记录最短路径。将起始位置 `(0, 0)` 加入到 `queue` 中,并标记为访问过。 -2. 如果队列不为空,则令 `count += 1`,并将队列中的节点位置依次取出。对于每一个节点位置: - - 先判断是否为右下角节点,即 `(n - 1, n - 1)`。如果是则返回当前最短路径长度 `count`。 - - 如果不是,则继续遍历 `8` 个方向上、没有访问过、并且值为 `0` 的相邻单元格。 - - 将其加入到队列 `queue` 中,并标记为访问过。 -3. 重复进行第 2 步骤,直到队列为空时,返回 `-1`。 - -## 代码 - -```python -class Solution: - def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: - if grid[0][0] == 1: - return -1 - size = len(grid) - directions = {(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)} - visited = set((0, 0)) - queue = [(0, 0)] - count = 0 - while queue: - count += 1 - for _ in range(len(queue)): - row, col = queue.pop(0) - - if row == size - 1 and col == size - 1: - return count - for direction in directions: - new_row = row + direction[0] - new_col = col + direction[1] - if 0 <= new_row < size and 0 <= new_col < size and grid[new_row][new_col] == 0 and (new_row, new_col) not in visited: - queue.append((new_row, new_col)) - visited.add((new_row, new_col)) - return -1 -``` - diff --git "a/Solutions/1095. \345\261\261\350\204\211\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\347\233\256\346\240\207\345\200\274.md" "b/Solutions/1095. \345\261\261\350\204\211\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\347\233\256\346\240\207\345\200\274.md" deleted file mode 100644 index 87b68c98..00000000 --- "a/Solutions/1095. \345\261\261\350\204\211\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\347\233\256\346\240\207\345\200\274.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [1095. 山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) - -- 标签:数组、二分查找、交互 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个山脉数组 $mountainArr$。 - -**要求**:返回能够使得 `mountainArr.get(index)` 等于 $target$ 最小的下标 $index$ 值。如果不存在这样的下标 $index$,就请返回 $-1$。 - -**说明**: - -- 山脉数组:满足以下属性的数组: - - - $len(arr) \ge 3$; - - 存在 $i$($0 < i < len(arr) - 1$),使得: - - $arr[0] < arr[1] < ... arr[i-1] < arr[i]$; - - $arr[i] > arr[i+1] > ... > arr[len(arr) - 1]$。 -- 不能直接访问该山脉数组,必须通过 `MountainArray` 接口来获取数据: - - - `MountainArray.get(index)`:会返回数组中索引为 $k$ 的元素(下标从 $0$ 开始)。 - - - `MountainArray.length()`:会返回该数组的长度。 -- 对 `MountainArray.get` 发起超过 $100$ 次调用的提交将被视为错误答案。 -- $3 \le mountain_arr.length() \le 10000$。 -- $0 \le target \le 10^9$。 -- $0 \le mountain_arr.get(index) \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:array = [1,2,3,4,5,3,1], target = 3 -输出:2 -解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。 -``` - -- 示例 2: - -```python -输入:array = [0,1,2,4,2,1], target = 3 -输出:-1 -解释:3 在数组中没有出现,返回 -1。 -``` - -## 解题思路 - -### 思路 1:二分查找 - -因为题目要求不能对 `MountainArray.get` 发起超过 $100$ 次调用。所以遍历数组进行查找是不可行的。 - -根据山脉数组的性质,我们可以把山脉数组分为两部分:「前半部分的升序数组」和「后半部分的降序数组」。在有序数组中查找目标值可以使用二分查找来减少查找次数。 - -而山脉的峰顶元素索引也可以通过二分查找来做。所以这道题我们可以分为三步: - -1. 通过二分查找找到山脉数组的峰顶元素索引。 -2. 通过二分查找在前半部分的升序数组中查找目标元素。 -3. 通过二分查找在后半部分的降序数组中查找目标元素。 - -最后,通过对查找结果的判断来输出最终答案。 - -### 思路 1:代码 - -```python -#class MountainArray: -# def get(self, index: int) -> int: -# def length(self) -> int: - -class Solution: - def binarySearchPeak(self, mountain_arr) -> int: - left, right = 0, mountain_arr.length() - 1 - while left < right: - mid = left + (right - left) // 2 - if mountain_arr.get(mid) < mountain_arr.get(mid + 1): - left = mid + 1 - else: - right = mid - return left - - def binarySearchAscending(self, mountain_arr, left, right, target): - while left < right: - mid = left + (right - left) // 2 - if mountain_arr.get(mid) < target: - left = mid + 1 - else: - right = mid - return left if mountain_arr.get(left) == target else -1 - - def binarySearchDescending(self, mountain_arr, left, right, target): - while left < right: - mid = left + (right - left) // 2 - if mountain_arr.get(mid) > target: - left = mid + 1 - else: - right = mid - return left if mountain_arr.get(left) == target else -1 - - def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int: - size = mountain_arr.length() - peek_i = self.binarySearchPeak(mountain_arr) - - res_left = self.binarySearchAscending(mountain_arr, 0, peek_i, target) - res_right = self.binarySearchDescending(mountain_arr, peek_i + 1, size - 1, target) - - return res_left if res_left != -1 else res_right -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1099. \345\260\217\344\272\216 K \347\232\204\344\270\244\346\225\260\344\271\213\345\222\214.md" "b/Solutions/1099. \345\260\217\344\272\216 K \347\232\204\344\270\244\346\225\260\344\271\213\345\222\214.md" deleted file mode 100644 index 8067f974..00000000 --- "a/Solutions/1099. \345\260\217\344\272\216 K \347\232\204\344\270\244\346\225\260\344\271\213\345\222\214.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [1099. 小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) - -- 标签:数组、双指针、二分查找、排序 -- 难度:简单 - -## 题目大意 - -给你一个整数数组 `nums` 和整数 `k`。 - -要求:返回最大和 `sum`,满足存在 `i < j` 使得 `nums[i] + nums[j] = sum` 且 `sum < k`。如果没有满足此等式的 `i`, `j` 存在,则返回 `-1`。 - -## 解题思路 - -常规暴力枚举时间复杂度为 $O(n^2)$。可以通过双指针降低时间复杂度。具体做法如下: - -- 先对数组进行排序(时间复杂度为 $O(n \log n$),使用 `res` 记录答案,初始赋值为最小值 `float('-inf')`。 -- 使用两个指针 `left`、`right`。`left` 指向第 `0` 个元素位置,`right` 指向数组的最后一个元素位置。 -- 计算 `nums[left] + nums[right]`,与 `k` 进行比较。 - - 如果 `nums[left] + nums[right] >= k`,则将 `right` 左移,继续查找。 - - 如果 `nums[left] + nums[rigth] < k`,则将 `left` 右移,并更新答案值。 -- 当 `left == right` 时,区间搜索完毕,判断 `res` 是否等于 `fload('-inf')`,如果等于,则返回 `-1`,否则返回 `res`。 - -## 代码 - -```python -class Solution: - def twoSumLessThanK(self, nums: List[int], k: int) -> int: - - nums.sort() - res = float('-inf') - left, right = 0, len(nums) - 1 - while left < right: - total = nums[left] + nums[right] - if total >= k: - right -= 1 - else: - res = max(res, total) - left += 1 - - return res if res != float('-inf') else -1 -``` - diff --git "a/Solutions/1100. \351\225\277\345\272\246\344\270\272 K \347\232\204\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\345\255\220\344\270\262.md" "b/Solutions/1100. \351\225\277\345\272\246\344\270\272 K \347\232\204\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\345\255\220\344\270\262.md" deleted file mode 100644 index 9848d465..00000000 --- "a/Solutions/1100. \351\225\277\345\272\246\344\270\272 K \347\232\204\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\345\255\220\344\270\262.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [1100. 长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:找出所有长度为 `k` 且不含重复字符的子串,返回全部满足要求的子串的数目。 - -## 解题思路 - -固定长度滑动窗口的题目。维护一个长度为 `k` 的滑动窗口。用 `window_count` 来表示窗口内所有字符个数。可以用字典、数组来实现,也可以直接用 `collections.Counter()` 实现。然后不断向右滑动,然后进行比较。如果窗口内字符无重复,则答案数目 + 1。然后继续滑动。直到末尾时。整个解题步骤具体如下: - -1. `window_count` 用来维护窗口中 `2` 对应子串的各个字符数量。 -2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 -4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,判断窗口内各个字符数量 `window_count` 是否等于 `k`。 - 1. 如果等于,则答案 + 1。 - 2. 如果不等于,则向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。返回答案。 - -## 代码 - -```python -import collections - -class Solution: - def numKLenSubstrNoRepeats(self, s: str, k: int) -> int: - left, right = 0, 0 - window_count = collections.Counter() - ans = 0 - - while right < len(s): - window_count[s[right]] += 1 - - if right - left + 1 >= k: - if len(window_count) == k: - ans += 1 - window_count[s[left]] -= 1 - if window_count[s[left]] == 0: - del window_count[s[left]] - left += 1 - - right += 1 - - return ans -``` - diff --git "a/Solutions/1103. \345\210\206\347\263\226\346\236\234 II.md" "b/Solutions/1103. \345\210\206\347\263\226\346\236\234 II.md" deleted file mode 100644 index 9a0c99d7..00000000 --- "a/Solutions/1103. \345\210\206\347\263\226\346\236\234 II.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [1103. 分糖果 II](https://leetcode.cn/problems/distribute-candies-to-people/) - -- 标签:数学、模拟 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $candies$,代表糖果的数量。再给定一个整数 $num\underline{}people$,代表小朋友的数量。 - -现在开始分糖果,给第 $1$ 个小朋友分 $1$ 颗糖果,第 $2$ 个小朋友分 $2$ 颗糖果,以此类推,直到最后一个小朋友分 $n$ 颗糖果。 - -然后回到第 $1$ 个小朋友,给第 $1$ 个小朋友分 $n + 1$ 颗糖果,第 $2$ 个小朋友分 $n + 2$ 颗糖果,一次类推,直到最后一个小朋友分 $n + n$ 颗糖果。 - -重复上述过程(每次都比上一次多给出 $1$ 颗糖果,当分完第 $n$ 个小朋友时回到第 $1$ 个小朋友),直到我们分完所有的糖果。 - -> 注意:如果我们手中剩下的糖果数不够(小于等于前一次发的糖果数),则将剩下的糖果全部发给当前的小朋友。 - -**要求**:返回一个长度为 $num\underline{}people$、元素之和为 $candies$ 的数组,以表示糖果的最终分发情况(即 $ans[i]$ 表示第 $i$ 个小朋友分到的糖果数)。 - -**说明**: - -- $1 \le candies \le 10^9$。 -- $1 \le num\underline{}people \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:candies = 7, num_people = 4 -输出:[1,2,3,1] -解释: -第一次,ans[0] += 1,数组变为 [1,0,0,0]。 -第二次,ans[1] += 2,数组变为 [1,2,0,0]。 -第三次,ans[2] += 3,数组变为 [1,2,3,0]。 -第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。 -``` - -- 示例 2: - -```python -输入:candies = 10, num_people = 3 -输出:[5,2,3] -解释: -第一次,ans[0] += 1,数组变为 [1,0,0]。 -第二次,ans[1] += 2,数组变为 [1,2,0]。 -第三次,ans[2] += 3,数组变为 [1,2,3]。 -第四次,ans[0] += 4,最终数组变为 [5,2,3]。 -``` - -## 解题思路 - -### 思路 1:暴力模拟 - -不断遍历数组,将对应糖果数分给当前小朋友,直到糖果数为 $0$ 时停止。 - -### 思路 1:代码 - -```python -class Solution: - def distributeCandies(self, candies: int, num_people: int) -> List[int]: - ans = [0 for _ in range(num_people)] - idx = 0 - while candies: - ans[idx % num_people] += min(idx + 1, candies) - candies -= min(idx + 1, candies) - idx += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(max(\sqrt{m}, n))$,其中 $m$ 为糖果数量,$n$ 为小朋友数量。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1108. IP \345\234\260\345\235\200\346\227\240\346\225\210\345\214\226.md" "b/Solutions/1108. IP \345\234\260\345\235\200\346\227\240\346\225\210\345\214\226.md" deleted file mode 100644 index e3d87ac8..00000000 --- "a/Solutions/1108. IP \345\234\260\345\235\200\346\227\240\346\225\210\345\214\226.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [1108. IP 地址无效化](https://leetcode.cn/problems/defanging-an-ip-address/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个有效的 IPv4 的地址 `address`。。 - -**要求**:返回这个 IP 地址的无效化版本。 - -**说明**: - -- **无效化 IP 地址**:其实就是用 `"[.]"` 代替了每个 `"."`。 - -**示例**: - -- 示例 1: - -```python -输入:address = "255.100.50.0" -输出:"255[.]100[.]50[.]0" -``` - -## 解题思路 - -### 思路 1:字符串替换 - -依次将字符串 `address` 中的 `"."` 替换为 `"[.]"`。这里为了方便,直接调用了 `replace` 方法。 - -### 思路 1:字符串替换代码 - -```python -class Solution: - def defangIPaddr(self, address: str) -> str: - return address.replace('.', '[.]') -``` diff --git "a/Solutions/1109. \350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.md" "b/Solutions/1109. \350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.md" deleted file mode 100644 index ae9b2688..00000000 --- "a/Solutions/1109. \350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.md" +++ /dev/null @@ -1,220 +0,0 @@ -# [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) - -- 标签:数组、前缀和 -- 难度:中等 - -## 题目大意 - -**描述**:给定整数 `n`,代表 `n` 个航班。再给定一个包含三元组的数组 `bookings`,代表航班预订表。表中第 `i` 条预订记录 $bookings[i] = [first_i, last_i, seats_i]$ 意味着在从 $first_i$ 到 $last_i$ (包含 $first_i$ 和 $last_i$)的 每个航班上预订了 $seats_i$ 个座位。 - -**要求**:返回一个长度为 `n` 的数组 `answer`,里面元素是每个航班预定的座位总数。 - -**说明**: - -- $1 \le n \le 2 * 10^4$。 -- $1 \le bookings.length \le 2 * 10^4$。 -- $bookings[i].length == 3$。 -- $1 \le first_i \le last_i \le n$。 -- $1 \le seats_i \le 10^4$ - -**示例**: - -- 示例 1: - -```python -给定 n = 5。初始 answer = [0, 0, 0, 0, 0] - -航班编号 1 2 3 4 5 -预订记录 1 : 10 10 -预订记录 2 : 20 20 -预订记录 3 : 25 25 25 25 -总座位数: 10 55 45 25 25 - -最终 answer = [10, 55, 45, 25, 25] -``` - -## 解题思路 - -### 思路 1:线段树 - -- 初始化一个长度为 `n`,值全为 `0` 的 `nums` 数组。 -- 然后根据 `nums` 数组构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。并且线段树使用延迟标记。 -- 然后遍历三元组操作,进行区间累加运算。 -- 最后从线段树中查询数组所有元素,返回该数组即可。 - -这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 - -### 思路 1 线段树代码: - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if self.tree[index].lazy_tag is not None: - self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val - else: - self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - interval_size = (right - left + 1) # 当前节点所在区间大小 - self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val - return - - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - self.__pushdown(index) # 向下更新节点的区间值 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if lazy_tag is None: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - if self.tree[left_index].lazy_tag is not None: - self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - self.tree[left_index].lazy_tag = lazy_tag - left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) - self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag - - if self.tree[right_index].lazy_tag is not None: - self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - self.tree[right_index].lazy_tag = lazy_tag - right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) - self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag - - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 - - -class Solution: - def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: - nums = [0 for _ in range(n)] - self.STree = SegmentTree(nums, lambda x, y: x + y) - for booking in bookings: - self.STree.update_interval(booking[0] - 1, booking[1] - 1, booking[2]) - - return self.STree.get_nums() -``` - diff --git "a/Solutions/1122. \346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.md" "b/Solutions/1122. \346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.md" deleted file mode 100644 index 42686a24..00000000 --- "a/Solutions/1122. \346\225\260\347\273\204\347\232\204\347\233\270\345\257\271\346\216\222\345\272\217.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [1122. 数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) - -- 标签:数组、哈希表、计数排序、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个数组,$arr1$ 和 $arr2$,其中 $arr2$ 中的元素各不相同,$arr2$ 中的每个元素都出现在 $arr1$ 中。 - -**要求**:对 $arr1$ 中的元素进行排序,使 $arr1$ 中项的相对顺序和 $arr2$ 中的相对顺序相同。未在 $arr2$ 中出现过的元素需要按照升序放在 $arr1$ 的末尾。 - -**说明**: - -- $1 \le arr1.length, arr2.length \le 1000$。 -- $0 \le arr1[i], arr2[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] -输出:[2,2,2,1,4,3,3,9,6,7,19] -``` - -- 示例 2: - -```python -输入:arr1 = [28,6,22,8,44,17], arr2 = [22,28,8,6] -输出:[22,28,8,6,17,44] -``` - -## 解题思路 - -### 思路 1:计数排序 - -因为元素值范围在 $[0, 1000]$,所以可以使用计数排序的思路来解题。 - -1. 使用数组 $count$ 统计 $arr1$ 各个元素个数。 -2. 遍历 $arr2$ 数组,将对应元素$num2$ 按照个数 $count[num2]$ 添加到答案数组 $ans$ 中,同时在 $count$ 数组中减去对应个数。 -3. 然后在处理 $count$ 中剩余元素,将 $count$ 中大于 $0$ 的元素下标依次添加到答案数组 $ans$ 中。 -4. 最后返回答案数组 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]: - # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min - arr1_min, arr1_max = min(arr1), max(arr1) - # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 - size = arr1_max - arr1_min + 1 - counts = [0 for _ in range(size)] - - # 统计值为 num 的元素出现的次数 - for num in arr1: - counts[num - arr1_min] += 1 - - res = [] - for num in arr2: - while counts[num - arr1_min] > 0: - res.append(num) - counts[num - arr1_min] -= 1 - - for i in range(size): - while counts[i] > 0: - num = i + arr1_min - res.append(num) - counts[i] -= 1 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n + max(arr_1))$。其中 $m$ 是数组 $arr_1$ 的长度,$n$ 是数组 $arr_2$ 的长度,$max(arr_1)$ 是数组 $arr_1$ 的最大值。 -- **空间复杂度**:$O(max(arr_1))$。 - - - diff --git "a/Solutions/1136. \345\271\266\350\241\214\350\257\276\347\250\213.md" "b/Solutions/1136. \345\271\266\350\241\214\350\257\276\347\250\213.md" deleted file mode 100644 index 2412ca76..00000000 --- "a/Solutions/1136. \345\271\266\350\241\214\350\257\276\347\250\213.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [1136. 并行课程](https://leetcode.cn/problems/parallel-courses/) - -- 标签:图、拓扑排序 -- 难度:中等 - -## 题目大意 - -有 N 门课程,分别以 1 到 N 进行编号。现在给定一份课程关系表 `relations[i] = [X, Y]`,用以表示课程 `X` 和课程 `Y` 之间的先修关系:课程 `X` 必须在课程 `Y` 之前修完。假设在一个学期里,你可以学习任何数量的课程,但前提是你已经学习了将要学习的这些课程的所有先修课程。 - -要求:返回学完全部课程所需的最少学期数。如果没有办法做到学完全部这些课程的话,就返回 `-1`。 - -## 解题思路 - -拓扑排序。具体解法如下: - -1. 使用列表 `edges` 存放课程关系图,并统计每门课程节点的入度,存入入度列表 `indegrees`。使用 `ans` 表示学期数。 -2. 借助队列 `queue`,将所有入度为 `0` 的节点入队。 -3. 将队列中所有节点依次取出,学期数 +1。对于取出的每个节点: - 1. 对应课程数 -1。 - 2. 将该顶点以及该顶点为出发点的所有边的另一个节点入度 -1。如果入度 -1 后的节点入度不为 0,则将其加入队列 `queue`。 -4. 重复 3~4 的步骤,直到队列中没有节点。 -5. 最后判断剩余课程数是否为 0,如果为 0,则返回 `ans`,否则,返回 `-1`。 - -## 代码 - -```python -import collections - -class Solution: - def minimumSemesters(self, n: int, relations: List[List[int]]) -> int: - indegrees = [0 for _ in range(n + 1)] - edges = collections.defaultdict(list) - for x, y in relations: - edges[x].append(y) - indegrees[y] += 1 - queue = collections.deque([]) - for i in range(1, n + 1): - if not indegrees[i]: - queue.append(i) - ans = 0 - - while queue: - size = len(queue) - for i in range(size): - x = queue.popleft() - n -= 1 - for y in edges[x]: - indegrees[y] -= 1 - if not indegrees[y]: - queue.append(y) - ans += 1 - - return ans if n == 0 else -1 -``` - diff --git "a/Solutions/1137. \347\254\254 N \344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.md" "b/Solutions/1137. \347\254\254 N \344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.md" deleted file mode 100644 index 23963918..00000000 --- "a/Solutions/1137. \347\254\254 N \344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.md" +++ /dev/null @@ -1,117 +0,0 @@ -# [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) - -- 标签:记忆化搜索、数学、动态规划 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回第 $n$ 个泰波那契数。 - -**说明**: - -- **泰波那契数**:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 -- $0 \le n \le 37$。 -- 答案保证是一个 32 位整数,即 $answer \le 2^{31} - 1$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:4 -解释: -T_3 = 0 + 1 + 1 = 2 -T_4 = 1 + 1 + 2 = 4 -``` - -- 示例 2: - -```python -输入:n = 25 -输出:1389537 -``` - -## 解题思路 - -### 思路 1:记忆化搜索 - -1. 问题的状态定义为:第 $n$ 个泰波那契数。其状态转移方程为:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 -2. 定义一个长度为 $n + 1$ 数组 `memo` 用于保存一斤个计算过的泰波那契数。 -3. 定义递归函数 `my_tribonacci(n, memo)`。 - 1. 当 $n = 0$ 或者 $n = 1$,或者 $n = 2$ 时直接返回结果。 - 2. 当 $n > 2$ 时,首先检查是否计算过 $T(n)$,即判断 $memo[n]$ 是否等于 $0$。 - 1. 如果 $memo[n] \ne 0$,说明已经计算过 $T(n)$,直接返回 $memo[n]$。 - 2. 如果 $memo[n] = 0$,说明没有计算过 $T(n)$,则递归调用 `my_tribonacci(n - 3, memo)`、`my_tribonacci(n - 2, memo)`、`my_tribonacci(n - 1, memo)`,并将计算结果存入 $memo[n]$ 中,并返回 $memo[n]$。 - -### 思路 1:代码 - -```python -class Solution: - def tribonacci(self, n: int) -> int: - # 使用数组保存已经求解过的 T(k) 的结果 - memo = [0 for _ in range(n + 1)] - return self.my_tribonacci(n, memo) - - def my_tribonacci(self, n: int, memo: List[int]) -> int: - if n == 0: - return 0 - if n == 1 or n == 2: - return 1 - - if memo[n] != 0: - return memo[n] - memo[n] = self.my_tribonacci(n - 3, memo) + self.my_tribonacci(n - 2, memo) + self.my_tribonacci(n - 1, memo) - return memo[n] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:动态规划 - -###### 1. 划分阶段 - -我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 - -###### 2. 定义状态 - -定义状态 `dp[i]` 为:第 `i` 个泰波那契数。 - -###### 3. 状态转移方程 - -根据题目中所给的泰波那契数的定义:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。,则直接得出状态转移方程为 $dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1]$(当 $i > 2$ 时)。 - -###### 4. 初始条件 - -根据题目中所给的初始条件 $T_0 = 0, T_1 = 1, T_2 = 1$ 确定动态规划的初始条件,即 `dp[0] = 0, dp[1] = 1, dp[2] = 1`。 - -###### 5. 最终结果 - -根据状态定义,最终结果为 `dp[n]`,即第 `n` 个泰波那契数为 `dp[n]`。 - -### 思路 2:代码 - -```python -class Solution: - def tribonacci(self, n: int) -> int: - if n == 0: - return 0 - if n == 1 or n == 2: - return 1 - dp = [0 for _ in range(n + 1)] - dp[1] = dp[2] = 1 - for i in range(3, n + 1): - dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1] - return dp[n] -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" "b/Solutions/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index b7ccac26..00000000 --- "a/Solutions/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,89 +0,0 @@ -# [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个字符串 $text1$ 和 $text2$。 - -**要求**:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 $0$。 - -**说明**: - -- **子序列**:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 -- **公共子序列**:两个字符串所共同拥有的子序列。 -- $1 \le text1.length, text2.length \le 1000$。 -- $text1$ 和 $text2$ 仅由小写英文字符组成。 - -**示例**: - -- 示例 1: - -```python -输入:text1 = "abcde", text2 = "ace" -输出:3 -解释:最长公共子序列是 "ace",它的长度为 3。 -``` - -- 示例 2: - -```python -输入:text1 = "abc", text2 = "abc" -输出:3 -解释:最长公共子序列是 "abc",它的长度为 3。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照两个字符串的结尾位置进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度为 $dp[i][j]$。 - -###### 3. 状态转移方程 - -双重循环遍历字符串 $text1$ 和 $text2$,则状态转移方程为: - -1. 如果 $text1[i - 1] = text2[j - 1]$,说明两个子字符串的最后一位是相同的,所以最长公共子序列长度加 $1$。即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 -2. 如果 $text1[i - 1] \ne text2[j - 1]$,说明两个子字符串的最后一位是不同的,则 $dp[i][j]$ 需要考虑以下两种情况,取两种情况中最大的那种:$dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$。 - 1. 「以 $text1$ 中前 $i - 1$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i - 1][j]$。 - 2. 「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j - 1$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i][j - 1]$。 - -###### 4. 初始条件 - -1. 当 $i = 0$ 时,$str1$ 表示的是空串,空串与 $str2$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 -2. 当 $j = 0$ 时,$str2$ 表示的是空串,$str1$ 与 空串的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 - -###### 5. 最终结果 - -根据状态定义,最后输出 $dp[sise1][size2]$(即 $text1$ 与 $text2$ 的最长公共子序列长度)即可,其中 $size1$、$size2$ 分别为 $text1$、$text2$ 的字符串长度。 - -### 思路 1:代码 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - size1 = len(text1) - size2 = len(text2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if text1[i - 1] == text2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - else: - dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - - return dp[size1][size2] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $text1$、$text2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 -- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 - diff --git "a/Solutions/1151. \346\234\200\345\260\221\344\272\244\346\215\242\346\254\241\346\225\260\346\235\245\347\273\204\345\220\210\346\211\200\346\234\211\347\232\204 1.md" "b/Solutions/1151. \346\234\200\345\260\221\344\272\244\346\215\242\346\254\241\346\225\260\346\235\245\347\273\204\345\220\210\346\211\200\346\234\211\347\232\204 1.md" deleted file mode 100644 index db5cb4a7..00000000 --- "a/Solutions/1151. \346\234\200\345\260\221\344\272\244\346\215\242\346\254\241\346\225\260\346\235\245\347\273\204\345\220\210\346\211\200\346\234\211\347\232\204 1.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [1151. 最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) - -- 标签:数组、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个二进制数组 `data`。 - -要求:通过交换位置,将数组中任何位置上的 `1` 组合到一起,并返回所有可能中所需的最少交换次数。 - -## 解题思路 - -将数组中任何位置上的 `1` 组合到一起,并要求最少的交换次数。也就是说交换之后,某个连续子数组中全是 `1`,数组其他位置全是 `0`。为此,我们可以维护一个固定长度为 `1` 的个数的滑动窗口,找到滑动窗口中 `0` 最少的个数,这样最终交换出去的 `0` 最少,交换次数也最少。 - -求最少交换次数,也就是求滑动窗口中最少的 `0` 的个数。具体做法如下: - -1. 统计 `1` 的个数,并设置为窗口长度 `window_size`。使用 `window_count` 维护窗口中 `0` 的个数。使用 `ans` 维护窗口中最少的 `0` 的个数,也可以叫做最少交换次数。 -2. 如果 `window_size` 为 `0`,则说明不用交换,直接返回 `0`。 -3. 使用两个指针 `left`、`right`。`left` 、`right` 都指向数组的第一个元素,即:`left = 0`,`right = 0`。 -4. 如果 `data[right] == 0`,则更新窗口中 `0` 的个数,即 `window_count += 1`。然后向右移动 `right`。 -5. 当窗口元素个数为 `window_size` 时,即:`right - left + 1 >= window_size` 时,更新窗口中最少的 `0` 的个数。 -6. 然后如果左侧 `data[left] == 0`,则更新窗口中 `0` 的个数,即 `window_count -= 1`。然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `window_size`。 -7. 重复 4 ~ 6 步,直到 `right` 到达数组末尾。返回答案 `ans`。 - -## 代码 - -```python -class Solution: - def minSwaps(self, data: List[int]) -> int: - window_size = 0 - for item in data: - if item == 1: - window_size += 1 - if window_size == 0: - return 0 - - left, right = 0, 0 - window_count = 0 - ans = float('inf') - while right < len(data): - if data[right] == 0: - window_count += 1 - - if right - left + 1 >= window_size: - ans = min(ans, window_count) - if data[left] == 0: - window_count -= 1 - left += 1 - right += 1 - return ans if ans != float('inf') else 0 -``` - diff --git "a/Solutions/1155. \346\216\267\351\252\260\345\255\220\347\255\211\344\272\216\347\233\256\346\240\207\345\222\214\347\232\204\346\226\271\346\263\225\346\225\260.md" "b/Solutions/1155. \346\216\267\351\252\260\345\255\220\347\255\211\344\272\216\347\233\256\346\240\207\345\222\214\347\232\204\346\226\271\346\263\225\346\225\260.md" deleted file mode 100644 index fb02bdbc..00000000 --- "a/Solutions/1155. \346\216\267\351\252\260\345\255\220\347\255\211\344\272\216\347\233\256\346\240\207\345\222\214\347\232\204\346\226\271\346\263\225\346\225\260.md" +++ /dev/null @@ -1,89 +0,0 @@ -# [1155. 掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) - -- 标签:动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:有 $n$ 个一样的骰子,每个骰子上都有 $k$ 个面,分别标号为 $1 \sim k$。现在给定三个整数 $n$、$k$ 和 $target$,滚动 $n$ 个骰子。 - -**要求**:计算出使所有骰子正面朝上的数字和等于 $target$ 的方案数量。 - -**说明**: - -- $1 \le n, k \le 30$。 -- $1 \le target \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 1, k = 6, target = 3 -输出:1 -解释:你扔一个有 6 个面的骰子。 -得到 3 的和只有一种方法。 -``` - -- 示例 2: - -```python -输入:n = 2, k = 6, target = 7 -输出:6 -解释:你扔两个骰子,每个骰子有 6 个面。 -得到 7 的和有 6 种方法 1+6 2+5 3+4 4+3 5+2 6+1。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -我们可以将这道题转换为「分组背包问题」中求方案总数的问题。将每个骰子看做是一组物品,骰子每一个面上的数值当做是每组物品中的一个物品。这样问题就转换为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $target$ 的方案数。 - -###### 1. 划分阶段 - -按照总价值 $target$ 进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数。 - -###### 3. 状态转移方程 - -用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数,等于用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w - d$ 的方案数累积值,其中 $d$ 为当前骰子掷出的价值,即:$dp[w] = dp[w] + dp[w - d]$。 - -###### 4. 初始条件 - -- 用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $0$ 的方案数为 $1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[w]$ 表示为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数。则最终结果为 $dp[target]$。 - -### 思路 1:代码 - -```python -class Solution: - def numRollsToTarget(self, n: int, k: int, target: int) -> int: - dp = [0 for _ in range(target + 1)] - dp[0] = 1 - MOD = 10 ** 9 + 7 - - # 枚举前 i 组物品 - for i in range(1, n + 1): - # 逆序枚举背包装载重量 - for w in range(target, -1, -1): - dp[w] = 0 - # 枚举第 i - 1 组物品能取个数 - for d in range(1, k + 1): - if w >= d: - dp[w] = (dp[w] + dp[w - d]) % MOD - - return dp[target] % MOD -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times m \times target)$。 -- **空间复杂度**:$O(target)$。 - diff --git "a/Solutions/1161. \346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.md" "b/Solutions/1161. \346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.md" deleted file mode 100644 index ddfd2d9e..00000000 --- "a/Solutions/1161. \346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [1161. 最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -**描述**:给你一个二叉树的根节点 $root$。设根节点位于二叉树的第 $1$ 层,而根节点的子节点位于第 $2$ 层,依此类推。 - -**要求**:返回层内元素之和最大的那几层(可能只有一层)的层号,并返回其中层号最小的那个。 - -**说明**: - -- 树中的节点数在 $[1, 10^4]$ 范围内。 -- $-10^5 \le Node.val \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/08/17/capture.jpeg) - -```python -输入:root = [1,7,0,7,-8,null,null] -输出:2 -解释: -第 1 层各元素之和为 1, -第 2 层各元素之和为 7 + 0 = 7, -第 3 层各元素之和为 7 + -8 = -1, -所以我们返回第 2 层的层号,它的层内元素之和最大。 -``` - -- 示例 2: - -```python -输入:root = [989,null,10250,98693,-89388,null,null,null,-32127] -输出:2 -``` - -## 解题思路 - -### 思路 1:二叉树的层序遍历 - -1. 利用广度优先搜索,在二叉树的层序遍历的基础上,统计每一层节点和,并存入数组 $levels$ 中。 -2. 遍历 $levels$ 数组,从 $levels$ 数组中找到最大层和 $max\underline{}sum$。 -3. 再次遍历 $levels$ 数组,找出等于最大层和 $max\underline{}sum$ 的那一层,并返回该层序号。 - -### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - levels = [] - while queue: - level = 0 - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - level += curr.val - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - levels.append(level) - return levels - - def maxLevelSum(self, root: Optional[TreeNode]) -> int: - levels = self.levelOrder(root) - max_sum = max(levels) - for i in range(len(levels)): - if levels[i] == max_sum: - return i + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1176. \345\201\245\350\272\253\350\256\241\345\210\222\350\257\204\344\274\260.md" "b/Solutions/1176. \345\201\245\350\272\253\350\256\241\345\210\222\350\257\204\344\274\260.md" deleted file mode 100644 index 9e00ed50..00000000 --- "a/Solutions/1176. \345\201\245\350\272\253\350\256\241\345\210\222\350\257\204\344\274\260.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [1176. 健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) - -- 标签:数组、滑动窗口 -- 难度:简单 - -## 题目大意 - -好友给自己制定了一份健身计划。想请你帮他评估一下这份计划是否合理。 - -给定一个数组 `calories`,其中 `calories[i]` 代表好友第 `i` 天需要消耗的卡路里总量。再给定 `lower` 代表较低消耗的卡路里,`upper` 代表较高消耗的卡路里。再给定一个整数 `k`,代表连续 `k` 天。 - -- 如果你的好友在这一天以及之后连续 `k` 天内消耗的总卡路里 `T` 小于 `lower`,则这一天的计划相对糟糕,并失去 `1` 分。 -- 如果你的好友在这一天以及之后连续 `k` 天内消耗的总卡路里 `T` 高于 `upper`,则这一天的计划相对优秀,并得到 `1` 分。 -- 如果你的好友在这一天以及之后连续 `k` 天内消耗的总卡路里 `T` 大于等于 `lower`,并且小于等于 `upper`,则这份计划普普通通,分值不做变动。 - -输出最后评估的得分情况。 - -## 解题思路 - -固定长度为 `k` 的滑动窗口题目。具体做法如下: - -1. `score` 用来维护得分情况,初始值为 `0`。`window_sum` 用来维护窗口中卡路里总量。 -2. `left` 、`right` 都指向数组的第一个元素,即:`left = 0`,`right = 0`。 -3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 -4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,计算窗口内的卡路里总量,并判断和 `upper`、`lower` 的关系。同时维护得分情况。 -5. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -6. 重复 4 ~ 5 步,直到 `right` 到达数组末尾。 - -最后输出得分情况 `score`。 - -## 代码 - -```python -class Solution: - def dietPlanPerformance(self, calories: List[int], k: int, lower: int, upper: int) -> int: - left, right = 0, 0 - window_sum = 0 - score = 0 - while right < len(calories): - window_sum += calories[right] - - if right - left + 1 >= k: - if window_sum < lower: - score -= 1 - elif window_sum > upper: - score += 1 - window_sum -= calories[left] - left += 1 - - right += 1 - return score -``` - diff --git "a/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" "b/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" deleted file mode 100644 index a7a1b4bd..00000000 --- "a/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [1184. 公交站间的距离](https://leetcode.cn/problems/distance-between-bus-stops/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:环形公交路线上有 $n$ 个站,序号为 $0 \sim n - 1$。给定一个数组 $distance$ 表示每一对相邻公交站之间的距离,其中 $distance[i]$ 表示编号为 $i$ 的车站与编号为 $(i + 1) \mod n$ 的车站之间的距离。再给定乘客的出发点编号 $start$ 和目的地编号 $destination$。 - -**要求**:返回乘客从出发点 $start$ 到目的地 $destination$ 之间的最短距离。 - -**说明**: - -- $1 \le n \le 10^4$。 -- $distance.length == n$。 -- $0 \le start, destination < n$。 -- $0 \le distance[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1.jpg) - -```python -输入:distance = [1,2,3,4], start = 0, destination = 1 -输出:1 -解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1-1.jpg) - -```python -输入:distance = [1,2,3,4], start = 0, destination = 2 -输出:3 -解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。 -``` - -## 解题思路 - -### 思路 1:简单模拟 - -1. 因为 $start$ 和 $destination$ 的先后顺序不影响结果,为了方便计算,我们先令 $start \le destination$。 -2. 遍历数组 $distance$,计算出 $[start, destination]$ 之间的距离和 $dist$。 -3. 计算出环形路线中 $[destination, start]$ 之间的距离和为 $sum(distance) - dist$。 -4. 比较 $2 \sim 3$ 中两个距离的大小,将距离最小值作为答案返回。 - -### 思路 1:代码 - -```python -class Solution: - def distanceBetweenBusStops(self, distance: List[int], start: int, destination: int) -> int: - start, destination = min(start, destination), max(start, destination) - dist = 0 - for i in range(len(distance)): - if start <= i < destination: - dist += distance[i] - - return min(dist, sum(distance) - dist) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1202. \344\272\244\346\215\242\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\347\264\240.md" "b/Solutions/1202. \344\272\244\346\215\242\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\347\264\240.md" deleted file mode 100644 index 48683d57..00000000 --- "a/Solutions/1202. \344\272\244\346\215\242\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\347\264\240.md" +++ /dev/null @@ -1,103 +0,0 @@ -# [1202. 交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) - -- 标签:深度优先搜索、广度优先搜索、并查集、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s`,再给定一个数组 `pairs`,其中 `pairs[i] = [a, b]` 表示字符串的第 `a` 个字符可以跟第 `b` 个字符交换。只要满足 `pairs` 中的交换关系,可以任意多次交换字符串中的字符。 - -**要求**:返回 `s` 经过若干次交换之后,可以变成的字典序最小的字符串。 - -**说明**: - -- $1 \le s.length \le 10^5$。 -- $0 \le pairs.length \le 10^5$。 -- $0 \le pairs[i][0], pairs[i][1] < s.length$。 -- `s` 中只含有小写英文字母。 - -**示例**: - -- 示例 1: - -```python -输入:s = "dcab", pairs = [[0,3],[1,2]] -输出:"bacd" -解释: -交换 s[0] 和 s[3], s = "bcad" -交换 s[1] 和 s[2], s = "bacd" -``` - -- 示例 2: - -```python -输入:s = "dcab", pairs = [[0,3],[1,2],[0,2]] -输出:"abcd" -解释: -交换 s[0] 和 s[3], s = "bcad" -交换 s[0] 和 s[2], s = "acbd" -交换 s[1] 和 s[2], s = "abcd" -``` - -## 解题思路 - -### 思路 1:并查集 - -如果第 `a` 个字符可以跟第 `b` 个字符交换,第 `b` 个字符可以跟第 `c` 个字符交换,那么第 `a` 个字符、第 `b` 个字符、第 `c` 个字符之间就可以相互交换。我们可以把可以相互交换的「位置」都放入一个集合中。然后对每个集合中的字符进行排序。然后将其放置回在字符串中原有位置即可。 - -### 思路 1:代码 - -```python -import collections - -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str: - size = len(s) - union_find = UnionFind(size) - for pair in pairs: - union_find.union(pair[0], pair[1]) - mp = collections.defaultdict(list) - - for i, ch in enumerate(s): - mp[union_find.find(i)].append(ch) - - for vec in mp.values(): - vec.sort(reverse=True) - - ans = [] - for i in range(size): - x = union_find.find(i) - ans.append(mp[x][-1]) - mp[x].pop() - - return "".join(ans) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2 n + m * \alpha(n))$。其中 $n$ 是字符串的长度,$m$ 为 $pairs$ 的索引对数量,$\alpha$ 是反 `Ackerman` 函数。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" "b/Solutions/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" deleted file mode 100644 index 4af7af24..00000000 --- "a/Solutions/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [1208. 尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) - -- 标签:字符串、二分查找、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定两个长度相同的字符串,`s` 和 `t`。将 `s` 中的第 `i` 个字符变到 `t` 中的第 `i` 个字符需要 $| s[i] - t[i] |$ 的开销(开销可能为 `0`),也就是两个字符的 ASCII 码值的差的绝对值。用于变更字符串的最大预算是 `maxCost`。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。 - -要求:如果你可以将 `s` 的子字符串转化为它在 `t` 中对应的子字符串,则返回可以转化的最大长度。如果 `s` 中没有子字符串可以转化成 `t` 中对应的子字符串,则返回 `0`。 - -## 解题思路 - -维护一个滑动窗口 `window_sum` 用于记录窗口内的开销总和,保证窗口内的开销总和小于等于 `maxCost`。使用 `ans` 记录可以转化的最大长度。具体做法如下: - -使用两个指针 `left`、`right`。分别指向滑动窗口的左右边界,保证窗口内所有元素转化开销总和小于等于 `maxCost`。 - -- 先统计出 `s` 中第 `i` 个字符变为 `t` 的第 `i` 个字符的开销,用数组 `costs` 保存。 -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧字符的转变开销填入窗口中,向右移动 `right`。 -- 直到窗口内开销总和 `window_sum` 大于 `maxCost`。则不断右移 `left`,缩小窗口长度。直到 `window_sum <= maxCost` 时,更新可以转换的最大长度 `ans`。 -- 向右移动 `right`,直到 `right >= len(s)` 为止。 -- 输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def equalSubstring(self, s: str, t: str, maxCost: int) -> int: - size = len(s) - costs = [0 for _ in range(size)] - for i in range(size): - costs[i] = abs(ord(s[i]) - ord(t[i])) - - left, right = 0, 0 - ans = 0 - window_sum = 0 - while right < size: - window_sum += costs[right] - while window_sum > maxCost: - window_sum -= costs[left] - left += 1 - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - diff --git "a/Solutions/1217. \347\216\251\347\255\271\347\240\201.md" "b/Solutions/1217. \347\216\251\347\255\271\347\240\201.md" deleted file mode 100644 index d75ab4ef..00000000 --- "a/Solutions/1217. \347\216\251\347\255\271\347\240\201.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [1217. 玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) - -- 标签:贪心、数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 `position` 代表 `n` 个筹码的位置,其中 `position[i]` 代表第 `i` 个筹码的位置。现在需要把所有筹码移到同一个位置。在一步中,我们可以将第 `i` 个芯片的位置从 `position[i]` 改变为: - -- `position[i] + 2` 或 `position[i] - 2`,此时 `cost = 0`; -- `position[i] + 1` 或 `position[i] - 1`,此时 `cost = 1`。 - -即移动偶数位长度的代价为 `0`,移动奇数位长度的代价为 `1`。 - -**要求**:返回将所有筹码移动到同一位置上所需要的 最小代价 。 - -**说明**: - -- $1 \le chips.length \le 100$。 -- $1 \le chips[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:position = [2,2,2,3,3] -输出:2 -解释:我们可以把位置3的两个芯片移到位置 2。每一步的成本为 1。总成本 = 2。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -题目中移动偶数位长度是不需要代价的,所以奇数位移动到奇数位不需要代价,偶数位移动到偶数位也不需要代价。 - -则我们可以想将所有偶数位都移动到下标为 `0` 的位置,奇数位都移动到下标为 `1` 的位置。 - -这样,所有的奇数位、偶数位上的人都到相同或相邻位置了。 - -我们只需要统计一下奇数位和偶数位的数字个数。将少的数移动到多的数上边就是最小代价。 - -则这道题就可以通过以下步骤求解: - -- 遍历数组,统计数组中奇数个数和偶数个数。 -- 返回奇数个数和偶数个数中较小的数即为答案。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def minCostToMoveChips(self, position: List[int]) -> int: - odd, even = 0, 0 - for p in position: - if p & 1: - odd += 1 - else: - even += 1 - return min(odd, even) -``` diff --git "a/Solutions/1220. \347\273\237\350\256\241\345\205\203\351\237\263\345\255\227\346\257\215\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" "b/Solutions/1220. \347\273\237\350\256\241\345\205\203\351\237\263\345\255\227\346\257\215\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index 169f00f7..00000000 --- "a/Solutions/1220. \347\273\237\350\256\241\345\205\203\351\237\263\345\255\227\346\257\215\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [1220. 统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) - -- 标签:动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 `n`,我们可以按照以下规则生成长度为 `n` 的字符串: - -- 字符串中的每个字符都应当是小写元音字母(`'a'`、`'e'`、`'i'`、`'o'`、`'u'`)。 -- 每个元音 `'a'` 后面都只能跟着 `'e'`。 -- 每个元音 `'e'` 后面只能跟着 `'a'` 或者是 `'i'`。 -- 每个元音 `'i'` 后面不能再跟着另一个 `'i'`。 -- 每个元音 `'o'` 后面只能跟着 `'i'` 或者是 `'u'`。 -- 每个元音 `'u'` 后面只能跟着 `'a'`。 - -**要求**:统计一下我们可以按上述规则形成多少个长度为 `n` 的字符串。由于答案可能会很大,所以请返回模 $10^9 + 7$ 之后的结果。 - -**说明**: - -- $1 \le n \le 2 * 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:10 -解释:所有可能的字符串分别是:"ae", "ea", "ei", "ia", "ie", "io", "iu", "oi", "ou" 和 "ua"。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -根据题目给定的字符串规则,我们可以将其整理一下: - -- 元音字母 `'a'` 前面只能跟着 `'e'`、`'i'`、`'u'`。 -- 元音字母 `'e'` 前面只能跟着 `'a'`、`'i'`。 -- 元音字母 `'i'` 前面只能跟着 `'e'`、`'o'`。 -- 元音字母 `'o'` 前面只能跟着 `'i'`。 -- 元音字母 `'u'` 前面只能跟着 `'o'`、`'i'`。 - -现在我们可以按照字符串的长度以及字符结尾进行阶段划分,并按照上述规则推导状态转移方程。 - -###### 1. 划分阶段 - -按照字符串的结尾位置和结尾位置上的字符进行阶段划分。 - -###### 2. 定义状态 - -定义状态 `dp[i][j]` 表示为:长度为 `i` 并且以字符 `j` 结尾的字符串数量。这里 $j = 0, 1, 2, 3, 4$ 分别代表元音字母 `'a'`、`'e'`、`'i'`、`'o'`、`'u'`。 - -###### 3. 状态转移方程 - -通过上面的字符规则,可以得到状态转移方程为: - - -$\begin{cases} dp[i][0] = dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4] \cr dp[i][1] = dp[i - 1][0] + dp[i - 1][2] \cr dp[i][2] = dp[i - 1][1] + dp[i - 1][3] \cr dp[i][3] = dp[i - 1][2] \cr dp[i][4] = dp[i - 1][2] + dp[i - 1][3] \end{cases}$ - -###### 4. 初始条件 - -- 长度为 `1` 并且以字符 `j` 结尾的字符串数量为 `1`,即 `dp[1][j] = 1`。 - -###### 5. 最终结果 - -根据我们之前定义的状态,`dp[i]` 表示为:长度为 `i` 并且以字符 `j` 结尾的字符串数量。则将 `dp[n]` 行所有列相加,就是长度为 `n` 的字符串数量。 - -### 思路 1:动态规划代码 - -```python -class Solution: - def countVowelPermutation(self, n: int) -> int: - mod = 10 ** 9 + 7 - dp = [[0 for _ in range(5)] for _ in range(n + 1)] - - for j in range(5): - dp[1][j] = 1 - - for i in range(2, n + 1): - dp[i][0] = (dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4]) % mod - dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod - dp[i][2] = (dp[i - 1][1] + dp[i - 1][3]) % mod - dp[i][3] = dp[i - 1][2] % mod - dp[i][4] = (dp[i - 1][2] + dp[i - 1][3]) % mod - - ans = 0 - for j in range(5): - ans += dp[n][j] % mod - ans %= mod - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1227. \351\243\236\346\234\272\345\272\247\344\275\215\345\210\206\351\205\215\346\246\202\347\216\207.md" "b/Solutions/1227. \351\243\236\346\234\272\345\272\247\344\275\215\345\210\206\351\205\215\346\246\202\347\216\207.md" deleted file mode 100644 index ec48ac3e..00000000 --- "a/Solutions/1227. \351\243\236\346\234\272\345\272\247\344\275\215\345\210\206\351\205\215\346\246\202\347\216\207.md" +++ /dev/null @@ -1,117 +0,0 @@ -# [1227. 飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) - -- 标签:脑筋急转弯、数学、动态规划、概率与统计 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$,代表 $n$ 位乘客即将登飞机。飞机上刚好有 $n$ 个座位。第一位乘客的票丢了,他随便选择了一个座位坐下。则剩下的乘客将会: - -- 如果自己的座位还空着,就坐到自己的座位上。 -- 如果自己的座位被占用了,就随机选择其他座位。 - -**要求**:计算出第 $n$ 位乘客坐在自己座位上的概率是多少。 - -**说明**: - -- $1 \le n \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 1 -输出:1.00000 -解释:第一个人只会坐在自己的位置上。 -``` - -- 示例 2: - -```python -输入: n = 2 -输出: 0.50000 -解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。 -``` - -## 解题思路 - -### 思路 1:数学 - -我们按照乘客的登机顺序为乘客编下号:$1 \sim n$,我们用 $f(n)$ 来表示第 $n$ 位乘客登机时,坐在自己座位上的概率。先从简单的情况开始考虑: - -当 $n = 1$ 时: - -- 第 $1$ 位乘客只能坐在第 $1$ 个座位上,$f(1) = 1$。 - -当 $n = 2$ 时: - -- 第 $1$ 位乘客有 $\frac{1}{2}$ 的概率选中自己的位置,第 $2$ 位乘客一定能坐到自己的位置上,则第 $2$ 位乘客坐在自己座位上的概率为 $\frac{1}{2} * 1.0$。 -- 第 $1$ 位乘客有 $\frac{1}{2}$ 的概率坐在第 $2$ 位乘客的位置上,第 $2$ 位乘客只能坐到第 $1$ 位乘客的位置上,那么第 $2$ 位乘客坐在自己座位上的概率为 $\frac{1}{2} * 0.0$。 -- 综上,$f(2) = \frac{1}{2} * 1.0 + \frac{1}{2} * 0.0 = 0.5$。 - -当 $n \ge 3$ 时: - -- 先来考虑第 $1$ 位乘客登机情况: - - - 第 $1$ 位乘客有 $\frac{1}{n}$ 的概率选择坐在自己位置上,这样第 $1$ 位到第 $n - 1$ 位乘客的座位都不会被占,第 n 位乘客一定能坐到自己位置上。那么第 n 位乘客坐在自己座位上的概率为 $\frac{1}{n} * 1.0$。 - - - 第 $1$ 位乘客有 $\frac{1}{n}$ 的概率选择坐在第 $n$ 位乘客的位置上,这样第 $2$ 位到第 $n - 1$ 位乘客的座位都不会被占,第 $n$ 位乘客只能坐到第 $1$ 位乘客的位置上,那么第 $n$ 位乘客坐在自己座位上的概率为 $\frac{1}{n} * 0.0$。 - - - 第 $1$ 位乘客有 $\frac{n-2}{n}$ 的概率坐在第 $i$ 号座位上,$2 \le i \le n - 1$,每个座位被选中概率为 $\frac{1}{n}$。这样第 $2$ 位到第 $i - 1$ 位乘客的座位都不会被占。此时第 $i$ 位乘客,会在剩下的 $n - (i - 1)$ 个座位中进行选择: - - - 坐在第 $1$ 位乘客的位置上,这样后面的乘客座位都不会被占,第 $n$ 位乘客一定能坐到自己位置上。 - - - 坐在第 $n$ 个乘客的位置上,这样第 $n$ 个乘客肯定无法坐到自己的位置上。 - - - 在第 $[i + 1, n - 1]$ 之间找个位置坐。 - -- 再来考虑第 $i$ 位乘客登机情况: - - 第 $i$ 为乘客所面临的情况跟第 $1$ 位乘客所面临的情况类似,只不过问题的规模数从 $n$ 减小到了 $n - (i - 1)$。 - -那么综合上面情况,可以得到 $f(n),(n \ge 3)$ 的递推式: - -$\begin{aligned} f(n) & = \frac{1}{n} * 1.0 + \frac{1}{n} * 0.0 + \frac{1}{n} * \sum_{i = 2}^{n-1} f(n - i + 1) \cr & = \frac{1}{n} (1.0 + \sum_{i = 2}^{n-1} f(n - i + 1)) \end{aligned}$ - -接下来我们从等式中寻找规律,消去 $\sum_{i = 2}^{n-1} f(n - i + 1)$ 部分。 - -将 $n$ 换为 $n - 1$,得: - -$\begin{aligned} f(n - 1) & = \frac{1}{n - 1} * 1.0 + \frac{1}{n - 1} * 0.0 + \frac{1}{n - 1} * \sum_{i = 2}^{n-2} f(n - i) \cr & = \frac{1}{n - 1} (1.0 + \sum_{i = 2}^{n-2} f(n - i)) \end{aligned} $ - -将 $f(n) * n$ 与 $f(n - 1) * (n - 1)$ 进行比较: - -$\begin{aligned} f(n) * n & = 1.0 + \sum_{i = 2}^{n-1} f(n - i + 1) & (1) \cr f(n - 1) * (n - 1) & = 1.0 + \sum_{i = 2}^{n-2} f(n - i) & (2) \end{aligned}$ - -将上述 (1)、(2) 式相减得: - -$\begin{aligned} & f(n) * n - f(n - 1) * (n - 1) & \cr = & \sum_{i = 2}^{n-1} f(n - i + 1) - \sum_{i = 2}^{n-2} f(n - i) \cr = & f(n-1) \end{aligned}$ - -整理后得:$f(n) = f(n - 1)$。 - -已知 $f(1) = 1$,$f(2) = 0.5$,因此当 $n \ge 3$ 时,$f(n) = 0.5$。 - -所以可以得出结论: - -$f(n) = \begin{cases} 1.0 & n = 1 \cr 0.5 & n \ge 2 \end{cases}$ - -### 思路 1:代码 - -```python -class Solution: - def nthPersonGetsNthSeat(self, n: int) -> float: - if n == 1: - return 1.0 - else: - return 0.5 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- [飞机座位分配概率 - 力扣(LeetCode)](https://leetcode.cn/problems/airplane-seat-assignment-probability/solution/fei-ji-zuo-wei-fen-pei-gai-lu-by-leetcod-gyw4/) - diff --git "a/Solutions/1229. \345\256\211\346\216\222\344\274\232\350\256\256\346\227\245\347\250\213.md" "b/Solutions/1229. \345\256\211\346\216\222\344\274\232\350\256\256\346\227\245\347\250\213.md" deleted file mode 100644 index 51210c99..00000000 --- "a/Solutions/1229. \345\256\211\346\216\222\344\274\232\350\256\256\346\227\245\347\250\213.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [1229. 安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -给定两位客户的空闲时间表:`slots1` 和 `slots2`,再给定会议的预计持续时间 `duration`。 - -其中 `slots1[i] = [start_i, end_i]` 表示空闲时间第从 `start_i` 开始,到 `end_i` 结束。 `slots2` 也是如此。 - -要求:为他们安排合适的会议时间,如果有合适的会议时间,则返回该时间的起止时刻。如果没有满足要求的会议时间,就请返回一个 空数组。 - -- 会议时间:两位客户都有空参加,并且持续时间能够满足预计时间 `duration` 的最早的时间间隔。 - -注意: 题目保证数据有效。同一个人的空闲时间不会出现交叠的情况,也就是说,对于同一个人的两个空闲时间 `[start1, end1]` 和 `[start2, end2]`,要么 `start1 > end2`,要么 `start2 > end1`。 - -## 解题思路 - -题目保证了同一个人的空闲时间不会出现交叠。那么可以先直接对两个客户的空间时间表按照开始时间从小到大排序。然后使用分离双指针来遍历两个数组,求出重合部分,并判断重合区间是否大于等于 `duration`。具体做法如下: - -- 先对两个数组排序。使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组开始位置,`left_2` 指向第二个数组开始位置。 -- 遍历两个数组。计算当前两个空闲时间区间的重叠范围。 - - 如果重叠范围大于等于 `duration`,直接返回当前重叠范围开始时间和会议结束时间,即 `[start, start + duration]`,`start` 为重叠范围开始时间。 - - 如果第一个客户的空闲结束时间小于第二个客户的空闲结束时间,则令 `left_1` 右移,即 `left_1 += 1`,继续比较重叠范围。 - - 如果第一个客户的空闲结束时间大于等于第二个客户的空闲结束时间,则令 `left_2` 右移,即 `left_2 += 1`,继续比较重叠范围。 -- 直到 `left_1 == len(slots1)` 或者 `left_2 == len(slots2)` 时跳出循环,返回空数组 `[]`。 - -## 代码 - -```python -class Solution: - def minAvailableDuration(self, slots1: List[List[int]], slots2: List[List[int]], duration: int) -> List[int]: - slots1.sort() - slots2.sort() - size1 = len(slots1) - size2 = len(slots2) - left_1, left_2 = 0, 0 - while left_1 < size1 and left_2 < size2: - start_1, end_1 = slots1[left_1] - start_2, end_2 = slots2[left_2] - start = max(start_1, start_2) - end = min(end_1, end_2) - if end - start >= duration: - return [start, start + duration] - if end_1 < end_2: - left_1 += 1 - else: - left_2 += 1 - return [] -``` - diff --git "a/Solutions/1232. \347\274\200\347\202\271\346\210\220\347\272\277.md" "b/Solutions/1232. \347\274\200\347\202\271\346\210\220\347\272\277.md" deleted file mode 100644 index f853fda5..00000000 --- "a/Solutions/1232. \347\274\200\347\202\271\346\210\220\347\272\277.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [1232. 缀点成线](https://leetcode.cn/problems/check-if-it-is-a-straight-line/) - -- 标签:几何、数组、数学 -- 难度:简单 - -## 题目大意 - -给定一系列的二维坐标点的坐标 `(xi, yi)`,判断这些点是否属于同一条直线。若属于同一条直线,则返回 True,否则返回 False。 - -## 解题思路 - -如果根据斜率来判断点是否处于同一条直线,需要处理斜率不存在(无穷大)的情况。我们可以使用叉乘来判断三个点构成的两个向量是否处于同一条直线上。 - -叉乘原理: - -设向量 P 为 `(x1, y1)` 向量,Q 为 `(x2, y2)`,则向量 P、Q 的叉积定义为:$P × Q = x_1y_2 - x_2y_1$,其几何意义表示为如果以向量 P 和向量 Q 为边构成一个平行四边形,那么这两个向量叉乘的模长与这个平行四边形的正面积相等。 - -![向量叉积](https://img.geek-docs.com/mathematical-basis/linear-algebra/220px-Cross_product_parallelogram.png) - -- 如果 `P × Q = 0`,则 P 与 Q 共线,有可能同向,也有可能反向。 -- 如果 `P × Q > 0`,则 P 在 Q 的顺时针方向。 -- 如果 `P × Q < 0`,则 P 在 Q 的逆时针方向。 - -具体求解方法: - -- 先求出第一个坐标与第二个坐标构成的向量 P。 -- 遍历所有坐标,求出所有坐标与第一个坐标构成的向量 Q。 - - 如果 `P × Q ≠ 0`,则返回 False。 -- 如果遍历完仍没有发现 `P × Q ≠ 0`,则返回 True。 - -## 代码 - -```python -class Solution: - def checkStraightLine(self, coordinates: List[List[int]]) -> bool: - x1 = coordinates[1][0] - coordinates[0][0] - y1 = coordinates[1][1] - coordinates[0][1] - - for i in range(len(coordinates)): - x2 = coordinates[i][0] - coordinates[0][0] - y2 = coordinates[i][1] - coordinates[0][1] - if x1 * y2 != x2 * y1: - return False - return True -``` - diff --git "a/Solutions/1245. \346\240\221\347\232\204\347\233\264\345\276\204.md" "b/Solutions/1245. \346\240\221\347\232\204\347\233\264\345\276\204.md" deleted file mode 100644 index eb021c57..00000000 --- "a/Solutions/1245. \346\240\221\347\232\204\347\233\264\345\276\204.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [1245. 树的直径](https://leetcode.cn/problems/tree-diameter/) - -- 标签:树、深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $edges$,用来表示一棵无向树。其中 $edges[i] = [u, v]$ 表示节点 $u$ 和节点 $v$ 之间的双向边。书上的节点编号为 $0 \sim edges.length$,共 $edges.length + 1$ 个节点。 - -**要求**:求出这棵无向树的直径。 - -**说明**: - -- $0 \le edges.length < 10^4$。 -- $edges[i][0] \ne edges[i][1]$。 -- $0 \le edges[i][j] \le edges.length$。 -- $edges$ 会形成一棵无向树。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/10/31/1397_example_1.png) - -```python -输入:edges = [[0,1],[0,2]] -输出:2 -解释: -这棵树上最长的路径是 1 - 0 - 2,边数为 2。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/10/31/1397_example_2.png) - -```python -输入:edges = [[0,1],[1,2],[2,3],[1,4],[4,5]] -输出:4 -解释: -这棵树上最长的路径是 3 - 2 - 1 - 4 - 5,边数为 4。 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -对于根节点为 $u$ 的树来说: - -1. 如果其最长路径经过根节点 $u$,则:**最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 -2. 如果其最长路径不经过根节点 $u$,则:**最长路径长度 = 某个子树中的最长路径长度**。 - -即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 - -对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{}len$。 - -1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{}len$。 -2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{}len + v\underline{}len + 1)$。 -3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{}len = max(u\underline{}len, \quad v\underline{}len + 1)$。 - -> 注意:在遍历邻接节点的过程中,为了避免造成重复遍历,我们在使用深度优先搜索时,应过滤掉父节点。 - -### 思路 1:代码 - -```python -class Solution: - def __init__(self): - self.ans = 0 - - def dfs(self, graph, u, fa): - u_len = 0 - for v in graph[u]: - if v != fa: - v_len = self.dfs(graph, v, u) - self.ans = max(self.ans, u_len + v_len + 1) - u_len = max(u_len, v_len + 1) - return u_len - - def treeDiameter(self, edges: List[List[int]]) -> int: - size = len(edges) + 1 - - graph = [[] for _ in range(size)] - for u, v in edges: - graph[u].append(v) - graph[v].append(u) - - self.dfs(graph, 0, -1) - return self.ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为无向树中的节点个数。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/1247. \344\272\244\346\215\242\345\255\227\347\254\246\344\275\277\345\276\227\345\255\227\347\254\246\344\270\262\347\233\270\345\220\214.md" "b/Solutions/1247. \344\272\244\346\215\242\345\255\227\347\254\246\344\275\277\345\276\227\345\255\227\347\254\246\344\270\262\347\233\270\345\220\214.md" deleted file mode 100644 index 42b0256c..00000000 --- "a/Solutions/1247. \344\272\244\346\215\242\345\255\227\347\254\246\344\275\277\345\276\227\345\255\227\347\254\246\344\270\262\347\233\270\345\220\214.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [1247. 交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) - -- 标签:贪心、数学、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定两个长度相同的字符串 `s1` 和 `s2`,并且两个字符串中只含有字符 `'x'` 和 `'y'`。现在需要通过「交换字符」的方式使两个字符串相同。 - -- 每次「交换字符」,需要分别从两个字符串中各选一个字符进行交换。 -- 「交换字符」只能发生在两个不同的字符串之间,不能发生在同一个字符串内部。 - -**要求**:返回使 `s1` 和 `s2` 相同的最小交换次数,如果没有方法能够使得这两个字符串相同,则返回 `-1`。 - -**说明**: - -- $1 \le s1.length, s2.length \le 1000$。 -- `s1`、` s2` 只包含 `'x'` 或 `'y'`。 - -**示例**: - -- 示例 1: - -```python -输入:s1 = "xy", s2 = "yx" -输出:2 -解释: -交换 s1[0] 和 s2[0],得到 s1 = "yy",s2 = "xx" 。 -交换 s1[0] 和 s2[1],得到 s1 = "xy",s2 = "xy" 。 -注意,你不能交换 s1[0] 和 s1[1] 使得 s1 变成 "yx",因为我们只能交换属于两个不同字符串的字符。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -- 如果 `s1 == s2`,则不需要交换。 -- 如果 `s1 = "xx"`,`s2 = "yy"`,则最少需要交换一次,才可以使两个字符串相等。 -- 如果 `s1 = "yy"`,`s2 = "xx"`,则最少需要交换一次,才可以使两个字符串相等。 -- 如果 `s1 = "xy"`,`s2 = "yx"`,则最少需要交换两次,才可以使两个字符串相等。 -- 如果 `s1 = "yx"`,`s2 = "xy"`,则最少需要交换两次,才可以使两个字符串相等。 - -则可以总结为: - -- `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"` 只需要交换一次。 -- `"xy"` 与 `"yx"`、`"yx"` 与 `"xy"` 需要交换两次。 - -我们把这两种情况分别进行统计。 - -- 当遇到 `s1[i] == s2[i]` 时直接跳过。 -- 当遇到 `s1[i] == 'x'`,`s2[i] == 'y'` 时,则统计数量到变量 `xyCnt` 中。 -- 当遇到 `s1[i] == 'y'`,`s2[i] == 'y'` 时,则统计数量到变量 `yxCnt` 中。 - -则最后我们只需要判断 `xyCnt` 和 `yxCnt` 的个数即可。 - -- 如果 `xyCnt + yxCnt` 是奇数,则说明最终会有一个位置上的两个字符无法通过交换相匹配。 -- 如果 `xyCnt + yxCnt` 是偶数,并且 `xyCnt` 为偶数,则 `yxCnt` 也为偶数。则优先交换 `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"`。即每两个 `xyCnt` 对应一次交换,每两个 `yxCnt` 对应交换一次,则结果为 `xyCnt // 2 + yxCnt // 2`。 -- 如果 `xyCnt + yxCnt` 是偶数,并且 `xyCnt` 为奇数,则 `yxCnt` 也为奇数。则优先交换 `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"`。即每两个 `xyCnt` 对应一次交换,每两个 `yxCnt` 对应交换一次,则结果为 `xyCnt // 2 + yxCnt // 2`。最后还剩一组 `"xy"` 与 `"yx"` 或者 `"yx"` 与 `"xy"`,则再交换一次,则结果为 `xyCnt // 2 + yxCnt // 2 + 2`。 - -以上结果可以统一写成 `xyCnt // 2 + yxCnt // 2 + xyCnt % 2 * 2`。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def minimumSwap(self, s1: str, s2: str) -> int: - xyCnt, yxCnt = 0, 0 - for i in range(len(s1)): - if s1[i] == s2[i]: - continue - if s1[i] == 'x': - xyCnt += 1 - else: - yxCnt += 1 - - if (xyCnt + yxCnt) & 1: - return -1 - return xyCnt // 2 + yxCnt // 2 + (xyCnt % 2 * 2) -``` diff --git "a/Solutions/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.md" "b/Solutions/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index a23bab48..00000000 --- "a/Solutions/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二维矩阵 `grid`,每个位置要么是陆地(记号为 `0`)要么是水域(记号为 `1`)。 - -我们从一块陆地出发,每次可以往上下左右 `4` 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。 - -如果一座岛屿完全由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为「封闭岛屿」。 - -**要求**:返回封闭岛屿的数目。 - -**说明**: - -- $1 \le grid.length, grid[0].length \le 100$。 -- $0 \le grid[i][j] \le 1$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2019/10/31/sample_3_1610.png) - -```python -输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]] -输出:2 -解释:灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/07/sample_4_1610.png) - -```python -输入:grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]] -输出:1 -``` - -## 解题思路 - -### 思路 1:深度优先搜索 - -1. 从 `grid[i][j] == 0` 的位置出发,使用深度优先搜索的方法遍历上下左右四个方向上相邻区域情况。 - 1. 如果上下左右都是 `grid[i][j] == 1`,则返回 `True`。 - 2. 如果有一个以上方向的 `grid[i][j] == 0`,则返回 `False`。 - 3. 遍历之后将当前陆地位置置为 `1`,表示该位置已经遍历过了。 -2. 最后统计出上下左右都满足 `grid[i][j] == 1` 的情况数量,即为答案。 - -### 思路 1:代码 - -```python -class Solution: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def dfs(self, grid, i, j): - n, m = len(grid), len(grid[0]) - if i < 0 or i >= n or j < 0 or j >= m: - return False - if grid[i][j] == 1: - return True - grid[i][j] = 1 - - res = True - for direct in self.directs: - new_i = i + direct[0] - new_j = j + direct[1] - if not self.dfs(grid, new_i, new_j): - res = False - return res - - def closedIsland(self, grid: List[List[int]]) -> int: - res = 0 - for i in range(len(grid)): - for j in range(len(grid[0])): - if grid[i][j] == 0 and self.dfs(grid, i, j): - res += 1 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 -- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git "a/Solutions/1266. \350\256\277\351\227\256\346\211\200\346\234\211\347\202\271\347\232\204\346\234\200\345\260\217\346\227\266\351\227\264.md" "b/Solutions/1266. \350\256\277\351\227\256\346\211\200\346\234\211\347\202\271\347\232\204\346\234\200\345\260\217\346\227\266\351\227\264.md" deleted file mode 100644 index 1fe14afe..00000000 --- "a/Solutions/1266. \350\256\277\351\227\256\346\211\200\346\234\211\347\202\271\347\232\204\346\234\200\345\260\217\346\227\266\351\227\264.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [1266. 访问所有点的最小时间](https://leetcode.cn/problems/minimum-time-visiting-all-points/) - -- 标签:几何、数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定 $n$ 个点的整数坐标数组 $points$。其中 $points[i] = [xi, yi]$,表示第 $i$ 个点坐标为 $(xi, yi)$。可以按照以下规则在平面上移动: - -1. 每一秒内,可以: - 1. 沿着水平方向移动一个单位长度。 - 2. 沿着竖直方向移动一个单位长度。 - 3. 沿着对角线移动 $\sqrt 2$ 个单位长度(可看做在一秒内沿着水平方向和竖直方向各移动一个单位长度)。 -2. 必须按照坐标数组 $points$ 中的顺序来访问这些点。 -3. 在访问某个点时,可以经过该点后面出现的点,但经过的那些点不算作有效访问。 - -**要求**:计算出访问这些点需要的最小时间(以秒为单位)。 - -**说明**: - -- $points.length == n$。 -- $1 \le n \le 100$。 -- $points[i].length == 2$。 -- $-1000 \le points[i][0], points[i][1] \le 1000$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/24/1626_example_1.png) - -```python -输入:points = [[1,1],[3,4],[-1,0]] -输出:7 -解释:一条最佳的访问路径是: [1,1] -> [2,2] -> [3,3] -> [3,4] -> [2,3] -> [1,2] -> [0,1] -> [-1,0] -从 [1,1] 到 [3,4] 需要 3 秒 -从 [3,4] 到 [-1,0] 需要 4 秒 -一共需要 7 秒 -``` - -```python -输入:points = [[3,2],[-2,2]] -输出:5 -``` - -## 解题思路 - -### 思路 1:数学 - -根据题意,每一秒可以沿着水平方向移动一个单位长度、或者沿着竖直方向移动一个单位长度、或者沿着对角线移动 $\sqrt 2$ 个单位长度。而沿着对角线移动 $\sqrt 2$ 个单位长度可以看做是先沿着水平方向移动一个单位长度,又沿着竖直方向移动一个单位长度,算是一秒走了两步距离。 - -现在假设从 A 点(坐标为 $(x1, y1)$)移动到 B 点(坐标为 $(x2, y2)$)。 - -那么从 A 点移动到 B 点如果要想得到最小时间,我们应该计算出沿着水平方向走的距离为 $dx = |x2 - x1|$,沿着竖直方向走的距离为 $dy = |y2 - y1|$。 - -然后比较沿着水平方向的移动距离和沿着竖直方向的移动距离。 - -- 如果 $dx > dy$,则我们可以先沿着对角线移动 $dy$ 次,再水平移动 $dx - dy$ 次,总共 $dx$ 次。 -- 如果 $dx == dy$,则我们可以直接沿着对角线移动 $dx$ 次,总共 $dx$ 次。 -- 如果 $dx < dy$,则我们可以先沿着对角线移动 $dx$ 次,再水平移动 $dy - dx$ 次,,总共 $dy$ 次。 - -根据上面观察可以发现:最小时间取决于「走的步数较多的那个方向所走的步数」,即 $max(dx, dy)$。 - -根据题目要求,需要按照坐标数组 $points$ 中的顺序来访问这些点,则我们需要按顺序遍历整个数组,计算出相邻点之间的 $max(dx, dy)$,将其累加到答案中。 - -最后将答案输出即可。 - -### 思路 1:代码 - -```python -class Solution: - def minTimeToVisitAllPoints(self, points: List[List[int]]) -> int: - ans = 0 - x1, y1 = points[0] - for point in points: - x2, y2 = point - ans += max(abs(x2 - x1), abs(y2 - y1)) - x1, y1 = point - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1268. \346\220\234\347\264\242\346\216\250\350\215\220\347\263\273\347\273\237.md" "b/Solutions/1268. \346\220\234\347\264\242\346\216\250\350\215\220\347\263\273\347\273\237.md" deleted file mode 100644 index 181f3d8c..00000000 --- "a/Solutions/1268. \346\220\234\347\264\242\346\216\250\350\215\220\347\263\273\347\273\237.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [1268. 搜索推荐系统](https://leetcode.cn/problems/search-suggestions-system/) - -- 标签:字典树、数组、字符串 -- 难度:中等 - -## 题目大意 - -给定一个产品数组 `products` 和一个字符串 `searchWord` ,`products` 数组中每个产品都是一个字符串。 - -要求:设计一个推荐系统,在依次输入单词 `searchWord` 的每一个字母后,推荐 `products` 数组中前缀与 `searchWord` 相同的最多三个产品(如果前缀相同的可推荐产品超过三个,请按字典序返回最小的三个)。 - -- 请你以二维列表的形式,返回在输入 `searchWord` 每个字母后相应的推荐产品的列表。 - -## 解题思路 - -先将产品数组按字典序排序。 - -然后使用字典树结构存储每个产品,并在字典树中维护一个数组,用于表示当前前缀所对应的产品列表(只保存最多 3 个产品)。 - -在查询的时候,将不同前缀所对应的产品列表加入到答案数组中。 - -最后输出答案数组。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.words = list() - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - if len(cur.words) < 3: - cur.words.append(word) - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - res = [] - flag = False - for ch in word: - if flag or ch not in cur.children: - res.append([]) - flag = True - else: - cur = cur.children[ch] - res.append(cur.words) - - return res - -class Solution: - def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]: - products.sort() - trie_tree = Trie() - for product in products: - trie_tree.insert(product) - - return trie_tree.search(searchWord) -``` - diff --git "a/Solutions/1281. \346\225\264\346\225\260\347\232\204\345\220\204\344\275\215\347\247\257\345\222\214\344\271\213\345\267\256.md" "b/Solutions/1281. \346\225\264\346\225\260\347\232\204\345\220\204\344\275\215\347\247\257\345\222\214\344\271\213\345\267\256.md" deleted file mode 100644 index 6549c35b..00000000 --- "a/Solutions/1281. \346\225\264\346\225\260\347\232\204\345\220\204\344\275\215\347\247\257\345\222\214\344\271\213\345\267\256.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [1281. 整数的各位积和之差](https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 `n`。 - -**要求**:计算并返回该整数「各位数字之积」与「各位数字之和」的差。 - -**说明**: - -- $1 <= n <= 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 234 -输出:15 - -解释: -各位数之积 2 * 3 * 4 = 24 -各位数之和 2 + 3 + 4 = 9 -结果 24 - 9 = 15 -``` - -## 解题思路 - -### 思路 1:数学 - -- 通过取模运算得到 `n` 的最后一位,即 `n %= 10`。 -- 然后去除 `n` 的最后一位,及`n //= 10`。 -- 一次求出各位数字之积与各位数字之和,并返回其差值。 - -### 思路 1:数学代码 - -```python -class Solution: - def subtractProductAndSum(self, n: int) -> int: - product = 1 - total = 0 - while n: - digit = n % 10 - product *= digit - total += digit - n //= 10 - return product - total -``` - diff --git "a/Solutions/1296. \345\210\222\345\210\206\346\225\260\347\273\204\344\270\272\350\277\236\347\273\255\346\225\260\345\255\227\347\232\204\351\233\206\345\220\210.md" "b/Solutions/1296. \345\210\222\345\210\206\346\225\260\347\273\204\344\270\272\350\277\236\347\273\255\346\225\260\345\255\227\347\232\204\351\233\206\345\220\210.md" deleted file mode 100644 index 60078b1a..00000000 --- "a/Solutions/1296. \345\210\222\345\210\206\346\225\260\347\273\204\344\270\272\350\277\236\347\273\255\346\225\260\345\255\227\347\232\204\351\233\206\345\220\210.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [1296. 划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) - -- 标签:贪心、数组、哈希表、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 `nums` 和一个正整数 `k`。 - -**要求**:判断是否可以把这个数组划分成一些由 `k` 个连续数字组成的集合。如果可以,则返回 `True`;否则,返回 `False`。 - -**说明**: - -- $1 \le k \le nums.length \le 10^5$。 -- $1 \le nums[i] \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,3,4,4,5,6], k = 4 -输出:True -解释:数组可以分成 [1,2,3,4] 和 [3,4,5,6]。 -``` - -## 解题思路 - -### 思路 1:哈希表 + 排序 - -1. 使用哈希表存储每个数出现的次数。 -2. 将哈希表中每个键从小到大排序。 -3. 从哈希表中最小的数开始,以它作为当前连续数字的开头,然后依次判断连续的 `k` 个数是否在哈希表中,如果在的话,则将哈希表中对应数的数量减 `1`。不在的话,说明无法满足题目要求,直接返回 `False`。 -4. 重复执行 2 ~ 3 步,直到哈希表为空。最后返回 `True`。 - -### 思路 1:哈希表 + 排序代码 - -```python -class Solution: - def isPossibleDivide(self, nums: List[int], k: int) -> bool: - hand_map = collections.defaultdict(int) - for i in range(len(nums)): - hand_map[nums[i]] += 1 - for key in sorted(hand_map.keys()): - value = hand_map[key] - if value == 0: - continue - count = 0 - for i in range(k): - hand_map[key + count] -= value - if hand_map[key + count] < 0: - return False - count += 1 - return True -``` diff --git "a/Solutions/1300. \350\275\254\345\217\230\346\225\260\347\273\204\345\220\216\346\234\200\346\216\245\350\277\221\347\233\256\346\240\207\345\200\274\347\232\204\346\225\260\347\273\204\345\222\214.md" "b/Solutions/1300. \350\275\254\345\217\230\346\225\260\347\273\204\345\220\216\346\234\200\346\216\245\350\277\221\347\233\256\346\240\207\345\200\274\347\232\204\346\225\260\347\273\204\345\222\214.md" deleted file mode 100644 index 512f0cf3..00000000 --- "a/Solutions/1300. \350\275\254\345\217\230\346\225\260\347\273\204\345\220\216\346\234\200\346\216\245\350\277\221\347\233\256\346\240\207\345\200\274\347\232\204\346\225\260\347\273\204\345\222\214.md" +++ /dev/null @@ -1,108 +0,0 @@ -# [1300. 转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) - -- 标签:数组、二分查找、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $arr$ 和一个目标值 $target$。 - -**要求**:返回一个整数 $value$,使得将数组中所有大于 $value$ 的值变成 $value$ 后,数组的和最接近 $target$(最接近表示两者之差的绝对值最小)。如果有多种使得和最接近 $target$ 的方案,请你返回这些整数中的最小值。 - -**说明**: - -- 答案 $value$ 不一定是 $arr$ 中的数字。 -- $1 \le arr.length \le 10^4$。 -- $1 \le arr[i], target \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [4,9,3], target = 10 -输出:3 -解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。 -``` - -- 示例 2: - -```python -输入:arr = [60864,25176,27249,21296,20204], target = 56803 -输出:11361 -``` - -## 解题思路 - -### 思路 1:二分查找 - -题目可以理解为:在 $[0, max(arr)]$ 的区间中,查找一个值 $value$。使得「转变后的数组和」与 $target$ 最接近。 - -- 转变规则:将数组中大于 $value$ 的值变为 $value$。 - -在 $[0, max(arr)]$ 的区间中,查找一个值 $value$ 可以使用二分查找答案的方式减少时间复杂度。但是这个最接近 $target$ 应该怎么理解,或者说怎么衡量接近程度。 - -最接近 $target$ 的肯定是数组和等于 $target$ 的时候。不过更可能是出现数组和恰好比 $target$ 大一点,或数组和恰好比 $target$ 小一点。我们可以将 $target$ 上下两个值相对应的数组和与 $target$ 进行比较,输出差值更小的那一个 $value$。 - -在根据查找的值 $value$ 计算数组和时,也可以通过二分查找方法查找出数组刚好大于等于 $value$ 元素下标。还可以根据事先处理过的前缀和数组,快速得到转变后的数组和。 - -最后输出使得数组和与 $target$ 差值更小的 $value$。 - -整个算法步骤如下: - -- 先对数组排序,并计算数组的前缀和 $pre\underline{}sum$。 -- 通过二分查找在 $[0, arr[-1]]$ 中查找使得转变后数组和刚好大于等于 $target$ 的值 $value$。 -- 计算 $value$ 对应的数组和 $sum\underline{}1$,以及 $value - 1$ 对应的数组和 $sum\underline{}2$。并分别计算与 $target$ 的差值 $diff\underline{}1$、$diff\underline{}2$。 -- 输出差值小的那个值。 - -### 思路 1:代码 - -```python -class Solution: - # 计算 value 对应的转变后的数组 - def calc_sum(self, arr, value, pre_sum): - size = len(arr) - left, right = 0, size - 1 - while left < right: - mid = left + (right - left) // 2 - if arr[mid] < value: - left = mid + 1 - else: - right = mid - - return pre_sum[left] + (size - left) * value - - # 查找使得转变后的数组和刚好大于等于 target 的 value - def binarySearchValue(self, arr, target, pre_sum): - left, right = 0, arr[-1] - while left < right: - mid = left + (right - left) // 2 - if self.calc_sum(arr, mid, pre_sum) < target: - left = mid + 1 - else: - right = mid - return left - - def findBestValue(self, arr: List[int], target: int) -> int: - size = len(arr) - arr.sort() - pre_sum = [0 for _ in range(size + 1)] - - for i in range(size): - pre_sum[i + 1] = pre_sum[i] + arr[i] - - value = self.binarySearchValue(arr, target, pre_sum) - - sum_1 = self.calc_sum(arr, value, pre_sum) - sum_2 = self.calc_sum(arr, value - 1, pre_sum) - diff_1 = abs(sum_1 - target) - diff_2 = abs(sum_2 - target) - - return value if diff_1 < diff_2 else value - 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O((n + k) \times \log n)$。其中 $n$ 是数组 $arr$ 的长度,$k$ 是数组 $arr$ 中的最大值。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/1305. \344\270\244\346\243\265\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\211\200\346\234\211\345\205\203\347\264\240.md" "b/Solutions/1305. \344\270\244\346\243\265\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\211\200\346\234\211\345\205\203\347\264\240.md" deleted file mode 100644 index 50dfdd16..00000000 --- "a/Solutions/1305. \344\270\244\346\243\265\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\211\200\346\234\211\345\205\203\347\264\240.md" +++ /dev/null @@ -1,102 +0,0 @@ -# [1305. 两棵二叉搜索树中的所有元素](https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定两棵二叉搜索树的根节点 $root1$ 和 $root2$。 - -**要求**:返回一个列表,其中包含两棵树中所有整数并按升序排序。 - -**说明**: - -- 每棵树的节点数在 $[0, 5000]$ 范围内。 -- $-10^5 \le Node.val \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/12/29/q2-e1.png) - -```python -输入:root1 = [2,1,4], root2 = [1,0,3] -输出:[0,1,1,2,3,4] -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/12/29/q2-e5-.png) - -```python -输入:root1 = [1,null,8], root2 = [8,1] -输出:[1,1,8,8] -``` - -## 解题思路 - -### 思路 1:二叉树的中序遍历 + 快慢指针 - -根据二叉搜索树的特性,如果我们以中序遍历的方式遍历整个二叉搜索树时,就会得到一个有序递增列表。我们按照这样的方式分别对两个二叉搜索树进行中序遍历,就得到了两个有序数组,那么问题就变成了:两个有序数组的合并问题。 - -两个有序数组的合并可以参考归并排序中的归并过程,使用快慢指针将两个有序数组合并为一个有序数组。 - -具体步骤如下: - -1. 分别使用中序遍历的方式遍历两个二叉搜索树,得到两个有序数组 $nums1$、$nums2$。 -2. 使用两个指针 $index1$、$index2$ 分别指向两个有序数组的开始位置。 -3. 比较两个指针指向的元素,将两个有序数组中较小元素依次存入结果数组 $nums$ 中,并将指针移动到下一个位置。 -4. 重复步骤 $3$,直到某一指针到达数组末尾。 -5. 将另一个数组中的剩余元素依次存入结果数组 $nums$ 中。 -6. 返回结果数组 $nums$。 - -### 思路 1:代码 - -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -class Solution: - def inorderTraversal(self, root: TreeNode) -> List[int]: - res = [] - def inorder(root): - if not root: - return - inorder(root.left) - res.append(root.val) - inorder(root.right) - - inorder(root) - return res - def getAllElements(self, root1: TreeNode, root2: TreeNode) -> List[int]: - nums1 = self.inorderTraversal(root1) - nums2 = self.inorderTraversal(root2) - nums = [] - index1, index2 = 0, 0 - while index1 < len(nums1) and index2 < len(nums2): - if nums1[index1] < nums2[index2]: - nums.append(nums1[index1]) - index1 += 1 - else: - nums.append(nums2[index2]) - index2 += 1 - - while index1 < len(nums1): - nums.append(nums1[index1]) - index1 += 1 - - while index2 < len(nums2): - nums.append(nums2[index2]) - index2 += 1 - - return nums -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m)$,其中 $n$ 和 $m$ 分别为两棵二叉搜索树的节点个数。 -- **空间复杂度**:$O(n + m)$。 diff --git "a/Solutions/1310. \345\255\220\346\225\260\347\273\204\345\274\202\346\210\226\346\237\245\350\257\242.md" "b/Solutions/1310. \345\255\220\346\225\260\347\273\204\345\274\202\346\210\226\346\237\245\350\257\242.md" deleted file mode 100644 index d05586f2..00000000 --- "a/Solutions/1310. \345\255\220\346\225\260\347\273\204\345\274\202\346\210\226\346\237\245\350\257\242.md" +++ /dev/null @@ -1,151 +0,0 @@ -# [1310. 子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) - -- 标签:位运算、数组、前缀和 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个正整数数组 `arr`,再给定一个对应的查询数组 `queries`,其中 `queries[i] = [Li, Ri]`。 - -**要求**:对于每个查询 `queries[i]`,要求计算从 `Li` 到 `Ri` 的异或值(即 `arr[Li] ^ arr[Li+1] ^ ... ^ arr[Ri]`)作为本次查询的结果。并返回一个包含给定查询 `queries` 所有结果的数组。 - -**说明**: - -- $1 \le arr.length \le 3 * 10^4$。 -- $1 \le arr[i] \le 10^9$。 -- $1 \le queries.length \le 3 * 10^4$。 -- $queries[i].length == 2$。 -- $0 \le queries[i][0] \le queries[i][1] < arr.length$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]] -输出:[2,7,14,8] -解释 - -数组中元素的二进制表示形式是: -1 = 0001 -3 = 0011 -4 = 0100 -8 = 1000 - -查询的 XOR 值为: -[0,1] = 1 xor 3 = 2 -[1,2] = 3 xor 4 = 7 -[0,3] = 1 xor 3 xor 4 xor 8 = 14 -[3,3] = 8 -``` - -## 解题思路 - -### 思路 1:线段树 - -- 使用数组 `res` 作为答案数组,用于存放每个查询的结果值。 -- 根据 `nums` 数组构建一棵线段树。 -- 然后遍历查询数组 `queries`。对于每个查询 `queries[i]`,在线段树中查询对应区间的异或值,将其结果存入答案数组 `res` 中。 -- 返回答案数组 `res` 即可。 - -这样构建线段树的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(k * \log n)$,其中 $k$ 是查询次数。 - -### 思路 1:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - -class Solution: - def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: - self.STree = SegmentTree(arr, lambda x, y: (x ^ y)) - res = [] - for query in queries: - ans = self.STree.query_interval(query[0], query[1]) - res.append(ans) - return res -``` diff --git "a/Solutions/1317. \345\260\206\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\270\244\344\270\252\346\227\240\351\233\266\346\225\264\346\225\260\347\232\204\345\222\214.md" "b/Solutions/1317. \345\260\206\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\270\244\344\270\252\346\227\240\351\233\266\346\225\264\346\225\260\347\232\204\345\222\214.md" deleted file mode 100644 index 67db29e2..00000000 --- "a/Solutions/1317. \345\260\206\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\270\244\344\270\252\346\227\240\351\233\266\346\225\264\346\225\260\347\232\204\345\222\214.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [1317. 将整数转换为两个无零整数的和](https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回一个由两个整数组成的列表 $[A, B]$,满足: - -- $A$ 和 $B$ 都是无零整数。 -- $A + B = n$。 - -**说明**: - -- **无零整数**:十进制表示中不含任何 $0$ 的正整数。 -- 题目数据保证至少一个有效的解决方案。 -- 如果存在多个有效解决方案,可以返回其中任意一个。 -- $2 \le n \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:[1,1] -解释:A = 1, B = 1. A + B = n 并且 A 和 B 的十进制表示形式都不包含任何 0。 -``` - -- 示例 2: - -```python -输入:n = 11 -输出:[2,9] -``` - -## 解题思路 - -### 思路 1:枚举 - -1. 由于给定的 $n$ 范围为 $[1, 10000]$,比较小,我们可以直接在 $[1, n)$ 的范围内枚举 $A$,并通过 $n - A$ 得到 $B$。 -2. 在判断 $A$ 和 $B$ 中是否都不包含 $0$。如果都不包含 $0$,则返回 $[A, B]$。 - -### 思路 1:代码 - -```python -class Solution: - def getNoZeroIntegers(self, n: int) -> List[int]: - for A in range(1, n): - B = n - A - if '0' not in str(A) and '0' not in str(B): - return [A, B] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1319. \350\277\236\351\200\232\347\275\221\347\273\234\347\232\204\346\223\215\344\275\234\346\254\241\346\225\260.md" "b/Solutions/1319. \350\277\236\351\200\232\347\275\221\347\273\234\347\232\204\346\223\215\344\275\234\346\254\241\346\225\260.md" deleted file mode 100644 index 6ab07b66..00000000 --- "a/Solutions/1319. \350\277\236\351\200\232\347\275\221\347\273\234\347\232\204\346\223\215\344\275\234\346\254\241\346\225\260.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [1319. 连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -`n` 台计算机通过网线连接成一个网络,计算机的编号从 `0` 到 `n - 1`。线缆用 `comnnections` 表示,其中 `connections[i] = [a, b]` 表示连接了计算机 `a` 和 `b`。 - -给定这个计算机网络的初始布线 `connections`,可以拔除任意两台直接相连的计算机之间的网线,并用这根网线连接任意一对未直接连接的计算机。现在要求:计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 `-1`。 - -## 解题思路 - -`n` 台计算机至少需要 `n - 1` 根线才能进行连接,如果网线的数量少于 `n - 1`,那么就不可能将其连接。接下来计算最少操作次数。 - -把 `n` 台计算机看做是 `n` 个节点,每条网线看做是一条无向边。维护两个变量:多余电线数 `removeCount`、需要电线数 `needConnectCount`。初始 `removeCount = 1, needConnectCount = n - 1`。 - -遍历网线数组,将相连的节点 `a` 和 `b` 利用并查集加入到一个集合中(调用 `union` 操作)。 - -- 如果 `a` 和 `b` 已经在同一个集合中,说明该连接线多余,多余电线数 +1。 -- 如果 `a` 和 `b` 不在一个集合中,则将其合并,则 `a` 和 `b` 之间不再需要用额外的电线连接了,所以需要电线数 -1。 - -最后,判断多余的电线数是否满足需要电线数,不满足返回 -1,如果满足,则返回需要电线数。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return False - self.parent[root_x] = root_y - return True - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def makeConnected(self, n: int, connections: List[List[int]]) -> int: - union_find = UnionFind(n) - removeCount = 0 - needConnectCount = n - 1 - for connection in connections: - if union_find.union(connection[0], connection[1]): - needConnectCount -= 1 - else: - removeCount += 1 - - if removeCount < needConnectCount: - return -1 - return needConnectCount -``` - diff --git "a/Solutions/1343. \345\244\247\345\260\217\344\270\272 K \344\270\224\345\271\263\345\235\207\345\200\274\345\244\247\344\272\216\347\255\211\344\272\216\351\230\210\345\200\274\347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\347\233\256.md" "b/Solutions/1343. \345\244\247\345\260\217\344\270\272 K \344\270\224\345\271\263\345\235\207\345\200\274\345\244\247\344\272\216\347\255\211\344\272\216\351\230\210\345\200\274\347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\347\233\256.md" deleted file mode 100644 index 668fb137..00000000 --- "a/Solutions/1343. \345\244\247\345\260\217\344\270\272 K \344\270\224\345\271\263\345\235\207\345\200\274\345\244\247\344\272\216\347\255\211\344\272\216\351\230\210\345\200\274\347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [1343. 大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) - -- 标签:数组、滑动窗口 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $arr$ 和两个整数 $k$ 和 $threshold$。 - -**要求**:返回长度为 $k$ 且平均值大于等于 $threshold$ 的子数组数目。 - -**说明**: - -- $1 \le arr.length \le 10^5$。 -- $1 \le arr[i] \le 10^4$。 -- $1 \le k \le arr.length$。 -- $0 \le threshold \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 -输出:3 -解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。 -``` - -- 示例 2: - -```python -输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 -输出:6 -解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 -``` - -## 解题思路 - -### 思路 1:滑动窗口(固定长度) - -这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 `k`。具体做法如下: - -1. `ans` 用来维护答案数目。`window_sum` 用来维护窗口中元素的和。 -2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 -4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 `threshold`。 - 1. 如果满足,则答案数目 + 1。 - 2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 -6. 最后输出答案数目。 - -### 思路 1:代码 - -```python -class Solution: - def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int: - left = 0 - right = 0 - window_sum = 0 - ans = 0 - - while right < len(arr): - window_sum += arr[right] - - if right - left + 1 >= k: - if window_sum >= k * threshold: - ans += 1 - window_sum -= arr[left] - left += 1 - - right += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/1349. \345\217\202\345\212\240\350\200\203\350\257\225\347\232\204\346\234\200\345\244\247\345\255\246\347\224\237\346\225\260.md" "b/Solutions/1349. \345\217\202\345\212\240\350\200\203\350\257\225\347\232\204\346\234\200\345\244\247\345\255\246\347\224\237\346\225\260.md" deleted file mode 100644 index 5e878be4..00000000 --- "a/Solutions/1349. \345\217\202\345\212\240\350\200\203\350\257\225\347\232\204\346\234\200\345\244\247\345\255\246\347\224\237\346\225\260.md" +++ /dev/null @@ -1,126 +0,0 @@ -# [1349. 参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) - -- 标签:位运算、数组、动态规划、状态压缩、矩阵 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 大小的矩阵 $seats$ 表示教室中的座位分布,其中如果座位是坏的(不可用),就用 `'#'` 表示,如果座位是好的,就用 `'.'` 表示。 - -学生可以看到左侧、右侧、左上方、右上方这四个方向上紧邻他的学生答卷,但是看不到直接坐在他前面或者后面的学生答卷。 - -**要求**:计算并返回该考场可以容纳的一期参加考试且无法作弊的最大学生人数。 - -**说明**: - -- 学生必须坐在状况良好的座位上。 -- $seats$ 只包含字符 `'.'` 和 `'#'`。 -- $m == seats.length$。 -- $n == seats[i].length$。 -- $1 \le m \le 8$。 -- $1 \le n \le 8$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/09/image.png) - -```python -输入:seats = [["#",".","#","#",".","#"], - [".","#","#","#","#","."], - ["#",".","#","#",".","#"]] -输出:4 -解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。 -``` - -- 示例 2: - -```python -输入:seats = [[".","#"], - ["#","#"], - ["#","."], - ["#","#"], - [".","#"]] -输出:3 -解释:让所有学生坐在可用的座位上。 -``` - -## 解题思路 - -### 思路 1:状态压缩 DP - -题目中给定的 $m$、$n$ 范围为 $1 \le m, n \le 8$,每一排最多有 $8$ 个座位,那么我们可以使用一个 $8$ 位长度的二进制数来表示当前排座位的选择情况(也就是「状态压缩」的方式)。 - -同时从题目中可以看出,当前排的座位与当前行左侧、右侧座位有关,并且也与上一排中左上方、右上方的座位有关,则我们可以使用一个二维数组来表示状态。其中第一维度为排数,第二维度为当前排的座位选择情况。 - -具体做法如下: - -###### 1. 划分阶段 - -按照排数、当前排的座位选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][state]$ 表示为:前 $i$ 排,并且最后一排座位选择状态为 $state$ 时,可以参加考试的最大学生数。 - -###### 3. 状态转移方程 - -因为学生可以看到左侧、右侧、左上方、右上方这四个方向上紧邻他的学生答卷,所以对于当前排的某个座位来说,其左侧、右侧、左上方、右上方都不应有人坐。我们可以根据当前排的座位选取状态 $cur\underline{}state$,并通过枚举的方式,找出符合要求的上一排座位选取状态 $pre\underline{}state$,并计算出当前排座位选择个数,即 $f(cur\underline{}state)$,则状态转移方程为: - - $dp[i][state] = \max \lbrace dp[i - 1][pre\underline{}state]\rbrace + f(state) $ - -因为所给座位中还有坏座位(不可用)的情况,我们可以使用一个 $8$ 位的二进制数 $bad\underline{}seat$ 来表示当前排的坏座位情况,如果 $cur\underline{}state \text{ \& } bad\underline{}seat == 1$,则说明当前状态下,选择了坏椅子,则可直接跳过这种状态。 - -我们还可以通过 $cur\underline{}state \text{ \& } (cur\underline{}state \text{ <}\text{< } 1)$ 和 $cur\underline{}state \& (cur\underline{}state \text{ >}\text{> } 1)$ 来判断当前排选择状态下,左右相邻座位上是否有人,如果有人,则可直接跳过这种状态。 - -同理,我们还可以通过 $cur\underline{}state \text{ \& } (pre\underline{}state \text{ <}\text{< } 1)$ 和 $cur\underline{}state \text{ \& } (pre\underline{}state \text{ >}\text{> } 1)$ 来判断当前排选择状态下,上一行左上、右上相邻座位上是否有人,如果有人,则可直接跳过这种状态。 - -###### 4. 初始条件 - -- 默认情况下,前 $0$ 排所有选择状态下,可以参加考试的最大学生数为 $0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][state]$ 表示为:前 $i$ 排,并且最后一排座位选择状态为 $state$ 时,可以参加考试的最大学生数。 所以最终结果为最后一排 $dp[rows]$ 中的最大值。 - -### 思路 1:代码 - -```python -class Solution: - def maxStudents(self, seats: List[List[str]]) -> int: - rows, cols = len(seats), len(seats[0]) - states = 1 << cols - dp = [[0 for _ in range(states)] for _ in range(rows + 1)] - - for i in range(1, rows + 1): # 模拟 1 ~ rows 排分配座位 - bad_seat = 0 # 当前排的坏座位情况 - for j in range(cols): - if seats[i - 1][j] == '#': # 记录坏座位情况 - bad_seat |= 1 << j - - for cur_state in range(states): # 枚举当前排的座位选取状态 - if cur_state & bad_seat: # 当前排的座位选择了换座位,跳过 - continue - if cur_state & (cur_state << 1): # 当前排左侧座位有人,跳过 - continue - if cur_state & (cur_state >> 1): # 当前排右侧座位有人,跳过 - continue - - count = bin(cur_state).count('1') # 计算当前排最多可以坐多少人 - for pre_state in range(states): # 枚举前一排情况 - if cur_state & (pre_state << 1): # 左上座位有人,跳过 - continue - if cur_state & (pre_state >> 1): # 右上座位有人,跳过 - continue - # dp[i][cur_state] 取自上一排分配情况为 pre_state 的最大值 + 当前排最多可以坐的人数 - dp[i][cur_state] = max(dp[i][cur_state], dp[i - 1][pre_state] + count) - - return max(dp[rows]) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times 2^{2n})$,其中 $m$、$n$ 分别为所给矩阵的行数、列数。 -- **空间复杂度**:$O(m \times 2^n)$。 - diff --git "a/Solutions/1358. \345\214\205\345\220\253\346\211\200\346\234\211\344\270\211\347\247\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" "b/Solutions/1358. \345\214\205\345\220\253\346\211\200\346\234\211\344\270\211\347\247\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" deleted file mode 100644 index 61652253..00000000 --- "a/Solutions/1358. \345\214\205\345\220\253\346\211\200\346\234\211\344\270\211\347\247\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [1358. 包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给你一个字符串 `s` ,`s` 只包含三种字符 `a`, `b` 和 `c`。 - -请你返回 `a`,`b` 和 `c` 都至少出现过一次的子字符串数目。 - -## 解题思路 - -只要找到首个 `a`、`b`、`c` 同时存在的子字符串,则在该子字符串后面追加字符构成的新字符串还是满足题意的。假设该子串末尾字母的位置为 `i`,则以此字符串构建的新字符串有 `len(s) - i`个。所以题目可以转换为找出 `a`、`b`、`c` 同时存在的最短子串,并记录所有满足题意的字符串数量。具体做法如下: - -用滑动窗口 `window` 来记录各个字符个数,`window` 为哈希表类型。用 `ans` 来维护 `a`,`b` 和 `c` 都至少出现过一次的子字符串数目。 - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中不超过 `k` 种字符。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧字符 `s[right]` 加入当前窗口 `window_counts` 中,记录该字符个数,向右移动 `right`。 -- 如果该窗口中字符的种数大于等于 `3` 种,即 `len(window) >= 3`,则累积答案个数为 `len(s) - right`,并不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `len(window) < 3`。 -- 然后继续右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def numberOfSubstrings(self, s: str) -> int: - window = dict() - ans = 0 - left, right = 0, 0 - - while right < len(s): - if s[right] in window: - window[s[right]] += 1 - else: - window[s[right]] = 1 - - while len(window) >= 3: - ans += len(s) - right - window[s[left]] -= 1 - if window[s[left]] == 0: - del window[s[left]] - left += 1 - right += 1 - return ans -``` - diff --git "a/Solutions/1381. \350\256\276\350\256\241\344\270\200\344\270\252\346\224\257\346\214\201\345\242\236\351\207\217\346\223\215\344\275\234\347\232\204\346\240\210.md" "b/Solutions/1381. \350\256\276\350\256\241\344\270\200\344\270\252\346\224\257\346\214\201\345\242\236\351\207\217\346\223\215\344\275\234\347\232\204\346\240\210.md" deleted file mode 100644 index 5eabdd7c..00000000 --- "a/Solutions/1381. \350\256\276\350\256\241\344\270\200\344\270\252\346\224\257\346\214\201\345\242\236\351\207\217\346\223\215\344\275\234\347\232\204\346\240\210.md" +++ /dev/null @@ -1,118 +0,0 @@ -# [1381. 设计一个支持增量操作的栈](https://leetcode.cn/problems/design-a-stack-with-increment-operation/) - -- 标签:栈、设计、数组 -- 难度:中等 - -## 题目大意 - -**要求**:设计一个支持对其元素进行增量操作的栈。 - -实现自定义栈类 $CustomStack$: - -- `CustomStack(int maxSize)`:用 $maxSize$ 初始化对象,$maxSize$ 是栈中最多能容纳的元素数量。 -- `void push(int x)`:如果栈还未增长到 $maxSize$,就将 $x$ 添加到栈顶。 -- `int pop()`:弹出栈顶元素,并返回栈顶的值,或栈为空时返回 $-1$。 -- `void inc(int k, int val)`:栈底的 $k$ 个元素的值都增加 $val$。如果栈中元素总数小于 $k$,则栈中的所有元素都增加 $val$。 - -**说明**: - -- $1 \le maxSize, x, k \le 1000$。 -- $0 \le val \le 100$。 -- 每种方法 `increment`,`push` 以及 `pop` 分别最多调用 $1000$ 次。 - -**示例**: - -- 示例 1: - -```python -输入: -["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"] -[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]] -输出: -[null,null,null,2,null,null,null,null,null,103,202,201,-1] -解释: -CustomStack stk = new CustomStack(3); // 栈是空的 [] -stk.push(1); // 栈变为 [1] -stk.push(2); // 栈变为 [1, 2] -stk.pop(); // 返回 2 --> 返回栈顶值 2,栈变为 [1] -stk.push(2); // 栈变为 [1, 2] -stk.push(3); // 栈变为 [1, 2, 3] -stk.push(4); // 栈仍然是 [1, 2, 3],不能添加其他元素使栈大小变为 4 -stk.increment(5, 100); // 栈变为 [101, 102, 103] -stk.increment(2, 100); // 栈变为 [201, 202, 103] -stk.pop(); // 返回 103 --> 返回栈顶值 103,栈变为 [201, 202] -stk.pop(); // 返回 202 --> 返回栈顶值 202,栈变为 [201] -stk.pop(); // 返回 201 --> 返回栈顶值 201,栈变为 [] -stk.pop(); // 返回 -1 --> 栈为空,返回 -1 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 初始化: - 1. 使用空数组 $stack$ 用于表示栈。 - 2. 使用 $size$ 用于表示当前栈中元素个数, - 3. 使用 $maxSize$ 用于表示栈中允许的最大元素个数。 - 4. 使用另一个空数组 $increments$ 用于增量操作。 -2. `push(x)` 操作: - 1. 判断当前元素个数与栈中允许的最大元素个数关系。 - 2. 如果当前元素个数小于栈中允许的最大元素个数,则: - 1. 将 $x$ 添加到数组 $stack$ 中,即:`self.stack.append(x)`。 - 2. 当前元素个数加 $1$,即:`self.size += 1`。 - 3. 将 $0$ 添加到增量数组 $increments$ 中,即:`self.increments.append(0)`。 -3. `increment(k, val)` 操作: - 1. 如果增量数组不为空,则取 $k$ 与元素个数 `self.size` 的较小值,令增量数组对应位置加上 `val`(等 `pop()` 操作时,再计算出准确值)。 -4. `pop()` 操作: - 1. 如果当前元素个数为 $0$,则直接返回 $-1$。 - 2. 如果当前元素个数大于等于 $2$,则更新弹出元素后的增量数组(保证剩余元素弹出时能够正确计算出),即:`self.increments[-2] += self.increments[-1]` - 3. 令元素个数减 $1$,即:`self.size -= 1`。 - 4. 弹出数组 $stack$ 中的栈顶元素和增量数组 $increments$ 中的栈顶元素,令其相加,即为弹出元素值,将其返回。 - -### 思路 1:代码 - -```python -class CustomStack: - - def __init__(self, maxSize: int): - self.maxSize = maxSize - self.stack = [] - self.increments = [] - self.size = 0 - - - def push(self, x: int) -> None: - if self.size < self.maxSize: - self.stack.append(x) - self.increments.append(0) - self.size += 1 - - - def pop(self) -> int: - if self.size == 0: - return -1 - if self.size >= 2: - self.increments[-2] += self.increments[-1] - self.size -= 1 - - val = self.stack.pop() + self.increments.pop() - return val - - - def increment(self, k: int, val: int) -> None: - if self.increments: - self.increments[min(k, self.size) - 1] += val - - - -# Your CustomStack object will be instantiated and called as such: -# obj = CustomStack(maxSize) -# obj.push(x) -# param_2 = obj.pop() -# obj.increment(k,val) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:初始化、`push` 操作、`pop` 操作、`increment` 操作的时间复杂度为 $O(1)$。 -- **空间复杂度**:$O(maxSize)$。 diff --git "a/Solutions/1400. \346\236\204\351\200\240 K \344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262.md" "b/Solutions/1400. \346\236\204\351\200\240 K \344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index d31589fd..00000000 --- "a/Solutions/1400. \346\236\204\351\200\240 K \344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [1400. 构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) - -- 标签:贪心、哈希表、字符串、计数 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个字符串 `s` 和一个整数 `k`。 - -**要求**:用 `s` 字符串中所有字符构造 `k` 个非空回文串。如果可以用 `s` 中所有字符构造 `k` 个回文字符串,那么请你返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le s.length \le 10^5$。 -- `s` 中所有字符都是小写英文字母。 -- $1 \le k \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:s = "annabelle", k = 2 -输出:True -解释:可以用 s 中所有字符构造 2 个回文字符串。 -一些可行的构造方案包括:"anna" + "elble","anbna" + "elle","anellena" + "b" -``` - -## 解题思路 - -### 思路 1:贪心算法 - -- 用字符串 `s` 中所有字符构造回文串最多可以构造 `len(s)` 个(将每个字符当做一个回文串)。所以如果 `len(s) < k`,则说明字符数量不够,无法构成 `k` 个回文串,直接返回 `False`。 -- 如果 `len(s) == k`,则可以直接使用单个字符构建回文串,直接返回 `True`。 -- 如果 `len(s) > k`,则需要判断一下字符串 `s `中每个字符的个数。因为当字符是偶数个时,可以直接构造成回文串。所以我们只需要考虑个数为奇数的字符即可。如果个位为奇数的字符种类小于等于 `k`,则说明可以构造 `k` 个回文串,返回 `True`。如果个位为奇数的字符种类大于 `k`,则说明无法构造 `k` 个回文串,返回 `Fasle`。 - -### 思路 1:贪心算法代码 - -```python -import collections - -class Solution: - def canConstruct(self, s: str, k: int) -> bool: - size = len(s) - if size < k: - return False - if size == k: - return True - letter_dict = dict() - for i in range(size): - if s[i] in letter_dict: - letter_dict[s[i]] += 1 - else: - letter_dict[s[i]] = 1 - - odd = 0 - for key in letter_dict: - if letter_dict[key] % 2 == 1: - odd += 1 - return odd <= k -``` diff --git "a/Solutions/1408. \346\225\260\347\273\204\344\270\255\347\232\204\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" "b/Solutions/1408. \346\225\260\347\273\204\344\270\255\347\232\204\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" deleted file mode 100644 index ffc9ea52..00000000 --- "a/Solutions/1408. \346\225\260\347\273\204\344\270\255\347\232\204\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215.md" +++ /dev/null @@ -1,88 +0,0 @@ -# [1408. 数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) - -- 标签:数组、字符串、字符串匹配 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串数组 `words`,数组中的每个字符串都可以看作是一个单词。如果可以删除 `words[j]` 最左侧和最右侧的若干字符得到 `word[i]`,那么字符串 `words[i]` 就是 `words[j]` 的一个子字符串。 - -**要求**:按任意顺序返回 `words` 中是其他单词的子字符串的所有单词。 - -**说明**: - -- $1 \le words.length \le 100$。 -- $1 \le words[i].length \le 30$ -- `words[i]` 仅包含小写英文字母。 -- 题目数据保证每个 `words[i]` 都是独一无二的。 - -**示例**: - -- 示例 1: - -```python -输入:words = ["mass","as","hero","superhero"] -输出:["as","hero"] -解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。此外,["hero","as"] 也是有效的答案。 -``` - -## 解题思路 - -### 思路 1:KMP 算法 - -1. 先按照字符串长度从小到大排序,使用数组 `res` 保存答案。 -2. 使用两重循环遍历,对于 `words[i]` 和 `words[j]`,使用 `KMP` 匹配算法,如果 `wrods[j]` 包含 `words[i]`,则将其加入到答案数组中,并跳出最里层循环。 -3. 返回答案数组 `res`。 - -### 思路 1:代码 - -```python -class Solution: - # 生成 next 数组 - # next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度 - def generateNext(self, p: str): - m = len(p) - next = [0 for _ in range(m)] # 初始化数组元素全部为 0 - - left = 0 # left 表示前缀串开始所在的下标位置 - for right in range(1, m): # right 表示后缀串开始所在的下标位置 - while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退 - left = next[left - 1] # left 进行回退操作 - if p[left] == p[right]: # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度 - left += 1 - next[right] = left # 记录前缀长度,更新 next[right], 结束本次循环, right += 1 - - return next - - # KMP 匹配算法,T 为文本串,p 为模式串 - def kmp(self, T: str, p: str) -> int: - n, m = len(T), len(p) - - next = self.generateNext(p) # 生成 next 数组 - - j = 0 # j 为模式串中当前匹配的位置 - for i in range(n): # i 为文本串中当前匹配的位置 - while j > 0 and T[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退 - j = next[j - 1] - if T[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配 - j += 1 - if j == m: # 当前模式串完全匹配成功,返回匹配开始位置 - return i - j + 1 - return -1 # 匹配失败,返回 -1 - - def stringMatching(self, words: List[str]) -> List[str]: - words.sort(key=lambda x:len(x)) - - res = [] - for i in range(len(words) - 1): - for j in range(i + 1, len(words)): - if self.kmp(words[j], words[i]) != -1: - res.append(words[i]) - break - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 \times m)$,其中字符串数组长度为 $n$,字符串数组中最长字符串长度为 $m$。 -- **空间复杂度**:$O(m)$。 diff --git "a/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" "b/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" deleted file mode 100644 index e6d29a01..00000000 --- "a/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" +++ /dev/null @@ -1,75 +0,0 @@ -# [1422. 分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个由若干 $0$ 和 $1$ 组成的字符串。将字符串分割成两个非空子字符串的得分为:左子字符串中 $0$ 的数量 + 右子字符串中 $1$ 的数量。 - -**要求**:计算并返回该字符串分割成两个非空子字符串(即左子字符串和右子字符串)所能获得的最大得分。 - -**说明**: - -- $2 \le s.length \le 500$。 -- 字符串 $s$ 仅由字符 $0$ 和 $1$ 组成。 - -**示例**: - -- 示例 1: - -```python -输入:s = "011101" -输出:5 -解释: -将字符串 s 划分为两个非空子字符串的可行方案有: -左子字符串 = "0" 且 右子字符串 = "11101",得分 = 1 + 4 = 5 -左子字符串 = "01" 且 右子字符串 = "1101",得分 = 1 + 3 = 4 -左子字符串 = "011" 且 右子字符串 = "101",得分 = 1 + 2 = 3 -左子字符串 = "0111" 且 右子字符串 = "01",得分 = 1 + 1 = 2 -左子字符串 = "01110" 且 右子字符串 = "1",得分 = 2 + 1 = 3 -``` - -- 示例 2: - -```python -输入:s = "00111" -输出:5 -解释:当 左子字符串 = "00" 且 右子字符串 = "111" 时,我们得到最大得分 = 2 + 3 = 5 -``` - -## 解题思路 - -### 思路 1:前缀和 - -1. 遍历字符串 $s$,使用前缀和数组来记录每个前缀子字符串中 $1$ 的个数。 -2. 再次遍历字符串 $s$,枚举每个分割点,利用前缀和数组计算出当前分割出的左子字符串中 $1$ 的个数与右子字符串中 $0$ 的个数,并计算当前得分,然后更新最大得分。 -3. 返回最大得分作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def maxScore(self, s: str) -> int: - size = len(s) - one_cnts = [0 for _ in range(size + 1)] - - for i in range(1, size + 1): - if s[i - 1] == '1': - one_cnts[i] = one_cnts[i - 1] + 1 - else: - one_cnts[i] = one_cnts[i - 1] - - ans = 0 - for i in range(1, size): - left_score = i - one_cnts[i] - right_score = one_cnts[size] - one_cnts[i] - ans = max(ans, left_score + right_score) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" "b/Solutions/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" deleted file mode 100644 index e5d7eae1..00000000 --- "a/Solutions/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [1423. 可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) - -- 标签:数组、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -将卡牌排成一行,给定每张卡片的点数数组 `cardPoints`,其中 `cardPoints[i]` 表示第 `i` 张卡牌对应点数。 - -每次行动,可以从行的开头或者末尾拿一张卡牌,最终保证正好拿到了 `k` 张卡牌。所得点数就是你拿到手中的所有卡牌的点数之和。 - -现在给定一个整数数组 `cardPoints` 和整数 `k`。 - -要求:返回可以获得的最大点数。 - -## 解题思路 - -可以用固定长度的滑动窗口来做。 - -由于只能从开头或末尾位置拿 `k` 张牌,则最后剩下的肯定是连续的 `len(cardPoints) - k` 张牌。要求求出 `k` 张牌可以获得的最大收益,我们可以反向先求出连续 `len(cardPoints) - k` 张牌的最小点数。则答案为 `sum(cardPoints) - min_sum`。维护一个固定长度为 `len(cardPoints) - k` 的滑动窗口,求最小和。具体做法如下: - -1. `window_sum` 用来维护窗口内的元素和,初始值为 `0`。`min_sum` 用来维护滑动窗口元素的最小和。初始值为 `sum(cardPoints)`。滑动窗口的长度为 `window_size`,值为 `len(cardPoints) - k`。 -2. 使用双指针 `left`、`right`。`left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 向右移动 `right`,先将 `window_size` 个元素填入窗口中。 -4. 当窗口元素个数为 `window_size` 时,即:`right - left + 1 >= window_size` 时,计算窗口内的元素和,并维护子数组最小和 `min_sum`。 -5. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -6. 重复 4 ~ 5 步,直到 `right` 到达数组末尾。 -6. 最后输出 `sum(cardPoints) - min_sum` 即为答案。 - -注意:如果 `window_size` 为 `0` 时需要特殊判断,此时答案为数组和 `sum(cardPoints)`。 - -## 代码 - -```python -class Solution: - def maxScore(self, cardPoints: List[int], k: int) -> int: - window_size = len(cardPoints) - k - window_sum = 0 - cards_sum = sum(cardPoints) - min_sum = cards_sum - - left, right = 0, 0 - if window_size == 0: - return cards_sum - - while right < len(cardPoints): - window_sum += cardPoints[right] - - if right - left + 1 >= window_size: - min_sum = min(window_sum, min_sum) - window_sum -= cardPoints[left] - left += 1 - - right += 1 - - return cards_sum - min_sum -``` - diff --git "a/Solutions/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.md" "b/Solutions/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 100f1814..00000000 --- "a/Solutions/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) - -- 标签:队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`,和一个表示限制的整数 `limit`。 - -要求:返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 `limit`。 - -如果不存在满足条件的子数组,则返回 `0`。 - -## 解题思路 - -求最长连续子数组,可以使用滑动窗口来解决。这道题目的难点在于如何维护滑动窗口内的最大值和最小值的差值。遍历滑动窗口求最大值和最小值,每次计算的时间复杂度为 $O(k)$,时间复杂度过高。考虑使用特殊的数据结构来降低时间复杂度。可以使用堆(优先队列)来解决。这里使用 `Python` 中 `heapq` 实现。具体做法如下: - -- 使用 `left`、`right` 两个指针,分别指向滑动窗口的左右边界,保证窗口中最大值和最小值的差值不超过 `limit`。 -- 一开始,`left`、`right` 都指向 `0`。 -- 向右移动 `right`,将最右侧元素加入当前窗口和大顶堆、小顶堆中。 -- 如果大顶堆堆顶元素和小顶堆堆顶元素大于 `limit`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口内的大顶堆、小顶堆。 -- 如果大顶堆堆顶元素和小顶堆堆顶元素小于等于 `limit`,则更新最长连续子数组长度。 -- 然后继续右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出答案。 - -## 代码 - -```python -import heapq - -class Solution: - def longestSubarray(self, nums: List[int], limit: int) -> int: - size = len(nums) - heap_max = [] - heap_min = [] - - ans = 0 - left, right = 0, 0 - while right < size: - heapq.heappush(heap_max, [-nums[right], right]) - heapq.heappush(heap_min, [nums[right], right]) - - while -heap_max[0][0] - heap_min[0][0] > limit: - while heap_min[0][1] <= left: - heapq.heappop(heap_min) - while heap_max[0][1] <= left: - heapq.heappop(heap_max) - left += 1 - ans = max(ans, right - left + 1) - right += 1 - - return ans -``` - diff --git "a/Solutions/1446. \350\277\236\347\273\255\345\255\227\347\254\246.md" "b/Solutions/1446. \350\277\236\347\273\255\345\255\227\347\254\246.md" deleted file mode 100644 index 4e125760..00000000 --- "a/Solutions/1446. \350\277\236\347\273\255\345\255\227\347\254\246.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [1446. 连续字符](https://leetcode.cn/problems/consecutive-characters/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -给你一个字符串 `s` ,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。 - -要求:返回字符串的能量。 - -注意: - -- `1 <= s.length <= 500` -- `s` 只包含小写英文字母。 - -## 解题思路 - -使用 `count` 统计连续不重复子串的长度,使用 `ans` 记录最长连续不重复子串的长度。 - -## 代码 - -```python -class Solution: - def maxPower(self, s: str) -> int: - ans = 1 - count = 1 - for i in range(1, len(s)): - if s[i] == s[i - 1]: - count += 1 - else: - count = 1 - ans = max(ans, count) - return ans -``` - diff --git "a/Solutions/1447. \346\234\200\347\256\200\345\210\206\346\225\260.md" "b/Solutions/1447. \346\234\200\347\256\200\345\210\206\346\225\260.md" deleted file mode 100644 index f81f7e4a..00000000 --- "a/Solutions/1447. \346\234\200\347\256\200\345\210\206\346\225\260.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [1447. 最简分数](https://leetcode.cn/problems/simplified-fractions/) - -- 标签:数学、字符串、数论 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:返回所有 $0$ 到 $1$ 之间(不包括 $0$ 和 $1$)满足分母小于等于 $n$ 的最简分数。分数可以以任意顺序返回。 - -**说明**: - -- $1 \le n \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 2 -输出:["1/2"] -解释:"1/2" 是唯一一个分母小于等于 2 的最简分数。 -``` - -- 示例 2: - -```python -输入:n = 4 -输出:["1/2","1/3","1/4","2/3","3/4"] -解释:"2/4" 不是最简分数,因为它可以化简为 "1/2"。 -``` - -## 解题思路 - -### 思路 1:数学 - -如果分子和分母的最大公约数为 $1$ 时,则当前分数为最简分数。 - -而 $n$ 的数据范围为 $(1, 100)$。因此我们可以使用两重遍历,分别枚举分子和分母,然后通过判断分子和分母是否为最大公约数,来确定当前分数是否为最简分数。 - -### 思路 1:代码 - -```python -class Solution: - def simplifiedFractions(self, n: int) -> List[str]: - res = [] - - for i in range(1, n): - for j in range(i + 1, n + 1): - if math.gcd(i, j) == 1: - res.append(str(i) + "/" + str(j)) - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2 \times \log n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" "b/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" deleted file mode 100644 index 8a5859e0..00000000 --- "a/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" +++ /dev/null @@ -1,119 +0,0 @@ -# [1449. 数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数数组 $cost$ 和一个整数 $target$。现在从 `""` 开始,不断通过以下规则得到一个新的整数: - -1. 给当前结果添加一个数位($i + 1$)的成本为 $cost[i]$($cost$ 数组下标从 $0$ 开始)。 -2. 总成本必须恰好等于 $target$。 -3. 添加的数位中没有数字 $0$。 - -**要求**:找到按照上述规则可以得到的最大整数。 - -**说明**: - -- 由于答案可能会很大,请你以字符串形式返回。 -- 如果按照上述要求无法得到任何整数,请你返回 `"0"`。 -- $cost.length == 9$。 -- $1 \le cost[i] \le 5000$。 -- $1 \le target \le 5000$。 - -**示例**: - -- 示例 1: - -```python -输入:cost = [4,3,2,5,6,7,2,5,5], target = 9 -输出:"7772" -解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "977" 也是满足要求的数字,但 "7772" 是较大的数字。 - 数字 成本 - 1 -> 4 - 2 -> 3 - 3 -> 2 - 4 -> 5 - 5 -> 6 - 6 -> 7 - 7 -> 2 - 8 -> 5 - 9 -> 5 -``` - -- 示例 2: - -```python -输入:cost = [7,6,5,5,5,6,8,7,8], target = 12 -输出:"85" -解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12。 - 数字 成本 - 1 -> 7 - 2 -> 6 - 3 -> 5 - 4 -> 5 - 5 -> 5 - 6 -> 6 - 7 -> 8 - 8 -> 7 - 9 -> 8 -``` - -## 解题思路 - -把每个数位($1 \sim 9$)看做是一件物品,$cost[i]$ 看做是物品的重量,一共有无数件物品可以使用,$target$ 看做是背包的载重上限,得到的最大整数可以看做是背包的最大价值。那么问题就变为了「完全背包问题」中的「恰好装满背包的最大价值问题」。 - -因为答案可能会很大,要求以字符串形式返回。这里我们可以直接令 $dp[w]$ 为字符串形式,然后定义一个 `def maxInt(a, b):` 方法用于判断两个字符串代表的数字大小。 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照背包载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大整数。 - -###### 3. 状态转移方程 - -$dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]])$ - -###### 4. 初始条件 - -1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为空字符串,即 `dp[0] = ""`。 -2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值为自定义字符 `"#"`,即 ,`dp[w] = "#"`,$0 \le w \le target$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 所以最终结果为 $dp[target]$。 - -### 思路 1:代码 - -```python -class Solution: - def largestNumber(self, cost: List[int], target: int) -> str: - def maxInt(a, b): - if len(a) == len(b): - return max(a, b) - if len(a) > len(b): - return a - return b - - size = len(cost) - dp = ["#" for _ in range(target + 1)] - dp[0] = "" - - for i in range(1, size + 1): - for w in range(cost[i - 1], target + 1): - if dp[w - cost[i - 1]] != "#": - dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]]) - if dp[target] == "#": - return "0" - return dp[target] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $cost$ 的元素个数,$target$ 为所给整数。 -- **空间复杂度**:$O(target)$。 diff --git "a/Solutions/1450. \345\234\250\346\227\242\345\256\232\346\227\266\351\227\264\345\201\232\344\275\234\344\270\232\347\232\204\345\255\246\347\224\237\344\272\272\346\225\260.md" "b/Solutions/1450. \345\234\250\346\227\242\345\256\232\346\227\266\351\227\264\345\201\232\344\275\234\344\270\232\347\232\204\345\255\246\347\224\237\344\272\272\346\225\260.md" deleted file mode 100644 index 0a94a339..00000000 --- "a/Solutions/1450. \345\234\250\346\227\242\345\256\232\346\227\266\351\227\264\345\201\232\344\275\234\344\270\232\347\232\204\345\255\246\347\224\237\344\272\272\346\225\260.md" +++ /dev/null @@ -1,273 +0,0 @@ -# [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:给你两个长度相等的整数数组,一个表示开始时间的数组 `startTime` ,另一个表示结束时间的数组 `endTime`。再给定一个整数 `queryTime` 作为查询时间。已知第 `i` 名学生在 `startTime[i]` 时开始写作业并于 `endTime[i]` 时完成作业。 - -**要求**:返回在查询时间 `queryTime` 时正在做作业的学生人数。即能够使 `queryTime` 处于区间 `[startTime[i], endTime[i]]` 的学生人数。 - -**说明**: - -- $startTime.length == endTime.length$。 -- $1\le startTime.length \le 100$。 -- $1 \le startTime[i] \le endTime[i] \le 1000$。 -- $1 \le queryTime \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:startTime = [4], endTime = [4], queryTime = 4 -输出:1 -解释:在查询时间只有一名学生在做作业。 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -- 维护一个用于统计在查询时间 `queryTime` 时正在做作业的学生人数的变量 `cnt`。然后遍历所有学生的开始时间和结束时间。 -- 如果 `queryTime` 在区间 `[startTime[i], endTime[i]]` 之间,即 `startTime[i] <= queryTime <= endTime[i]`,则令 `cnt` 加 `1`。 -- 遍历完输出统计人数 `cnt`。 - -### 思路 1:枚举算法代码 - -```python -class Solution: - def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: - cnt = 0 - size = len(startTime) - for i in range(size): - if startTime[i] <= queryTime <= endTime[i]: - cnt += 1 - return cnt -``` - -### 思路 2:线段树 - -- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 `[0, 1000]` 的线段树,初始化所有区间值都为 `0`。 -- 然后遍历所有学生的开始时间和结束时间,并将区间 `[startTime[i], endTime[i]]` 值加 `1`。 -- 在线段树中查询 `queryTime` 对应的单点区间 `[queryTime, queryTime]` 的最大值为多少。 - -### 思路 2:线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, val=0): - self.left = -1 # 区间左边界 - self.right = -1 # 区间右边界 - self.val = val # 节点值(区间值) - self.lazy_tag = None # 区间和问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, nums, function): - self.size = len(nums) - self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 - self.nums = nums # 原始数据 - self.function = function # function 是一个函数,左右区间的聚合方法 - if self.size > 0: - self.__build(0, 0, self.size - 1) - - # 单点更新接口:将 nums[i] 更改为 val - def update_point(self, i, val): - self.nums[i] = val - self.__update_point(i, val, 0) - - # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, 0) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, 0) - - # 获取 nums 数组接口:返回 nums 数组 - def get_nums(self): - for i in range(self.size): - self.nums[i] = self.query_interval(i, i) - return self.nums - - - # 以下为内部实现方法 - - # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] - def __build(self, index, left, right): - self.tree[index].left = left - self.tree[index].right = right - if left == right: # 叶子节点,节点值为对应位置的元素值 - self.tree[index].val = self.nums[left] - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.__build(left_index, left, mid) # 递归创建左子树 - self.__build(right_index, mid + 1, right) # 递归创建右子树 - self.__pushup(index) # 向上更新节点的区间值 - - # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index - def __update_point(self, i, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left == right: - self.tree[index].val = val # 叶子节点,节点值修改为 val - return - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if i <= mid: # 在左子树中更新节点值 - self.__update_point(i, val, left_index) - else: # 在右子树中更新节点值 - self.__update_point(i, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - if self.tree[index].lazy_tag is not None: - self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val - else: - self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - interval_size = (right - left + 1) # 当前节点所在区间大小 - self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val - return - - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - self.__pushdown(index) # 向下更新节点的区间值 - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - if q_left <= mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, left_index) - if q_right > mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, right_index) - - self.__pushup(index) # 向上更新节点的区间值 - - # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, index): - left = self.tree[index].left - right = self.tree[index].right - - if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return self.tree[index].val # 直接返回节点值 - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(index) - - mid = left + (right - left) // 2 # 左右节点划分点 - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, left_index) - if q_right > mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, right_index) - - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, index): - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) - - # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, index): - lazy_tag = self.tree[index].lazy_tag - if lazy_tag is None: - return - - left_index = index * 2 + 1 # 左子节点的存储下标 - right_index = index * 2 + 2 # 右子节点的存储下标 - - if self.tree[left_index].lazy_tag is not None: - self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 - else: - self.tree[left_index].lazy_tag = lazy_tag - left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) - self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag - - if self.tree[right_index].lazy_tag is not None: - self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 - else: - self.tree[right_index].lazy_tag = lazy_tag - right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) - self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag - - self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 - - -class Solution: - def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: - nums = [0 for _ in range(1010)] - self.STree = SegmentTree(nums, lambda x, y: max(x, y)) - size = len(startTime) - for i in range(size): - self.STree.update_interval(startTime[i], endTime[i], 1) - - return self.STree.query_interval(queryTime, queryTime) -``` - -### 思路 3:树状数组 - -- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 `[0, 1000]` 的树状数组。 -- 注意: - - 树状数组中 `update(self, index, delta):` 指的是将对应元素 `nums[index] ` 加上 `delta`。 - - `query(self, index):` 指的是 `index` 位置之前的元素和,即前缀和。 -- 然后遍历所有学生的开始时间和结束时间,将树状数组上 `startTime[i]` 的值增加 `1`,再将树状数组上`endTime[i]` 的值减少 `1`。 -- 则查询 `queryTime` 位置的前缀和即为答案。 - -### 思路 3:树状数组代码 - -```python -class BinaryIndexTree: - - def __init__(self, n): - self.size = n - self.tree = [0 for _ in range(n + 1)] - - def lowbit(self, index): - return index & (-index) - - def update(self, index, delta): - while index <= self.size: - self.tree[index] += delta - index += self.lowbit(index) - - def query(self, index): - res = 0 - while index > 0: - res += self.tree[index] - index -= self.lowbit(index) - return res - -class Solution: - def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: - bit = BinaryIndexTree(1010) - size = len(startTime) - for i in range(size): - bit.update(startTime[i], 1) - bit.update(endTime[i] + 1, -1) - return bit.query(queryTime) -``` - diff --git "a/Solutions/1456. \345\256\232\351\225\277\345\255\220\344\270\262\344\270\255\345\205\203\351\237\263\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" "b/Solutions/1456. \345\256\232\351\225\277\345\255\220\344\270\262\344\270\255\345\205\203\351\237\263\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" deleted file mode 100644 index 6424f7a9..00000000 --- "a/Solutions/1456. \345\256\232\351\225\277\345\255\220\344\270\262\344\270\255\345\205\203\351\237\263\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [1456. 定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) - -- 标签:字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定字符串 `s` 和整数 `k`。 - -要求:返回字符串 `s` 中长度为 `k` 的单个子字符串中可能包含的最大元音字母数。 - -注意:英文中的元音字母为(`a`, `e`, `i`, `o`, `u`)。 - -## 解题思路 - -固定长度的滑动窗口题目。维护一个长度为 `k` 的窗口,并统计滑动窗口中最大元音字母数。具体做法如下: - -1. `ans` 用来维护长度为 `k` 的单个字符串中最大元音字母数。`window_count` 用来维护窗口中元音字母数。集合 `vowel_set` 用来存储元音字母。 -2. `left` 、`right` 都指向字符串 `s` 的第一个元素,即:`left = 0`,`right = 0`。 -3. 判断 `s[right]` 是否在元音字母集合中,如果在则用 `window_count` 进行计数。 -4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,更新 `ans`。然后判断 `s[left]` 是否为元音字母,如果是则 `window_count -= 1`,并向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 -6. 最后输出 `ans`。 - -## 代码 - -```python -class Solution: - def maxVowels(self, s: str, k: int) -> int: - left, right = 0, 0 - ans = 0 - window_count = 0 - vowel_set = ('a','e','i','o','u') - - while right < len(s): - if s[right] in vowel_set: - window_count += 1 - - if right - left + 1 >= k: - ans = max(ans, window_count) - if s[left] in vowel_set: - window_count -= 1 - left += 1 - - right += 1 - return ans -``` - diff --git "a/Solutions/1480. \344\270\200\347\273\264\346\225\260\347\273\204\347\232\204\345\212\250\346\200\201\345\222\214.md" "b/Solutions/1480. \344\270\200\347\273\264\346\225\260\347\273\204\347\232\204\345\212\250\346\200\201\345\222\214.md" deleted file mode 100644 index b2a8b0da..00000000 --- "a/Solutions/1480. \344\270\200\347\273\264\346\225\260\347\273\204\347\232\204\345\212\250\346\200\201\345\222\214.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [1480. 一维数组的动态和](https://leetcode.cn/problems/running-sum-of-1d-array/) - -- 标签:数组、前缀和 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数组 $nums$。 - -**要求**:返回数组 $nums$ 的动态和。 - -**说明**: - -- **动态和**:数组前 $i$ 项元素和构成的数组,计算公式为 $runningSum[i] = \sum_{x = 0}^{x = i}(nums[i])$。 -- $1 \le nums.length \le 1000$。 -- $-10^6 \le nums[i] \le 10^6$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,4] -输出:[1,3,6,10] -解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4]。 -``` - -- 示例 2: - -```python -输入:nums = [1,1,1,1,1] -输出:[1,2,3,4,5] -解释:动态和计算过程为 [1, 1+1, 1+1+1, 1+1+1+1, 1+1+1+1+1]。 -``` - -## 解题思路 - -### 思路 1:递推 - -根据动态和的公式 $runningSum[i] = \sum_{x = 0}^{x = i}(nums[i])$,可以推导出: - -$runningSum = \begin{cases} nums[0], & i = 0 \cr runningSum[i - 1] + nums[i], & i > 0\end{cases}$ - -则解决过程如下: - -1. 新建一个长度等于 $nums$ 的数组 $res$ 用于存放答案。 -2. 初始化 $res[0] = nums[0]$。 -3. 从下标 $1$ 开始遍历数组 $nums$,递推更新 $res$,即:`res[i] = res[i - 1] + nums[i]`。 -4. 遍历结束,返回 $res$ 作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def runningSum(self, nums: List[int]) -> List[int]: - size = len(nums) - res = [0 for _ in range(size)] - for i in range(size): - if i == 0: - res[i] = nums[i] - else: - res[i] = res[i - 1] + nums[i] - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 -- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 - diff --git "a/Solutions/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260.md" "b/Solutions/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260.md" deleted file mode 100644 index 6615d1b1..00000000 --- "a/Solutions/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [1482. 制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `bloomDay`,以及两个整数 `m` 和 `k`。`bloomDay` 代表花朵盛开的时间,`bloomDay[i]` 表示第 `i` 朵花的盛开时间。盛开后就可以用于一束花中。 - -现在需要制作 `m` 束花。制作花束时,需要使用花园中相邻的 `k` 朵花 。 - -要求:返回从花园中摘 `m` 束花需要等待的最少的天数。如果不能摘到 `m` 束花则返回 `-1`。 - -## 解题思路 - -这道题跟「[0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/)」、「[1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/)」有点相似。 - -根据题目可知: - -- 制作花束最少使用时间跟花朵开花最短时间有关系,即 `min(bloomDay)`。 -- 制作花束最多使用时间跟花朵开花最长时间有关系,即 `max(bloomDay)`。 -- 则制作花束所需要的天数就变成了一个区间 `[min(bloomDay), max(bloomDay)]`。 - -那么,我们就可以根据这个区间,利用二分查找算法找到一个符合题意的最少天数。而判断某个天数下能否摘到 `m` 束花则可以写个方法判断。具体步骤如下: - -- 遍历数组 `bloomDay`。 - - 如果 `bloomDay[i]` 小于等于天数 `days`。就将花朵数量 + 1。 - - 当能摘的花朵数等于 `k` 时,能摘的花束数目 + 1,花朵数量置为 `0`。 - - 如果 `bloomDay[i]` 大于天数 `days`。就将花朵数置为 `0`。 -- 最后判断能摘的花束数目是否大于等于 `m`。 - -整个算法的步骤如下: - -- 如果 `m * k` 大于 `len(bloomDay)`,说明无法满足要求,直接返回 `-1`。 -- 使用两个指针 `left`、`right`。令 `left` 指向 `min(bloomDay)`,`right` 指向 `max(bloomDay)`。代表待查找区间为 `[left, right]`。 -- 取两个节点中心位置 `mid`,判断是否能在 `mid` 天制作 `m` 束花。 - - 如果不能,则将区间 `[left, mid]` 排除掉,继续在区间 `[mid + 1, right]` 中查找。 - - 如果能,说明天数还可以继续减少,则继续在区间 `[left, mid]` 中查找。 -- 当 `left == right` 时跳出循环,返回 `left`。 - -## 代码 - -```python -class Solution: - def canMake(self, bloomDay, days, m, k): - count = 0 - flower = 0 - for i in range(len(bloomDay)): - if bloomDay[i] <= days: - flower += 1 - if flower == k: - count += 1 - flower = 0 - else: - flower = 0 - return count >= m - - def minDays(self, bloomDay: List[int], m: int, k: int) -> int: - if m > len(bloomDay) / k: - return -1 - - left, right = min(bloomDay), max(bloomDay) - - while left < right: - mid = left + (right - left) // 2 - if not self.canMake(bloomDay, mid, m, k): - left = mid + 1 - else: - right = mid - - return left -``` - -## 参考资料 - -- 【题解】[【赤小豆】为什么是二分法,思路及模板 python - 制作 m 束花所需的最少天数 - 力扣(LeetCode)](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/solution/chi-xiao-dou-python-wei-shi-yao-shi-er-f-24p7/) - diff --git "a/Solutions/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234.md" "b/Solutions/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234.md" deleted file mode 100644 index c7f4486e..00000000 --- "a/Solutions/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234.md" +++ /dev/null @@ -1,84 +0,0 @@ -# [1486. 数组异或操作](https://leetcode.cn/problems/xor-operation-in-an-array/) - -- 标签:位运算、数学 -- 难度:简单 - -## 题目大意 - -给定两个整数 n、start。数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)。n 为数组长度。返回数组 nums 中所有元素按位异或(XOR)后得到的结果。 - -## 解题思路 - -### 1. 模拟 - -直接按照题目要求模拟即可。 - -### 2. 规律 - -- $x \oplus x = 0$; -- $x \oplus y = y \oplus x$(交换律); -- $(x \oplus y) \oplus z = x \oplus (y \oplus z)$(结合律); -- $x \oplus y \oplus y = x$(自反性); -- $\forall i \in Z$,有 $4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0$; -- $\forall i \in Z$,有 $2i \oplus (2i+1) = 1$; -- $\forall i \in Z$,有 $2i \oplus 1 = 2i+1$。 - -本题中计算的是 $start \oplus (start + 2) \oplus (start + 4) \oplus (start + 6) \oplus … \oplus (start+(2*(n-1)))$。 - -可以看出,若 start 为奇数,则 $start+2, start + 4, …, start + (2 \times(n - 1))$ 都为奇数。若 start 为偶数,则 $start + 2, start + 4, …, start + (2 \times(n - 1))$ 都为偶数。则它们对应二进制的最低位相同,则我们可以将最低位提取处理单独处理。从而将公式转换一下。 - -令 $s = \frac{start}{2}$,则等式变为 $(s) \oplus (s+1) \oplus (s+2) \oplus (s+3) \oplus … \oplus (s+(n-1)) * 2 + e$,e 表示运算结果的最低位。 - -根据自反性,$(s) \oplus (s+1) \oplus (s+2) \oplus (s+3) \oplus … \oplus (s+(n-1)) = \\ (1 \oplus 2 \oplus … \oplus (s-1)) \oplus (1 \oplus 2 \oplus … \oplus (s-1) \oplus (s) \oplus (s+1) \oplus … \oplus (s+(n-1)))$ - -例如: $3 \oplus 4 \oplus 5 \oplus 6 \oplus 7 = (1 \oplus 2) \oplus (1 \oplus 2 \oplus 3 \oplus 4 \oplus 5 \oplus 6 \oplus7)$ - -就变为了计算前 n 项序列的异或值。假设我们定义一个函数 sumXor(x) 用于计算前 n 项数的异或结果,通过观察可得出: - -$sumXor(x) = \begin{cases} \begin{array} \ x, & x = 4i, k \in Z \cr (x-1) \oplus x, & x = 4i+1, k \in Z \cr (x-2) \oplus (x-1) \oplus x, & x = 4i+2, k \in Z \cr (x-3) \oplus (x-2) \oplus (x-3) \oplus x, & x = 4i+3, k \in Z \end{array} \end{cases}$ - -继续化简得: - -$sumXor(x) = \begin{cases} \begin{array} \ x, & x = 4i, k \in Z \cr 1, & x = 4i+1, k \in Z \cr x+1, & x = 4i+2, k \in Z \cr 0, & x = 4i+3, k \in Z \end{array} \end{cases}$ - -则最终结果为 $sumXor(s-1) \oplus sumXor(s+n-1) * 2 + e$。 - -下面还有最后一位 e 的计算。 - -- 若 start 为偶数,则最后一位 e 为 0。 -- 若 start 为奇数,最后一位 e 跟 n 有关,若 n 为奇数,则最后一位 e 为 1,若 n 为偶数,则最后一位 e 为 0。 - -总结下来就是 `e = start & n & 1`。 - -## 代码 - -1. 模拟 - -```python -class Solution: - def xorOperation(self, n: int, start: int) -> int: - ans = 0 - for i in range(n): - ans ^= (start + i * 2) - return ans -``` - -2. 规律 - -```python -class Solution: - def sumXor(self, x): - if x % 4 == 0: - return x - if x % 4 == 1: - return 1 - if x % 4 == 2: - return x + 1 - return 0 - def xorOperation(self, n: int, start: int) -> int: - s = start >> 1 - e = n & start & 1 - ans = self.sumXor(s-1) ^ self.sumXor(s + n - 1) - return ans << 1 | e -``` - diff --git "a/Solutions/1491. \345\216\273\346\216\211\346\234\200\344\275\216\345\267\245\350\265\204\345\222\214\346\234\200\351\253\230\345\267\245\350\265\204\345\220\216\347\232\204\345\267\245\350\265\204\345\271\263\345\235\207\345\200\274.md" "b/Solutions/1491. \345\216\273\346\216\211\346\234\200\344\275\216\345\267\245\350\265\204\345\222\214\346\234\200\351\253\230\345\267\245\350\265\204\345\220\216\347\232\204\345\267\245\350\265\204\345\271\263\345\235\207\345\200\274.md" deleted file mode 100644 index c5bbf47a..00000000 --- "a/Solutions/1491. \345\216\273\346\216\211\346\234\200\344\275\216\345\267\245\350\265\204\345\222\214\346\234\200\351\253\230\345\267\245\350\265\204\345\220\216\347\232\204\345\267\245\350\265\204\345\271\263\345\235\207\345\200\274.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [1491. 去掉最低工资和最高工资后的工资平均值](https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/) - -- 标签:数组、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 `salary`,数组中的每一个数都是唯一的,其中 `salary[i]` 是第 `i` 个员工的工资。 - -**要求**:返回去掉最低工资和最高工资之后,剩下员工工资的平均值。 - -**说明**: - -- $3 \le salary.length \le 100$。 -- $10^3 \le salary[i] \le 10^6$。 -- $salary[i]$ 是唯一的。 -- 与真实值误差在 $10^{-5}$ 以内的结果都将视为正确答案。 - -**示例**: - -- 示例 1: - -```python -给定 salary = [1000,2000,3000] -输出 2000.00000 -解释 最低工资为 1000,最高工资为 3000,去除最低工资和最高工资之后,剩下员工工资的平均值为 2000 / 1 = 2000 -``` - -## 解题思路 - -### 思路 1: - -因为给定 $salary.length \ge 3$,并且 $salary[i]$ 是唯一的,所以无需考虑最低工资和最高工资是同一个。接下来就是按照题意模拟过程: - -- 计算出最小工资为 `min_s`,即 `min_s = min(salary)`。 -- 计算出最大工资为 `max_s`,即 `max_s = max(salary)`。 -- 计算出所有工资和之后再减去最小工资和最大工资,即 `total = sum(salary) - min_s - max_s`。 -- 求剩下工资的平均值,并返回,即 `return total / (len(salary) - 2)`。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def average(self, salary: List[int]) -> float: - min_s, max_s = min(salary), max(salary) - total = sum(salary) - min_s - max_s - return total / (len(salary) - 2) -``` - diff --git "a/Solutions/1493. \345\210\240\346\216\211\344\270\200\344\270\252\345\205\203\347\264\240\344\273\245\345\220\216\345\205\250\344\270\272 1 \347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204.md" "b/Solutions/1493. \345\210\240\346\216\211\344\270\200\344\270\252\345\205\203\347\264\240\344\273\245\345\220\216\345\205\250\344\270\272 1 \347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index b99a1782..00000000 --- "a/Solutions/1493. \345\210\240\346\216\211\344\270\200\344\270\252\345\205\203\347\264\240\344\273\245\345\220\216\345\205\250\344\270\272 1 \347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [1493. 删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) - -- 标签:数组、动态规划、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个二进制数组 `nums`,需要 - -要求:从数组中删掉一个元素,并返回最长的且只包含 `1` 的非空子数组的长度。如果不存在这样的子数组,请返回 `0`。 - -## 解题思路 - -维护一个元素值为 `0` 的元素数量少于 `1` 个的滑动窗口。则答案为滑动窗口长度减去窗口内 `0` 的个数求最大值。具体做法如下: - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口 `0` 的个数小于 `1` 个。使用 `window_count` 记录窗口中 `0` 的个数,使用 `ans` 记录删除一个元素后,最长的只包含 `1` 的非空子数组长度。 - -- 一开始,`left`、`right` 都指向 `0`。 - -- 如果最右侧元素等于 `0`,则 `window_count += 1` 。 - -- 如果 `window_count > 1` ,则不断右移 `left`,缩小滑动窗口长度。并更新当前窗口中 `0` 的个数,直到 `window_count <= 1`。 -- 更新答案值,然后向右移动 `right`,直到 `right >= len(nums)` 结束。 -- 输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def longestSubarray(self, nums: List[int]) -> int: - left, right = 0, 0 - window_count = 0 - ans = 0 - - while right < len(nums): - if nums[right] == 0: - window_count += 1 - - while window_count > 1: - if nums[left] == 0: - window_count -= 1 - left += 1 - ans = max(ans, right - left + 1 - window_count) - right += 1 - - if ans == len(nums): - return len(nums) - 1 - else: - return ans -``` - diff --git "a/Solutions/1502. \345\210\244\346\226\255\350\203\275\345\220\246\345\275\242\346\210\220\347\255\211\345\267\256\346\225\260\345\210\227.md" "b/Solutions/1502. \345\210\244\346\226\255\350\203\275\345\220\246\345\275\242\346\210\220\347\255\211\345\267\256\346\225\260\345\210\227.md" deleted file mode 100644 index 523a95d7..00000000 --- "a/Solutions/1502. \345\210\244\346\226\255\350\203\275\345\220\246\345\275\242\346\210\220\347\255\211\345\267\256\346\225\260\345\210\227.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [1502. 判断能否形成等差数列](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) - -- 标签:数组、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个数字数组 `arr`。如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数序就称为等差数列。 - -**要求**:如果数组 `arr` 通过重新排列可以形成等差数列,则返回 `True`;否则返回 `False`。 - -**说明**: - -- $2 \le arr.length \le 1000$ -- $-10^6 \le arr[i] \le 10^6$ - -**示例**: - -- 示例 1: - -```python -输入:arr = [3,5,1] -输出:True -解释:数组重新排序后得到 [1,3,5] 或者 [5,3,1],任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。 -``` - -## 解题思路 - -### 思路 1: - -- 如果数组元素个数小于等于 `2`,则数组肯定可以形成等差数列,直接返回 `True`。 -- 对数组进行排序。 -- 从下标为 `2` 的元素开始,遍历相邻的 `3` 个元素 `arr[i]` 、`arr[i - 1]`、`arr[i - 2]`。判断 `arr[i] - arr[i - 1]` 是否等于 `arr[i - 1] - arr[i - 2]`。如果不等于,则数组无法形成等差数列,返回 `False`。 -- 如果遍历完数组,则说明数组可以形成等差数列,返回 `True`。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def canMakeArithmeticProgression(self, arr: List[int]) -> bool: - size = len(arr) - if size <= 2: - return True - - arr.sort() - for i in range(2, size): - if arr[i] - arr[i - 1] != arr[i - 1] - arr[i - 2]: - return False - return True -``` - diff --git "a/Solutions/1507. \350\275\254\345\217\230\346\227\245\346\234\237\346\240\274\345\274\217.md" "b/Solutions/1507. \350\275\254\345\217\230\346\227\245\346\234\237\346\240\274\345\274\217.md" deleted file mode 100644 index 33e04697..00000000 --- "a/Solutions/1507. \350\275\254\345\217\230\346\227\245\346\234\237\346\240\274\345\274\217.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [1507. 转变日期格式](https://leetcode.cn/problems/reformat-date/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $date$,它的格式为 `Day Month Year` ,其中: - -- $Day$ 是集合 `{"1st", "2nd", "3rd", "4th", ..., "30th", "31st"}` 中的一个元素。 -- $Month$ 是集合 `{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}` 中的一个元素。 -- $Year$ 的范围在 $[1900, 2100]$ 之间。 - -**要求**:将字符串转变为 `YYYY-MM-DD` 的格式,其中: - -- $YYYY$ 表示 $4$ 位的年份。 -- $MM$ 表示 $2$ 位的月份。 -- $DD$ 表示 $2$ 位的天数。 - -**说明**: - -- 给定日期保证是合法的,所以不需要处理异常输入。 - -**示例**: - -- 示例 1: - -```python -输入:date = "20th Oct 2052" -输出:"2052-10-20" -``` - -- 示例 2: - -```python -输入:date = "6th Jun 1933" -输出:"1933-06-06" -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 将字符串分割为三部分,分别按照以下规则得到日、月、年: - 1. 日:去掉末尾两位英文字母,将其转为整型数字,并且进行补零操作,使其宽度为 $2$。 - 2. 月:使用哈希表将其映射为对应两位数字。 - 3. 年:直接赋值。 -2. 将得到的年、月、日使用 `"-"` 进行链接并返回。 - -### 思路 1:代码 - -```python -class Solution: - def reformatDate(self, date: str) -> str: - months = { - "Jan" : "01", "Feb" : "02", "Mar" : "03", "Apr" : "04", "May" : "05", "Jun" : "06", - "Jul" : "07", "Aug" : "08", "Sep" : "09", "Oct" : "10", "Nov" : "11", "Dec" : "12" - } - date_list = date.split(' ') - day = "{:0>2d}".format(int(date_list[0][: -2])) - month = months[date_list[1]] - year = date_list[2] - return year + "-" + month + "-" + day -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1523. \345\234\250\345\214\272\351\227\264\350\214\203\345\233\264\345\206\205\347\273\237\350\256\241\345\245\207\346\225\260\346\225\260\347\233\256.md" "b/Solutions/1523. \345\234\250\345\214\272\351\227\264\350\214\203\345\233\264\345\206\205\347\273\237\350\256\241\345\245\207\346\225\260\346\225\260\347\233\256.md" deleted file mode 100644 index 05e35d92..00000000 --- "a/Solutions/1523. \345\234\250\345\214\272\351\227\264\350\214\203\345\233\264\345\206\205\347\273\237\350\256\241\345\245\207\346\225\260\346\225\260\347\233\256.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [1523. 在区间范围内统计奇数数目](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个非负整数 `low` 和 `high`。 - -**要求**:返回 `low` 与 `high` 之间(包括二者)的奇数数目。 - -**说明**: - -- $0 \le low \le high \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:low = 3, high = 7 -输出:3 -解释:3 到 7 之间奇数数字为 [3,5,7] -``` - -## 解题思路 - -### 思路 1: - -暴力枚举 `[low, high]` 之间的奇数可能会超时。我们可以通过公式直接计算出 `[0, low - 1]` 之间的奇数个数和 `[0, high]` 之间的奇数个数,然后将两者相减即为答案。 - -计算奇数个数的公式为:$pre(x) = \lfloor \frac{x + 1}{2} \rfloor$。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def pre(self, val): - return (val + 1) >> 1 - - def countOdds(self, low: int, high: int) -> int: - return self.pre(high) - self.pre(low - 1) -``` - diff --git "a/Solutions/1534. \347\273\237\350\256\241\345\245\275\344\270\211\345\205\203\347\273\204.md" "b/Solutions/1534. \347\273\237\350\256\241\345\245\275\344\270\211\345\205\203\347\273\204.md" deleted file mode 100644 index 34311f48..00000000 --- "a/Solutions/1534. \347\273\237\350\256\241\345\245\275\344\270\211\345\205\203\347\273\204.md" +++ /dev/null @@ -1,130 +0,0 @@ -# [1534. 统计好三元组](https://leetcode.cn/problems/count-good-triplets/) - -- 标签:数组、枚举 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数组 $arr$,以及 $a$、$b$、$c$ 三个整数。 - -**要求**:统计其中好三元组的数量。 - -**说明**: - -- **好三元组**:如果三元组($arr[i]$、$arr[j]$、$arr[k]$)满足下列全部条件,则认为它是一个好三元组。 - - $0 \le i < j < k < arr.length$。 - - $| arr[i] - arr[j] | \le a$。 - - $| arr[j] - arr[k] | \le b$。 - - $| arr[i] - arr[k] | \le c$。 - -- $3 \le arr.length \le 100$。 -- $0 \le arr[i] \le 1000$。 -- $0 \le a, b, c \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [3,0,1,1,9,7], a = 7, b = 2, c = 3 -输出:4 -解释:一共有 4 个好三元组:[(3,0,1), (3,0,1), (3,1,1), (0,1,1)]。 -``` - -- 示例 2: - -```python -输入:arr = [1,1,2,2,3], a = 0, b = 0, c = 1 -输出:0 -解释:不存在满足所有条件的三元组。 -``` - -## 解题思路 - -### 思路 1:枚举 - -- 使用三重循环依次枚举所有的 $(i, j, k)$,判断对应 $arr[i]$、$arr[j]$、$arr[k]$ 是否满足条件。 -- 然后统计出所有满足条件的三元组的数量。 - -### 思路 1:代码 - -```python -class Solution: - def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int: - size = len(arr) - ans = 0 - - for i in range(size): - for j in range(i + 1, size): - for k in range(j + 1, size): - if abs(arr[i] - arr[j]) <= a and abs(arr[j] - arr[k]) <= b and abs(arr[i] - arr[k]) <= c: - ans += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^3)$,其中 $n$ 是数组 $arr$ 的长度。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:枚举优化 + 前缀和 - -我们可以先通过二重循环遍历二元组 $(j, k)$,找出所有满足 $| arr[j] - arr[k] | \le b$ 的二元组。 - -然后在 $| arr[j] - arr[k] | \le b$ 的条件下,我们需要找到满足以下要求的 $arr[i]$ 数量: - -1. $i < j$。 -2. $| arr[i] - arr[j] | \le a$。 -3. $| arr[i] - arr[k] | \le c$。 -4. $0 \le arr[i] \le 1000$。 - -其中 $2$、$3$ 去除绝对值之后可变为: - -1. $arr[j] - a \le arr[i] \le arr[j] + a$。 -2. $arr[k] - c \le arr[i] \le arr[k] + c$。 - -将这两个条件再结合第 $4$ 个条件综合一下就变为:$max(0, arr[j] - a, arr[k] - c) \le arr[i] \le min(arr[j] + a, arr[k] + c, 1000)$。 - -假如定义 $left = max(0, arr[j] - a, arr[k] - c)$,$right = min(arr[j] + a, arr[k] + c, 1000)$。 - -现在问题就转变了如何快速获取在值域区间 $[left, right]$ 中,有多少个 $arr[i]$。 - -我们可以利用前缀和数组,先计算出 $[0, 1000]$ 范围中,满足 $arr[i] < num$ 的元素个数,即为 $prefix\underline{}cnts[num]$。 - -然后对于区间 $[left, right]$,通过 $prefix\underline{}cnts[right] - prefix\underline{}cnts[left - 1]$ 即可快速求解出区间 $[left, right]$ 内 $arr[i]$ 的个数。 - -因为 $i < j < k$,所以我们可以在每次 $j$ 向右移动一位的时候,更新 $arr[j]$ 对应的前缀和数组,保证枚举到 $j$ 时,$prefix\underline{}cnts$ 存储对应元素值的个数足够正确。 - -### 思路 2:代码 - -```python -class Solution: - def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int: - size = len(arr) - ans = 0 - prefix_cnts = [0 for _ in range(1010)] - - for j in range(size): - for k in range(j + 1, size): - if abs(arr[j] - arr[k]) <= b: - left_j, right_j = arr[j] - a, arr[j] + a - left_k, right_k = arr[k] - c, arr[k] + c - left, right = max(0, left_j, left_k), min(1000, right_j, right_k) - if left <= right: - if left == 0: - ans += prefix_cnts[right] - else: - ans += prefix_cnts[right] - prefix_cnts[left - 1] - - for k in range(arr[j], 1001): - prefix_cnts[k] += 1 - - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n^2 + n \times S)$,其中 $n$ 是数组 $arr$ 的长度,$S$ 为数组的值域上限。 -- **空间复杂度**:$O(S)$。 - diff --git "a/Solutions/1547. \345\210\207\346\243\215\345\255\220\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" "b/Solutions/1547. \345\210\207\346\243\215\345\255\220\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" deleted file mode 100644 index eb0c67fe..00000000 --- "a/Solutions/1547. \345\210\207\346\243\215\345\255\220\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [1547. 切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) - -- 标签:数组、动态规划、排序 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$,代表一根长度为 $n$ 个单位的木根,木棍从 $0 \sim n$ 标记了若干位置。例如,长度为 $6$ 的棍子可以标记如下: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg) - -再给定一个整数数组 $cuts$,其中 $cuts[i]$ 表示需要将棍子切开的位置。 - -我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 - -每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 - -**要求**:返回切棍子的最小总成本。 - -**说明**: - -- $2 \le n \le 10^6$。 -- $1 \le cuts.length \le min(n - 1, 100)$。 -- $1 \le cuts[i] \le n - 1$。 -- $cuts$ 数组中的所有整数都互不相同。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e1.jpg) - -```python -输入:n = 7, cuts = [1,3,4,5] -输出:16 -解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 -第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。 -``` - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e11.jpg) - -- 示例 2: - -```python -输入:n = 9, cuts = [5,6,1,4,2] -输出:22 -解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -我们可以预先在数组 $cuts$ 种添加位置 $0$ 和位置 $n$,然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 - -###### 1. 划分阶段 - -按照区间长度进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 - -###### 3. 状态转移方程 - -假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k$,则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 - -而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 - -则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ - -###### 4. 初始条件 - -- 相邻位置之间没有切割点,不需要切割,最小成本为 $0$,即 $dp[i - 1][i] = 0$。 -- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 - -### 思路 1:代码 - -```python -class Solution: - def minCost(self, n: int, cuts: List[int]) -> int: - cuts.append(0) - cuts.append(n) - cuts.sort() - - size = len(cuts) - dp = [[float('inf') for _ in range(size)] for _ in range(size)] - for i in range(1, size): - dp[i - 1][i] = 0 - - for l in range(3, size + 1): # 枚举区间长度 - for i in range(size): # 枚举区间起点 - j = i + l - 1 # 根据起点和长度得到终点 - if j >= size: - continue - dp[i][j] = float('inf') - for k in range(i + 1, j): # 枚举区间分割点 - # 状态转移方程,计算合并区间后的最优值 - dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) - return dp[0][size - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m^3)$,其中 $m$ 为数组 $cuts$ 的元素个数。 -- **空间复杂度**:$O(m^2)$。 diff --git "a/Solutions/1551. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" "b/Solutions/1551. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" deleted file mode 100644 index 485dbc83..00000000 --- "a/Solutions/1551. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [1551. 使数组中所有元素相等的最小操作数](https://leetcode.cn/problems/minimum-operations-to-make-array-equal/) - -- 标签:数学 -- 难度:中等 - -## 题目大意 - -**描述**:存在一个长度为 $n$ 的数组 $arr$,其中 $arr[i] = (2 \times i) + 1$,$(0 \le i < n)$。 - -在一次操作中,我们可以选出两个下标,记作 $x$ 和 $y$($0 \le x, y < n$),并使 $arr[x]$ 减去 $1$,$arr[y]$ 加上 $1$)。最终目标是使数组中所有元素都相等。 - -现在给定一个整数 $n$,即数组 $arr$ 的长度。 - -**要求**:返回使数组 $arr$ 中所有元素相等所需要的最小操作数。 - -**说明**: - -- 题目测试用例将会保证:在执行若干步操作后,数组中的所有元素最终可以全部相等。 -- $1 \le n \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 3 -输出:2 -解释:arr = [1, 3, 5] -第一次操作选出 x = 2 和 y = 0,使数组变为 [2, 3, 4] -第二次操作继续选出 x = 2 和 y = 0,数组将会变成 [3, 3, 3] -``` - -- 示例 2: - -```python -输入:n = 6 -输出:9 -``` - -## 解题思路 - -### 思路 1:贪心 - -通过观察可以发现,数组中所有元素构成了一个等差数列,为了使所有元素相等,在每一次操作中,尽可能让较小值增大,让较大值减小,直到到达平均值为止,这样才能得到最小操作次数。 - -在一次操作中,我们可以同时让第 $i$ 个元素增大与第 $n - 1 - i$ 个元素减小。这样,我们只需要统计出数组前半部分元素变化幅度即可。 - -### 思路 1:代码 - -```python -class Solution: - def minOperations(self, n: int) -> int: - ans = 0 - for i in range(n // 2): - ans += n - 1 - 2 * i - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:贪心 + 优化 - -数组前半部分元素变化幅度的计算可以看做是一个等差数列求和,所以我们可以直接根据高斯求和公式求出结果。 - -$\lbrace n - 1 + [n - 1 - 2 * (n \div 2 - 1)]\rbrace \times (n \div 2) \div 2 = n \times n \div 4$ - -### 思路 2:代码 - -```python -class Solution: - def minOperations(self, n: int) -> int: - return n * n // 4 -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1556. \345\215\203\344\275\215\345\210\206\351\232\224\346\225\260.md" "b/Solutions/1556. \345\215\203\344\275\215\345\210\206\351\232\224\346\225\260.md" deleted file mode 100644 index 97de9a07..00000000 --- "a/Solutions/1556. \345\215\203\344\275\215\345\210\206\351\232\224\346\225\260.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [1556. 千位分隔数](https://leetcode.cn/problems/thousand-separator/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:每隔三位田间点(即 `"."` 符号)作为千位分隔符,并将结果以字符串格式返回。 - -**说明**: - -- $0 \le n \le 2^{31}$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 987 -输出:"987" -``` - -- 示例 2: - -```python -输入:n = 123456789 -输出:"123.456.789" -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 使用字符串变量 $ans$ 用于存储答案,使用一个计数器 $idx$ 来记录当前位数的个数。 -2. 将 $n$ 转为字符串 $s$ 后,从低位向高位遍历。 -3. 将当前数字 $s[i]$ 存入 $ans$ 中,计数器加 $1$,当计数器为 $3$ 的整数倍并且当前数字位不是最高位时,将 `"."` 存入 $ans$ 中。 -4. 遍历完成后,将 $ans$ 翻转后返回。 - -### 思路 1:代码 - -```python -class Solution: - def thousandSeparator(self, n: int) -> str: - s = str(n) - ans = "" - - idx = 0 - for i in range(len(s) - 1, -1, -1): - ans += s[i] - idx += 1 - if idx % 3 == 0 and i != 0: - ans += "." - - return ''.join(reversed(ans)) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/1561. \344\275\240\345\217\257\344\273\245\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\241\254\345\270\201\346\225\260\347\233\256.md" "b/Solutions/1561. \344\275\240\345\217\257\344\273\245\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\241\254\345\270\201\346\225\260\347\233\256.md" deleted file mode 100644 index fd8ae3ed..00000000 --- "a/Solutions/1561. \344\275\240\345\217\257\344\273\245\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\241\254\345\270\201\346\225\260\347\233\256.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [1561. 你可以获得的最大硬币数目](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/) - -- 标签:贪心、数组、数学、博弈、排序 -- 难度:中等 - -## 题目大意 - -有 `3*n` 堆数目不一的硬币,三个人按照下面的规则分硬币: - -- 每一轮选出任意 3 堆硬币。 -- Alice 拿走硬币数量最多的那一堆。 -- 我们自己拿走硬币数量第二多的那一堆。 -- Bob 拿走最后一堆。 -- 重复这个过程,直到没有更多硬币。 - -现在给定一个整数数组 `piles`,代表 `3*n` 堆硬币,其中 `piles[i]` 表示第 `i` 堆中硬币的数目。 - -## 解题思路 - -每次 `3` 堆,总共取 `n` 次。Bob 每次总是选择最少的一堆,所以最终 Bob 得到 `3*n` 堆中最少的 `n` 堆才能使得另外两个人获得更多。所以先对硬币堆进行排序。Bob 拿走最少的 `n` 堆。我们接着分剩下的 `2*n` 堆。 - -按照大小顺序,每次都选取硬币数目最多的两堆, Alice 取得较大的一堆,我们取较小的一堆。 - -然后继续在剩余堆中选取硬币数目最多的两堆,同样 Alice 取得较大的一堆,我们取较小的一堆。 - -只有这样才能在满足规则的情况下,使我们所获得硬币数最多。 - -最后统计我们所获取的硬币数,并返回结果。 - -## 代码 - -```python -class Solution: - def maxCoins(self, piles: List[int]) -> int: - piles.sort() - ans = 0 - for i in range(len(piles) // 3, len(piles), 2): - ans += piles[i] - return ans -``` - diff --git "a/Solutions/1567. \344\271\230\347\247\257\344\270\272\346\255\243\346\225\260\347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204\351\225\277\345\272\246.md" "b/Solutions/1567. \344\271\230\347\247\257\344\270\272\346\255\243\346\225\260\347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204\351\225\277\345\272\246.md" deleted file mode 100644 index 223a764c..00000000 --- "a/Solutions/1567. \344\271\230\347\247\257\344\270\272\346\255\243\346\225\260\347\232\204\346\234\200\351\225\277\345\255\220\346\225\260\347\273\204\351\225\277\345\272\246.md" +++ /dev/null @@ -1,67 +0,0 @@ -# [1567. 乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) - -- 标签:贪心、数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`。 - -要求:求出乘积为正数的最长子数组的长度。 - -- 子数组:是由原数组中零个或者更多个连续数字组成的数组。 - -## 解题思路 - -使用动态规划来做。使用数组 `pos` 表示以下标 `i` 结尾的乘积为正数的最长子数组长度。使用数组 `neg` 表示以下标 `i` 结尾的乘积为负数的最长子数组长度。 - -- 先初始化 `pos[0]`、`neg[0]`。 - - 如果 `nums[0] == 0`,则 `pos[0] = 0, neg[0] = 0`。 - - 如果 `nums[0] > 0`,则 `pos[0] = 1, neg[0] = 0`。 - - 如果 `nums[0] < 0`,则 `pos[0] = 0, neg[0] = 1`。 - -- 然后从下标 `1` 开始递推遍历数组 `nums`,对于 `nums[i - 1]` 和 `nums[i]`: - - - 如果 `nums[i - 1] == 0`,显然有 `pos[i] = 0`,`neg[i] = 0`。表示:以`i` 结尾的乘积为正数的最长子数组长度为 `0`,以`i` 结尾的乘积为负数数的最长子数组长度也为 `0`。 - - - 如果 `nums[i - 1] > 0`,则 `pos[i] = pos[i - 1] + 1`。而 `neg[i]` 需要进行判断,如果 `neg[i - 1] > 0`,则再乘以当前 `nums[i]` 后仍为负数,此时长度 +1,即 `neg[i] = neg[i - 1] + 1 `。而如果 `neg[i - 1] == 0`,则 `neg[i] = 0`。 - - - 如果 `nums[i - 1] < 0`,则 `pos[i]` 需要进行判断,如果 `neg[i - 1] > 0`,再乘以当前 `nums[i]` 后变为正数,此时长度 +1,即 `pos[i] = neg[i - 1] + 1`。而如果 `neg[i - 1] = 0`,则 `pos[i] = 0`。 - - 更新 `ans` 答案为 `pos[i]` 最大值。 - -- 最后输出答案 `ans`。 - -## 代码 - -```python -class Solution: - def getMaxLen(self, nums: List[int]) -> int: - size = len(nums) - pos = [0 for _ in range(size + 1)] - neg = [0 for _ in range(size + 1)] - - if nums[0] == 0: - pos[0], neg[0] = 0, 0 - elif nums[0] > 0: - pos[0], neg[0] = 1, 0 - else: - pos[0], neg[0] = 0, 1 - - ans = pos[0] - for i in range(1, size): - if nums[i] == 0: - pos[i] = 0 - neg[i] = 0 - elif nums[i] > 0: - pos[i] = pos[i - 1] + 1 - neg[i] = neg[i - 1] + 1 if neg[i - 1] > 0 else 0 - elif nums[i] < 0: - pos[i] = neg[i - 1] + 1 if neg[i - 1] > 0 else 0 - neg[i] = pos[i - 1] + 1 - ans = max(ans, pos[i]) - return ans -``` - -## 参考资料 - -- 【题解】[递推就完事了,巨好理解~ - 乘积为正数的最长子数组长度 - 力扣](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/solution/di-tui-jiu-wan-shi-liao-ju-hao-li-jie-by-time-limi/) diff --git "a/Solutions/1593. \346\213\206\345\210\206\345\255\227\347\254\246\344\270\262\344\275\277\345\224\257\344\270\200\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\346\234\200\345\244\247.md" "b/Solutions/1593. \346\213\206\345\210\206\345\255\227\347\254\246\344\270\262\344\275\277\345\224\257\344\270\200\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\346\234\200\345\244\247.md" deleted file mode 100644 index 9a113a2e..00000000 --- "a/Solutions/1593. \346\213\206\345\210\206\345\255\227\347\254\246\344\270\262\344\275\277\345\224\257\344\270\200\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\346\234\200\345\244\247.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [1593. 拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) - -- 标签:哈希表、字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。将字符串 `s` 拆分后可以得到若干非空子字符串,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是唯一的 。 - -要求:拆分该字符串,并返回拆分后唯一子字符串的最大数目。 - -注意:子字符串是字符串中的一个连续字符序列。 - -## 解题思路 - -维护一个全局变量 `ans` 用于记录拆分后唯一子字符串的最大数目。并使用集合 `s_set` 记录不重复的子串。 - -- 从下标为 `0` 开头的子串回溯。 -- 对于下标为 `index` 开头的子串,我们可以在 `index + 1` 开始到 `len(s) - 1` 的位置上,分别进行子串拆分,将子串拆分为 `s[index: i + 1]`。 - -- 如果当前子串不在 `s_set` 中,则将其存入 `s_set` 中,然后记录当前拆分子串个数,并从 `i + 1` 的位置进行下一层递归拆分。然后在拆分完,对子串进行回退操作。 -- 如果拆到字符串 `s` 的末尾,则记录并更新 `ans`。 -- 在开始位置还可以进行以下剪枝:如果剩余字符个数 + 当前子串个数 <= 当前拆分后子字符串的最大数目,则直接返回。 - -最后输出 `ans`。 - -## 代码 - -```python -class Solution: - ans = 0 - def backtrack(self, s, index, count, s_set): - if len(s) - index + count <= self.ans: - return - if index >= len(s): - self.ans = max(self.ans, count) - return - - for i in range(index, len(s)): - sub_s = s[index: i + 1] - if sub_s not in s_set: - s_set.add(sub_s) - self.backtrack(s, i + 1, count + 1, s_set) - s_set.remove(sub_s) - - - def maxUniqueSplit(self, s: str) -> int: - s_set = set() - self.ans = 0 - self.backtrack(s, 0, 0, s_set) - return self.ans -``` - diff --git "a/Solutions/1595. \350\277\236\351\200\232\344\270\244\347\273\204\347\202\271\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" "b/Solutions/1595. \350\277\236\351\200\232\344\270\244\347\273\204\347\202\271\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" deleted file mode 100644 index 879b7ae5..00000000 --- "a/Solutions/1595. \350\277\236\351\200\232\344\270\244\347\273\204\347\202\271\347\232\204\346\234\200\345\260\217\346\210\220\346\234\254.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [1595. 连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) - -- 标签:位运算、数组、动态规划、状态压缩、矩阵 -- 难度:困难 - -## 题目大意 - -**描述**:有两组点,其中一组中有 $size_1$ 个点,第二组中有 $size_2$ 个点,且 $size_1 \ge size_2$。现在给定一个大小为 $size_1 \times size_2$ 的二维数组 $cost$ 用于表示两组点任意两点之间的链接成本。其中 $cost[i][j]$ 表示第一组中第 $i$ 个点与第二组中第 $j$ 个点的链接成本。 - -如果两个组中每个点都与另一个组中的一个或多个点连接,则称这两组点是连通的。 - -**要求**:返回连通两组点所需的最小成本。 - -**说明**: - -- $size_1 == cost.length$。 -- $size_2 == cost[i].length$。 -- $1 \le size_1, size_2 \le 12$。 -- $size_1 \ge size_2$。 -- $0 \le cost[i][j] \le 100$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/09/20/ex1.jpg) - -```python -输入:cost = [[15, 96], [36, 2]] -输出:17 -解释:连通两组点的最佳方法是: -1--A -2--B -总成本为 17。 -``` - -- 示例 2: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/09/20/ex2.jpg) - -```python -输入:cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]] -输出:4 -解释:连通两组点的最佳方法是: -1--A -2--B -2--C -3--A -最小成本为 4。 -请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。 -``` - -## 解题思路 - -### 思路 1:状压 DP - - - -### 思路 1:代码 - -```python -class Solution: - def connectTwoGroups(self, cost: List[List[int]]) -> int: - m, n = len(cost), len(cost[0]) - states = 1 << n - dp = [[float('inf') for _ in range(states)] for _ in range(m + 1)] - dp[0][0] = 0 - for i in range(1, m + 1): - for state in range(states): - for j in range(n): - dp[i][state | (1 << j)] = min(dp[i][state | (1 << j)], dp[i - 1][state] + cost[i - 1][j], dp[i][state] + cost[i - 1][j]) - - return dp[m][states - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**: -- **空间复杂度**: - diff --git "a/Solutions/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237.md" "b/Solutions/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237.md" deleted file mode 100644 index d09f72cd..00000000 --- "a/Solutions/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [1603. 设计停车系统](https://leetcode.cn/problems/design-parking-system/) - -- 标签:设计、计数、模拟 -- 难度:简单 - -## 题目大意 - -给一个停车场设计一个停车系统。停车场总共有三种尺寸的车位:大、中、小,每种尺寸的车位分别有固定数目。 - -现在要求实现 `ParkingSystem` 类: - -- `ParkingSystem(big, medium, small)`:初始化 ParkingSystem 类,三个参数分别对应三种尺寸车位的数目。 -- `addCar(carType) -> bool:`:检测是否有 `carType` 对应的停车位,如果有,则将车停入车位,并返回 `True`,否则返回 `False`。 - -## 解题思路 - -使用不同成员变量存放车位数目。并根据给定操作进行判断。 - -## 代码 - -```python -class ParkingSystem: - - def __init__(self, big: int, medium: int, small: int): - self.park = [0, big, medium, small] - - def addCar(self, carType: int) -> bool: - if self.park[carType] == 0: - return False - self.park[carType] -= 1 - return True -``` - diff --git "a/Solutions/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" "b/Solutions/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" deleted file mode 100644 index fd3c48ea..00000000 --- "a/Solutions/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [1605. 给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) - -- 标签:贪心、数组、矩阵 -- 难度:中等 - -## 题目大意 - -**描述**:给你两个非负整数数组 `rowSum` 和 `colSum` ,其中 `rowSum[i]` 是二维矩阵中第 `i` 行元素的和,`colSum[j]` 是第 `j` 列元素的和。换句话说,我们不知道矩阵里的每个元素,只知道每一行的和,以及每一列的和。 - -**要求**:找到并返回一个大小为 `rowSum.length * colSum.length` 的任意非负整数矩阵,且该矩阵满足 `rowSum` 和 `colSum` 的要求。 - -**说明**: - -- 返回任意一个满足题目要求的二维矩阵即可,题目保证存在至少一个可行矩阵。 -- $1 \le rowSum.length, colSum.length \le 500$。 -- $0 \le rowSum[i], colSum[i] \le 10^8$。 -- $sum(rows) == sum(columns)$。 - -**示例**: - -- 示例 1: - -```python -输入:rowSum = [3,8], colSum = [4,7] -输出:[[3,0], - [1,7]] - -解释 -第 0 行:3 + 0 = 3 == rowSum[0] -第 1 行:1 + 7 = 8 == rowSum[1] -第 0 列:3 + 1 = 4 == colSum[0] -第 1 列:0 + 7 = 7 == colSum[1] -行和列的和都满足题目要求,且所有矩阵元素都是非负的。 -另一个可行的矩阵为 [[1,2], - [3,5]] -``` - -## 解题思路 - -### 思路 1:贪心算法 - -题目要求找出一个满足要求的非负整数矩阵,矩阵中元素值可以为 `0`。所以我们可以尽可能将大的值填入前面的行和列中,然后剩余位置用 `0` 补齐即可。具体做法如下: - -1. 使用二维数组 `board` 来保存答案,初始情况下,`board` 中元素全部赋值为 `0`。 -2. 遍历二维数组的每一行,每一列。当前位置下的值为当前行的和与当前列的和的较小值,即 `board[row][col] = min(rowSum[row], colSum[col])`。 -3. 更新当前行的和,将当前行的和减去 `board[row][col]`。 -4. 更新当前列的和,将当前列的和减去 `board[row][col]`。 -5. 遍历完返回二维数组 `board`。 - -### 思路 1:贪心算法代码 - -```python -class Solution: - def restoreMatrix(self, rowSum: List[int], colSum: List[int]) -> List[List[int]]: - rows, cols = len(rowSum), len(colSum) - board = [[0 for _ in range(cols)] for _ in range(rows)] - for row in range(rows): - for col in range(cols): - board[row][col] = min(rowSum[row], colSum[col]) - rowSum[row] -= board[row][col] - colSum[col] -= board[row][col] - return board -``` diff --git "a/Solutions/1617. \347\273\237\350\256\241\345\255\220\346\240\221\344\270\255\345\237\216\345\270\202\344\271\213\351\227\264\346\234\200\345\244\247\350\267\235\347\246\273.md" "b/Solutions/1617. \347\273\237\350\256\241\345\255\220\346\240\221\344\270\255\345\237\216\345\270\202\344\271\213\351\227\264\346\234\200\345\244\247\350\267\235\347\246\273.md" deleted file mode 100644 index 6100496d..00000000 --- "a/Solutions/1617. \347\273\237\350\256\241\345\255\220\346\240\221\344\270\255\345\237\216\345\270\202\344\271\213\351\227\264\346\234\200\345\244\247\350\267\235\347\246\273.md" +++ /dev/null @@ -1,94 +0,0 @@ -# [1617. 统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) - -- 标签:位运算、树、动态规划、状态压缩、枚举 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$,代表 $n$ 个城市,城市编号为 $1 \sim n$。同时给定一个大小为 $n - 1$ 的数组 $edges$,其中 $edges[i] = [u_i, v_i]$ 表示城市 $u_i$ 和 $v_i$ 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵树。 - -**要求**:返回一个大小为 $n - 1$ 的数组,其中第 $i$ 个元素(下标从 $1$ 开始)是城市间距离恰好等于 $i$ 的子树数目。 - -**说明**: - -- **两个城市间距离**:定义为它们之间需要经过的边的数目。 -- **一棵子树**:城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。 -- $2 \le n \le 15$。 -- $edges.length == n - 1$。 -- $edges[i].length == 2$。 -- $1 \le u_i, v_i \le n$。 -- 题目保证 $(ui, vi)$ 所表示的边互不相同。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4, edges = [[1,2],[2,3],[2,4]] -输出:[3,4,0] -解释: -子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。 -子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。 -不存在城市间最大距离为 3 的子树。 -``` - -- 示例 2: - -```python -输入:n = 2, edges = [[1,2]] -输出:[1] -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -因为题目中给定 $n$ 的范围为 $2 \le n \le 15$,范围比较小,我们可以通过类似「[0078. 子集](https://leetcode.cn/problems/subsets/)」中二进制枚举的方式,得到所有子树的子集。 - -而对于一个确定的子树来说,求子树中两个城市间距离就是在求子树的直径,这就跟 [「1245. 树的直径」](https://leetcode.cn/problems/tree-diameter/) 和 [「2246. 相邻字符不同的最长路径」](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) 一样了。 - -那么这道题的思路就变成了: - -1. 通过二进制枚举的方式,得到所有子树。 -2. 对于当前子树,通过树形 DP + 深度优先搜索的方式,计算出当前子树的直径。 -3. 统计所有子树直径中经过的不同边数个数,将其放入答案数组中。 - -### 思路 1:代码 - -```python -class Solution: - def countSubgraphsForEachDiameter(self, n: int, edges: List[List[int]]) -> List[int]: - graph = [[] for _ in range(n)] # 建图 - for u, v in edges: - graph[u - 1].append(v - 1) - graph[v - 1].append(u - 1) - - def dfs(mask, u): - nonlocal visited, diameter - visited |= 1 << u # 标记 u 访问过 - u_len = 0 # u 节点的最大路径长度 - for v in graph[u]: # 遍历 u 节点的相邻节点 - if (visited >> v) & 1 == 0 and mask >> v & 1: # v 没有访问过,且在子集中 - v_len = dfs(mask, v) # 相邻节点的最大路径长度 - diameter = max(diameter, u_len + v_len + 1) # 维护最大路径长度 - u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 - return u_len - - ans = [0 for _ in range(n - 1)] - - for mask in range(3, 1 << n): # 二进制枚举子集 - if mask & (mask - 1) == 0: # 子集至少需要两个点 - continue - visited = 0 - diameter = 0 - u = mask.bit_length() - 1 - dfs(mask, u) # 在子集 mask 中递归求树的直径 - if visited == mask: - ans[diameter - 1] += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为给定的城市数目。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204.md" "b/Solutions/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204.md" deleted file mode 100644 index 3fb02a0b..00000000 --- "a/Solutions/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个 `rows * cols` 大小的二维数组 `heights`,其中 `heights[i][j]` 表示为位置 `(i, j)` 的高度。 - -现在要从左上角 `(0, 0)` 位置出发,经过方格的一些点,到达右下角 `(n - 1, n - 1)` 位置上。其中所经过路径的花费为「这条路径上所有相邻位置的最大高度差绝对值」。 - -现在要求:计算从 `(0, 0)` 位置到 `(n - 1, n - 1)` 的最优路径的花费。 - -最优路径指的路径上「所有相邻位置最大高度差绝对值」最小的那条路径。 - -## 解题思路 - -将整个网络抽象为一个无向图,每个点与相邻的点(上下左右)之间都存在一条无向边,边的权重为两个点之间的高度差绝对值。 - -我们要找到左上角到右下角的最优路径,可以遍历所有的点,将所有的边存储到数组中,每条边的存储格式为 `[x, y, h]`,意思是编号 `x` 的点和编号为 `y` 的点之间的权重为 `h`。 - -然后按照权重从小到大的顺序,对所有边进行排序。 - -再按照权重大小遍历所有边,将其依次加入并查集中。并且每次都需要判断 `(0, 0)` 点和 `(n - 1, n - 1)` 点是否连通。 - -如果连通,则该边的权重即为答案。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def minimumEffortPath(self, heights: List[List[int]]) -> int: - row_size = len(heights) - col_size = len(heights[0]) - size = row_size * col_size - edges = [] - for row in range(row_size): - for col in range(col_size): - if row < row_size - 1: - x = row * col_size + col - y = (row + 1) * col_size + col - h = abs(heights[row][col] - heights[row + 1][col]) - edges.append([x, y, h]) - if col < col_size - 1: - x = row * col_size + col - y = row * col_size + col + 1 - h = abs(heights[row][col] - heights[row][col + 1]) - edges.append([x, y, h]) - - edges.sort(key=lambda x: x[2]) - - union_find = UnionFind(size) - - for edge in edges: - x, y, h = edge[0], edge[1], edge[2] - union_find.union(x, y) - if union_find.is_connected(0, size - 1): - return h - return 0 -``` - diff --git "a/Solutions/1646. \350\216\267\345\217\226\347\224\237\346\210\220\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/1646. \350\216\267\345\217\226\347\224\237\346\210\220\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 8358597d..00000000 --- "a/Solutions/1646. \350\216\267\345\217\226\347\224\237\346\210\220\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [1646. 获取生成数组中的最大值](https://leetcode.cn/problems/get-maximum-in-generated-array/) - -- 标签:数组、动态规划、模拟 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数 $n$,按照下述规则生成一个长度为 $n + 1$ 的数组 $nums$: - -- $nums[0] = 0$。 -- $nums[1] = 1$。 -- 当 $2 \le 2 \times i \le n$ 时,$nums[2 \times i] = nums[i]$。 -- 当 $2 \le 2 \times i + 1 \le n$ 时,$nums[2 \times i + 1] = nums[i] + nums[i + 1]$。 - -**要求**:返回生成数组 $nums$ 中的最大值。 - -**说明**: - -- $0 \le n \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 7 -输出:3 -解释:根据规则: - nums[0] = 0 - nums[1] = 1 - nums[(1 * 2) = 2] = nums[1] = 1 - nums[(1 * 2) + 1 = 3] = nums[1] + nums[2] = 1 + 1 = 2 - nums[(2 * 2) = 4] = nums[2] = 1 - nums[(2 * 2) + 1 = 5] = nums[2] + nums[3] = 1 + 2 = 3 - nums[(3 * 2) = 6] = nums[3] = 2 - nums[(3 * 2) + 1 = 7] = nums[3] + nums[4] = 2 + 1 = 3 -因此,nums = [0,1,1,2,1,3,2,3],最大值 3 -``` - -- 示例 2: - -```python -输入:n = 2 -输出:1 -解释:根据规则,nums[0]、nums[1] 和 nums[2] 之中的最大值是 1 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 按照题目要求,定义一个长度为 $n + 1$ 的数组 $nums$。 -2. 按照规则模拟生成对应的 $nums$ 数组元素。 -3. 求出数组 $nums$ 中最大值,并作为答案返回。 - -### 思路 1:代码 - -```python -class Solution: - def getMaximumGenerated(self, n: int) -> int: - if n <= 1: - return n - - nums = [0 for _ in range(n + 1)] - nums[1] = 1 - - for i in range(n): - if 2 * i <= n: - nums[2 * i] = nums[i] - if 2 * i + 1 <= n: - nums[2 * i + 1] = nums[i] + nums[i + 1] - - ans = max(nums) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1658. \345\260\206 x \345\207\217\345\210\260 0 \347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" "b/Solutions/1658. \345\260\206 x \345\207\217\345\210\260 0 \347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" deleted file mode 100644 index 8244e574..00000000 --- "a/Solutions/1658. \345\260\206 x \345\207\217\345\210\260 0 \347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) - -- 标签:数组、哈希表、二分查找、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -给你一个整数数组 `nums` 和一个整数 `x` 。每一次操作时,你应当移除数组 `nums` 最左边或最右边的元素,然后从 `x` 中减去该元素的值。请注意,需要修改数组以供接下来的操作使用。 - -要求:如果可以将 `x` 恰好减到 `0`,返回最小操作数;否则,返回 `-1`。 - -## 解题思路 - -将 `x` 减到 `0` 的最小操作数可以转换为求和等于 `sum(nums) - x` 的最长连续子数组长度。我们可以维护一个区间和为 `sum(nums) - x` 的滑动窗口,求出最长的窗口长度。具体做法如下: - -令 `target = sum(nums) - x`,使用 `max_len` 维护和等于 `target` 的最长连续子数组长度。然后用滑动窗口 `window_sum` 来记录连续子数组的和,设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中的和刚好等于 `target`。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。 -- 如果 `window_sum > target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum <= target`。 -- 如果 `window_sum == target`,则更新最长连续子数组长度。 -- 然后继续右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出 `len(nums) - max_len` 作为答案。 -- 注意判断题目中的特殊情况。 - -## 代码 - -```python -class Solution: - def minOperations(self, nums: List[int], x: int) -> int: - target = sum(nums) - x - size = len(nums) - if target < 0: - return -1 - if target == 0: - return size - left, right = 0, 0 - window_sum = 0 - max_len = float('-inf') - - while right < size: - window_sum += nums[right] - - while window_sum > target: - window_sum -= nums[left] - left += 1 - if window_sum == target: - max_len = max(max_len, right - left + 1) - right += 1 - return len(nums) - max_len if max_len != float('-inf') else -1 -``` - diff --git "a/Solutions/1672. \346\234\200\345\257\214\346\234\211\345\256\242\346\210\267\347\232\204\350\265\204\344\272\247\346\200\273\351\207\217.md" "b/Solutions/1672. \346\234\200\345\257\214\346\234\211\345\256\242\346\210\267\347\232\204\350\265\204\344\272\247\346\200\273\351\207\217.md" deleted file mode 100644 index e48b4072..00000000 --- "a/Solutions/1672. \346\234\200\345\257\214\346\234\211\345\256\242\346\210\267\347\232\204\350\265\204\344\272\247\346\200\273\351\207\217.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [1672. 最富有客户的资产总量](https://leetcode.cn/problems/richest-customer-wealth/) - -- 标签:数组、矩阵 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个 $m \times n$ 的整数网格 $accounts$,其中 $accounts[i][j]$ 是第 $i$ 位客户在第 $j$ 家银行托管的资产数量。 - -**要求**:返回最富有客户所拥有的资产总量。 - -**说明**: - -- 客户的资产总量:指的是他们在各家银行托管的资产数量之和。 -- 最富有客户:资产总量最大的客户。 -- $m == accounts.length$。 -- $n == accounts[i].length$。 -- $1 \le m, n \le 50$。 -- $1 \le accounts[i][j] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入:accounts = [[1,2,3],[3,2,1]] -输出:6 -解释: -第 1 位客户的资产总量 = 1 + 2 + 3 = 6 -第 2 位客户的资产总量 = 3 + 2 + 1 = 6 -两位客户都是最富有的,资产总量都是 6 ,所以返回 6。 -``` - -- 示例 2: - -```python -输入:accounts = [[1,5],[7,3],[3,5]] -输出:10 -解释: -第 1 位客户的资产总量 = 6 -第 2 位客户的资产总量 = 10 -第 3 位客户的资产总量 = 8 -第 2 位客户是最富有的,资产总量是 10,随意返回 10。 -``` - -## 解题思路 - -### 思路 1:直接模拟 - -1. 使用变量 $max\underline{}ans$ 存储最富有客户所拥有的资产总量。 -2. 遍历所有客户,对于当前客户 $accounts[i]$,统计其拥有的资产总量。 -3. 将当前客户的资产总量与 $max\underline{}ans$ 进行比较,如果大于 $max\underline{}ans$,则更新 $max\underline{}ans$ 的值。 -4. 遍历完所有客户,最终返回 $max\underline{}ans$ 作为结果。 - -### 思路 1:代码 - -```python -class Solution: - def maximumWealth(self, accounts: List[List[int]]) -> int: - max_ans = 0 - for i in range(len(accounts)): - total = 0 - for j in range(len(accounts[i])): - total += accounts[i][j] - if total > max_ans: - max_ans = total - return max_ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为二维数组 $accounts$ 的行数和列数。两重循环遍历的时间复杂度为 $O(m * n)$ 。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1695. \345\210\240\351\231\244\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" "b/Solutions/1695. \345\210\240\351\231\244\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" deleted file mode 100644 index 7f878721..00000000 --- "a/Solutions/1695. \345\210\240\351\231\244\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [1695. 删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) - -- 标签:数组、哈希表、滑动窗口 -- 难度:中等 - -## 题目大意 - -给你一个正整数数组 `nums`,从中删除一个含有若干不同元素的子数组。删除子数组的「得分」就是子数组各元素之和 。 - -要求:返回只删除一个子数组可获得的最大得分 。 - -## 解题思路 - -题目要求的是含有不同元素的连续子数组最大和,我们可以用滑动窗口来做,维护一个不包含重复元素的滑动窗口,计算最大的窗口和。具体方法如下: - -- 用滑动窗口 `window` 来记录不重复的元素个数,`window` 为哈希表类型。用 `window_sum` 来记录窗口内子数组元素和,`ans` 用来维护最大子数组和。设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中没有重复元素。 - -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧数组元素 `nums[right]` 加入当前窗口 `window` 中,记录该元素个数。 -- 如果该窗口中该元素的个数多于 `1` 个,即 `window[s[right]] > 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应元素的个数,直到 `window[s[right]] <= 1`。 -- 维护更新无重复元素的最大子数组和。然后右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出无重复元素的最大子数组和。 - -## 代码 - -```python -class Solution: - def maximumUniqueSubarray(self, nums: List[int]) -> int: - window_sum = 0 - left, right = 0, 0 - window = dict() - ans = 0 - while right < len(nums): - window_sum += nums[right] - if nums[right] not in window: - window[nums[right]] = 1 - else: - window[nums[right]] += 1 - - while window[nums[right]] > 1: - window[nums[left]] -= 1 - window_sum -= nums[left] - left += 1 - ans = max(ans, window_sum) - right += 1 - return ans -``` - diff --git "a/Solutions/1698. \345\255\227\347\254\246\344\270\262\347\232\204\344\270\215\345\220\214\345\255\220\345\255\227\347\254\246\344\270\262\344\270\252\346\225\260.md" "b/Solutions/1698. \345\255\227\347\254\246\344\270\262\347\232\204\344\270\215\345\220\214\345\255\220\345\255\227\347\254\246\344\270\262\344\270\252\346\225\260.md" deleted file mode 100644 index b258b851..00000000 --- "a/Solutions/1698. \345\255\227\347\254\246\344\270\262\347\232\204\344\270\215\345\220\214\345\255\220\345\255\227\347\254\246\344\270\262\344\270\252\346\225\260.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [1698. 字符串的不同子字符串个数](https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/) - -- 标签:字典树、字符串、后缀数组、哈希函数、滚动哈希 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:返回 `s` 的不同子字符串的个数。 - -注意:字符串的「子字符串」是由原字符串删除开头若干个字符(可能是 0 个)并删除结尾若干个字符(可能是 0 个)形成的字符串。 - -## 解题思路 - -构建一颗字典树。分别将原字符串删除开头若干个字符的子字符串依次插入到字典树中。 - -每次插入过程中碰到字典树中没有的字符节点时,说明此时插入的字符串可作为新的子字符串。 - -我们可以通过统计插入过程中新建字符节点的次数的方式来获取不同子字符串的个数。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> int: - """ - Inserts a word into the trie. - """ - cur = self - cnt = 0 - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cnt += 1 - cur = cur.children[ch] - cur.isEnd = True - return cnt - - -class Solution: - def countDistinct(self, s: str) -> int: - trie_tree = Trie() - cnt = 0 - for i in range(len(s)): - cnt += trie_tree.insert(s[i:]) - return cnt -``` - diff --git "a/Solutions/1710. \345\215\241\350\275\246\344\270\212\347\232\204\346\234\200\345\244\247\345\215\225\345\205\203\346\225\260.md" "b/Solutions/1710. \345\215\241\350\275\246\344\270\212\347\232\204\346\234\200\345\244\247\345\215\225\345\205\203\346\225\260.md" deleted file mode 100644 index 7de1b5ae..00000000 --- "a/Solutions/1710. \345\215\241\350\275\246\344\270\212\347\232\204\346\234\200\345\244\247\345\215\225\345\205\203\346\225\260.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [1710. 卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) - -- 标签:贪心、数组、排序 -- 难度:简单 - -## 题目大意 - -**描述**:现在需要将一些箱子装在一辆卡车上。给定一个二维数组 `boxTypes`,其中 `boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi]`。 - -`numberOfBoxesi` 是类型 `i` 的箱子的数量。``numberOfUnitsPerBoxi` 是类型 `i` 的每个箱子可以装载的单元数量。 - -再给定一个整数 `truckSize` 表示一辆卡车上可以装载箱子的最大数量。只要箱子数量不超过 `truckSize`,你就可以选择任意箱子装到卡车上。 - -**要求**:返回卡车可以装载的最大单元数量。 - -**说明**: - -- $1 \le boxTypes.length \le 1000$。 -- $1 \le numberOfBoxesi, numberOfUnitsPerBoxi \le 1000$。 -- $1 \le truckSize \le 106$。 - -**示例**: - -- 示例 1: - -```python -输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4 -输出:8 -解释 -箱子的情况如下: -- 1 个第一类的箱子,里面含 3 个单元。 -- 2 个第二类的箱子,每个里面含 2 个单元。 -- 3 个第三类的箱子,每个里面含 1 个单元。 -可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。 -单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8 -``` - -- 示例 2: - -```python -输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10 -输出:91 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -题目中,一辆卡车上可以装载箱子的最大数量是固定的(`truckSize`),那么如果想要使卡车上装载的单元数量最大,就应该优先选取装载单元数量多的箱子。 - -所以,从贪心算法的角度来考虑,我们应该按照每个箱子可以装载的单元数量对数组 `boxTypes` 从大到小排序。然后优先选取装载单元数量多的箱子。 - -下面我们使用贪心算法三步走的方法解决这道题。 - -1. **转换问题**:将原问题转变为,在 `truckSize` 的限制下,当选取完装载单元数量最多的箱子 `box` 之后,再解决剩下箱子(`truckSize - box[0]`)的选择问题(子问题)。 -2. **贪心选择性质**:对于当前 `truckSize`,优先选取装载单元数量最多的箱子。 -3. **最优子结构性质**:在上面的贪心策略下,当前 `truckSize` 的贪心选择 + 剩下箱子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得卡车可以装载的单元数量达到最大。 - -使用贪心算法的解决步骤描述如下: - -1. 对数组 `boxTypes` 按照每个箱子可以装载的单元数量从大到小排序。使用变量 `res` 记录卡车可以装载的最大单元数量。 -2. 遍历数组 `boxTypes`,对于当前种类的箱子 `box`: - 1. 如果 `truckSize > box[0]`,说明当前种类箱子可以全部装载。则答案数量加上该种箱子的单元总数,即 `box[0] * box[1]`,并且最大数量 `truckSize` 减去装载的箱子数。 - 2. 如果 `truckSize <= box[0]`,说明当前种类箱子只能部分装载。则答案数量加上 `truckSize * box[1]`,并跳出循环。 -3. 最后返回答案 `res`。 - -### 思路 1:代码 - -```python -class Solution: - def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int: - boxTypes.sort(key=lambda x:x[1], reverse=True) - res = 0 - for box in boxTypes: - if truckSize > box[0]: - res += box[0] * box[1] - truckSize -= box[0] - else: - res += truckSize * box[1] - break - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是数组 `boxTypes` 的长度。 -- **空间复杂度**:$O(\log n)$。 diff --git "a/Solutions/1716. \350\256\241\347\256\227\345\212\233\346\211\243\351\223\266\350\241\214\347\232\204\351\222\261.md" "b/Solutions/1716. \350\256\241\347\256\227\345\212\233\346\211\243\351\223\266\350\241\214\347\232\204\351\222\261.md" deleted file mode 100644 index a7f16813..00000000 --- "a/Solutions/1716. \350\256\241\347\256\227\345\212\233\346\211\243\351\223\266\350\241\214\347\232\204\351\222\261.md" +++ /dev/null @@ -1,93 +0,0 @@ -# [1716. 计算力扣银行的钱](https://leetcode.cn/problems/calculate-money-in-leetcode-bank/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -**描述**:Hercy 每天都往力扣银行里存钱。 - -最开始,他在周一的时候存入 $1$ 块钱。从周二到周日,他每天都比前一天多存入 $1$ 块钱。在接下来的每个周一,他都会比前一个周一多存入 $1$ 块钱。 - -给定一个整数 $n$。 - -**要求**:计算在第 $n$ 天结束的时候,Hercy 在力扣银行中总共存了多少块钱。 - -**说明**: - -- $1 \le n \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 4 -输出:10 -解释:第 4 天后,总额为 1 + 2 + 3 + 4 = 10。 -``` - -- 示例 2: - -```python -输入:n = 10 -输出:37 -解释:第 10 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4) = 37 。注意到第二个星期一,Hercy 存入 2 块钱。 -``` - -## 解题思路 - -### 思路 1:暴力模拟 - -1. 记录当前周 $week$ 和当前周的当前天数 $day$。 -2. 按照题目要求,每天增加 $1$ 块钱,每周一比上周一增加 $1$ 块钱。这样,每天存钱数为 $week + day - 1$。 -3. 将每天存的钱数累加起来即为答案。 - -### 思路 1:代码 - -```python -class Solution: - def totalMoney(self, n: int) -> int: - weak, day = 1, 1 - ans = 0 - for i in range(n): - ans += weak + day - 1 - day += 1 - if day == 8: - day = 1 - weak += 1 - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:等差数列计算优化 - -每周一比上周一增加 $1$ 块钱,则每周七天存钱总数比上一周多 $7$ 块钱。所以每周存的钱数是一个等差数列。我们可以通过高斯求和公式求出所有整周存的钱数,再计算出剩下天数存的钱数,两者相加即为答案。 - -### 思路 2:代码 - -```python -class Solution: - def totalMoney(self, n: int) -> int: - week_cnt = n // 7 - weak_first_money = (1 + 7) * 7 // 2 - weak_last_money = weak_first_money + 7 * (week_cnt - 1) - week_ans = (weak_first_money + weak_last_money) * week_cnt // 2 - - day_cnt = n % 7 - day_first_money = 1 + week_cnt - day_last_money = day_first_money + day_cnt - 1 - day_ans = (day_first_money + day_last_money) * day_cnt // 2 - - return week_ans + day_ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1720. \350\247\243\347\240\201\345\274\202\346\210\226\345\220\216\347\232\204\346\225\260\347\273\204.md" "b/Solutions/1720. \350\247\243\347\240\201\345\274\202\346\210\226\345\220\216\347\232\204\346\225\260\347\273\204.md" deleted file mode 100644 index 3fe8195c..00000000 --- "a/Solutions/1720. \350\247\243\347\240\201\345\274\202\346\210\226\345\220\216\347\232\204\346\225\260\347\273\204.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [1720. 解码异或后的数组](https://leetcode.cn/problems/decode-xored-array/) - -- 标签:位运算、数组 -- 难度:简单 - -## 题目大意 - -n 个非负整数构成数组 arr,经过编码后变为长度为 n-1 的整数数组 encoded,其中 `encoded[i] = arr[i] XOR arr[i+1]`。例如 arr = [1, 0, 2, 1] 经过编码后变为 encoded = [1, 2, 3]。 - -现在给定编码后的数组 encoded 和原数组 arr 的第一个元素 arr[0]。要求返回原数组 arr。 - -## 解题思路 - -首先要了解异或的性质: - -- 异或运算满足交换律和结合律。 - - 交换律:`a^b = b^a` - - 结合律:`(a^b)^c = a^(b^c)` -- 任何整数和自身做异或运算结果都为 0,即 `x^x = 0`。 -- 任何整数和 0 做异或运算结果都为其本身,即 `x^0 = 0`。 - -已知当 $1 \le i \le n$ 时,有 `encoded[i-1] = arr[i-1] XOR arr[i]`。两边同时「异或」上 arr[i-1]。得: - -- `encoded[i-1] XOR arr[i-1] = arr[i-1] XOR arr[i] XOR arr[i-1]` -- `encoded[i-1] XOR arr[i-1] = arr[i] XOR 0` -- `encoded[i-1] XOR arr[i-1] = arr[i]` - -所以就可以根据所得结论 `arr[i] = encoded[i-1] XOR arr[i-1]` 模拟得出原数组 arr。 - -## 代码 - -```python -class Solution: - def decode(self, encoded: List[int], first: int) -> List[int]: - n = len(encoded) + 1 - arr = [0] * n - arr[0] = first - for i in range(1, n): - arr[i] = encoded[i-1] ^ arr[i-1] - return arr -``` - diff --git "a/Solutions/1736. \346\233\277\346\215\242\351\232\220\350\227\217\346\225\260\345\255\227\345\276\227\345\210\260\347\232\204\346\234\200\346\231\232\346\227\266\351\227\264.md" "b/Solutions/1736. \346\233\277\346\215\242\351\232\220\350\227\217\346\225\260\345\255\227\345\276\227\345\210\260\347\232\204\346\234\200\346\231\232\346\227\266\351\227\264.md" deleted file mode 100644 index 3f67c5b0..00000000 --- "a/Solutions/1736. \346\233\277\346\215\242\351\232\220\350\227\217\346\225\260\345\255\227\345\276\227\345\210\260\347\232\204\346\234\200\346\231\232\346\227\266\351\227\264.md" +++ /dev/null @@ -1,83 +0,0 @@ -# [1736. 替换隐藏数字得到的最晚时间](https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/) - -- 标签:贪心、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $time$,格式为 `hh:mm`(小时:分钟),其中某几位数字被隐藏(用 `?` 表示)。 - -**要求**:替换 $time$ 中隐藏的数字,返回你可以得到的最晚有效时间。 - -**说明**: - -- **有效时间**: `00:00` 到 `23:59` 之间的所有时间,包括 `00:00` 和 `23:59`。 -- $time$ 的格式为 `hh:mm`。 -- 题目数据保证你可以由输入的字符串生成有效的时间。 - -**示例**: - -- 示例 1: - -```python -输入:time = "2?:?0" -输出:"23:50" -解释:以数字 '2' 开头的最晚一小时是 23 ,以 '0' 结尾的最晚一分钟是 50。 -``` - -- 示例 2: - -```python -输入:time = "0?:3?" -输出:"09:39" -``` - -## 解题思路 - -### 思路 1:贪心算法 - -为了使有效时间尽可能晚,我们可以从高位到低位依次枚举所有符号为 `?` 的字符。在保证时间有效的前提下,每一位上取最大值,并进行保存。具体步骤如下: - -- 如果第 $1$ 位为 `?`: - - 如果第 $2$ 位已经确定,并且范围在 $[4, 9]$ 中间,则第 $1$ 位最大为 $1$; - - 否则第 $1$ 位最大为 $2$。 -- 如果第 $2$ 位为 `?`: - - 如果第 $1$ 位上值为 $2$,则第 $2$ 位最大可以为 $3$; - - 否则第 $2$ 位最大为 $9$。 -- 如果第 $3$ 位为 `?`: - - 第 $3$ 位最大可以为 $5$。 -- 如果第 $4$ 位为 `?`: - - 第 $4$ 位最大可以为 $9$。 - -### 思路 1:代码 - -```python -class Solution: - def maximumTime(self, time: str) -> str: - time_list = list(time) - if time_list[0] == '?': - if '4' <= time_list[1] <= '9': - time_list[0] = '1' - else: - time_list[0] = '2' - - if time_list[1] == '?': - if time_list[0] == '2': - time_list[1] = '3' - else: - time_list[1] = '9' - - if time_list[3] == '?': - time_list[3] = '5' - - if time_list[4] == '?': - time_list[4] = '9' - - return "".join(time_list) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1742. \347\233\222\345\255\220\344\270\255\345\260\217\347\220\203\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" "b/Solutions/1742. \347\233\222\345\255\220\344\270\255\345\260\217\347\220\203\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" deleted file mode 100644 index 8bc4768e..00000000 --- "a/Solutions/1742. \347\233\222\345\255\220\344\270\255\345\260\217\347\220\203\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" +++ /dev/null @@ -1,118 +0,0 @@ -# [1742. 盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) - -- 标签:哈希表、数学、计数 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个整数 $lowLimit$ 和 $highLimt$,代表 $n$ 个小球的编号(包括 $lowLimit$ 和 $highLimit$,即 $n == highLimit = lowLimit + 1$)。另外有无限个盒子。 - -现在的工作是将每个小球放入盒子中,其中盒子的编号应当等于小球编号上每位数字的和。例如,编号 $321$ 的小球应当放入编号 $3 + 2 + 1 = 6$ 的盒子,而编号 $10$ 的小球应当放入编号 $1 + 0 = 1$ 的盒子。 - -**要求**:返回放有最多小球的盒子中的小球数量。如果有多个盒子都满足放有最多小球,只需返回其中任一盒子的小球数量。 - -**说明**: - -- $1 \le lowLimit \le highLimit \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:lowLimit = 1, highLimit = 10 -输出:2 -解释: -盒子编号:1 2 3 4 5 6 7 8 9 10 11 ... -小球数量:2 1 1 1 1 1 1 1 1 0 0 ... -编号 1 的盒子放有最多小球,小球数量为 2。 -``` - -- 示例 2: - -```python -输入:lowLimit = 5, highLimit = 15 -输出:2 -解释: -盒子编号:1 2 3 4 5 6 7 8 9 10 11 ... -小球数量:1 1 1 1 2 2 1 1 1 0 0 ... -编号 5 和 6 的盒子放有最多小球,每个盒子中的小球数量都是 2。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $lowLimit$、$highLimit$ 转为字符串 $s1$、$s2$,并将 $s1$ 补上前导 $0$,令其与 $s2$ 长度一致。定义递归函数 `def dfs(pos, remainTotal, isMaxLimit, isMinLimit):` 表示构造第 $pos$ 位及之后剩余数位和为 $remainTotal$ 的合法方案数。 - -因为数据范围为 $[1, 10^5]$,对应数位和范围为 $[1, 45]$。因此我们可以枚举所有的数位和,并递归调用 `dfs(i, remainTotal, isMaxLimit, isMinLimit)`,求出不同数位和对应的方案数,并求出最大方案数。 - -接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, i, True, True)` 开始递归。 `dfs(0, i, True, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 剩余数位和为 $i$。 - 3. 开始时当前数位最大值受到最高位数位的约束。 - 4. 开始时当前数位最小值受到最高位数位的约束。 - -2. 如果剩余数位和小于 $0$,说明当前方案不符合要求,则返回方案数 $0$。 - -3. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果剩余数位和 $remainTotal$ 等于 $0$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果剩余数位和 $remainTotal$ 不等于 $0$,说明当前方案不符合要求,则返回方案数 $0$。 - -4. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -5. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -6. 根据 $isMaxLimit$ 和 $isMinLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 - -7. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 -8. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, remainTotal - d, isMaxLimit and d == maxX, isMinLimit and d == minX)`。 - 1. `remainTotal - d` 表示当前剩余数位和减去 $d$。 - 2. `isMaxLimit and d == maxX` 表示 $pos + 1$ 位最大值受到之前 $pos$ 位限制。 - 3. `isMinLimit and d == maxX` 表示 $pos + 1$ 位最小值受到之前 $pos$ 位限制。 -9. 最后返回所有 `dfs(0, i, True, True)` 中最大的方案数即可。 - -### 思路 1:代码 - -```python -class Solution: - def countBalls(self, lowLimit: int, highLimit: int) -> int: - s1, s2 = str(lowLimit), str(highLimit) - - m, n = len(s1), len(s2) - if m < n: - s1 = '0' * (n - m) + s1 - - @cache - # pos: 第 pos 个数位 - # remainTotal: 表示剩余数位和 - # isMaxLimit: 表示是否受到上限选择限制。如果为真,则第 pos 位填入数字最多为 s2[pos];如果为假,则最大可为 9。 - # isMinLimit: 表示是否受到下限选择限制。如果为真,则第 pos 位填入数字最小为 s1[pos];如果为假,则最小可为 0。 - def dfs(pos, remainTotal, isMaxLimit, isMinLimit): - if remainTotal < 0: - return 0 - if pos == n: - # remainTotal 为 0,则表示当前方案符合要求 - return int(remainTotal == 0) - - ans = 0 - # 如果前一位没有填写数字,或受到选择限制,则最小可选择数字为 s1[pos],否则最少为 0(可以含有前导 0)。 - minX = int(s1[pos]) if isMinLimit else 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s2[pos]) if isMaxLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - ans += dfs(pos + 1, remainTotal - d, isMaxLimit and d == maxX, isMinLimit and d == minX) - return ans - - ans = 0 - for i in range(46): - ans = max(ans, dfs(0, i, True, True)) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n \times 45)$。 -- **空间复杂度**:$O(\log n)$。 diff --git "a/Solutions/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 3752bd37..00000000 --- "a/Solutions/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,77 +0,0 @@ -# [1749. 任意子数组和的绝对值的最大值](https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:找出 $nums$ 中「和的绝对值」最大的任意子数组(可能为空),并返回最大值。 - -**说明**: - -- **子数组 $[nums_l, nums_{l+1}, ..., nums_{r-1}, nums_{r}]$ 的和的绝对值**:$abs(nums_l + nums_{l+1} + ... + nums_{r-1} + nums_{r})$。 -- $abs(x)$ 定义如下: - - 如果 $x$ 是负整数,那么 $abs(x) = -x$。 - - 如果 $x$ 是非负整数,那么 $abs(x) = x$。 - -- $1 \le nums.length \le 10^5$。 -- $-10^4 \le nums[i] \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,-3,2,3,-4] -输出:5 -解释:子数组 [2,3] 和的绝对值最大,为 abs(2+3) = abs(5) = 5。 -``` - -- 示例 2: - -```python -输入:nums = [2,-5,1,-4,3,-2] -输出:8 -解释:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8。 -``` - -## 解题思路 - -### 思路 1:动态规划 - -子数组和的绝对值的最大值,可能来自于「连续子数组的最大和」,也可能来自于「连续子数组的最小和」。 - -而求解「连续子数组的最大和」,我们可以参考「[0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/)」的做法,使用一个变量 $mmax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。使用另一个变量 $mmin$ 来表示以第 $i$ 个数结尾的连续子数组的最小和。然后取两者绝对值的最大值为答案 $ans$。 - -具体步骤如下: - -1. 遍历数组 $nums$,对于当前元素 $nums[i]$: - 1. 如果 $mmax < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,所以 $mmax$ 应取「第 $i$ 个数的值」,即:$mmax = nums[i]$。 - 2. 如果 $mmax \ge 0$ ,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,所以 $mmax$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」,即:$mmax = mmax + nums[i]$。 - 3. 如果 $mmin > 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」>「第 $i$ 个数的值」,所以 $mmax$ 应取「第 $i$ 个数的值」,即:$mmax = nums[i]$。 - 4. 如果 $mmin \le 0$ ,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 <= 第 $i$ 个数的值,所以 $mmax$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」,即:$mmin = mmin + nums[i]$。 - 5. 维护答案 $ans$,将 $mmax$ 和 $mmin$ 绝对值的最大值与 $ans$ 进行比较,并更新 $ans$。 -2. 遍历完返回答案 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def maxAbsoluteSum(self, nums: List[int]) -> int: - ans = 0 - mmax, mmin = 0, 0 - for num in nums: - mmax = max(mmax, 0) + num - mmin = min(mmin, 0) + num - ans = max(ans, mmax, -mmin) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1779. \346\211\276\345\210\260\346\234\200\350\277\221\347\232\204\346\234\211\347\233\270\345\220\214 X \346\210\226 Y \345\235\220\346\240\207\347\232\204\347\202\271.md" "b/Solutions/1779. \346\211\276\345\210\260\346\234\200\350\277\221\347\232\204\346\234\211\347\233\270\345\220\214 X \346\210\226 Y \345\235\220\346\240\207\347\232\204\347\202\271.md" deleted file mode 100644 index bc10c3cb..00000000 --- "a/Solutions/1779. \346\211\276\345\210\260\346\234\200\350\277\221\347\232\204\346\234\211\347\233\270\345\220\214 X \346\210\226 Y \345\235\220\346\240\207\347\232\204\347\202\271.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [1779. 找到最近的有相同 X 或 Y 坐标的点](https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个整数 `x` 和 `y`,表示笛卡尔坐标系下的 `(x, y)` 点。再给定一个数组 `points`,其中 `points[i] = [ai, bi]`,表示在 `(ai, bi)` 处有一个点。当一个点与 `(x, y)` 拥有相同的 `x` 坐标或者拥有相同的 `y` 坐标时,我们称这个点是有效的。 - -**要求**:返回数组中距离 `(x, y)` 点出曼哈顿距离最近的有效点在 `points` 中的下标位置。如果有多个最近的有效点,则返回下标最小的一个。如果没有有效点,则返回 `-1`。 - -**说明**: - -- **曼哈顿距离**:`(x1, y1)` 和 `(x2, y2)` 之间的曼哈顿距离为 `abs(x1 - x2) + abs(y1 - y2)` 。 -- $1 \le points.length \le 10^4$。 -- $points[i].length == 2$。 -- $1 \le x, y, ai, bi \le 10^4$。 - -**示例**: - -- 示例 1: - -```python -输入:x = 3, y = 4, points = [[1, 2], [3, 1], [2, 4], [2, 3], [4, 4]] -输出:2 -解释:在所有点中 [3, 1]、[2, 4]、[4, 4] 为有效点。其中 [2, 4]、[4, 4] 距离 [3, 4] 曼哈顿距离最近,都为 1。[2, 4] 下标最小,所以返回 2。 -``` - -## 解题思路 - -### 思路 1: - -- 使用 `min_dist` 记录下有效点中最近的曼哈顿距离,初始化为 `float('inf')`。使用 `min_index` 记录下符合要求的最小下标。 -- 遍历 `points` 数组,遇到有效点之后计算一下当前有效点与 `(x, y)` 的曼哈顿距离,并判断更新一下有效点中最近的曼哈顿距离 `min_dist` 和符合要求的最小下标 `min_index`。 -- 遍历完之后,判断一下 `min_dist` 是否等于 `float('inf')`。如果等于,说明没有找到有效点,则返回 `-1`。如果不等于,则返回符合要求的最小下标 `min_index`。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def nearestValidPoint(self, x: int, y: int, points: List[List[int]]) -> int: - min_dist = float('inf') - min_index = 0 - for i in range(len(points)): - if points[i][0] == x or points[i][1] == y: - dist = abs(points[i][0] - x) + abs(points[i][1] - y) - if dist < min_dist: - min_dist = dist - min_index = i - - if min_dist == float('inf'): - return -1 - return min_index -``` - diff --git "a/Solutions/1790. \344\273\205\346\211\247\350\241\214\344\270\200\346\254\241\345\255\227\347\254\246\344\270\262\344\272\244\346\215\242\350\203\275\345\220\246\344\275\277\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" "b/Solutions/1790. \344\273\205\346\211\247\350\241\214\344\270\200\346\254\241\345\255\227\347\254\246\344\270\262\344\272\244\346\215\242\350\203\275\345\220\246\344\275\277\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" deleted file mode 100644 index 06795403..00000000 --- "a/Solutions/1790. \344\273\205\346\211\247\350\241\214\344\270\200\346\254\241\345\255\227\347\254\246\344\270\262\344\272\244\346\215\242\350\203\275\345\220\246\344\275\277\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [1790. 仅执行一次字符串交换能否使两个字符串相等](https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/) - -- 标签:哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个长度相等的字符串 `s1` 和 `s2`。 - -已知一次「字符串交换操作」步骤如下:选出某个字符串中的两个下标(不一定要相同),并交换这两个下标所对应的字符。 - -**要求**:如果对其中一个字符串执行最多一次字符串交换可以使两个字符串相等,则返回 `True`;否则返回 `False`。 - -**说明**: - -- $1 \le s1.length, s2.length \le 100$。 -- $s1.length == s2.length$。 -- `s1` 和 `s2` 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -```python -给定:s1 = "bank", s2 = "kanb" -输出:True -解释:交换 s1 中的第一个和最后一个字符可以得到 "kanb",与 s2 相同 -``` - -## 解题思路 - -### 思路 1: - -- 用一个变量 `diff_cnt` 记录两个字符串中对应位置上出现不同字符的次数。用 `c1`、`c2` 记录第一次出现不同字符时两个字符串对应位置上的字符。 -- 遍历两个字符串,对于第 `i` 个位置的字符 `s1[i]` 和 `s2[i]`: - - 如果 `s1[i] == s2[i]`,继续判断下一个位置。 - - 如果 `s1[i] != s2[i]`,则出现不同字符的次数加 `1`。 - - 如果出现不同字符的次数等于 `1`,则记录第一次出现不同字符时两个字符串对应位置上的字符。 - - 如果出现不同字符的次数等于 `2`,则判断第一次出现不同字符时两个字符串对应位置上的字符与当前位置字符交换之后是否相等。如果不等,则说明交换之后 `s1` 和 `s2` 不相等,返回 `False`。如果相等,则继续判断下一个位置。 - - 如果出现不同字符的次数超过 `2`,则不符合最多一次字符串交换的要求,返回 `False`。 -- 如果遍历完,出现不同字符的次数为 `0` 或者 `2`,为 `0` 说明无需交换,本身 `s1` 和 `s2` 就是相等的,为 `2` 说明交换一次字符串之后 `s1` 和 `s2` 相等,此时返回 `True`。否则返回 `False`。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def areAlmostEqual(self, s1: str, s2: str) -> bool: - size = len(s1) - diff_cnt = 0 - c1, c2 = None, None - for i in range(size): - if s1[i] == s2[i]: - continue - diff_cnt += 1 - if diff_cnt == 1: - c1 = s1[i] - c2 = s2[i] - elif diff_cnt == 2: - if c1 != s2[i] or c2 != s1[i]: - return False - else: - return False - - return diff_cnt == 0 or diff_cnt == 2 -``` - diff --git "a/Solutions/1791. \346\211\276\345\207\272\346\230\237\345\236\213\345\233\276\347\232\204\344\270\255\345\277\203\350\212\202\347\202\271.md" "b/Solutions/1791. \346\211\276\345\207\272\346\230\237\345\236\213\345\233\276\347\232\204\344\270\255\345\277\203\350\212\202\347\202\271.md" deleted file mode 100644 index 1f94e7fc..00000000 --- "a/Solutions/1791. \346\211\276\345\207\272\346\230\237\345\236\213\345\233\276\347\232\204\344\270\255\345\277\203\350\212\202\347\202\271.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [1791. 找出星型图的中心节点](https://leetcode.cn/problems/find-center-of-star-graph/) - -- 标签:图 -- 难度:简单 - -## 题目大意 - -**描述**:有一个无向的行型图,由 $n$ 个编号 $1 \sim n$ 的节点组成。星型图有一个中心节点,并且恰好有 $n - 1$ 条边将中心节点与其他每个节点连接起来。 - -给定一个二维整数数组 $edges$,其中 $edges[i] = [u_i, v_i]$ 表示节点 $u_i$ 与节点 $v_i$ 之间存在一条边。 - -**要求**:找出并返回该星型图的中心节点。 - -**说明**: - -- $3 \le n \le 10^5$。 -- $edges.length == n - 1$。 -- $edges[i].length == 2$。 -- $1 \le ui, vi \le n$。 -- $ui \ne vi$。 -- 题目数据给出的 $edges$ 表示一个有效的星型图。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/14/star_graph.png) - -```python -输入:edges = [[1,2],[2,3],[4,2]] -输出:2 -解释:如上图所示,节点 2 与其他每个节点都相连,所以节点 2 是中心节点。 -``` - -- 示例 2: - -```python -输入:edges = [[1,2],[5,1],[1,3],[1,4]] -输出:1 -``` - -## 解题思路 - -### 思路 1:求度数 - -根据题意可知:中心节点恰好有 $n - 1$ 条边将中心节点与其他每个节点连接起来,那么中心节点的度数一定为 $n - 1$。则我们可以遍历边集数组 $edges$,统计出每个节点 $u$ 的度数 $degrees[u]$。最后返回度数为 $n - 1$ 的节点编号。 - -### 思路 1:代码 - -```python -class Solution: - def findCenter(self, edges: List[List[int]]) -> int: - n = len(edges) + 1 - degrees = collections.Counter() - - for u, v in edges: - degrees[u] += 1 - degrees[v] += 1 - - for i in range(1, n + 1): - if degrees[i] == n - 1: - return i - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1822. \346\225\260\347\273\204\345\205\203\347\264\240\347\247\257\347\232\204\347\254\246\345\217\267.md" "b/Solutions/1822. \346\225\260\347\273\204\345\205\203\347\264\240\347\247\257\347\232\204\347\254\246\345\217\267.md" deleted file mode 100644 index 484978bd..00000000 --- "a/Solutions/1822. \346\225\260\347\273\204\345\205\203\347\264\240\347\247\257\347\232\204\347\254\246\345\217\267.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [1822. 数组元素积的符号](https://leetcode.cn/problems/sign-of-the-product-of-an-array/) - -- 标签:数组、数学 -- 难度:简单 - -## 题目大意 - -**描述**:已知函数 `signFunc(x)` 会根据 `x` 的正负返回特定值: - -- 如果 `x` 是正数,返回 `1`。 -- 如果 `x` 是负数,返回 `-1`。 -- 如果 `x` 等于 `0`,返回 `0`。 - -现在给定一个整数数组 `nums`。令 `product` 为数组 `nums` 中所有元素值的乘积。 - -**要求**:返回 `signFun(product)` 的值。 - -**说明**: - -- $1 \le nums.length \le 1000$。 -- $-100 \le nums[i] \le 100$。 - -**示例**: - -- 示例 1: - -```python -输入 nums = [-1,-2,-3,-4,3,2,1] -输出 1 -解释 数组中所有值的乘积是 144,且 signFunc(144) = 1 -``` - -## 解题思路 - -### 思路 1: - -题目要求的是数组所有值乘积的正负性,但是我们没必要将所有数乘起来再判断正负性。只需要统计出数组中负数的个数,再加以判断即可。 - -- 使用变量 `minus_count` 记录数组中负数个数。 -- 然后遍历数组 `nums`,对于当前元素 `num`: - - 如果为 `0`,则最终乘积肯定为 `0`,直接返回 `0`。 - - 如果小于 `0`,负数个数加 `1`。 -- 最终统计出数组中负数的个数为 `minus_count`。 -- 如果 `minus_count` 是 `2` 的倍数,则说明最终乘积为正数,返回 `1`。 -- 如果 `minus_count` 不是 `2` 的倍数,则说明最终乘积为负数,返回 `-1`。 - -## 代码 - -### 思路 1 代码: - -```python -class Solution: - def arraySign(self, nums: List[int]) -> int: - minus_count = 0 - for num in nums: - if num < 0: - minus_count += 1 - elif num == 0: - return 0 - - if minus_count % 2 == 0: - return 1 - else: - return -1 -``` - diff --git "a/Solutions/1833. \351\233\252\347\263\225\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" "b/Solutions/1833. \351\233\252\347\263\225\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" deleted file mode 100644 index d576def0..00000000 --- "a/Solutions/1833. \351\233\252\347\263\225\347\232\204\346\234\200\345\244\247\346\225\260\351\207\217.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [1833. 雪糕的最大数量](https://leetcode.cn/problems/maximum-ice-cream-bars/) - -- 标签:贪心、数组、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数组 $costs$ 表示不同雪糕的定价,其中 $costs[i]$ 表示第 $i$ 支雪糕的定价。再给定一个整数 $coins$ 表示 Tony 一共有的现金数量。 - -**要求**:计算并返回 Tony 用 $coins$ 现金能够买到的雪糕的最大数量。 - -**说明**: - -- $costs.length == n$。 -- $1 \le n \le 10^5$。 -- $1 \le costs[i] \le 10^5$。 -- $1 \le coins \le 10^8$。 - -**示例**: - -- 示例 1: - -```python -输入:costs = [1,3,2,4,1], coins = 7 -输出:4 -解释:Tony 可以买下标为 0、1、2、4 的雪糕,总价为 1 + 3 + 2 + 1 = 7 -``` - -- 示例 2: - -```python -输入:costs = [10,6,8,7,7,8], coins = 5 -输出:0 -解释:Tony 没有足够的钱买任何一支雪糕。 -``` - -## 解题思路 - -### 思路 1:排序 + 贪心 - -贪心思路,如果想尽可能买到多的雪糕,就应该优先选择价格便宜的雪糕。具体步骤如下: - -1. 对数组 $costs$ 进行排序。 -2. 按照雪糕价格从低到高开始买雪糕,并记录下购买雪糕的数量,知道现有钱买不起雪糕为止。 -3. 输出购买雪糕的数量作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def maxIceCream(self, costs: List[int], coins: int) -> int: - costs.sort() - ans = 0 - for cost in costs: - if coins >= cost: - ans += 1 - coins -= cost - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log_2n)$。 -- **空间复杂度**:$O(1)$。 - - diff --git "a/Solutions/1844. \345\260\206\346\211\200\346\234\211\346\225\260\345\255\227\347\224\250\345\255\227\347\254\246\346\233\277\346\215\242.md" "b/Solutions/1844. \345\260\206\346\211\200\346\234\211\346\225\260\345\255\227\347\224\250\345\255\227\347\254\246\346\233\277\346\215\242.md" deleted file mode 100644 index cadbdeb4..00000000 --- "a/Solutions/1844. \345\260\206\346\211\200\346\234\211\346\225\260\345\255\227\347\224\250\345\255\227\347\254\246\346\233\277\346\215\242.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [1844. 将所有数字用字符替换](https://leetcode.cn/problems/replace-all-digits-with-characters/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个下标从 $0$ 开始的字符串 $s$。字符串 $s$ 的偶数下标处为小写英文字母,奇数下标处为数字。 - -定义一个函数 `shift(c, x)`,其中 $c$ 是一个字符且 $x$ 是一个数字,函数返回字母表中 $c$ 后边第 $x$ 个字符。 - -- 比如,`shift('a', 5) = 'f'`,`shift('x', 0) = 'x'`。 - -对于每个奇数下标 $i$,我们需要将数字 $s[i]$ 用 `shift(s[i - 1], s[i])` 替换。 - -**要求**:替换字符串 $s$ 中所有数字以后,将字符串 $s$ 返回。 - -**说明**: - -- 题目保证 `shift(s[i - 1], s[i])` 不会超过 `'z'`。 -- $1 \le s.length \le 100$。 -- $s$ 只包含小写英文字母和数字。 -- 对所有奇数下标处的 $i$,满足 `shift(s[i - 1], s[i]) <= 'z'` 。 - -**示例**: - -- 示例 1: - -```python -输入:s = "a1c1e1" -输出:"abcdef" -解释:数字被替换结果如下: -- s[1] -> shift('a',1) = 'b' -- s[3] -> shift('c',1) = 'd' -- s[5] -> shift('e',1) = 'f' -``` - -- 示例 2: - -```python -输入:s = "a1b2c3d4e" -输出:"abbdcfdhe" -解释:数字被替换结果如下: -- s[1] -> shift('a',1) = 'b' -- s[3] -> shift('b',2) = 'd' -- s[5] -> shift('c',3) = 'f' -- s[7] -> shift('d',4) = 'h' -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 先定义一个 `shift(ch, x)` 用于替换 `s[i]`。 -2. 将字符串转为字符串列表,定义为 $res$。 -3. 以两个字符为一组遍历字符串,对 $res[i]$ 进行修改。 -4. 将字符串列表连接起来,作为答案返回。 - -### 思路 1:代码 - -```python -class Solution: - def replaceDigits(self, s: str) -> str: - def shift(ch, x): - return chr(ord(ch) + x) - - res = list(s) - for i in range(1, len(s), 2): - res[i] = shift(res[i - 1], int(res[i])) - - return "".join(res) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/1858. \345\214\205\345\220\253\346\211\200\346\234\211\345\211\215\347\274\200\347\232\204\346\234\200\351\225\277\345\215\225\350\257\215.md" "b/Solutions/1858. \345\214\205\345\220\253\346\211\200\346\234\211\345\211\215\347\274\200\347\232\204\346\234\200\351\225\277\345\215\225\350\257\215.md" deleted file mode 100644 index 030a8df2..00000000 --- "a/Solutions/1858. \345\214\205\345\220\253\346\211\200\346\234\211\345\211\215\347\274\200\347\232\204\346\234\200\351\225\277\345\215\225\350\257\215.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [1858. 包含所有前缀的最长单词](https://leetcode.cn/problems/longest-word-with-all-prefixes/) - -- 标签:深度优先搜索、字典树 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `words`。 - -要求:找出 `words` 中所有前缀从都在 `words` 中的最长字符串。如果存在多个符合条件相同长度的字符串,则输出字典序中最小的字符串。如果不存在这样的字符串,返回 `' '`。 - -- 例如:令 `words = ["a", "app", "ap"]`。字符串 `"app"` 含前缀 `"ap"` 和 `"a"` ,都在 `words` 中。 - -## 解题思路 - -使用字典树存储所有单词,再将字典中单词按照长度从大到小、字典序从小到大排序。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - if not cur.isEnd: - return False - return True - - -class Solution: - def longestWord(self, words: List[str]) -> str: - tire_tree = Trie() - for word in words: - tire_tree.insert(word) - words.sort(key=lambda x:(-len(x), x)) - for word in words: - if tire_tree.search(word): - return word - return '' -``` - diff --git "a/Solutions/1859. \345\260\206\345\217\245\345\255\220\346\216\222\345\272\217.md" "b/Solutions/1859. \345\260\206\345\217\245\345\255\220\346\216\222\345\272\217.md" deleted file mode 100644 index b3f248c1..00000000 --- "a/Solutions/1859. \345\260\206\345\217\245\345\255\220\346\216\222\345\272\217.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [1859. 将句子排序](https://leetcode.cn/problems/sorting-the-sentence/) - -- 标签:字符串、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个句子 $s$,句子中包含的单词不超过 $9$ 个。并且句子 $s$ 中每个单词末尾添加了「从 $1$ 开始的单词位置索引」,并且将句子中所有单词打乱顺序。 - -举个例子,句子 `"This is a sentence"` 可以被打乱顺序得到 `"sentence4 a3 is2 This1"` 或者 `"is2 sentence4 This1 a3"` 。 - -**要求**:重新构造并得到原本顺序的句子。 - -**说明**: - -- **一个句子**:指的是一个序列的单词用单个空格连接起来,且开头和结尾没有任何空格。每个单词都只包含小写或大写英文字母。 -- $2 \le s.length \le 200$。 -- $s$ 只包含小写和大写英文字母、空格以及从 $1$ 到 $9$ 的数字。 -- $s$ 中单词数目为 $1$ 到 $9$ 个。 -- $s$ 中的单词由单个空格分隔。 -- $s$ 不包含任何前导或者后缀空格。 - -**示例**: - -- 示例 1: - -```python -输入:s = "is2 sentence4 This1 a3" -输出:"This is a sentence" -解释:将 s 中的单词按照初始位置排序,得到 "This1 is2 a3 sentence4" ,然后删除数字。 -``` - -- 示例 2: - -```python -输入:s = "Myself2 Me1 I4 and3" -输出:"Me Myself and I" -解释:将 s 中的单词按照初始位置排序,得到 "Me1 Myself2 and3 I4" ,然后删除数字。 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 将句子 $s$ 按照空格分隔成数组 $s\underline{}list$。 -2. 遍历数组 $s\underline{}list$ 中的单词: - 1. 从单词中分割出对应单词索引 $idx$ 和对应单词 $word$。 - 2. 将单词 $word$ 存入答案数组 $res$ 对应位置 $idx - 1$ 上,即:$res[int(idx) - 1] = word$。 -3. 将答案数组用空格拼接成句子字符串,并返回。 - -### 思路 1:代码 - -```python -class Solution: - def sortSentence(self, s: str) -> str: - s_list = s.split() - size = len(s_list) - res = ["" for _ in range(size)] - for sub in s_list: - idx = "" - word = "" - for ch in sub: - if '1' <= ch <= '9': - idx += ch - else: - word += ch - res[int(idx) - 1] = word - - return " ".join(res) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m)$,其中 $m$ 为给定句子 $s$ 的长度。 -- **空间复杂度**:$O(m)$。 - diff --git "a/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index fc6fe4a7..00000000 --- "a/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [1876. 长度为三且各字符不同的子字符串](https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/) - -- 标签:哈希表、字符串、计数、滑动窗口 -- 难度:简单 - -## 题目大意 - -**描述**:给定搞一个字符串 $s$。 - -**要求**:返回 $s$ 中长度为 $3$ 的好子字符串的数量。如果相同的好子字符串出现多次,则每一次都应该被记入答案之中。 - -**说明**: - -- **子字符串**:指的是一个字符串中连续的字符序列。 -- **好子字符串**:如果一个字符串中不含有任何重复字符,则称这个字符串为好子字符串。 -- $1 \le s.length \le 100$。 -- $s$ 只包含小写英文字母。 - -**示例**: - -- 示例 1: - -```python -输入:s = "xyzzaz" -输出:1 -解释:总共有 4 个长度为 3 的子字符串:"xyz","yzz","zza" 和 "zaz" 。 -唯一的长度为 3 的好子字符串是 "xyz" 。 -``` - -- 示例 2: - -```python -输入:s = "aababcabc" -输出:4 -解释:总共有 7 个长度为 3 的子字符串:"aab","aba","bab","abc","bca","cab" 和 "abc" 。 -好子字符串包括 "abc","bca","cab" 和 "abc" 。 -``` - -## 解题思路 - -### 思路 1:模拟 - -1. 遍历字符串 $s$ 中长度为 3 的子字符串。 -2. 判断子字符串中的字符是否有重复。如果没有重复,则答案进行计数。 -3. 遍历完输出答案。 - -### 思路 1:代码 - -```python -class Solution: - def countGoodSubstrings(self, s: str) -> int: - ans = 0 - for i in range(2, len(s)): - if s[i - 2] != s[i - 1] and s[i - 1] != s[i] and s[i - 2] != s[i]: - ans += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/1877. \346\225\260\347\273\204\344\270\255\346\234\200\345\244\247\346\225\260\345\257\271\345\222\214\347\232\204\346\234\200\345\260\217\345\200\274.md" "b/Solutions/1877. \346\225\260\347\273\204\344\270\255\346\234\200\345\244\247\346\225\260\345\257\271\345\222\214\347\232\204\346\234\200\345\260\217\345\200\274.md" deleted file mode 100644 index 88eb0a77..00000000 --- "a/Solutions/1877. \346\225\260\347\273\204\344\270\255\346\234\200\345\244\247\346\225\260\345\257\271\345\222\214\347\232\204\346\234\200\345\260\217\345\200\274.md" +++ /dev/null @@ -1,71 +0,0 @@ -# [1877. 数组中最大数对和的最小值](https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/) - -- 标签:贪心、数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -**描述**:一个数对 $(a, b)$ 的数对和等于 $a + b$。最大数对和是一个数对数组中最大的数对和。 - -- 比如,如果我们有数对 $(1, 5)$,$(2, 3)$ 和 $(4, 4)$,最大数对和为 $max(1 + 5, 2 + 3, 4 + 4) = max(6, 5, 8) = 8$。 - -给定一个长度为偶数 $n$ 的数组 $nums$,现在将 $nums$ 中的元素分为 $n / 2$ 个数对,使得: - -- $nums$ 中每个元素恰好在一个数对中。 -- 最大数对和的值最小。 - -**要求**:在最优数对划分的方案下,返回最小的最大数对和。 - -**说明**: - -- $n == nums.length$。 -- $2 \le n \le 10^5$。 -- $n$ 是偶数。 -- $1 \le nums[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [3,5,2,3] -输出:7 -解释:数组中的元素可以分为数对 (3,3) 和 (5,2)。 -最大数对和为 max(3+3, 5+2) = max(6, 7) = 7。 -``` - -- 示例 2: - -```python -输入:nums = [3,5,4,2,4,6] -输出:8 -解释:数组中的元素可以分为数对 (3,5),(4,4) 和 (6,2)。 -最大数对和为 max(3+5, 4+4, 6+2) = max(8, 8, 8) = 8。 -``` - -## 解题思路 - -### 思路 1:排序 + 贪心 - -为了使最大数对和的值尽可能的小,我们应该尽可能的让数组中最大值与最小值组成一对,次大值与次小值组成一对。而其他任何方案都会使得最大数对和的值更大。 - -那么,我们可以先将数组进行排序,然后首尾依次进行组对,并计算这种方案下的最大数对和即为答案。 - -### 思路 1:代码 - -```python -class Solution: - def minPairSum(self, nums: List[int]) -> int: - nums.sort() - ans, size = 0, len(nums) - for i in range(len(nums) // 2): - ans = max(ans, nums[i] + nums[size - 1 - i]) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(\log n)$。 - diff --git "a/Solutions/1879. \344\270\244\344\270\252\346\225\260\347\273\204\346\234\200\345\260\217\347\232\204\345\274\202\346\210\226\345\200\274\344\271\213\345\222\214.md" "b/Solutions/1879. \344\270\244\344\270\252\346\225\260\347\273\204\346\234\200\345\260\217\347\232\204\345\274\202\346\210\226\345\200\274\344\271\213\345\222\214.md" deleted file mode 100644 index d95fe4cb..00000000 --- "a/Solutions/1879. \344\270\244\344\270\252\346\225\260\347\273\204\346\234\200\345\260\217\347\232\204\345\274\202\346\210\226\345\200\274\344\271\213\345\222\214.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [1879. 两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) - -- 标签:位运算、数组、动态规划、状态压缩 -- 难度:困难 - -## 题目大意 - -**描述**:给定两个整数数组 $nums1$ 和 $nums2$,两个数组长度都为 $n$。 - -**要求**:将 $nums2$ 中的元素重新排列,使得两个数组的异或值之和最小。并返回重新排列之后的异或值之和。 - -**说明**: - -- **两个数组的异或值之和**:$(nums1[0] \oplus nums2[0]) + (nums1[1] \oplus nums2[1]) + ... + (nums1[n - 1] \oplus nums2[n - 1])$(下标从 $0$ 开始)。 -- 举个例子,$[1, 2, 3]$ 和 $[3,2,1]$ 的异或值之和 等于 $(1 \oplus 3) + (2 \oplus 2) + (3 \oplus 1) + (3 \oplus 1) = 2 + 0 + 2 = 4$。 -- $n == nums1.length$。 -- $n == nums2.length$。 -- $1 \le n \le 14$。 -- $0 \le nums1[i], nums2[i] \le 10^7$。 - -**示例**: - -- 示例 1: - -```python -输入:nums1 = [1,2], nums2 = [2,3] -输出:2 -解释:将 nums2 重新排列得到 [3,2] 。 -异或值之和为 (1 XOR 3) + (2 XOR 2) = 2 + 0 = 2。 -``` - -- 示例 2: - -```python -输入:nums1 = [1,0,3], nums2 = [5,3,4] -输出:8 -解释:将 nums2 重新排列得到 [5,4,3] 。 -异或值之和为 (1 XOR 5) + (0 XOR 4) + (3 XOR 3) = 4 + 4 + 0 = 8。 -``` - -## 解题思路 - -### 思路 1:状态压缩 DP - -由于数组 $nums2$ 可以重新排列,所以我们可以将数组 $nums1$ 中的元素顺序固定,然后将数组 $nums1$ 中第 $i$ 个元素与数组 $nums2$ 中所有还没被选择的元素进行组合,找到异或值之和最小的组合。 - -同时因为两个数组长度 $n$ 的大小范围只有 $[1, 14]$,所以我们可以采用「状态压缩」的方式来表示 $nums2$ 中当前元素的选择情况。 - -「状态压缩」指的是使用一个 $n$ 位的二进制数 $state$ 来表示排列中数的选取情况。 - -如果二进制数 $state$ 的第 $i$ 位为 $1$,说明数组 $nums2$ 第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明数组 $nums2$ 中第 $i$ 个元素在该状态中没有被选取。 - -举个例子: - -1. $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。 -2. $nums2 = \lbrace 1, 2, 3, 4, 5, 6 \rbrace$,$state = (011010)_2$,表示选择了第 $2$ 个元素、第 $4$ 个元素、第 $5$ 个元素,也就是 $2$、$4$、$5$。 - -这样,我们就可以通过动态规划的方式来解决这道题。 - -###### 1. 划分阶段 - -按照数组 $nums$ 中元素选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义当前数组 $nums2$ 中元素选择状态为 $state$,$state$ 对应选择的元素个数为 $count(state)$。 - -则可以定义状态 $dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 - -###### 3. 状态转移方程 - -对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以组成的异或值之和最小值,赋值给 $dp[state]$。 - -举个例子 $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。那么 $state$ 只能从 $(1000)_2$ 和 $(0001)_2$ 这两个状态转移而来,我们只需要枚举这两种状态,并求出转移过来的异或值之和最小值。 - -即状态转移方程为:$dp[state] = min(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + (nums1[i] \oplus nums2[one\underline{}cnt - 1]))$,其中 $state$ 第 $i$ 位一定为 $1$,$one\underline{}cnt$ 为 $state$ 中 $1$ 的个数。 - -###### 4. 初始条件 - -- 既然是求最小值,不妨将所有状态初始为最大值。 -- 未选择任何数时,异或值之和为 $0$,所以初始化 $dp[0] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } n$。 - -### 思路 1:代码 - -```python -class Solution: - def minimumXORSum(self, nums1: List[int], nums2: List[int]) -> int: - ans = float('inf') - size = len(nums1) - states = 1 << size - - dp = [float('inf') for _ in range(states)] - dp[0] = 0 - for state in range(states): - one_cnt = bin(state).count('1') - for i in range(size): - if (state >> i) & 1: - dp[state] = min(dp[state], dp[state ^ (1 << i)] + (nums1[i] ^ nums2[one_cnt - 1])) - - return dp[states - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 $nums1$、$nums2$ 的长度。 -- **空间复杂度**:$O(2^n)$。 - diff --git "a/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" "b/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" deleted file mode 100644 index 102275ed..00000000 --- "a/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [1903. 字符串中的最大奇数](https://leetcode.cn/problems/largest-odd-number-in-string/) - -- 标签:贪心、数学、字符串 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $num$,表示一个大整数。 - -**要求**:在字符串 $num$ 的所有非空子字符串中找出值最大的奇数,并以字符串形式返回。如果不存在奇数,则返回一个空字符串 `""`。 - -**说明**: - -- **子字符串**:指的是字符串中一个连续的字符序列。 -- $1 \le num.length \le 10^5$ -- $num$ 仅由数字组成且不含前导零。 - -**示例**: - -- 示例 1: - -```python -输入:num = "52" -输出:"5" -解释:非空子字符串仅有 "5"、"2" 和 "52" 。"5" 是其中唯一的奇数。 -``` - -- 示例 2: - -```python -输入:num = "4206" -输出:"" -解释:在 "4206" 中不存在奇数。 -``` - -## 解题思路 - -### 思路 1:贪心算法 - -如果某个数 $x$ 为奇数,则 $x$ 末尾位上的数字一定为奇数。那么我们只需要在末尾为奇数的字符串中考虑最大的奇数即可。显而易见的是,最大的奇数一定是长度最长的那个。所以我们只需要逆序遍历字符串,找到第一个奇数,从整个字符串开始位置到该奇数位置所代表的整数,就是最大的奇数。具体步骤如下: - -1. 逆序遍历字符串 $s$。 -2. 找到第一个奇数位置 $i$,则 $num[0: i + 1]$ 为最大的奇数,将其作为答案返回。 - -### 思路 1:代码 - -```python -class Solution: - def largestOddNumber(self, num: str) -> str: - for i in range(len(num) - 1, -1, -1): - if int(num[i]) % 2 == 1: - return num[0: i + 1] - return "" -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1925. \347\273\237\350\256\241\345\271\263\346\226\271\345\222\214\344\270\211\345\205\203\347\273\204\347\232\204\346\225\260\347\233\256.md" "b/Solutions/1925. \347\273\237\350\256\241\345\271\263\346\226\271\345\222\214\344\270\211\345\205\203\347\273\204\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index 191ea162..00000000 --- "a/Solutions/1925. \347\273\237\350\256\241\345\271\263\346\226\271\345\222\214\344\270\211\345\205\203\347\273\204\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [1925. 统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) - -- 标签:数学、枚举 -- 难度:简单 - -## 题目大意 - -**描述**:给你一个整数 $n$。 - -**要求**:请你返回满足 $1 \le a, b, c \le n$ 的平方和三元组的数目。 - -**说明**: - -- **平方和三元组**:指的是满足 $a^2 + b^2 = c^2$ 的整数三元组 $(a, b, c)$。 -- $1 \le n \le 250$。 - -**示例**: - -- 示例 1: - -```python -输入 n = 5 -输出 2 -解释 平方和三元组为 (3,4,5) 和 (4,3,5)。 -``` - -- 示例 2: - -```python -输入:n = 10 -输出:4 -解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n$,并且是完全平方数。 - -在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终,我们返回该数目作为答案。 - -利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。 - -- 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 $1$,所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。 - -### 思路 1:代码 - -```python -class Solution: - def countTriples(self, n: int) -> int: - cnt = 0 - for a in range(1, n + 1): - for b in range(1, n + 1): - c = int(sqrt(a * a + b * b + 1)) - if c <= n and a * a + b * b == c * c: - cnt += 1 - return cnt -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1929. \346\225\260\347\273\204\344\270\262\350\201\224.md" "b/Solutions/1929. \346\225\260\347\273\204\344\270\262\350\201\224.md" deleted file mode 100644 index 318fee64..00000000 --- "a/Solutions/1929. \346\225\260\347\273\204\344\270\262\350\201\224.md" +++ /dev/null @@ -1,85 +0,0 @@ -# [1929. 数组串联](https://leetcode.cn/problems/concatenation-of-array/) - -- 标签:数组 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个长度为 $n$ 的整数数组 $nums$。 - -**要求**:构建一个长度为 $2 \times n$ 的答案数组 $ans$,答案数组下标从 $0$ 开始计数 ,对于所有 $0 \le i < n$ 的 $i$ ,满足下述所有要求: - -- $ans[i] == nums[i]$。 -- $ans[i + n] == nums[i]$。 - -具体而言,$ans$ 由两个 $nums$ 数组「串联」形成。 - -**说明**: - -- $n == nums.length$。 -- $1 \le n \le 1000$。 -- $1 \le nums[i] \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,1] -输出:[1,2,1,1,2,1] -解释:数组 ans 按下述方式形成: -- ans = [nums[0],nums[1],nums[2],nums[0],nums[1],nums[2]] -- ans = [1,2,1,1,2,1] -``` - -- 示例 2: - -```python -输入:nums = [1,3,2,1] -输出:[1,3,2,1,1,3,2,1] -解释:数组 ans 按下述方式形成: -- ans = [nums[0],nums[1],nums[2],nums[3],nums[0],nums[1],nums[2],nums[3]] -- ans = [1,3,2,1,1,3,2,1] -``` - -## 解题思路 - -### 思路 1:按要求模拟 - -1. 定义一个数组变量(列表)$ans$ 作为答案数组。 -2. 然后按顺序遍历两次数组 $nums$ 中的元素,并依次添加到 $ans$ 的尾部。最后返回 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def getConcatenation(self, nums: List[int]) -> List[int]: - ans = [] - for num in nums: - ans.append(num) - for num in nums: - ans.append(num) - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 - -### 思路 2:利用运算符 - -Python 中可以直接利用 `+` 号运算符将两个列表快速进行串联。即 `return nums + nums`。 - -### 思路 2:代码 - -```python -class Solution: - def getConcatenation(self, nums: List[int]) -> List[int]: - return nums + nums -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 -- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 diff --git "a/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" "b/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" deleted file mode 100644 index aec00e21..00000000 --- "a/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [1941. 检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) - -- 标签:哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个字符串 $s$。如果 $s$ 中出现过的所有字符的出现次数相同,那么我们称字符串 $s$ 是「好字符串」。 - -**要求**:如果 $s$ 是一个好字符串,则返回 `True`,否则返回 `False`。 - -**说明**: - -- $1 \le s.length \le 1000$。 -- $s$ 只包含小写英文字母。 - -**示例**: - -- 示例 1: - -```python -输入:s = "abacbc" -输出:true -解释:s 中出现过的字符为 'a','b' 和 'c' 。s 中所有字符均出现 2 次。 -``` - -- 示例 2: - -```python -输入:s = "aaabb" -输出:false -解释:s 中出现过的字符为 'a' 和 'b' 。 -'a' 出现了 3 次,'b' 出现了 2 次,两者出现次数不同。 -``` - -## 解题思路 - -### 思路 1:哈希表 - -1. 使用哈希表记录字符串 $s$ 中每个字符的频数。 -2. 然后遍历哈希表中的键值对,检测每个字符的频数是否相等。 -3. 如果发现频数不相等,则直接返回 `False`。 -4. 如果检查完发现所有频数都相等,则返回 `True`。 - -### 思路 1:代码 - -```python -class Solution: - def areOccurrencesEqual(self, s: str) -> bool: - counter = Counter(s) - flag = -1 - for key in counter: - if flag == -1: - flag = counter[key] - else: - if flag != counter[key]: - return False - return True -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1947. \346\234\200\345\244\247\345\205\274\345\256\271\346\200\247\350\257\204\345\210\206\345\222\214.md" "b/Solutions/1947. \346\234\200\345\244\247\345\205\274\345\256\271\346\200\247\350\257\204\345\210\206\345\222\214.md" deleted file mode 100644 index c52d5166..00000000 --- "a/Solutions/1947. \346\234\200\345\244\247\345\205\274\345\256\271\346\200\247\350\257\204\345\210\206\345\222\214.md" +++ /dev/null @@ -1,112 +0,0 @@ -# [1947. 最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) - -- 标签:位运算、数组、动态规划、回溯、状态压缩 -- 难度:中等 - -## 题目大意 - -**描述**:有一份由 $n$ 个问题组成的调查问卷,每个问题的答案只有 $0$ 或 $1$。将这份调查问卷分发给 $m$ 名学生和 $m$ 名老师,学生和老师的编号都是 $0 \sim m - 1$。现在给定一个二维整数数组 $students$ 表示 $m$ 名学生给出的答案,其中 $studuents[i][j]$ 表示第 $i$ 名学生第 $j$ 个问题给出的答案。再给定一个二维整数数组 $mentors$ 表示 $m$ 名老师给出的答案,其中 $mentors[i][j]$ 表示第 $i$ 名导师第 $j$ 个问题给出的答案。 - -每个学生要和一名导师互相配对。配对的学生和导师之间的兼容性评分等于学生和导师答案相同的次数。 - -- 例如,学生答案为 $[1, 0, 1]$,而导师答案为 $[0, 0, 1]$,那么他们的兼容性评分为 $2$,因为只有第 $2$ 个和第 $3$ 个答案相同。 - -**要求**:找出最优的学生与导师的配对方案,以最大程度上提高所有学生和导师的兼容性评分和。然后返回可以得到的最大兼容性评分和。 - -**说明**: - -- $m == students.length == mentors.length$。 -- $n == students[i].length == mentors[j].length$。 -- $1 \le m, n \le 8$。 -- $students[i][k]$ 为 $0$ 或 $1$。 -- $mentors[j][k]$ 为 $0$ 或 $1$。 - -**示例**: - -- 示例 1: - -```python -输入:students = [[1,1,0],[1,0,1],[0,0,1]], mentors = [[1,0,0],[0,0,1],[1,1,0]] -输出:8 -解释:按下述方式分配学生和导师: -- 学生 0 分配给导师 2 ,兼容性评分为 3。 -- 学生 1 分配给导师 0 ,兼容性评分为 2。 -- 学生 2 分配给导师 1 ,兼容性评分为 3。 -最大兼容性评分和为 3 + 2 + 3 = 8。 -``` - -- 示例 2: - -```python -输入:students = [[0,0],[0,0],[0,0]], mentors = [[1,1],[1,1],[1,1]] -输出:0 -解释:任意学生与导师配对的兼容性评分都是 0。 -``` - -## 解题思路 - -### 思路 1:状压 DP - -因为 $m$、$n$ 的范围都是 $[1, 8]$,所以我们可以使用「状态压缩」的方式来表示学生的分配情况。即使用一个 $m$ 位长度的二进制数 $state$ 来表示每一位老师是否被分配了学生。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 位老师被分配了学生,如果 $state$ 的第 $i$ 位为 $0$,则表示第 $i$ 位老师没有分配到学生。 - -这样,我们就可以通过动态规划的方式来解决这道题。 - -###### 1. 划分阶段 - -按照学生的分配情况进行阶段划分。 - -###### 2. 定义状态 - -定义当前学生的分配情况为 $state$,$state$ 中包含 $count(state)$ 个 $1$,表示有 $count(state)$ 个老师被分配了学生。 - -则可以定义状态 $dp[state]$ 表示为:当前老师被分配学生的状态为 $state$,其中有 $count(state)$ 个老师被分配了学生的情况下,可以得到的最大兼容性评分和。 - -###### 3. 状态转移方程 - -对于当前状态 $state$,肯定是从比 $state$ 少选一个老师被分配的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以得到的最大兼容性评分和,赋值给 $dp[state]$。 - -即状态转移方程为:$dp[state] = max(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + score[i][one\underline{}cnt - 1])$,其中: - -1. $state$ 第 $i$ 位一定为 $1$。 -2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 -3. $scores[i][one\underline{}cnt - 1]$ 为第 $i$ 名老师分配到第 $one\underline{}cnt - 1$ 名学生的兼容性评分。 - -关于每位老师与每位同学之间的兼容性评分,我们可以事先通过一个 $m \times m \times n$ 的三重循环计算得出,并且存入到 $m \times m$ 大小的二维矩阵 $scores$ 中。 - -###### 4. 初始条件 - -- 初始每个老师都没有分配到学生的状态下,可以得到的最兼容性评分和为 $0$,即 $dp[0] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当前老师被分配学生的状态为 $state$,其中有 $count(state)$ 个老师被分配了学生的情况下,可以得到的最大兼容性评分和。所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } m$。 - -### 思路 1:代码 - -```python -class Solution: - def maxCompatibilitySum(self, students: List[List[int]], mentors: List[List[int]]) -> int: - m, n = len(students), len(students[0]) - scores = [[0 for _ in range(m)] for _ in range(m)] - - for i in range(m): - for j in range(m): - for k in range(n): - scores[i][j] += (students[i][k] == mentors[j][k]) - - states = 1 << m - dp = [0 for _ in range(states)] - - for state in range(states): - one_cnt = bin(state).count('1') - for i in range(m): - if (state >> i) & 1: - dp[state] = max(dp[state], dp[state ^ (1 << i)] + scores[i][one_cnt - 1]) - return dp[states - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m^2 \times n + m \times 2^m)$。 -- **空间复杂度**:$O(2^m)$。 - diff --git "a/Solutions/1986. \345\256\214\346\210\220\344\273\273\345\212\241\347\232\204\346\234\200\345\260\221\345\267\245\344\275\234\346\227\266\351\227\264\346\256\265.md" "b/Solutions/1986. \345\256\214\346\210\220\344\273\273\345\212\241\347\232\204\346\234\200\345\260\221\345\267\245\344\275\234\346\227\266\351\227\264\346\256\265.md" deleted file mode 100644 index 51986908..00000000 --- "a/Solutions/1986. \345\256\214\346\210\220\344\273\273\345\212\241\347\232\204\346\234\200\345\260\221\345\267\245\344\275\234\346\227\266\351\227\264\346\256\265.md" +++ /dev/null @@ -1,82 +0,0 @@ -# [1986. 完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) - -- 标签:位运算、数组、动态规划、回溯、状态压缩 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个整数数组 $tasks$ 代表需要完成的任务。 其中 $tasks[i]$ 表示第 $i$ 个任务需要花费的时长(单位为小时)。再给定一个整数 $sessionTime$,代表在一个工作时段中,最多可以连续工作的小时数。在连续工作至多 $sessionTime$ 小时后,需要进行休息。 - -现在需要按照如下条件完成给定任务: - -1. 如果你在某一个时间段开始一个任务,你需要在同一个时间段完成它。 -2. 完成一个任务后,你可以立马开始一个新的任务。 -3. 你可以按任意顺序完成任务。 - -**要求**:按照上述要求,返回完成所有任务所需要的最少数目的工作时间段。 - -**说明**: - -- $n == tasks.length$。 -- $1 \le n \le 14$。 -- $1 \le tasks[i] \le 10$。 -- $max(tasks[i]) \le sessionTime \le 15$。 - -**示例**: - -- 示例 1: - -```python -输入:tasks = [1,2,3], sessionTime = 3 -输出:2 -解释:你可以在两个工作时间段内完成所有任务。 -- 第一个工作时间段:完成第一和第二个任务,花费 1 + 2 = 3 小时。 -- 第二个工作时间段:完成第三个任务,花费 3 小时。 -``` - -- 示例 2: - -```python -输入:tasks = [3,1,3,1,1], sessionTime = 8 -输出:2 -解释:你可以在两个工作时间段内完成所有任务。 -- 第一个工作时间段:完成除了最后一个任务以外的所有任务,花费 3 + 1 + 3 + 1 = 8 小时。 -- 第二个工作时间段,完成最后一个任务,花费 1 小时。 -``` - -## 解题思路 - -### 思路 1:状压 DP - -### 思路 1:代码 - -```python -class Solution: - def minSessions(self, tasks: List[int], sessionTime: int) -> int: - size = len(tasks) - states = 1 << size - - prefix_sum = [0 for _ in range(states)] - for state in range(states): - for i in range(size): - if (state >> i) & 1: - prefix_sum[state] = prefix_sum[state ^ (1 << i)] + tasks[i] - break - - dp = [float('inf') for _ in range(states)] - dp[0] = 0 - for state in range(states): - sub = state - while sub > 0: - if prefix_sum[sub] <= sessionTime: - dp[state] = min(dp[state], dp[state ^ sub] + 1) - sub = (sub - 1) & state - - return dp[states - 1] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**: -- **空间复杂度**: - diff --git "a/Solutions/1991. \346\211\276\345\210\260\346\225\260\347\273\204\347\232\204\344\270\255\351\227\264\344\275\215\347\275\256.md" "b/Solutions/1991. \346\211\276\345\210\260\346\225\260\347\273\204\347\232\204\344\270\255\351\227\264\344\275\215\347\275\256.md" deleted file mode 100644 index 3ed0d2fa..00000000 --- "a/Solutions/1991. \346\211\276\345\210\260\346\225\260\347\273\204\347\232\204\344\270\255\351\227\264\344\275\215\347\275\256.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [1991. 找到数组的中间位置](https://leetcode.cn/problems/find-the-middle-index-in-array/) - -- 标签:数组、前缀和 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个下标从 $0$ 开始的整数数组 $nums$。 - -**要求**:返回最左边的中间位置 $middleIndex$(也就是所有可能中间位置下标做小的一个)。如果找不到这样的中间位置,则返回 $-1$。 - -**说明**: - -- **中间位置 $middleIndex$**:满足 $nums[0] + nums[1] + … + nums[middleIndex - 1] == nums[middleIndex + 1] + nums[middleIndex + 2] + … + nums[nums.length - 1]$ 的数组下标。 -- 如果 $middleIndex == 0$,左边部分的和定义为 $0$。类似的,如果 $middleIndex == nums.length - 1$,右边部分的和定义为 $0$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [2,3,-1,8,4] -输出:3 -解释: -下标 3 之前的数字和为:2 + 3 + -1 = 4 -下标 3 之后的数字和为:4 = 4 -``` - -- 示例 2: - -```python -输入:nums = [1,-1,4] -输出:2 -解释: -下标 2 之前的数字和为:1 + -1 = 0 -下标 2 之后的数字和为:0 -``` - -## 解题思路 - -### 思路 1:前缀和 - -1. 先遍历一遍数组,求出数组中全部元素和为 $total$。 -2. 再遍历一遍数组,使用变量 $prefix\underline{}sum$ 为前 $i$ 个元素和。 -3. 当遍历到第 $i$ 个元素时,其数组左侧元素之和为 $prefix\underline{}sum$,右侧元素和为 $total - prefix\underline{}sum - nums[i]$。 - 1. 如果左右元素之和相等,即 $prefix\underline{}sum == total - prefix\underline{}sum - nums[i]$($2 \times prefix\underline{}sum + nums[i] == total$) 时,$i$ 为中间位置。此时返回 $i$。 - 2. 如果不满足,则继续累加当前元素到 $prefix\underline{}sum$ 中,继续向后遍历。 -4. 如果找不到符合要求的中间位置,则返回 $-1$。 - -### 思路 1:代码 - -```python -class Solution: - def findMiddleIndex(self, nums: List[int]) -> int: - total = sum(nums) - - prefix_sum = 0 - for i in range(len(nums)): - if 2 * prefix_sum + nums[i] == total: - return i - prefix_sum += nums[i] - - return -1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(1)$。 - diff --git "a/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" "b/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index b0dbda91..00000000 --- "a/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,136 +0,0 @@ -# [1994. 好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) - -- 标签:位运算、数组、数学、动态规划、状态压缩 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数数组 $nums$。 - -**要求**:返回 $nums$ 中不同的好子集的数目对 $10^9 + 7$ 取余的结果。 - -**说明**: - -- **子集**:通过删除 $nums$ 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集。 - -- **好子集**:如果 $nums$ 的一个子集中,所有元素的乘积可以表示为一个或多个互不相同的质数的乘积,那么我们称它为好子集。 - - 比如,如果 `nums = [1, 2, 3, 4]`: - - `[2, 3]` ,`[1, 2, 3]` 和 `[1, 3]` 是好子集,乘积分别为 `6 = 2*3` ,`6 = 2*3` 和 `3 = 3` 。 - - `[1, 4]` 和 `[4]` 不是好子集,因为乘积分别为 `4 = 2*2` 和 `4 = 2*2` 。 - -- $1 \le nums.length \le 10^5$。 -- $1 \le nums[i] \le 30$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,4] -输出:6 -解释:好子集为: -- [1,2]:乘积为 2,可以表示为质数 2 的乘积。 -- [1,2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。 -- [1,3]:乘积为 3,可以表示为质数 3 的乘积。 -- [2]:乘积为 2,可以表示为质数 2 的乘积。 -- [2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。 -- [3]:乘积为 3,可以表示为质数 3 的乘积。 -``` - -- 示例 2: - -```python -输入:nums = [4,2,3,15] -输出:5 -解释:好子集为: -- [2]:乘积为 2,可以表示为质数 2 的乘积。 -- [2,3]:乘积为 6,可以表示为互不相同质数 2 和 3 的乘积。 -- [2,15]:乘积为 30,可以表示为互不相同质数 2,3 和 5 的乘积。 -- [3]:乘积为 3,可以表示为质数 3 的乘积。 -- [15]:乘积为 15,可以表示为互不相同质数 3 和 5 的乘积。 -``` - -## 解题思路 - -### 思路 1:状态压缩 DP - -根据题意可以看出: - -1. 虽然 $nums$ 的长度是 $[1, 10^5]$,但是其值域范围只有 $[1, 30]$,则我们可以将 $[1, 30]$ 的数分为 $3$ 类: - 1. 质数:$[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]$(共 $10$ 个数)。由于好子集的乘积拆解后的质因子只能包含这 $10$ 个,我们可以使用一个数组 $primes$ 记录下这 $10$ 个质数,将好子集的乘积拆解为质因子后,每个 $primes[i]$ 最多出现一次。 - 2. 非质数:$[4, 6, 8, 9, 10, 12, 14, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30]$。非质数肯定不会出现在好子集的乘积拆解后的质因子中。 - 3. 特殊的数:$[1]$。对于一个好子集而言,无论向中间添加多少个 $1$,得到的新子集仍是好子集。 -2. 分类完成后,由于 $[1, 30]$ 中只有 $10$ 个质数,因此我们可以使用一个长度为 $10$ 的二进制数 $state$ 来表示 $primes$ 中质因数的选择情况。其中,如果 $state$ 第 $i$ 位为 $1$,则说明第 $i$ 个质因数 $primes[i]$ 被使用过;如果 $state$ 第 $i$ 位为 $0$,则说明第 $i$ 个质因数 $primes[i]$ 没有被使用过。 -3. 题目规定值相同,但是下标不同的子集视为不同子集,那么我们可先统计出 $nums$ 中每个数 $nums[i]$ 的出现次数,将其存入 $cnts$ 数组中,其中 $cnts[num]$ 表示 $num$ 出现的次数。这样在统计方案时,直接计算出 $num$ 的方案数,再乘以 $cnts[num]$ 即可。 - -接下来,我们就可以使用「动态规划」的方式来解决这道题目了。 - -###### 1. 划分阶段 - -按照质因数的选择情况进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[state]$ 表示为:当质因数选择的情况为 $state$ 时,好子集的数目。 - -###### 3. 状态转移方程 - -对于 $nums$ 中的每个数 $num$,其对应出现次数为 $cnt$。我们可以通过试除法,将 $num$ 分解为不同的质因数,并使用「状态压缩」的方式,用一个二进制数 $cur\underline{}state$ 来表示当前数 $num$ 中使用了哪些质因数。然后枚举所有状态,找到与 $cur\underline{}state$ 不冲突的状态 $state$(也就是除了 $cur\underline{}state$ 中选择的质因数外,选择的其他质因数情况,比如 $cur\underline{}state$ 选择了 $2$ 和 $5$,则枚举不选择 $2$ 和 $5$ 的状态)。 - -此时,状态转移方程为:$dp[state | cur\underline{}state] = \sum (dp[state] \times cnt) \mod MOD , \quad state \text{ \& } cur\underline{}state == 0$ - -###### 4. 初始条件 - -- 当 $state == 0$,所选质因数为空时,空集为好子集,则 $dp[0] = 1$。同时,对于一个好子集而言,无论向中间添加多少个 $1$,得到的新子集仍是好子集,所以对于空集来说,可以对应出 $2^{cnts[1]}$ 个方案,则最终 $dp[0] = 2^{cnts[1]}$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:当质因数的选择的情况为 $state$ 时,好子集的数目。 所以最终结果为所有状态下的好子集数目累积和。所以我们可以枚举所有状态,并记录下所有好子集的数目和,就是最终结果。 - -### 思路 1:代码 - -```python -class Solution: - def numberOfGoodSubsets(self, nums: List[int]) -> int: - MOD = 10 ** 9 + 7 - primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] - - cnts = Counter(nums) - dp = [0 for _ in range(1 << len(primes))] - dp[0] = pow(2, cnts[1], MOD) # 计算 1 - - # num 分解质因数 - for num, cnt in cnts.items(): # 遍历 nums 中所有数及其频数 - if num == 1: # 跳过 1 - continue - - flag = True # 检查 num 的质因数是否都不超过 1 - cur_num = num - cur_state = 0 - for i, prime in enumerate(primes): # 对 num 进行试除 - cur_cnt = 0 - while cur_num % prime == 0: - cur_cnt += 1 - cur_state |= 1 << i - cur_num //= prime - if cur_cnt > 1: # 当前质因数超过 1,则 num 不能添加到子集中,跳过 - flag = False - break - if not flag: - continue - - for state in range(1 << len(primes)): - if state & cur_state == 0: # 只有当前选择状态与前一状态不冲突时,才能进行动态转移 - dp[state | cur_state] = (dp[state | cur_state] + dp[state] * cnt) % MOD - - ans = 0 # 统计所有非空集合的方案数 - for i in range(1, 1 << len(primes)): - ans = (ans + dp[i]) % MOD - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n + m \times 2^p)$,其中 $n$ 为数组 $nums$ 的元素个数,$m$ 为 $nums$ 的最大值,$p$ 为 $[1, 30]$ 中的质数个数。 -- **空间复杂度**:$O(2^p)$。 diff --git "a/Solutions/2011. \346\211\247\350\241\214\346\223\215\344\275\234\345\220\216\347\232\204\345\217\230\351\207\217\345\200\274.md" "b/Solutions/2011. \346\211\247\350\241\214\346\223\215\344\275\234\345\220\216\347\232\204\345\217\230\351\207\217\345\200\274.md" deleted file mode 100644 index 2a422a8f..00000000 --- "a/Solutions/2011. \346\211\247\350\241\214\346\223\215\344\275\234\345\220\216\347\232\204\345\217\230\351\207\217\345\200\274.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [2011. 执行操作后的变量值](https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/) - -- 标签:数组、字符串、模拟 -- 难度:简单 - -## 题目大意 - -存在一种支持 `4` 种操作和 `1` 个变量 `X` 的编程语言: - -- `++X` 和 `x++` 使得变量 `X` 值加 `1`。 -- `--X` 和 `X--` 使得变脸 `X ` 值减 `1`。 - -`X` 的初始值是 `0`。现在给定一个字符串数组 `operations`,这是由操作组成的一个列表。 - -要求:返回执行所有操作后,`X` 的最终值。 - -## 解题思路 - -思路很简单,初始答案 `res` 赋值为 `0`。 - -然后遍历操作列表 `operations`,判断每一个操作 `operation` 的符号。如果操作中含有 `+`,则让答案加 `1`,否则,则让答案减 `1`。最后输出答案。 - -## 代码 - -```python -def finalValueAfterOperations(self, operations): - """ - :type operations: List[str] - :rtype: int - """ - res = 0 - - for opration in operations: - res += 1 if '+' in opration else -1 - - return res -``` - diff --git "a/Solutions/2023. \350\277\236\346\216\245\345\220\216\347\255\211\344\272\216\347\233\256\346\240\207\345\255\227\347\254\246\344\270\262\347\232\204\345\255\227\347\254\246\344\270\262\345\257\271.md" "b/Solutions/2023. \350\277\236\346\216\245\345\220\216\347\255\211\344\272\216\347\233\256\346\240\207\345\255\227\347\254\246\344\270\262\347\232\204\345\255\227\347\254\246\344\270\262\345\257\271.md" deleted file mode 100644 index 55a1bd1e..00000000 --- "a/Solutions/2023. \350\277\236\346\216\245\345\220\216\347\255\211\344\272\216\347\233\256\346\240\207\345\255\227\347\254\246\344\270\262\347\232\204\345\255\227\347\254\246\344\270\262\345\257\271.md" +++ /dev/null @@ -1,105 +0,0 @@ -# [2023. 连接后等于目标字符串的字符串对](https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/) - -- 标签:数组、字符串 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个数字字符串数组 `nums` 和一个数字字符串 `target`。 - -**要求**:返回 `nums[i] + nums[j]` (两个字符串连接,其中 `i != j`)结果等于 `target` 的下标 `(i, j)` 的数目。 - -**说明**: - -- $2 \le nums.length \le 100$。 -- $1 \le nums[i].length \le 100$。 -- $2 \le target.length \le 100$。 -- `nums[i]` 和 `target` 只包含数字。 -- `nums[i]` 和 `target` 不含有任何前导 $0$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = ["777","7","77","77"], target = "7777" -输出:4 -解释:符合要求的下标对包括: -- (0, 1):"777" + "7" -- (1, 0):"7" + "777" -- (2, 3):"77" + "77" -- (3, 2):"77" + "77" -``` - -- 示例 2: - -```python -输入:nums = ["123","4","12","34"], target = "1234" -输出:2 -解释:符合要求的下标对包括 -- (0, 1):"123" + "4" -- (2, 3):"12" + "34" -``` - -## 解题思路 - -### 思路 1:暴力枚举 - -1. 双重循环遍历所有的 `i` 和 `j`,满足 `i != j` 并且 `nums[i] + nums[j] == target` 时,记入到答案数目中。 -2. 遍历完,返回答案数目。 - -### 思路 1:代码 - -```python -class Solution: - def numOfPairs(self, nums: List[str], target: str) -> int: - res = 0 - for i in range(len(nums)): - for j in range(len(nums)): - if i != j and nums[i] + nums[j] == target: - res += 1 - - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n^2)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:哈希表 - -1. 使用哈希表记录字符串数组 `nums` 中所有数字字符串的数量。 -2. 遍历哈希表中的键 `num`。 -3. 将 `target` 根据 `num` 的长度分为前缀 `prefix` 和 `suffix`。 -4. 如果 `num` 等于 `prefix`,则判断后缀 `suffix` 是否在哈希表中,如果在哈希表中,则说明 `prefix` 和 `suffix` 能够拼接为 `target`。 - 1. 如果 `num` 等于 `suffix`,此时 `perfix == suffix`,则答案数目累积为 `table[prefix] * (table[suffix] - 1)`。 - 2. 如果 `num` 不等于 `suffix`,则答案数目累积为 `table[prefix] * table[suffix]`。 -5. 最后输出答案数目。 - -### 思路 2:代码 - -```python -class Solution: - def numOfPairs(self, nums: List[str], target: str) -> int: - res = 0 - table = collections.defaultdict(int) - for num in nums: - table[num] += 1 - - for num in table: - size = len(num) - prefix, suffix = target[ :size], target[size: ] - if num == prefix and suffix in table: - if num == suffix: - res += table[prefix] * (table[suffix] - 1) - else: - res += table[prefix] * table[suffix] - - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。 -- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/2050. \345\271\266\350\241\214\350\257\276\347\250\213 III.md" "b/Solutions/2050. \345\271\266\350\241\214\350\257\276\347\250\213 III.md" deleted file mode 100644 index 7a595260..00000000 --- "a/Solutions/2050. \345\271\266\350\241\214\350\257\276\347\250\213 III.md" +++ /dev/null @@ -1,113 +0,0 @@ -# [2050. 并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) - -- 标签:图、拓扑排序、数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$,表示有 $n$ 节课,课程编号为 $1 \sim n$。 - -再给定一个二维整数数组 $relations$,其中 $relations[j] = [prevCourse_j, nextCourse_j]$,表示课程 $prevCourse_j$ 必须在课程 $nextCourse_j$ 之前完成(先修课的关系)。 - -再给定一个下标从 $0$ 开始的整数数组 $time$,其中 $time[i]$ 表示完成第 $(i + 1)$ 门课程需要花费的月份数。 - -现在根据以下规则计算完成所有课程所需要的最少月份数: - -- 如果一门课的所有先修课都已经完成,则可以在任意时间开始这门课程。 -- 可以同时上任意门课程。 - -**要求**:返回完成所有课程所需要的最少月份数。 - -**说明**: - -- $1 \le n \le 5 * 10^4$。 -- $0 \le relations.length \le min(n * (n - 1) / 2, 5 \times 10^4)$。 -- $relations[j].length == 2$。 -- $1 \le prevCourse_j, nextCourse_j \le n$。 -- $prevCourse_j != nextCourse_j$。 -- 所有的先修课程对 $[prevCourse_j, nextCourse_j]$ 都是互不相同的。 -- $time.length == n$。 -- $1 \le time[i] \le 10^4$。 -- 先修课程图是一个有向无环图。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2021/10/07/ex1.png) - -```python -输入:n = 3, relations = [[1,3],[2,3]], time = [3,2,5] -输出:8 -解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 -你可以在月份 0 同时开始课程 1 和 2 。 -课程 1 花费 3 个月,课程 2 花费 2 个月。 -所以,最早开始课程 3 的时间是月份 3 ,完成所有课程所需时间为 3 + 5 = 8 个月。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2021/10/07/ex2.png) - -```python -输入:n = 5, relations = [[1,5],[2,5],[3,5],[3,4],[4,5]], time = [1,2,3,4,5] -输出:12 -解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 -你可以在月份 0 同时开始课程 1 ,2 和 3 。 -在月份 1,2 和 3 分别完成这三门课程。 -课程 4 需在课程 3 之后开始,也就是 3 个月后。课程 4 在 3 + 4 = 7 月完成。 -课程 5 需在课程 1,2,3 和 4 之后开始,也就是在 max(1,2,3,7) = 7 月开始。 -所以完成所有课程所需的最少时间为 7 + 5 = 12 个月。 -``` - -## 解题思路 - -### 思路 1:拓扑排序 + 动态规划 - -1. 使用邻接表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。定义 $dp[i]$ 为完成第 $i$ 门课程所需要的最少月份数。使用 $ans$ 表示完成所有课程所需要的最少月份数。 -2. 借助队列 $queue$,将所有入度为 $0$ 的节点入队。 -3. 将队列中入度为 $0$ 的节点依次取出。对于取出的每个节点 $u$: - 1. 遍历该节点的相邻节点 $v$,更新相邻节点 $v$ 所需要的最少月份数,即:$dp[v] = max(dp[v], dp[u] + time[v - 1])$。 - 2. 更新完成所有课程所需要的最少月份数 $ans$,即:$ans = max(ans, dp[v])$。 - 3. 相邻节点 $v$ 的入度减 $1$,如果入度减 $1$ 后的节点入度为 0,则将其加入队列 $queue$。 -4. 重复 $3$ 的步骤,直到队列中没有节点。 -5. 最后返回 $ans$。 - -### 思路 1:代码 - -```python -class Solution: - def minimumTime(self, n: int, relations: List[List[int]], time: List[int]) -> int: - graph = [[] for _ in range(n + 1)] - indegrees = [0 for _ in range(n + 1)] - - for u, v in relations: - graph[u].append(v) - indegrees[v] += 1 - - queue = collections.deque() - dp = [0 for _ in range(n + 1)] - - ans = 0 - for i in range(1, n + 1): - if indegrees[i] == 0: - queue.append(i) - dp[i] = time[i - 1] - ans = max(ans, time[i - 1]) - - while queue: - u = queue.popleft() - for v in graph[u]: - dp[v] = max(dp[v], dp[u] + time[v - 1]) - ans = max(ans, dp[v]) - indegrees[v] -= 1 - if indegrees[v] == 0: - queue.append(v) - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$,其中 $m$ 为数组 $relations$ 的长度。 -- **空间复杂度**:$O(m + n)$。 diff --git "a/Solutions/2156. \346\237\245\346\211\276\347\273\231\345\256\232\345\223\210\345\270\214\345\200\274\347\232\204\345\255\220\344\270\262.md" "b/Solutions/2156. \346\237\245\346\211\276\347\273\231\345\256\232\345\223\210\345\270\214\345\200\274\347\232\204\345\255\220\344\270\262.md" deleted file mode 100644 index 816cb5f5..00000000 --- "a/Solutions/2156. \346\237\245\346\211\276\347\273\231\345\256\232\345\223\210\345\270\214\345\200\274\347\232\204\345\255\220\344\270\262.md" +++ /dev/null @@ -1,100 +0,0 @@ -# [2156. 查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) - -- 标签:字符串、滑动窗口、哈希函数、滚动哈希 -- 难度:困难 - -## 题目大意 - -**描述**:如果给定整数 `p` 和 `m`,一个长度为 `k` 且下标从 `0` 开始的字符串 `s` 的哈希值按照如下函数计算: - -- $hash(s, p, m) = (val(s[0]) * p^0 + val(s[1]) * p^1 + ... + val(s[k-1]) * p^{k-1}) mod m$. - -其中 `val(s[i])` 表示 `s[i]` 在字母表中的下标,从 `val('a') = 1` 到 `val('z') = 26`。 - -现在给定一个字符串 `s` 和整数 `power`,`modulo`,`k` 和 `hashValue` 。 - -**要求**:返回 `s` 中 第一个 长度为 `k` 的 子串 `sub`,满足 `hash(sub, power, modulo) == hashValue`。 - -**说明**: - -- 子串:定义为一个字符串中连续非空字符组成的序列。 -- $1 \le k \le s.length \le 2 * 10^4$。 -- $1 \le power, modulo \le 10^9$。 -- $0 \le hashValue < modulo$。 -- `s` 只包含小写英文字母。 -- 测试数据保证一定存在满足条件的子串。 - -**示例**: - -- 示例 1: - -```python -输入:s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0 -输出:"ee" -解释:"ee" 的哈希值为 hash("ee", 7, 20) = (5 * 1 + 5 * 7) mod 20 = 40 mod 20 = 0 。 -"ee" 是长度为 2 的第一个哈希值为 0 的子串,所以我们返回 "ee" 。 -``` - -## 解题思路 - -### 思路 1:Rabin Karp 算法、滚动哈希算法 - -这道题目的思想和 Rabin Karp 字符串匹配算法中用到的滚动哈希思想是一样的。不过两者计算的公式是相反的。 - -- 本题目中的子串哈希计算公式:$hash(s, p, m) = (val(s[i]) * p^0 + val(s[i+1]) * p^1 + ... + val(s[i+k-1]) * p^{k-1}) \mod m$. - -- RK 算法中的子串哈希计算公式:$hash(s, p, m) = (val(s[i]) * p^{k-1} + val(s[i+1]) * p^{k-2} + ... + val(s[i+k-1]) * p^0) \mod m$. - -可以看出两者的哈希计算公式是反的。 - -在 RK 算法中,下一个子串的哈希值计算方式为:$Hash(s_{[i + 1, i + k]}) = \{[Hash(s_{[i, i + k - 1]}) - s_i \times d^{k - 1}] \times d + s_{i + k} \times d^{0} \} \mod m$。其中 $Hash(s_{[i, i + k - 1]}$ 为当前子串的哈希值,$Hash(s_{[i + 1, i + k]})$ 为下一个子串的哈希值。 - -这个公式也可以用文字表示为:**在计算完当前子串的哈希值后,向右滚动字符串,即移除当前子串中最左侧字符的哈希值($val(s[i]) * p^{k-1}$)之后,再将整体乘以 $p$,再移入最右侧字符的哈希值 $val(s[i+k])$**。 - -我们可以参考 RK 算法中滚动哈希的计算方式,将其应用到本题中。 - -因为两者的哈希计算公式相反,所以本题中,我们可以从右侧想左侧逆向遍历字符串,当计算完当前子串的哈希值后,移除当前子串最右侧字符的哈希值($ val(s[i+k-1]) * p^{k-1}$)之后,再整体乘以 $p$,再移入最左侧字符的哈希值 $val(s[i - 1])$。 - -在本题中,对应的下一个逆向子串的哈希值计算方式为:$Hash(s_{[i - 1, i + k - 2]}) = \{ [Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d + s_{i - 1} \times d^{0} \} \mod m$。其中 $Hash(s_{[i, i + k - 1]})$ 为当前子串的哈希值,$Hash(s_{[i - 1, i + k - 2]})$ 是下一个逆向子串的哈希值。 - -利用取模运算的两个公式: - -- $(a \times b) \mod m = ((a \mod m) \times (b \mod m)) \mod m$ -- $(a + b) \mod m = (a \mod m + b \mod m) \mod m$ - -我们可以把上面的式子转变为: - -$\begin{align} Hash(s_{[i - 1, i + k - 2]}) &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d + s_{i - 1} \times d^{0} \} \mod m \cr &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d \mod m + s_{i - 1} \times d^{0} \mod m \} \mod m \cr &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \mod m \times d \mod m + s_{i - 1} \times d^{0} \mod m \} \mod m \end{align}$ - -> 注意:这里之所以用了「反向迭代」而不是「正向迭代」是因为如果使用了正向迭代,那么每次移除的最左侧字符哈希值为 $val(s[i]) * p^0$,之后整体需要除以 $p$,再移入最右侧字符哈希值为($val(s[i+k]) * p^{k-1})$)。 -> -> 这样就用到了「除法」。而除法是不满足取模运算对应的公式的,所以这里不能用这种方法进行迭代。 -> -> 而反向迭代,用到的是乘法。在整个过程中是满足取模运算相关的公式。乘法取余不影响最终结果。 - -### 思路 1:代码 - -```python -class Solution: - def subStrHash(self, s: str, power: int, modulo: int, k: int, hashValue: int) -> str: - hash_t = 0 - n = len(s) - for i in range(n - 1, n - k - 1, -1): - hash_t = (hash_t * power + (ord(s[i]) - ord('a') + 1)) % modulo # 计算最后一个子串的哈希值 - - h = pow(power, k - 1) % modulo # 计算最高位项,方便后续移除操作 - ans = "" - if hash_t == hashValue: - ans = s[n - k: n] - for i in range(n - k - 1, -1, -1): # 反向迭代,滚动计算子串的哈希值 - hash_t = (hash_t - h * (ord(s[i + k]) - ord('a') + 1)) % modulo # 移除 s[i + k] 的哈希值 - hash_t = (hash_t * power % modulo + (ord(s[i]) - ord('a') + 1) % modulo) % modulo # 添加 s[i] 的哈希值 - if hash_t == hashValue: # 如果子串哈希值等于 hashValue,则为答案 - ans = s[i: i + k] - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$。其中字符串 $s$ 的长度为 $n$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/2172. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\344\270\216\345\222\214.md" "b/Solutions/2172. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\344\270\216\345\222\214.md" deleted file mode 100644 index 8e5c3b38..00000000 --- "a/Solutions/2172. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\344\270\216\345\222\214.md" +++ /dev/null @@ -1,110 +0,0 @@ -# [2172. 数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) - -- 标签:位运算、数组、动态规划、状态压缩 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个长度为 $n$ 的整数数组 $nums$ 和一个整数 $numSlots$ 满足 $2 \times numSlots \ge n$。一共有 $numSlots$ 个篮子,编号为 $1 \sim numSlots$。 - -现在需要将所有 $n$ 个整数分到这些篮子中,且每个篮子最多有 $2$ 个整数。 - -**要求**:返回将 $nums$ 中所有数放入 $numSlots$ 个篮子中的最大与和。 - -**说明**: - -- **与和**:当前方案中,每个数与它所在篮子编号的按位与运算结果之和。 - - 比如,将数字 $[1, 3]$ 放入篮子 $1$ 中,$[4, 6]$ 放入篮子 $2$ 中,这个方案的与和为 $(1 \text{ AND } 1) + (3 \text{ AND } 1) + (4 \text{ AND } 2) + (6 \text{ AND } 2) = 1 + 1 + 0 + 2 = 4$。 -- $n == nums.length$。 -- $1 \le numSlots \le 9$。 -- $1 \le n \le 2 \times numSlots$。 -- $1 \le nums[i] \le 15$。 - -**示例**: - -- 示例 1: - -```python -输入:nums = [1,2,3,4,5,6], numSlots = 3 -输出:9 -解释:一个可行的方案是 [1, 4] 放入篮子 1 中,[2, 6] 放入篮子 2 中,[3, 5] 放入篮子 3 中。 -最大与和为 (1 AND 1) + (4 AND 1) + (2 AND 2) + (6 AND 2) + (3 AND 3) + (5 AND 3) = 1 + 0 + 2 + 2 + 3 + 1 = 9。 -``` - -- 示例 2: - -```python -输入:nums = [1,3,10,4,7,1], numSlots = 9 -输出:24 -解释:一个可行的方案是 [1, 1] 放入篮子 1 中,[3] 放入篮子 3 中,[4] 放入篮子 4 中,[7] 放入篮子 7 中,[10] 放入篮子 9 中。 -最大与和为 (1 AND 1) + (1 AND 1) + (3 AND 3) + (4 AND 4) + (7 AND 7) + (10 AND 9) = 1 + 1 + 3 + 4 + 7 + 8 = 24 。 -注意,篮子 2 ,5 ,6 和 8 是空的,这是允许的。 -``` - -## 解题思路 - -### 思路 1:状压 DP - -每个篮子最多可分 $2$ 个整数,则我们可以将 $1$ 个篮子分成两个篮子,这样总共有 $2 \times numSlots$ 个篮子,每个篮子中最多可以装 $1$ 个整数。 - -同时因为 $numSlots$ 的范围为 $[1, 9]$,$2 \times numSlots$ 的范围为 $[2, 19]$,范围不是很大,所以我们可以用「状态压缩」的方式来表示每个篮子中的整数放取情况。 - -即使用一个 $n \times numSlots$ 位的二进制数 $state$ 来表示每个篮子中的整数放取情况。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 个篮子里边放了整数,如果 $state$ 的第 $i$ 位为 $0$,表示第 $i$ 个篮子为空。 - -这样,我们就可以通过动态规划的方式来解决这道题。 - -###### 1. 划分阶段 - -按照 $2 \times numSlots$ 个篮子中的整数放取情况进行阶段划分。 - -###### 2. 定义状态 - -定义当前每个篮子中的整数放取情况为 $state$,$state$ 对应选择的整数个数为 $count(state)$。 - -则可以定义状态 $dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。 - -###### 3. 状态转移方程 - -对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以获得的最大与和,赋值给 $dp[state]$。 - -即状态转移方程为:$dp[state] = min(dp[state], dp[state \oplus (1 \text{ <}\text{< } i)] + (i // 2 + 1) \text{ \& } nums[one\underline{}cnt - 1])$,其中: - -1. $state$ 第 $i$ 位一定为 $1$。 -2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 -3. $i // 2 + 1$ 为篮子对应编号 -4. $nums[one\underline{}cnt - 1]$ 为当前正在考虑的数组元素。 - -###### 4. 初始条件 - -- 初始每个篮子中都没有放整数的情况下,可以获得的最大与和为 $0$,即 $dp[0] = 0$。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。所以最终结果为 $max(dp)$。 - -> 注意:当 $one\underline{}cnt > len(nums)$ 时,无法通过递推得到 $dp[state]$,需要跳过。 - -### 思路 1:代码 - -```python -class Solution: - def maximumANDSum(self, nums: List[int], numSlots: int) -> int: - states = 1 << (numSlots * 2) - dp = [0 for _ in range(states)] - - for state in range(states): - one_cnt = bin(state).count('1') - if one_cnt > len(nums): - continue - for i in range(numSlots * 2): - if (state >> i) & 1: - dp[state] = max(dp[state], dp[state ^ (1 << i)] + ((i // 2 + 1) & nums[one_cnt - 1])) - - return max(dp) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(2^m \times m)$,其中 $m = 2 \times numSlots$。 -- **空间复杂度**:$O(2^m)$。 - diff --git "a/Solutions/2235. \344\270\244\346\225\264\346\225\260\347\233\270\345\212\240.md" "b/Solutions/2235. \344\270\244\346\225\264\346\225\260\347\233\270\345\212\240.md" deleted file mode 100644 index 6f4d3075..00000000 --- "a/Solutions/2235. \344\270\244\346\225\264\346\225\260\347\233\270\345\212\240.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [2235. 两整数相加](https://leetcode.cn/problems/add-two-integers/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个整数 $num1$ 和 $num2$。 - -**要求**:返回这两个整数的和。 - -**说明**: - -- $-100 \le num1, num2 \le 100$。 - -**示例**: - -- 示例 1: - -```python -示例 1: -输入:num1 = 12, num2 = 5 -输出:17 -解释:num1 是 12,num2 是 5,它们的和是 12 + 5 = 17,因此返回 17。 -``` - -- 示例 2: - -```python -输入:num1 = -10, num2 = 4 -输出:-6 -解释:num1 + num2 = -6,因此返回 -6。 -``` - -## 解题思路 - -### 思路 1:直接计算 - -1. 直接计算整数 $num1$ 与 $num2$ 的和,返回 $num1 + num2$ 即可。 - -### 思路 1:代码 - -```python -class Solution: - def sum(self, num1: int, num2: int) -> int: - return num1 + num2 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/2246. \347\233\270\351\202\273\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\346\234\200\351\225\277\350\267\257\345\276\204.md" "b/Solutions/2246. \347\233\270\351\202\273\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\346\234\200\351\225\277\350\267\257\345\276\204.md" deleted file mode 100644 index 2a77e1f2..00000000 --- "a/Solutions/2246. \347\233\270\351\202\273\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\346\234\200\351\225\277\350\267\257\345\276\204.md" +++ /dev/null @@ -1,99 +0,0 @@ -# [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) - -- 标签:树、深度优先搜索、图、拓扑排序、数组、字符串 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个长度为 $n$ 的数组 $parent$ 来表示一棵树(即一个连通、无向、无环图)。该树的节点编号为 $0 \sim n - 1$,共 $n$ 个节点,其中根节点的编号为 $0$。其中 $parent[i]$ 表示节点 $i$ 的父节点,由于节点 $0$ 是根节点,所以 $parent[0] == -1$。再给定一个长度为 $n$ 的字符串,其中 $s[i]$ 表示分配给节点 $i$ 的字符。 - -**要求**:找出路径上任意一对相邻节点都没有分配到相同字符的最长路径,并返回该路径的长度。 - -**说明**: - -- $n == parent.length == s.length$。 -- $1 \le n \le 10^5$。 -- 对所有 $i \ge 1$ ,$0 \le parent[i] \le n - 1$ 均成立。 -- $parent[0] == -1$。 -- $parent$ 表示一棵有效的树。 -- $s$ 仅由小写英文字母组成。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2022/03/25/testingdrawio.png) - -```python -输入:parent = [-1,0,0,1,1,2], s = "abacbe" -输出:3 -解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3。 -可以证明不存在满足上述条件且比 3 更长的路径。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2022/03/25/graph2drawio.png) - -```python -输入:parent = [-1,0,0,0], s = "aabc" -输出:3 -解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3。 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -因为题目给定的是表示父子节点的 $parent$ 数组,为了方便递归遍历相邻节点,我们可以根据 $partent$ 数组,建立一个由父节点指向子节点的有向图 $graph$。 - -如果不考虑相邻节点是否为相同字符这一条件,那么这道题就是在求树的直径(树的最长路径长度)中的节点个数。 - -对于根节点为 $u$ 的树来说: - -1. 如果其最长路径经过根节点 $u$,则 **最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 -2. 如果其最长路径不经过根节点 $u$,则 **最长路径长度 = 某个子树中的最长路径长度**。 - -即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 - -对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{}len$。 - -1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{}len$。 -2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{}len + v\underline{}len + 1)$。 -3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{}len = max(u\underline{}len, \quad v\underline{}len + 1)$。 - -因为题目限定了「相邻节点字符不同」,所以在更新全局最长路径长度和当前节点 $u$ 的最长路径长度时,我们需要判断一下节点 $u$ 与相邻节点 $v$ 的字符是否相同,只有在字符不同的条件下,才能够更新维护。 - -最后,因为题目要求的是树的直径(树的最长路径长度)中的节点个数,而:**路径的节点 = 路径长度 + 1**,所以最后我们返回 $self.ans + 1$ 作为答案。 - -### 思路 1:代码 - -```python -class Solution: - def longestPath(self, parent: List[int], s: str) -> int: - size = len(parent) - - # 根据 parent 数组,建立有向图 - graph = [[] for _ in range(size)] - for i in range(1, size): - graph[parent[i]].append(i) - - ans = 0 - def dfs(u): - nonlocal ans - u_len = 0 # u 节点的最大路径长度 - for v in graph[u]: # 遍历 u 节点的相邻节点 - v_len = dfs(v) # 相邻节点的最大路径长度 - if s[u] != s[v]: # 相邻节点字符不同 - ans = max(ans, u_len + v_len + 1) # 维护最大路径长度 - u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 - return u_len # 返回 u 节点的最大路径长度 - - dfs(0) - return ans + 1 -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 是树的节点数目。 -- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/2249. \347\273\237\350\256\241\345\234\206\345\206\205\346\240\274\347\202\271\346\225\260\347\233\256.md" "b/Solutions/2249. \347\273\237\350\256\241\345\234\206\345\206\205\346\240\274\347\202\271\346\225\260\347\233\256.md" deleted file mode 100644 index 451d2cd3..00000000 --- "a/Solutions/2249. \347\273\237\350\256\241\345\234\206\345\206\205\346\240\274\347\202\271\346\225\260\347\233\256.md" +++ /dev/null @@ -1,89 +0,0 @@ -# [2249. 统计圆内格点数目](https://leetcode.cn/problems/count-lattice-points-inside-a-circle/) - -- 标签:几何、数组、哈希表、数学、枚举 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个二维整数数组 `circles`。其中 `circles[i] = [xi, yi, ri]` 表示网格上圆心为 `(xi, yi)` 且半径为 `ri` 的第 $i$ 个圆。 - -**要求**:返回出现在至少一个圆内的格点数目。 - -**说明**: - -- **格点**:指的是整数坐标对应的点。 -- 圆周上的点也被视为出现在圆内的点。 -- $1 \le circles.length \le 200$。 -- $circles[i].length == 3$。 -- $1 \le xi, yi \le 100$。 -- $1 \le ri \le min(xi, yi)$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2022/03/02/exa-11.png) - -```python -输入:circles = [[2,2,1]] -输出:5 -解释: -给定的圆如上图所示。 -出现在圆内的格点为 (1, 2)、(2, 1)、(2, 2)、(2, 3) 和 (3, 2),在图中用绿色标识。 -像 (1, 1) 和 (1, 3) 这样用红色标识的点,并未出现在圆内。 -因此,出现在至少一个圆内的格点数目是 5。 -``` - -- 示例 2: - -```python -输入:circles = [[2,2,2],[3,4,1]] -输出:16 -解释: -给定的圆如上图所示。 -共有 16 个格点出现在至少一个圆内。 -其中部分点的坐标是 (0, 2)、(2, 0)、(2, 4)、(3, 2) 和 (4, 4)。 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -题目要求中 $1 \le xi, yi \le 100$,$1 \le ri \le min(xi, yi)$。则圆中点的范围为 $1 \le x, y \le 200$。 - -我们可以枚举所有坐标和所有圆,检测该坐标是否在圆中。 - -为了优化枚举范围,我们可以先遍历一遍所有圆,计算最小、最大的 $x$、$y$ 范围,再枚举所有坐标和所有圆,并进行检测。 - -### 思路 1:代码 - -```python -class Solution: - def countLatticePoints(self, circles: List[List[int]]) -> int: - min_x, min_y = 200, 200 - max_x, max_y = 0, 0 - for circle in circles: - if circle[0] + circle[2] > max_x: - max_x = circle[0] + circle[2] - if circle[0] - circle[2] < min_x: - min_x = circle[0] - circle[2] - if circle[1] + circle[2] > max_y: - max_y = circle[1] + circle[2] - if circle[1] - circle[2] < min_y: - min_y = circle[1] - circle[2] - - ans = 0 - for x in range(min_x, max_x + 1): - for y in range(min_y, max_y + 1): - for xi, yi, ri in circles: - if (xi - x) * (xi - x) + (yi - y) * (yi - y) <= ri * ri: - ans += 1 - break - - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(x \times y)$,其中 $x$、$y$ 分别为横纵坐标的个数。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/2276. \347\273\237\350\256\241\345\214\272\351\227\264\344\270\255\347\232\204\346\225\264\346\225\260\346\225\260\347\233\256.md" "b/Solutions/2276. \347\273\237\350\256\241\345\214\272\351\227\264\344\270\255\347\232\204\346\225\264\346\225\260\346\225\260\347\233\256.md" deleted file mode 100644 index d36ebe20..00000000 --- "a/Solutions/2276. \347\273\237\350\256\241\345\214\272\351\227\264\344\270\255\347\232\204\346\225\264\346\225\260\346\225\260\347\233\256.md" +++ /dev/null @@ -1,186 +0,0 @@ -# [2276. 统计区间中的整数数目](https://leetcode.cn/problems/count-integers-in-intervals/) - -- 标签:设计、线段树、有序集合 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个区间的空集。 - -**要求**:设计并实现满足要求的数据结构: - -- 新增:添加一个区间到这个区间集合中。 -- 统计:计算出现在 至少一个 区间中的整数个数。 - -实现 CountIntervals 类: - -- `CountIntervals()` 使用区间的空集初始化对象 -- `void add(int left, int right)` 添加区间 `[left, right]` 到区间集合之中。 -- `int count()` 返回出现在 至少一个 区间中的整数个数。 - -**说明**: - -- 区间 `[left, right]` 表示满足 $left \le x \le right$ 的所有整数 `x`。 -- $1 \le left \le right \le 10^9$。 -- 最多调用 `add` 和 `count` 方法 **总计** $10^5$ 次。 -- 调用 `count` 方法至少一次。 - -**示例**: - -- 示例 1: - -```python -输入: -["CountIntervals", "add", "add", "count", "add", "count"] -[[], [2, 3], [7, 10], [], [5, 8], []] -输出: -[null, null, null, 6, null, 8] - -解释: -CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象 -countIntervals.add(2, 3); // 将 [2, 3] 添加到区间集合中 -countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中 -countIntervals.count(); // 返回 6 - // 整数 2 和 3 出现在区间 [2, 3] 中 - // 整数 7、8、9、10 出现在区间 [7, 10] 中 -countIntervals.add(5, 8); // 将 [5, 8] 添加到区间集合中 -countIntervals.count(); // 返回 8 - // 整数 2 和 3 出现在区间 [2, 3] 中 - // 整数 5 和 6 出现在区间 [5, 8] 中 - // 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中 - // 整数 9 和 10 出现在区间 [7, 10] 中 -``` - -## 解题思路 - -### 思路 1:动态开点线段树 - -这道题可以使用线段树来做。 - -因为区间的范围是 $[1, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。具体做法如下: - -- 初始化方法,构建一棵线段树。每个线段树的节点类存储当前区间中保存的元素个数。 - -- 在 `add` 方法中,将区间 `[left, right]` 上的每个元素值赋值为 `1`,则区间值为 `right - left + 1`。 - -- 在 `count` 方法中,返回区间 $[0, 10^9]$ 的区间值(即区间内元素个数)。 - -### 思路 1:动态开点线段树代码 - -```python -# 线段树的节点类 -class SegTreeNode: - def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): - self.left = left # 区间左边界 - self.right = right # 区间右边界 - self.mid = left + (right - left) // 2 - self.leftNode = leftNode # 区间左节点 - self.rightNode = rightNode # 区间右节点 - self.val = val # 节点值(区间值) - self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 - - -# 线段树类 -class SegmentTree: - # 初始化线段树接口 - def __init__(self, function): - self.tree = SegTreeNode(0, int(1e9)) - self.function = function # function 是一个函数,左右区间的聚合方法 - - # 区间更新接口:将区间为 [q_left, q_right] 上的元素值修改为 val - def update_interval(self, q_left, q_right, val): - self.__update_interval(q_left, q_right, val, self.tree) - - # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 - def query_interval(self, q_left, q_right): - return self.__query_interval(q_left, q_right, self.tree) - - - # 以下为内部实现方法 - - # 区间更新实现方法 - def __update_interval(self, q_left, q_right, val, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - node.lazy_tag = val # 将当前节点的延迟标记标记为 val - interval_size = (node.right - node.left + 1) # 当前节点所在区间大小 - node.val = val * interval_size # 当前节点所在区间每个元素值改为 val - return - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - if q_left <= node.mid: # 在左子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.leftNode) - if q_right > node.mid: # 在右子树中更新区间值 - self.__update_interval(q_left, q_right, val, node.rightNode) - - self.__pushup(node) - - # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 - def __query_interval(self, q_left, q_right, node): - if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 - return node.val # 直接返回节点值 - if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return 0 - - self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 - - res_left = 0 # 左子树查询结果 - res_right = 0 # 右子树查询结果 - if q_left <= node.mid: # 在左子树中查询 - res_left = self.__query_interval(q_left, q_right, node.leftNode) - if q_right > node.mid: # 在右子树中查询 - res_right = self.__query_interval(q_left, q_right, node.rightNode) - return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 - - # 向上更新实现方法:更新 node 节点区间值 等于 该节点左右子节点元素值的聚合计算结果 - def __pushup(self, node): - if node.leftNode and node.rightNode: - node.val = self.function(node.leftNode.val, node.rightNode.val) - - # 向下更新实现方法:更新 node 节点所在区间的左右子节点的值和懒惰标记 - def __pushdown(self, node): - if node.leftNode is None: - node.leftNode = SegTreeNode(node.left, node.mid) - if node.rightNode is None: - node.rightNode = SegTreeNode(node.mid + 1, node.right) - - lazy_tag = node.lazy_tag - if node.lazy_tag is None: - return - - node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 - left_size = (node.leftNode.right - node.leftNode.left + 1) - node.leftNode.val = lazy_tag * left_size # 更新左子节点值 - - node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 - right_size = (node.rightNode.right - node.rightNode.left + 1) - node.rightNode.val = lazy_tag * right_size # 更新右子节点值 - - node.lazy_tag = None # 更新当前节点的懒惰标记 - - -class CountIntervals: - - def __init__(self): - self.STree = SegmentTree(lambda x, y: x + y) - self.left = 10 ** 9 - self.right = 0 - - - def add(self, left: int, right: int) -> None: - self.STree.update_interval(left, right, 1) - - - def count(self) -> int: - return self.STree.query_interval(0, int(1e9)) - - - -# Your CountIntervals object will be instantiated and called as such: -# obj = CountIntervals() -# obj.add(left,right) -# param_2 = obj.count() -``` - diff --git "a/Solutions/2376. \347\273\237\350\256\241\347\211\271\346\256\212\346\225\264\346\225\260.md" "b/Solutions/2376. \347\273\237\350\256\241\347\211\271\346\256\212\346\225\264\346\225\260.md" deleted file mode 100644 index 3ab07d52..00000000 --- "a/Solutions/2376. \347\273\237\350\256\241\347\211\271\346\256\212\346\225\264\346\225\260.md" +++ /dev/null @@ -1,104 +0,0 @@ -# [2376. 统计特殊整数](https://leetcode.cn/problems/count-special-integers/) - -- 标签:数学、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个正整数 $n$。 - -**要求**:求区间 $[1, n]$ 内的所有整数中,特殊整数的数目。 - -**说明**: - -- **特殊整数**:如果一个正整数的每一个数位都是互不相同的,则称它是特殊整数。 -- $1 \le n \le 2 \times 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入:n = 20 -输出:19 -解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。 -``` - -- 示例 2: - -```python -输入:n = 5 -输出:5 -解释:1 到 5 所有整数都是特殊整数。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 - 4. 开始时没有填写数字。 - -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 - -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), - 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 - 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前 $pos$ 位限制。 - 3. $isNum == True$ 表示 $pos$ 位选择了数字。 - -6. 最后的方案数为 `dfs(0, 0, True, False)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def countSpecialNumbers(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # state: 之前选过的数字集合。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 - def dfs(pos, state, isLimit, isNum): - if pos == len(s): - # isNum 为 True,则表示当前方案符合要求 - return int(isNum) - - ans = 0 - if not isNum: - # 如果 isNum 为 False,则可以跳过当前数位 - ans = dfs(pos + 1, state, False, False) - - # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 - minX = 0 if isNum else 1 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - # d 不在选择的数字集合中,即之前没有选择过 d - if (state >> d) & 1 == 0: - ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) - return ans - - return dfs(0, 0, True, False) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$,其中 $n$ 为给定整数。 -- **空间复杂度**:$O(\log n \times 2^{10})$。 diff --git "a/Solutions/2427. \345\205\254\345\233\240\345\255\220\347\232\204\346\225\260\347\233\256.md" "b/Solutions/2427. \345\205\254\345\233\240\345\255\220\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index 4f326fea..00000000 --- "a/Solutions/2427. \345\205\254\345\233\240\345\255\220\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [2427. 公因子的数目](https://leetcode.cn/problems/number-of-common-factors/) - -- 标签:数学、枚举、数论 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个正整数 $a$ 和 $b$。 - -**要求**:返回 $a$ 和 $b$ 的公因子数目。 - -**说明**: - -- **公因子**:如果 $x$ 可以同时整除 $a$ 和 $b$,则认为 $x$ 是 $a$ 和 $b$ 的一个公因子。 -- $1 \le a, b \le 1000$。 - -**示例**: - -- 示例 1: - -```python -输入:a = 12, b = 6 -输出:4 -解释:12 和 6 的公因子是 1、2、3、6。 -``` - -- 示例 2: - -```python -输入:a = 25, b = 30 -输出:2 -解释:25 和 30 的公因子是 1、5。 -``` - -## 解题思路 - -### 思路 1:枚举算法 - -最直接的思路就是枚举所有 $[1, min(a, b)]$ 之间的数,并检查是否能同时整除 $a$ 和 $b$。 - -当然,因为 $a$ 与 $b$ 的公因子肯定不会超过 $a$ 与 $b$ 的最大公因数,则我们可以直接枚举 $[1, gcd(a, b)]$ 之间的数即可,其中 $gcd(a, b)$ 是 $a$ 与 $b$ 的最大公约数。 - -### 思路 1:代码 - -```python -class Solution: - def commonFactors(self, a: int, b: int) -> int: - ans = 0 - for i in range(1, math.gcd(a, b) + 1): - if a % i == 0 and b % i == 0: - ans += 1 - return ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\sqrt{min(a, b)})$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/2538. \346\234\200\345\244\247\344\273\267\345\200\274\345\222\214\344\270\216\346\234\200\345\260\217\344\273\267\345\200\274\345\222\214\347\232\204\345\267\256\345\200\274.md" "b/Solutions/2538. \346\234\200\345\244\247\344\273\267\345\200\274\345\222\214\344\270\216\346\234\200\345\260\217\344\273\267\345\200\274\345\222\214\347\232\204\345\267\256\345\200\274.md" deleted file mode 100644 index 1f7a6b6e..00000000 --- "a/Solutions/2538. \346\234\200\345\244\247\344\273\267\345\200\274\345\222\214\344\270\216\346\234\200\345\260\217\344\273\267\345\200\274\345\222\214\347\232\204\345\267\256\345\200\274.md" +++ /dev/null @@ -1,121 +0,0 @@ -# [2538. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) - -- 标签:树、深度优先搜索、数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$ 和一个长度为 $n - 1$ 的二维整数数组 $edges$ 用于表示一个 $n$ 个节点的无向无根图,节点编号为 $0 \sim n - 1$。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间有一条边。再给定一个整数数组 $price$,其中 $price[i]$ 表示图中节点 $i$ 的价值。 - -一条路径的价值和是这条路径上所有节点的价值之和。 - -你可以选择树中任意一个节点作为根节点 $root$。选择 $root$ 为根的开销是以 $root$ 为起点的所有路径中,价值和最大的一条路径与最小的一条路径的差值。 - -**要求**:返回所有节点作为根节点的选择中,最大的开销为多少。 - -**说明**: - -- $1 \le n \le 10^5$。 -- $edges.length == n - 1$。 -- $0 \le ai, bi \le n - 1$。 -- $edges$ 表示一棵符合题面要求的树。 -- $price.length == n$。 -- $1 \le price[i] \le 10^5$。 - -**示例**: - -- 示例 1: - -![](https://assets.leetcode.com/uploads/2022/12/01/example14.png) - -```python -输入:n = 6, edges = [[0,1],[1,2],[1,3],[3,4],[3,5]], price = [9,8,7,6,10,5] -输出:24 -解释:上图展示了以节点 2 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。 -- 第一条路径节点为 [2,1,3,4]:价值为 [7,8,6,10] ,价值和为 31 。 -- 第二条路径节点为 [2] ,价值为 [7] 。 -最大路径和与最小路径和的差值为 24 。24 是所有方案中的最大开销。 -``` - -- 示例 2: - -![](https://assets.leetcode.com/uploads/2022/11/24/p1_example2.png) - -```python -输入:n = 3, edges = [[0,1],[1,2]], price = [1,1,1] -输出:2 -解释:上图展示了以节点 0 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。 -- 第一条路径包含节点 [0,1,2]:价值为 [1,1,1] ,价值和为 3 。 -- 第二条路径节点为 [0] ,价值为 [1] 。 -最大路径和与最小路径和的差值为 2 。2 是所有方案中的最大开销。 -``` - -## 解题思路 - -### 思路 1:树形 DP + 深度优先搜索 - -1. 因为 $price$ 数组中元素都为正数,所以价值和最小的一条路径一定为「单个节点」,也就是根节点 $root$ 本身。 -2. 因为价值和最大的路径是从根节点 $root$ 出发的价值和最大的一条路径,所以「最大的开销」等于「从根节点 $root$ 出发的价值和最大的一条路径」与「路径中一个端点值」 的差值。 -3. 价值和最大的路径的两个端点中,一个端点为根节点 $root$,另一个节点为叶子节点。 - -这样问题就变为了求树中一条路径,使得路径的价值和减去其中一个端点值的权值最大。 - -对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大开销变量 $ans$。 - -然后定义函数 ` def dfs(self, u, father):` 计算以节点 $u$ 为根节点的子树中,带端点的最大路径和 $max\underline{}s1$,以及去掉端点的最大路径和 $max\underline{}s2$,其中 $father$ 表示节点 $u$ 的根节点,用于遍历邻接节点的过程中过滤父节点,避免重复遍历。 - -初始化带端点的最大路径和 $max\underline{}s1$ 为 $price[u]$,表示当前只有一个节点,初始化去掉端点的最大路径和 $max\underline{}s2$ 为 $0$,表示当前没有节点。 - -然后在遍历节点 $u$ 的相邻节点 $v$ 时,递归调用 $dfs(v, u)$,获取以节点 $v$ 为根节点的子树中,带端点的最大路径和 $s1$,以及去掉端点的最大路径和 $s2$。此时最大开销变量 $self.ans$ 有两种情况: - -1. $u$ 的子树中带端点的最大路径和,加上 $v$ 的子树中不带端点的最大路径和,即:$max\underline{}s1 + s2$。 -2. $u$ 的子树中去掉端点的最大路径和,加上 $v$ 的子树中带端点的最大路径和,即:$max\underline{}s2 + s1$。 - -此时我们更新最大开销变量 $self.ans$,即:$self.ans = max(self.ans, \quad max\underline{}s1 + s2, \quad max\underline{}s2 + s1)$。 - -然后更新 $u$ 的子树中带端点的最大路径和 $max\underline{}s1$,即:$max\underline{}s1= max(max\underline{}s1, \quad s1 + price[u])$。 - -再更新 $u$ 的子树中去掉端点的最大路径和 $max\underline{}s2$,即:$max\underline{}s2 = max(max\underline{}s2, \quad s2 + price[u])$。 - -最后返回带端点 $u$ 的最大路径和 $max\underline{}s1$,以及去掉端点 $u$ 的最大路径和 $。 - -最终,最大开销变量 $self.ans$ 即为答案。 - -### 思路 1:代码 - -```python -class Solution: - def __init__(self): - self.ans = 0 - - def dfs(self, graph, price, u, father): - max_s1 = price[u] - max_s2 = 0 - for v in graph[u]: - if v == father: # 过滤父节点,避免重复遍历 - continue - s1, s2 = self.dfs(graph, price, v, u) - self.ans = max(self.ans, max_s1 + s2, max_s2 + s1) - max_s1 = max(max_s1, s1 + price[u]) - max_s2 = max(max_s2, s2 + price[u]) - return max_s1, max_s2 - - def maxOutput(self, n: int, edges: List[List[int]], price: List[int]) -> int: - graph = [[] for _ in range(n)] - for u, v in edges: - graph[u].append(v) - graph[v].append(u) - - self.dfs(graph, price, 0, -1) - return self.ans -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n)$,其中 $n$ 为树中节点个数。 -- **空间复杂度**:$O(n)$。 - -## 参考链接 - -- 【题解】[二维差分模板 双指针 树形DP 树的直径【力扣周赛 328】](https://www.bilibili.com/video/BV1QT41127kJ/) -- 【题解】[2538. 最大价值和与最小价值和的差值 题解](https://github.com/doocs/leetcode/blob/main/solution/2500-2599/2538.Difference Between Maximum and Minimum Price Sum/README.md) diff --git "a/Solutions/2585. \350\216\267\345\276\227\345\210\206\346\225\260\347\232\204\346\226\271\346\263\225\346\225\260.md" "b/Solutions/2585. \350\216\267\345\276\227\345\210\206\346\225\260\347\232\204\346\226\271\346\263\225\346\225\260.md" deleted file mode 100644 index 168faf91..00000000 --- "a/Solutions/2585. \350\216\267\345\276\227\345\210\206\346\225\260\347\232\204\346\226\271\346\263\225\346\225\260.md" +++ /dev/null @@ -1,105 +0,0 @@ -# [2585. 获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) - -- 标签:数组、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:考试中有 $n$ 种类型的题目。给定一个整数 $target$ 和一个下标从 $0$ 开始的二维整数数组 $types$,其中 $types[i] = [count_i, marks_i]$ 表示第 $i$ 种类型的题目有 $count_i$ 道,每道题目对应 $marks_i$ 分。 - -**要求**:返回你在考试中恰好得到 $target$ 分的方法数。由于答案可能很大,结果需要对 $10^9 + 7$ 取余。 - -**说明**: - -- 同类型题目无法区分。比如说,如果有 $3$ 道同类型题目,那么解答第 $1$ 和第 $2$ 道题目与解答第 $1$ 和第 $3$ 道题目或者第 $2$ 和第 $3$ 道题目是相同的。 -- $1 \le target \le 1000$。 -- $n == types.length$。 -- $1 \le n \le 50$。 -- $types[i].length == 2$。 -- $1 \le counti, marksi \le 50$。 - -**示例**: - -- 示例 1: - -```python -输入:target = 6, types = [[6,1],[3,2],[2,3]] -输出:7 -解释:要获得 6 分,你可以选择以下七种方法之一: -- 解决 6 道第 0 种类型的题目:1 + 1 + 1 + 1 + 1 + 1 = 6 -- 解决 4 道第 0 种类型的题目和 1 道第 1 种类型的题目:1 + 1 + 1 + 1 + 2 = 6 -- 解决 2 道第 0 种类型的题目和 2 道第 1 种类型的题目:1 + 1 + 2 + 2 = 6 -- 解决 3 道第 0 种类型的题目和 1 道第 2 种类型的题目:1 + 1 + 1 + 3 = 6 -- 解决 1 道第 0 种类型的题目、1 道第 1 种类型的题目和 1 道第 2 种类型的题目:1 + 2 + 3 = 6 -- 解决 3 道第 1 种类型的题目:2 + 2 + 2 = 6 -- 解决 2 道第 2 种类型的题目:3 + 3 = 6 -``` - -- 示例 2: - -```python -输入:target = 5, types = [[50,1],[50,2],[50,5]] -输出:4 -解释:要获得 5 分,你可以选择以下四种方法之一: -- 解决 5 道第 0 种类型的题目:1 + 1 + 1 + 1 + 1 = 5 -- 解决 3 道第 0 种类型的题目和 1 道第 1 种类型的题目:1 + 1 + 1 + 2 = 5 -- 解决 1 道第 0 种类型的题目和 2 道第 1 种类型的题目:1 + 2 + 2 = 5 -- 解决 1 道第 2 种类型的题目:5 -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i][w]$ 表示为:前 $i$ 种题目恰好组成 $w$ 分的方案数。 - -###### 3. 状态转移方程 - -前 $i$ 种题目恰好组成 $w$ 分的方案数,等于前 $i - 1$ 种问题恰好组成 $w - k \times marks_i$ 分的方案数总和,即状态转移方程为:$dp[i][w] = \sum_{k = 0} dp[i - 1][w - k \times marks_i]$。 - -###### 4. 初始条件 - -- 前 $0$ 种题目恰好组成 $0$ 分的方案数为 $1$。 - -###### 5. 最终结果 - -根据我们之前定义的状态, $dp[i][w]$ 表示为:前 $i$ 种题目恰好组成 $w$ 分的方案数。 所以最终结果为 $dp[size][target]$。 - -### 思路 1:代码 - -```python -class Solution: - def waysToReachTarget(self, target: int, types: List[List[int]]) -> int: - size = len(types) - group_count = [types[i][0] for i in range(len(types))] - weight = [[(types[i][1] * k) for k in range(types[i][0] + 1)] for i in range(len(types))] - mod = 1000000007 - - dp = [[0 for _ in range(target + 1)] for _ in range(size + 1)] - dp[0][0] = 1 - - # 枚举前 i 组物品 - for i in range(1, size + 1): - # 枚举背包装载重量 - for w in range(target + 1): - # 枚举第 i 组物品能取个数 - dp[i][w] = dp[i - 1][w] - for k in range(1, group_count[i - 1] + 1): - if w >= weight[i - 1][k]: - dp[i][w] += dp[i - 1][w - weight[i - 1][k]] - dp[i][w] %= mod - - return dp[size][target] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times target \times m)$,其中 $n$ 为题目种类数,$target$ 为目标分数,$m$ 为每种题目的最大分数。 -- **空间复杂度**:$O(n \times target)$。 - diff --git "a/Solutions/2719. \347\273\237\350\256\241\346\225\264\346\225\260\346\225\260\347\233\256.md" "b/Solutions/2719. \347\273\237\350\256\241\346\225\264\346\225\260\346\225\260\347\233\256.md" deleted file mode 100644 index 39335ec9..00000000 --- "a/Solutions/2719. \347\273\237\350\256\241\346\225\264\346\225\260\346\225\260\347\233\256.md" +++ /dev/null @@ -1,106 +0,0 @@ -# [2719. 统计整数数目](https://leetcode.cn/problems/count-of-integers/) - -- 标签:数学、字符串、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定两个数字字符串 $num1$ 和 $num2$,以及两个整数 $max\underline{}sum$ 和 $min\underline{}sum$。 - -**要求**:返回好整数的数目。答案可能很大,请返回答案对 $10^9 + 7$ 取余后的结果。 - -**说明**: - -- **好整数**:如果一个整数 $x$ 满足一下条件,我们称它是一个好整数: - - $num1 \le x \le num2$。 - - $num\underline{}sum \le digit\underline{}sum(x) \le max\underline{}sum$。 - -- $digit\underline{}sum(x)$ 表示 $x$ 各位数字之和。 -- $1 \le num1 \le num2 \le 10^{22}$。 -- $1 \le min\underline{}sum \le max\underline{}sum \le 400$。 - -**示例**: - -- 示例 1: - -```python -输入:num1 = "1", num2 = "12", min_num = 1, max_num = 8 -输出:11 -解释:总共有 11 个整数的数位和在 1 到 8 之间,分别是 1,2,3,4,5,6,7,8,10,11 和 12 。所以我们返回 11。 -``` - -- 示例 2: - -```python -输入:num1 = "1", num2 = "5", min_num = 1, max_num = 5 -输出:5 -解释:数位和在 1 到 5 之间的 5 个整数分别为 1,2,3,4 和 5 。所以我们返回 5。 -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $num1$ 补上前导 $0$,补到和 $num2$ 长度一致,定义递归函数 `def dfs(pos, total, isMaxLimit, isMinLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True, True)` 开始递归。 `dfs(0, 0, True, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始数位和为 $0$。 - 3. 开始时当前数位最大值受到最高位数位的约束。 - 4. 开始时当前数位最小值受到最高位数位的约束。 -2. 如果 $total > max\underline{}sum$,说明当前方案不符合要求,则返回方案数 $0$。 -3. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: - 1. 如果 $min\underline{}sum \le total \le max\underline{}sum$,说明当前方案符合要求,则返回方案数 $1$。 - 2. 如果不满足,则当前方案不符合要求,则返回方案数 $0$。 -4. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -5. 根据 $isMaxLimit$ 和 $isMinLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 -6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 -7. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, total + d, isMaxLimit and d == maxX, isMinLimit and d == minX)`。 - 1. `total + d` 表示当前数位和 $total$ 加上 $d$。 - 2. `isMaxLimit and d == maxX` 表示 $pos + 1$ 位最大值受到之前 $pos$ 位限制。 - 3. `isMinLimit and d == maxX` 表示 $pos + 1$ 位最小值受到之前 $pos$ 位限制。 -8. 最后的方案数为 `dfs(0, 0, True, True) % MOD`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def count(self, num1: str, num2: str, min_sum: int, max_sum: int) -> int: - MOD = 10 ** 9 + 7 - # 将 num1 补上前导 0,补到和 num2 长度一致 - m, n = len(num1), len(num2) - if m < n: - num1 = '0' * (n - m) + num1 - - @cache - # pos: 第 pos 个数位 - # total: 表示数位和 - # isMaxLimit: 表示是否受到上限选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - # isMaxLimit: 表示是否受到下限选择限制。如果为真,则第 pos 位填入数字最小为 s[pos];如果为假,则最小可为 0。 - def dfs(pos, total, isMaxLimit, isMinLimit): - if total > max_sum: - return 0 - - if pos == n: - # 当 min_sum <= total <= max_sum 时,当前方案符合要求 - return int(total >= min_sum) - - ans = 0 - # 如果受到选择限制,则最小可选择数字为 num1[pos],否则最大可选择数字为 0。 - minX = int(num1[pos]) if isMinLimit else 0 - # 如果受到选择限制,则最大可选择数字为 num2[pos],否则最大可选择数字为 9。 - maxX = int(num2[pos]) if isMaxLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - ans += dfs(pos + 1, total + d, isMaxLimit and d == maxX, isMinLimit and d == minX) - return ans % MOD - - return dfs(0, 0, True, True) % MOD -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times 10)$,其中 $n$ 为数组 $nums2$ 的长度。 -- **空间复杂度**:$O(n \times max\underline{}sum)$。 - diff --git "a/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275 (\345\212\250\346\200\201\350\247\204\345\210\222).md" "b/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275 (\345\212\250\346\200\201\350\247\204\345\210\222).md" deleted file mode 100644 index f4a9e49a..00000000 --- "a/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275 (\345\212\250\346\200\201\350\247\204\345\210\222).md" +++ /dev/null @@ -1,61 +0,0 @@ -# 题目相关 - -- 标签: -- 难度: - -## 题目大意 - -**描述**: - -**要求**: - -**说明**: - -- - -**示例**: - -- 示例 1: - -```python -``` - -- 示例 2: - -```python -``` - -## 解题思路 - -### 思路 1:动态规划 - -###### 1. 划分阶段 - -按照 进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[i]$ 表示为:。 - -###### 3. 状态转移方程 - - - -###### 4. 初始条件 - - - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[i]$ 表示为:。 所以最终结果为 $dp[size]$。 - -### 思路 1:代码 - -```python - -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**: -- **空间复杂度**: diff --git "a/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275.md" "b/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275.md" deleted file mode 100644 index 1e1c8ef3..00000000 --- "a/Solutions/LeetCode \350\247\243\351\242\230\346\212\245\345\221\212\347\251\272\347\231\275.md" +++ /dev/null @@ -1,58 +0,0 @@ -# 题目相关 - -- 标签: -- 难度: - -## 题目大意 - -**描述**: - -**要求**: - -**说明**: - -- - -**示例**: - -- 示例 1: - -```python -``` - -- 示例 2: - -```python -``` - -## 解题思路 - -### 思路 1: - - - -### 思路 1:代码 - -```python - -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**: -- **空间复杂度**: - -### 思路 2: - - - -### 思路 2:代码 - -```python - -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**: -- **空间复杂度**: \ No newline at end of file diff --git "a/Solutions/\345\211\221\346\214\207 Offer 03. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 03. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 1bb00223..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 03. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,26 +0,0 @@ -# [剑指 Offer 03. 数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) - -- 标签:数组、哈希表、排序 -- 难度:简单 - -## 题目大意 - -给定一个包含 `n + 1` 个整数的数组 `nums`,里边包含的值都在 `1 ~ n` 之间。假设 `nums` 中只存在一个重复的整数,要求找出这个重复的数。 - -## 解题思路 - -使用哈希表存储数组每个元素,遇到重复元素则直接返回该元素。 - -## 代码 - -```python -class Solution: - def findRepeatNumber(self, nums: List[int]) -> int: - nums_dict = dict() - for num in nums: - if num in nums_dict: - return num - nums_dict[num] = 1 - return -1 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 04. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" "b/Solutions/\345\211\221\346\214\207 Offer 04. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" deleted file mode 100644 index 06439d9d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 04. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [剑指 Offer 04. 二维数组中的查找](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) - -- 标签:数组、二分查找、分治、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 大小的有序整数矩阵 `matrix`。每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 `target`。 - -要求:判断矩阵中是否可以找到 `target`,若找到 `target`,返回 `True`,否则返回 `False`。 - -## 解题思路 - -矩阵是有序的,可以考虑使用二分搜索来进行查找。 - -迭代对角线元素,假设对角线元素的坐标为 `(row, col)`。把数组元素按对角线分为右上角部分和左下角部分。 - -则对于当前对角线元素右侧第 `row` 行、对角线元素下侧第 `col` 列进行二分查找。 - -- 如果找到目标,直接返回 `True`。 -- 如果找不到目标,则缩小范围,继续查找。 -- 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 - -## 代码 - -```python -class Solution: - def diagonalBinarySearch(self, matrix, diagonal, target): - left = 0 - right = diagonal - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][mid] < target: - left = mid + 1 - else: - right = mid - return left - - def rowBinarySearch(self, matrix, begin, cols, target): - left = begin - right = cols - while left < right: - mid = left + (right - left) // 2 - if matrix[begin][mid] < target: - left = mid + 1 - elif matrix[begin][mid] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= cols and matrix[begin][left] == target - - def colBinarySearch(self, matrix, begin, rows, target): - left = begin + 1 - right = rows - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][begin] < target: - left = mid + 1 - elif matrix[mid][begin] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= rows and matrix[left][begin] == target - - def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool: - rows = len(matrix) - if rows == 0: - return False - cols = len(matrix[0]) - if cols == 0: - return False - - min_val = min(rows, cols) - index = self.diagonalBinarySearch(matrix, min_val - 1, target) - if matrix[index][index] == target: - return True - for i in range(index + 1): - row_search = self.rowBinarySearch(matrix, i, cols - 1, target) - col_search = self.colBinarySearch(matrix, i, rows - 1, target) - if row_search or col_search: - return True - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 05. \346\233\277\346\215\242\347\251\272\346\240\274.md" "b/Solutions/\345\211\221\346\214\207 Offer 05. \346\233\277\346\215\242\347\251\272\346\240\274.md" deleted file mode 100644 index ad5bbd15..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 05. \346\233\277\346\215\242\347\251\272\346\240\274.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [剑指 Offer 05. 替换空格](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) - -- 标签:字符串 -- 难度:简单 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:将字符串 `s` 中的每个空格换成 `%20`。 - -## 解题思路 - -Python 的字符串是不可变类型,所以需要先用数组存储答案,再将其转为字符串返回。具体操作如下。 - -- 定义数组 `res`,遍历字符串 `s`。 - - 如果当前字符 `ch` 为空格,则将 ` %20` 加入到数组中。 - - 如果当前字符 `ch` 不为空格,则直接加入到数组中。 -- 遍历完之后,通过 `join` 将其转为字符串返回。 - -## 代码 - -```python -class Solution: - def replaceSpace(self, s: str) -> str: - res = [] - for ch in s: - if ch == ' ': - res.append("%20") - else: - res.append(ch) - return "".join(res) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 06. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer 06. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" deleted file mode 100644 index 4bee5afe..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 06. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [剑指 Offer 06. 从尾到头打印链表](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) - -- 标签:栈、递归、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定一个链表的头节点 `head`。 - -要求:从尾到头反过来返回每个节点的值(用数组返回)。 - -## 解题思路 - -- 定义数组 `res`,从头到尾遍历链表。 -- 将每个节点值存入数组中。 -- 直接返回倒序数组。 - -## 代码 - -```python -class Solution: - def reversePrint(self, head: ListNode) -> List[int]: - res = [] - while head: - res.append(head.val) - head = head.next - return res[::-1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 07. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer 07. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 5305e4d0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 07. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer 07. 重建二叉树](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/) - -- 标签:树、数组、哈希表、分治、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的前序遍历结果和中序遍历结果。 - -要求:构建该二叉树,并返回其根节点。假设树中没有重复的元素。 - -## 解题思路 - -前序遍历的顺序是:根 -> 左 -> 右。中序遍历的顺序是:左 -> 根 -> 右。根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: - -- 从前序遍历顺序中当前根节点的位置在 `postorder[0]`。 -- 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 -- 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 -- 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 - -## 代码 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def createTree(preorder, inorder, n): - if n == 0: - return None - k = 0 - while preorder[0] != inorder[k]: - k += 1 - node = TreeNode(inorder[k]) - node.left = createTree(preorder[1: k + 1], inorder[0: k], k) - node.right = createTree(preorder[k + 1:], inorder[k + 1:], n - k - 1) - return node - - return createTree(preorder, inorder, len(inorder)) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 09. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 09. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" deleted file mode 100644 index 704fda3c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 09. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer 09. 用两个栈实现队列](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) - -- 标签:栈、设计、队列 -- 难度:简单 - -## 题目大意 - -要求:使用两个栈实现先入先出队列。需要实现对应的两个函数: - -- `appendTail`:在队列尾部插入整数。 -- `deleteHead`:在队列头部删除整数(如果队列中没有元素,`deleteHead` 返回 -1)。 - -## 解题思路 - -使用两个栈,inStack 用于输入,outStack 用于输出。 - -- `appendTail` 操作:将元素压入 inStack 中 -- `deleteHead` 操作: - - 先判断 `inStack` 和 `outStack` 是否都为空,如果都为空则说明队列中没有元素,直接返回 `-1`。 - - 如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 - -## 代码 - -```python -class CQueue: - - def __init__(self): - self.inStack = [] - self.outStack = [] - - - def appendTail(self, value: int) -> None: - self.inStack.append(value) - - - def deleteHead(self) -> int: - if len(self.outStack) == 0 and len(self.inStack) == 0: - return -1 - if (len(self.outStack) == 0): - while (len(self.inStack) != 0): - self.outStack.append(self.inStack[-1]) - self.inStack.pop() - top = self.outStack[-1] - self.outStack.pop() - return top -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 10- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 10- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" deleted file mode 100644 index 8cc7a2c2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 10- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 10- I. 斐波那契数列](https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/) - -- 标签:记忆化搜索、数学、动态规划 -- 难度:简单 - -## 题目大意 - -给定一个整数 `n`。 - -要求:计算斐波那契数列的第 `n` 项。 - -注意:答案需对 `1000000007` 进行取余操作。 - -## 解题思路 - -斐波那契的递推公式为:`F(n) = F(n-1) + F(n-2)`。 - -直接根据递推公式求解即可。注意答案需要取余。 - -## 代码 - -```python -class Solution: - def fib(self, n: int) -> int: - if n < 2: - return n - f1 = 0 - f2 = 0 - f3 = 1 - for i in range(2, n + 1): - f1, f2 = f2, f3 - f3 = (f1 + f2) % 1000000007 - return f3 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 10- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" "b/Solutions/\345\211\221\346\214\207 Offer 10- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" deleted file mode 100644 index 45ef67ad..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 10- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [剑指 Offer 10- II. 青蛙跳台阶问题](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) - -- 标签:记忆化搜索、数学、动态规划 -- 难度:简单 - -## 题目大意 - -一直青蛙一次可以跳上 `1` 级台阶,也可以跳上 `2` 级台阶。 - -要求:求该青蛙跳上 `n` 级台阶共有多少中跳法。答案需要对 `1000000007` 取余。 - -## 解题思路 - -先来看一下规律: - -第 0 级台阶:1 种方法(比较特殊) - -第 1 级台阶:1 种方法(从 0 阶爬 1 阶) - -第 2 阶台阶:2 种方法(从 0 阶爬 2 阶,从 1 阶爬 1 阶) - -第 i 阶台阶:从第 i-1 阶台阶爬 1 阶,或者从第 i-2 阶台阶爬 2 阶。 - -则推出递推公式为: - -- 当 `n = 0` 时,`F(i) = 1`。 -- 当 `n > 0` 时,`F(i) = F(i-1) + F(i-2)`。 - -## 代码 - -```python -class Solution: - def numWays(self, n: int) -> int: - if n == 0: - return 1 - - f1, f2, f3 = 0, 1, 1 - for i in range(2, n + 1): - f1, f2 = f2, f3 - f3 = (f1 + f2) % 1000000007 - return f3 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" deleted file mode 100644 index 47240b4a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [剑指 Offer 11. 旋转数组的最小数字](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个数组 `numbers`,`numbers` 是有升序数组经过「旋转」得到的。但是旋转次数未知。数组中可能存在重复元素。 - -要求:找出数组中的最小元素。 - -- 旋转:将数组整体右移。 - -## 解题思路 - -数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 - -第一种的最小值在最左边。第二种最小值在第二段升序序列的第一个元素。 - -``` - * - * - * - * - * -* -``` - - - -``` - * - * -* - * - * - * -``` - -最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 - -创建两个指针 left、right,分别指向数组首尾。让后计算出两个指针中间值 mid。将 mid 与右边界进行比较。 - -1. 如果 `numbers[mid] > numbers[right]`,则最小值不可能在 `mid` 左侧,一定在 `mid` 右侧,则将 `left` 移动到 `mid + 1` 位置,继续查找右侧区间。 -2. 如果 `numbers[mid] < numbers[right]`,则最小值一定在 `mid` 左侧,将 `right` 移动到 `mid` 位置上,继续查找左侧区间。 -3. 当 `numbers[mid] == numbers[right]`,无法判断在 `mid` 的哪一侧,可以采用 `right = right - 1` 逐步缩小区域。 - -## 代码 - -```python -class Solution: - def minArray(self, numbers: List[int]) -> int: - left = 0 - right = len(numbers) - 1 - while left < right: - mid = left + (right - left) // 2 - if numbers[mid] > numbers[right]: - left = mid + 1 - elif numbers[mid] < numbers[right]: - right = mid - else: - right = right - 1 - return numbers[left] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" "b/Solutions/\345\211\221\346\214\207 Offer 12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index 36d4f565..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [剑指 Offer 12. 矩阵中的路径](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/) - -- 标签:数组、回溯、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 大小的二维字符矩阵 `board` 和一个字符串单词 `word`。如果 `word` 存在于网格中,返回 `True`,否则返回 `False`。 - -- 单词必须按照字母顺序通过上下左右相邻的单元格字母构成。且同一个单元格内的字母不允许被重复使用。 - -## 解题思路 - -回溯算法在二维矩阵 `board` 中按照上下左右四个方向递归搜索。设函数 `backtrack(i, j, index)` 表示从 `board[i][j]` 出发,能否搜索到单词字母 `word[index]`,以及 `index` 位置之后的后缀子串。如果能搜索到,则返回 `True`,否则返回 `False`。`backtrack(i, j, index)` 执行步骤如下: - -- 如果 $board[i][j] = word[index]$,而且 `index` 已经到达 `word` 字符串末尾,则返回 `True`。 -- 如果 $board[i][j] = word[index]$,而且 `index` 未到达 `word` 字符串末尾,则遍历当前位置的所有相邻位置。如果从某个相邻位置能搜索到后缀子串,则返回 `True`,否则返回 `False`。 -- 如果 $board[i][j] \ne word[index]$,则当前字符不匹配,返回 `False`。 - -## 代码 - -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - rows = len(board) - if rows == 0: - return False - cols = len(board[0]) - visited = [[False for _ in range(cols)] for _ in range(rows)] - - def backtrack(i, j, index): - if index == len(word) - 1: - return board[i][j] == word[index] - - if board[i][j] == word[index]: - visited[i][j] = True - for direct in directs: - new_i = i + direct[0] - new_j = j + direct[1] - if 0 <= new_i < rows and 0 <= new_j < cols and visited[new_i][new_j] == False: - if backtrack(new_i, new_j, index + 1): - return True - visited[i][j] = False - return False - - for i in range(rows): - for j in range(cols): - if backtrack(i, j, 0): - return True - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" "b/Solutions/\345\211\221\346\214\207 Offer 13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" deleted file mode 100644 index c71ace25..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [剑指 Offer 13. 机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) - -- 标签:深度优先搜索、广度优先搜索、动态规划 -- 难度:中等 - -## 题目大意 - -**描述**:有一个 `m * n` 大小的方格,坐标从 `(0, 0)` 到 `(m - 1, n - 1)`。一个机器人从 `(0, 0)` 处的格子开始移动,每次可以向上、下、左、右移动一格(不能移动到方格外),也不能移动到行坐标和列坐标的数位之和大于 `k` 的格子。现在给定 `3` 个整数 `m`、`n`、`k`。 - -**要求**:计算并输出该机器人能够达到多少个格子。 - -**说明**: - -- $1 \le n, m \le 100$。 -- $0 \le k \le 20$。 - -**示例**: - -- 示例 1: - -```python -输入:m = 2, n = 3, k = 1 -输出:3 -``` - -- 示例 2: - -```python -输入:m = 3, n = 1, k = 0 -输出:1 -``` - -## 解题思路 - -### 思路 1:广度优先搜索 - -先定义一个计算数位和的方法 `digitsum`,该方法输入一个整数,返回该整数各个数位的总和。 - -然后我们使用广度优先搜索方法,具体步骤如下: - -- 将 `(0, 0)` 加入队列 `queue` 中。 -- 当队列不为空时,每次将队首坐标弹出,加入访问集合 `visited` 中。 -- 再将满足行列坐标的数位和不大于 `k` 的格子位置加入到队列中,继续弹出队首位置。 -- 直到队列为空时停止。输出访问集合的长度。 - -### 思路 1:代码 - -```python -import collections - -class Solution: - def digitsum(self, n: int): - ans = 0 - while n: - ans += n % 10 - n //= 10 - return ans - - def movingCount(self, m: int, n: int, k: int) -> int: - queue = collections.deque([(0, 0)]) - visited = set() - - while queue: - x, y = queue.popleft() - if (x, y) not in visited and self.digitsum(x) + self.digitsum(y) <= k: - visited.add((x, y)) - for dx, dy in [(1, 0), (0, 1)]: - nx = x + dx - ny = y + dy - if 0 <= nx < m and 0 <= ny < n: - queue.append((nx, ny)) - return len(visited) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m \times n)$。其中 $m$ 为方格的行数,$n$ 为方格的列数。 -- **空间复杂度**:$O(m \times n)$。 - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 14- I. \345\211\252\347\273\263\345\255\220.md" "b/Solutions/\345\211\221\346\214\207 Offer 14- I. \345\211\252\347\273\263\345\255\220.md" deleted file mode 100644 index 5d7ef088..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 14- I. \345\211\252\347\273\263\345\255\220.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer 14- I. 剪绳子](https://leetcode.cn/problems/jian-sheng-zi-lcof/) - -- 标签:数学、动态规划 -- 难度:中等 - -## 题目大意 - -给定一根长度为 `n` 的绳子,将绳子剪成整数长度的 `m` 段,每段绳子长度即为 `k[0]`、`k[1]`、...、`k[m - 1]`。 - -要求:计算出 `k[0] * k[1] * ... * k[m - 1]` 可能的最大乘积。 - -## 解题思路 - -可以使用动态规划求解。 - -定义状态 `dp[i]` 为:拆分长度为 `i` 的绳子,可以获得的最大乘积为 `dp[i]`。 - -将 `j` 从 `1` 遍历到 `i - 1`,通过两种方式得到 `dp[i]`: - -- `(i - j) * j` ,直接将长度为 `i` 的绳子分割为 `i - j` 和 `j`,获取两者乘积。 -- `dp[i - j] * j`,将长度为 `i`的绳子 中的 `i - j` 部分拆分,得到 `dp[i - j]`,和 `j` ,获取乘积。 - -则 `dp[i]` 取两者中的最大值。遍历 `j`,得到 `dp[i]` 的最大值。 - -则状态转移方程为:`dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j)`。 - -最终输出 `dp[n]`。 - -## 代码 - -```python -class Solution: - def cuttingRope(self, n: int) -> int: - dp = [0 for _ in range(n + 1)] - dp[1] = 1 - for i in range(2, n + 1): - for j in range(1, i): - dp[i] = max(dp[i], dp[i - j] * j, (i - j) * j) - return dp[n] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 15. \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 15. \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index 07700acb..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 15. \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [剑指 Offer 15. 二进制中1的个数](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) - -- 标签:位运算 -- 难度:简单 - -## 题目大意 - -给定一个无符号整数 `n`。 - -要求:统计其对应二进制表达式中 `1` 的个数。 - -## 解题思路 - -### 1. 循环按位计算 - -对整数 n 的每一位进行按位与运算,并统计结果。 - -### 2. 改进位运算 - -利用 $n \text{ \& } (n-1)$ 。这个运算刚好可以将 n 的二进制中最低位的 $1$ 变为 $0$。 比如 $n = 6$ 时,$6 = (110)_2$,$6 - 1 = (101)_2$,$(110)_2 \text{ \& } (101)_2 = (100)_2$ 。 - -利用这个位运算,不断的将 $n$ 中最低位的 $1$ 变为 $0$,直到 $n$ 变为 $0$ 即可,其变换次数就是我们要求的结果。 - -## 代码 - -1. 循环按位计算 - -```python -class Solution: - def hammingWeight(self, n: int) -> int: - ans = 0 - while n: - ans += (n & 1) - n = n >> 1 - return ans -``` - -2. 改进位运算 - -```python -class Solution: - def hammingWeight(self, n: int) -> int: - ans = 0 - while n: - n &= n-1 - ans += 1 - return ans -``` - - - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" deleted file mode 100644 index df97e6f5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer 16. 数值的整数次方](https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) - -- 标签:递归、数学 -- 难度:中等 - -## 题目大意 - -给定浮点数 `x` 和整数 `n`。 - -要求:实现 `pow(x, n)`,即计算 $x^n$,不能使用库函数,不需要考虑大数问题。 - -## 解题思路 - -常规方法是直接将 x 累乘 n 次得出结果,时间复杂度为 $O(n)$。可以利用快速幂来减少时间复杂度。 - -如果 n 为偶数,$x^n = x^{n/2} * x^{n/2}$。如果 n 为奇数,$x^n = x * x^{(n-1)/2} * x^{(n-1)/2}$。 - -$x^(n/2)$ 又可以继续向下递归划分。则我们可以利用低纬度的幂计算结果,来得到高纬度的幂计算结果。 - -这样递归求解,时间复杂度为 $O(logn)$,并且递归也可以转为递推来做。 - -需要注意如果 n 为负数,可以转换为 $\frac{1}{x} ^{(-n)}$。 - -## 代码 - -```python -class Solution: - def myPow(self, x: float, n: int) -> float: - if x == 0.0: - return 0.0 - res = 1 - if n < 0: - x = 1 / x - n = -n - while n: - if n & 1: - res *= x - x *= x - n >>= 1 - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 17. \346\211\223\345\215\260\344\273\2161\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 17. \346\211\223\345\215\260\344\273\2161\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260.md" deleted file mode 100644 index 4634b991..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 17. \346\211\223\345\215\260\344\273\2161\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260.md" +++ /dev/null @@ -1,23 +0,0 @@ -# [剑指 Offer 17. 打印从1到最大的n位数](https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) - -- 标签:数组、数学 -- 难度:简单 - -## 题目大意 - -给定一个数字 `n`。 - -要求:按顺序打印从 `1` 到最大 `n` 位的十进制数。 - -## 解题思路 - -直接枚举 $1 \sim 10^{n} - 1$,生成列表并返回。 - -## 代码 - -```python -class Solution: - def printNumbers(self, n: int) -> List[int]: - return [i for i in range(1, 10 ** n)] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 18. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 18. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.md" deleted file mode 100644 index fc49ce81..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 18. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 18. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) - -- 标签:链表 -- 难度:简单 - -## 题目大意 - -给定一个链表。 - -要求:删除链表中值为 `val` 的节点,并返回新的链表头节点。 - -## 解题思路 - -用两个指针 `prev` 和 `curr`。`prev` 指向前一节点和当前节点,`curr` 指向当前节点。从前向后遍历链表,遇到值为 `val` 的节点时,将 `prev` 指向当前节点的下一个节点,继续递归遍历。遇不到则更新 `prev` 指针,并继续遍历。 - -需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 - -## 代码 - -```python -class Solution: - def deleteNode(self, head: ListNode, val: int) -> ListNode: - newHead = ListNode(0, head) - newHead.next = head - - prev, curr = newHead, head - while curr: - if curr.val == val: - prev.next = curr.next - else: - prev = curr - curr = curr.next - return newHead.next -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" "b/Solutions/\345\211\221\346\214\207 Offer 21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" deleted file mode 100644 index 233f5f0a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) - -- 标签:数组、双指针、排序 -- 难度:简单 - -## 题目大意 - -给定一个整数数组 `nums`。 - -要求:将奇数元素位于数组的前半部分,偶数元素位于数组的后半部分。 - -## 解题思路 - -定义快慢指针 `slow`、`fast`,开始时都指向 `0`。 - -- `fast` 向前搜索奇数位置,`slow` 指向下一个奇数应当存放的位置。 -- `fast` 不断进行右移,当遇到奇数时,将该奇数与 `slow` 指向的元素进行交换,并将 `slow` 进行右移。 -- 重复上面操作,直到 `fast` 指向数组末尾。 - -## 代码 - -```python -class Solution: - def exchange(self, nums: List[int]) -> List[int]: - slow, fast = 0, 0 - while fast < len(nums): - if nums[fast] % 2 == 1: - nums[slow], nums[fast] = nums[fast], nums[slow] - slow += 1 - fast += 1 - - return nums -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.md" deleted file mode 100644 index 18095d9a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [剑指 Offer 22. 链表中倒数第k个节点](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) - -- 标签:链表、双指针 -- 难度:简单 - -## 题目大意 - -给定一个链表的头节点 `head`,以及一个整数 `k`。 - -要求返回链表的倒数第 `k` 个节点。 - -## 解题思路 - -常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,返回该位置上的节点。 - -如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `k` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `k` 个节点位置。返回该该位置上的节点即可。 - -## 代码 - -```python -class Solution: - def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: - slow = head - fast = head - for _ in range(k): - if fast == None: - return fast - fast = fast.next - while fast: - slow = slow.next - fast = fast.next - return slow -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 24. \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer 24. \345\217\215\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index 2dadb2da..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 24. \345\217\215\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,80 +0,0 @@ -# [剑指 Offer 24. 反转链表](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个链表的头节点 `head`。 - -**要求**:将该链表反转并输出反转后链表的头节点。 - -## 解题思路 - -### 思路 1. 迭代 - -1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 - -2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: - 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; - 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; - 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; - 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 -3. 继续执行第 2 步中的 1、2、3、4。 -4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 - -使用迭代法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111133639.png) - -### 思路 2. 递归 - -具体做法如下: - -- 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 -- 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 -- 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 -- 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 -- 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 -- 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 -- 最后返回反转后的链表头节点 `new_head`。 - -使用递归法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111134246.png) - -## 代码 - -1. 迭代 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - pre = None - cur = head - while cur != None: - next = cur.next - cur.next = pre - pre = cur - cur = next - return pre -``` - -2. 递归 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - if head == None or head.next == None: - return head - new_head = self.reverseList(head.next) - head.next.next = head - head.next = None - return new_head -``` - -## 参考资料 - -- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) -- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer 25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" deleted file mode 100644 index ab99337b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer 25. 合并两个排序的链表](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -给定两个升序链表。 - -要求:将其合并为一个升序链表。 - -## 解题思路 - -利用归并排序的思想。 - -创建一个新的链表节点作为头节点(记得保存),然后判断 l1和 l2 头节点的值,将较小值的节点添加到新的链表中。 - -当一个节点添加到新的链表中之后,将对应的 l1 或 l2 链表向后移动一位。 - -然后继续判断当前 l1 节点和当前 l2 节点的值,继续将较小值的节点添加到新的链表中,然后将对应的链表向后移动一位。 - -这样,当 l1 或 l2 遍历到最后,最多有一个链表还有节点未遍历,则直接将该节点链接到新的链表尾部即可。 - -## 代码 - -```python -class Solution: - def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: - newHead = ListNode(-1) - - curr = newHead - while l1 and l2: - if l1.val <= l2.val: - curr.next = l1 - l1 = l1.next - else: - curr.next = l2 - l2 = l2.next - curr = curr.next - - curr.next = l1 if l1 is not None else l2 - - return newHead.next -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" "b/Solutions/\345\211\221\346\214\207 Offer 26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" deleted file mode 100644 index ce24142f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer 26. 树的子结构](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定两棵二叉树的根节点 `A`、`B`。 - -要求:判断 `B` 是不是 `A` 的子结构。(空树不是任意一棵树的子结构)。 - -- `B` 是 `A` 的子结构:`A` 中有出现和 `B` 相同的结构和节点值。 - -## 解题思路 - -深度优先搜索。 - -- 先判断特例,如果 `A`、`B` 都为空树,则直接返回 `False`。 -- 然后递归判断 `A`、`B` 是否相等。 - - 如果 `A`、`B` 相等,则返回 `True`。 - - 如果 `A`、`B` 不相等,则递归判断 `B` 是否是 `A` 的左子树的子结构,或者 `B` 是否是 `A` 的右子树的子结构,如果有一种满足,则返回 `True`,如果都不满足,则返回 `False`。 - -递归判断 `A`、`B` 是否相等的具体方法如下: - -- 如果 `B` 为空树,则直接返回 `False`,因为空树不是任意一棵树的子结构。 -- 如果 `A` 为空树或者 `A` 节点的值不等于 `B` 节点的值,则返回 `False`。 -- 如果 `A`、`B` 都不为空,且节点值相同,则递归判断 `A` 的左子树和 `B` 的左子树是否相等,判断 `A` 的右子树和 `B` 的右子树是否相等。如果都相等,则返回 `True`,否则返回 `False`。 - -## 代码 - -```python -class Solution: - def hasSubStructure(self, A: TreeNode, B: TreeNode) -> bool: - if not B: - return True - if not A or A.val != B.val: - return False - return self.hasSubStructure(A.left, B.left) and self.hasSubStructure(A.right, B.right) - - def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool: - if not A or not B: - return False - if self.hasSubStructure(A, B): - return True - return self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" "b/Solutions/\345\211\221\346\214\207 Offer 27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" deleted file mode 100644 index ec5a0f9d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [剑指 Offer 27. 二叉树的镜像](https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:将其进行左右翻转。 - -## 解题思路 - -从根节点开始遍历,然后从叶子节点向上递归交换左右子树位置。 - -## 代码 - -```python -class Solution: - def mirrorTree(self, root: TreeNode) -> TreeNode: - if not root: - return root - left = self.mirrorTree(root.left) - right = self.mirrorTree(root.right) - root.left = right - root.right = left - return root -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer 28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index f0b826ac..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer 28. 对称的二叉树](https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:检查这课二叉树是否是左右对称的。 - -## 解题思路 - -递归遍历左右子树, 然后判断当前节点的左右子节点。如果可以直接判断的情况,则跳出递归,直接返回结果。如果无法直接判断结果,则递归检测左右子树的外侧节点是否相等,同理再递归检测左右子树的内侧节点是否相等。 - -## 代码 - -```python -class Solution: - def isSymmetric(self, root: TreeNode) -> bool: - if not root: - return True - return self.check(root.left, root.right) - - def check(self, left: TreeNode, right: TreeNode): - if not left and not right: - return True - elif not left and right: - return False - elif left and not right: - return False - elif left.val != right.val: - return False - - return self.check(left.left, right.right) and self.check(left.right, right.left) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" "b/Solutions/\345\211\221\346\214\207 Offer 29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" deleted file mode 100644 index 1d4e0cd6..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [剑指 Offer 29. 顺时针打印矩阵](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) - -- 标签:数组、矩阵、模拟 -- 难度:简单 - -## 题目大意 - -给定一个 `m * n` 大小的二维矩阵 `matrix`。 - -要求:按照顺时针旋转的顺序,返回矩阵中的所有元素。 - -## 解题思路 - -按照题意进行模拟。可以实现定义一下上、下、左、右的边界,然后按照逆时针的顺序从边界上依次访问元素。 - -当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 - -## 代码 - -```python -class Solution: - def spiralOrder(self, matrix: List[List[int]]) -> List[int]: - size_m = len(matrix) - if size_m == 0: - return [] - size_n = len(matrix[0]) - if size_n == 0: - return [] - - up, down, left, right = 0, size_m - 1, 0, size_n - 1 - ans = [] - while True: - for i in range(left, right + 1): - ans.append(matrix[up][i]) - up += 1 - if up > down: - break - for i in range(up, down + 1): - ans.append(matrix[i][right]) - right -= 1 - if right < left: - break - for i in range(right, left - 1, -1): - ans.append(matrix[down][i]) - down -= 1 - if down < up: - break - for i in range(down, up - 1, -1): - ans.append(matrix[i][left]) - left += 1 - if left > right: - break - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 30. \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" "b/Solutions/\345\211\221\346\214\207 Offer 30. \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" deleted file mode 100644 index 814ee3fa..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 30. \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [剑指 Offer 30. 包含min函数的栈](https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/) - -- 标签:栈、设计 -- 难度:简单 - -## 题目大意 - -要求:设计一个「栈」,实现 `push` ,`pop` ,`top` ,`min` 操作,并且操作时间复杂度都是 `O(1)`。 - -## 解题思路 - -使用一个栈,栈元素中除了保存当前值之外,再保存一个当前最小值。 - -- `push` 操作:如果栈不为空,则判断当前值与栈顶元素所保存的最小值,并更新当前最小值,将新元素保存到栈中。 -- `pop`操作:正常出栈 -- `top` 操作:返回栈顶元素保存的值。 -- `min` 操作:返回栈顶元素保存的最小值。 - -## 代码 - -```python -class MinStack: - - def __init__(self): - """ - initialize your data structure here. - """ - self.stack = [] - - class Node: - def __init__(self, x): - self.val = x - self.min = x - - def push(self, x: int) -> None: - node = self.Node(x) - if len(self.stack) == 0: - self.stack.append(node) - else: - topNode = self.stack[-1] - if node.min > topNode.min: - node.min = topNode.min - - self.stack.append(node) - - - def pop(self) -> None: - self.stack.pop() - - - def top(self) -> int: - return self.stack[-1].val - - - def min(self) -> int: - return self.stack[-1].min -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" deleted file mode 100644 index 8d83f5e4..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [剑指 Offer 31. 栈的压入、弹出序列](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) - -- 标签:栈、数组、模拟 -- 难度:中等 - -## 题目大意 - -给定连个整数序列 `pushed` 和 `popped`,其中 `pushed` 表示栈的压入顺序。 - -要求:判断第二个序列 `popped` 是否为栈的压出序列。 - -## 解题思路 - -借助一个栈来模拟压入、压出的操作。检测最后是否能模拟成功。 - -## 代码 - -```python -class Solution: - def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: - stack = [] - index = 0 - for item in pushed: - stack.append(item) - while(stack and stack[-1] == popped[index]): - stack.pop() - index += 1 - - return len(stack) == 0 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 32 - I. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer 32 - I. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 5c2ae5c1..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 32 - I. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [剑指 Offer 32 - I. 从上到下打印二叉树](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 - -## 解题思路 - -广度优先搜索。 - -具体步骤如下: - -- 根节点入队。 -- 当队列不为空时,求出当前队列长度 $s_i$。 - - 依次从队列中取出这 $s_i$ 个元素,将其加入答案数组,并将其左右子节点入队,然后继续迭代。 -- 当队列为空时,结束。 - -## 代码 - -```python -class Solution: - def levelOrder(self, root: TreeNode) -> List[int]: - if not root: - return [] - queue = [root] - order = [] - while queue: - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - order.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - return order -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 32 - II. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 II.md" "b/Solutions/\345\211\221\346\214\207 Offer 32 - II. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 II.md" deleted file mode 100644 index 3d1109c5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 32 - II. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 II.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 - -## 解题思路 - -广度优先搜索,需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 - -具体步骤如下: - -- 根节点入队。 -- 当队列不为空时,求出当前队列长度 $s_i$。 - - 依次从队列中取出这 $s_i$ 个元素,并将其左右子节点入队,遍历完之后将这层节点数组加入答案数组中,然后继续迭代。 -- 当队列为空时,结束。 - -## 代码 - -```python -class Solution: - def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - while queue: - level = [] - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - level.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(level) - return order -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 32 - III. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 III.md" "b/Solutions/\345\211\221\346\214\207 Offer 32 - III. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 III.md" deleted file mode 100644 index 55b7a19f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 32 - III. \344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221 III.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) - -- 标签:树、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:返回其之字形层序遍历。 - -- 之字形层序遍历:从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行。 - -## 解题思路 - -广度优先搜索,在二叉树的层序遍历的基础上需要增加一些变化。 - -普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 - -新增一个变量 odd,用于判断当前层数是奇数层,还是偶数层。从而判断元素遍历方向。 - -存储每层元素的 level 列表改用双端队列,如果是奇数层,则从末尾添加元素。如果是偶数层,则从头部添加元素。 - -具体步骤如下: - -- 根节点入队。 -- 当队列不为空时,求出当前队列长度 $s_i$,并判断当前层数的奇偶性。 -- 依次从队列中取出这 $s_i$ 个元素。 - - 如果为奇数层,如果是奇数层,则从 level 末尾添加元素。 - - 如果是偶数层,则从 level头部添加元素。 -- 然后保存将其左右子节点入队,然后继续迭代。 -- 当队列为空时,结束。 - -## 代码 - -```python -import collections - -class Solution: - def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - queue = [root] - order = [] - odd = True - while queue: - level = collections.deque() - size = len(queue) - for _ in range(size): - curr = queue.pop(0) - if odd: - level.append(curr.val) - else: - level.appendleft(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if level: - order.append(list(level)) - odd = not odd - return order -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" deleted file mode 100644 index ee800db9..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer 33. 二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) - -- 标签:栈、树、二叉搜索树、递归、二叉树、单调栈 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `postorder`。数组的任意两个数字都互不相同。 - -要求:判断该数组是不是某二叉搜索树的后序遍历结果。如果是,则返回 `True`,否则返回 `False`。 - -## 解题思路 - -后序遍历的顺序为:左 -> 右 -> 根。而二叉搜索树的定义是:左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值。 - -所以,可以把数组最右侧元素作为二叉搜索树的根节点值。然后判断数组的左右两侧是否符合左侧值都小于该节点值,右侧值都大于该节点值。如果不满足,则说明不是某二叉搜索树的后序遍历结果。 - -找到左右分界线位置,然后递归左右数组继续查找。 - -终止条件为数组 开始位置 > 结束位置,此时该树的子节点数目小于等于 1,直接返回 `True` 即可。 - -## 代码 - -```python -class Solution: - def verifyPostorder(self, postorder: List[int]) -> bool: - def verify(left, right): - if left >= right: - return True - index = left - while postorder[index] < postorder[right]: - index += 1 - mid = index - while postorder[index] > postorder[right]: - index += 1 - - return index == right and verify(left, mid - 1) and verify(mid, right - 1) - if len(postorder) <= 2: - return True - return verify(0, len(postorder) - 1) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" "b/Solutions/\345\211\221\346\214\207 Offer 34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index aaae05af..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) - -- 标签:树、深度优先搜索、回溯、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root` 和一个整数 `target`。 - -要求:打印出二叉树中各节点的值的和为 `target` 的所有路径。从根节点开始往下一直到叶节点所经过的节点形成一条路径。 - -## 解题思路 - -回溯求解。在回溯的同时,记录下当前路径。同时维护 `target`,每遍历到一个节点,就减去该节点值。如果遇到叶子节点,并且 `target == 0` 时,将当前路径加入答案数组中。然后递归遍历左右子树,并回退当前节点,继续遍历。 - -## 代码 - -```python -class Solution: - def pathSum(self, root: TreeNode, target: int) -> List[List[int]]: - res = [] - path = [] - def dfs(root: TreeNode, target: int): - if not root: - return - path.append(root.val) - target -= root.val - if not root.left and not root.right and target == 0: - res.append(path[:]) - dfs(root.left, target) - dfs(root.right, target) - path.pop() - dfs(root, target) - return res - -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" "b/Solutions/\345\211\221\346\214\207 Offer 35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" deleted file mode 100644 index e55b808e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [剑指 Offer 35. 复杂链表的复制](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) - -- 标签:哈希表、链表 -- 难度:中等 - -## 题目大意 - -给定一个链表,每个节点除了 `next` 指针之后,还包含一个随机指针 `random`,该指针可以指向链表中的任何节点或者空节点。 - -要求:将该链表进行深拷贝。 - -## 解题思路 - -遍历链表,利用哈希表,以旧节点:新节点为映射关系,将节点关系存储下来。 - -再次遍历链表,将新链表的 `next` 和 `random` 指针设置好。 - -## 代码 - -```python -class Solution: - def copyRandomList(self, head: 'Node') -> 'Node': - if not head: - return None - node_dict = dict() - curr = head - while curr: - new_node = Node(curr.val, None, None) - node_dict[curr] = new_node - curr = curr.next - curr = head - while curr: - if curr.next: - node_dict[curr].next = node_dict[curr.next] - if curr.random: - node_dict[curr].random = node_dict[curr.random] - curr = curr.next - return node_dict[head] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer 36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" deleted file mode 100644 index 7c6b4aad..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer 36. 二叉搜索树与双向链表](https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/) - -- 标签:栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:将这棵二叉树转换为一个排序的循环双向链表。要求不能创建新的节点,只能调整树中节点指针的指向。 - -## 解题思路 - -通过中序递归遍历可以将二叉树升序排列输出。这道题需要在中序遍历的同时,将节点的左右指向进行改变。使用 `head`、`tail` 存放双向链表的头尾节点,然后从根节点开始,进行中序递归遍历。 - -具体做法如下: - -- 如果当前节点为空,直接返回。 -- 如果当前节点不为空: - - 递归遍历左子树。 - - 如果尾节点不为空,则将尾节点与当前节点进行连接。 - - 如果尾节点为空,则初始化头节点。 - - 将当前节点标记为尾节点。 - - 递归遍历右子树。 -- 最后将头节点和尾节点进行连接。 - -## 代码 - -```python -class Solution: - def treeToDoublyList(self, root: 'Node') -> 'Node': - def dfs(node: 'Node'): - if not node: - return - - dfs(node.left) - if self.tail: - self.tail.right = node - node.left = self.tail - else: - self.head = node - self.tail = node - dfs(node.right) - - if not root: - return None - - self.head, self.tail = None, None - dfs(root) - self.head.left = self.tail - self.tail.right = self.head - return self.head -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer 37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 170b5b5b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [剑指 Offer 37. 序列化二叉树](https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/) - -- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 -- 难度:困难 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:设计一个算法,来实现二叉树的序列化与反序列化。 - -## 解题思路 - -1. 序列化:将二叉树转为字符串数据表示 - -按照前序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 - -注意:如果遇到空节点,则标记为 'None',这样在反序列化时才能唯一确定一棵二叉树。 - -2. 反序列化:将字符串数据转为二叉树结构 - -先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 - -- 从数组左侧取出一个元素。 - - 如果当前元素为 'None',则返回 None。 - - 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 - - 最后返回当前根节点。 - -## 代码 - -```python -class Codec: - - def serialize(self, root): - """Encodes a tree to a single string. - - :type root: TreeNode - :rtype: str - """ - if not root: - return 'None' - return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) - - def deserialize(self, data): - """Decodes your encoded data to tree. - - :type data: str - :rtype: TreeNode - """ - def dfs(datalist): - val = datalist.pop(0) - if val == 'None': - return None - root = TreeNode(int(val)) - root.left = dfs(datalist) - root.right = dfs(datalist) - return root - - datalist = data.split(',') - return dfs(datalist) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" deleted file mode 100644 index cd82c4fa..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [剑指 Offer 38. 字符串的排列](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) - -- 标签:字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组,但里边不能有重复元素。 - -## 解题思路 - -因为原字符串可能含有重复元素,所以在回溯的时候需要进行去重。先将字符串 `s` 转为 `list` 列表,再对列表进行排序,然后使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 - -然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 - -然后进行回溯遍历。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - def backtrack(self, ls, visited): - if len(self.path) == len(ls): - self.res.append(''.join(self.path)) - return - for i in range(len(ls)): - if i > 0 and ls[i] == ls[i - 1] and not visited[i - 1]: - continue - - if not visited[i]: - visited[i] = True - self.path.append(ls[i]) - self.backtrack(ls, visited) - self.path.pop() - visited[i] = False - - def permutation(self, s: str) -> List[str]: - self.res.clear() - self.path.clear() - ls = list(s) - ls.sort() - visited = [False for _ in range(len(s))] - self.backtrack(ls, visited) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 9960eb2b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 39. 数组中出现次数超过一半的数字](https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) - -- 标签:数组、哈希表、分治、计数、排序 -- 难度:简单 - -## 题目大意 - -给定一个数组 `nums`,其中有一个数字出现次数超过数组长度一半。 - -要求:找到出现次数超过数组长度一半的数字。 - -## 解题思路 - -可以利用哈希表。遍历一遍数组 `nums`,用哈希表统计每个元素 `num` 出现的次数,再遍历一遍哈希表,找出元素个数最多的元素即可。 - -## 代码 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - numDict = dict() - for num in nums: - if num in numDict: - numDict[num] += 1 - else: - numDict[num] = 1 - max = 0 - max_index = -1 - for num in numDict: - if numDict[num] > max: - max = numDict[num] - max_index = num - return max_index -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 40. \346\234\200\345\260\217\347\232\204k\344\270\252\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 40. \346\234\200\345\260\217\347\232\204k\344\270\252\346\225\260.md" deleted file mode 100644 index 48240732..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 40. \346\234\200\345\260\217\347\232\204k\344\270\252\346\225\260.md" +++ /dev/null @@ -1,166 +0,0 @@ -# [剑指 Offer 40. 最小的k个数](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) - -- 标签:数组、分治、快速选择、排序、堆(优先队列) -- 难度:简单 - -## 题目大意 - -**描述**:给定整数数组 $arr$,再给定一个整数 $k$。 - -**要求**:返回数组 $arr$ 中最小的 $k$ 个数。 - -**说明**: - -- $0 \le k \le arr.length \le 10000$。 -- $0 \le arr[i] \le 10000$。 - -**示例**: - -- 示例 1: - -```python -输入:arr = [3,2,1], k = 2 -输出:[1,2] 或者 [2,1] -``` - -- 示例 2: - -```python -输入:arr = [0,1,2,1], k = 1 -输出:[0] -``` - -## 解题思路 - -直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。 - -冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 - -可考虑堆排序、归并排序、快速排序。 - -### 思路 1:堆排序(基于大顶堆) - -具体做法如下: - -1. 使用数组前 $k$ 个元素,维护一个大小为 $k$ 的大顶堆。 -2. 遍历数组 $[k, size - 1]$ 的元素,判断其与堆顶元素关系,如果遇到比堆顶元素小的元素,则将与堆顶元素进行交换。再将这 $k$ 个元素调整为大顶堆。 -3. 最后输出大顶堆的 $k$ 个元素。 - -### 思路 1:代码 - -```python -class Solution: - def heapify(self, nums: [int], index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if nums[left] > nums[max_index]: - max_index = left - if right <= end and nums[right] > nums[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(self, nums: [int], k: int): - # (k-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((k - 2) // 2, -1, -1): - self.heapify(nums, i, k - 1) - return nums - - def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: - size = len(arr) - if k <= 0 or not arr: - return [] - if size <= k: - return arr - - self.buildMaxHeap(arr, k) - - for i in range(k, size): - if arr[i] < arr[0]: - arr[i], arr[0] = arr[0], arr[i] - self.heapify(arr, 0, k - 1) - - return arr[:k] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n\log_2k)$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:快速排序 - -使用快速排序在每次调整时,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了左右两个子数组,左子数组中的元素都比该元素小,右子树组中的元素都比该元素大。 - -这样,只要某次划分的元素恰好是第 $k$ 个元素下标,就找到了数组中最小的 $k$ 个数所对应的区间,即 $[0, k - 1]$。 并且我们只需关注第 $k$ 个最小元素所在区间的排序情况,与第 $k$ 个最小元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 - -### 思路 2:代码 - -```python -import random - -class Solution: - # 从 arr[low: high + 1] 中随机挑选一个基准数,并进行移动排序 - def randomPartition(self, arr: [int], low: int, high: int): - # 随机挑选一个基准数 - i = random.randint(low, high) - # 将基准数与最低位互换 - arr[i], arr[low] = arr[low], arr[i] - # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 - return self.partition(arr, low, high) - - # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 - def partition(self, arr: [int], low: int, high: int): - pivot = arr[low] # 以第 1 为为基准数 - i = low + 1 # 从基准数后 1 位开始遍历,保证位置 i 之前的元素都小于基准数 - - for j in range(i, high + 1): - # 发现一个小于基准数的元素 - if arr[j] < pivot: - # 将小于基准数的元素 arr[j] 与当前 arr[i] 进行换位,保证位置 i 之前的元素都小于基准数 - arr[i], arr[j] = arr[j], arr[i] - # i 之前的元素都小于基准数,所以 i 向右移动一位 - i += 1 - # 将基准节点放到正确位置上 - arr[i - 1], arr[low] = arr[low], arr[i - 1] - # 返回基准数位置 - return i - 1 - - def quickSort(self, arr, low, high, k): - size = len(arr) - if low < high: - # 按照基准数的位置,将序列划分为左右两个子序列 - pi = self.randomPartition(arr, low, high) - if pi == k: - return arr[:k] - if pi > k: - # 对左子序列进行递归快速排序 - self.quickSort(arr, low, pi - 1, k) - if pi < k: - # 对右子序列进行递归快速排序 - self.quickSort(arr, pi + 1, high, k) - - return arr[:k] - - def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: - size = len(arr) - if k >= size: - return arr - return self.quickSort(arr, 0, size - 1, k) -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n)$。证明过程可参考「算法导论 9.2:期望为线性的选择算法」。 -- **空间复杂度**:$O(\log n)$。递归使用栈空间的空间代价期望为 $O(\log n)$。 - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 41. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 41. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" deleted file mode 100644 index 1d1c8b39..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 41. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [剑指 Offer 41. 数据流中的中位数](https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) - -- 标签:设计、双指针、数据流、排序、堆(优先队列) -- 难度:困难 - -## 题目大意 - -要求:设计一个支持一下两种操作的数组结构: - -- `void addNum(int num)`:从数据流中添加一个整数到数据结构中。 -- `double findMedian()`:返回目前所有元素的中位数。 - -## 解题思路 - -使用一个大顶堆 `queMax` 记录大于中位数的数,使用一个小顶堆 `queMin` 小于中位数的数。 - -- 当添加元素数量为偶数: `queMin` 和 `queMax` 中元素数量相同,则中位数为它们队头的平均值。 -- 当添加元素数量为奇数:`queMin` 中的数比 `queMax` 多一个,此时中位数为 `queMin` 的队头。 - -为了满足上述条件,在进行 `addNum` 操作时,我们应当分情况处理: - -- `num > max{queMin}`:此时 `num` 大于中位数,将该数添加到大顶堆 `queMax` 中。新的中位数将大于原来的中位数,所以可能需要将 `queMax` 中的最小数移动到 `queMin` 中。 -- `num ≤ max{queMin}`:此时 `num` 小于中位数,将该数添加到小顶堆 `queMin` 中。新的中位数将小于等于原来的中位数,所以可能需要将 `queMin` 中最大数移动到 `queMax` 中。 - -## 代码 - -```python -import heapq - -class MedianFinder: - - def __init__(self): - """ - initialize your data structure here. - """ - self.queMin = list() - self.queMax = list() - - - def addNum(self, num: int) -> None: - if not self.queMin or num < -self.queMin[0]: - heapq.heappush(self.queMin, -num) - if len(self.queMax) + 1 < len(self.queMin): - heapq.heappush(self.queMax, -heapq.heappop(self.queMin)) - else: - heapq.heappush(self.queMax, num) - if len(self.queMax) > len(self.queMin): - heapq.heappush(self.queMin, -heapq.heappop(self.queMax)) - - - def findMedian(self) -> float: - if len(self.queMin) > len(self.queMax): - return -self.queMin[0] - return (-self.queMin[0] + self.queMax[0]) / 2 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer 42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" deleted file mode 100644 index 10f1a8f9..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 42. 连续子数组的最大和](https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) - -- 标签:数组、分治、动态规划 -- 难度:简单 - -## 题目大意 - -给定一个整数数组 `nums` 。 - -要求:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和,要求时间复杂度为 `O(n)`。 - -## 解题思路 - -动态规划的方法,关键点是要找到状态转移方程。 - -假设 f(i) 表示第 i 个数结尾的「连续子数组的最大和」,那么 $max_{0 < i \le n-1} {f(i)} = max(f(i-1) + nums[i], nums[i])$ - -即将之前累加和加上当前值与当前值做比较。 - -- 如果将之前累加和加上当前值 > 当前值,那么加上当前值。 -- 如果将之前累加和加上当前值 < 当前值,那么 $f(i) = nums[i]$。 - -## 代码 - -```python -class Solution: - def maxSubArray(self, nums: List[int]) -> int: - max_ans = nums[0] - ans = 0 - for num in nums: - ans = max(ans + num, num) - max_ans = max(max_ans, ans) - return max_ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\346\237\220\344\270\200\344\275\215\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\346\237\220\344\270\200\344\275\215\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index bece89ad..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\346\237\220\344\270\200\344\275\215\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer 44. 数字序列中某一位的数字](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) - -- 标签:数学、二分查找 -- 难度:中等 - -## 题目大意 - -数字以 `0123456789101112131415…` 的格式序列化到一个字符序列中。在这个序列中,第 `5` 位(从下标 `0` 开始计数)是 `5`,第 `13` 位是 `1`,第 `19` 位是 `4`,等等。 - -要求:返回任意第 `n` 位对应的数字。 - -## 解题思路 - -根据题意中的字符串,找数学规律: - -- `123456789`:是 `9` 个 `1` 位数字。 -- `10111213...9899`:是 `90` 个 `2` 位数字。 -- `100...999`:是 `900` 个 `3` 位数字。 -- `1000...9999` 是 `9000` 个 `4` 位数字。 - -- 我们可以先找到对应的数字对应的位数 `digits`。 -- 然后找到该位数 `digits` 的起始数字 `start`。 -- 再计算出 `n` 所在的数字 `number`。`number` 等于从起始数字 `start` 开始的第 $\lfloor(n - 1) / digits\rfloor$ 个数字。即 `number = start + (n - 1) // digits`。 -- 然后确定 `n` 对应的是数字 `number` 中的哪一位。即 `idx = (n - 1) % digits`。 -- 最后返回结果。 - -## 代码 - -```python -class Solution: - def findNthDigit(self, n: int) -> int: - digits = 1 - start = 1 - base = 9 - while n > base: - n -= base - digits += 1 - start *= 10 - base = start * digits * 9 - - number = start + (n - 1) // digits - idx = (n - 1) % digits - return int(str(number)[idx]) -``` - -## 参考资料 - -- 【题解】[面试题44. 数字序列中某一位的数字(迭代 + 求整 / 求余,清晰图解) - 数字序列中某一位的数字 - 力扣](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/mian-shi-ti-44-shu-zi-xu-lie-zhong-mou-yi-wei-de-6/) diff --git "a/Solutions/\345\211\221\346\214\207 Offer 45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" deleted file mode 100644 index 3271a250..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" +++ /dev/null @@ -1,72 +0,0 @@ -# [剑指 Offer 45. 把数组排成最小的数](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) - -- 标签:贪心、字符串、排序 -- 难度:中等 - -## 题目大意 - -**描述**:给定一个非负整数数组 $nums$。 - -**要求**:将数组中的数字拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个。 - -**说明**: - -- $0 < nums.length \le 100$。 -- 输出结果可能非常大,所以你需要返回一个字符串而不是整数。 -- 拼接起来的数字可能会有前导 $0$,最后结果不需要去掉前导 $0$。 - -**示例**: - -- 示例 1: - -```python -输入: [10,2] -输出: "102" -``` - -- 示例 2: - -```python -输入:[3,30,34,5,9] -输出:"3033459" -``` - -## 解题思路 - -### 思路 1:自定义排序 - -本质上是给数组进行排序。假设 $x$、$y$ 是数组 $nums$ 中的两个元素。则排序的判断规则如下所示: - -- 如果拼接字符串 $x + y > y + x$,则 $x$ 大于 $y$,$y$ 应该排在 $x$ 前面,从而使拼接起来的数字尽可能的小。 -- 反之,如果拼接字符串 $x + y < y + x$,则 $x$ 小于 $y$,$x$ 应该排在 $y$ 前面,从而使拼接起来的数字尽可能的小。 - -按照上述规则,对原数组进行排序。这里使用了 `functools.cmp_to_key` 自定义排序函数。 - -### 思路 1:自定义排序代码 - -```python -import functools - -class Solution: - def minNumber(self, nums: List[int]) -> str: - def cmp(a, b): - if a + b == b + a: - return 0 - elif a + b > b + a: - return 1 - else: - return -1 - - nums_s = list(map(str, nums)) - nums_s.sort(key=functools.cmp_to_key(cmp)) - return ''.join(nums_s) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。排序算法的时间复杂度为 $O(n \times \log n)$。 -- **空间复杂度**:$O(1)$。 - -## 参考资料 - -- 【题解】[剑指 Offer 45. 把数组排成最小的数(自定义排序,清晰图解) - 把数组排成最小的数 - 力扣](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45-ba-shu-zu-pai-cheng-zui-xiao-de-s-4/) diff --git "a/Solutions/\345\211\221\346\214\207 Offer 46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer 46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index b1591d6f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer 46. 把数字翻译成字符串](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个数字 `num`,按照如下规则将其翻译为字符串:`0` 翻译为 `a`,`1` 翻译为 `b`,…,`11` 翻译为 `l`,…,`25` 翻译为 `z`。 - -要求:计算出共有多少种可能的翻译方案。 - -## 解题思路 - -可用动态规划来做。 - -将数字 `nums` 转为字符串 `s`。设 `dp[i]` 表示字符串 `s` 前 `i` 个数字 `s[0: i]` 的翻译方案数。`dp[i]` 的来源有两种情况: - -1. 第 `i - 1`、`i - 2` 构成的数字在 `[10, 25]`之间,则 `dp[i]` 来源于: `s[i - 1]` 单独翻译的方案数(即 `dp[i - 1]`) + `s[i - 2]` 和 `s[i - 1]` 连起来进行翻译的方案数(即 `dp[i - 2]`)。 -2. 第 `i - 1`、`i - 2` 构成的数字在 `[10, 25]`之外,则 `dp[i]` 来源于:`s[i]` 单独翻译的方案数。 - -## 代码 - -```python -class Solution: - def translateNum(self, num: int) -> int: - s = str(num) - size = len(s) - dp = [0 for _ in range(size + 1)] - dp[0] = 1 - dp[1] = 1 - for i in range(2, size + 1): - temp = int(s[i-2:i]) - if temp >= 10 and temp <= 25: - dp[i] = dp[i - 1] + dp[i - 2] - else: - dp[i] = dp[i - 1] - return dp[size] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" deleted file mode 100644 index d8c7f6c8..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 47. 礼物的最大价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/) - -- 标签:数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 大小的二维矩阵 `grid` 代表棋盘,棋盘的每一格都放有一个礼物,每个礼物有一定的价值(价值大于 `0`)。`grid[i][j]` 表示棋盘第 `i` 行第 `j` 列的礼物价值。我们可以从左上角的格子开始拿礼物,每次只能向右或者向下移动一格,直到到达棋盘的右下角。 - -要求:计算出最多能拿多少价值的礼物。 - -## 解题思路 - -可以用动态规划求解,设 `dp[i][j]` 是从 `(0, 0)` 到 `(i - 1, j - 1)` 能得礼物的最大价值。 - -显然 `dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1]) + grid[i][j]`。 - -因为是自上而下递推 `dp[i-1][j]` 可以用 `dp[j]` 来表示,所以也可以将二维改为一位。状态转移公式为: `dp[j] = max(dp[j], dp[j - 1]) + grid[i][j]`。 - -## 代码 - -```python -class Solution: - def maxValue(self, grid: List[List[int]]) -> int: - if not grid: - return 0 - size_m = len(grid) - size_n = len(grid[0]) - dp = [0 for _ in range(size_n + 1)] - for i in range(size_m): - for j in range(size_n): - dp[j + 1] = max(dp[j], dp[j + 1]) + grid[i][j] - return dp[size_n] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer 48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 1026fc25..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [剑指 Offer 48. 最长不含重复字符的子字符串](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:找出其中不含有重复字符的最长子串的长度。 - -## 解题思路 - -利用集合来存储不重复的字符。用两个指针分别指向最长子串的左右节点。遍历字符串,右指针不断右移,利用集合来判断有没有重复的字符,如果没有,就持续向右扩大右边界。如果出现重复字符,就缩小左侧边界。每次移动终止,都要计算一下当前不含重复字符的子串长度,并判断一下是否需要更新最大长度。 - -## 代码 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - if not s: - return 0 - - letterSet = set() - right = 0 - ans = 0 - for i in range(len(s)): - if i != 0: - letterSet.remove(s[i - 1]) - while right < len(s) and s[right] not in letterSet: - letterSet.add(s[right]) - right += 1 - ans = max(ans, right - i) - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 49. \344\270\221\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 49. \344\270\221\346\225\260.md" deleted file mode 100644 index 50d736b3..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 49. \344\270\221\346\225\260.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer 49. 丑数](https://leetcode.cn/problems/chou-shu-lcof/) - -- 标签:哈希表、数学、动态规划、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:找出并返回第 `n` 个丑数。 - -- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 - -## 解题思路 - -动态规划求解。 - -定义状态 `dp[i]` 表示第 `i` 个丑数。 - -状态转移方程为:`dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)` ,其中 `p2`、`p3`、`p5` 分别表示当前 `i` 中 `2`、`3`、`5` 的质因子数量。 - -## 代码 - -```python -class Solution: - def nthUglyNumber(self, n: int) -> int: - dp = [1 for _ in range(n)] - p2, p3, p5 = 0, 0, 0 - for i in range(1, n): - dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5) - if dp[i] == dp[p2] * 2: - p2 += 1 - if dp[i] == dp[p3] * 3: - p3 += 1 - if dp[i] == dp[p5] * 5: - p5 += 1 - return dp[n - 1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" "b/Solutions/\345\211\221\346\214\207 Offer 50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" deleted file mode 100644 index 25c0698f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer 50. 第一个只出现一次的字符](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) - -- 标签:队列、哈希表、字符串、计数 -- 难度:简单 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:从字符串 `s` 中找到第一个只出现一次的字符。如果没有,则返回空格 ` `。 - -## 解题思路 - -遍历字符串 `s`,使用哈希表存储每个字符频数。 - -再次遍历字符串 `s`,返回第一个频数为 `1` 的字符。 - -## 代码 - -```python -class Solution: - def firstUniqChar(self, s: str) -> str: - dic = dict() - for ch in s: - if ch in dic: - dic[ch] += 1 - else: - dic[ch] = 1 - - for ch in s: - if ch in dic and dic[ch] == 1: - return ch - return ' ' -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" deleted file mode 100644 index ee77dfe8..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" +++ /dev/null @@ -1,158 +0,0 @@ -# [剑指 Offer 51. 数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) - -- 标签:树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个数组 $nums$。 - -**要求**:计算出数组中的逆序对的总数。 - -**说明**: - -- **逆序对**:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 -- $0 \le nums.length \le 50000$。 - -**示例**: - -- 示例 1: - -```python -输入: [7,5,6,4] -输出: 5 -``` - -## 解题思路 - -### 思路 1:归并排序 - -归并排序主要分为:「分解过程」和「合并过程」。其中「合并过程」实质上是两个有序数组的合并过程。 - -![](https://qcdn.itcharge.cn/images/20220414204405.png) - -每当遇到 左子数组当前元素 > 右子树组当前元素时,意味着「左子数组从当前元素开始,一直到左子数组末尾元素」与「右子树组当前元素」构成了若干个逆序对。 - -比如上图中的左子数组 $[0, 3, 5, 7]$ 与右子树组 $[1, 4, 6, 8]$,遇到左子数组中元素 $3$ 大于右子树组中元素 $1$。则左子数组从 $3$ 开始,经过 $5$ 一直到 $7$,与右子数组当前元素 $1$ 都构成了逆序对。即 $[3, 1]$、$[5, 1]$、$[7, 1]$ 都构成了逆序对。 - -因此,我们可以在合并两个有序数组的时候计算逆序对。具体做法如下: - -1. 使用全局变量 $cnt$ 来存储逆序对的个数。然后进行归并排序。 -2. **分割过程**:先递归地将当前序列平均分成两半,直到子序列长度为 $1$。 - 1. 找到序列中心位置 $mid$,从中心位置将序列分成左右两个子序列 $left\underline{}arr$、$right\underline{}arr$。 - 2. 对左右两个子序列 $left\underline{}arr$、$right\underline{}arr$ 分别进行递归分割。 - 3. 最终将数组分割为 $n$ 个长度均为 $1$ 的有序子序列。 -3. **归并过程**:从长度为 $1$ 的有序子序列开始,依次进行两两归并,直到合并成一个长度为 $n$ 的有序序列。 - 1. 使用数组变量 $arr$ 存放归并后的有序数组。 - 2. 使用两个指针 $left\underline{}i$、$right\underline{}i$ 分别指向两个有序子序列 $left\underline{}arr$、$right\underline{}arr$ 的开始位置。 - 3. 比较两个指针指向的元素: - 1. 如果 $left\underline{}arr[left\underline{}i] \le right\underline{}arr[right\underline{}i]$,则将 $left\underline{}arr[left\underline{}i]$ 存入到结果数组 $arr$ 中,并将指针移动到下一位置。 - 2. 如果 $left\underline{}arr[left\underline{}i] > right\underline{}arr[right\underline{}i]$,则 **记录当前左子序列中元素与当前右子序列元素所形成的逆序对的个数,并累加到 $cnt$ 中,即 `self.cnt += len(left_arr) - left_i`**,然后将 $right\underline{}arr[right\underline{}i]$ 存入到结果数组 $arr$ 中,并将指针移动到下一位置。 - 4. 重复步骤 $3$,直到某一指针到达子序列末尾。 - 5. 将另一个子序列中的剩余元素存入到结果数组 $arr$ 中。 - 6. 返回归并后的有序数组 $arr$。 -4. 返回数组中的逆序对的总数,即 $self.cnt$。 - -### 思路 1:代码 - -```python -class Solution: - cnt = 0 - def merge(self, left_arr, right_arr): # 归并过程 - arr = [] - left_i, right_i = 0, 0 - while left_i < len(left_arr) and right_i < len(right_arr): - # 将两个有序子序列中较小元素依次插入到结果数组中 - if left_arr[left_i] <= right_arr[right_i]: - arr.append(left_arr[left_i]) - left_i += 1 - else: - self.cnt += len(left_arr) - left_i - arr.append(right_arr[right_i]) - right_i += 1 - - while left_i < len(left_arr): - # 如果左子序列有剩余元素,则将其插入到结果数组中 - arr.append(left_arr[left_i]) - left_i += 1 - - while right_i < len(right_arr): - # 如果右子序列有剩余元素,则将其插入到结果数组中 - arr.append(right_arr[right_i]) - right_i += 1 - - return arr # 返回排好序的结果数组 - - def mergeSort(self, arr): # 分割过程 - if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 - return arr - - mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 - left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序 - right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序 - return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 - - def reversePairs(self, nums: List[int]) -> int: - self.cnt = 0 - self.mergeSort(nums) - return self.cnt -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - -### 思路 2:树状数组 - -数组 $tree[i]$ 表示数字 $i$ 是否在序列中出现过,如果数字 $i$ 已经存在于序列中,$tree[i] = 1$,否则 $tree[i] = 0$。 - -1. 按序列从左到右将值为 $nums[i]$ 的元素当作下标为$nums[i]$,赋值为 $1$ 插入树状数组里,这时,比 $nums[i]$ 大的数个数就是 $i + 1 - query(a)$。 -2. 将全部结果累加起来就是逆序数了。 - -### 思路 2:代码 - -```python -import bisect - -class BinaryIndexTree: - - def __init__(self, n): - self.size = n - self.tree = [0 for _ in range(n + 1)] - - def lowbit(self, index): - return index & (-index) - - def update(self, index, delta): - while index <= self.size: - self.tree[index] += delta - index += self.lowbit(index) - - def query(self, index): - res = 0 - while index > 0: - res += self.tree[index] - index -= self.lowbit(index) - return res - -class Solution: - def reversePairs(self, nums: List[int]) -> int: - size = len(nums) - sort_nums = sorted(nums) - for i in range(size): - nums[i] = bisect.bisect_left(sort_nums, nums[i]) + 1 - - bit = BinaryIndexTree(size) - ans = 0 - for i in range(size): - bit.update(nums[i], 1) - ans += (i + 1 - bit.query(nums[i])) - return ans -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(n \times \log n)$。 -- **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\350\212\202\347\202\271.md" deleted file mode 100644 index cf02094c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\350\212\202\347\202\271.md" +++ /dev/null @@ -1,44 +0,0 @@ -# [剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) - -- 标签:哈希表、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定 A、B 两个链表,判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 None。 - -比如:链表 A 为 [4, 1, 8, 4, 5],链表 B 为 [5, 0, 1, 8, 4, 5]。则如下图所示,两个链表相交的起始节点为 8,则输出结果为 8。 - -![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) - -## 解题思路 - -如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 A 的长度为 m、链表 B 的长度为 n,他们的相交序列有 k 个,则相交情况可以如下如所示: - -![](https://qcdn.itcharge.cn/images/20210401113538.png) - -现在问题是如何找到 m-k 或者 n-k 的位置。 - -考虑将链表 A 的末尾拼接上链表 B,链表 B 的末尾拼接上链表 A。 - -然后使用两个指针 pA 、PB,分别从链表 A、链表 B 的头节点开始遍历,如果走到共同的节点,则返回该节点。 - -否则走到两个链表末尾,返回 None。 - -![](https://qcdn.itcharge.cn/images/20210401114100.png) - -## 代码 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: - if not headA or not headB: - return None - pA = headA - pB = headB - while pA != pB: - pA = pA.next if pA else headB - pB = pB.next if pB else headA - return pA -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 53 - I. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227 I.md" "b/Solutions/\345\211\221\346\214\207 Offer 53 - I. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227 I.md" deleted file mode 100644 index 0bd81eba..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 53 - I. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227 I.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个排序数组 `nums`,以及一个整数 `target`。 - -要求:统计 `target` 在排序数组 `nums` 中出现的次数。 - -## 解题思路 - -两次二分查找。 - -- 先查找 `target` 第一次出现的位置(下标):`left`。 -- 再查找 `target` 最后一次出现的位置(下标):`right`。 -- 最终答案为 `right - left + 1`。 - -## 代码 - -```python -class Solution: - def searchLeft(self, nums, target): - left, right = 0, len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - if nums[mid] < target: - left = mid + 1 - else: - right = mid - if nums[left] == target: - return left - else: - return -1 - - def searchRight(self, nums, target): - left, right = 0, len(nums) - 1 - while left < right: - mid = left + (right - left + 1) // 2 - if nums[mid] <= target: - left = mid - else: - right = mid - 1 - return left - - def search(self, nums: List[int], target: int) -> int: - if len(nums) == 0: - return 0 - left = self.searchLeft(nums, target) - right = self.searchRight(nums, target) - - if left == -1: - return 0 - - return right - left + 1 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 53 - II. 0\357\275\236n-1\344\270\255\347\274\272\345\244\261\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 53 - II. 0\357\275\236n-1\344\270\255\347\274\272\345\244\261\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index ce4f6aeb..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 53 - II. 0\357\275\236n-1\344\270\255\347\274\272\345\244\261\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer 53 - II. 0~n-1中缺失的数字](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) - -- 标签:位运算、数组、哈希表、数学、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个 `n - 1` 个数的升序数组,数组中元素值都在 `0 ~ n - 1` 之间。 `nums` 中有且只有一个数字不在该数组中。 - -要求:找出这个缺失的数字。 - -## 解题思路 - -可以用二分查找解决。 - -对于中间值,判断元素值与索引值是否一致,如果一致,则说明缺失数字在索引的右侧。如果不一致,则可能为当前索引或者索引的左侧。 - -## 代码 - -```python -class Solution: - def missingNumber(self, nums: List[int]) -> int: - if len(nums) == 0: - return 0 - left, right = 0, len(nums) - 1 - while left < right: - mid = left + (right - left) // 2 - if mid == nums[mid]: - left = mid + 1 - else: - right = mid - if left == nums[left]: - return left + 1 - else: - return left -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 54. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\345\244\247\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer 54. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\345\244\247\350\212\202\347\202\271.md" deleted file mode 100644 index 630a97c6..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 54. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\345\244\247\350\212\202\347\202\271.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer 54. 二叉搜索树的第k大节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root`,以及一个整数 `k`。 - -要求:找出二叉搜索树书第 `k` 大的节点。 - -## 解题思路 - -已知中序遍历「左 -> 根 -> 右」能得到递增序列。逆中序遍历「右 -> 根 -> 左」可以得到递减序列。 - -则根据「右 -> 根 -> 左」递归遍历 k 次,找到第 `k` 个节点位置,并记录答案。 - -## 代码 - -```python -class Solution: - res = 0 - k = 0 - def dfs(self, root): - if not root: - return - self.dfs(root.right) - if self.k == 0: - return - self.k -= 1 - if self.k == 0: - self.res = root.val - return - self.dfs(root.left) - - def kthLargest(self, root: TreeNode, k: int) -> int: - self.res = 0 - self.k = k - self.dfs(root) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 55 - I. \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" "b/Solutions/\345\211\221\346\214\207 Offer 55 - I. \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" deleted file mode 100644 index 6f5bc72e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 55 - I. \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" +++ /dev/null @@ -1,30 +0,0 @@ -# [剑指 Offer 55 - I. 二叉树的深度](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:找出树的深度。 - -- 深度:从根节点到叶节点一次经过的节点形成一条路径,最长路径的长度为树的深度。 - -## 解题思路 - -递归遍历,先递归遍历左右子树,返回左右子树的高度,则当前节点的高度为左右子树最大深度 + 1。即 `max(left_height, right_height) + 1`。 - -## 代码 - -```python -class Solution: - def maxDepth(self, root: TreeNode) -> int: - if root == None: - return 0 - - left_height = self.maxDepth(root.left) - right_height = self.maxDepth(root.right) - return max(left_height, right_height) + 1 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 55 - II. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer 55 - II. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 7342d8c9..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 55 - II. \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer 55 - II. 平衡二叉树](https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:判断该树是不是平衡二叉树。如果是平衡二叉树,返回 `True`,否则,返回 `False`。 - -- 平衡二叉树:任意节点的左右子树深度不超过 `1`。 - -## 解题思路 - -递归遍历二叉树。先递归遍历左右子树,判断左右子树是否平衡,再判断以当前节点为根节点的左右子树是否平衡。 - -如果遍历的子树是平衡的,则返回它的高度,否则返回 `-1`。 - -只要出现不平衡的子树,则该二叉树一定不是平衡二叉树。 - -## 代码 - -```python -class Solution: - def isBalanced(self, root: TreeNode) -> bool: - def height(root: TreeNode) -> int: - if root == None: - return False - leftHeight = height(root.left) - rightHeight = height(root.right) - if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1: - return -1 - else: - return max(leftHeight, rightHeight) + 1 - - return height(root) >= 0 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 56 - I. \346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 56 - I. \346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" deleted file mode 100644 index b099901a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 56 - I. \346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer 56 - I. 数组中数字出现的次数](https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) - -- 标签:位运算、数组 -- 难度:中等 - -## 题目大意 - -给定一个整型数组 `nums` 。`nums` 里除两个数字之外,其他数字都出现了两次。 - -要求:找出这两个只出现一次的数字。要求时间复杂度是 $O(n)$,空间复杂度是 $O(1)$。 - -## 解题思路 - -- 求解这道题之前,我们先来看看如何求解「一个数组中除了某个元素只出现一次以外,其余每个元素均出现两次。」即「[136. 只出现一次的数字](https://leetcode.cn/problems/single-number/)」问题。我们可以对所有数不断进行异或操作,最终可得到单次出现的元素。 - -- 如果数组中有两个数字只出现一次,其余每个元素均出现两次。那么经过全部异或运算。我们可以得到只出现一次的两个数字的异或结果。 -- 根据异或结果的性质,异或运算中如果某一位上为 `1`,则说明异或的两个数在该位上是不同的。根据这个性质,我们将数字分为两组:一组是和该位为 `0` 的数字,另一组是该位为 `1` 的数字。然后将这两组分别进行异或运算,就可以得到最终要求的两个数字。 - -## 代码 - -```python -class Solution: - def singleNumbers(self, nums: List[int]) -> List[int]: - all_xor = 0 - for num in nums: - all_xor ^= num - # 获取所有异或中最低位的 1 - mask = 1 - while all_xor & mask == 0: - mask <<= 1 - - a_xor, b_xor = 0, 0 - for num in nums: - if num & mask == 0: - a_xor ^= num - else: - b_xor ^= num - - return a_xor, b_xor -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 57 - II. \345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 57 - II. \345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" deleted file mode 100644 index 8571b729..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 57 - II. \345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" +++ /dev/null @@ -1,112 +0,0 @@ -# [剑指 Offer 57 - II. 和为s的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) - -- 标签:数学、双指针、枚举 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个正整数 `target`。 - -**要求**:输出所有和为 `target` 的连续正整数序列(至少含有两个数)。序列中的数字由小到大排列,不同序列按照首个数字从小到大排列。 - -**说明**: - -- $1 \le target \le 10^5$。 - -**示例**: - -- 示例 1: - -```python -输入:target = 9 -输出:[[2,3,4],[4,5]] -``` - -- 示例 2: - -```python -输入:target = 15 -输出:[[1,2,3,4,5],[4,5,6],[7,8]] -``` - -## 解题思路 - -### 思路 1:枚举算法 - -连续正整数序列中元素的最小值大于等于 `1`,而最大值不会超过 `target`。所以我们可以枚举可行的区间,并计算出区间和,将其与 `target` 进行比较,如果相等则将对应的区间元素加入答案数组中,最终返回答案数组。 - -因为题目要求至少含有两个数,则序列开始元素不会超过 `target` 的一半,所以序列开始元素可以从 `1` 开始,枚举到 `target // 2` 即可。 - -具体步骤如下: - -1. 使用列表变量 `res` 作为答案数组。 -2. 使用一重循环 `i`,用于枚举序列开始位置,枚举范围为 `[1, target // 2]`。 -3. 使用变量 `cur_sum` 维护当前区间的区间和,`cur_sum` 初始为 `0`。 -4. 使用第 `2` 重循环 `j`,用于枚举序列的结束位置,枚举范围为 `[i, target - 1]`,并累积计算当前区间的区间和,即 `cur_sum += j`。 - 1. 如果当前区间的区间和大于 `target`,则跳出循环。 - 2. 如果当前区间的区间和等于 `target`,则将区间上的元素保存为列表,并添加到答案数组中,然后跳出第 `2` 重循环。 -5. 遍历完返回答案数组。 - -### 思路 1:代码 - -```python -class Solution: - def findContinuousSequence(self, target: int) -> List[List[int]]: - res = [] - for i in range(1, target // 2 + 1): - cur_sum = 0 - for j in range(i, target): - cur_sum += j - if cur_sum > target: - break - if cur_sum == target: - cur_res = [] - for k in range(i, j + 1): - cur_res.append(k) - res.append(cur_res) - break - return res -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$target \times \sqrt{target}$。 -- **空间复杂度**:$O(1)$。 - -### 思路 2:滑动窗口 - -具体做法如下: - -- 初始化窗口,令 `left = 1`,`right = 2`。 -- 计算 `sum = (left + right) * (right - left + 1) // 2`。 -- 如果 `sum == target`,时,将其加入答案数组中。 -- 如果 `sum < target` 时,说明需要扩大窗口,则 `right += 1`。 -- 如果 `sum > target` 时,说明需要缩小窗口,则 `left += 1`。 -- 直到 `left >= right` 时停止,返回答案数组。 - -### 思路 2:滑动窗口代码 - -```python -class Solution: - def findContinuousSequence(self, target: int) -> List[List[int]]: - left, right = 1, 2 - res = [] - while left < right: - sum = (left + right) * (right - left + 1) // 2 - if sum == target: - arr = [] - for i in range(0, right - left + 1): - arr.append(i + left) - res.append(arr) - left += 1 - elif sum < target: - right += 1 - else: - left += 1 - return res -``` - -### 思路 2:复杂度分析 - -- **时间复杂度**:$O(target)$。 -- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/\345\211\221\346\214\207 Offer 57. \345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 57. \345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" deleted file mode 100644 index 6ff5eebf..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 57. \345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer 57. 和为s的两个数字](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) - -- 标签:数组、双指针、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个升序数组 `nums`,以及一个目标整数 `target`。 - -要求:在数组中查找两个数,使它们的和刚好等于 `target`。 - -## 解题思路 - -因为数组是升序的,可以使用双指针。`left`、`right` 分别指向数组首尾位置。 - -- 计算 `sum = nums[left] + nums[right]`。 -- 如果 `sum > target`,则 `right` 进行左移。 -- 如果 `sum < target`,则 `left` 进行右移。 -- 如果 `sum == target`,则返回 `[nums[left], nums[right]]`。 - -## 代码 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - left, right = 0, len(nums) - 1 - while left < right: - sum = nums[left] + nums[right] - if sum > target: - right -= 1 - elif sum < target: - left += 1 - else: - return nums[left], nums[right] - return [] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 58 - I. \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.md" "b/Solutions/\345\211\221\346\214\207 Offer 58 - I. \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.md" deleted file mode 100644 index 06a4075f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 58 - I. \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [剑指 Offer 58 - I. 翻转单词顺序](https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:逐个翻转字符串中所有的单词。 - -说明: - -- 数组字符串 `s` 可以再前面、后面或者单词间包含多余的空格。 -- 翻转后的单词应当只有一个空格分隔。 -- 翻转后的字符串不应该包含额外的空格。 - -## 解题思路 - -最简单的就是调用 API 进行切片,翻转。复杂一点的也可以根据 API 的思路写出模拟代码。 - -## 代码 - -```python -class Solution: - def reverseWords(self, s: str) -> str: - return " ".join(reversed(s.split())) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 58 - II. \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer 58 - II. \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index a1e45898..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 58 - II. \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [剑指 Offer 58 - II. 左旋转字符串](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) - -- 标签:数学、双指针、字符串 -- 难度:简单 - -## 题目大意 - -给定一个字符串 `s` 和一个整数 `n`。 - -要求:将字符串 `s` 每个字符向左旋转 `n` 位。 - -- 左旋转:将字符串前面的若干字符转移到字符串的尾部。 - -## 解题思路 - -- 使用数组 `res` 存放答案。 -- 先遍历 `[n, len(s) - 1]` 范围的字符,将其存入数组。 -- 再遍历 `[0, n - 1]` 范围的字符,将其存入数组。 -- 将数组转为字符串返回。 - -## 代码 - -```python -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - res = [] - for i in range(n, len(s)): - res.append(s[i]) - for i in range(n): - res.append(s[i]) - return "".join(res) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 59 - I. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer 59 - I. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index cb4b252c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 59 - I. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [剑指 Offer 59 - I. 滑动窗口的最大值](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) - -- 标签:队列、滑动窗口、单调队列、堆(优先队列) -- 难度:困难 - -## 题目大意 - -给定一个整数数组 `nums` 和滑动窗口的大小 `k`。表示为大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 `k` 个数字,滑动窗口每次只能向右移动一位。 - -要求:返回滑动窗口中的最大值。 - -## 解题思路 - -暴力求解的话,二重循环,时间复杂度为 $O(n * k)$。 - -我们可以使用优先队列,每次窗口移动时想优先队列中增加一个节点,并删除一个节点。将窗口中的最大值加入到答案数组中。 - -## 代码 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - size = len(nums) - if size == 0: - return [] - - q = [(-nums[i], i) for i in range(k)] - heapq.heapify(q) - res = [-q[0][0]] - - for i in range(k, size): - heapq.heappush(q, (-nums[i], i)) - while q[0][1] <= i - k: - heapq.heappop(q) - res.append(-q[0][0]) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 59 - II. \351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer 59 - II. \351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 145e5166..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 59 - II. \351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer 59 - II. 队列的最大值](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) - -- 标签:设计、队列、单调队列 -- 难度:中等 - -## 题目大意 - -要求:设计一个「队列」,实现 `max_value` 函数,可通过 `max_value` 得到大年队列的最大值。并且要求 `max_value`、`push_back`、`pop_front` 的均摊时间复杂度都是 `O(1)`。 - -## 解题思路 - -利用空间换时间,使用两个队列。其中一个为原始队列 `queue`,另一个为递减队列 `deque`,`deque` 用来保存队列的最大值,具体做法如下: - -- `push_back` 操作:如果 `deque` 队尾元素小于即将入队的元素 `value`,则将小于 `value` 的元素全部出队,再将 `valuew` 入队。否则直接将 `value` 直接入队,这样 `deque` 队首元素保存的就是队列的最大值。 -- `pop_front` 操作:先判断 `deque`、`queue` 是否为空,如果 `deque` 或者 `queue` 为空,则说明队列为空,直接返回 `-1`。如果都不为空,从 `queue` 中取出一个元素,并跟 `deque` 队首元素进行比较,如果两者相等则需要将 `deque` 队首元素弹出。 -- `max_value` 操作:如果 `deque` 不为空,则返回 `deque` 队首元素。否则返回 `-1`。 - -## 代码 - -```python -import collections -import queue - - -class MaxQueue: - - def __init__(self): - self.queue = queue.Queue() - self.deque = collections.deque() - - - def max_value(self) -> int: - if self.deque: - return self.deque[0] - else: - return -1 - - - def push_back(self, value: int) -> None: - while self.deque and self.deque[-1] < value: - self.deque.pop() - self.deque.append(value) - self.queue.put(value) - - - def pop_front(self) -> int: - if not self.deque or not self.queue: - return -1 - ans = self.queue.get() - if ans == self.deque[0]: - self.deque.popleft() - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 61. \346\211\221\345\205\213\347\211\214\344\270\255\347\232\204\351\241\272\345\255\220.md" "b/Solutions/\345\211\221\346\214\207 Offer 61. \346\211\221\345\205\213\347\211\214\344\270\255\347\232\204\351\241\272\345\255\220.md" deleted file mode 100644 index 94a30fb0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 61. \346\211\221\345\205\213\347\211\214\344\270\255\347\232\204\351\241\272\345\255\220.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer 61. 扑克牌中的顺子](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) - -- 标签:数组、排序 -- 难度:简单 - -## 题目大意 - -给定一个 `5` 位数的数组 `nums` 代表扑克牌中的 `5` 张牌。其中 `2~10` 为数字本身,`A` 用 `1` 表示,`J` 用 `11` 表示,`Q` 用 `12` 表示,`K` 用 `13` 表示,大小王用 `0` 表示,且大小王可以替换任意数字。 - -要求:判断给定的 `5` 张牌是否是一个顺子,即是否为连续的`5` 个数。 - -## 解题思路 - -先不考虑牌中有大小王,如果 `5` 个数是连续的,则这 `5` 个数中最大值最小值的关系为:`最大值 - 最小值 = 4`。如果牌中有大小王可以替换这 `5` 个数中的任意数字,则除大小王之外剩下数的最大值最小值关系为 `最大值 - 最小值 <= 4`。而且剩余数不能有重复数字。于是可以这样进行判断。 - -遍历 `5` 张牌: - -- 如果出现大小王,则跳过。 -- 判断 `5` 张牌中是否有重复数,如果有则直接返回 `False`,如果没有则将其加入集合。 -- 计算 `5` 张牌的最大值,最小值。 - -最后判断 `最大值 - 最小值 <= 4` 是否成立。如果成立,返回 `True`,否则返回 `False`。 - -## 代码 - -```python -class Solution: - def isStraight(self, nums: List[int]) -> bool: - max_num, min_num = 0, 14 - repeat = set() - for num in nums: - if num == 0: - continue - if num in repeat: - return False - repeat.add(num) - max_num = max(max_num, num) - min_num = min(min_num, num) - return max_num - min_num <= 4 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index b52cf489..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,56 +0,0 @@ -# [剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) - -- 标签:递归、数学 -- 难度:简单 - -## 题目大意 - -`0`、`1`、…、`n - 1` 这 `n` 个数字排成一个圆圈,从数字 `0` 开始,每次从圆圈里删除第 `m` 个数字。现在给定整数 `n` 和 `m`。 - -要求:求出这个圆圈中剩下的最后一个数字。 - -## 解题思路 - -模拟循环删除,需要进行 `n - 1` 轮,每轮需要对节点进行 `m` 次访问操作。总体时间复杂度为 `O(nm)`。 - -可以通过找规律来做,以 `n = 5`、`m = 3` 为例。 - -- 刚开始为 `0`、`1`、`2`、`3`、`4`。 -- 第一次从 `0` 开始数,数 `3` 个数,于是 `2` 出圈,变为 `3`、`4`、`0`、`1`。 -- 第二次从 `3` 开始数,数 `3` 个数,于是 `0` 出圈,变为 `1`、`3`、`4`。 -- 第三次从 `1` 开始数,数 `3` 个数,于是 `4` 出圈,变为 `1`、`3`。 -- 第四次从 `1` 开始数,数 `3` 个数,于是 `1` 出圈,变为 `3`。 -- 所以最终为 `3`。 - -通过上面的流程可以发现:每隔 `m` 个数就要删除一个数,那么被删除的这个数的下一个数就会成为新的起点。就相当于数组进行左移了 `m` 位。反过来思考的话,从最后一步向前推,则每一步都向右移动了 `m` 位(包括胜利者)。 - -如果用 `f(n, m)` 表示: `n` 个数构成环没删除 `m` 个数后,最终胜利者的位置,则 `f(n, m) = f(n - 1, m) + m`。 - -即等于 `n - 1` 个数构成的环没删除 `m` 个数后最终胜利者的位置,像右移动 `m` 次。 - -问题是现在并不是真的进行了右移,因为当前数组右移后超过数组容量的部分应该重新放到数组头部位置。所以公式应为:`f(n, m) = [f(n - 1, m) + m] % n`,`n` 为反过来向前推的时候,每一步剩余的数字个数(比如第二步推回第一步,n `4`),则反过来递推公式为: - -- `f(1, m) = 0`。 -- `f(2, m) = [f(1, m) + m] % 2`。 -- `f(3, m) = [f(2, m) + m] % 3`。 -- 。。。。。。 - -- `f(n, m) = [f(n - 1, m) + m] % n `。 - -接下来就是递推求解了。 - -## 代码 - -```python -class Solution: - def lastRemaining(self, n: int, m: int) -> int: - ans = 0 - for i in range(2, n + 1): - ans = (m + ans) % i - return ans -``` - -## 参考资料: - -- [字节题库 - #剑62 - 简单 - 圆圈中最后剩下的数字 - 1刷](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/zi-jie-ti-ku-jian-62-jian-dan-yuan-quan-3hlji/) - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" "b/Solutions/\345\211\221\346\214\207 Offer 63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" deleted file mode 100644 index 9e4c9eaf..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" +++ /dev/null @@ -1,32 +0,0 @@ -# [剑指 Offer 63. 股票的最大利润](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个数组 `nums`,`nums[i]` 表示一支给定股票第 `i` 天的价格。选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。求能获取的最大利润。 - -## 解题思路 - -最简单的思路当然是两重循环暴力枚举,寻找不同天数下的最大利润。 - -但更好的做法是进行一次遍历。设置两个变量 `minprice`(用来记录买入的最小值)、`maxprofit`(用来记录可获取的最大利润)。 - -进行一次遍历,遇到当前价格比 `minprice` 还要小的,就更新 `minprice`。如果单签价格大于或者等于 `minprice`,则判断一下以当前价格卖出的话能卖多少,如果比 `maxprofit` 还要大,就更新 `maxprofit`。 - -## 代码 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - minprice = 10010 - maxprofit = 0 - for price in prices: - if price < minprice: - minprice = price - elif price - minprice > maxprofit: - maxprofit = price - minprice - return maxprofit -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 64. \346\261\2021+2+\342\200\246+n.md" "b/Solutions/\345\211\221\346\214\207 Offer 64. \346\261\2021+2+\342\200\246+n.md" deleted file mode 100644 index 85ee476e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 64. \346\261\2021+2+\342\200\246+n.md" +++ /dev/null @@ -1,23 +0,0 @@ -# [剑指 Offer 64. 求1+2+…+n](https://leetcode.cn/problems/qiu-12n-lcof/) - -- 标签:位运算、递归、脑筋急转弯 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:计算 `1 + 2 + ... + n`,并且不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。 - -## 解题思路 - -Python 中的逻辑运算最终返回的是最后一个非空值。比如 `3 and 2 and 'a'` 最终返回的是 `'a'`。利用这个特性可以递归求解。 - -## 代码 - -```python -class Solution: - def sumNums(self, n: int) -> int: - return n and n + self.sumNums(n - 1) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" "b/Solutions/\345\211\221\346\214\207 Offer 65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" deleted file mode 100644 index 045a7bd3..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer 65. 不用加减乘除做加法](https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) - -- 标签:位运算、数学 -- 难度:简单 - -## 题目大意 - -给定两个整数 `a`、`b`。 - -要求:不能使用运算符 `+`、`-`、`*`、`/`,计算两整数 `a` 、`b` 之和。 - -## 解题思路 - -需要用到位运算的一些知识。 - -- 异或运算 a ^ b :可以获得 a + b 无进位的加法结果。 -- 与运算 a & b:对应位置为 1,说明 a、b 该位置上原来都为 1,则需要进位。 -- 座椅运算 a << 1:将 a 对应二进制数左移 1 位。 - -这样,通过 a^b 运算,我们可以得到相加后无进位结果,再根据 (a&b) << 1,计算进位后结果。 - -进行 a^b 和 (a&b) << 1操作之后判断进位是否为 0,若不为 0,则继续上一步操作,直到进位为 0。 - -> 注意: -> -> Python 的整数类型是无限长整数类型,负数不确定符号位是第几位。所以我们可以将输入的数字手动转为 32 位无符号整数。 -> -> 通过 a &= 0xFFFFFFFF 即可将 a 转为 32 位无符号整数。最后通过对 a 的范围判断,将其结果映射为有符号整数。 - -## 代码 - -```python -class Solution: - def getSum(self, a: int, b: int) -> int: - MAX_INT = 0x7FFFFFFF - MASK = 0xFFFFFFFF - a &= MASK - b &= MASK - while b: - carry = ((a & b) << 1) & MASK - a ^= b - b = carry - if a <= MAX_INT: - return a - else: - return ~(a ^ MASK) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer 66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" deleted file mode 100644 index 18d912c7..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer 66. 构建乘积数组](https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/) - -- 标签:数组、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个数组 `A`。 - -要求:构建一个数组 `B`,其中 `B[i]` 为数组 `A` 中除了 `A[i]` 之外的其他所有元素乘积。 - -要求不能使用除法。 - -## 解题思路 - -构造一个答案数组 `B`,长度和数组 `A` 长度一致。先从左到右遍历一遍 `A` 数组,将 `A[i]` 左侧的元素乘积累积起来,存储到 `B` 数组中。再从右到左遍历一遍,将 `A[i]` 右侧的元素乘积累积起来,再乘以原本 `B[i]` 的值,即为 `A` 中除了 `A[i]` 之外的其他所有元素乘积。 - -## 代码 - -```python -class Solution: - def constructArr(self, a: List[int]) -> List[int]: - size = len(a) - b = [1 for _ in range(size)] - - left = 1 - for i in range(size): - b[i] *= left - left *= a[i] - - right = 1 - for i in range(size - 1, -1, -1): - b[i] *= right - right *= a[i] - return b -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer 67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" deleted file mode 100644 index 19082b10..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [剑指 Offer 67. 把字符串转换成整数](https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) - -- 标签:字符串 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `str`。 - -要求:使其能换成一个 32 位有符号整数。并且该方法满足以下要求: - -- 丢弃开头无用的空格字符,直到找到第一个非空格字符为止。 -- 当找到的第一个非空字符为正负号时,将该符号与后面尽可能多的连续数组组合起来,作为该整数的正负号。如果第一个非空字符为数字,则直接将其与之后连续的数字字符组合起来,形成整数。 -- 该字符串中除了有效的整数部分之后也可能会存在多余字符,可直接将这些字符忽略,不会对函数造成影响。 -- 如果第一个非空格字符不是一个有效整数字符、或者字符串为空、字符串仅包含空白字符时,函数不需要进行转换。 -- 需要检测有效性,无法读取返回 0。 -- 所有整数范围为 $[-2^{31}, 2^{31} - 1]$,超过这个范围,则返回 $2^{31} - 1$ 或者 $-2^{31}$。 - -## 解题思路 - -根据题意直接模拟即可。 - -1. 先去除前后空格。 -2. 检测正负号。 -3. 读入数字,并用字符串存储数字结果 -4. 将数字字符串转为整数,并根据正负号转换整数结果。 -5. 判断整数范围,并返回最终结果。 - -## 代码 - -```python -class Solution: - def strToInt(self, str: str) -> int: - num_str = "" - positive = True - start = 0 - - s = str.lstrip() - if not s: - return 0 - - if s[0] == '-': - positive = False - start = 1 - elif s[0] == '+': - positive = True - start = 1 - elif not s[0].isdigit(): - return 0 - - for i in range(start, len(s)): - if s[i].isdigit(): - num_str += s[i] - else: - break - if not num_str: - return 0 - num = int(num_str) - if not positive: - num = -num - return max(num, -2 ** 31) - else: - return min(num, 2 ** 31 - 1) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 68 - I. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Solutions/\345\211\221\346\214\207 Offer 68 - I. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" deleted file mode 100644 index 90f5c973..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 68 - I. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer 68 - I. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root` 和两个指定节点 `p`、`q`。 - -要求:找到该树中两个指定节点 `p`、`q` 的最近公共祖先。 - -- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 -- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先) - -## 解题思路 - -对于节点 `p`、节点 `q`,最近公共祖先就是从根节点分别到它们路径上的分岔点,也是路径中最后一个相同的节点,现在我们的问题就是求这个分岔点。 - -使用递归遍历查找最近公共祖先。 - -- 从根节点开始遍历; - - 如果当前节点的值大于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的左子树,因此将当前节点移动到它的左子节点,继续遍历; - - 如果当前节点的值小于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的右子树,因此将当前节点移动到它的右子节点,继续遍历; - - 如果当前节点不满足上面两种情况,则说明 `p` 和 `q` 分别在当前节点的左右子树上,则当前节点就是分岔点,直接返回该节点即可。 - -## 代码 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - ancestor = root - while True: - if ancestor.val > p.val and ancestor.val > q.val: - ancestor = ancestor.left - elif ancestor.val < p.val and ancestor.val < q.val: - ancestor = ancestor.right - else: - break - return ancestor -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer 68 - II. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Solutions/\345\211\221\346\214\207 Offer 68 - II. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" deleted file mode 100644 index 38412714..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer 68 - II. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [剑指 Offer 68 - II. 二叉树的最近公共祖先](https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉树的根节点 `root`,再给定两个指定节点 `p`、`q`。 - -要求:找到两个指定节点 `p`、`q` 的最近公共祖先。 - -- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 -- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先) - -## 解题思路 - -设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: - -- `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 -- `p = lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 -- `q = lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 - -下面递归求解 `lca_node`。递归需要满足以下条件: - -- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 -- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 -- 如果 `p`、`q` 都不存在,则返回存在的一个。 - -具体思路为: - -- 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 -- 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node` -- 递归遍历左子树、右子树,并判断左右子树结果。 - - 如果左子树为空,则返回右子树。 - - 如果右子树为空,则返回左子树。 - - 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 - - 如果左右子树都为空,则返回空。 - -## 代码 - -```python -class Solution: - def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: - if root == p or root == q: - return root - - if root: - node_left = self.lowestCommonAncestor(root.left, p, q) - node_right = self.lowestCommonAncestor(root.right, p, q) - if node_left and node_right: - return root - elif not node_left: - return node_right - else: - return node_left - return None -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 001. \346\225\264\346\225\260\351\231\244\346\263\225.md" "b/Solutions/\345\211\221\346\214\207 Offer II 001. \346\225\264\346\225\260\351\231\244\346\263\225.md" deleted file mode 100644 index 7f02a963..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 001. \346\225\264\346\225\260\351\231\244\346\263\225.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [剑指 Offer II 001. 整数除法](https://leetcode.cn/problems/xoh6Oh/) - -- 标签:位运算、数学 -- 难度:简单 - -## 题目大意 - -给定两个整数,被除数 dividend 和除数 divisor。要求返回两数相除的商,并且不能使用乘法,除法和取余运算。取值范围在 $[-2^{31}, 2^{31}-1]$。如果结果溢出,则返回 $2^{31} - 1$。 - -## 解题思路 - -题目要求不能使用乘法,除法和取余运算。 - -可以把被除数和除数当做二进制,这样进行运算的时候,就可以通过移位运算来实现二进制的乘除。 - -- 先将除数不断左移,移位到位数大于或等于被除数。记录其移位次数 count。 - -- 然后再将除数右移 count 次,模拟二进制除法运算。 - - 如果当前被除数大于等于除数,则将 1 左移 count 位,即为当前位的商,并将其累加答案上。再用除数减去被除数,进行下一次运算。 - -## 代码 - -```python - -添加备注 - - -class Solution: - def divide(self, a: int, b: int) -> int: - MIN_INT, MAX_INT = -2147483648, 2147483647 - symbol = True if (a ^ b) < 0 else False - if a < 0: - a = -a - if b < 0: - b = -b - - # 除数不断左移,移位到位数大于或等于被除数 - count = 0 - while a >= b: - count += 1 - b <<= 1 - - # 向右移位,不断模拟二进制除法运算 - res = 0 - while count > 0: - count -= 1 - b >>= 1 - if a >= b: - res += (1 << count) - a -= b - if symbol: - res = -res - if MIN_INT <= res <= MAX_INT: - return res - else: - return MAX_INT -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 002. \344\272\214\350\277\233\345\210\266\345\212\240\346\263\225.md" "b/Solutions/\345\211\221\346\214\207 Offer II 002. \344\272\214\350\277\233\345\210\266\345\212\240\346\263\225.md" deleted file mode 100644 index be1943d5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 002. \344\272\214\350\277\233\345\210\266\345\212\240\346\263\225.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer II 002. 二进制加法](https://leetcode.cn/problems/JFETK5/) - -- 标签:位运算、数学、字符串、模拟 -- 难度:简单 - -## 题目大意 - -给定两个二进制数的字符串 `a`、`b`。 - -要求:计算 `a` 和 `b` 的和,返回结果也用二进制表示。 - -## 解题思路 - -这道题可以直接将 `a`、`b` 转换为十进制数,相加后再转换为二进制数。 - -也可以利用位运算的一些知识,直接求和。 - -因为 `a`、`b` 为二进制的字符串,先将其转换为二进制数。 - -本题用到的位运算知识: - -- 异或运算 `x ^ y` :可以获得 `x + y` 无进位的加法结果。 -- 与运算 `x & y`:对应位置为 `1`,说明 `x`、`y` 该位置上原来都为 `1`,则需要进位。 -- 座椅运算 `x << 1`:将 a 对应二进制数左移 `1` 位。 - -这样,通过 `x ^ y` 运算,我们可以得到相加后无进位结果,再根据 `(x & y) << 1`,计算进位后结果。 - -进行 `x ^ y` 和 `(x & y) << 1`操作之后判断进位是否为 `0`,若不为 `0`,则继续上一步操作,直到进位为 `0`。 - -最后将其结果转为 `2` 进制返回。 - -## 代码 - -```python -class Solution: - def addBinary(self, a: str, b: str) -> str: - x = int(a, 2) - y = int(b, 2) - while y: - carry = ((x & y) << 1) - x ^= y - y = carry - return bin(x)[2:] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 003. \345\211\215 n \344\270\252\346\225\260\345\255\227\344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer II 003. \345\211\215 n \344\270\252\346\225\260\345\255\227\344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index e7a716e9..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 003. \345\211\215 n \344\270\252\346\225\260\345\255\227\344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer II 003. 前 n 个数字二进制中 1 的个数](https://leetcode.cn/problems/w3tCBm/) - -- 标签:位运算、动态规划 -- 难度:简单 - -## 题目大意 - -给定一个整数 `n`。 - -要求:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 - -## 解题思路 - -可以根据整数的二进制特点将其分为两类: - -- 奇数:一定比前面相邻的偶数多一个 `1`。 -- 偶数:一定和除以 `2` 之后的数一样多。 -- 边界 `0`:`1` 的个数为 `0`。 - -于是可以根据规律,从 `0` 开始到 `n` 进行递推求解。 - -## 代码 - -```python -class Solution: - def countBits(self, n: int) -> List[int]: - dp = [0 for _ in range(n + 1)] - for i in range(1, n + 1): - if i % 2 == 1: - dp[i] = dp[i - 1] + 1 - else: - dp[i] = dp[i // 2] - return dp -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 004. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 004. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index 4a2442b7..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 004. \345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,65 +0,0 @@ -# [剑指 Offer II 004. 只出现一次的数字](https://leetcode.cn/problems/WGki4K/) - -- 标签:位运算、数组 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`,除了某个元素仅出现一次外,其余每个元素恰好出现三次。 - -要求:找到并返回那个只出现了一次的元素。 - -## 解题思路 - -### 1. 哈希表 - -朴素解法就是利用哈希表。统计出每个元素的出现次数。再遍历哈希表,找到仅出现一次的元素。 - -### 2. 位运算 - -将出现三次的元素换成二进制形式放在一起,其二进制对应位置上,出现 `1` 的个数一定是 `3` 的倍数(包括 `0`)。此时,如果在放进来只出现一次的元素,则某些二进制位置上出现 `1` 的个数就不是 `3` 的倍数了。 - -将这些二进制位置上出现 `1` 的个数不是 `3` 的倍数位置值置为 `1`,是 `3` 的倍数则置为 `0`。这样对应下来的二进制就是答案所求。 - -注意:因为 Python 的整数没有位数限制,所以不能通过最高位确定正负。所以 Python 中负整数的补码会被当做正整数。所以在遍历到最后 `31` 位时进行 `ans -= (1 << 31)` 操作,目的是将负数的补码转换为「负号 + 原码」的形式。这样就可以正常识别二进制下的负数。参考:[Two's Complement Binary in Python? - Stack Overflow](https://stackoverflow.com/questions/12946116/twos-complement-binary-in-python/12946226) - -## 代码 - -1. 哈希表 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - nums_dict = dict() - for num in nums: - if num in nums_dict: - nums_dict[num] += 1 - else: - nums_dict[num] = 1 - for key in nums_dict: - value = nums_dict[key] - if value == 1: - return key - return 0 -``` - -2. 位运算 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - ans = 0 - for i in range(32): - count = 0 - for j in range(len(nums)): - count += (nums[j] >> i) & 1 - if count % 3 != 0: - if i == 31: - ans -= (1 << 31) - else: - ans = ans | 1 << i - return ans -``` - - - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 005. \345\215\225\350\257\215\351\225\277\345\272\246\347\232\204\346\234\200\345\244\247\344\271\230\347\247\257.md" "b/Solutions/\345\211\221\346\214\207 Offer II 005. \345\215\225\350\257\215\351\225\277\345\272\246\347\232\204\346\234\200\345\244\247\344\271\230\347\247\257.md" deleted file mode 100644 index c6168049..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 005. \345\215\225\350\257\215\351\225\277\345\272\246\347\232\204\346\234\200\345\244\247\344\271\230\347\247\257.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [剑指 Offer II 005. 单词长度的最大乘积](https://leetcode.cn/problems/aseY1I/) - -- 标签:位运算、数组、字符串 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `words`。字符串中只包含英语的小写字母。 - -要求:计算当两个字符串 `words[i]` 和 `words[j]` 不包含相同字符时,它们长度的乘积的最大值。如果没有不包含相同字符的一对字符串,返回 0。 - -## 解题思路 - -这道题的核心难点是判断任意两个字符串之间是否包含相同字符。最直接的做法是先遍历第一个字符串的每个字符,再遍历第二个字符串查看是否有相同字符。但是这样做的话,时间复杂度过高。考虑怎么样可以优化一下。 - -题目中说字符串中只包含英语的小写字母,也就是 `26` 种字符。一个 `32` 位的 `int` 整数每一个二进制位都可以表示一种字符的有无,那么我们就可以通过一个整数来表示一个字符串中所拥有的字符种类。延伸一下,我们可以用一个整数数组来表示一个字符串数组中,每个字符串所拥有的字符种类。 - -接下来事情就简单了,两重循环遍历整数数组,遇到两个字符串不包含相同字符的情况,就计算一下他们长度的乘积,并维护一个乘积最大值。最后输出最大值即可。 - -## 代码 - -```python -class Solution: - def maxProduct(self, words: List[str]) -> int: - size = len(words) - arr = [0 for _ in range(size)] - for i in range(size): - word = words[i] - len_word = len(word) - for j in range(len_word): - arr[i] |= 1 << (ord(word[j]) - ord('a')) - ans = 0 - for i in range(size): - for j in range(i + 1, size): - if arr[i] & arr[j] == 0: - k = len(words[i]) * len(words[j]) - ans = k if ans < k else ans - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 006. \346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\345\255\227\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 006. \346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\345\255\227\344\271\213\345\222\214.md" deleted file mode 100644 index bcd7bdbc..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 006. \346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\345\255\227\344\271\213\345\222\214.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [剑指 Offer II 006. 排序数组中两个数字之和](https://leetcode.cn/problems/kLl5u1/) - -- 标签:数组、双指针、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个升序数组:`numbers` 和一个目标值 `target`。 - -要求:从数组中找出满足相加之和等于 `target` 的两个数,并返回两个数在数组中下的标值。 - -## 解题思路 - -因为数组是有序的,所以我们可以使用两个指针 low,high。low 指向数组开始较小元素位置,high 指向数组较大元素位置。判断两个位置上的元素和,如果和等于目标值,则返回两个元素位置。如果和大于目标值,则 high 左移,继续检测。如果和小于目标值,则 low 右移,继续检测。直到 low 和 high 移动到相同位置停止检测。若最终仍没找到,则返回 [0, 0]。 - -## 代码 - -```python -class Solution: - def twoSum(self, numbers: List[int], target: int) -> List[int]: - low = 0 - high = len(numbers) - 1 - while low < high: - total = numbers[low] + numbers[high] - if total == target: - return [low, high] - elif total < target: - low += 1 - else: - high -= 1 - return [0, 0] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 007. \346\225\260\347\273\204\344\270\255\345\222\214\344\270\272 0 \347\232\204\344\270\211\344\270\252\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer II 007. \346\225\260\347\273\204\344\270\255\345\222\214\344\270\272 0 \347\232\204\344\270\211\344\270\252\346\225\260.md" deleted file mode 100644 index 246c6a13..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 007. \346\225\260\347\273\204\344\270\255\345\222\214\344\270\272 0 \347\232\204\344\270\211\344\270\252\346\225\260.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer II 007. 数组中和为 0 的三个数](https://leetcode.cn/problems/1fGaJU/) - -- 标签:数组、双指针、排序 -- 难度:中等 - -## 题目大意 - -给定一个包含 `n` 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 `a`、`b`、`c`,满足 `a + b + c = 0`。 - -要求:找出所有满足要求的不重复的三元组。 - -## 解题思路 - -直接三重遍历查找 a、b、c 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 - -先将数组进行排序,以保证按顺序查找 a、b、c 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$ - -第一重循环遍历 a,对于每个 a 元素,从 a 元素的下一个位置开始,使用双指针 left,right。left 指向 a 元素的下一个位置,right 指向末尾位置。先将 left 右移、right 左移去除重复元素,再进行下边的判断。 - -- 若 `nums[a] + nums[left] + nums[right] = 0`,则得到一个解,将其加入答案数组中,并继续将 left 右移,right 左移; - -- 若 `nums[a] + nums[left] + nums[right] > 0`,说明 nums[right] 值太大,将 right 向左移; -- 若 `nums[a] + nums[left] + nums[right] < 0`,说明 nums[left] 值太小,将 left 右移。 - -## 代码 - -```python -class Solution: - def threeSum(self, nums: List[int]) -> List[List[int]]: - n = len(nums) - nums.sort() - ans = [] - - for i in range(n): - if i > 0 and nums[i] == nums[i - 1]: - continue - left = i + 1 - right = n - 1 - while left < right: - while left < right and left > i + 1 and nums[left] == nums[left - 1]: - left += 1 - while left < right and right < n - 1 and nums[right + 1] == nums[right]: - right -= 1 - if left < right and nums[i] + nums[left] + nums[right] == 0: - ans.append([nums[i], nums[left], nums[right]]) - left += 1 - right -= 1 - elif nums[i] + nums[left] + nums[right] > 0: - right -= 1 - else: - left += 1 - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 1b215e44..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 008. 和大于等于 target 的最短子数组](https://leetcode.cn/problems/2VG8Kg/) - -- 标签:数组、二分查找、前缀和、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 - -要求:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。 - -## 解题思路 - -最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 - -定义两个指针 `start` 和 `end`。`start` 代表滑动窗口开始位置,`end` 代表滑动窗口结束位置。再定义一个变量 `sum` 用来存储滑动窗口中的元素和,一个变量 `ans` 来存储满足提议的最小长度。 - -先不断移动 `end`,直到 `sum ≥ target`,则更新最小长度值 `ans`。然后再将滑动窗口的起始位置从滑动窗口中移出去,直到 `sum ≤ target`,在移出的期间,同样要更新最小长度值 `ans`。 - -然后等满足 `sum ≤ target` 时,再移动 `end`,重复上一步,直到遍历到数组末尾。 - -## 代码 - -```python -class Solution: - def minSubArrayLen(self, target: int, nums: List[int]) -> int: - if not nums: - return 0 - n = len(nums) - - start = 0 - end = 0 - sum = 0 - ans = n + 1 - while end < n: - sum += nums[end] - while sum >= target: - ans = min(ans, end - start + 1) - sum -= nums[start] - start += 1 - end += 1 - if ans == n + 1: - return 0 - else: - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 009. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 009. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 379b653a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 009. \344\271\230\347\247\257\345\260\217\344\272\216 K \347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 009. 乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/) - -- 标签:数组、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个正整数数组 `nums` 和一个整数 `k`。 - -要求:找出该数组内乘积小于 `k` 的连续子数组的个数。 - -## 解题思路 - -滑动窗口求解。 - -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 `product` 都小于 `k`。 - -- 一开始,`left`、`right` 都指向 `0`。 - -- 向右移动 `right`,将最右侧元素加入当前子数组乘积 `product` 中。 - -- 如果 `product >= k` ,则不断右移 `left`,缩小滑动窗口,并更新当前乘积值 `product` 直到 `product < k`。 -- 累积答案个数 += 1,继续右移 `right`,直到 `right >= len(nums)` 结束。 -- 输出累积答案个数。 - -## 代码 - -```python -class Solution: - def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int: - if k <= 1: - return 0 - - size = len(nums) - left, right = 0, 0 - count = 0 - product = 1 - while right < size: - product *= nums[right] - right += 1 - while product >= k: - product /= nums[left] - left += 1 - count += (right - left) - return count -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 010. \345\222\214\344\270\272 k \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 010. \345\222\214\344\270\272 k \347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 89c3be6a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 010. \345\222\214\344\270\272 k \347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [剑指 Offer II 010. 和为 k 的子数组](https://leetcode.cn/problems/QTMn0o/) - -- 标签:数组、哈希表、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums` 和一个整数 `k`。 - -要求:找到该数组中和为 `k` 的连续子数组的个数。 - -## 解题思路 - -看到题目的第一想法是通过滑动窗口求解。但是做下来发现有些数据样例无法通过。发现这道题目中的整数不能保证都为正数,则无法通过滑动窗口进行求解。 - -先考虑暴力做法,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: - -```python -for i in range(len(nums)): - for j in range(i + 1): - sum = countSum(i, j) -``` - -这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 - -先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^3)$。 - -但是还是超时了。。 - -由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 - -`pre_sum[i]` 的定义是前 `i` 个元素和,则 `pre_sum[i]` 可以由 `pre_sum[i - 1]` 递推而来,即:`pre_sum[i] = pre_sum[i - 1] + sum[i]`。 `[j..i]` 子数组和为 `k` 可以转换为:`pre_sum[i] - pre_sum[j - 1] == k`。 - -综合一下,可得:`pre_sum[j - 1] == pre_sum[i] - k `。 - -所以,当我们考虑以 `i` 结尾和为 `k` 的连续子数组个数时,只需要统计有多少个前缀和为 `pre_sum[i] - k` (即 `pre_sum[j - 1]`)的个数即可。具体做法如下: - -- 使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。 -- 使用哈希表 `pre_dic` 记录 `pre_sum[i]` 出现的次数。键值对为 `pre_sum[i] : pre_sum_count`。 -- 从左到右遍历数组,计算当前前缀和 `pre_sum`。 -- 如果 `pre_sum - k` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum - k]`。 -- 如果 `pre_sum` 在哈希表中,则前缀和个数累加 1,即 `pre_dic[pre_sum] += 1`。 -- 最后输出答案个数。 - -## 代码 - -```python -class Solution: - def subarraySum(self, nums: List[int], k: int) -> int: - pre_dic = {0: 1} - pre_sum = 0 - count = 0 - for num in nums: - pre_sum += num - if pre_sum - k in pre_dic: - count += pre_dic[pre_sum - k] - if pre_sum in pre_dic: - pre_dic[pre_sum] += 1 - else: - pre_dic[pre_sum] = 1 - return count -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 011. 0 \345\222\214 1 \344\270\252\346\225\260\347\233\270\345\220\214\347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 011. 0 \345\222\214 1 \344\270\252\346\225\260\347\233\270\345\220\214\347\232\204\345\255\220\346\225\260\347\273\204.md" deleted file mode 100644 index 4b6175fc..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 011. 0 \345\222\214 1 \344\270\252\346\225\260\347\233\270\345\220\214\347\232\204\345\255\220\346\225\260\347\273\204.md" +++ /dev/null @@ -1,64 +0,0 @@ -# [剑指 Offer II 011. 0 和 1 个数相同的子数组](https://leetcode.cn/problems/A1NYOS/) - -- 标签:数组、哈希表、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个二进制数组 `nums`。 - -要求:找到含有相同数量 `0` 和 `1` 的最长连续子数组,并返回该子数组的长度。 - -## 解题思路 - -「`0` 和 `1` 数量相同」等价于「`1` 的数量减去 `0` 的数量等于 `0`」。 - -我们可以使用一个变量 `pre_diff` 来记录下前 `i` 个数中,`1` 的数量比 `0` 的数量多多少个。我们把这个 `pre_diff`叫做「`1` 和 `0` 数量差」,也可以理解为变种的前缀和。 - -然后我们再用一个哈希表 `pre_dic` 来记录「`1` 和 `0` 数量差」第一次出现的下标。 - -那么,如果我们在遍历的时候,发现 `pre_diff` 相同的数量差已经在之前出现过了,则说明:这两段之间相减的 `1` 和 `0` 数量差为 `0`。 - -什么意思呢? - -比如说:`j < i`,前 `j` 个数中第一次出现 `pre_diff == 2` ,然后前 `i` 个数中个第二次又出现了 `pre_diff == 2`。那么这两段形成的子数组 `nums[j + 1: i]` 中 `1` 比 `0` 多 `0` 个,则 `0` 和 `1` 数量相同的子数组长度为 `i - j`。 - -而第二次之所以又出现 `pre_diff == 2` ,是因为前半段子数组 `nums[0: j]` 贡献了相同的差值。 - -接下来还有一个小问题,如何计算「`1` 和 `0` 数量差」? - -我们可以把数组中的 `1` 记为贡献 `+1`,`0` 记为贡献 `-1`。然后使用一个变量 `count`,只要出现 `1` 就让 `count` 加上 `1`,意思是又多出了 `1` 个 `1`。只要出现 `0`,将让 `count` 减去 `1`,意思是 `0` 和之前累积的 `1` 个 `1` 相互抵消掉了。这样遍历完数组,也就计算出了对应的「`1` 和 `0` 数量差」。 - -整个思路的具体做法如下: - -- 创建一个哈希表,键值对关系为「`1` 和 `0` 的数量差:最早出现的下标 `i`」。 -- 使用变量 `pre_diff` 来计算「`1` 和 `0` 数量差」,使用变量 `count` 来记录 `0` 和 `1` 数量相同的连续子数组的最长长度,然后遍历整个数组。 -- 如果 `nums[i] == 1`,则让 `pre_diff += 1`;如果 `nums[i] == 0`,则让 `pre_diff -= 1`。 -- 如果在哈希表中发现了相同的 `pre_diff`,则计算相应的子数组长度,与 `count` 进行比较并更新 `count` 值。 -- 如果在哈希表中没有发现相同的 `pre_diff`,则在哈希表中记录下第一次出现 `pre_diff` 的下标 `i`。 -- 最后遍历完输出 `count`。 - -> 注意:初始化哈希表为:`pre_dic = {0: -1}`,意思为空数组时,默认「`1` 和 `0` 数量差」为 `0`,且第一次出现的下标为 `-1`。 -> -> 之所以这样做,是因为在遍历过程中可能会直接出现 `pre_diff == 0` 的情况,这种情况下说明 `nums[0: i]` 中 `0` 和 `1` 数量相同,如果像上边这样初始化后,就可以直接计算出此时子数组长度为 `i - (-1) = i + 1`。 - -## 代码 - -```python -class Solution: - def findMaxLength(self, nums: List[int]) -> int: - pre_dic = {0: -1} - count = 0 - pre_sum = 0 - for i in range(len(nums)): - if nums[i]: - pre_sum += 1 - else: - pre_sum -= 1 - if pre_sum in pre_dic: - count = max(count, i - pre_dic[pre_sum]) - else: - pre_dic[pre_sum] = i - return count -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 012. \345\267\246\345\217\263\344\270\244\350\276\271\345\255\220\346\225\260\347\273\204\347\232\204\345\222\214\347\233\270\347\255\211.md" "b/Solutions/\345\211\221\346\214\207 Offer II 012. \345\267\246\345\217\263\344\270\244\350\276\271\345\255\220\346\225\260\347\273\204\347\232\204\345\222\214\347\233\270\347\255\211.md" deleted file mode 100644 index adb9cced..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 012. \345\267\246\345\217\263\344\270\244\350\276\271\345\255\220\346\225\260\347\273\204\347\232\204\345\222\214\347\233\270\347\255\211.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [剑指 Offer II 012. 左右两边子数组的和相等](https://leetcode.cn/problems/tvdfij/) - -- 标签:数组、前缀和 -- 难度:简单 - -## 题目大意 - -给定一个数组 `nums`。 - -要求:找到「左侧元素和」与「右侧元素和相等」的位置,若找不到,则返回 `-1`。 - -## 解题思路 - -两次遍历,第一次遍历先求出数组全部元素和。第二次遍历找到左侧元素和恰好为全部元素和一半的位置。 - -## 代码 - -```python -class Solution: - def pivotIndex(self, nums: List[int]) -> int: - sum = 0 - for i in range(len(nums)): - sum += nums[i] - curr_sum = 0 - for i in range(len(nums)): - if curr_sum * 2 + nums[i] == sum: - return i - curr_sum += nums[i] - return -1 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 013. \344\272\214\347\273\264\345\255\220\347\237\251\351\230\265\347\232\204\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 013. \344\272\214\347\273\264\345\255\220\347\237\251\351\230\265\347\232\204\345\222\214.md" deleted file mode 100644 index 7b06570d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 013. \344\272\214\347\273\264\345\255\220\347\237\251\351\230\265\347\232\204\345\222\214.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [剑指 Offer II 013. 二维子矩阵的和](https://leetcode.cn/problems/O4NDxx/) - -- 标签:设计、数组、矩阵、前缀和 -- 难度:中等 - -## 题目大意 - -给定一个二维矩阵 `matrix`。 - -要求:满足以下多个请求: - -- ` def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:`计算以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和。 -- `def __init__(self, matrix: List[List[int]]):` 对二维矩阵 `matrix` 进行初始化操作。 - -## 解题思路 - -在进行初始化的时候做预处理,这样在多次查询时可以减少重复计算,也可以减少时间复杂度。 - -在进行初始化的时候,使用一个二维数组 `pre_sum` 记录下以 `(0, 0)` 为左上角,以当前 `(row, col)` 为右下角的子数组各个元素和,即 `pre_sum[row + 1][col + 1]`。 - -则在查询时,以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和就等于以 `(0, 0)` 到 `(row2, col2)` 的大子矩阵减去左边 `(0, 0)` 到 `(row2, col1 - 1)`的子矩阵,再减去上边 `(0, 0)` 到 `(row1 - 1, col2)` 的子矩阵,再加上左上角 `(0, 0)` 到 `(row1 - 1, col1 - 1)` 的子矩阵(因为之前重复减了)。即 `pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1]`。 - -## 代码 - -```python -class NumMatrix: - - def __init__(self, matrix: List[List[int]]): - rows = len(matrix) - cols = len(matrix[0]) - self.pre_sum = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] - for row in range(rows): - for col in range(cols): - self.pre_sum[row + 1][col + 1] = self.pre_sum[row + 1][col] + self.pre_sum[row][col + 1] - self.pre_sum[row][col] + matrix[row][col] - - - def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int: - return self.pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 016. \344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer II 016. \344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 58cd1d55..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 016. \344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [剑指 Offer II 016. 不含重复字符的最长子字符串](https://leetcode.cn/problems/wtcaE1/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:找出其中不含有重复字符的 最长子串 的长度。 - -## 解题思路 - -利用集合来存储不重复的字符。用两个指针分别指向最长子串的左右节点。遍历字符串,右指针不断右移,利用集合来判断有没有重复的字符,如果没有,就持续向右扩大右边界。如果出现重复字符,就缩小左侧边界。每次移动终止,都要计算一下当前不含重复字符的子串长度,并判断一下是否需要更新最大长度。 - -## 代码 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - if not s: - return 0 - - letterSet = set() - right = 0 - ans = 0 - for i in range(len(s)): - if i != 0: - letterSet.remove(s[i - 1]) - while right < len(s) and s[right] not in letterSet: - letterSet.add(s[right]) - right += 1 - ans = max(ans, right - i) - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 017. \345\220\253\346\234\211\346\211\200\346\234\211\345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer II 017. \345\220\253\346\234\211\346\211\200\346\234\211\345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 4f0bfe65..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 017. \345\220\253\346\234\211\346\211\200\346\234\211\345\255\227\347\254\246\347\232\204\346\234\200\347\237\255\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,58 +0,0 @@ -# [剑指 Offer II 017. 含有所有字符的最短字符串](https://leetcode.cn/problems/M1oyTv/) - -- 标签:哈希表、字符串、滑动窗口 -- 难度:困难 - -## 题目大意 - -给定一个字符串 `s`、一个字符串 `t`。 - -要求:返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""`。如果存在多个符合条件的子字符串,返回任意一个。 - -## 解题思路 - -使用滑动窗口求解。 - -`left`、`right` 表示窗口的边界,一开始都位于下标 `0` 处。`need` 用于记录短字符串需要的字符数。`window` 记录当前窗口内的字符数。 - -将 `right` 右移,直到出现了 `t` 中全部字符,开始右移 `left`,减少滑动窗口的大小,并记录下最小覆盖子串的长度和起始位置。最后输出结果。 - -## 代码 - -```python -class Solution: - def minWindow(self, s: str, t: str) -> str: - need = collections.defaultdict(int) - window = collections.defaultdict(int) - for ch in t: - need[ch] += 1 - - left, right = 0, 0 - valid = 0 - start = 0 - size = len(s) + 1 - - while right < len(s): - insert_ch = s[right] - right += 1 - - if insert_ch in need: - window[insert_ch] += 1 - if window[insert_ch] == need[insert_ch]: - valid += 1 - - while valid == len(need): - if right - left < size: - start = left - size = right - left - remove_ch = s[left] - left += 1 - if remove_ch in need: - if window[remove_ch] == need[remove_ch]: - valid -= 1 - window[remove_ch] -= 1 - if size == len(s) + 1: - return '' - return s[start:start + size] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 018. \346\234\211\346\225\210\347\232\204\345\233\236\346\226\207.md" "b/Solutions/\345\211\221\346\214\207 Offer II 018. \346\234\211\346\225\210\347\232\204\345\233\236\346\226\207.md" deleted file mode 100644 index 539ce5e2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 018. \346\234\211\346\225\210\347\232\204\345\233\236\346\226\207.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [剑指 Offer II 018. 有效的回文](https://leetcode.cn/problems/XltzEq/) - -- 标签:双指针、字符串 -- 难度:简单 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:判断是否为回文串。(只考虑字符串中的字母和数字字符,并且忽略字母的大小写) - -## 解题思路 - -左右两个指针 `start` 和 `end`,左指针 `start` 指向字符串头部,右指针 `end` 指向字符串尾部。先过滤掉除字母和数字字符以外的字符,在判断 `s[start]` 和 `s[end]` 是否相等。不相等返回 `False`,相等则继续过滤和判断。 - -## 代码 - -```python -class Solution: - def isPalindrome(self, s: str) -> bool: - n = len(s) - start = 0 - end = n - 1 - while start < end: - if not s[start].isalnum(): - start += 1 - continue - if not s[end].isalnum(): - end -= 1 - continue - if s[start].lower() == s[end].lower(): - start += 1 - end -= 1 - else: - return False - return True -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 019. \346\234\200\345\244\232\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\276\227\345\210\260\345\233\236\346\226\207.md" "b/Solutions/\345\211\221\346\214\207 Offer II 019. \346\234\200\345\244\232\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\276\227\345\210\260\345\233\236\346\226\207.md" deleted file mode 100644 index 8f1927bd..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 019. \346\234\200\345\244\232\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\276\227\345\210\260\345\233\236\346\226\207.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [剑指 Offer II 019. 最多删除一个字符得到回文](https://leetcode.cn/problems/RQku0D/) - -- 标签:贪心、双指针、字符串 -- 难度:简单 - -## 题目大意 - -给定一个非空字符串 `s`。 - -要求:判断如果最多从字符串中删除一个字符能否得到一个回文字符串。 - -## 解题思路 - -双指针 + 贪心算法。 - -- 用两个指针 `left`、`right` 分别指向字符串的开始和结束位置。 - -- 判断 `s[left]` 是否等于 `s[right]`。 - - 如果等于,则 `left` 右移、`right`左移。 - - 如果不等于,则判断 `s[left: right - 1]` 或 `s[left + 1, right]` 是为回文串。 - - 如果是则返回 `True`。 - - 如果不是则返回 `False`,然后继续判断。 -- 如果 `right >= left`,则说明字符串 `s` 本身就是回文串,返回 `True`。 - - - -## 代码 - -```python -class Solution: - def checkPalindrome(self, s: str, left: int, right: int): - i, j = left, right - while i < j: - if s[i] != s[j]: - return False - i += 1 - j -= 1 - return True - - def validPalindrome(self, s: str) -> bool: - left, right = 0, len(s) - 1 - while left < right: - if s[left] == s[right]: - left += 1 - right -= 1 - else: - return self.checkPalindrome(s, left + 1, right) or self.checkPalindrome(s, left, right - 1) - return True -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 020. \345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\344\270\252\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer II 020. \345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index 4bd4a042..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 020. \345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [剑指 Offer II 020. 回文子字符串的个数](https://leetcode.cn/problems/a7VOhD/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`。 - -要求:计算 `s` 中有多少个回文子串。 - -## 解题思路 - -动态规划求解。 - -先定义状态 `dp[i][j]` 表示为区间 `[i, j]` 的子串是否为回文子串,如果是,则 `dp[i][j] = True`,如果不是,则 `dp[i][j] = False`。 - -接下来确定状态转移共识: - -如果 `s[i] == s[j]`,分为以下几种情况: - -- `i == j`,单字符肯定是回文子串,`dp[i][j] == True`。 -- `j - i == 1`,比如 `aa` 肯定也是回文子串,`dp[i][j] = True`。 -- 如果 `j - i > 1`,则需要看 `[i + 1, j - 1]` 区间是不是回文子串,`dp[i][j] = dp[i + 1][j - 1]`。 - -如果 `s[i] != s[j]`,那肯定不是回文子串,`dp[i][j] = False`。 - -下一步确定遍历方向。 - -由于 `dp[i][j]` 依赖于 `dp[i + 1][j - 1]`,所以我们可以从左下角向右上角遍历。 - -同时,在递推过程中记录下 `dp[i][j] == True` 的个数,即为最后结果。 - -## 代码 - -```python -class Solution: - def countSubstrings(self, s: str) -> int: - size = len(s) - dp = [[False for _ in range(size)] for _ in range(size)] - res = 0 - for i in range(size - 1, -1, -1): - for j in range(i, size): - if s[i] == s[j]: - if j - i <= 1: - dp[i][j] = True - else: - dp[i][j] = dp[i + 1][j - 1] - else: - dp[i][j] = False - if dp[i][j]: - res += 1 - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 021. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 n \344\270\252\347\273\223\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 021. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 n \344\270\252\347\273\223\347\202\271.md" deleted file mode 100644 index b76af31c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 021. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 n \344\270\252\347\273\223\347\202\271.md" +++ /dev/null @@ -1,37 +0,0 @@ -# [剑指 Offer II 021. 删除链表的倒数第 n 个结点](https://leetcode.cn/problems/SLwz0R/) - -- 标签:链表、双指针 -- 难度:中等 - -## 题目大意 - -给你一个链表的头节点 `head` 和一个整数 `n`。 - -要求:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。并且要求使用一次遍历实现。 - -## 解题思路 - -常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 - -如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 n 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 n 个节点位置。将该位置上的节点删除即可。 - -需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 - -## 代码 - -```python -class Solution: - def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: - newHead = ListNode(0, head) - fast = head - slow = newHead - while n: - fast = fast.next - n -= 1 - while fast: - fast = fast.next - slow = slow.next - slow.next = slow.next.next - return newHead.next -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 022. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 022. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\350\212\202\347\202\271.md" deleted file mode 100644 index 35a1b9a5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 022. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\350\212\202\347\202\271.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [剑指 Offer II 022. 链表中环的入口节点](https://leetcode.cn/problems/c32eOV/) - -- 标签:哈希表、链表、双指针 -- 难度:中等 - -## 题目大意 - -给定一个链表的头节点 `head`。 - -要求:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 `None`。 - -## 解题思路 - -利用两个指针,一个慢指针每次前进一步,快指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 - -如果有环,则再定义一个指针,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 - -这是因为:假设入环位置为 A,快慢指针在在 B 点相遇,则相遇时慢指针走了 a + b 步,快指针走了 $a + n(b+c) + b$ 步。 - -$2(a + b) = a + n(b + c) + b$。可以推出:$a = c + (n-1)(b + c)$。 - -我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 - -## 代码 - -```python -class Solution: - def detectCycle(self, head: ListNode) -> ListNode: - fast, slow = head, head - while True: - if not fast or not fast.next: - return None - fast = fast.next.next - slow = slow.next - if fast == slow: - break - - ans = head - while ans != slow: - ans, slow = ans.next, slow.next - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 023. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\351\207\215\345\220\210\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 023. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\351\207\215\345\220\210\350\212\202\347\202\271.md" deleted file mode 100644 index 95061981..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 023. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\351\207\215\345\220\210\350\212\202\347\202\271.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer II 023. 两个链表的第一个重合节点](https://leetcode.cn/problems/3u1WK4/) - -- 标签:哈希表、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定 `A`、`B` 两个链表。 - -要求:判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 `None`。 - -比如:链表 A 为 [4, 1, 8, 4, 5],链表 B 为 [5, 0, 1, 8, 4, 5]。则如下图所示,两个链表相交的起始节点为 8,则输出结果为 8。 - -![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) - - - -## 解题思路 - -如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 A 的长度为 m、链表 B 的长度为 n,他们的相交序列有 k 个,则相交情况可以如下如所示: - -![](https://qcdn.itcharge.cn/images/20210401113538.png) - -现在问题是如何找到 m-k 或者 n-k 的位置。 - -考虑将链表 A 的末尾拼接上链表 B,链表 B 的末尾拼接上链表 A。 - -然后使用两个指针 pA 、PB,分别从链表 A、链表 B 的头节点开始遍历,如果走到共同的节点,则返回该节点。 - -否则走到两个链表末尾,返回 None。 - -![](https://qcdn.itcharge.cn/images/20210401114100.png) - -## 代码 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: - if headA == None or headB == None: - return None - pA = headA - pB = headB - while pA != pB: - pA = pA.next if pA != None else headB - pB = pB.next if pB != None else headA - return pA -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 024. \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 024. \345\217\215\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index 95383a8f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 024. \345\217\215\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [剑指 Offer II 024. 反转链表](https://leetcode.cn/problems/UHnkqh/) - -- 标签:递归、链表 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个单链表的头节点 `head`。 - -**要求**:将其进行反转,并返回反转后的链表的头节点。 - -## 解题思路 - -### 思路 1. 迭代 - -1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 - -2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: - 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; - 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; - 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; - 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 -3. 继续执行第 2 步中的 1、2、3、4。 -4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 - -使用迭代法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111133639.png) - -### 思路 2. 递归 - -具体做法如下: - -- 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 -- 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 -- 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 -- 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 -- 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 -- 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 -- 最后返回反转后的链表头节点 `new_head`。 - -使用递归法反转链表的示意图如下所示: - -![](https://qcdn.itcharge.cn/images/20220111134246.png) - -## 代码 - -1. 迭代 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - pre = None - cur = head - while cur != None: - next = cur.next - cur.next = pre - pre = cur - cur = next - return pre -``` - -2. 递归 - -```python -class Solution: - def reverseList(self, head: ListNode) -> ListNode: - if head == None or head.next == None: - return head - new_head = self.reverseList(head.next) - head.next.next = head - head.next = None - return new_head -``` - -## 参考资料 - -- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) -- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) \ No newline at end of file diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 025. \351\223\276\350\241\250\344\270\255\347\232\204\344\270\244\346\225\260\347\233\270\345\212\240.md" "b/Solutions/\345\211\221\346\214\207 Offer II 025. \351\223\276\350\241\250\344\270\255\347\232\204\344\270\244\346\225\260\347\233\270\345\212\240.md" deleted file mode 100644 index bcedfeaf..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 025. \351\223\276\350\241\250\344\270\255\347\232\204\344\270\244\346\225\260\347\233\270\345\212\240.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer II 025. 链表中的两数相加](https://leetcode.cn/problems/lMSNwu/) - -- 标签:栈、链表、数学 -- 难度:中等 - -## 题目大意 - -给定两个非空链表的头节点 `l1` 和 `l2` 来代表两个非负整数。数字最高位位于链表开始位置。每个节点只储存一位数字。除了数字 `0` 之外,这两个链表代表的数字都不会以 `0` 开头。 - -要求:将这两个数相加会返回一个新的链表。 - -## 解题思路 - -链表中最高位位于链表开始位置,最低位位于链表结束位置。这与我们做加法的数位顺序是相反的。为了将链表逆序,从而从低位开始处理数位,我们可以借用两个栈:将链表中所有数字分别压入两个栈中,再依次取出相加。 - -同时,在相加的时候,还要考虑进位问题。具体步骤如下: - -- 将链表 `l1` 中所有节点值压入 `stack1` 栈中,再将链表 `l2` 中所有节点值压入 `stack2` 栈中。 -- 使用 `res` 存储新的结果链表,一开始指向 `None`,`carry` 记录进位。 -- 如果 `stack1` 或 `stack2` 不为空,或着进位 `carry` 不为 `0`,则: - - 从 `stack1` 中取出栈顶元素 `num1`,如果 `stack1` 为空,则 `num1 = 0`。 - - 从 `stack2` 中取出栈顶元素 `num2`,如果 `stack2` 为空,则 `num2 = 0`。 - - 计算相加结果,并计算进位。 - - 建立新节点,存储进位后余下的值,并令其指向 `res`。 - - `res` 指向新节点,继续判断。 -- 如果 `stack1`、`stack2` 都为空,并且进位 `carry` 为 `0`,则输出 `res`。 - -## 代码 - -```python -class Solution: - def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: - stack1, stack2 = [], [] - while l1: - stack1.append(l1.val) - l1 = l1.next - while l2: - stack2.append(l2.val) - l2 = l2.next - - res = None - carry = 0 - while stack1 or stack2 or carry != 0: - num1 = stack1.pop() if stack1 else 0 - num2 = stack2.pop() if stack2 else 0 - cur_sum = num1 + num2 + carry - carry = cur_sum // 10 - cur_sum %= 10 - cur_node = ListNode(cur_sum) - cur_node.next = res - res = cur_node - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 026. \351\207\215\346\216\222\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 026. \351\207\215\346\216\222\351\223\276\350\241\250.md" deleted file mode 100644 index fe6d08e7..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 026. \351\207\215\346\216\222\351\223\276\350\241\250.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer II 026. 重排链表](https://leetcode.cn/problems/LGjMqU/) - -- 标签:栈、递归、链表、双指针 -- 难度:中等 - -## 题目大意 - -给定一个单链表 `L` 的头节点 `head`,单链表 `L` 表示为:$L_0$ -> $L_1$ -> $L_2$ -> ... -> $L_{n-1}$ -> $L_n$。 - -要求:将单链表 `L` 重新排列为:$L_0$ -> $L_n$ -> $L_1$ -> $L_{n-1}$ -> $L_2$ -> $L_{n-2}$ -> $L_3$ -> $L_{n-3}$ -> ...。 - -注意:需要将实际节点进行交换。 - -## 解题思路 - -链表不能像数组那样直接进行随机访问。所以我们可以先将链表转为线性表。然后直接按照提要要求的排列顺序访问对应数据元素,重新建立链表。 - -## 代码 - -```python -class Solution: - def reorderList(self, head: ListNode) -> None: - """ - Do not return anything, modify head in-place instead. - """ - if not head: - return - - vec = [] - node = head - while node: - vec.append(node) - node = node.next - - left, right = 0, len(vec) - 1 - while left < right: - vec[left].next = vec[right] - left += 1 - if left == right: - break - vec[right].next = vec[left] - right -= 1 - vec[left].next = None -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 027. \345\233\236\346\226\207\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 027. \345\233\236\346\226\207\351\223\276\350\241\250.md" deleted file mode 100644 index 9454ef43..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 027. \345\233\236\346\226\207\351\223\276\350\241\250.md" +++ /dev/null @@ -1,28 +0,0 @@ -# [剑指 Offer II 027. 回文链表](https://leetcode.cn/problems/aMhZSa/) - -- 标签:栈、递归、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定一个链表的头节点 `head`。 - -要求:判断该链表是否为回文链表。 - -## 解题思路 - -利用数组,将链表元素依次存入。然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置,依次判断首尾对应元素是否相等,若都相等,则为回文链表。若不相等,则不是回文链表。 - -## 代码 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - nodes = [] - p1 = head - while p1 != None: - nodes.append(p1.val) - p1 = p1.next - return nodes == nodes[::-1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 028. \345\261\225\345\271\263\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 028. \345\261\225\345\271\263\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" deleted file mode 100644 index b29580b1..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 028. \345\261\225\345\271\263\345\244\232\347\272\247\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [剑指 Offer II 028. 展平多级双向链表](https://leetcode.cn/problems/Qv1Da2/) - -- 标签:深度优先搜索、链表、双向链表 -- 难度:中等 - -## 题目大意 - -给定一个带子链表指针 `child` 的双向链表。 - -要求:将 `child` 的子链表进行扁平化处理,使所有节点出现在单级双向链表中。 - -扁平化处理如下: - -``` -原链表: -1---2---3---4---5---6--NULL - | - 7---8---9---10--NULL - | - 11--12--NULL -扁平化之后: -1---2---3---7---8---11---12---9---10---4---5---6--NULL -``` - -## 解题思路 - -递归处理多层链表的扁平化。遍历链表,找到 `child` 非空的节点, 将其子链表链接到当前节点的 `next` 位置(自身扁平化处理)。然后继续向后遍历,不断找到 `child` 节点,并进行链接。直到处理到尾部位置。 - -## 代码 - -```python -class Solution: - def dfs(self, node: 'Node'): - # 找到链表的尾节点或 child 链表不为空的节点 - while node.next and not node.child: - node = node.next - tail = None - if node.child: - # 如果 child 链表不为空,将 child 链表扁平化 - tail = self.dfs(node.child) - - # 将扁平化的 child 链表链接在该节点之后 - temp = node.next - node.next = node.child - node.next.prev = node - node.child = None - tail.next = temp - if temp: - temp.prev = tail - # 链接之后,从 child 链表的尾节点继续向后处理链表 - return self.dfs(tail) - # child 链表为空,则该节点是尾节点,直接返回 - return node - - def flatten(self, head: 'Node') -> 'Node': - if not head: - return head - self.dfs(head) - return head -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 029. \346\216\222\345\272\217\347\232\204\345\276\252\347\216\257\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 029. \346\216\222\345\272\217\347\232\204\345\276\252\347\216\257\351\223\276\350\241\250.md" deleted file mode 100644 index ea2a7dea..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 029. \346\216\222\345\272\217\347\232\204\345\276\252\347\216\257\351\223\276\350\241\250.md" +++ /dev/null @@ -1,49 +0,0 @@ -# [剑指 Offer II 029. 排序的循环链表](https://leetcode.cn/problems/4ueAj6/) - -- 标签:链表 -- 难度:中等 - -## 题目大意 - -给定循环升序链表中的一个节点 `head` 和一个整数 `insertVal`。 - -要求:将整数 `insertVal` 插入循环升序链表中,并且满足链表仍为循环升序链表。最终返回原先给定的节点。 - -## 解题思路 - -- 先判断所给节点 `head` 是否为空,为空直接创建一个值为 `insertVal` 的新节点,并指向自己,返回即可。 - -- 如果 `head` 不为空,把 `head` 赋值给 `node` ,方便最后返回原节点 `head`。 -- 然后遍历 `node`,判断插入值 `insertVal` 与 `node.val` 和 `node.next.val` 的关系,找到插入位置,具体判断如下: - - 如果新节点值在两个节点值中间, 即 `node.val <= insertVal <= node.next.val`。则说明新节点值在最大值最小值中间,应将新节点插入到当前位置,则应将 `insertVal` 插入到这个位置。 - - 如果新节点值比当前节点值和当前节点下一节点值都大,并且当前节点值比当前节点值的下一节点值大,即 `node.next.val < node.val <= insertVal`,则说明 `insertVal` 比链表最大值都大,应插入最大值后边。 - - 如果新节点值比当前节点值和当前节点下一节点值都小,并且当前节点值比当前节点值的下一节点值大,即 `insertVal < node.next.val < node.val`,则说明 `insertVal` 比链表中最小值都小,应插入最小值前边。 -- 找到插入位置后,跳出循环,在插入位置插入值为 `insertVal` 的新节点。 - -## 代码 - -```python -class Solution: - def insert(self, head: 'Node', insertVal: int) -> 'Node': - if not head: - node = Node(insertVal) - node.next = node - return node - - node = head - while node.next != head: - if node.val <= insertVal <= node.next.val: - break - elif node.next.val < node.val <= insertVal: - break - elif insertVal < node.next.val < node.val: - break - else: - node = node.next - - insert_node = Node(insertVal) - insert_node.next = node.next - node.next = insert_node - return head -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 030. \346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\351\232\217\346\234\272\350\256\277\351\227\256\351\203\275\346\230\257 O(1) \347\232\204\345\256\271\345\231\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 030. \346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\351\232\217\346\234\272\350\256\277\351\227\256\351\203\275\346\230\257 O(1) \347\232\204\345\256\271\345\231\250.md" deleted file mode 100644 index 28a9c053..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 030. \346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\351\232\217\346\234\272\350\256\277\351\227\256\351\203\275\346\230\257 O(1) \347\232\204\345\256\271\345\231\250.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器](https://leetcode.cn/problems/FortPu/) - -- 标签:设计、数组、哈希表、数学、随机化 -- 难度:中等 - -## 题目大意 - -设计一个数据结构 ,支持时间复杂度为 $O(1)$ 的以下操作: - -- `insert(val)`:当元素 val 不存在时,向集合中插入该项。 -- `remove(val)`:元素 val 存在时,从集合中移除该项。 -- `getRandom`:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 - -## 解题思路 - -普通动态数组进行访问操作,需要线性时间查找解决。我们可以利用哈希表记录下每个元素的下标,这样在访问时可以做到常数时间内访问元素了。对应的插入、删除、后去随机元素需要做相应的变化。 - -- 插入操作:将元素直接插入到数组尾部,并用哈希表记录插入元素的下标位置。 -- 删除操作:使用哈希表找到待删除元素所在位置,将其与数组末尾位置元素相互交换,更新哈希表中交换后元素的下标值,并将末尾元素删除。 -- 获取随机元素:使用` random.choice` 获取。 - -## 代码 - -```python -import random - -class RandomizedSet: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.dict = dict() - self.list = list() - - - def insert(self, val: int) -> bool: - """ - Inserts a value to the set. Returns true if the set did not already contain the specified element. - """ - if val in self.dict: - return False - self.dict[val] = len(self.list) - self.list.append(val) - return True - - - def remove(self, val: int) -> bool: - """ - Removes a value from the set. Returns true if the set contained the specified element. - """ - if val in self.dict: - idx = self.dict[val] - last = self.list[-1] - self.list[idx] = last - self.dict[last] = idx - self.list.pop() - self.dict.pop(val) - return True - return False - - - def getRandom(self) -> int: - """ - Get a random element from the set. - """ - return random.choice(self.list) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 031. \346\234\200\350\277\221\346\234\200\345\260\221\344\275\277\347\224\250\347\274\223\345\255\230.md" "b/Solutions/\345\211\221\346\214\207 Offer II 031. \346\234\200\350\277\221\346\234\200\345\260\221\344\275\277\347\224\250\347\274\223\345\255\230.md" deleted file mode 100644 index ee3a8480..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 031. \346\234\200\350\277\221\346\234\200\345\260\221\344\275\277\347\224\250\347\274\223\345\255\230.md" +++ /dev/null @@ -1,79 +0,0 @@ -# [剑指 Offer II 031. 最近最少使用缓存](https://leetcode.cn/problems/OrIXps/) - -- 标签:设计、哈希表、链表、双向链表 -- 难度:中等 - -## 题目大意 - -要求:实现一个 `LRU(最近最少使用)缓存机制`,并且在 `O(1)` 时间复杂度内完成 `get`、`put` 操作。 - -实现 `LRUCache` 类: - -- `LRUCache(int capacity)` 以正整数作为容量 `capacity` 初始化 LRU 缓存。 -- `int get(int key)` 如果关键字 `key` 存在于缓存中,则返回关键字的值,否则返回 `-1`。 -- `void put(int key, int value)` 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 - -## 解题思路 - -LRU(最近最少使用缓存)是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。LRU 更新和插入新页面都发生在链表首,删除页面都发生在链表尾。 - -## 代码 - -```python -class Node: - def __init__(self, key=None, val=None, prev=None, next=None): - self.key = key - self.val = val - self.prev = prev - self.next = next - -class LRUCache: - - def __init__(self, capacity: int): - self.capacity = capacity - self.hashmap = dict() - self.head = Node() - self.tail = Node() - self.head.next = self.tail - self.tail.prev = self.head - - - def get(self, key: int) -> int: - if key not in self.hashmap: - return -1 - node = self.hashmap[key] - self.move_node(node) - return node.val - - - def put(self, key: int, value: int) -> None: - if key in self.hashmap: - node = self.hashmap[key] - node.val = value - self.move_node(node) - return - if len(self.hashmap) == self.capacity: - self.hashmap.pop(self.head.next.key) - self.remove_node(self.head.next) - - node = Node(key=key, val=value) - self.hashmap[key] = node - self.add_node(node) - - def remove_node(self, node): - node.prev.next = node.next - node.next.prev = node.prev - - - def add_node(self, node): - self.tail.prev.next = node - node.prev = self.tail.prev - node.next = self.tail - self.tail.prev = node - - - def move_node(self, node): - self.remove_node(node) - self.add_node(node) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 032. \346\234\211\346\225\210\347\232\204\345\217\230\344\275\215\350\257\215.md" "b/Solutions/\345\211\221\346\214\207 Offer II 032. \346\234\211\346\225\210\347\232\204\345\217\230\344\275\215\350\257\215.md" deleted file mode 100644 index 7a31073b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 032. \346\234\211\346\225\210\347\232\204\345\217\230\344\275\215\350\257\215.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer II 032. 有效的变位词](https://leetcode.cn/problems/dKk3P7/) - -- 标签:哈希表、字符串、排序 -- 难度:简单 - -## 题目大意 - -给定两个字符串 `s` 和 `t`。 - -要求:判断 `t` 和 `s` 是否使用了相同的字符构成(字符出现的种类和数目都相同,字符顺序不完全相同)。 - -## 解题思路 - -1. 先判断字符串 `s` 和 `t` 的长度,不一样直接返回 `False`; -2. 如果 `s` 和 `t` 相等,则直接返回 `False`,因为变位词的字符顺序不完全相同; -3. 分别遍历字符串 `s` 和 `t`。先遍历字符串 `s`,用哈希表存储字符串 `s` 中字符出现的频次; -4. 再遍历字符串 `t`,哈希表中减去对应字符的频次,出现频次小于 `0` 则输出 `False`; -5. 如果没出现频次小于 `0`,则输出 `True`。 - -## 代码 - -```python -class Solution: - def isAnagram(self, s: str, t: str) -> bool: - if len(s) != len(t) or s == t: - return False - strDict = dict() - for ch in s: - if ch in strDict: - strDict[ch] += 1 - else: - strDict[ch] = 1 - for ch in t: - if ch in strDict: - strDict[ch] -= 1 - if strDict[ch] < 0: - return False - else: - return False - return True -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 033. \345\217\230\344\275\215\350\257\215\347\273\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 033. \345\217\230\344\275\215\350\257\215\347\273\204.md" deleted file mode 100644 index fe5bc2b9..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 033. \345\217\230\344\275\215\350\257\215\347\273\204.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [剑指 Offer II 033. 变位词组](https://leetcode.cn/problems/sfvd7V/) - -- 标签:数组、哈希表、字符串、排序 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `strs`。 - -要求:将包含字母相同的字符串组合在一起,不需要考虑输出顺序。 - -## 解题思路 - -使用哈希表记录字母相同的字符串。对每一个字符串进行排序,按照 排序字符串:字母相同的字符串数组 的键值顺序进行存储。最终将哈希表的值转换为对应数组返回结果。 - -## 代码 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - str_dict = dict() - res = [] - for s in strs: - sort_s = str(sorted(s)) - if sort_s in str_dict: - str_dict[sort_s] += [s] - else: - str_dict[sort_s] = [s] - - for sort_s in str_dict: - res += [str_dict[sort_s]] - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 034. \345\244\226\346\230\237\350\257\255\350\250\200\346\230\257\345\220\246\346\216\222\345\272\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 034. \345\244\226\346\230\237\350\257\255\350\250\200\346\230\257\345\220\246\346\216\222\345\272\217.md" deleted file mode 100644 index 30e11640..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 034. \345\244\226\346\230\237\350\257\255\350\250\200\346\230\257\345\220\246\346\216\222\345\272\217.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [剑指 Offer II 034. 外星语言是否排序](https://leetcode.cn/problems/lwyVBB/) - -- 标签:数组、哈希表、字符串 -- 难度:简单 - -## 题目大意 - -给定一组用外星语书写的单词字符串数组 `words`,以及表示外星字母表的顺序的字符串 `order` 。 - -要求:判断 `words` 中的单词是否都是按照 `order` 来排序的。如果是,则返回 `True`,否则返回 `False`。 - -## 解题思路 - -如果所有单词是按照 `order` 的规则升序排列,则所有单词都符合规则。而判断所有单词是升序排列,只需要两两比较相邻的单词即可。所以我们可以先用哈希表存储所有字母的顺序,然后对所有相邻单词进行两两比较,如果最终是升序排列,则符合要求。具体步骤如下: - -- 使用哈希表 `order_map` 存储字母的顺序。 -- 遍历单词数组 `words`,比较相邻单词 `word1` 和 `word2` 中所有字母在 `order_map` 中的下标,看是否满足 `word1 <= word2`。 -- 如果全部满足,则返回 `True`。如果有不满足的情况,则直接返回 `False`。 - -## 代码 - -```python -class Solution: - def isAlienSorted(self, words: List[str], order: str) -> bool: - order_map = dict() - for i in range(len(order)): - order_map[order[i]] = i - for i in range(len(words) - 1): - word1 = words[i] - word2 = words[i + 1] - - flag = True - - for j in range(min(len(word1), len(word2))): - if word1[j] != word2[j]: - if order_map[word1[j]] > order_map[word2[j]]: - return False - else: - flag = False - break - - if flag and len(word1) > len(word2): - return False - return True -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 035. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 035. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" deleted file mode 100644 index 733f42d5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 035. \346\234\200\345\260\217\346\227\266\351\227\264\345\267\256.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [剑指 Offer II 035. 最小时间差](https://leetcode.cn/problems/569nqc/) - -- 标签:数组、数学、字符串、排序 -- 难度:中等 - -## 题目大意 - -给定一个 24 小时制形式(小时:分钟 "HH:MM")的时间列表 `timePoints`。 - -要求:找出列表中任意两个时间的最小时间差并以分钟数表示。 - -## 解题思路 - -- 遍历时间列表 `timePoints`,将每个时间转换为以分钟计算的整数形式,比如时间 `14:20`,将其转换为 `14 * 60 + 20 = 860`,存放到新的时间列表 `times` 中。 -- 为了处理最早时间、最晚时间之间的时间间隔,我们将 `times` 中最小时间添加到列表末尾一起进行排序。 -- 然后将新的时间列表 `times` 按照升序排列。 -- 遍历排好序的事件列表 `times` ,找出相邻两个时间的最小间隔值即可。 - -## 代码 - -```python -class Solution: - def changeTime(self, timePoint: str): - hours, minutes = timePoint.split(':') - return int(hours) * 60 + int(minutes) - - def findMinDifference(self, timePoints: List[str]) -> int: - if not timePoints or len(timePoints) > 24 * 60: - return 0 - - times = sorted(self.changeTime(time) for time in timePoints) - times.append(times[0] + 24 * 60) - res = times[-1] - for i in range(1, len(times)): - res = min(res, times[i] - times[i - 1]) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 036. \345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 036. \345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.md" deleted file mode 100644 index d7e9dea8..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 036. \345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [剑指 Offer II 036. 后缀表达式](https://leetcode.cn/problems/8Zf90G/) - -- 标签:栈、数组、数学 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `tokens`,表示「逆波兰表达式」,求解表达式的值。 - -## 解题思路 - -栈的典型应用。遍历字符串数组。遇到操作字符的时候,取出栈顶两个元素,进行运算之后,再将结果入栈。遇到数字,则直接入栈。 - -## 代码 - -```python -class Solution: - def evalRPN(self, tokens: List[str]) -> int: - stack = [] - for token in tokens: - if token == '+': - stack.append(stack.pop() + stack.pop()) - elif token == '-': - stack.append(-stack.pop() + stack.pop()) - elif token == '*': - stack.append(stack.pop() * stack.pop()) - elif token == '/': - stack.append(int(1 / stack.pop() * stack.pop())) - else: - stack.append(int(token)) - return stack.pop() -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 037. \345\260\217\350\241\214\346\230\237\347\242\260\346\222\236.md" "b/Solutions/\345\211\221\346\214\207 Offer II 037. \345\260\217\350\241\214\346\230\237\347\242\260\346\222\236.md" deleted file mode 100644 index a4a2966e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 037. \345\260\217\350\241\214\346\230\237\347\242\260\346\222\236.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 037. 小行星碰撞](https://leetcode.cn/problems/XagZNi/) - -- 标签:栈、数组 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `asteroids`,表示在同一行的小行星。 - -数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。小行星按照下面的规则发生碰撞。 - -- 碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 - -要求:找出碰撞后剩下的所有小行星,将答案存入数组并返回。 - -## 解题思路 - -用栈模拟小行星碰撞,具体步骤如下: - -- 遍历数组 `asteroids`。 -- 如果栈为空或者当前元素 `asteroid` 为正数,将其压入栈。 -- 如果当前栈不为空并且当前元素 `asteroid` 为负数: - - 与栈中元素发生碰撞,判断当前元素和栈顶元素的大小和方向,如果栈顶元素为正数,并且当前元素的绝对值大于栈顶元素,则将栈顶元素弹出,并继续与栈中元素发生碰撞。 - - 碰撞完之后,如果栈为空并且栈顶元素为负数,则将当前元素 `asteroid` 压入栈,表示碰撞完剩下了 `asteroid`。 - - 如果栈顶元素恰好与当前元素值大小相等、方向相反,则弹出栈顶元素,表示碰撞完两者都爆炸了。 -- 最后返回栈作为答案。 - -## 代码 - -```python -class Solution: - def asteroidCollision(self, asteroids: List[int]) -> List[int]: - stack = [] - for asteroid in asteroids: - if not stack or asteroid > 0: - stack.append(asteroid) - else: - while stack and 0 < stack[-1] < -asteroid: - stack.pop() - if not stack or stack[-1] < 0: - stack.append(asteroid) - elif stack[-1] == -asteroid: - stack.pop() - - return stack -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 038. \346\257\217\346\227\245\346\270\251\345\272\246.md" "b/Solutions/\345\211\221\346\214\207 Offer II 038. \346\257\217\346\227\245\346\270\251\345\272\246.md" deleted file mode 100644 index 9c754a3a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 038. \346\257\217\346\227\245\346\270\251\345\272\246.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [剑指 Offer II 038. 每日温度](https://leetcode.cn/problems/iIQa4I/) - -- 标签:栈、数组、单调栈 -- 难度:中等 - -## 题目大意 - -给定一个列表 `temperatures`,每一个位置对应每天的气温。要求输出一个列表,列表上每个位置代表如果要观测到更高的气温,至少需要等待的天数。如果之后的气温不再升高,则用 `0` 来代替。 - -## 解题思路 - -题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置后侧找到第一个比当前值更高的值。求该点与该位置的距离,将所有距离保存为数组返回结果。 - -很简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 - -更好的方式使用「递减栈」。栈中保存元素的下标。 - -首先,将答案数组全部赋值为 0。然后遍历数组每个位置元素。 - -- 如果栈为空,则将当前元素的下标入栈。 -- 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 - - 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组中保存起来,判断栈顶元素。 -- 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 - -## 代码 - -```python -class Solution: - def dailyTemperatures(self, temperatures: List[int]) -> List[int]: - n = len(temperatures) - stack = [] - ans = [0 for _ in range(n)] - for i in range(n): - while stack and temperatures[i] > temperatures[stack[-1]]: - index = stack.pop() - ans[index] = (i - index) - stack.append(i) - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 039. \347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\351\235\242\347\247\257.md" "b/Solutions/\345\211\221\346\214\207 Offer II 039. \347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\351\235\242\347\247\257.md" deleted file mode 100644 index 376e7c05..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 039. \347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\351\235\242\347\247\257.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer II 039. 直方图最大矩形面积](https://leetcode.cn/problems/0ynMMM/) - -- 标签:栈、数组、单调栈 -- 难度:困难 - -## 题目大意 - -给定一个非负整数数组 `heights` ,`heights[i]` 用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 - -要求:计算出在该柱状图中,能够勾勒出来的矩形的最大面积。 - -## 解题思路 - -思路一:枚举「宽度」。一重循环枚举所有柱子,第二重循环遍历柱子右侧的柱子,所得的宽度就是两根柱子形成区间的宽度,高度就是这段区间中的最小高度。然后计算出对应面积,记录并更新最大面积。这样下来,时间复杂度为 $O(n^2)$。 - -思路二:枚举「高度」。一重循环枚举所有柱子,以柱子高度为当前矩形高度,然后向两侧延伸,遇到小于当前矩形高度的情况就停止。然后计算当前矩形面积,记录并更新最大面积。这样下来,时间复杂度也是 $O(n^2)$。 - -思路三:利用「单调栈」减少两侧延伸的复杂度。 - -- 枚举所有柱子。 -- 如果当前柱子高度较大,大于等于栈顶柱体的高度,则直接将当前柱体入栈。 -- 如果当前柱体高度较小,小于栈顶柱体的高度,则一直出栈,直到当前柱体大于等于栈顶柱体高度。 - - 出栈后,说明当前柱体是出栈柱体向右找到的第一个小于当前柱体高度的柱体,那么就可以向右将宽度扩展到当前柱体。 - - 出栈后,说明新的栈顶柱体是出栈柱体向左找到的第一个小于新的栈顶柱体高度的柱体,那么就可以向左将宽度扩展到新的栈顶柱体。 - - 以新的栈顶柱体为左边界,当前柱体为右边界,以出栈柱体为高度。计算矩形面积,然后记录并更新最大面积。 - -## 代码 - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - heights.append(0) - ans = 0 - stack = [] - for i in range(len(heights)): - while stack and heights[stack[-1]] >= heights[i]: - cur = stack.pop(-1) - left = stack[-1] + 1 if stack else 0 - right = i - 1 - ans = max(ans, (right - left + 1) * heights[cur]) - stack.append(i) - - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 041. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\345\271\263\345\235\207\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer II 041. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\345\271\263\345\235\207\345\200\274.md" deleted file mode 100644 index 27487565..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 041. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\345\271\263\345\235\207\345\200\274.md" +++ /dev/null @@ -1,78 +0,0 @@ -# [剑指 Offer II 041. 滑动窗口的平均值](https://leetcode.cn/problems/qIsx9U/) - -- 标签:设计、队列、数组、数据流 -- 难度:简单 - -## 题目大意 - -**描述**:给定一个整数数据流和一个窗口大小 `size`。 - -**要求**:根据滑动窗口的大小,计算滑动窗口里所有数字的平均值。要求实现 `MovingAverage` 类: - -- `MovingAverage(int size)`:用窗口大小 `size` 初始化对象。 -- `double next(int val)`:成员函数 `next` 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 `size` 个值的移动平均值,即滑动窗口里所有数字的平均值。 - -**说明**: - -- $1 \le size \le 1000$。 -- $-10^5 \le val \le 10^5$。 -- 最多调用 `next` 方法 $10^4$ 次。 - -**示例**: - -- 示例 1: - -```python -输入: -inputs = ["MovingAverage", "next", "next", "next", "next"] -inputs = [[3], [1], [10], [3], [5]] -输出: -[null, 1.0, 5.5, 4.66667, 6.0] - -解释: -MovingAverage movingAverage = new MovingAverage(3); -movingAverage.next(1); // 返回 1.0 = 1 / 1 -movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2 -movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3 -movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3 -``` - -## 解题思路 - -### 思路 1:队列 - -1. 使用队列保存滑动窗口的元素,并记录对应窗口大小和元素和。 -2. 当队列长度小于窗口大小的时候,直接向队列中添加元素,并记录当前窗口中的元素和。 -3. 当队列长度等于窗口大小的时候,先将队列头部元素弹出,再添加元素,并记录当前窗口中的元素和。 -4. 然后根据元素和和队列中元素个数计算出平均值。 - -### 思路 1:代码 - -```python -class MovingAverage: - - def __init__(self, size: int): - """ - Initialize your data structure here. - """ - self.queue = [] - self.size = size - self.sum = 0 - - - def next(self, val: int) -> float: - if len(self.queue) < self.size: - self.queue.append(val) - else: - if self.queue: - self.sum -= self.queue[0] - self.queue.pop(0) - self.queue.append(val) - self.sum += val - return self.sum / len(self.queue) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(1)$。初始化方法和每次调用 `next` 方法的时间复杂度都是 $O(1)$。 -- **空间复杂度**:$O(size)$。其中 $size$ 就是给定的滑动窗口的大小。 \ No newline at end of file diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 042. \346\234\200\350\277\221\350\257\267\346\261\202\346\254\241\346\225\260.md" "b/Solutions/\345\211\221\346\214\207 Offer II 042. \346\234\200\350\277\221\350\257\267\346\261\202\346\254\241\346\225\260.md" deleted file mode 100644 index 8f114bd4..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 042. \346\234\200\350\277\221\350\257\267\346\261\202\346\254\241\346\225\260.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [剑指 Offer II 042. 最近请求次数](https://leetcode.cn/problems/H8086Q/) - -- 标签:设计、队列、数据流 -- 难度:简单 - -## 题目大意 - -要求:实现一个用来计算特定时间范围内的最近请求的 `RecentCounter` 类: - -- `RecentCounter()` 初始化计数器,请求数为 0 。 -- `int ping(int t)` 在时间 `t` 时添加一个新请求,其中 `t` 表示以毫秒为单位的某个时间,并返回在 `[t-3000, t]` 内发生的请求数。 - -## 解题思路 - -使用一个队列,用于存储 `[t - 3000, t]` 范围内的请求。 - -获取请求数时,将队首所有小于 `t - 3000` 时间的请求将其从队列中移除,然后返回队列的长度即可。 - -## 代码 - -```python -class RecentCounter: - - def __init__(self): - self.queue = [] - - - def ping(self, t: int) -> int: - self.queue.append(t) - while self.queue[0] < t - 3000: - self.queue.pop(0) - return len(self.queue) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 043. \345\276\200\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\267\273\345\212\240\350\212\202\347\202\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 043. \345\276\200\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\267\273\345\212\240\350\212\202\347\202\271.md" deleted file mode 100644 index b20bec27..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 043. \345\276\200\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\267\273\345\212\240\350\212\202\347\202\271.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [剑指 Offer II 043. 往完全二叉树添加节点](https://leetcode.cn/problems/NaqhDT/) - -- 标签:树、广度优先搜索、设计、二叉树 -- 难度:中等 - -## 题目大意 - -要求:设计一个用完全二叉树初始化的数据结构 `CBTInserter`,并支持以下几种操作: - -- `CBTInserter(TreeNode root)` 使用根节点为 `root` 的给定树初始化该数据结构; -- `CBTInserter.insert(int v)` 向树中插入一个新节点,节点类型为 `TreeNode`,值为 `v`。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值; -- `CBTInserter.get_root()` 返回树的根节点。 - -## 解题思路 - -使用数组标记完全二叉树中节点的序号,初始化数组为 `[None]`。完全二叉树中节点的序号从 `1` 开始,对于序号为 `k` 的节点,其左子节点序号为 `2k`,右子节点的序号为 `2k + 1`,其父节点的序号为 `k // 2`。 - -然后在初始化和插入节点的同时,按顺序向数组中插入节点。 - -## 代码 - -```python -class CBTInserter: - - def __init__(self, root: TreeNode): - self.queue = [root] - self.nodelist = [None] - - while self.queue: - node = self.queue.pop(0) - self.nodelist.append(node) - if node.left: - self.queue.append(node.left) - if node.right: - self.queue.append(node.right) - - - def insert(self, v: int) -> int: - self.nodelist.append(TreeNode(v)) - index = len(self.nodelist) - 1 - father = self.nodelist[index // 2] - if index % 2 == 0: - father.left = self.nodelist[-1] - else: - father.right = self.nodelist[-1] - return father.val - - - def get_root(self) -> TreeNode: - return self.nodelist[1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 044. \344\272\214\345\217\211\346\240\221\346\257\217\345\261\202\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer II 044. \344\272\214\345\217\211\346\240\221\346\257\217\345\261\202\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 4a650447..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 044. \344\272\214\345\217\211\346\240\221\346\257\217\345\261\202\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [剑指 Offer II 044. 二叉树每层的最大值](https://leetcode.cn/problems/hPov7L/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:找出二叉树中每一层的最大值。 - -## 解题思路 - -利用队列进行层序遍历,并记录下每一层的最大值,将其存入答案数组中。 - -## 代码 - -```python -class Solution: - def largestValues(self, root: TreeNode) -> List[int]: - queue = [] - res = [] - if root: - queue.append(root) - while queue: - max_level = float('-inf') - size_level = len(queue) - for i in range(size_level): - node = queue.pop(0) - max_level = max(max_level, node.val) - if node.left: - queue.append(node.left) - if node.right: - queue.append(node.right) - res.append(max_level) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 045. \344\272\214\345\217\211\346\240\221\346\234\200\345\272\225\345\261\202\346\234\200\345\267\246\350\276\271\347\232\204\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer II 045. \344\272\214\345\217\211\346\240\221\346\234\200\345\272\225\345\261\202\346\234\200\345\267\246\350\276\271\347\232\204\345\200\274.md" deleted file mode 100644 index 1009a128..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 045. \344\272\214\345\217\211\346\240\221\346\234\200\345\272\225\345\261\202\346\234\200\345\267\246\350\276\271\347\232\204\345\200\274.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer II 045. 二叉树最底层最左边的值](https://leetcode.cn/problems/LwUNpT/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:找出该二叉树 「最底层」的「最左边」节点的值。 - -## 解题思路 - -这个问题拆开来看,一是如何找到「最底层」,而是在「最底层」如何找到最左边的节点。 - -通过层序遍历,我们可以直接确定最底层节点。而「最底层」的「最左边」节点可以改变层序遍历的左右节点访问顺序。 - -每层元素先访问右节点,在访问左节点,则最后一个遍历的元素就是「最底层」的「最左边」节点,即左下角的节点,返回该点对应的值即可。 - -## 代码 - -```python -import collections - -class Solution: - def findBottomLeftValue(self, root: TreeNode) -> int: - if not root: - return -1 - queue = collections.deque() - queue.append(root) - while queue: - cur = queue.popleft() - if cur.right: - queue.append(cur.right) - if cur.left: - queue.append(cur.left) - return cur.val -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 046. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\344\276\247\350\247\206\345\233\276.md" "b/Solutions/\345\211\221\346\214\207 Offer II 046. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\344\276\247\350\247\206\345\233\276.md" deleted file mode 100644 index cdb1d950..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 046. \344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\344\276\247\350\247\206\345\233\276.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer II 046. 二叉树的右侧视图](https://leetcode.cn/problems/WNC0Lk/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`。 - -要求:按照从顶部到底部的顺序,返回从右侧能看到的节点值。 - -## 解题思路 - -二叉树的层次遍历,不过遍历每层节点的时候,只需要将最后一个节点加入结果数组即可。 - -## 代码 - -```python -class Solution: - def rightSideView(self, root: TreeNode) -> List[int]: - if not root: - return [] - queue = [root] - order = [] - while queue: - level = [] - size = len(queue) - for i in range(size): - curr = queue.pop(0) - level.append(curr.val) - if curr.left: - queue.append(curr.left) - if curr.right: - queue.append(curr.right) - if i == size - 1: - order.append(curr.val) - return order -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 047. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" "b/Solutions/\345\211\221\346\214\207 Offer II 047. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" deleted file mode 100644 index 2b08f5d3..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 047. \344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [剑指 Offer II 047. 二叉树剪枝](https://leetcode.cn/problems/pOCWxh/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉树的根节点 `root`,树的每个节点值要么是 `0`,要么是 `1`。 - -要求:剪除该二叉树中所有节点值为 `0` 的子树。 - -- 节点 `node` 的子树为: `node` 本身,以及所有 `node` 的后代。 - -## 解题思路 - -定义辅助方法 `containsOnlyZero(root)` 递归判断以 `root` 为根的子树中是否只包含 `0`。如果子树中只包含 `0`,则返回 `True`。如果子树中含有 `1`,则返回 `False`。当 `root` 为空时,也返回 `True`。 - -然后递归遍历二叉树,判断当前节点 `root` 是否只包含 `0`。如果只包含 `0`,则将其置空,返回 `None`。否则递归遍历左右子树,并设置对应的左右指针。 - -最后返回根节点 `root`。 - -## 代码 - -```python -class Solution: - def containsOnlyZero(self, root: TreeNode): - if not root: - return True - if root.val == 1: - return False - return self.containsOnlyZero(root.left) and self.containsOnlyZero(root.right) - - def pruneTree(self, root: TreeNode) -> TreeNode: - if not root: - return root - if self.containsOnlyZero(root): - return None - - root.left = self.pruneTree(root.left) - root.right = self.pruneTree(root.right) - return root -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 048. \345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer II 048. \345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 34b6e031..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 048. \345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [剑指 Offer II 048. 序列化与反序列化二叉树](https://leetcode.cn/problems/h54YBf/) - -- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 -- 难度:困难 - -## 题目大意 - -要求:设计一个算法,来实现二叉树的序列化与反序列化。 - -## 解题思路 - -### 1. 序列化:将二叉树转为字符串数据表示 - -按照前序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 - -注意:如果遇到空节点,则标记为 'None',这样在反序列化时才能唯一确定一棵二叉树。 - -### 2. 反序列化:将字符串数据转为二叉树结构 - -先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 - -- 从数组左侧取出一个元素。 - - 如果当前元素为 'None',则返回 None。 - - 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 - - 最后返回当前根节点。 - -## 代码 - -```python -class Codec: - - def serialize(self, root): - """Encodes a tree to a single string. - - :type root: TreeNode - :rtype: str - """ - if not root: - return 'None' - return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) - - - - def deserialize(self, data): - """Decodes your encoded data to tree. - - :type data: str - :rtype: TreeNode - """ - - def dfs(datalist): - val = datalist.pop(0) - if val == 'None': - return None - root = TreeNode(int(val)) - root.left = dfs(datalist) - root.right = dfs(datalist) - return root - - datalist = data.split(',') - return dfs(datalist) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 049. \344\273\216\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\347\232\204\350\267\257\345\276\204\346\225\260\345\255\227\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 049. \344\273\216\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\347\232\204\350\267\257\345\276\204\346\225\260\345\255\227\344\271\213\345\222\214.md" deleted file mode 100644 index 080a965c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 049. \344\273\216\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\347\232\204\350\267\257\345\276\204\346\225\260\345\255\227\344\271\213\345\222\214.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [剑指 Offer II 049. 从根节点到叶节点的路径数字之和](https://leetcode.cn/problems/3Etpl5/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`,树中每个节点都存放有一个 `0` 到 `9` 之间的数字。每条从根节点到叶节点的路径都代表一个数字。例如,从根节点到叶节点的路径是 `1` -> `2` -> `3`,表示数字 `123`。 - -要求:计算从根节点到叶节点生成的所有数字的和。 - -## 解题思路 - -使用深度优先搜索,记录下路径上所有节点构成的数字,使用 `pretotal` 保存下当前路径上构成的数字。如果遇到叶节点直接返回当前数字,否则递归遍历左右子树,并累加对应结果。 - -## 代码 - -```python -class Solution: - def dfs(self, root, pretotal): - if not root: - return 0 - total = pretotal * 10 + root.val - if not root.left and not root.right: - return total - return self.dfs(root.left, total) + self.dfs(root.right, total) - - def sumNumbers(self, root: TreeNode) -> int: - return self.dfs(root, 0) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 050. \345\220\221\344\270\213\347\232\204\350\267\257\345\276\204\350\212\202\347\202\271\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 050. \345\220\221\344\270\213\347\232\204\350\267\257\345\276\204\350\212\202\347\202\271\344\271\213\345\222\214.md" deleted file mode 100644 index e096af7c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 050. \345\220\221\344\270\213\347\232\204\350\267\257\345\276\204\350\212\202\347\202\271\344\271\213\345\222\214.md" +++ /dev/null @@ -1,40 +0,0 @@ -# [剑指 Offer II 050. 向下的路径节点之和](https://leetcode.cn/problems/6eUYwP/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - - - -## 解题思路 - - - -## 代码 - -```python -class Solution: - prefixsum_count = dict() - def dfs(self, root, prefixsum_count, target_sum, cur_sum): - if not root: - return 0 - res = 0 - cur_sum += root.val - res += prefixsum_count.get(cur_sum - target_sum, 0) - prefixsum_count[cur_sum] = prefixsum_count.get(cur_sum, 0) + 1 - - res += self.dfs(root.left, prefixsum_count, target_sum, cur_sum) - res += self.dfs(root.right, prefixsum_count, target_sum, cur_sum) - - prefixsum_count[cur_sum] -= 1 - return res - - def pathSum(self, root: TreeNode, targetSum: int) -> int: - if not root: - return 0 - prefixsum_count = dict() - prefixsum_count[0] = 1 - return self.dfs(root, prefixsum_count, targetSum, 0) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 051. \350\212\202\347\202\271\344\271\213\345\222\214\346\234\200\345\244\247\347\232\204\350\267\257\345\276\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 051. \350\212\202\347\202\271\344\271\213\345\222\214\346\234\200\345\244\247\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index d8d288d4..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 051. \350\212\202\347\202\271\344\271\213\345\222\214\346\234\200\345\244\247\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer II 051. 节点之和最大的路径](https://leetcode.cn/problems/jC7MId/) - -- 标签:树、深度优先搜索、动态规划、二叉树 -- 难度:困难 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:返回其最大路径和。 - -- 路径:从树中的任意节点出发,沿父节点——子节点连接,到达任意节点的序列。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 -- 路径和:路径中各节点值的总和。 - -## 解题思路 - -深度优先搜索遍历二叉树。递归的同时,维护一个最大路径和变量。定义函数 `dfs(self, root)` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 - -计算的结果可能的情况有 2 种: - -- 经过空节点的最大贡献值等于 `0`。 - -- 经过非空节点的最大贡献值等于 当前节点值 + 左右子节点的最大贡献值中较大的一个。 - -在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。 - -最终 `max_sum` 即为答案。 - -## 代码 - -```python -class Solution: - def __init__(self): - self.max_sum = float('-inf') - - def dfs(self, root): - if not root: - return 0 - left_max = max(self.dfs(root.left), 0) - right_max = max(self.dfs(root.right), 0) - self.max_sum = max(self.max_sum, root.val + left_max + right_max) - return root.val + max(left_max, right_max) - - def maxPathSum(self, root: TreeNode) -> int: - self.dfs(root) - return self.max_sum -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 052. \345\261\225\345\271\263\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer II 052. \345\261\225\345\271\263\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index 6a5f9463..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 052. \345\261\225\345\271\263\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [剑指 Offer II 052. 展平二叉搜索树](https://leetcode.cn/problems/NYBBNL/) - -- 标签:栈、树、深度优先搜索、二叉搜索树、二叉树 -- 难度:简单 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root`。 - -要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 - -## 解题思路 - -可以分为两步: - -1. 中序遍历二叉搜索树,将节点先存储到列表中。 -2. 将列表中的节点构造成一棵递增顺序搜索树。 - -中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 - -构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 - -最后返回 `head.right` 即可。 - -## 代码 - -```python -class Solution: - def inOrder(self, root, res): - if not root: - return - self.inOrder(root.left, res) - res.append(root) - self.inOrder(root.right, res) - - def increasingBST(self, root: TreeNode) -> TreeNode: - res = [] - self.inOrder(root, res) - - if not res: - return - head = TreeNode(-1) - cur = head - for node in res: - node.left = node.right = None - cur.right = node - cur = cur.right - return head.right -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 053. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" "b/Solutions/\345\211\221\346\214\207 Offer II 053. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" deleted file mode 100644 index 782ebff3..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 053. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\344\270\255\345\272\217\345\220\216\347\273\247.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer II 053. 二叉搜索树中的中序后继](https://leetcode.cn/problems/P5rCT8/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root` 和其中一个节点 `p`。 - -要求:找到该节点在树中的中序后继,即按照中序遍历的顺序节点 `p` 的下一个节点。 - -## 解题思路 - -递归遍历,具体步骤如下: - -- 如果 `root.val` 小于等于 `p.val`,则直接从 `root` 的右子树递归查找比 `p.val` 大的节点,从而找到中序后继。 -- 如果 `root.val` 大于 `p.val`,则 `root` 有可能是中序后继,也有可能是 `root` 的左子树。则从 `root` 的左子树递归查找更接近(更小的)。如果查找的值为 `None`,则当前 `root` 就是中序后继,否则继续递归查找,从而找到中序后继。 - -## 代码 - -```python -class Solution: - def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode': - if not p or not root: - return None - - if root.val <= p.val: - node = self.inorderSuccessor(root.right, p) - else: - node = self.inorderSuccessor(root.left, p) - if not node: - node = root - return node -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 054. \346\211\200\346\234\211\345\244\247\344\272\216\347\255\211\344\272\216\350\212\202\347\202\271\347\232\204\345\200\274\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 054. \346\211\200\346\234\211\345\244\247\344\272\216\347\255\211\344\272\216\350\212\202\347\202\271\347\232\204\345\200\274\344\271\213\345\222\214.md" deleted file mode 100644 index 2883ec05..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 054. \346\211\200\346\234\211\345\244\247\344\272\216\347\255\211\344\272\216\350\212\202\347\202\271\347\232\204\345\200\274\344\271\213\345\222\214.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer II 054. 所有大于等于节点的值之和](https://leetcode.cn/problems/w6cpku/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树(BST)的根节点 `root`,且二叉搜索树的节点值各不相同。要求将其转化为「累加树」,使其每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和。 - -二叉搜索树的定义: - -- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; -- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; -- 任意节点的左、右子树也分别为二叉搜索树。 - -## 解题思路 - -题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 - -题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 - -二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 - -当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 - -## 代码 - -```python -class Solution: - pre = 0 - - def createBinaryTree(self, root: TreeNode): - if not root: - return - self.createBinaryTree(root.right) - root.val += self.pre - self.pre = root.val - self.createBinaryTree(root.left) - - def convertBST(self, root: TreeNode) -> TreeNode: - self.pre = 0 - self.createBinaryTree(root) - return root -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 055. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 055. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" deleted file mode 100644 index 0b856bb0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 055. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer II 055. 二叉搜索树迭代器](https://leetcode.cn/problems/kTOapQ/) - -- 标签:栈、树、设计、二叉搜索树、二叉树、迭代器 -- 难度:中等 - -## 题目大意 - -要求:实现一个二叉搜索树的迭代器 `BSTIterator`。表示一个按中序遍历二叉搜索树(BST)的迭代器: - -- `def __init__(self, root: TreeNode):`:初始化 `BSTIterator` 类的一个对象,会给出二叉搜索树的根节点。 -- `def hasNext(self) -> bool:`:如果向右指针遍历存在数字,则返回 `True`,否则返回 `False`。 -- `def next(self) -> int:`:将指针向右移动,返回指针处的数字。 - -## 解题思路 - -中序遍历的顺序是:左、根、右。我们使用一个栈来保存节点,以便于迭代的时候取出对应节点。 - -- 初始的遍历当前节点的左子树,将其路径上的节点存储到栈中。 -- 调用 next 方法的时候,从栈顶取出节点,因为之前已经将路径上的左子树全部存入了栈中,所以此时该节点的左子树为空,这时候取出节点右子树,再将右子树的左子树进行递归遍历,并将其路径上的节点存储到栈中。 -- 调用 hasNext 的方法的时候,直接判断栈中是否有值即可。 - -## 代码 - -```python -class BSTIterator: - - def __init__(self, root: TreeNode): - self.stack = [] - self.in_order(root) - - - def in_order(self, node): - while node: - self.stack.append(node) - node = node.left - - - def next(self) -> int: - node = self.stack.pop() - if node.right: - self.in_order(node.right) - return node.val - - - def hasNext(self) -> bool: - return len(self.stack) != 0 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 056. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 056. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\344\271\213\345\222\214.md" deleted file mode 100644 index 7c479a45..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 056. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\344\271\213\345\222\214.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer II 056. 二叉搜索树中两个节点之和](https://leetcode.cn/problems/opLdQZ/) - -- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个二叉搜索树的根节点 `root` 和一个整数 `k`。 - -要求:判断该二叉搜索树是否存在两个节点值的和等于 `k`。如果存在,则返回 `True`,不存在则返回 `False`。 - -## 解题思路 - -二叉搜索树中序遍历的结果是从小到大排序,所以我们可以先对二叉搜索树进行中序遍历,将中序遍历结果存储到列表中。再使用左右指针查找节点值和为 `k` 的两个节点。 - -## 代码 - -```python -class Solution: - def inOrder(self, root, nums): - if not root: - return - self.inOrder(root.left, nums) - nums.append(root.val) - self.inOrder(root.right, nums) - - def findTarget(self, root: TreeNode, k: int) -> bool: - nums = [] - self.inOrder(root, nums) - left, right = 0, len(nums) - 1 - while left < right: - sum = nums[left] + nums[right] - if sum == k: - return True - elif sum < k: - left += 1 - else: - right -= 1 - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 057. \345\200\274\345\222\214\344\270\213\346\240\207\344\271\213\345\267\256\351\203\275\345\234\250\347\273\231\345\256\232\347\232\204\350\214\203\345\233\264\345\206\205.md" "b/Solutions/\345\211\221\346\214\207 Offer II 057. \345\200\274\345\222\214\344\270\213\346\240\207\344\271\213\345\267\256\351\203\275\345\234\250\347\273\231\345\256\232\347\232\204\350\214\203\345\233\264\345\206\205.md" deleted file mode 100644 index fe3dc161..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 057. \345\200\274\345\222\214\344\270\213\346\240\207\344\271\213\345\267\256\351\203\275\345\234\250\347\273\231\345\256\232\347\232\204\350\214\203\345\233\264\345\206\205.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer II 057. 值和下标之差都在给定的范围内](https://leetcode.cn/problems/7WqeDu/) - -- 标签:数组、桶排序、有序集合、排序、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`,以及两个整数 `k`、`t`。判断数组中是否存在两个不同下标的 `i` 和 `j`,其对应元素满足 `abs(nums[i] - nums[j]) <= t`,同时满足 `abs(i - j) <= k`。如果满足条件则返回 `True`,不满足条件返回 `False`。 - -## 解题思路 - -对于第 `i` 个元素 `nums[i]`,需要查找的区间为 `[i - t, i + t]`。可以利用桶排序的思想。 - -桶的大小设置为 `t + 1`。我们将元素按照大小依次放入不同的桶中。 - -遍历数组 `nums` 中的元素,对于元素 `nums[i]` : - -- 如果 `nums[i]` 放入桶之前桶里已经有元素了,那么这两个元素必然满足 `abs(nums[i] - nums[j]) <= t`, -- 如果之前桶里没有元素,那么就将 `nums[i]` 放入对应桶中。 -- 然后再判断左右桶的左右两侧桶中是否有元素满足 `abs(nums[i] - nums[j]) <= t`。 -- 然后将 `nums[i - k]` 之前的桶清空,因为这些桶中的元素与 `nums[i]` 已经不满足 `abs(i - j) <= k` 了。 - -最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 - -## 代码 - -```python -class Solution: - def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: - bucket_dict = dict() - for i in range(len(nums)): - # 将 nums[i] 划分到大小为 t + 1 的不同桶中 - num = nums[i] // (t + 1) - - # 桶中已经有元素了 - if num in bucket_dict: - return True - - # 把 nums[i] 放入桶中 - bucket_dict[num] = nums[i] - - # 判断左侧桶是否满足条件 - if (num - 1) in bucket_dict and abs(bucket_dict[num - 1] - nums[i]) <= t: - return True - # 判断右侧桶是否满足条件 - if (num + 1) in bucket_dict and abs(bucket_dict[num + 1] - nums[i]) <= t: - return True - # 将 i-k 之前的旧桶清除,因为之前的桶已经不满足条件了 - if i >= k: - bucket_dict.pop(nums[i - k] // (t + 1)) - - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 059. \346\225\260\346\215\256\346\265\201\347\232\204\347\254\254 K \345\244\247\346\225\260\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer II 059. \346\225\260\346\215\256\346\265\201\347\232\204\347\254\254 K \345\244\247\346\225\260\345\200\274.md" deleted file mode 100644 index c731407e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 059. \346\225\260\346\215\256\346\265\201\347\232\204\347\254\254 K \345\244\247\346\225\260\345\200\274.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer II 059. 数据流的第 K 大数值](https://leetcode.cn/problems/jBjn9C/) - -- 标签:树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) -- 难度:简单 - -## 题目大意 - -设计一个 ` KthLargest` 类,用于找到数据流中第 `k` 大元素。 - -- `KthLargest(int k, int[] nums)`:使用整数 `k` 和整数流 `nums` 初始化对象。 -- `int add(int val)`:将 `val` 插入数据流 `nums` 后,返回当前数据流中第 `k` 大的元素。 - -## 解题思路 - -- 建立大小为 `k` 的大顶堆,堆中元素保证不超过 k 个。 -- 每次 `add` 操作时,将新元素压入堆中,如果堆中元素超出了 `k` 个,则将堆中最小元素(堆顶)移除。 -- 此时堆中最小元素(堆顶)就是整个数据流中的第 `k` 大元素。 - -## 代码 - -```python -import heapq - -class KthLargest: - - def __init__(self, k: int, nums: List[int]): - self.min_heap = [] - self.k = k - for num in nums: - heapq.heappush(self.min_heap, num) - if len(self.min_heap) > k: - heapq.heappop(self.min_heap) - - - def add(self, val: int) -> int: - heapq.heappush(self.min_heap, val) - if len(self.min_heap) > self.k: - heapq.heappop(self.min_heap) - return self.min_heap[0] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 060. \345\207\272\347\216\260\351\242\221\347\216\207\346\234\200\351\253\230\347\232\204 k \344\270\252\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 060. \345\207\272\347\216\260\351\242\221\347\216\207\346\234\200\351\253\230\347\232\204 k \344\270\252\346\225\260\345\255\227.md" deleted file mode 100644 index 0385ad06..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 060. \345\207\272\347\216\260\351\242\221\347\216\207\346\234\200\351\253\230\347\232\204 k \344\270\252\346\225\260\345\255\227.md" +++ /dev/null @@ -1,90 +0,0 @@ -# [剑指 Offer II 060. 出现频率最高的 k 个数字](https://leetcode.cn/problems/g5c51o/) - -- 标签:数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums` 和一个整数 `k`。 - -要求:返回出现频率前 `k` 高的元素。可以按任意顺序返回答案。 - -## 解题思路 - -- 使用哈希表记录下数组中各个元素的频数。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -- 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -- 利用建立大顶堆,此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 -- 将堆顶元素加入到答案数组中,并交换堆顶元素与末尾元素,此时末尾元素已移出堆。继续调整大顶堆。时间复杂度 $O(log{n})$。 -- 调整玩大顶堆之后,此时堆顶元素为频数第二高的元素,和上一步一样,将其加入到答案数组中,继续交换堆顶元素与末尾元素,继续调整大顶堆。 -- 不断重复上步,直到 k 次结束。调整 k 次的时间复杂度 $O(nlog{n})$。 - -总体时间复杂度 $O(nlog{n})$。 - -因为用的是大顶堆,堆的规模是 N 个元素,调整 k 次,所以时间复杂度是 $O(nlog{n})$。 -如果用小顶堆,只需维护 k 个元素的小顶堆,不断向堆中替换元素即可,时间复杂度为 $O(nlog{k})$。 - -## 代码 - -```python -class Solution: - # 调整为大顶堆 - def heapify(self, nums, nums_dict, index, end): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if nums_dict[nums[left]] > nums_dict[nums[max_index]]: - max_index = left - if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(self, nums, nums_dict): - size = len(nums) - # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - self.heapify(nums, nums_dict, i, size - 1) - return nums - - # 堆排序方法(本题未用到) - def maxHeapSort(self, nums, nums_dict): - self.buildMaxHeap(nums) - size = len(nums) - for i in range(size): - nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] - self.heapify(nums, nums_dict, 0, size - i - 2) - return nums - - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - # 统计元素频数 - nums_dict = dict() - for num in nums: - if num in nums_dict: - nums_dict[num] += 1 - else: - nums_dict[num] = 1 - - # 使用 set 方法去重,得到新数组 - new_nums = list(set(nums)) - size = len(new_nums) - # 初始化大顶堆 - self.buildMaxHeap(new_nums, nums_dict) - res = list() - for i in range(k): - # 堆顶元素为当前堆中频数最高的元素,将其加入答案中 - res.append(new_nums[0]) - # 交换堆顶和末尾元素,继续调整大顶堆 - new_nums[0], new_nums[size - i - 1] = new_nums[size - i - 1], new_nums[0] - self.heapify(new_nums, nums_dict, 0, size - i - 2) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 062. \345\256\236\347\216\260\345\211\215\347\274\200\346\240\221.md" "b/Solutions/\345\211\221\346\214\207 Offer II 062. \345\256\236\347\216\260\345\211\215\347\274\200\346\240\221.md" deleted file mode 100644 index 0c6eb457..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 062. \345\256\236\347\216\260\345\211\215\347\274\200\346\240\221.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [剑指 Offer II 062. 实现前缀树](https://leetcode.cn/problems/QC3q1f/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -要求:实现前缀树数据结构的相关类 `Trie` 类。 - -`Trie` 类: - -- `Trie()` 初始化前缀树对象。 -- `void insert(String word)` 向前缀树中插入字符串 `word`。 -- `boolean search(String word)` 如果字符串 `word` 在前缀树中,返回 `True`(即,在检索之前已经插入);否则,返回 `False`。 -- `boolean startsWith(String prefix)` 如果之前已经插入的字符串 `word` 的前缀之一为 `prefix`,返回 `True`;否则,返回 `False`。 - -## 解题思路 - -前缀树(字典树)是一棵多叉数,其中每个节点包含指向子节点的指针数组 `children`,以及布尔变量 `isEnd`。`children` 用于存储当前字符节点,一般长度为所含字符种类个数,也可以使用哈希表代替指针数组。`isEnd` 用于判断该节点是否为字符串的结尾。 - -下面依次讲解插入、查找前缀的具体步骤: - -插入字符串: - -- 从根节点开始插入字符串。对于待插入的字符,有两种情况: - - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续处理下一个字符。 - - 如果该字符对应的节点不存在,则创建一个新的节点,保存在 `children` 中对应位置上,然后沿着指针移动到子节点,继续处理下一个字符。 -- 重复上述步骤,直到最后一个字符,然后将该节点标记为字符串的结尾。 - -查找前缀: - -- 从跟姐点开始查找前缀,对于待查找的字符,有两种情况: - - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续查找下一个字符。 - - 如果该字符对应的节点不存在,则说明字典树中不包含该前缀,直接返回空指针。 -- 重复上述步骤,直到最后一个字符搜索完毕,则说明字典树中存在该前缀。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - - - def startsWith(self, prefix: str) -> bool: - """ - Returns if there is any word in the trie that starts with the given prefix. - """ - cur = self - for ch in prefix: - if ch not in cur.children: - return False - cur = cur.children[ch] - return cur is not None -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 063. \346\233\277\346\215\242\345\215\225\350\257\215.md" "b/Solutions/\345\211\221\346\214\207 Offer II 063. \346\233\277\346\215\242\345\215\225\350\257\215.md" deleted file mode 100644 index 5657f6cb..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 063. \346\233\277\346\215\242\345\215\225\350\257\215.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [剑指 Offer II 063. 替换单词](https://leetcode.cn/problems/UhWRSj/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -给定一个由许多词根组成的字典列表 `dictionary`,以及一个句子字符串 `sentence`。 - -要求:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。 - -## 解题思路 - -将所有的词根存入到前缀树(字典树)中。然后在树上查找每个单词的最短词根。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> str: - """ - Returns if the word is in the trie. - """ - cur = self - index = 0 - for ch in word: - if ch not in cur.children: - return word - cur = cur.children[ch] - index += 1 - if cur.isEnd: - break - return word[:index] - - -class Solution: - def replaceWords(self, dictionary: List[str], sentence: str) -> str: - trie_tree = Trie() - for word in dictionary: - trie_tree.insert(word) - - words = sentence.split(" ") - size = len(words) - for i in range(size): - word = words[i] - words[i] = trie_tree.search(word) - return ' '.join(words) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 064. \347\245\236\345\245\207\347\232\204\345\255\227\345\205\270.md" "b/Solutions/\345\211\221\346\214\207 Offer II 064. \347\245\236\345\245\207\347\232\204\345\255\227\345\205\270.md" deleted file mode 100644 index 05695991..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 064. \347\245\236\345\245\207\347\232\204\345\255\227\345\205\270.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [剑指 Offer II 064. 神奇的字典](https://leetcode.cn/problems/US1pGT/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -要求:设计一个使用单词表进行初始化的数据结构。单词表中的单词互不相同。如果给出一个单词,要求判定能否将该单词中的一个字母替换成另一个字母,是的所形成的新单词已经在够构建的单词表中。 - -实现 MagicDictionary 类: - -- `MagicDictionary()` 初始化对象。 -- `void buildDict(String[] dictionary)` 使用字符串数组 `dictionary` 设定该数据结构,`dictionary` 中的字符串互不相同。 -- `bool search(String searchWord)` 给定一个字符串 `searchWord`,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 `True`;否则,返回 `False`。 - -## 解题思路 - -- 初始化使用字典树结构。 - -- `buildDict` 方法中将所有单词存入字典树中。 - -- `search` 方法中替换 `searchWord` 每一个位置上的字符,然后在字典树中查询。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class MagicDictionary: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.trie_tree = Trie() - - - def buildDict(self, dictionary: List[str]) -> None: - for word in dictionary: - self.trie_tree.insert(word) - - - def search(self, searchWord: str) -> bool: - size = len(searchWord) - for i in range(size): - for j in range(26): - new_ch = chr(ord('a') + j) - if searchWord[i] != new_ch: - new_word = searchWord[:i] + new_ch + searchWord[i + 1:] - if self.trie_tree.search(new_word): - return True - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 065. \346\234\200\347\237\255\347\232\204\345\215\225\350\257\215\347\274\226\347\240\201.md" "b/Solutions/\345\211\221\346\214\207 Offer II 065. \346\234\200\347\237\255\347\232\204\345\215\225\350\257\215\347\274\226\347\240\201.md" deleted file mode 100644 index 614ba522..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 065. \346\234\200\347\237\255\347\232\204\345\215\225\350\257\215\347\274\226\347\240\201.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [剑指 Offer II 065. 最短的单词编码](https://leetcode.cn/problems/iSwD2y/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -给定一个单词数组 `words`。要求对 `words` 进行编码成一个助记字符串,用来帮助记忆。`words` 中拥有相同字符后缀的单词可以合并成一个单词,比如`time` 和 `me` 可以合并成 `time`。同时每个不能再合并的单词末尾以 `#` 为结束符,将所有合并后的单词排列起来就是一个助记字符串。 - -要求:返回对 `words` 进行编码的最小助记字符串 `s` 的长度。 - -## 解题思路 - -构建一个字典树。然后对字符串长度进行从小到大排序。 - -再依次将去重后的所有单词插入到字典树中。如果出现比当前单词更长的单词,则将短单词的结尾置为 `False`,意为替换掉短单词。 - -然后再依次在字典树中查询所有单词,「单词长度 + 1」就是当前不能在合并的单词,累加起来就是答案。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = False - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - -class Solution: - def minimumLengthEncoding(self, words: List[str]) -> int: - trie_tree = Trie() - words = list(set(words)) - words.sort(key=lambda i: len(i)) - - ans = 0 - for word in words: - trie_tree.insert(word[::-1]) - - for word in words: - if trie_tree.search(word[::-1]): - ans += len(word) + 1 - - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 066. \345\215\225\350\257\215\344\271\213\345\222\214.md" "b/Solutions/\345\211\221\346\214\207 Offer II 066. \345\215\225\350\257\215\344\271\213\345\222\214.md" deleted file mode 100644 index 9692e7f2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 066. \345\215\225\350\257\215\344\271\213\345\222\214.md" +++ /dev/null @@ -1,87 +0,0 @@ -# [剑指 Offer II 066. 单词之和](https://leetcode.cn/problems/z1R5dt/) - -- 标签:设计、字典树、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -要求:实现一个 MapSum 类,支持两个方法,`insert` 和 `sum`: - -- `MapSum()` 初始化 MapSum 对象。 -- `void insert(String key, int val)` 插入 `key-val` 键值对,字符串表示键 `key`,整数表示值 `val`。如果键 `key` 已经存在,那么原来的键值对将被替代成新的键值对。 -- `int sum(string prefix)` 返回所有以该前缀 `prefix` 开头的键 `key` 的值的总和。 - -## 解题思路 - -可以构造前缀树(字典树)解题。 - -- 初始化时,构建一棵前缀树(字典树),并增加 `val` 变量。 - -- 调用插入方法时,用字典树存储 `key`,并在对应字母节点存储对应的 `val`。 -- 在调用查询总和方法时,先查找该前缀 `prefix` 对应的前缀树节点,从该节点开始,递归遍历该节点的子节点,并累积子节点的 `val`,进行求和,并返回求和累加结果。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.value = 0 - - - def insert(self, word: str, value: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.value = value - - - def search(self, word: str) -> int: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return 0 - cur = cur.children[ch] - return self.dfs(cur) - - def dfs(self, root) -> int: - if not root: - return 0 - res = root.value - for node in root.children.values(): - res += self.dfs(node) - return res - - - -class MapSum: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.trie_tree = Trie() - - - def insert(self, key: str, val: int) -> None: - self.trie_tree.insert(key, val) - - - def sum(self, prefix: str) -> int: - return self.trie_tree.search(prefix) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 067. \346\234\200\345\244\247\347\232\204\345\274\202\346\210\226.md" "b/Solutions/\345\211\221\346\214\207 Offer II 067. \346\234\200\345\244\247\347\232\204\345\274\202\346\210\226.md" deleted file mode 100644 index 0f40000f..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 067. \346\234\200\345\244\247\347\232\204\345\274\202\346\210\226.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [剑指 Offer II 067. 最大的异或](https://leetcode.cn/problems/ms70jA/) - -- 标签:位运算、字典树、数组、哈希表 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`。 - -要求:返回 `num[i] XOR nums[j]` 的最大运算结果。其中 `0 ≤ i ≤ j < n`。 - -## 解题思路 - -最直接的想法暴力求解。两层循环计算两两之间的异或结果,记录并更新最大异或结果。 - -更好的做法可以减少一重循环。首先,要取得异或结果的最大值,那么从二进制的高位到低位,尽可能的让每一位异或结果都为 `1`。 - -将数组中所有数字的二进制形式从高位到低位依次存入字典树中。然后是利用异或运算交换律:如果 `a ^ b = max` 成立,那么 `a ^ max = b` 与 `b ^ max = a` 均成立。这样当我们知道 `a` 和 `max` 时,可以通过交换律求出 `b`。`a` 是我们遍历的每一个数,`max` 是我们想要尝试的最大值,从 `111111...` 开始,从高位到低位依次填 `1`。 - -对于 `a` 和 `max`,如果我们所求的 `b` 也在字典树中,则表示 `max` 是可以通过 `a` 和 `b` 得到的,那么 `max` 就是所求最大的异或。如果 `b` 不在字典树中,则减小 `max` 值继续判断,或者继续查询下一个 `a`。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, num: int, max_bit: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for i in range(max_bit, -1, -1): - bit = num >> i & 1 - if bit not in cur.children: - cur.children[bit] = Trie() - cur = cur.children[bit] - cur.isEnd = True - - def search(self, num: int, max_bit: int) -> int: - """ - Returns if the word is in the trie. - """ - cur = self - res = 0 - for i in range(max_bit, -1, -1): - bit = num >> i & 1 - if 1 - bit not in cur.children: - res = res * 2 - cur = cur.children[bit] - else: - res = res * 2 + 1 - cur = cur.children[1 - bit] - return res - -class Solution: - def findMaximumXOR(self, nums: List[int]) -> int: - trie_tree = Trie() - max_bit = len(format(max(nums), 'b')) - 1 - ans = 0 - for num in nums: - trie_tree.insert(num, max_bit) - ans = max(ans, trie_tree.search(num, max_bit)) - - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 068. \346\237\245\346\211\276\346\217\222\345\205\245\344\275\215\347\275\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 068. \346\237\245\346\211\276\346\217\222\345\205\245\344\275\215\347\275\256.md" deleted file mode 100644 index d35d5241..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 068. \346\237\245\346\211\276\346\217\222\345\205\245\344\275\215\347\275\256.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer II 068. 查找插入位置](https://leetcode.cn/problems/N6YdxV/) - -- 标签:数组、二分查找 -- 难度:简单 - -## 题目大意 - -给定一个排好序的数组 `nums`,以及一个目标值 `target`。 - -要求:在数组中找到目标值,并返回下标。如果找不到,则返回目标值按顺序插入数组的位置。 - -## 解题思路 - -二分查找法。利用两个指针 `left` 和 `right`,分别指向数组首尾位置。每次用 `left` 和 `right` 中间位置上的元素值与目标值做比较,如果等于目标值,则返回当前位置。如果小于目标值,则更新 `left` 位置为 `mid + 1`,继续查找。如果大于目标值,则更新 `right` 位置为 `mid - 1`,继续查找。直到查找到目标值,或者 `left > right` 值时停止查找。然后返回 `left` 所在位置,即是代插入数组的位置。 - -## 代码 - -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - n = len(nums) - left = 0 - right = n - 1 - while left <= right: - mid = left + (right - left) // 2 - if nums[mid] == target: - return mid - elif nums[mid] < target: - left = mid + 1 - else: - right = mid - 1 - - return left -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 072. \346\261\202\345\271\263\346\226\271\346\240\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 072. \346\261\202\345\271\263\346\226\271\346\240\271.md" deleted file mode 100644 index 7bf46d4e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 072. \346\261\202\345\271\263\346\226\271\346\240\271.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [剑指 Offer II 072. 求平方根](https://leetcode.cn/problems/jJ0w9p/) - -- 标签:数学、二分查找 -- 难度:简单 - -## 题目大意 - -要求:实现 `int sqrt(int x)` 函数。计算并返回 `x` 的平方根(只保留整数部分),其中 `x` 是非负整数。 - -## 解题思路 - -因为求解的是 x 开方的整数部分。所以我们可以从 0~x 的范围进行遍历,找到 k^2 <= x 的最大结果。 - -为了减少时间复杂度,使用二分查找的方式来搜索答案。 - -## 代码 - -```python -class Solution: - def mySqrt(self, x: int) -> int: - left = 0 - right = x - ans = -1 - while left <= right: - mid = (left + right) // 2 - if mid * mid <= x: - ans = mid - left = mid + 1 - else: - right = mid - 1 - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 073. \347\213\222\347\213\222\345\220\203\351\246\231\350\225\211.md" "b/Solutions/\345\211\221\346\214\207 Offer II 073. \347\213\222\347\213\222\345\220\203\351\246\231\350\225\211.md" deleted file mode 100644 index e3dc5c44..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 073. \347\213\222\347\213\222\345\220\203\351\246\231\350\225\211.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 073. 狒狒吃香蕉](https://leetcode.cn/problems/nZZqjQ/) - -- 标签:数组、二分查找 -- 难度:中等 - -## 题目大意 - -给定一个数组 `piles` 代表 `n` 堆香蕉。其中 `piles[i]` 表示第 `i` 堆香蕉的个数。再给定一个整数 `h` ,表示最多可以在 `h` 小时内吃完所有香蕉。狒狒决定以速度每小时 `k`(未知)根的速度吃香蕉。每一个小时,她将选择其中一堆香蕉,从中吃掉 `k` 根。如果这堆香蕉少于 `k` 根,狒狒将在这一小时吃掉这堆的所有香蕉,并且这一小时不会再吃其他堆的香蕉。 - -要求:返回狒狒可以在 `h` 小时内吃掉所有香蕉的最小速度 `k`(`k` 为整数)。 - -## 解题思路 - -先来看 `k` 的取值范围,因为 `k` 是整数,且速度肯定不能为 `0` 吧,为 `0` 的话就永远吃不完了。所以`k` 的最小值可以取 `1`。`k` 的最大值根香蕉中最大堆的香蕉个数有关,因为 `1` 个小时内只能选择一堆吃,不能再吃其他堆的香蕉,则 `k` 的最大值取香蕉堆的最大值即可。即 `k` 的最大值为 `max(piles)`。 - -我们的目标是求出 `h` 小时内吃掉所有香蕉的最小速度 `k`。现在有了区间「`[1, max(piles)]`」,有了目标「最小速度 `k`」。接下来使用二分查找算法来查找「最小速度 `k`」。至于计算 `h` 小时内能否以 `k` 的速度吃完香蕉,我们可以再写一个方法 `canEat` 用于判断。如果能吃完就返回 `True`,不能吃完则返回 `False`。下面说一下算法的具体步骤。 - -- 使用两个指针 `left`、`right`。令 `left` 指向 `1`,`right` 指向 `max(piles)`。代表待查找区间为 `[left, right]` - -- 取两个节点中心位置 `mid`,判断是否能在 `h` 小时内以 `k` 的速度吃完香蕉。 - - 如果不能吃完,则将区间 `[left, mid]` 排除掉,继续在区间 `[mid + 1, right]` 中查找。 - - 如果能吃完,说明 `k` 还可以继续减小,则继续在区间 `[left, mid]` 中查找。 -- 当 `left == right` 时跳出循环,返回 `left`。 - -## 代码 - -```python -class Solution: - def canEat(self, piles, hour, speed): - time = 0 - for pile in piles: - time += (pile + speed - 1) // speed - return time <= hour - - def minEatingSpeed(self, piles: List[int], h: int) -> int: - left, right = 1, max(piles) - - while left < right: - mid = left + (right - left) // 2 - if not self.canEat(piles, h, mid): - left = mid + 1 - else: - right = mid - - return left -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 074. \345\220\210\345\271\266\345\214\272\351\227\264.md" "b/Solutions/\345\211\221\346\214\207 Offer II 074. \345\220\210\345\271\266\345\214\272\351\227\264.md" deleted file mode 100644 index a3b62427..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 074. \345\220\210\345\271\266\345\214\272\351\227\264.md" +++ /dev/null @@ -1,33 +0,0 @@ -# [剑指 Offer II 074. 合并区间](https://leetcode.cn/problems/SsGoHC/) - -- 标签:数组、排序 -- 难度:中等 - -## 题目大意 - -给定一个数组 `intervals` 表示若干个区间的集合,`intervals[i] = [starti, endi]` 表示单个区间。 - -要求:合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需要恰好覆盖原数组中的所有区间。 - -## 解题思路 - -设定一个数组 `ans` 用于表示最终不重叠的区间数组,然后对原始区间先按照区间左端点大小从小到大进行排序。 - -遍历所有区间。先将第一个区间加入 `ans` 数组中。然后依次考虑后边的区间,如果第 `i` 个区间左端点在前一个区间右端点右侧,则这两个区间不会重合,直接将该区间加入 `ans` 数组中。否则的话,这两个区间重合,判断一下两个区间的右区间值,更新前一个区间的右区间值为较大值,然后继续考虑下一个区间,以此类推。 - -## 代码 - -```python -class Solution: - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - intervals.sort(key=lambda x: x[0]) - - ans = [] - for interval in intervals: - if not ans or ans[-1][1] < interval[0]: - ans.append(interval) - else: - ans[-1][1] = max(ans[-1][1], interval[1]) - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 075. \346\225\260\347\273\204\347\233\270\345\257\271\346\216\222\345\272\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 075. \346\225\260\347\273\204\347\233\270\345\257\271\346\216\222\345\272\217.md" deleted file mode 100644 index 9c49cbc5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 075. \346\225\260\347\273\204\347\233\270\345\257\271\346\216\222\345\272\217.md" +++ /dev/null @@ -1,48 +0,0 @@ -# [剑指 Offer II 075. 数组相对排序](https://leetcode.cn/problems/0H97ZC/) - -- 标签:数组、哈希表、计数排序、排序 -- 难度:简单 - -## 题目大意 - -给定两个数组,`arr1` 和 `arr2`,其中 `arr2` 中的元素各不相同,`arr2` 中的每个元素都出现在 `arr1` 中。 - -要求:对 `arr1` 中的元素进行排序,使 `arr1` 中项的相对顺序和 `arr2` 中的相对顺序相同。未在 `arr2` 中出现过的元素需要按照升序放在 `arr1` 的末尾。 - -注意: - -- `1 <= arr1.length, arr2.length <= 1000`。 -- `0 <= arr1[i], arr2[i] <= 1000`。 - -## 解题思路 - -因为元素值范围在 `[0, 1000]`,所以可以使用计数排序的思路来解题。 - -使用数组 `count` 统计 `arr1` 各个元素个数。 - -遍历 `arr2` 数组,将对应元素`num2` 按照个数 `count[num2]` 添加到答案数组 `ans` 中,同时在 `count` 数组中减去对应个数。 - -然后在处理 `count` 中剩余元素,将 `count` 中大于 `0` 的元素下标依次添加到答案数组 `ans` 中。 - -## 代码 - -```python -class Solution: - def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]: - count = [0 for _ in range(1010)] - for num1 in arr1: - count[num1] += 1 - res = [] - for num2 in arr2: - while count[num2] > 0: - res.append(num2) - count[num2] -= 1 - - for num in range(len(count)): - while count[num] > 0: - res.append(num) - count[num] -= 1 - - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 076. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254 k \345\244\247\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 076. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254 k \345\244\247\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index a3a569a0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 076. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254 k \345\244\247\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,153 +0,0 @@ -# [剑指 Offer II 076. 数组中的第 k 大的数字](https://leetcode.cn/problems/xx4gT2/) - -- 标签:数组、分治、快速选择、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定一个未排序的数组 `nums`,从中找到第 `k` 个最大的数字。 - -## 解题思路 - -很不错的一道题,面试常考。 - -直接可以想到的思路是:排序后输出数组上对应第 k 位大的数。所以问题关键在于排序方法的复杂度。 - -冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 - -可考虑堆排序、归并排序、快速排序。 - -这道题的要求是找到第 k 大的元素,使用归并排序只有到最后排序完毕才能返回第 k 大的数。而堆排序每次排序之后,就会确定一个元素的准确排名,同理快速排序也是如此。 - -### 1. 堆排序 - -升序堆排序的思路如下: - -1. 先建立大顶堆 - -2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值 - -3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值 - -4. 以此类推,直到最后一个元素交换之后完毕。 - -这道题我们只需进行 1 次建立大顶堆, k-1 次调整即可得到第 k 大的数。 - -时间复杂度:$O(n^2)$ - -### 2. 快速排序 - -快速排序每次调整,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了两个数组,前一个数组元素都比该元素小,后一个元素都比该元素大。 - -这样,只要某次划分的元素恰好是第 k 个下标就找到了答案。并且我们只需关注 k 元素所在区间的排序情况,与 k 元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 - -### 3. 借用标准库(不建议) - -提交代码中的最快代码是调用了 Python 的 heapq 库,或者 sort 方法。 -这样的确可以通过,但是不建议这样做。借用标准库实现,只能说对这个库的 API 和相关数据结构的用途相对熟悉,而不代表着掌握了这个数据结构。可以问问自己,如果换一种语言,自己还能不能实现对应的数据结构?刷题的本质目的是为了把算法学会学透,而不仅仅是调 API。 - -## 代码 - -1. 堆排序 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 调整为大顶堆 - def heapify(nums, index, end): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if nums[left] > nums[max_index]: - max_index = left - if right <= end and nums[right] > nums[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(nums): - size = len(nums) - # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((size - 2) // 2, -1, -1): - heapify(nums, i, size - 1) - return nums - - buildMaxHeap(nums) - size = len(nums) - for i in range(k-1): - nums[0], nums[size-i-1] = nums[size-i-1], nums[0] - heapify(nums, 0, size-i-2) - return nums[0] -``` - -2. 快速排序 - -```python -import random -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - def randomPartition(nums, low, high): - i = random.randint(low, high) - nums[i], nums[high] = nums[high], nums[i] - return partition(nums, low, high) - - def partition(nums, low, high): - x = nums[high] - i = low-1 - for j in range(low, high): - if nums[j] <= nums[high]: - i += 1 - nums[i], nums[j] = nums[j], nums[i] - nums[i+1], nums[high] = nums[high], nums[i+1] - return i+1 - - def quickSort(nums, low, high, k): - n = len(nums) - if low < high: - pi = randomPartition(nums, low, high) - if pi == n-k: - return nums[len(nums)-k] - if pi > n-k: - quickSort(nums, low, pi-1, k) - if pi < n-k: - quickSort(nums, pi+1, high, k) - - return nums[len(nums)-k] - - return quickSort(nums, 0, len(nums)-1, k) -``` - -3. 借用标准库 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - nums.sort() - return nums[len(nums)-k] -``` - -```python -import heapq -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - res = [] - for n in nums: - if len(res) < k: - heapq.heappush(res, n) - elif n > res[0]: - heapq.heappop(res) - heapq.heappush(res, n) - return heapq.heappop(res) -``` - - - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 077. \351\223\276\350\241\250\346\216\222\345\272\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 077. \351\223\276\350\241\250\346\216\222\345\272\217.md" deleted file mode 100644 index f8828fe0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 077. \351\223\276\350\241\250\346\216\222\345\272\217.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [剑指 Offer II 077. 链表排序](https://leetcode.cn/problems/7WHec2/) - -- 标签:链表、双指针、分治、排序、归并排序 -- 难度:中等 - -## 题目大意 - -给定链表的头节点 `head`。 - -要求:按照升序排列并返回排序后的链表。 - -## 解题思路 - -归并排序。 - -1. 利用快慢指针找到链表的中点,以中点为界限将链表拆分成两个子链表。 -2. 然后对两个子链表分别递归排序。 -3. 将排序后的子链表进行归并排序,得到完整的排序后的链表。 - -## 代码 - -```python -class Solution: - def merge_sort(self, head: ListNode, tail: ListNode) -> ListNode: - if not head: - return head - if head.next == tail: - head.next = None - return head - slow = fast = head - while fast != tail: - slow = slow.next - fast = fast.next - if fast != tail: - fast = fast.next - mid = slow - return self.merge(self.merge_sort(head, mid), self.merge_sort(mid, tail)) - - def merge(self, a: ListNode, b: ListNode) -> ListNode: - root = ListNode(-1) - cur = root - while a and b: - if a.val < b.val: - cur.next = a - a = a.next - else: - cur.next = b - b = b.next - cur = cur.next - if a: - cur.next = a - if b: - cur.next = b - return root.next - - def sortList(self, head: ListNode) -> ListNode: - return self.merge_sort(head, None) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 078. \345\220\210\345\271\266\346\216\222\345\272\217\351\223\276\350\241\250.md" "b/Solutions/\345\211\221\346\214\207 Offer II 078. \345\220\210\345\271\266\346\216\222\345\272\217\351\223\276\350\241\250.md" deleted file mode 100644 index d03143f7..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 078. \345\220\210\345\271\266\346\216\222\345\272\217\351\223\276\350\241\250.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [剑指 Offer II 078. 合并排序链表](https://leetcode.cn/problems/vvXgSW/) - -- 标签:链表、分治、堆(优先队列)、归并排序 -- 难度:困难 - -## 题目大意 - -给定一个链表数组 `lists`,每个链表都已经按照升序排列。 - -要求:将所有链表合并到一个升序链表中,返回合并后的链表。 - -## 解题思路 - -分而治之的思想。将链表数组不断二分,转为规模为二分之一的子问题,然后再进行归并排序。 - -## 代码 - -```python -class Solution: - def merge_sort(self, lists: List[ListNode], left: int, right: int) -> ListNode: - if left == right: - return lists[left] - mid = left + (right - left) // 2 - node_left = self.merge_sort(lists, left, mid) - node_right = self.merge_sort(lists, mid + 1, right) - return self.merge(node_left, node_right) - - def merge(self, a: ListNode, b: ListNode) -> ListNode: - root = ListNode(-1) - cur = root - while a and b: - if a.val < b.val: - cur.next = a - a = a.next - else: - cur.next = b - b = b.next - cur = cur.next - if a: - cur.next = a - if b: - cur.next = b - return root.next - - def mergeKLists(self, lists: List[ListNode]) -> ListNode: - if not lists: - return None - size = len(lists) - return self.merge_sort(lists, 0, size - 1) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 079. \346\211\200\346\234\211\345\255\220\351\233\206.md" "b/Solutions/\345\211\221\346\214\207 Offer II 079. \346\211\200\346\234\211\345\255\220\351\233\206.md" deleted file mode 100644 index e6cecce6..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 079. \346\211\200\346\234\211\345\255\220\351\233\206.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [剑指 Offer II 079. 所有子集](https://leetcode.cn/problems/TVdhkn/) - -- 标签:位运算、数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums`,数组中的元素互不相同。 - -要求:返回该数组所有可能的不重复子集。 - -## 解题思路 - -回溯算法,遍历数组 `nums`。为了使得子集不重复,每次遍历从当前位置的下一个位置进行下一层遍历。 - -## 代码 - -```python -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - def backtrack(size, subset, index): - res.append(subset) - for i in range(index, size): - backtrack(size, subset + [nums[i]], i + 1) - - size = len(nums) - res = list() - backtrack(size, [], 0) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 080. \345\220\253\346\234\211 k \344\270\252\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" "b/Solutions/\345\211\221\346\214\207 Offer II 080. \345\220\253\346\234\211 k \344\270\252\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" deleted file mode 100644 index 24a231c8..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 080. \345\220\253\346\234\211 k \344\270\252\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 080. 含有 k 个元素的组合](https://leetcode.cn/problems/uUsW3B/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -给定两个整数 `n` 和 `k`。 - -要求:返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。可以按任何顺序返回答案。 - -## 解题思路 - -组合问题通常可以用回溯算法来解决。定义两个数组 `res`、`path`。`res` 用来存放最终答案,`path` 用来存放当前符合条件的一个结果。再使用一个变量 `start_index` 来表示从哪一个数开始遍历。 - -定义回溯方法,`start_index = 1` 开始进行回溯。 - -- 如果 `path` 数组的长度等于 `k`,则将 `path` 中的元素加入到 `res` 数组中。 -- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 - - 将当前元素 `i` 加入 `path` 数组。 - - 递归遍历 `[start_index, n]` 上的数。 - - 将遍历的 `i` 元素进行回退。 -- 最终返回 `res` 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, n: int, k: int, start_index: int): - if len(self.path) == k: - self.res.append(self.path[:]) - return - for i in range(start_index, n - (k - len(self.path)) + 2): - self.path.append(i) - self.backtrack(n, k, i + 1) - self.path.pop() - - def combine(self, n: int, k: int) -> List[List[int]]: - self.res.clear() - self.path.clear() - self.backtrack(n, k, 1) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 081. \345\205\201\350\256\270\351\207\215\345\244\215\351\200\211\346\213\251\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" "b/Solutions/\345\211\221\346\214\207 Offer II 081. \345\205\201\350\256\270\351\207\215\345\244\215\351\200\211\346\213\251\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" deleted file mode 100644 index 3d063b8d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 081. \345\205\201\350\256\270\351\207\215\345\244\215\351\200\211\346\213\251\345\205\203\347\264\240\347\232\204\347\273\204\345\220\210.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [剑指 Offer II 081. 允许重复选择元素的组合](https://leetcode.cn/problems/Ygoe9J/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个无重复元素的正整数数组 `candidates` 和一个正整数 `target`。 - -要求:找出 `candidates` 中所有可以使数字和为目标数 `target` 的唯一组合。 - -注意:数组 `candidates` 中的数字可以无限重复选取,且 `1 ≤ candidates[i] ≤ 200`。 - -## 解题思路 - -回溯算法,因为 `1 ≤ candidates[i] ≤ 200`,所以即便是 `candidates[i]` 值为 `1`,重复选取也会等于或大于 target,从而终止回溯。 - -建立两个数组 `res`、`path`。`res` 用于存放所有满足题意的组合,`path` 用于存放当前满足题意的一个组合。 - -定义回溯方法,`start_index = 1` 开始进行回溯。 - -- 如果 `sum > target`,则直接返回。 -- 如果 `sum == target`,则将 `path` 中的元素加入到 `res` 数组中。 -- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 - - 如果 `sum + candidates[i] > target`,可以直接跳出循环。 - - 将和累积,即 `sum += candidates[i]`,然后将当前元素 `i` 加入 `path` 数组。 - - 递归遍历 `[start_index, n]` 上的数。 - - 加之前的和回退,即 `sum -= candidates[i]`,然后将遍历的 `i` 元素进行回退。 -- 最终返回 `res` 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): - if sum > target: - return - - if sum == target: - self.res.append(self.path[:]) - return - - for i in range(start_index, len(candidates)): - if sum + candidates[i] > target: - break - sum += candidates[i] - self.path.append(candidates[i]) - self.backtrack(candidates, target, sum, i) - sum -= candidates[i] - self.path.pop() - - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - self.res.clear() - self.path.clear() - candidates.sort() - self.backtrack(candidates, target, 0, 0) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 082. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\347\273\204\345\220\210.md" "b/Solutions/\345\211\221\346\214\207 Offer II 082. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\347\273\204\345\220\210.md" deleted file mode 100644 index 1b6661de..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 082. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\347\273\204\345\220\210.md" +++ /dev/null @@ -1,52 +0,0 @@ -# [剑指 Offer II 082. 含有重复元素集合的组合](https://leetcode.cn/problems/4sjJUc/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个数组 `candidates` 和一个目标数 `target`。 - -要求:找出 `candidates` 中所有可以使数字和为目标数 `target` 的组合。 - -数组 `candidates` 中的数字在每个组合中只能使用一次,且 `1 ≤ candidates[i] ≤ 50`。 - -## 解题思路 - -本题不能有重复组合,关键步骤在于去重。 - -在回溯遍历的时候,下一层递归的 `start_index` 要从当前节点的后一位开始遍历,即 `i + 1` 位开始。而且统一递归层不能使用相同的元素,即需要增加一句判断 `if i > start_index and candidates[i] == candidates[i - 1]: continue`。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): - if sum > target: - return - if sum == target: - self.res.append(self.path[:]) - return - - for i in range(start_index, len(candidates)): - if sum + candidates[i] > target: - break - if i > start_index and candidates[i] == candidates[i - 1]: - continue - sum += candidates[i] - self.path.append(candidates[i]) - self.backtrack(candidates, target, sum, i + 1) - sum -= candidates[i] - self.path.pop() - - def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: - self.res.clear() - self.path.clear() - candidates.sort() - self.backtrack(candidates, target, 0, 0) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 083. \346\262\241\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 083. \346\262\241\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" deleted file mode 100644 index a327b471..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 083. \346\262\241\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [剑指 Offer II 083. 没有重复元素集合的全排列](https://leetcode.cn/problems/VvJkup/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个不含重复数字的数组 `nums` 。 - -要求:返回其有可能的全排列,可以按任意顺序返回。 - -## 解题思路 - -回溯算法递归遍历 `nums` 元素。同时使用 `visited` 数组来标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 - -## 代码 - -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - def backtrack(size, arrange, index): - if index == size: - res.append(arrange) - return - for i in range(size): - if visited[i] == True: - continue - visited[i] = True - backtrack(size, arrange + [nums[i]], index + 1) - visited[i] = False - - size = len(nums) - res = list() - visited = [False for _ in range(size)] - backtrack(size, [], 0) - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 084. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 084. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" deleted file mode 100644 index 502c02e5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 084. \345\220\253\346\234\211\351\207\215\345\244\215\345\205\203\347\264\240\351\233\206\345\220\210\347\232\204\345\205\250\346\216\222\345\210\227.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [剑指 Offer II 084. 含有重复元素集合的全排列](https://leetcode.cn/problems/7p8L0Z/) - -- 标签:数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个可包含重复数字的序列 `nums` 。 - -要求:按任意顺序返回所有不重复的全排列。 - -## 解题思路 - -这道题跟「[剑指 Offer II 083. 没有重复元素集合的全排列](https://leetcode.cn/problems/VvJkup/)」不一样的地方在于增加了序列中的元素可重复这一条件。这就涉及到了去重。先对 `nums` 进行排序,然后使用 visited 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 - -然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 - -然后进行回溯遍历。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, nums: List[int], visited: List[bool]): - if len(self.path) == len(nums): - self.res.append(self.path[:]) - return - for i in range(len(nums)): - if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: - continue - - if not visited[i]: - visited[i] = True - self.path.append(nums[i]) - self.backtrack(nums, visited) - self.path.pop() - visited[i] = False - - def permuteUnique(self, nums: List[int]) -> List[List[int]]: - self.res.clear() - self.path.clear() - nums.sort() - visited = [False for _ in range(len(nums))] - self.backtrack(nums, visited) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 085. \347\224\237\346\210\220\345\214\271\351\205\215\347\232\204\346\213\254\345\217\267.md" "b/Solutions/\345\211\221\346\214\207 Offer II 085. \347\224\237\346\210\220\345\214\271\351\205\215\347\232\204\346\213\254\345\217\267.md" deleted file mode 100644 index 3166cdad..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 085. \347\224\237\346\210\220\345\214\271\351\205\215\347\232\204\346\213\254\345\217\267.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [剑指 Offer II 085. 生成匹配的括号](https://leetcode.cn/problems/IDBivT/) - -- 标签:字符串、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:生成所有有可能且有效的括号组合。 - -## 解题思路 - -通过回溯算法生成所有答案。为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 - -如果在当前组合中增加一个 `(`,则 `symbol += 1`,如果增加一个 `)`,则 `symbol -= 1`。显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 - -如果最终生成 `2 * n` 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 - -最终输出最终答案数组。 - -## 代码 - -```python -class Solution: - def generateParenthesis(self, n: int) -> List[str]: - def backtrack(parenthesis, symbol, index): - if n * 2 == index: - if symbol == 0: - parentheses.append(parenthesis) - else: - if symbol < n: - backtrack(parenthesis + '(', symbol + 1, index + 1) - if symbol > 0: - backtrack(parenthesis + ')', symbol - 1, index + 1) - - parentheses = list() - backtrack("", 0, 0) - return parentheses -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 086. \345\210\206\345\211\262\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/\345\211\221\346\214\207 Offer II 086. \345\210\206\345\211\262\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 7baed53d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 086. \345\210\206\345\211\262\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,59 +0,0 @@ -# [剑指 Offer II 086. 分割回文子字符串](https://leetcode.cn/problems/M99OJA/) - -- 标签:深度优先搜索、广度优先搜索、图、哈希表 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`将 `s` 分割成一些子串,保证每个子串都是「回文串」。 - -要求:返回 `s` 所有可能的分割方案。 - -## 解题思路 - -回溯算法,建立两个数组 `res`、`path`。`res` 用于存放所有满足题意的组合,`path` 用于存放当前满足题意的一个组合。 - -在回溯的时候判断当前子串是否为回文串,如果不是则跳过,如果是则继续向下一层遍历。 - -定义判断是否为回文串的方法和回溯方法,从 `start_index = 0` 的位置开始回溯。 - -- 如果 `start_index >= len(s)`,则将 `path` 中的元素加入到 `res` 数组中。 -- 然后对 `[start_index, len(s) - 1]` 范围内的子串进行遍历取值。 - - 如果字符串 `s` 在范围 `[start_index, i]` 所代表的子串是回文串,则将其加入 `path` 数组。 - - 递归遍历 `[i + 1, len(s) - 1]` 范围上的子串。 - - 然后将遍历的范围 `[start_index, i]` 所代表的子串进行回退。 -- 最终返回 `res` 数组。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, s: str, start_index: int): - if start_index >= len(s): - self.res.append(self.path[:]) - return - for i in range(start_index, len(s)): - if self.ispalindrome(s, start_index, i): - self.path.append(s[start_index: i + 1]) - self.backtrack(s, i + 1) - self.path.pop() - - def ispalindrome(self, s: str, start: int, end: int): - i, j = start, end - while i < j: - if s[i] != s[j]: - return False - i += 1 - j -= 1 - return True - - def partition(self, s: str) -> List[List[str]]: - self.res.clear() - self.path.clear() - self.backtrack(s, 0) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 087. \345\244\215\345\216\237 IP.md" "b/Solutions/\345\211\221\346\214\207 Offer II 087. \345\244\215\345\216\237 IP.md" deleted file mode 100644 index 65ec9111..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 087. \345\244\215\345\216\237 IP.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [剑指 Offer II 087. 复原 IP](https://leetcode.cn/problems/0on3uN/) - -- 标签:字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个只包含数字的字符串,用来表示一个 IP 地址。 - -要求:返回所有由 `s` 构成的有效 IP 地址,可以按任何顺序返回答案。 - -- 有效 IP 地址:正好由四个整数(每个整数由 0~255 的数构成,且不能含有前导 0),整数之间用 `.` 分割。 - -例如:`0.1.2.201` 和 `192.168.1.1` 是有效 IP 地址,但是 `0.011.255.245`、`192.168.1.312` 和 `192.168@1.1` 是 无效 IP 地址。 - -## 解题思路 - -回溯算法。使用 `res` 存储所有有效 IP 地址。用 `point_num` 表示当前 IP 地址的 `.` 符号个数。 - -定义回溯方法,从 `start_index` 位置开始遍历字符串。 - -- 如果字符串中添加的 `.` 符号数量为 `3`,则判断当前字符串是否为有效 IP 地址,若为有效 IP 地址则加入到 `res` 数组中。直接返回。 -- 然后在 `[start_index, len(s) - 1]` 范围循环遍历,判断 `[start_index, i]` 范围所代表的子串是否合法。如果合法: - - 则 `point_num += 1`。 - - 然后在 i 位置后边增加 `.` 符号,继续回溯遍历。 - - 最后 `point_num -= 1` 进行回退。 -- 不符合则直接跳出循环。 -- 最后返回 `res`。 - -## 代码 - -```python -class Solution: - res = [] - - def backstrack(self, s: str, start_index: int, point_num: int): - if point_num == 3: - if self.isValid(s, start_index, len(s) - 1): - self.res.append(s) - return - for i in range(start_index, len(s)): - if self.isValid(s, start_index, i): - point_num += 1 - self.backstrack(s[:i + 1] + '.' + s[i + 1:], i + 2, point_num) - point_num -= 1 - else: - break - - def isValid(self, s: str, start: int, end: int): - if start > end: - return False - if s[start] == '0' and start != end: - return False - num = 0 - for i in range(start, end + 1): - if s[i] > '9' or s[i] < '0': - return False - num = num * 10 + ord(s[i]) - ord('0') - if num > 255: - return False - return True - - def restoreIpAddresses(self, s: str) -> List[str]: - self.res.clear() - if len(s) > 12: - return self.res - self.backstrack(s, 0, 0) - return self.res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 088. \347\210\254\346\245\274\346\242\257\347\232\204\346\234\200\345\260\221\346\210\220\346\234\254.md" "b/Solutions/\345\211\221\346\214\207 Offer II 088. \347\210\254\346\245\274\346\242\257\347\232\204\346\234\200\345\260\221\346\210\220\346\234\254.md" deleted file mode 100644 index 47158b9b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 088. \347\210\254\346\245\274\346\242\257\347\232\204\346\234\200\345\260\221\346\210\220\346\234\254.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [剑指 Offer II 088. 爬楼梯的最少成本](https://leetcode.cn/problems/GzCJIP/) - -- 标签:数组、动态规划 -- 难度:简单 - -## 题目大意 - -给定一个数组 `cost` 代表一段楼梯,`cost[i]` 代表爬上第 `i` 阶楼梯醒酒药花费的体力值(下标从 `0` 开始)。 - -每爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 - -要求:找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 `0` 或 `1` 的元素作为初始阶梯。 - -## 解题思路 - -使用动态规划方法。 - -状态 `dp[i]` 表示为:到达第 `i` 个台阶所花费的最少体⼒。 - -则状态转移方程为: `dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]`。 - -表示为:到达第 `i` 个台阶所花费的最少体⼒ = 到达第 `i - 1` 个台阶所花费的最小体力 与 到达第 `i - 2` 个台阶所花费的最小体力中的最小值 + 到达第 `i` 个台阶所需要花费的体力值。 - -## 代码 - -```python -class Solution: - def minCostClimbingStairs(self, cost: List[int]) -> int: - size = len(cost) - dp = [0 for _ in range(size + 1)] - for i in range(2, size + 1): - dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]) - return dp[size] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 089. \346\210\277\345\261\213\345\201\267\347\233\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 089. \346\210\277\345\261\213\345\201\267\347\233\227.md" deleted file mode 100644 index f462ab3e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 089. \346\210\277\345\261\213\345\201\267\347\233\227.md" +++ /dev/null @@ -1,46 +0,0 @@ -# [剑指 Offer II 089. 房屋偷盗](https://leetcode.cn/problems/Gu0c2T/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。假如你是一名专业的小偷。 - -要求:计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 - -## 解题思路 - -可以用动态规划来解决问题,关键点在于找到状态转移方程。 - -先考虑最简单的情况。假如只有一间房,则直接偷这间屋子就能偷到最高金额,即 `dp[0] = nums[i]`。假如只有两间房屋,那么就选择金额最大的那间屋进行偷窃,就可以偷到最高金额,即 `dp[1] = max(nums[0], nums[1])`。 - -如果房屋大于两间,则偷窃第 `i` 间房屋的时候,就有两种状态: - -- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 `dp[i] = dp[i-2] + nums[i]`; -- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 `dp[i] = dp[i-1]`。 - -然后这两种状态取最大值即可,即 `dp[i] = max(dp[i-2] + nums[i], dp[i-1])`。 - -总结下就是: - -$dp[i] = \begin{cases} \begin{array} {**lr**} nums[0] & i = 0 \cr max( nums[0], nums[1]) & i = 1 \cr max( dp[i-2] + nums[i], dp[i-1]) & i \ge 2 \end{array} \end{cases}$ - -## 代码 - -```python -class Solution: - def rob(self, nums: List[int]) -> int: - size = len(nums) - dp = [0 for _ in range(size)] - for i in range(size): - if i == 0: - dp[i] = nums[i] - elif i == 1: - dp[i] = max(nums[i - 1], nums[i]) - else: - dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) - - return dp[size - 1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 090. \347\216\257\345\275\242\346\210\277\345\261\213\345\201\267\347\233\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 090. \347\216\257\345\275\242\346\210\277\345\261\213\345\201\267\347\233\227.md" deleted file mode 100644 index 90b33ec2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 090. \347\216\257\345\275\242\346\210\277\345\261\213\345\201\267\347\233\227.md" +++ /dev/null @@ -1,61 +0,0 @@ -# [剑指 Offer II 090. 环形房屋偷盗](https://leetcode.cn/problems/PzWKhm/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额,假设房屋可以围成一圈,首尾相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。假如你是一名专业的小偷。 - -要求:计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 - -## 解题思路 - -「[剑指 Offer II 089. 房屋偷盗](https://leetcode.cn/problems/Gu0c2T/)」的升级版。可以用动态规划来解决问题,关键点在于找到状态转移方程。 - -先来考虑最简单的情况。 - -假如只有一间房屋,则直接偷这间房屋就能偷到最高金额,即 $dp[0] = nums[i]$。假如有两间房屋,那么就选择金额最大的那间房屋进行偷窃,就可以偷到最高金额,即 $dp[1] = max(nums[0], nums[1])$。 - -两间屋子以下,最多只能偷窃一间房屋,则不用考虑首尾相连的情况。如果三个屋子以上,偷窃了第一间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第一间房屋。 - -假设总共房屋数量为 N,这种情况可以转换为分别求解 $[0, N - 2]$ 和 $[1, N - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额,这就变成了「[剑指 Offer II 089. 房屋偷盗](https://leetcode.cn/problems/Gu0c2T/)」的求解问题。 - -「[剑指 Offer II 089. 房屋偷盗](https://leetcode.cn/problems/Gu0c2T/)」求解思路如下: - -如果房屋大于两间,则偷窃第 `i` 间房屋的时候,就有两种状态: - -- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 $dp[i] = dp[i-2] + nums[i]$; -- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 $dp[i] = dp[i-1]$。 - -然后这两种状态取最大值即可,即 $dp[i] = max( dp[i-2] + nums[i], dp[i-1])$。 - -总结下就是: - -$dp[i] = \begin{cases} nums[0], & i = 0 \cr max( nums[0], nums[1]) & i = 1 \cr max( dp[i-2] + nums[i], dp[i-1]) & i \ge 2 \end{cases}$ - -## 代码 - -```python -class Solution: - def rob(self, nums: List[int]) -> int: - def helper(nums): - size = len(nums) - if size == 1: - return nums[0] - dp = [0 for _ in range(size)] - for i in range(size): - if i == 0: - dp[i] = nums[0] - elif i == 1: - dp[i] = max(nums[i - 1], nums[i]) - else: - dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) - return dp[-1] - - if len(nums) == 1: - return nums[0] - else: - return max(helper(nums[1:]), helper(nums[:-1])) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 093. \346\234\200\351\225\277\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 093. \346\234\200\351\225\277\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" deleted file mode 100644 index 58967187..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 093. \346\234\200\351\225\277\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" +++ /dev/null @@ -1,148 +0,0 @@ -# [剑指 Offer II 093. 最长斐波那契数列](https://leetcode.cn/problems/Q91FMA/) - -- 标签:数组、哈希表、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个严格递增的正整数数组 `arr`。 - -要求:从 `arr` 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 `0`。 - -- 斐波那契式序列:如果序列 $X_1, X_2, ..., X_n$ 满足: - - - $n \ge 3$; - - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 - - 则称该序列为斐波那契式序列。 - -- 斐波那契式子序列:从序列 `arr` 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:`arr = [3, 4, 5, 6, 7, 8]`。则 `[3, 5, 8]` 是 `arr` 的一个斐波那契式子序列。 - -## 解题思路 - -我们先从最简单的暴力做法思考。 - -**1. 暴力做法:** - -我们先来考虑暴力做法怎么做。 - -假设 `arr[i]`、`arr[j]`、`arr[k]` 是序列 `arr` 中的 3 个元素,且满足关系:`arr[i] + arr[j] == arr[k]`,则 `arr[i]`、`arr[j]`、`arr[k]` 就构成了 A 的一个斐波那契式子序列。 - -通过 `arr[i]`、`arr[j]`,我们可以确定下一个斐波那契式子序列元素的值为 `arr[i] + arr[j]`。 - -因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 `arr[i]`、`arr[j]`,则可以顺着 `arr` 序列,从第 `j + 1` 的元素开始,查找值为 `arr[i] + arr[j]` 的元素 。找到 `arr[i] + arr[j]` 之后,然后在顺着查找子序列的下一个元素。 - -简单来说,就是确定了 `arr[i]`、`arr[j]`,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 `arr[i]`、`arr[j]`,统计不同的斐波那契式子序列的长度。将这些长度进行比较,其中最长的长度就是答案。 - -下面是暴力做法的代码: - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - k = j + 1 - while k < size: - if arr[temp_i] + arr[temp_j] == arr[k]: - temp_ans += 1 - temp_i = temp_j - temp_j = k - k += 1 - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -毫无意外的,超出时间限制了。 - -那么我们怎么来优化呢? - -**2. 使用哈希表优化做法:** - -我们注意到:对于 `arr[i]`、`arr[j]`,要查找的元素 `arr[i] + arr[j]` 是否在 `arr` 中,我们可以预先建立一个反向的哈希表。键值对关系为 `value : idx`,这样就能在 `O(1)` 的时间复杂度通过 `arr[i] + arr[j]` 的值查找到对应的 `k` 值,而不用像原先一样线性查找 `arr[k]` 了。 - -使用哈希表优化之后的代码如下: - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - ans = 0 - idx_map = dict() - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - temp_ans = 0 - temp_i = i - temp_j = j - while arr[temp_i] + arr[temp_j] in idx_map: - temp_ans += 1 - k = idx_map[arr[temp_i] + arr[temp_j]] - temp_i = temp_j - temp_j = k - - if temp_ans > ans: - ans = temp_ans - - if ans > 0: - return ans + 2 - else: - return ans -``` - -再次提交,通过了。 - -但是,这道题我们还可以用动态规划来做。 - -**3. 动态规划做法:** - -这道题用动态规划来做,难点在于如何「定义状态」和「定义状态转移方程」。 - -- 定义状态:`dp[i][j]` 表示以 `arr[i]`、`arr[j]` 为结尾的斐波那契式子序列的最大长度。 -- 定义状态转移方程:$dp[j][k] = max_{(arr[i] + arr[j] = arr[k], i < j < k)}(dp[i][j] + 1)$ - - 意思为:以 `arr[j]`、`arr[k]` 结尾的斐波那契式子序列的最大长度 = 满足 `arr[i] + arr[j] = arr[k]` 条件下,以 `arr[i]`、`arr[j]` 结尾的斐波那契式子序列的最大长度 + 1。 - -但是直接这样做其实跟 **1. 暴力解法** 一样仍会超时,所以我们依旧采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 - -具体代码如下: - -## 代码 - -```python -class Solution: - def lenLongestFibSubseq(self, arr: List[int]) -> int: - size = len(arr) - # 初始化 dp - dp = [[0 for _ in range(size)] for _ in range(size)] - ans = 0 - idx_map = {} - # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx - for idx, value in enumerate(arr): - idx_map[value] = idx - - for i in range(size): - for j in range(i + 1, size): - if arr[i] + arr[j] in idx_map: - # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 - k = idx_map[arr[i] + arr[j]] - - dp[j][k] = max(dp[j][k], dp[i][j] + 1) - ans = max(ans, dp[j][k]) - - if ans > 0: - return ans + 2 - else: - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 095. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 095. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" deleted file mode 100644 index 32c6c7e8..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 095. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 095. 最长公共子序列](https://leetcode.cn/problems/qJnOS7/) - -- 标签:字符串、动态规划 -- 难度:中等 - -## 题目大意 - -给定两个字符串 `text1` 和 `text2`。 - -要求:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 `0`。 - -- 子序列:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 -- 公共子序列:两个字符串所共同拥有的子序列。 - -## 解题思路 - -用动态规划来做。 - -动态规划的状态 `dp[i][j]` 表示为:前 `i` 个字符组成的字符串 `str1` 与前 `j` 个字符组成的字符串 `str2` 的最长公共子序列长度为 `dp[i][j]`。 - -遍历字符串 `text1` 和 `text2`,则状态转移方程为: - -- 如果 `text1[i - 1] == text2[j - 1]`,则找到了一个公共元素,则 `dp[i][j] = dp[i - 1][j - 1] + 1`。 -- 如果 `text1[i - 1] != text2[j - 1]`,则 `dp[i][j]` 需要考虑两种情况,取其中最大的那种: - - `text1` 前 `i - 1` 个字符组成的字符串 `str1` 与 `text2` 前 `j` 个字符组成的 `str2` 的最长公共子序列长度,即 `dp[i - 1][j]`。 - - `text1` 前 `i` 个字符组成的字符串 `str1` 与 `text2` 前 `j - 1` 个字符组成的 `str2` 的最长公共子序列长度,即 `dp[i][j - 1]`。 - -最后输出 `dp[sise1][size2]` 即可,`size1`、`size2` 分别为 `text1`、`text2` 的字符串长度。 - -## 代码 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - size1 = len(text1) - size2 = len(text2) - dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] - for i in range(1, size1 + 1): - for j in range(1, size2 + 1): - if text1[i - 1] == text2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - else: - dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - - return dp[size1][size2] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 097. \345\255\220\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 097. \345\255\220\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index f3068bfd..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 097. \345\255\220\345\272\217\345\210\227\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [剑指 Offer II 097. 子序列的数目](https://leetcode.cn/problems/21dk04/) - -- 标签:字符串、动态规划 -- 难度:困难 - -## 题目大意 - -给定两个字符串 `s` 和 `t`。 - -要求:计算在 `s` 的子序列中 `t` 出现的个数。 - -## 解题思路 - -动态规划求解。 - -定义状态 `dp[i][j]`表示为:以 `i - 1` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数。 - -则状态转移方程为: - -- 如果 `s[i - 1] == t[j - 1]`,则:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`。即 `dp[i][j]` 来源于两部分: - - 使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 2` 为结尾的 `t` 的个数,即 `dp[i - 1][j - 1]`。 - - 不使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数,即 `dp[i - 1][j]`。 -- 如果 `s[i - 1] != t[j - 1]`,那么肯定不能用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于 `dp[i - 1][j]`。 - -下面来看看初始化: - -- `dp[i][0]` 表示以 `i - 1` 为结尾的 `s` 子序列中出现空字符串的个数。把 `s` 中的元素全删除,出现空字符串的个数就是 `1`,则 `dp[i][0] = 1`。 -- `dp[0][j]` 表示空字符串中出现以 `j - 1` 结尾的 `t` 的个数,空字符串无论怎么变都不会变成 `t`,则 `dp[0][j] = 0` -- `dp[0][0]` 表示空字符串中出现空字符串的个数,这个应该是 `1`,即 `dp[0][0] = 1`。 - -然后递推求解,最后输出 `dp[size_s][size_t]`。 - -## 代码 - -```python -class Solution: - def numDistinct(self, s: str, t: str) -> int: - size_s = len(s) - size_t = len(t) - dp = [[0 for _ in range(size_t + 1)] for _ in range(size_s + 1)] - for i in range(size_s): - dp[i][0] = 1 - for i in range(1, size_s + 1): - for j in range(1, size_t + 1): - if s[i - 1] == t[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] - else: - dp[i][j] = dp[i - 1][j] - return dp[size_s][size_t] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 098. \350\267\257\345\276\204\347\232\204\346\225\260\347\233\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 098. \350\267\257\345\276\204\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index 2ad108f5..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 098. \350\267\257\345\276\204\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,27 +0,0 @@ -# [剑指 Offer II 098. 路径的数目](https://leetcode.cn/problems/2AoeFn/) - -- 标签:数学、动态规划、组合数学 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 的棋盘, 机器人在左上角的位置,机器人每次只能向右、或者向下移动一步。 - -要求:求出到达棋盘右下角共有多少条不同的路径。 - -## 解题思路 - -可以用动态规划求解,设 `dp[i][j]` 是从 `(0, 0)`到 `(i, j)` 的不同路径数。显然 `dp[i][j] = dp[i-1][j] + dp[i][j-1]`。对于第一行、第一列,因为只能超一个方向走,所以 `dp[i][0] = 1`,`dp[0][j] = 1`。 - -## 代码 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - dp = [1 for _ in range(n)] - for i in range(1, m): - for j in range(1, n): - dp[j] += dp[j - 1] - return dp[-1] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 101. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" "b/Solutions/\345\211\221\346\214\207 Offer II 101. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" deleted file mode 100644 index 4e9c50e2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 101. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [剑指 Offer II 101. 分割等和子集](https://leetcode.cn/problems/NUPfPr/) - -- 标签:数学、字符串、模拟 -- 难度:简单 - -## 题目大意 - -给定一个只包含正整数的非空数组 `nums`。 - -要求:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 - -## 解题思路 - -动态规划求解。 - -如果两个子集和相等,则两个子集元素和刚好等于整个数组元素和的一半。这就相当于 `0-1` 背包问题。 - -定义 `dp[i][j]` 表示从 `[0, i]` 个数中任意选取一些数,放进容量为 j 的背包中,价值总和最大为多少。则 `dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i])`。 - -转换为一维 dp 就是:`dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])`。 - -然后进行递归求解。最后判断 `dp[target]` 和 `target` 是否相等即可。 - -## 代码 - -```python -class Solution: - def canPartition(self, nums: List[int]) -> bool: - size = 100010 - dp = [0 for _ in range(size)] - sum_nums = sum(nums) - if sum_nums & 1: - return False - target = sum_nums // 2 - for i in range(len(nums)): - for j in range(target, nums[i] - 1, -1): - dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) - - if dp[target] == target: - return True - return False -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 102. \345\212\240\345\207\217\347\232\204\347\233\256\346\240\207\345\200\274.md" "b/Solutions/\345\211\221\346\214\207 Offer II 102. \345\212\240\345\207\217\347\232\204\347\233\256\346\240\207\345\200\274.md" deleted file mode 100644 index 8ad9c352..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 102. \345\212\240\345\207\217\347\232\204\347\233\256\346\240\207\345\200\274.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer II 102. 加减的目标值](https://leetcode.cn/problems/YaVDxD/) - -- 标签:数组、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -给定一个整数数组 `nums` 和一个整数 `target`。数组长度不超过 `20`。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 - -要求:返回通过上述方法构造的、运算结果等于 `target` 的不同表达式数目。 - -## 解题思路 - -暴力方法就是使用深度优先搜索对每位数字遍历 `+`、`-`,并统计符合要求的表达式数目。但是实际发现超时了。所以采用动态规划的方法来做。 - -假设数组中所有元素和为 `sum`,数组中所有符号为 `+` 的元素为 `sum_x`,符号为 `-` 的元素和为 `sum_y`。则 `target = sum_x - sum_y`。 - -而 `sum_x + sum_y = sum`。根据两个式子可以求出 `2 * sum_x = target + sum `,即 `sum_x = (target + sum) / 2`。 - -那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 `(target + sum) / 2`。这就变为了求容量为 `(target + sum) / 2` 的 `01` 背包问题。 - -动态规划的状态 `dp[i]` 表示为:填满容量为 `i` 的背包,有 `dp[i]` 种方法。 - -动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i-num]`,意思为填满容量为 `i` 的背包的方法数 = 不使用当前 `num`,只使用之前元素填满容量为 `i` 的背包的方法数 + 填满容量 `i - num` 的包的方法数,再填入 `num` 的方法数。 - -## 代码 - -```python -class Solution: - def findTargetSumWays(self, nums: List[int], target: int) -> int: - sum_nums = sum(nums) - if target > sum_nums or (target + sum_nums) % 2 == 1: - return 0 - size = (target + sum_nums) // 2 - dp = [0 for _ in range(size + 1)] - dp[0] = 1 - for num in nums: - for i in range(size, num - 1, -1): - dp[i] = dp[i] + dp[i - num] - return dp[size] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 103. \346\234\200\345\260\221\347\232\204\347\241\254\345\270\201\346\225\260\347\233\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 103. \346\234\200\345\260\221\347\232\204\347\241\254\345\270\201\346\225\260\347\233\256.md" deleted file mode 100644 index 0546b3ea..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 103. \346\234\200\345\260\221\347\232\204\347\241\254\345\270\201\346\225\260\347\233\256.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer II 103. 最少的硬币数目](https://leetcode.cn/problems/gaM7Ch/) - -- 标签:广度优先搜索、数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定不同面额的硬币 `coins` 和一个总金额 `amount`。 - -乔秋:计算出凑成总金额所需的最少的硬币个数。如果无法凑出,则返回 `-1`。 - -## 解题思路 - -完全背包问题。 - -可以转换为有 `n` 枚不同的硬币,每种硬币可以无限次使用。凑成总金额为 `amount` 的背包,最少需要多少硬币。 - -动态规划的状态 `dp[i]` 可以表示为:凑成总金额为 `i` 的组合中,至少有 `dp[i]` 枚硬币。 - -动态规划的状态转移方程为:`dp[i] = min(dp[i], + dp[i-coin] + 1`,意思为凑成总金额为 `i` 最少硬币数量 = 「不使用当前 `coin`,只使用之前硬币凑成金额 `i` 的最少硬币数量」和「凑成金额 `i - num` 的最少硬币数量,再加上当前硬币」两者的较小值。 - -## 代码 - -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - dp = [float('inf') for _ in range(amount + 1)] - dp[0] = 0 - - for coin in coins: - for i in range(coin, amount + 1): - dp[i] = min(dp[i], dp[i - coin] + 1) - - if dp[amount] != float('inf'): - return dp[amount] - else: - return -1 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 104. \346\216\222\345\210\227\347\232\204\346\225\260\347\233\256.md" "b/Solutions/\345\211\221\346\214\207 Offer II 104. \346\216\222\345\210\227\347\232\204\346\225\260\347\233\256.md" deleted file mode 100644 index e0ad3d4e..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 104. \346\216\222\345\210\227\347\232\204\346\225\260\347\233\256.md" +++ /dev/null @@ -1,36 +0,0 @@ -# [剑指 Offer II 104. 排列的数目](https://leetcode.cn/problems/D0F0SV/) - -- 标签:数组、动态规划 -- 难度:中等 - -## 题目大意 - -给定一个由不同整数组成的数组 `nums` 和一个目标整数 `target`。 - -要求:从 `nums` 中找出并返回总和为 `target` 的元素组合个数。 - -## 解题思路 - -完全背包问题。题目求解的是组合数。 - -动态规划的状态 `dp[i]` 可以表示为:凑成总和 `i` 的组合数。 - -动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i - nums[j]]`,意思为凑成总和为 `i` 的组合数 = 「不使用当前 `nums[j]`,只使用之前整数凑成和为 `i` 的组合数」+「使用当前 `nums[j]` 凑成金额 `i - nums[j]` 的方案数」。 - -最终输出 `dp[target]`。 - -## 代码 - -```python -class Solution: - def combinationSum4(self, nums: List[int], target: int) -> int: - dp = [0 for _ in range(target + 1)] - dp[0] = 1 - size = len(nums) - for i in range(target + 1): - for j in range(size): - if i - nums[j] >= 0: - dp[i] += dp[i - nums[j]] - return dp[target] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 105. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" "b/Solutions/\345\211\221\346\214\207 Offer II 105. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" deleted file mode 100644 index f147f252..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 105. \345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.md" +++ /dev/null @@ -1,39 +0,0 @@ -# [剑指 Offer II 105. 岛屿的最大面积](https://leetcode.cn/problems/ZL6zAn/) - -- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个只包含 `0`、`1` 元素的二维数组,`1` 代表岛屿,`0` 代表水。一座岛的面积就是上下左右相邻相邻的 `1` 所组成的连通块的数目。找到最大的岛屿面积。 - -## 解题思路 - -使用深度优先搜索方法。遍历二维数组的每一个元素,对于每个值为 `1` 的元素,记下其面积。然后将该值置为 `0`(防止二次重复计算),再递归其上下左右四个位置,并将深度优先搜索搜到的值为 `1` 的元素个数,进行累积统计。 - -## 代码 - -```python -class Solution: - def dfs(self, grid, i, j): - size_n = len(grid) - size_m = len(grid[0]) - if i < 0 or i >= size_n or j < 0 or j >= size_m or grid[i][j] == 0: - return 0 - ans = 1 - grid[i][j] = 0 - ans += self.dfs(grid, i + 1, j) - ans += self.dfs(grid, i, j + 1) - ans += self.dfs(grid, i - 1, j) - ans += self.dfs(grid, i, j - 1) - return ans - - def maxAreaOfIsland(self, grid: List[List[int]]) -> int: - ans = 0 - for i in range(len(grid)): - for j in range(len(grid[0])): - if grid[i][j] == 1: - ans = max(ans, self.dfs(grid, i, j)) - return ans -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 106. \344\272\214\345\210\206\345\233\276.md" "b/Solutions/\345\211\221\346\214\207 Offer II 106. \344\272\214\345\210\206\345\233\276.md" deleted file mode 100644 index 115b7f9d..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 106. \344\272\214\345\210\206\345\233\276.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [剑指 Offer II 106. 二分图](https://leetcode.cn/problems/vEAB3K/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -给定一个代表 n 个节点的无向图的二维数组 `graph`,其中 `graph[u]` 是一个节点数组,由节点 `u` 的邻接节点组成。对于 `graph[u]` 中的每个 `v`,都存在一条位于节点 `u` 和节点 `v` 之间的无向边。 - -该无向图具有以下属性: - -- 不存在自环(`graph[u]` 不包含 `u`)。 -- 不存在平行边(`graph[u]` 不包含重复值)。 -- 如果 `v` 在 `graph[u]` 内,那么 `u` 也应该在 `graph[v]` 内(该图是无向图)。 -- 这个图可能不是连通图,也就是说两个节点 `u` 和 `v` 之间可能不存在一条连通彼此的路径。 - -要求:判断该图是否是二分图,如果是二分图,则返回 `True`;否则返回 `False`。 - -- 二分图:如果能将一个图的节点集合分割成两个独立的子集 `A` 和 `B`,并使图中的每一条边的两个节点一个来自 `A` 集合,一个来自 `B` 集合,就将这个图称为 二分图 。 - -## 解题思路 - -对于图中的任意节点 `u` 和 `v`,如果 `u` 和 `v` 之间有一条无向边,那么 `u` 和 `v` 必然属于不同的集合。 - -我们可以通过在深度优先搜索中对邻接点染色标记的方式,来识别该图是否是二分图。具体做法如下: - -- 找到一个没有染色的节点 `u`,将其染成红色。 -- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 -- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 -- 如果所有节点都顺利染上色,则说明该图为二分图,返回 `True`。否则,如果在途中不能顺利染色,则返回 `False`。 - -## 代码 - -```python -class Solution: - def dfs(self, graph, colors, i, color): - colors[i] = color - for j in graph[i]: - if colors[j] == colors[i]: - return False - if colors[j] == 0 and not self.dfs(graph, colors, j, -color): - return False - return True - - def isBipartite(self, graph: List[List[int]]) -> bool: - size = len(graph) - colors = [0 for _ in range(size)] - for i in range(size): - if colors[i] == 0 and not self.dfs(graph, colors, i, 1): - return False - return True -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 107. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\235\347\246\273.md" "b/Solutions/\345\211\221\346\214\207 Offer II 107. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\235\347\246\273.md" deleted file mode 100644 index 0fece48b..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 107. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\235\347\246\273.md" +++ /dev/null @@ -1,47 +0,0 @@ -# [剑指 Offer II 107. 矩阵中的距离](https://leetcode.cn/problems/2bCMpM/) - -- 标签:广度优先搜索、数组、动态规划、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个由 `0` 和 `1` 组成的矩阵,两个相邻元素间的距离为 `1` 。 - -要求:找出每个元素到最近的 `0` 的距离,并输出为矩阵。 - -## 解题思路 - -题目要求的是每个 `1` 到 `0`的最短曼哈顿距离。换句话也可以求每个 `0` 到 `1` 的最短曼哈顿距离。这样做的好处是,可以从所有值为 `0` 的元素开始进行搜索,可以不断累积距离,直到遇到值为 `1` 的元素时,可以直接将累积距离直接赋值。 - -具体操作如下:将所有值为 `0` 的元素坐标加入访问集合中,对所有值为`0` 的元素上下左右进行搜索。每进行一次上下左右搜索,更新新位置的距离值,并把新的位置坐标加入队列和访问集合中,直到遇见值为 `1` 的元素停止搜索。 - -## 代码 - -```python -class Solution: - def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: - row_count = len(mat) - col_count = len(mat[0]) - dist_map = [[0 for _ in range(col_count)] for _ in range(row_count)] - zeroes_pos = [] - for i in range(row_count): - for j in range(col_count): - if mat[i][j] == 0: - zeroes_pos.append((i, j)) - - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - queue = collections.deque(zeroes_pos) - visited = set(zeroes_pos) - - while queue: - i, j = queue.popleft() - for direction in directions: - new_i = i + direction[0] - new_j = j + direction[1] - if 0 <= new_i < row_count and 0 <= new_j < col_count and (new_i, new_j) not in visited: - dist_map[new_i][new_j] = dist_map[i][j] + 1 - queue.append((new_i, new_j)) - visited.add((new_i, new_j)) - return dist_map -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 108. \345\215\225\350\257\215\346\274\224\345\217\230.md" "b/Solutions/\345\211\221\346\214\207 Offer II 108. \345\215\225\350\257\215\346\274\224\345\217\230.md" deleted file mode 100644 index f42ef7c0..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 108. \345\215\225\350\257\215\346\274\224\345\217\230.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [剑指 Offer II 108. 单词演变](https://leetcode.cn/problems/om3reC/) - -- 标签:广度优先搜索、哈希表、字符串 -- 难度:困难 - -## 题目大意 - -给定两个单词 `beginWord` 和 `endWord`,以及一个字典 `wordList`。找到从 `beginWord` 到 `endWord` 的最短转换序列中的单词数目。如果不存在这样的转换序列,则返回 0。 - -转换需要遵守的规则如下: - -- 每次转换只能改变一个字母。 -- 转换过程中的中间单词必须为字典中的单词。 - -## 解题思路 - -广度优先搜索。使用队列存储将要遍历的单词和单词数目。 - -从 `beginWord` 开始变换,把单词的每个字母都用 `a ~ z` 变换一次,变换后的单词是否是 `endWord`,如果是则直接返回。 - -否则查找变换后的词是否在 `wordList` 中。如果在 `wordList` 中找到就加入队列,找不到就输出 `0`。然后按照广度优先搜索的算法急需要遍历队列中的节点,直到所有单词都出队时结束。 - -## 代码 - -```python -class Solution: - def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: - if not wordList or endWord not in wordList: - return 0 - word_set = set(wordList) - if beginWord in word_set: - word_set.remove(beginWord) - - queue = collections.deque() - queue.append((beginWord, 1)) - while queue: - word, level = queue.popleft() - if word == endWord: - return level - - for i in range(len(word)): - for j in range(26): - new_word = word[:i] + chr(ord('a') + j) + word[i + 1:] - if new_word in word_set: - word_set.remove(new_word) - queue.append((new_word, level + 1)) - - return 0 -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 109. \345\274\200\345\257\206\347\240\201\351\224\201.md" "b/Solutions/\345\211\221\346\214\207 Offer II 109. \345\274\200\345\257\206\347\240\201\351\224\201.md" deleted file mode 100644 index 2f9d7233..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 109. \345\274\200\345\257\206\347\240\201\351\224\201.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [剑指 Offer II 109. 开密码锁](https://leetcode.cn/problems/zlDJc7/) - -- 标签:广度优先搜索、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -有一把带有四个数字的密码锁,每个位置上有 0~9 共 10 个数字。每次只能将其中一个位置上的数字转动一下。可以向上转,也可以向下转。比如:1 -> 2、2 -> 1。 - -密码锁的初始数字为:`0000`。现在给定一组表示死亡数字的字符串数组 `deadends`,和一个带有四位数字的目标字符串 `target`。 - -如果密码锁转动到 `deadends` 中任一字符串状态,则锁就会永久锁定,无法再次旋转。 - -要求:求出最小的选择次数,使得锁的状态由 `0000` 转动到 `target`。 - -## 解题思路 - -使用宽度优先搜索遍历,将`0000` 状态入队。 - -- 将队列中的元素出队,判断是否为死亡字符串 -- 如果为死亡字符串,则跳过该状态,否则继续执行。 - -- 如果为目标字符串,则返回当前路径长度,否则继续执行。 -- 枚举当前状态所有位置所能到达的所有状态,并判断是否访问过该状态。 - -- 如果之前出现过该状态,则继续执行,否则将其存入队列,并标记访问。 - -## 代码 - -```python -class Solution: - def openLock(self, deadends: List[str], target: str) -> int: - queue = collections.deque(['0000']) - visited = set(['0000']) - deadset = set(deadends) - level = 0 - while queue: - size = len(queue) - for _ in range(size): - cur = queue.popleft() - if cur in deadset: - continue - if cur == target: - return level - for i in range(len(cur)): - up = self.upward_adjust(cur, i) - if up not in visited: - queue.append(up) - visited.add(up) - down = self.downward_adjust(cur, i) - if down not in visited: - queue.append(down) - visited.add(down) - level += 1 - return -1 - - def upward_adjust(self, s, i): - s_list = list(s) - if s_list[i] == '9': - s_list[i] = '0' - else: - s_list[i] = chr(ord(s_list[i]) + 1) - return "".join(s_list) - - def downward_adjust(self, s, i): - s_list = list(s) - if s_list[i] == '0': - s_list[i] = '9' - else: - s_list[i] = chr(ord(s_list[i]) - 1) - return "".join(s_list) -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 111. \350\256\241\347\256\227\351\231\244\346\263\225.md" "b/Solutions/\345\211\221\346\214\207 Offer II 111. \350\256\241\347\256\227\351\231\244\346\263\225.md" deleted file mode 100644 index 31cb5e2a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 111. \350\256\241\347\256\227\351\231\244\346\263\225.md" +++ /dev/null @@ -1,111 +0,0 @@ -# [剑指 Offer II 111. 计算除法](https://leetcode.cn/problems/vlzXQL/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图、数组、最短路 -- 难度:中等 - -## 题目大意 - -给定一个变量对数组 `equations` 和一个实数数组 `values` 作为已知条件,其中 `equations[i] = [Ai, Bi]` 和 `values[i]` 共同表示 `Ai / Bi = values[i]`。每个 `Ai` 或 `Bi` 是一个表示单个变量的字符串。 - -再给定一个表示多个问题的数组 `queries`,其中 `queries[j] = [Cj, Dj]` 表示第 `j` 个问题,要求:根据已知条件找出 `Cj / Dj = ?` 的结果作为答案。返回所有问题的答案。如果某个答案无法确定,则用 `-1.0` 代替,如果问题中出现了给定的已知条件中没有出现的表示变量的字符串,则也用 `-1.0` 代替这个答案。 - -## 解题思路 - -在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」的基础上增加了倍数关系。在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」中我们处理传递关系使用了并查集,这道题也是一样,不过在使用并查集的同时还要维护倍数关系。 - -举例说明: - -- `a / b = 2.0`:说明 `a = 2b`,`a` 和 `b` 在同一个集合。 -- `b / c = 3.0`:说明 `b = 3c`,`b` 和 `c` 在同一个集合。 - -根据上述两式可得:`a`、`b`、`c` 都在一个集合中,且 `a = 2b = 6c`。 - -我们可以将同一集合中的变量倍数关系都转换为与根节点变量的倍数关系,比如上述例子中都转变为与 `a` 的倍数关系。 - -具体操作如下: - -- 定义并查集结构,并在并查集中定义一个表示倍数关系的 `multiples` 数组。 -- 遍历 `equations` 数组、`values` 数组,将每个变量按顺序编号,并使用 `union` 将其并入相同集合。 -- 遍历 `queries` 数组,判断两个变量是否在并查集中,并且是否在同一集合。如果找到对应关系,则将计算后的倍数关系存入答案数组,否则则将 `-1` 存入答案数组。 -- 最终输出答案数组。 - -并查集中维护倍数相关方法说明: - -- `find` 方法: - - 递推寻找根节点,并将倍数累乘,然后进行路径压缩,并且更新当前节点的倍数关系。 -- `union` 方法: - - 如果两个节点属于同一集合,则直接返回。 - - 如果两个节点不属于同一个集合,合并之前当前节点的倍数关系更新,然后再进行更新。 -- `is_connect` 方法: - - 如果两个节点不属于同一集合,返回 `-1`。 - - 如果两个节点属于同一集合,则返回倍数关系。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.multiples = [1 for _ in range(n)] - - def find(self, x): - multiple = 1.0 - origin = x - while x != self.parent[x]: - multiple *= self.multiples[x] - x = self.parent[x] - self.parent[origin] = x - self.multiples[origin] = multiple - return x - - def union(self, x, y, multiple): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - self.parent[root_x] = root_y - self.multiples[root_x] = multiple * self.multiples[y] / self.multiples[x] - return - - def is_connected(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x != root_y: - return -1.0 - - return self.multiples[x] / self.multiples[y] - -class Solution: - def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: - equations_size = len(equations) - hash_map = dict() - union_find = UnionFind(2 * equations_size) - - id = 0 - for i in range(equations_size): - equation = equations[i] - var1, var2 = equation[0], equation[1] - if var1 not in hash_map: - hash_map[var1] = id - id += 1 - if var2 not in hash_map: - hash_map[var2] = id - id += 1 - union_find.union(hash_map[var1], hash_map[var2], values[i]) - - queries_size = len(queries) - res = [] - for i in range(queries_size): - query = queries[i] - var1, var2 = query[0], query[1] - if var1 not in hash_map or var2 not in hash_map: - res.append(-1.0) - else: - id1 = hash_map[var1] - id2 = hash_map[var2] - res.append(union_find.is_connected(id1, id2)) - - return res -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 112. \346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" "b/Solutions/\345\211\221\346\214\207 Offer II 112. \346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" deleted file mode 100644 index 08bd1435..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 112. \346\234\200\351\225\277\351\200\222\345\242\236\350\267\257\345\276\204.md" +++ /dev/null @@ -1,45 +0,0 @@ -# [剑指 Offer II 112. 最长递增路径](https://leetcode.cn/problems/fpTFWP/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 -- 难度:困难 - -## 题目大意 - -给定一个 `m * n` 大小的整数矩阵 `matrix`。要求:找出其中最长递增路径的长度。 - -对于每个单元格,可以往上、下、左、右四个方向移动,不能向对角线方向移动或移动到边界外。 - -## 解题思路 - -深度优先搜索。使用二维数组 `record` 存储遍历过的单元格最大路径长度,已经遍历过的单元格就不需要再次遍历了。 - -## 代码 - -```python -class Solution: - max_len = 0 - directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} - - def longestIncreasingPath(self, matrix: List[List[int]]) -> int: - if not matrix: - return 0 - rows, cols = len(matrix), len(matrix[0]) - record = [[0 for _ in range(cols)] for _ in range(rows)] - - def dfs(i, j): - record[i][j] = 1 - for direction in self.directions: - new_i, new_j = i + direction[0], j + direction[1] - if 0 <= new_i < rows and 0 <= new_j < cols and matrix[new_i][new_j] > matrix[i][j]: - if record[new_i][new_j] == 0: - dfs(new_i, new_j) - record[i][j] = max(record[i][j], record[new_i][new_j] + 1) - self.max_len = max(self.max_len, record[i][j]) - - for i in range(rows): - for j in range(cols): - if record[i][j] == 0: - dfs(i, j) - return self.max_len -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 113. \350\257\276\347\250\213\351\241\272\345\272\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 113. \350\257\276\347\250\213\351\241\272\345\272\217.md" deleted file mode 100644 index be9e83b2..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 113. \350\257\276\347\250\213\351\241\272\345\272\217.md" +++ /dev/null @@ -1,53 +0,0 @@ -# [剑指 Offer II 113. 课程顺序](https://leetcode.cn/problems/QA2IGt/) - -- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 -- 难度:中等 - -## 题目大意 - -给定一个整数 `numCourses`,代表这学期必须选修的课程数量,课程编号为 `0` 到 `numCourses - 1`。再给定一个数组 `prerequisites` 表示先修课程关系,其中 `prerequisites[i] = [ai, bi]` 表示如果要学习课程 `ai` 则必须要学习课程 `bi`。 - -要求:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 - -## 解题思路 - -拓扑排序。这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组即可。 - -1. 使用列表 `edges` 存放课程关系图,并统计每门课程节点的入度,存入入度列表 `indegrees`。 - -2. 借助队列 `queue`,将所有入度为 `0` 的节点入队。 - -3. 从队列中选择一个节点,并将其加入到答案数组 `res` 中,再让课程数 -1。 -4. 将该顶点以及该顶点为出发点的所有边的另一个节点入度 -1。如果入度 -1 后的节点入度不为 `0`,则将其加入队列 `queue`。 -5. 重复 3~4 的步骤,直到队列中没有节点。 -6. 最后判断剩余课程数是否为 `0`,如果为 `0`,则返回答案数组 `res`,否则,返回空数组。 - -## 代码 - -```python -class Solution: - def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: - indegrees = [0 for _ in range(numCourses)] - edges = collections.defaultdict(list) - res = [] - for x, y in prerequisites: - edges[y].append(x) - indegrees[x] += 1 - queue = collections.deque([]) - for i in range(numCourses): - if not indegrees[i]: - queue.append(i) - while queue: - y = queue.popleft() - res.append(y) - numCourses -= 1 - for x in edges[y]: - indegrees[x] -= 1 - if not indegrees[x]: - queue.append(x) - if not numCourses: - return res - else: - return [] -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 116. \347\234\201\344\273\275\346\225\260\351\207\217.md" "b/Solutions/\345\211\221\346\214\207 Offer II 116. \347\234\201\344\273\275\346\225\260\351\207\217.md" deleted file mode 100644 index 9e0dc625..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 116. \347\234\201\344\273\275\346\225\260\351\207\217.md" +++ /dev/null @@ -1,60 +0,0 @@ -# [剑指 Offer II 116. 省份数量](https://leetcode.cn/problems/bLyHh0/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -一个班上有 `n` 个同学,其中一些彼此是朋友,另一些不是。如果 `a` 与 `b` 是直接朋友,且 `b` 与 `c` 也是直接朋友,那么 `a` 与 `c` 是间接朋友。 - -现在定义「朋友圈」是由一组直接或间接朋友组成的集合。 - -现在给定一个 `n * n` 的矩阵 `isConnected` 表示班上的朋友关系。其中 `isConnected[i][j] = 1` 表示第 `i` 个同学和第 `j` 个同学是直接朋友,`isConnected[i][j] = 0` 表示第 `i` 个同学和第 `j` 个同学不是直接朋友。 - -要求:根据给定的同学关系,返回「朋友圈」的数量。 - -## 解题思路 - -可以利用并查集来做。具体做法如下: - -遍历矩阵 `isConnected`。如果 `isConnected[i][j] = 1`,将 `i` 节点和 `j` 节点相连。然后判断每个同学节点的根节点,然后统计不重复的根节点有多少个,即为「朋友圈」的数量。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - self.count = n - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: - return - - self.parent[root_x] = root_y - self.count -= 1 - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def findCircleNum(self, isConnected: List[List[int]]) -> int: - size = len(isConnected) - union_find = UnionFind(size) - for i in range(size): - for j in range(i + 1, size): - if isConnected[i][j] == 1: - union_find.union(i, j) - - return union_find.count -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 118. \345\244\232\344\275\231\347\232\204\350\276\271.md" "b/Solutions/\345\211\221\346\214\207 Offer II 118. \345\244\232\344\275\231\347\232\204\350\276\271.md" deleted file mode 100644 index f34af08c..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 118. \345\244\232\344\275\231\347\232\204\350\276\271.md" +++ /dev/null @@ -1,54 +0,0 @@ -# [剑指 Offer II 118. 多余的边](https://leetcode.cn/problems/7LpjUW/) - -- 标签:深度优先搜索、广度优先搜索、并查集、图 -- 难度:中等 - -## 题目大意 - -一个 `n` 个节点的树(节点值为 `1~n`)添加一条边后就形成了图,添加的这条边不属于树中已经存在的边。图的信息记录存储与长度为 `n` 的二维数组 `edges`,`edges[i] = [ai, bi]` 表示图中在 `ai` 和 `bi` 之间存在一条边。 - -现在给定代表边信息的二维数组 `edges`。 - -要求:找到一条可以山区的边,使得删除后的剩余部分是一个有着 `n` 个节点的树。如果有多个答案,则返回数组 `edges` 中最后出现的边。 - -## 解题思路 - -树可以看做是无环的图,这道题就是要找出那条添加边之后成环的边。可以考虑用并查集来做。 - -从前向后遍历每一条边,如果边的两个节点不在同一个集合,就加入到一个集合(链接到同一个根节点)。如果边的节点已经出现在同一个集合里,说明边的两个节点已经连在一起了,再加入这条边一定会出现环,则这条边就是所求答案。 - -## 代码 - -```python -class UnionFind: - - def __init__(self, n): - self.parent = [i for i in range(n)] - - def find(self, x): - while x != self.parent[x]: - self.parent[x] = self.parent[self.parent[x]] - x = self.parent[x] - return x - - def union(self, x, y): - root_x = self.find(x) - root_y = self.find(y) - self.parent[root_x] = root_y - - def is_connected(self, x, y): - return self.find(x) == self.find(y) - -class Solution: - def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: - size = len(edges) - union_find = UnionFind(size + 1) - - for edge in edges: - if union_find.is_connected(edge[0], edge[1]): - return edge - union_find.union(edge[0], edge[1]) - - return None -``` - diff --git "a/Solutions/\345\211\221\346\214\207 Offer II 119. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" "b/Solutions/\345\211\221\346\214\207 Offer II 119. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" deleted file mode 100644 index e5a87c1a..00000000 --- "a/Solutions/\345\211\221\346\214\207 Offer II 119. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [剑指 Offer II 119. 最长连续序列](https://leetcode.cn/problems/WhsWhI/) - -- 标签:并查集、数组、哈希表 -- 难度:中等 - -## 题目大意 - -给定一个未排序的整数数组 `nums`。 - -要求:找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。并且要用时间复杂度为 $O(n)$ 的算法解决此问题。 - -## 解题思路 - -暴力做法有两种思路。第 1 种思路是先排序再依次判断,这种做法时间复杂度最少是 $O(n \log n)$。第 2 种思路是枚举数组中的每个数 `num`,考虑以其为起点,不断尝试匹配 `num + 1`、`num + 2`、`...` 是否存在,最长匹配次数为 `len(nums)`。这样下来时间复杂度为 $O(n^2)$。但是可以使用集合或哈希表优化这个步骤。 - -- 先将数组存储到集合中进行去重,然后使用 `curr_streak` 维护当前连续序列长度,使用 `ans` 维护最长连续序列长度。 -- 遍历集合中的元素,对每个元素进行判断,如果该元素不是序列的开始(即 `num - 1` 在集合中),则跳过。 -- 如果 `num - 1` 不在集合中,说明 `num` 是序列的开始,判断 `num + 1` 、`nums + 2`、`...` 是否在哈希表中,并不断更新当前连续序列长度 `curr_streak`。并在遍历结束之后更新最长序列的长度。 -- 最后输出最长序列长度。 - -将数组存储到集合中进行去重的操作的时间复杂度是 $O(n)$。查询每个数是否在集合中的时间复杂度是 $O(1)$ ,并且跳过了所有不是起点的元素。更新当前连续序列长度 `curr_streak` 的时间复杂度是 $O(n)$,所以最终的时间复杂度是 $O(n)$。符合题意要求。 - -## 代码 - -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - ans = 0 - nums_set = set(nums) - for num in nums_set: - if num - 1 not in nums_set: - curr_num = num - curr_streak = 1 - - while curr_num + 1 in nums_set: - curr_num += 1 - curr_streak += 1 - ans = max(ans, curr_streak) - - return ans -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 01.07. \346\227\213\350\275\254\347\237\251\351\230\265.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 01.07. \346\227\213\350\275\254\347\237\251\351\230\265.md" deleted file mode 100644 index e812c7a9..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 01.07. \346\227\213\350\275\254\347\237\251\351\230\265.md" +++ /dev/null @@ -1,63 +0,0 @@ -# [面试题 01.07. 旋转矩阵](https://leetcode.cn/problems/rotate-matrix-lcci/) - -- 标签:数组、数学、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `n * n` 大小的二维矩阵用来表示图像,其中每个像素的大小为 4 字节。 - -要求:设计一种算法,将图像旋转 90 度。并且要不占用额外内存空间。 - -## 解题思路 - -题目要求不占用额外内存空间,就是要在原二维矩阵上直接进行旋转操作。我们可以用翻转操作代替旋转操作。具体可以分为两步: - -1. 上下翻转。 - -2. 主对角线翻转。 - -举个例子: - -``` - 1 2 3 4 - 5 6 7 8 - 9 10 11 12 -13 14 15 16 -``` - -上下翻转后变为: - -``` -13 14 15 16 - 9 10 11 12 - 5 6 7 8 - 1 2 3 4 -``` - -在经过主对角线翻转后变为: - -``` -13 9 5 1 -14 10 6 2 -15 11 7 3 -16 12 8 4 -``` - -## 代码 - -```python -class Solution: - def rotate(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - size = len(matrix) - for i in range(size // 2): - for j in range(size): - matrix[i][j], matrix[size - i - 1][j] = matrix[size - i - 1][j], matrix[i][j] - for i in range(size): - for j in range(i): - matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 01.08. \351\233\266\347\237\251\351\230\265.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 01.08. \351\233\266\347\237\251\351\230\265.md" deleted file mode 100644 index 18088f93..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 01.08. \351\233\266\347\237\251\351\230\265.md" +++ /dev/null @@ -1,66 +0,0 @@ -# [面试题 01.08. 零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) - -- 标签:数组、哈希表、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 大小的二维矩阵 `matrix`。 - -要求:编写一种算法,如果矩阵中某个元素为 `0`,增将其所在行与列清零。 - -## 解题思路 - -直观上可以使用两个数组或者集合来标记行和列出现 `0` 的情况,但更好的做法是不用开辟新的数组或集合,直接原本二维矩阵 `matrix` 的空间。使用数组原本的元素进行记录出现 0 的情况。 - -设定两个变量 `flag_row0`、`flag_col0` 来标记第一行、第一列是否出现了 `0`。 - -接下来我们使用数组第一行、第一列来标记 `0` 的情况。 - -对数组除第一行、第一列之外的每个元素进行遍历,如果某个元素出现 `0` 了,则使用数组的第一行、第一列对应位置来存储 `0` 的标记。 - -再对数组除第一行、第一列之外的每个元素进行遍历,通过对第一行、第一列的标记 0 情况,进行置为 `0` 的操作。 - -最后再根据 `flag_row0`、`flag_col0` 的标记情况,对第一行、第一列进行置为 `0` 的操作。 - -## 代码 - -```python -class Solution: - def setZeroes(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - rows = len(matrix) - cols = len(matrix[0]) - flag_col0 = False - flag_row0 = False - for i in range(rows): - if matrix[i][0] == 0: - flag_col0 = True - break - - for j in range(cols): - if matrix[0][j] == 0: - flag_row0 = True - break - - for i in range(1, rows): - for j in range(1, cols): - if matrix[i][j] == 0: - matrix[i][0] = matrix[0][j] = 0 - - for i in range(1, rows): - for j in range(1, cols): - if matrix[i][0] == 0 or matrix[0][j] == 0: - matrix[i][j] = 0 - - if flag_col0: - for i in range(rows): - matrix[i][0] = 0 - - if flag_row0: - for j in range(cols): - matrix[0][j] = 0 -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 02.02. \350\277\224\345\233\236\345\200\222\346\225\260\347\254\254 k \344\270\252\350\212\202\347\202\271.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 02.02. \350\277\224\345\233\236\345\200\222\346\225\260\347\254\254 k \344\270\252\350\212\202\347\202\271.md" deleted file mode 100644 index 2ea95755..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 02.02. \350\277\224\345\233\236\345\200\222\346\225\260\347\254\254 k \344\270\252\350\212\202\347\202\271.md" +++ /dev/null @@ -1,34 +0,0 @@ -# [面试题 02.02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) - -- 标签:链表、双指针 -- 难度:简单 - -## 题目大意 - -给定一个链表的头节点 `head`,以及一个整数 `k`。 - -要求:返回链表的倒数第 `k` 个节点的值。 - -## 解题思路 - -常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,返回该位置上的节点。 - -如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `k` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `k` 个节点位置。返回该该位置上的节点即可。 - -## 代码 - -```python -class Solution: - def kthToLast(self, head: ListNode, k: int) -> int: - slow = head - fast = head - for _ in range(k): - if fast == None: - return fast - fast = fast.next - while fast: - slow = slow.next - fast = fast.next - return slow.val -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 02.05. \351\223\276\350\241\250\346\261\202\345\222\214.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 02.05. \351\223\276\350\241\250\346\261\202\345\222\214.md" deleted file mode 100644 index 9708ba11..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 02.05. \351\223\276\350\241\250\346\261\202\345\222\214.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [面试题 02.05. 链表求和](https://leetcode.cn/problems/sum-lists-lcci/) - -- 标签:递归、链表、数学 -- 难度:中等 - -## 题目大意 - -给定两个非空的链表 `l1` 和 `l2`,表示两个非负整数,每位数字都是按照逆序的方式存储的,每个节点存储一位数字。 - -要求:计算两个整数的和,并逆序返回表示和的链表。 - -## 解题思路 - -模拟大数加法,按位相加,将结果添加到新链表上。需要注意进位和对 `10` 取余。 - -## 代码 - -```python -class Solution: - def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: - head = curr = ListNode(0) - carry = 0 - while l1 or l2 or carry: - if l1: - num1 = l1.val - l1 = l1.next - else: - num1 = 0 - if l2: - num2 = l2.val - l2 = l2.next - else: - num2 = 0 - - sum = num1 + num2 + carry - carry = sum // 10 - - curr.next = ListNode(sum % 10) - curr = curr.next - - return head.next -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 02.06. \345\233\236\346\226\207\351\223\276\350\241\250.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 02.06. \345\233\236\346\226\207\351\223\276\350\241\250.md" deleted file mode 100644 index bc2f1d0b..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 02.06. \345\233\236\346\226\207\351\223\276\350\241\250.md" +++ /dev/null @@ -1,28 +0,0 @@ -# [面试题 02.06. 回文链表](https://leetcode.cn/problems/palindrome-linked-list-lcci/) - -- 标签:栈、递归、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定一个链表的头节点 `head`。 - -要求:判断该链表是否为回文链表。 - -## 解题思路 - -利用数组,将链表元素依次存入。然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置,依次判断首尾对应元素是否相等,若都相等,则为回文链表。若不相等,则不是回文链表。 - -## 代码 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - nodes = [] - p1 = head - while p1 != None: - nodes.append(p1.val) - p1 = p1.next - return nodes == nodes[::-1] -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 02.07. \351\223\276\350\241\250\347\233\270\344\272\244.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 02.07. \351\223\276\350\241\250\347\233\270\344\272\244.md" deleted file mode 100644 index 06137e81..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 02.07. \351\223\276\350\241\250\347\233\270\344\272\244.md" +++ /dev/null @@ -1,50 +0,0 @@ -# [面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) - -- 标签:哈希表、链表、双指针 -- 难度:简单 - -## 题目大意 - -给定两个链表的头节点 `headA`、`headB`。 - -要求:找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 `None` 。 - -比如:链表 A 为 `[4, 1, 8, 4, 5]`,链表 B 为 `[5, 0, 1, 8, 4, 5]`。则如下图所示,两个链表相交的起始节点为 `8`,则输出结果为 `8`。 - -![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) - - - - - -## 解题思路 - -如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 `A` 的长度为 `m`、链表 `B` 的长度为 `n`,他们的相交序列有 `k` 个,则相交情况可以如下如所示: - -![](https://qcdn.itcharge.cn/images/20210401113538.png) - -现在问题是如何找到 `m - k` 或者 `n - k` 的位置。 - -考虑将链表 `A` 的末尾拼接上链表 `B`,链表 `B` 的末尾拼接上链表 `A`。 - -然后使用两个指针 `pA` 、`pB`,分别从链表 `A`、链表 `B` 的头节点开始遍历,如果走到共同的节点,则返回该节点。 - -否则走到两个链表末尾,返回 `None`。 - -![](https://qcdn.itcharge.cn/images/20210401114100.png) - -## 代码 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: - if headA == None or headB == None: - return None - pA = headA - pB = headB - while pA != pB : - pA = pA.next if pA != None else headB - pB = pB.next if pB != None else headA - return pA -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 02.08. \347\216\257\350\267\257\346\243\200\346\265\213.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 02.08. \347\216\257\350\267\257\346\243\200\346\265\213.md" deleted file mode 100644 index f0e36c02..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 02.08. \347\216\257\350\267\257\346\243\200\346\265\213.md" +++ /dev/null @@ -1,43 +0,0 @@ -# [面试题 02.08. 环路检测](https://leetcode.cn/problems/linked-list-cycle-lcci/) - -- 标签:哈希表、链表、双指针 -- 难度:中等 - -## 题目大意 - -给定一个链表的头节点 `head`。 - -要求:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 None。 - -## 解题思路 - -利用两个指针,一个慢指针每次前进一步,快指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 - -如果有环,则再定义一个指针,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 - -这是因为:假设入环位置为 A,快慢指针在在 B 点相遇,则相遇时慢指针走了 $a + b$ 步,快指针走了 $a + n(b+c) + b$ 步。 - -$2(a + b) = a + n(b + c) + b$。可以推出:$a = c + (n-1)(b + c)$。 - -我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 - -## 代码 - -```python -class Solution: - def detectCycle(self, head: ListNode) -> ListNode: - fast, slow = head, head - while True: - if not fast or not fast.next: - return None - fast = fast.next.next - slow = slow.next - if fast == slow: - break - - ans = head - while ans != slow: - ans, slow = ans.next, slow.next - return ans -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 03.02. \346\240\210\347\232\204\346\234\200\345\260\217\345\200\274.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 03.02. \346\240\210\347\232\204\346\234\200\345\260\217\345\200\274.md" deleted file mode 100644 index 2e539d31..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 03.02. \346\240\210\347\232\204\346\234\200\345\260\217\345\200\274.md" +++ /dev/null @@ -1,55 +0,0 @@ -# [面试题 03.02. 栈的最小值](https://leetcode.cn/problems/min-stack-lcci/) - -- 标签:栈、设计 -- 难度:简单 - -## 题目大意 - -设计一个「栈」,要求实现 `push` ,`pop` ,`top` ,`getMin` 操作,其中 `getMin` 要求能在常数时间内实现。 - -## 解题思路 - -使用一个栈,栈元素中除了保存当前值之外,再保存一个当前最小值。 - -- `push` 操作:如果栈不为空,则判断当前值与栈顶元素所保存的最小值,并更新当前最小值,将新元素保存到栈中。 -- `pop`操作:正常出栈 -- `top` 操作:返回栈顶元素保存的值。 -- `getMin` 操作:返回栈顶元素保存的最小值。 - -## 代码 - -```python -class MinStack: - - def __init__(self): - """ - initialize your data structure here. - """ - self.stack = [] - - class Node: - def __init__(self, x): - self.val = x - self.min = x - - def push(self, x: int) -> None: - node = self.Node(x) - if len(self.stack) == 0: - self.stack.append(node) - else: - topNode = self.stack[-1] - if node.min > topNode.min: - node.min = topNode.min - - self.stack.append(node) - - def pop(self) -> None: - self.stack.pop() - - def top(self) -> int: - return self.stack[-1].val - - def getMin(self) -> int: - return self.stack[-1].min -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 03.04. \345\214\226\346\240\210\344\270\272\351\230\237.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 03.04. \345\214\226\346\240\210\344\270\272\351\230\237.md" deleted file mode 100644 index 391036ae..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 03.04. \345\214\226\346\240\210\344\270\272\351\230\237.md" +++ /dev/null @@ -1,70 +0,0 @@ -# [面试题 03.04. 化栈为队](https://leetcode.cn/problems/implement-queue-using-stacks-lcci/) - -- 标签:栈、设计、队列 -- 难度:简单 - -## 题目大意 - -要求:实现一个 MyQueue 类,要求仅使用两个栈实现先入先出队列。 - -## 解题思路 - -使用两个栈,`inStack` 用于输入,`outStack` 用于输出。 - -- `push` 操作:将元素压入 `inStack` 中。 -- `pop` 操作:如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 -- `peek` 操作:和 `pop` 操作类似,只不过最后一步不需要取出顶层元素,直接将其返回即可。 -- `empty` 操作:如果 `inStack` 和 `outStack` 都为空,则队列为空,否则队列不为空。 - -## 代码 - -```python -class MyQueue: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.inStack = [] - self.outStack = [] - - - def push(self, x: int) -> None: - """ - Push element x to the back of queue. - """ - self.inStack.append(x) - - - def pop(self) -> int: - """ - Removes the element from in front of queue and returns that element. - """ - if (len(self.outStack) == 0): - while (len(self.inStack) != 0): - self.outStack.append(self.inStack[-1]) - self.inStack.pop() - top = self.outStack[-1] - self.outStack.pop() - return top - - - def peek(self) -> int: - """ - Get the front element. - """ - if (len(self.outStack) == 0): - while (len(self.inStack) != 0): - self.outStack.append(self.inStack[-1]) - self.inStack.pop() - top = self.outStack[-1] - return top - - - def empty(self) -> bool: - """ - Returns whether the queue is empty. - """ - return len(self.outStack) == 0 and len(self.inStack) == 0 -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 04.02. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 04.02. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" deleted file mode 100644 index 3c5f5041..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 04.02. \346\234\200\345\260\217\351\253\230\345\272\246\346\240\221.md" +++ /dev/null @@ -1,30 +0,0 @@ -# [面试题 04.02. 最小高度树](https://leetcode.cn/problems/minimum-height-tree-lcci/) - -- 标签:树、二叉搜索树、数组、分治、二叉树 -- 难度:简单 - -## 题目大意 - -给定一个升序的有序数组 `nums`。 - -要求:创建一棵高度最小的二叉搜索树(高度平衡的二叉搜索树)。 - -## 解题思路 - -直观上,如果把数组的中间元素当做根,那么数组左侧元素都小于根节点,右侧元素都大于根节点,且左右两侧元素个数相同,或最多相差 `1` 个。那么构建的树高度差也不会超过 `1`。所以猜想出:如果左右子树约平均,树就越平衡。这样我们就可以每次取中间元素作为当前的根节点,两侧的元素作为左右子树递归建树,左侧区间 `[L, mid - 1]` 作为左子树,右侧区间 `[mid + 1, R]` 作为右子树。 - -## 代码 - -```python -class Solution: - def sortedArrayToBST(self, nums: List[int]) -> TreeNode: - size = len(nums) - if size == 0: - return None - mid = size // 2 - root = TreeNode(nums[mid]) - root.left = Solution.sortedArrayToBST(self, nums[:mid]) - root.right = Solution.sortedArrayToBST(self, nums[mid + 1:]) - return root -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 04.05. \345\220\210\346\263\225\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 04.05. \345\220\210\346\263\225\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" deleted file mode 100644 index a04e652c..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 04.05. \345\220\210\346\263\225\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [面试题 04.05. 合法二叉搜索树](https://leetcode.cn/problems/legal-binary-search-tree-lcci/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`。 - -要求:检查该二叉树是否为二叉搜索树。 - -二叉搜索树特征: - -- 节点的左子树只包含小于当前节点的数。 -- 节点的右子树只包含大于当前节点的数。 -- 所有左子树和右子树自身必须也是二叉搜索树。 - -## 解题思路 - -根据题意进行递归遍历即可。前序、中序、后序遍历都可以。 - -以前序遍历为例,递归函数为:`preorderTraversal(root, min_v, max_v)` - -前序遍历时,先判断根节点的值是否在 `(min_v, max_v)` 之间。如果不在则直接返回 `False`。在区间内,则继续递归检测左右子树是否满足,都满足才是一棵二叉搜索树。 - -递归遍历左子树的时候,要将上界 `max_v` 改为左子树的根节点值,因为左子树上所有节点的值均小于根节点的值。同理,遍历右子树的时候,要将下界 `min_v` 改为右子树的根节点值,因为右子树上所有节点的值均大于根节点。 - -## 代码 - -```python -class Solution: - def isValidBST(self, root: TreeNode) -> bool: - def preorderTraversal(root, min_v, max_v): - if root == None: - return True - if root.val >= max_v or root.val <= min_v: - return False - return preorderTraversal(root.left, min_v, root.val) and preorderTraversal(root.right, root.val, max_v) - - return preorderTraversal(root, float('-inf'), float('inf')) -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 04.06. \345\220\216\347\273\247\350\200\205.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 04.06. \345\220\216\347\273\247\350\200\205.md" deleted file mode 100644 index f344b8f8..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 04.06. \345\220\216\347\273\247\350\200\205.md" +++ /dev/null @@ -1,35 +0,0 @@ -# [面试题 04.06. 后继者](https://leetcode.cn/problems/successor-lcci/) - -- 标签:树、深度优先搜索、二叉搜索树、二叉树 -- 难度:中等 - -## 题目大意 - -给定一棵二叉搜索树的根节点 `root` 和其中一个节点 `p`。 - -要求:找出该节点在树中的中序后继,即按照中序遍历的顺序节点 `p` 的下一个节点。如果节点 `p` 没有对应的下一个节点,则返回 `None`。 - -## 解题思路 - -递归遍历,具体步骤如下: - -- 如果 `root.val` 小于等于 `p.val`,则直接从 `root` 的右子树递归查找比 `p.val` 大的节点,从而找到中序后继。 -- 如果 `root.val` 大于 `p.val`,则 `root` 有可能是中序后继,也有可能是 `root` 的左子树。则从 `root` 的左子树递归查找更接近(更小的)。如果查找的值为 `None`,则当前 `root` 就是中序后继,否则继续递归查找,从而找到中序后继。 - -## 代码 - -```python -class Solution: - def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode: - if not p or not root: - return None - - if root.val <= p.val: - node = self.inorderSuccessor(root.right, p) - else: - node = self.inorderSuccessor(root.left, p) - if not node: - node = root - return node -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 04.08. \351\246\226\344\270\252\345\205\261\345\220\214\347\245\226\345\205\210.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 04.08. \351\246\226\344\270\252\345\205\261\345\220\214\347\245\226\345\205\210.md" deleted file mode 100644 index 029ca286..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 04.08. \351\246\226\344\270\252\345\205\261\345\220\214\347\245\226\345\205\210.md" +++ /dev/null @@ -1,57 +0,0 @@ -# [面试题 04.08. 首个共同祖先](https://leetcode.cn/problems/first-common-ancestor-lcci/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树,要求找到该树中指定节点 `p`、`q` 的最近公共祖先: - -- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p = node`,则称 `node` 是 `p` 的祖先。 - -- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 - -## 解题思路 - -设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: - -- `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 -- `p == lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 -- `q == lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 - -下面递归求解 `lca_node`。递归需要满足以下条件: - -- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 -- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 -- 如果 `p`、`q` 都不存在,则返回存在的一个。 - -具体思路为: - -- 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 -- 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node`。 -- 递归遍历左子树、右子树,并判断左右子树结果。 - - 如果左子树为空,则返回右子树。 - - 如果右子树为空,则返回左子树。 - - 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 - - 如果左右子树都为空,则返回空。 - -## 代码 - -```python -class Solution: - def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: - if root == p or root == q: - return root - - if root: - node_left = self.lowestCommonAncestor(root.left, p, q) - node_right = self.lowestCommonAncestor(root.right, p, q) - if node_left and node_right: - return root - elif not node_left: - return node_right - else: - return node_left - return None -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 04.12. \346\261\202\345\222\214\350\267\257\345\276\204.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 04.12. \346\261\202\345\222\214\350\267\257\345\276\204.md" deleted file mode 100644 index c1d57d5a..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 04.12. \346\261\202\345\222\214\350\267\257\345\276\204.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [面试题 04.12. 求和路径](https://leetcode.cn/problems/paths-with-sum-lcci/) - -- 标签:树、深度优先搜索、二叉树 -- 难度:中等 - -## 题目大意 - -给定一个二叉树的根节点 `root`,和一个整数 `targetSum`。 - -要求:求出该二叉树里节点值之和等于 `targetSum` 的路径的数目。 - -- 路径:不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 - -## 解题思路 - -直观想法是: - -以每一个节点 `node` 为起始节点,向下检测延伸的路径。递归遍历每一个节点所有可能的路径,然后将这些路径数目加起来即为答案。 - -但是这样会存在许多重复计算。我们可以定义节点的前缀和来减少重复计算。 - -- 节点的前缀和:从根节点到当前节点路径上所有节点的和。 - -有了节点的前缀和,我们就可以通过前缀和来计算两节点之间的路劲和。即:`则两节点之间的路径和 = 两节点之间的前缀和之差`。 - -为了计算符合要求的路径数量,我们用哈希表存储「前缀和的节点数量」。哈希表以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值。这样就能通过哈希表直接计算出符合要求的路径数量,从而累加到答案上。 - -整个算法的具体步骤如下: - -- 通过先序遍历方式递归遍历二叉树,计算每一个节点的前缀和 `cur_sum`。 -- 从哈希表中取出 `cur_sum - target_sum` 的路径数量(也就是表示存在从前缀和为 `cur_sum - target_sum` 所对应的节点到前缀和为 `cur_sum` 所对应的节点的路径个数)累加到答案 `res` 中。 -- 然后以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值,存入哈希表中。 -- 递归遍历二叉树,并累加答案值。 -- 恢复哈希表「当前前缀和的节点数量」,返回答案。 - -## 代码 - -```python - -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.04. \345\271\202\351\233\206.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.04. \345\271\202\351\233\206.md" deleted file mode 100644 index 4ae7a3e4..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.04. \345\271\202\351\233\206.md" +++ /dev/null @@ -1,31 +0,0 @@ -# [面试题 08.04. 幂集](https://leetcode.cn/problems/power-set-lcci/) - -- 标签:位运算、数组、回溯 -- 难度:中等 - -## 题目大意 - -给定一个集合 `nums`,集合中不包含重复元素。 - -压枪欧秋:返回该集合的所有子集。 - -## 解题思路 - -回溯算法,遍历集合 `nums`。为了使得子集不重复,每次遍历从当前位置的下一个位置进行下一层遍历。 - -## 代码 - -```python -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - def backtrack(size, subset, index): - res.append(subset) - for i in range(index, size): - backtrack(size, subset + [nums[i]], i + 1) - - size = len(nums) - res = list() - backtrack(size, [], 0) - return res -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.07. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.07. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" deleted file mode 100644 index 0ad3836e..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.07. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" +++ /dev/null @@ -1,42 +0,0 @@ -# [面试题 08.07. 无重复字符串的排列组合](https://leetcode.cn/problems/permutation-i-lcci/) - -- 标签:字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `S`。 - -要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组,但里边不能有重复元素。 - -## 解题思路 - -使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。然后进行回溯遍历。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, S, visited): - if len(self.path) == len(S): - self.res.append(''.join(self.path)) - return - for i in range(len(S)): - if not visited[i]: - visited[i] = True - self.path.append(S[i]) - self.backtrack(S, visited) - self.path.pop() - visited[i] = False - - def permutation(self, S: str) -> List[str]: - self.res.clear() - self.path.clear() - visited = [False for _ in range(len(S))] - self.backtrack(S, visited) - return self.res -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.08. \346\234\211\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.08. \346\234\211\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" deleted file mode 100644 index 4d289e24..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.08. \346\234\211\351\207\215\345\244\215\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\347\273\204\345\220\210.md" +++ /dev/null @@ -1,51 +0,0 @@ -# [面试题 08.08. 有重复字符串的排列组合](https://leetcode.cn/problems/permutation-ii-lcci/) - -- 标签:字符串、回溯 -- 难度:中等 - -## 题目大意 - -给定一个字符串 `s`,字符串中包含有重复字符。 - -要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组。 - -## 解题思路 - -因为原字符串可能含有重复元素,所以在回溯的时候需要进行去重。先将字符串 `s` 转为 `list` 列表,再对列表进行排序,然后使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 - -然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 - -然后进行回溯遍历。 - -## 代码 - -```python -class Solution: - res = [] - path = [] - - def backtrack(self, ls, visited): - if len(self.path) == len(ls): - self.res.append(''.join(self.path)) - return - for i in range(len(ls)): - if i > 0 and ls[i] == ls[i - 1] and not visited[i - 1]: - continue - - if not visited[i]: - visited[i] = True - self.path.append(ls[i]) - self.backtrack(ls, visited) - self.path.pop() - visited[i] = False - - def permutation(self, S: str) -> List[str]: - self.res.clear() - self.path.clear() - ls = list(S) - ls.sort() - visited = [False for _ in range(len(S))] - self.backtrack(ls, visited) - return self.res -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.09. \346\213\254\345\217\267.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.09. \346\213\254\345\217\267.md" deleted file mode 100644 index 52c7f584..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.09. \346\213\254\345\217\267.md" +++ /dev/null @@ -1,41 +0,0 @@ -# [面试题 08.09. 括号](https://leetcode.cn/problems/bracket-lcci/) - -- 标签:字符串、动态规划、回溯 -- 难度:中等 - -## 题目大意 - -给定一个整数 `n`。 - -要求:生成所有有可能且有效的括号组合。 - -## 解题思路 - -通过回溯算法生成所有答案。为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 - -如果在当前组合中增加一个 `(`,则 `symbol += 1`,如果增加一个 `)`,则 `symbol -= 1`。显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 - -如果最终生成 `2 * n` 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 - -最终输出最终答案数组。 - -## 代码 - -```python -class Solution: - def generateParenthesis(self, n: int) -> List[str]: - def backtrack(parenthesis, symbol, index): - if n * 2 == index: - if symbol == 0: - parentheses.append(parenthesis) - else: - if symbol < n: - backtrack(parenthesis + '(', symbol + 1, index + 1) - if symbol > 0: - backtrack(parenthesis + ')', symbol - 1, index + 1) - - parentheses = list() - backtrack("", 0, 0) - return parentheses -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.10. \351\242\234\350\211\262\345\241\253\345\205\205.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.10. \351\242\234\350\211\262\345\241\253\345\205\205.md" deleted file mode 100644 index 90b8a60c..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.10. \351\242\234\350\211\262\345\241\253\345\205\205.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [面试题 08.10. 颜色填充](https://leetcode.cn/problems/color-fill-lcci/) - -- 标签:深度优先搜索、广度优先搜索、数组、矩阵 -- 难度:简单 - -## 题目大意 - -给定一个二维整数矩阵 `image`,其中 `image[i][j]` 表示矩阵第 `i` 行、第 `j` 列上网格块的颜色值。再给定一个起始位置 `(sr, sc)`,以及一个目标颜色 `newColor`。 - -要求:对起始位置 `(sr, sc)` 所在位置周围区域填充颜色为 `newColor`。并返回填充后的图像 `image`。 - -- 周围区域:颜色相同且在上、下、左、右四个方向上存在相连情况的若干元素。 - -## 解题思路 - -深度优先搜索。使用二维数组 `visited` 标记访问过的节点。遍历上、下、左、右四个方向上的点。如果下一个点位置越界,或者当前位置与下一个点位置颜色不一样,则对该节点进行染色。 - -在遍历的过程中注意使用 `visited` 标记访问过的节点,以免重复遍历。 - -## 代码 - -```python -class Solution: - directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def dfs(self, image, i, j, origin_color, color, visited): - rows, cols = len(image), len(image[0]) - - for direct in self.directs: - new_i = i + direct[0] - new_j = j + direct[1] - - # 下一个位置越界,则当前点在边界,对其进行着色 - if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: - image[i][j] = color - continue - - # 如果访问过,则跳过 - if visited[new_i][new_j]: - continue - - # 如果下一个位置颜色与当前颜色相同,则继续搜索 - if image[new_i][new_j] == origin_color: - visited[new_i][new_j] = True - self.dfs(image, new_i, new_j, origin_color, color, visited) - # 下一个位置颜色与当前颜色不同,则当前位置为连通区域边界,对其进行着色 - else: - image[i][j] = color - - def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: - if not image: - return image - - rows, cols = len(image), len(image[0]) - visited = [[False for _ in range(cols)] for _ in range(rows)] - visited[sr][sc] = True - - self.dfs(image, sr, sc, image[sr][sc], newColor, visited) - - return image -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 08.12. \345\205\253\347\232\207\345\220\216.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 08.12. \345\205\253\347\232\207\345\220\216.md" deleted file mode 100644 index f006a44f..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 08.12. \345\205\253\347\232\207\345\220\216.md" +++ /dev/null @@ -1,73 +0,0 @@ -# [面试题 08.12. 八皇后](https://leetcode.cn/problems/eight-queens-lcci/) - -- 标签:数组、回溯 -- 难度:困难 - -## 题目大意 - -- n 皇后问题:将 n 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 -- 皇后彼此不能相互攻击:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 - -现在给定一个整数 `n`,返回所有不同的「n 皇后问题」的解决方案。每一种解法包含一个不同的「n 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 - -## 解题思路 - -经典的回溯问题。使用 `chessboard` 来表示棋盘,`Q` 代表皇后,`.` 代表空位,初始都为 `.`。然后使用 `res` 存放最终答案。 - -先定义棋盘合理情况判断方法,判断同一条横线、纵线或者斜线上是否存在两个以上的皇后。 - -再定义回溯方法,从第一行开始进行遍历。 - -- 如果当前行 `row` 等于 `n`,则当前棋盘为一个可行方案,将其拼接加入到 `res` 数组中。 -- 遍历 `[0, n]` 列元素,先验证棋盘是否可行,如果可行: - - 将当前行当前列尝试换为 `Q`。 - - 然后继续递归下一行。 - - 再将当前行回退为 `.`。 -- 最终返回 `res` 数组。 - -## 代码 - -```python -class Solution: - res = [] - def backtrack(self, n: int, row: int, chessboard: List[List[str]]): - if row == n: - temp_res = [] - for temp in chessboard: - temp_str = ''.join(temp) - temp_res.append(temp_str) - self.res.append(temp_res) - return - for col in range(n): - if self.isValid(n, row, col, chessboard): - chessboard[row][col] = 'Q' - self.backtrack(n, row + 1, chessboard) - chessboard[row][col] = '.' - - def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): - for i in range(row): - if chessboard[i][col] == 'Q': - return False - - i, j = row - 1, col - 1 - while i >= 0 and j >= 0: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j -= 1 - i, j = row - 1, col + 1 - while i >= 0 and j < n: - if chessboard[i][j] == 'Q': - return False - i -= 1 - j += 1 - - return True - - def solveNQueens(self, n: int) -> List[List[str]]: - self.res.clear() - chessboard = [['.' for _ in range(n)] for _ in range(n)] - self.backtrack(n, 0, chessboard) - return self.res -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 10.01. \345\220\210\345\271\266\346\216\222\345\272\217\347\232\204\346\225\260\347\273\204.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 10.01. \345\220\210\345\271\266\346\216\222\345\272\217\347\232\204\346\225\260\347\273\204.md" deleted file mode 100644 index 32c5c1b9..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 10.01. \345\220\210\345\271\266\346\216\222\345\272\217\347\232\204\346\225\260\347\273\204.md" +++ /dev/null @@ -1,68 +0,0 @@ -# [面试题 10.01. 合并排序的数组](https://leetcode.cn/problems/sorted-merge-lcci/) - -- 标签:数组、双指针、排序 -- 难度:简单 - -## 题目大意 - -**描述**:给定两个排序后的数组 `A` 和 `B`,以及 `A` 的元素数量 `m` 和 `B` 的元素数量 `n`。 `A` 的末端有足够的缓冲空间容纳 `B`。 - -**要求**:编写一个方法,将 `B` 合并入 `A` 并排序。 - -**说明**: - -- $A.length == n + m$。 - -**示例**: - -- 示例 1: - -```python -输入: -A = [1,2,3,0,0,0], m = 3 -B = [2,5,6], n = 3 - -输出: [1,2,2,3,5,6] -``` - -## 解题思路 - -### 思路 1:归并排序 - -可以利用归并排序算法的归并步骤思路。 - -1. 使用两个指针分别表示`A`、`B` 正在处理的元素下标。 -2. 对 `A`、`B` 进行归并操作,将结果存入新数组中。归并之后,再将所有元素赋值到数组 `A` 中。 - -### 思路 1:代码 - -```python -class Solution: - def merge(self, A: List[int], m: int, B: List[int], n: int) -> None: - """ - Do not return anything, modify A in-place instead. - """ - arr = [] - index_A, index_B = 0, 0 - while index_A < m and index_B < n: - if A[index_A] <= B[index_B]: - arr.append(A[index_A]) - index_A += 1 - else: - arr.append(B[index_B]) - index_B += 1 - while index_A < m: - arr.append(A[index_A]) - index_A += 1 - while index_B < n: - arr.append(B[index_B]) - index_B += 1 - for i in range(m + n): - A[i] = arr[i] -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(m + n)$。 -- **空间复杂度**:$O(m + n)$。 - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 10.02. \345\217\230\344\275\215\350\257\215\347\273\204.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 10.02. \345\217\230\344\275\215\350\257\215\347\273\204.md" deleted file mode 100644 index 8243e315..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 10.02. \345\217\230\344\275\215\350\257\215\347\273\204.md" +++ /dev/null @@ -1,38 +0,0 @@ -# [面试题 10.02. 变位词组](https://leetcode.cn/problems/group-anagrams-lcci/) - -- 标签:数组、哈希表、字符串、排序 -- 难度:中等 - -## 题目大意 - -给定一个字符串数组 `strs`。 - -要求:将所有变位词组合在一起。不需要考虑输出顺序。 - -- 变位词:字母相同,但排列不同的字符串。 - -## 解题思路 - -使用哈希表记录变位词。对每一个字符串进行排序,按照 `排序字符串:变位词数组` 的键值顺序进行存储。 - -最终将哈希表的值转换为对应数组返回结果。 - -## 代码 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - str_dict = dict() - res = [] - for s in strs: - sort_s = str(sorted(s)) - if sort_s in str_dict: - str_dict[sort_s] += [s] - else: - str_dict[sort_s] = [s] - - for sort_s in str_dict: - res += [str_dict[sort_s]] - return res -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 10.09. \346\216\222\345\272\217\347\237\251\351\230\265\346\237\245\346\211\276.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 10.09. \346\216\222\345\272\217\347\237\251\351\230\265\346\237\245\346\211\276.md" deleted file mode 100644 index 002edcd5..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 10.09. \346\216\222\345\272\217\347\237\251\351\230\265\346\237\245\346\211\276.md" +++ /dev/null @@ -1,86 +0,0 @@ -# [面试题 10.09. 排序矩阵查找](https://leetcode.cn/problems/sorted-matrix-search-lcci/) - -- 标签:数组、二分查找、分治、矩阵 -- 难度:中等 - -## 题目大意 - -给定一个 `m * n` 大小的有序整数矩阵。每一行、每一列都按升序排列。再给定一个目标值 `target`。 - -要求:判断矩阵中是否可以找到 `target`,若找到 `target`,返回 `True`,否则返回 `False`。 - -## 解题思路 - -矩阵是有序的,可以考虑使用二分搜索来进行查找。 - -迭代对角线元素,假设对角线元素的坐标为 `(row, col)`。把数组元素按对角线分为右上角部分和左下角部分。 - -则对于当前对角线元素右侧第 `row` 行、对角线元素下侧第 `col` 列进行二分查找。 - -- 如果找到目标,直接返回 `True`。 -- 如果找不到目标,则缩小范围,继续查找。 -- 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 - -## 代码 - -```python -class Solution: - def diagonalBinarySearch(self, matrix, diagonal, target): - left = 0 - right = diagonal - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][mid] < target: - left = mid + 1 - else: - right = mid - return left - - def rowBinarySearch(self, matrix, begin, cols, target): - left = begin - right = cols - while left < right: - mid = left + (right - left) // 2 - if matrix[begin][mid] < target: - left = mid + 1 - elif matrix[begin][mid] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= cols and matrix[begin][left] == target - - def colBinarySearch(self, matrix, begin, rows, target): - left = begin + 1 - right = rows - while left < right: - mid = left + (right - left) // 2 - if matrix[mid][begin] < target: - left = mid + 1 - elif matrix[mid][begin] > target: - right = mid - 1 - else: - left = mid - break - return begin <= left <= rows and matrix[left][begin] == target - - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - rows = len(matrix) - if rows == 0: - return False - cols = len(matrix[0]) - if cols == 0: - return False - - min_val = min(rows, cols) - index = self.diagonalBinarySearch(matrix, min_val - 1, target) - if matrix[index][index] == target: - return True - for i in range(index + 1): - row_search = self.rowBinarySearch(matrix, i, cols - 1, target) - col_search = self.colBinarySearch(matrix, i, rows - 1, target) - if row_search or col_search: - return True - return False -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 16.02. \345\215\225\350\257\215\351\242\221\347\216\207.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 16.02. \345\215\225\350\257\215\351\242\221\347\216\207.md" deleted file mode 100644 index 0d243ae1..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 16.02. \345\215\225\350\257\215\351\242\221\347\216\207.md" +++ /dev/null @@ -1,74 +0,0 @@ -# [面试题 16.02. 单词频率](https://leetcode.cn/problems/words-frequency-lcci/) - -- 标签:设计、字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -要求:设计一个方法,找出任意指定单词在一本书中的出现频率。 - -支持如下操作: - -- `WordsFrequency(book)` 构造函数,参数为字符串数组构成的一本书。 -- `get(word)` 查询指定单词在书中出现的频率。 - -## 解题思路 - -使用字典树统计单词频率。 - -构造函数时,构建一个字典树,并将所有单词存入字典树中,同时在字典树中记录并维护单词频率。 - -查询时,调用字典树查询方法,查询单词频率。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.count = 0 - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.count += 1 - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return 0 - cur = cur.children[ch] - if cur and cur.isEnd: - return cur.count - return 0 - -class WordsFrequency: - - def __init__(self, book: List[str]): - self.tire_tree = Trie() - for word in book: - self.tire_tree.insert(word) - - - def get(self, word: str) -> int: - return self.tire_tree.search(word) -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 16.05. \351\230\266\344\271\230\345\260\276\346\225\260.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 16.05. \351\230\266\344\271\230\345\260\276\346\225\260.md" deleted file mode 100644 index c28581ac..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 16.05. \351\230\266\344\271\230\345\260\276\346\225\260.md" +++ /dev/null @@ -1,29 +0,0 @@ -# [面试题 16.05. 阶乘尾数](https://leetcode.cn/problems/factorial-zeros-lcci/) - -- 标签:数学 -- 难度:简单 - -## 题目大意 - -给定一个整数 `n`。 - -要求:计算 `n` 的阶乘中尾随零的数量。 - -注意:$0 <= n <= 10^4$。 - -## 解题思路 - -阶乘中,末尾 `0` 的来源只有 `2 * 5`。所以尾随 `0` 的个数为 `2` 的倍数个数和 `5` 的倍数个数的最小值。又因为 `2 < 5`,`2` 的倍数个数肯定小于等于 `5` 的倍数,所以直接统计 `5` 的倍数个数即可。 - -## 代码 - -```python -class Solution: - def trailingZeroes(self, n: int) -> int: - count = 0 - while n > 0: - count += n // 5 - n = n // 5 - return count -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 16.26. \350\256\241\347\256\227\345\231\250.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 16.26. \350\256\241\347\256\227\345\231\250.md" deleted file mode 100644 index 6f6ca980..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 16.26. \350\256\241\347\256\227\345\231\250.md" +++ /dev/null @@ -1,62 +0,0 @@ -# [面试题 16.26. 计算器](https://leetcode.cn/problems/calculator-lcci/) - -- 标签:栈、数学、字符串 -- 难度:中等 - -## 题目大意 - -给定一个包含正整数、加(`+`)、减(`-`)、乘(`*`)、除(`/`)的算出表达式(括号除外)。表达式仅包含非负整数,`+`、`-`、`*`、`/` 四种运算符和空格 ` `。整数除法仅保留整数部分。 - -要求:计算其结果。 - -## 解题思路 - -计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 - -可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 - -具体做法: - -- 遍历字符串 s,使用变量 op 来标记数字之前的运算符,默认为 `+`。 -- 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 - - 如果 op 为 `+`,则将 num 压入栈中。 - - 如果 op 为 `-`,则将 -num 压入栈中。 - - 如果 op 为 `*`,则将栈顶元素 top 取出,计算 top * num,并将计算结果压入栈中。 - - 如果 op 为 `/`,则将栈顶元素 top 取出,计算 int(top / num),并将计算结果压入栈中。 -- 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 op。 -- 最后将栈中整数进行累加,并返回结果。 - -## 代码 - -```python -class Solution: - def calculate(self, s: str) -> int: - size = len(s) - stack = [] - op = '+' - index = 0 - while index < size: - if s[index] == ' ': - index += 1 - continue - if s[index].isdigit(): - num = ord(s[index]) - ord('0') - while index + 1 < size and s[index + 1].isdigit(): - index += 1 - num = 10 * num + ord(s[index]) - ord('0') - if op == '+': - stack.append(num) - elif op == '-': - stack.append(-num) - elif op == '*': - top = stack.pop() - stack.append(top * num) - elif op == '/': - top = stack.pop() - stack.append(int(top / num)) - elif s[index] in "+-*/": - op = s[index] - index += 1 - return sum(stack) -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 17.06. 2\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 17.06. 2\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" deleted file mode 100644 index 57d3fb43..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 17.06. 2\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" +++ /dev/null @@ -1,81 +0,0 @@ -# [面试题 17.06. 2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) - -- 标签:递归、数学、动态规划 -- 难度:困难 - -## 题目大意 - -**描述**:给定一个整数 $n$。 - -**要求**:计算从 $0$ 到 $n$ (包含 $n$) 中数字 $2$ 出现的次数。 - -**说明**: - -- $n \le 10^9$。 - -**示例**: - -- 示例 1: - -```python -输入: 25 -输出: 9 -解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次) -``` - -## 解题思路 - -### 思路 1:动态规划 + 数位 DP - -将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $2$ 出现的个数。接下来按照如下步骤进行递归。 - -1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: - 1. 从位置 $0$ 开始构造。 - 2. 初始数字 $2$ 出现的个数为 $0$。 - 3. 开始时受到数字 $n$ 对应最高位数位的约束。 -2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $2$ 出现的个数 $cnt$。 -3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 -4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 -5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: - 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 - 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 - 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 - 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 2), isLimit and d == maxX)`。 - 1. `cnt + (d == 2)` 表示之前数字 $2$ 出现的个数加上当前位为数字 $2$ 的个数。 - 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 -6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 - -### 思路 1:代码 - -```python -class Solution: - def numberOf2sInRange(self, n: int) -> int: - # 将 n 转换为字符串 s - s = str(n) - - @cache - # pos: 第 pos 个数位 - # cnt: 之前数字 2 出现的个数。 - # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 - def dfs(pos, cnt, isLimit): - if pos == len(s): - return cnt - - ans = 0 - # 不需要考虑前导 0,则最小可选择数字为 0 - minX = 0 - # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 - maxX = int(s[pos]) if isLimit else 9 - - # 枚举可选择的数字 - for d in range(minX, maxX + 1): - ans += dfs(pos + 1, cnt + (d == 2), isLimit and d == maxX) - return ans - - return dfs(0, 0, True) -``` - -### 思路 1:复杂度分析 - -- **时间复杂度**:$O(\log n)$。 -- **空间复杂度**:$O(\log n)$。 diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 17.14. \346\234\200\345\260\217K\344\270\252\346\225\260.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 17.14. \346\234\200\345\260\217K\344\270\252\346\225\260.md" deleted file mode 100644 index f5c1492c..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 17.14. \346\234\200\345\260\217K\344\270\252\346\225\260.md" +++ /dev/null @@ -1,69 +0,0 @@ -# [面试题 17.14. 最小K个数](https://leetcode.cn/problems/smallest-k-lcci/) - -- 标签:数组、分治、快速选择、排序、堆(优先队列) -- 难度:中等 - -## 题目大意 - -给定整数数组 `arr`,再给定一个整数 `k`。 - -要求:返回数组 `arr` 中最小的 `k` 个数。 - -## 解题思路 - -直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。 - -冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 - -可考虑堆排序、归并排序、快速排序。本题使用堆排序。具体做法如下: - -1. 利用数组前 `k` 个元素,建立大小为 `k` 的大顶堆。 -2. 遍历数组 `[k, size - 1]` 的元素,判断其与堆顶元素关系,如果比堆顶元素小,则将其赋值给堆顶元素,再对大顶堆进行调整。 -3. 最后输出前调整过后的大顶堆的前 `k` 个元素。 - -## 代码 - -```python -class Solution: - def heapify(self, nums: [int], index: int, end: int): - left = index * 2 + 1 - right = left + 1 - while left <= end: - # 当前节点为非叶子节点 - max_index = index - if nums[left] > nums[max_index]: - max_index = left - if right <= end and nums[right] > nums[max_index]: - max_index = right - if index == max_index: - # 如果不用交换,则说明已经交换结束 - break - nums[index], nums[max_index] = nums[max_index], nums[index] - # 继续调整子树 - index = max_index - left = index * 2 + 1 - right = left + 1 - - # 初始化大顶堆 - def buildMaxHeap(self, nums: [int], k: int): - # (k-2) // 2 是最后一个非叶节点,叶节点不用调整 - for i in range((k - 2) // 2, -1, -1): - self.heapify(nums, i, k - 1) - return nums - - def smallestK(self, arr: List[int], k: int) -> List[int]: - size = len(arr) - if k <= 0 or not arr: - return [] - if size <= k: - return arr - - self.buildMaxHeap(arr, k) - for i in range(k, size): - if arr[i] < arr[0]: - arr[i], arr[0] = arr[0], arr[i] - self.heapify(arr, 0, k - 1) - - return arr[:k] -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 17.15. \346\234\200\351\225\277\345\215\225\350\257\215.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 17.15. \346\234\200\351\225\277\345\215\225\350\257\215.md" deleted file mode 100644 index bf7e48d8..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 17.15. \346\234\200\351\225\277\345\215\225\350\257\215.md" +++ /dev/null @@ -1,91 +0,0 @@ -# [面试题 17.15. 最长单词](https://leetcode.cn/problems/longest-word-lcci/) - -- 标签:字典树、数组、哈希表、字符串 -- 难度:中等 - -## 题目大意 - -给定一组单词 `words`。 - -要求:找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。若有多个长度相同的结果,返回其中字典序最小的一项,若没有符合要求的单词则返回空字符串。 - -## 解题思路 - -先将所有单词按照长度从长到短排序,相同长度的字典序小的排在前面。然后将所有单词存入字典树中。 - -然后一重循环遍历所有单词 `word`,二重循环遍历单词中所有字符 `word[i]`。 - -如果当前遍历的字符为单词末尾,递归判断从 `i + 1` 位置开始,剩余部分是否可以切分为其他单词组合,如果可以切分,则返回当前单词 `word`。如果不可以切分,则返回空字符串 `""`。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - - - def insert(self, word: str) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - - - def search(self, word: str) -> bool: - """ - Returns if the word is in the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - return False - cur = cur.children[ch] - - return cur is not None and cur.isEnd - - def splitToWord(self, remain): - if not remain or remain == "": - return True - cur = self - for i in range(len(remain)): - ch = remain[i] - if ch not in cur.children: - return False - if cur.children[ch].isEnd and self.splitToWord(remain[i + 1:]): - return True - cur = cur.children[ch] - return False - - def dfs(self, words): - for word in words: - cur = self - size = len(word) - for i in range(size): - ch = word[i] - if i < size - 1 and cur.children[ch].isEnd and self.splitToWord(word[i+1:]): - return word - cur = cur.children[ch] - return "" - -class Solution: - def longestWord(self, words: List[str]) -> str: - words.sort(key=lambda x: (-len(x), x)) - trie_tree = Trie() - for word in words: - trie_tree.insert(word) - - ans = trie_tree.dfs(words) - return ans -``` - diff --git "a/Solutions/\351\235\242\350\257\225\351\242\230 17.17. \345\244\232\346\254\241\346\220\234\347\264\242.md" "b/Solutions/\351\235\242\350\257\225\351\242\230 17.17. \345\244\232\346\254\241\346\220\234\347\264\242.md" deleted file mode 100644 index 7415bd19..00000000 --- "a/Solutions/\351\235\242\350\257\225\351\242\230 17.17. \345\244\232\346\254\241\346\220\234\347\264\242.md" +++ /dev/null @@ -1,76 +0,0 @@ -# [面试题 17.17. 多次搜索](https://leetcode.cn/problems/multi-search-lcci/) - -- 标签:字典树、数组、哈希表、字符串、字符串匹配、滑动窗口 -- 难度:中等 - -## 题目大意 - -给定一个较长字符串 `big` 和一个包含较短字符串的数组 `smalls`。 - -要求:设计一个方法,根据 `smalls` 中的每一个较短字符串,对 `big` 进行搜索。输出 `smalls` 中的字符串在 `big` 里出现的所有位置 `positions`,其中 `positions[i]` 为 `smalls[i]` 出现的所有位置。 - -## 解题思路 - -构建字典树,将 `smalls` 中所有字符串存入字典树中,并在字典树中记录下插入字符串的顺序下标。 - -然后一重循环遍历 `big`,表示从第 `i` 位置开始的字符串 `big[i:]`。然后在字符串前缀中搜索对应的单词,将所有符合要求的单词插入顺序位置存入列表中,返回列表。 - -对于列表中每个单词插入下标顺序 `index` 和 `big[i:]` 来说, `i` 就是 `smalls` 中第 `index` 个字符串所对应在 `big` 中的开始位置,将其存入答案数组并返回即可。 - -## 代码 - -```python -class Trie: - - def __init__(self): - """ - Initialize your data structure here. - """ - self.children = dict() - self.isEnd = False - self.index = -1 - - - def insert(self, word: str, index: int) -> None: - """ - Inserts a word into the trie. - """ - cur = self - for ch in word: - if ch not in cur.children: - cur.children[ch] = Trie() - cur = cur.children[ch] - cur.isEnd = True - cur.index = index - - - def search(self, text: str) -> list: - """ - Returns if the word is in the trie. - """ - cur = self - res = [] - for i in range(len(text)): - ch = text[i] - if ch not in cur.children: - return res - cur = cur.children[ch] - if cur.isEnd: - res.append(cur.index) - return res - -class Solution: - def multiSearch(self, big: str, smalls: List[str]) -> List[List[int]]: - trie_tree = Trie() - for i in range(len(smalls)): - word = smalls[i] - trie_tree.insert(word, i) - - res = [[] for _ in range(len(smalls))] - - for i in range(len(big)): - for index in trie_tree.search(big[i:]): - res[index].append(i) - return res -``` - diff --git a/Templates/02.LinkedList/LinkedList-QuickSort.py b/Templates/02.LinkedList/LinkedList-QuickSort.py deleted file mode 100644 index 6cfe1d50..00000000 --- a/Templates/02.LinkedList/LinkedList-QuickSort.py +++ /dev/null @@ -1,42 +0,0 @@ -class ListNode: - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -class Solution: - def partition(self, left: ListNode, right: ListNode): - # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 - if left == right or left.next == right: - return left - # 选择头节点为基准节点 - pivot = left.val - # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 - node_i, node_j = left, left.next - - while node_j != right: - # 发现一个小与基准值的元素 - if node_j.val < pivot: - # node_i 之前节点都小于基准值,所以 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) - node_i = node_i.next - # 将小于基准值的元素与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 - node_i.val, node_j.val = node_j.val, node_i.val - node_j = node_j.next - - # 将基准节点放到正确位置上 - node_i.val, left.val = left.val, node_i.val - return node_i - - def quickSort(self, left: ListNode, right: ListNode): - if left == right or left.next == right: - return left - pi = self.partition(left, right) - self.quickSort(left, pi) - self.quickSort(pi.next, right) - return left - - def quickSort(self, head): - - def sortLinkedList(self, head: ListNode): - if not head or not head.next: - return head - return self.quickSort(head, None) \ No newline at end of file diff --git a/Templates/02.LinkedList/LinkedList.py b/Templates/02.LinkedList/LinkedList.py deleted file mode 100644 index a1bdc664..00000000 --- a/Templates/02.LinkedList/LinkedList.py +++ /dev/null @@ -1,109 +0,0 @@ -class ListNode: - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -class LinkedList: - def __init__(self): - self.head = None - - # 根据 data 初始化一个新链表 - def create(self, data): - self.head = ListNode(0) - cur = self.head - for i in range(len(data)): - node = ListNode(data[i]) - cur.next = node - cur = cur.next - - # 获取线性链表长度 - def length(self): - count = 0 - cur = self.head - while cur: - count += 1 - cur = cur.next - return count - - # 查找元素:在链表中查找值为 val 的元素 - def find(self, val): - cur = self.head - while cur: - if val == cur.val: - return cur - cur = cur.next - - return None - - # 链表头部插入元素 - def insertFront(self, val): - node = ListNode(val) - node.next = self.head - self.head = node - - # 链表尾部插入元素 - def insertRear(self, val): - node = ListNode(val) - cur = self.head - while cur.next: - cur = cur.next - cur.next = node - - - # 链表中间插入元素 - def insertInside(self, index, val): - count = 0 - cur = self.head - while cur and count < index - 1: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - node = ListNode(val) - node.next = cur.next - cur.next = node - - # 改变元素:将链表中第 i 个元素值改为 val - def change(self, index, val): - count = 0 - cur = self.head - while cur and count < index: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - cur.val = val - - # 链表头部删除元素 - def removeFront(self): - if self.head: - self.head = self.head.next - - # 链表尾部删除元素 - def removeRear(self): - if not self.head or not self.head.next: - return 'Error' - - cur = self.head - while cur.next.next: - cur = cur.next - cur.next = None - - # 链表中间删除元素 - def removeInside(self, index): - count = 0 - cur = self.head - - while cur.next and count < index - 1: - count += 1 - cur = cur.next - - if not cur: - return 'Error' - - del_node = cur.next - cur.next = del_node.next \ No newline at end of file diff --git a/Templates/07.Tree/Tree-UnionFind-UnoinByRank.py b/Templates/07.Tree/Tree-UnionFind-UnoinByRank.py deleted file mode 100644 index a85f33eb..00000000 --- a/Templates/07.Tree/Tree-UnionFind-UnoinByRank.py +++ /dev/null @@ -1,28 +0,0 @@ -class UnionFind: - def __init__(self, n): # 初始化 - self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 - self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 - - def find(self, x): # 查找元素根节点的集合编号内部实现方法 - while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 - self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 - x = self.fa[x] - return x # 返回元素根节点的集合编号 - - def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 - root_x = self.find(x) - root_y = self.find(y) - if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 - return False - - if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 - elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 - self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 - else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 - self.fa[root_x] = root_y # 向任意一方合并即可 - rank[y] += 1 # 因为层数相同,被合并的树必然层数会 +1 - return True - - def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 - return self.find(x) == self.find(y) \ No newline at end of file diff --git a/Templates/08.Graph/Graph-Prim.py b/Templates/08.Graph/Graph-Prim.py deleted file mode 100644 index 23f95f3e..00000000 --- a/Templates/08.Graph/Graph-Prim.py +++ /dev/null @@ -1,48 +0,0 @@ -class Solution: - def prim(self, graph): - size = len(graph) - vis = set() - dist = [float('inf') for _ in range(size)] - - ans = 0 - pos = 0 - dist[pos] = 0 - vis.add(pos) - - for i in range(1, size): - if 0 in graph and i in graph[0]: - dist[i] = graph[0][i] - - for i in range(size - 1): - cur_min = float('inf') - pos = -1 - for j in range(size): - if j not in vis and dist[j] < cur_min: - cur_min = dist[j] - pos = j - if pos == -1: - return -1 - ans += cur_min - vis.add(pos) - for j in range(size): - if j not in vis and dist[j] > graph[pos][j]: - dist[j] = graph[pos][j] - return ans - -points = [[0,0]] -graph = dict() -size = len(points) -for i in range(size): - x1, y1 = points[i] - for j in range(size): - x2, y2 = points[j] - dist = abs(x2 - x1) + abs(y2 - y1) - if i not in graph: - graph[i] = dict() - if j not in graph: - graph[j] = dict() - graph[i][j] = dist - graph[j][i] = dist - - -print(Solution().prim(graph)) \ No newline at end of file diff --git a/Templates/01.Array/Array-MaxHeap.py b/codes/python/01_array/array_maxheap.py similarity index 100% rename from Templates/01.Array/Array-MaxHeap.py rename to codes/python/01_array/array_maxheap.py diff --git a/Templates/01.Array/Array-BubbleSort.py b/codes/python/01_array/array_sort_bubble_sort.py similarity index 100% rename from Templates/01.Array/Array-BubbleSort.py rename to codes/python/01_array/array_sort_bubble_sort.py diff --git a/Templates/01.Array/Array-BucketSort.py b/codes/python/01_array/array_sort_bucket_sort.py similarity index 100% rename from Templates/01.Array/Array-BucketSort.py rename to codes/python/01_array/array_sort_bucket_sort.py diff --git a/Templates/01.Array/Array-CountingSort.py b/codes/python/01_array/array_sort_counting_sort.py similarity index 100% rename from Templates/01.Array/Array-CountingSort.py rename to codes/python/01_array/array_sort_counting_sort.py diff --git a/Templates/01.Array/Array-InsertionSort.py b/codes/python/01_array/array_sort_insertion_sort.py similarity index 100% rename from Templates/01.Array/Array-InsertionSort.py rename to codes/python/01_array/array_sort_insertion_sort.py diff --git a/Templates/01.Array/Array-MaxHeapSort.py b/codes/python/01_array/array_sort_maxheap_sort.py similarity index 100% rename from Templates/01.Array/Array-MaxHeapSort.py rename to codes/python/01_array/array_sort_maxheap_sort.py diff --git a/Templates/01.Array/Array-MergeSort.py b/codes/python/01_array/array_sort_merge_sort.py similarity index 100% rename from Templates/01.Array/Array-MergeSort.py rename to codes/python/01_array/array_sort_merge_sort.py diff --git a/Templates/01.Array/Array-MinHeapSort.py b/codes/python/01_array/array_sort_minheap_sort.py similarity index 100% rename from Templates/01.Array/Array-MinHeapSort.py rename to codes/python/01_array/array_sort_minheap_sort.py diff --git a/Templates/01.Array/Array-QuickSort.py b/codes/python/01_array/array_sort_quick_sort.py similarity index 100% rename from Templates/01.Array/Array-QuickSort.py rename to codes/python/01_array/array_sort_quick_sort.py diff --git a/Templates/01.Array/Array-RadixSort.py b/codes/python/01_array/array_sort_radix_sort.py similarity index 100% rename from Templates/01.Array/Array-RadixSort.py rename to codes/python/01_array/array_sort_radix_sort.py diff --git a/Templates/01.Array/Array-SelectionSort.py b/codes/python/01_array/array_sort_selection_sort.py similarity index 100% rename from Templates/01.Array/Array-SelectionSort.py rename to codes/python/01_array/array_sort_selection_sort.py diff --git a/Templates/01.Array/Array-ShellSort.py b/codes/python/01_array/array_sort_shell_sort.py similarity index 100% rename from Templates/01.Array/Array-ShellSort.py rename to codes/python/01_array/array_sort_shell_sort.py diff --git a/codes/python/02_linked_list/linked_list.py b/codes/python/02_linked_list/linked_list.py new file mode 100644 index 00000000..a3165e86 --- /dev/null +++ b/codes/python/02_linked_list/linked_list.py @@ -0,0 +1,111 @@ +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class LinkedList: + def __init__(self): + self.head = None + + # 根据 data 初始化一个新链表 + def create(self, data): + if not data: + return + self.head = ListNode(data[0]) + cur = self.head + for i in range(1, len(data)): + node = ListNode(data[i]) + cur.next = node + cur = cur.next + + # 获取线性链表长度 + def length(self): + count = 0 + cur = self.head + while cur: + count += 1 + cur = cur.next + return count + + # 查找元素:在链表中查找值为 val 的元素 + def find(self, val): + cur = self.head + while cur: + if val == cur.val: + return cur + cur = cur.next + + return None + + # 链表头部插入元素 + def insertFront(self, val): + node = ListNode(val) + node.next = self.head + self.head = node + + # 链表尾部插入元素 + def insertRear(self, val): + node = ListNode(val) + cur = self.head + while cur.next: + cur = cur.next + cur.next = node + + + # 链表中间插入元素 + def insertInside(self, index, val): + count = 0 + cur = self.head + while cur and count < index - 1: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + node = ListNode(val) + node.next = cur.next + cur.next = node + + # 改变元素:将链表中第 i 个元素值改为 val + def change(self, index, val): + count = 0 + cur = self.head + while cur and count < index: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + cur.val = val + + # 链表头部删除元素 + def removeFront(self): + if self.head: + self.head = self.head.next + + # 链表尾部删除元素 + def removeRear(self): + if not self.head or not self.head.next: + return 'Error' + + cur = self.head + while cur.next.next: + cur = cur.next + cur.next = None + + # 链表中间删除元素 + def removeInside(self, index): + count = 0 + cur = self.head + + while cur.next and count < index - 1: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + del_node = cur.next + cur.next = del_node.next \ No newline at end of file diff --git a/Templates/02.LinkedList/LinkedList-BubbleSort.py b/codes/python/02_linked_list/linked_list_bubble_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-BubbleSort.py rename to codes/python/02_linked_list/linked_list_bubble_sort.py diff --git a/Templates/02.LinkedList/LinkedList-BucketSort.py b/codes/python/02_linked_list/linked_list_bucket_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-BucketSort.py rename to codes/python/02_linked_list/linked_list_bucket_sort.py diff --git a/Templates/02.LinkedList/LinkedList-CountingSort.py b/codes/python/02_linked_list/linked_list_counting_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-CountingSort.py rename to codes/python/02_linked_list/linked_list_counting_sort.py diff --git a/Templates/02.LinkedList/LinkedList-InsertionSort.py b/codes/python/02_linked_list/linked_list_insertion_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-InsertionSort.py rename to codes/python/02_linked_list/linked_list_insertion_sort.py diff --git a/Templates/02.LinkedList/LinkedList-MergeSort.py b/codes/python/02_linked_list/linked_list_merge_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-MergeSort.py rename to codes/python/02_linked_list/linked_list_merge_sort.py diff --git a/codes/python/02_linked_list/linked_list_quick_sort.py b/codes/python/02_linked_list/linked_list_quick_sort.py new file mode 100644 index 00000000..96bd88e9 --- /dev/null +++ b/codes/python/02_linked_list/linked_list_quick_sort.py @@ -0,0 +1,40 @@ +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + def partition(self, left: ListNode, right: ListNode): + # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 + if left == right or left.next == right: + return left + # 选择头节点为基准节点 + pivot = left.val + # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 + node_i, node_j = left, left.next + + while node_j != right: + # 发现一个小与基准值的元素 + if node_j.val < pivot: + # node_i 之前节点都小于基准值,所以 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) + node_i = node_i.next + # 将小于基准值的元素与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 + node_i.val, node_j.val = node_j.val, node_i.val + node_j = node_j.next + + # 将基准节点放到正确位置上 + node_i.val, left.val = left.val, node_i.val + return node_i + + def quickSort(self, left: ListNode, right: ListNode): + if left == right or left.next == right: + return left + pi = self.partition(left, right) + self.quickSort(left, pi) + self.quickSort(pi.next, right) + return left + + def sortLinkedList(self, head: ListNode): + if not head or not head.next: + return head + return self.quickSort(head, None) \ No newline at end of file diff --git a/Templates/02.LinkedList/LinkedList-RadixSort.py b/codes/python/02_linked_list/linked_list_radix_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-RadixSort.py rename to codes/python/02_linked_list/linked_list_radix_sort.py diff --git a/Templates/02.LinkedList/LinkedList-SectionSort.py b/codes/python/02_linked_list/linked_list_section_sort.py similarity index 100% rename from Templates/02.LinkedList/LinkedList-SectionSort.py rename to codes/python/02_linked_list/linked_list_section_sort.py diff --git a/Templates/04.Queue/Queue-CircularSequentialQueue.py b/codes/python/03_stack_queue_hash_table/queue_circularSequential_queue.py similarity index 100% rename from Templates/04.Queue/Queue-CircularSequentialQueue.py rename to codes/python/03_stack_queue_hash_table/queue_circularSequential_queue.py diff --git a/Templates/04.Queue/Queue-LinkQueue.py b/codes/python/03_stack_queue_hash_table/queue_link_queue.py similarity index 100% rename from Templates/04.Queue/Queue-LinkQueue.py rename to codes/python/03_stack_queue_hash_table/queue_link_queue.py diff --git a/Templates/04.Queue/Queue-PriorityQueue.py b/codes/python/03_stack_queue_hash_table/queue_priority_queue.py similarity index 100% rename from Templates/04.Queue/Queue-PriorityQueue.py rename to codes/python/03_stack_queue_hash_table/queue_priority_queue.py diff --git a/Templates/04.Queue/Queue-SequentialQueue.py b/codes/python/03_stack_queue_hash_table/queue_sequential_queue.py similarity index 100% rename from Templates/04.Queue/Queue-SequentialQueue.py rename to codes/python/03_stack_queue_hash_table/queue_sequential_queue.py diff --git a/Templates/03.Stack/Stack-LinkStack.py b/codes/python/03_stack_queue_hash_table/stack_link_stack.py similarity index 100% rename from Templates/03.Stack/Stack-LinkStack.py rename to codes/python/03_stack_queue_hash_table/stack_link_stack.py diff --git a/Templates/03.Stack/Stack-MonotoneStack.py b/codes/python/03_stack_queue_hash_table/stack_monotone_stack.py similarity index 100% rename from Templates/03.Stack/Stack-MonotoneStack.py rename to codes/python/03_stack_queue_hash_table/stack_monotone_stack.py diff --git a/Templates/03.Stack/Stack-SequentialStack.py b/codes/python/03_stack_queue_hash_table/stack_sequential_stack.py similarity index 100% rename from Templates/03.Stack/Stack-SequentialStack.py rename to codes/python/03_stack_queue_hash_table/stack_sequential_stack.py diff --git a/Templates/06.String/String-Strcmp.py b/codes/python/04_string/string_Strcmp.py similarity index 100% rename from Templates/06.String/String-Strcmp.py rename to codes/python/04_string/string_Strcmp.py diff --git a/Templates/06.String/String-BM.py b/codes/python/04_string/string_boyer_moore.py similarity index 100% rename from Templates/06.String/String-BM.py rename to codes/python/04_string/string_boyer_moore.py diff --git a/Templates/06.String/String-BF.py b/codes/python/04_string/string_brute_force.py similarity index 100% rename from Templates/06.String/String-BF.py rename to codes/python/04_string/string_brute_force.py diff --git a/Templates/06.String/String-Horspool.py b/codes/python/04_string/string_horspool.py similarity index 100% rename from Templates/06.String/String-Horspool.py rename to codes/python/04_string/string_horspool.py diff --git a/Templates/06.String/String-KMP.py b/codes/python/04_string/string_kmp.py similarity index 100% rename from Templates/06.String/String-KMP.py rename to codes/python/04_string/string_kmp.py diff --git a/Templates/06.String/String-RK.py b/codes/python/04_string/string_rabin_karp.py similarity index 100% rename from Templates/06.String/String-RK.py rename to codes/python/04_string/string_rabin_karp.py diff --git a/Templates/06.String/String-Sunday.py b/codes/python/04_string/string_sunday.py similarity index 100% rename from Templates/06.String/String-Sunday.py rename to codes/python/04_string/string_sunday.py diff --git a/Templates/06.String/String-Trie.py b/codes/python/04_string/string_trie.py similarity index 100% rename from Templates/06.String/String-Trie.py rename to codes/python/04_string/string_trie.py diff --git a/Templates/07.Tree/Tree-BinaryIndexedTree.py b/codes/python/05_tree/tree_binaryindexed_tree.py similarity index 100% rename from Templates/07.Tree/Tree-BinaryIndexedTree.py rename to codes/python/05_tree/tree_binaryindexed_tree.py diff --git a/Templates/07.Tree/Tree-DynamicSegmentTree-Update-Interval-1.py b/codes/python/05_tree/tree_dynamicSegmentTree_update_interval_1.py similarity index 100% rename from Templates/07.Tree/Tree-DynamicSegmentTree-Update-Interval-1.py rename to codes/python/05_tree/tree_dynamicSegmentTree_update_interval_1.py diff --git a/Templates/07.Tree/Tree-DynamicSegmentTree-Update-Interval-2.py b/codes/python/05_tree/tree_dynamicSegmentTree_update_interval_2.py similarity index 100% rename from Templates/07.Tree/Tree-DynamicSegmentTree-Update-Interval-2.py rename to codes/python/05_tree/tree_dynamicSegmentTree_update_interval_2.py diff --git a/Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py b/codes/python/05_tree/tree_segmentTree_update_interval_1.py similarity index 100% rename from Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py rename to codes/python/05_tree/tree_segmentTree_update_interval_1.py diff --git a/Templates/07.Tree/Tree-SegmentTree-Update-Interval-2.py b/codes/python/05_tree/tree_segmentTree_update_interval_2.py similarity index 100% rename from Templates/07.Tree/Tree-SegmentTree-Update-Interval-2.py rename to codes/python/05_tree/tree_segmentTree_update_interval_2.py diff --git a/Templates/07.Tree/Tree-SegmentTree-Update-Point.py b/codes/python/05_tree/tree_segmentTree_update_point.py similarity index 100% rename from Templates/07.Tree/Tree-SegmentTree-Update-Point.py rename to codes/python/05_tree/tree_segmentTree_update_point.py diff --git a/Templates/07.Tree/Tree-UnionFind.py b/codes/python/05_tree/tree_unionFind.py similarity index 100% rename from Templates/07.Tree/Tree-UnionFind.py rename to codes/python/05_tree/tree_unionFind.py diff --git a/Templates/07.Tree/Tree-UnionFind-QuickFind.py b/codes/python/05_tree/tree_unionFind_QuickFind.py similarity index 100% rename from Templates/07.Tree/Tree-UnionFind-QuickFind.py rename to codes/python/05_tree/tree_unionFind_QuickFind.py diff --git a/Templates/07.Tree/Tree-UnionFind-QuickUnion.py b/codes/python/05_tree/tree_unionFind_QuickUnion.py similarity index 100% rename from Templates/07.Tree/Tree-UnionFind-QuickUnion.py rename to codes/python/05_tree/tree_unionFind_QuickUnion.py diff --git a/codes/python/05_tree/tree_unionFind_UnoinByRank.py b/codes/python/05_tree/tree_unionFind_UnoinByRank.py new file mode 100644 index 00000000..31313779 --- /dev/null +++ b/codes/python/05_tree/tree_unionFind_UnoinByRank.py @@ -0,0 +1,28 @@ +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 + self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 + else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # 向任意一方合并即可 + self.rank[root_y] += 1 # 因为层数相同,被合并的树必然层数会 +1 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) \ No newline at end of file diff --git a/Templates/07.Tree/Tree-UnionFind-UnoinBySize.py b/codes/python/05_tree/tree_unionFind_UnoinBySize.py similarity index 100% rename from Templates/07.Tree/Tree-UnionFind-UnoinBySize.py rename to codes/python/05_tree/tree_unionFind_UnoinBySize.py diff --git a/Templates/08.Graph/Graph-Adjacency-List.py b/codes/python/06_graph/Graph-Adjacency-List.py similarity index 100% rename from Templates/08.Graph/Graph-Adjacency-List.py rename to codes/python/06_graph/Graph-Adjacency-List.py diff --git a/Templates/08.Graph/Graph-Adjacency-Matrix.py b/codes/python/06_graph/Graph-Adjacency-Matrix.py similarity index 100% rename from Templates/08.Graph/Graph-Adjacency-Matrix.py rename to codes/python/06_graph/Graph-Adjacency-Matrix.py diff --git a/Templates/08.Graph/Graph-BFS.py b/codes/python/06_graph/Graph-BFS.py similarity index 100% rename from Templates/08.Graph/Graph-BFS.py rename to codes/python/06_graph/Graph-BFS.py diff --git a/Templates/08.Graph/Graph-Bellman-Ford.py b/codes/python/06_graph/Graph-Bellman-Ford.py similarity index 100% rename from Templates/08.Graph/Graph-Bellman-Ford.py rename to codes/python/06_graph/Graph-Bellman-Ford.py diff --git a/Templates/08.Graph/Graph-DFS.py b/codes/python/06_graph/Graph-DFS.py similarity index 100% rename from Templates/08.Graph/Graph-DFS.py rename to codes/python/06_graph/Graph-DFS.py diff --git a/Templates/08.Graph/Graph-Edgeset-Array.py b/codes/python/06_graph/Graph-Edgeset-Array.py similarity index 100% rename from Templates/08.Graph/Graph-Edgeset-Array.py rename to codes/python/06_graph/Graph-Edgeset-Array.py diff --git a/Templates/08.Graph/Graph-Hash-Table.py b/codes/python/06_graph/Graph-Hash-Table.py similarity index 100% rename from Templates/08.Graph/Graph-Hash-Table.py rename to codes/python/06_graph/Graph-Hash-Table.py diff --git a/codes/python/06_graph/Graph-Kruskal.py b/codes/python/06_graph/Graph-Kruskal.py new file mode 100644 index 00000000..b8fd0af1 --- /dev/null +++ b/codes/python/06_graph/Graph-Kruskal.py @@ -0,0 +1,55 @@ +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + + +class Solution: + def Kruskal(self, edges, size): + union_find = UnionFind(size) + + edges.sort(key=lambda x: x[2]) + + res, cnt = 0, 1 + for x, y, dist in edges: + if union_find.is_connected(x, y): + continue + ans += dist + cnt += 1 + union_find.union(x, y) + if cnt == size - 1: + return ans + return ans + + def minCostConnectPoints(self, points: List[List[int]]) -> int: + size = len(points) + edges = [] + for i in range(size): + xi, yi = points[i] + for j in range(i + 1, size): + xj, yj = points[j] + dist = abs(xi - xj) + abs(yi - yj) + edges.append([i, j, dist]) + + ans = Solution().Kruskal(edges, size) + return ans + \ No newline at end of file diff --git a/Templates/08.Graph/Graph-Linked-Forward-Star.py b/codes/python/06_graph/Graph-Linked-Forward-Star.py similarity index 100% rename from Templates/08.Graph/Graph-Linked-Forward-Star.py rename to codes/python/06_graph/Graph-Linked-Forward-Star.py diff --git a/codes/python/06_graph/Graph-Prim.py b/codes/python/06_graph/Graph-Prim.py new file mode 100644 index 00000000..f08276d8 --- /dev/null +++ b/codes/python/06_graph/Graph-Prim.py @@ -0,0 +1,47 @@ +class Solution: + # graph 为图的邻接矩阵,start 为起始顶点 + def Prim(self, graph, start): + size = len(graph) + vis = set() + dist = [float('inf') for _ in range(size)] + + ans = 0 # 最小生成树的边权和 + dist[start] = 0 # 初始化起始顶点到起始顶点的边权值为 0 + + for i in range(1, size): # 初始化起始顶点到其他顶点的边权值 + dist[i] = graph[start][i] + vis.add(start) # 将 start 顶点标记为已访问 + + for _ in range(size - 1): + min_dis = float('inf') + min_dis_pos = -1 + for i in range(size): + if i not in vis and dist[i] < min_dis: + min_dis = dist[i] + min_dis_pos = i + if min_dis_pos == -1: # 没有顶点可以加入 MST,图 G 不连通 + return -1 + ans += min_dis # 将顶点加入 MST,并将边权值加入到答案中 + vis.add(min_dis_pos) + for i in range(size): + if i not in vis and dist[i] > graph[min_dis_pos][i]: + dist[i] = graph[min_dis_pos][i] + return ans + +points = [[0,0]] +graph = dict() +size = len(points) +for i in range(size): + x1, y1 = points[i] + for j in range(size): + x2, y2 = points[j] + dist = abs(x2 - x1) + abs(y2 - y1) + if i not in graph: + graph[i] = dict() + if j not in graph: + graph[j] = dict() + graph[i][j] = dist + graph[j][i] = dist + + +print(Solution().Prim(graph)) \ No newline at end of file diff --git a/Templates/08.Graph/Graph-Topological-Sorting-DFS.py b/codes/python/06_graph/Graph-Topological-Sorting-DFS.py similarity index 100% rename from Templates/08.Graph/Graph-Topological-Sorting-DFS.py rename to codes/python/06_graph/Graph-Topological-Sorting-DFS.py diff --git a/Templates/08.Graph/Graph-Topological-Sorting-Kahn.py b/codes/python/06_graph/Graph-Topological-Sorting-Kahn.py similarity index 100% rename from Templates/08.Graph/Graph-Topological-Sorting-Kahn.py rename to codes/python/06_graph/Graph-Topological-Sorting-Kahn.py diff --git a/Templates/10.Dynamic-Programming/Digit-DP.py b/codes/python/08_dynamic_programming/Digit-DP.py similarity index 100% rename from Templates/10.Dynamic-Programming/Digit-DP.py rename to codes/python/08_dynamic_programming/Digit-DP.py diff --git a/Templates/10.Dynamic-Programming/Pack-2DCostPack.py b/codes/python/08_dynamic_programming/Pack-2DCostPack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-2DCostPack.py rename to codes/python/08_dynamic_programming/Pack-2DCostPack.py diff --git a/Templates/10.Dynamic-Programming/Pack-CompletePack.py b/codes/python/08_dynamic_programming/Pack-CompletePack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-CompletePack.py rename to codes/python/08_dynamic_programming/Pack-CompletePack.py diff --git a/Templates/10.Dynamic-Programming/Pack-GroupPack.py b/codes/python/08_dynamic_programming/Pack-GroupPack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-GroupPack.py rename to codes/python/08_dynamic_programming/Pack-GroupPack.py diff --git a/Templates/10.Dynamic-Programming/Pack-MixedPack.py b/codes/python/08_dynamic_programming/Pack-MixedPack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-MixedPack.py rename to codes/python/08_dynamic_programming/Pack-MixedPack.py diff --git a/Templates/10.Dynamic-Programming/Pack-MultiplePack.py b/codes/python/08_dynamic_programming/Pack-MultiplePack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-MultiplePack.py rename to codes/python/08_dynamic_programming/Pack-MultiplePack.py diff --git a/Templates/10.Dynamic-Programming/Pack-ProblemVariants.py b/codes/python/08_dynamic_programming/Pack-ProblemVariants.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-ProblemVariants.py rename to codes/python/08_dynamic_programming/Pack-ProblemVariants.py diff --git a/Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py b/codes/python/08_dynamic_programming/Pack-ZeroOnePack.py similarity index 100% rename from Templates/10.Dynamic-Programming/Pack-ZeroOnePack.py rename to codes/python/08_dynamic_programming/Pack-ZeroOnePack.py diff --git a/docs/00_preface/00_01_preface.md b/docs/00_preface/00_01_preface.md new file mode 100644 index 00000000..274b3ade --- /dev/null +++ b/docs/00_preface/00_01_preface.md @@ -0,0 +1,52 @@ +## 1. 创作历程 + +### 1.1 创作起因 + +我想写一本通俗易懂的算法书已经很久了,久到大概有 7 年那么久。至今我还记得大学时曾立下的 flag:**我要把我所学到的算法知识总结起来,整理成册,编纂成书,然后大大方方的在封面上写上自己的名字,再将它分享给所有喜欢学习算法的朋友们看。** + +结果是万万没想到,一晃过去,毕业后参加工作都已经 6 年了,每天忙于开发需求、业务逻辑、项目文档,写算法书这件事也跟其他大多数的待办事项一样,被无限制的闲置在一旁,再也不管不顾了。 + +直到 2021 年 3 月底的时候,在朋友的怂恿下,我们建了一个算法群,制定了一个为期 3 个月的算法打卡计划(2021 年 4 月 ~ 6 月),这个计划的唯一规则就是:连续两天不刷题就会被踢出群。 + +就这样我们一群小伙伴们(39 个人)开启了为期 3 个月的刷题计划。3 个月过后,最终只有 13 个人成功完成了刷题计划,另外三分之二的人都因为打卡失败被踢出群了。**这些失败的人中也包括我自己,我是在计划即将结束的时候,一次周末忘记了打卡,惨遭淘汰。** + +虽然这次我的刷题计划失败了,但是经过这 3 个月的刷题练习,让我重新拾起了学习算法的乐趣,并且把刷题变成了自己的日常习惯。在工作之余,我总是习惯性地打开 LeetCode,刷上几道题,然后写下题解,这种感觉很充实也很有成就感。 + +在这次刷题计划结束之后,2021 年 7 月份的时候,我们重新建立了算法打卡群,并持续到了今天。 + +就这样,刷题打卡成了我们的日常,群里的小伙伴也越来越多。我们每天刷题,写题解,在群里讨论每日题目的思路,提出问题和回答问题、探讨更好的求解思路。再后来群里的一些小伙伴们开始参加周赛、双周赛,在比赛结束之后我们还会交流赛题思路、做题心得。 + +### 1.2 输出是最好的学习方法 + +在写刷算法题、写题解的过程中,我又开始写算法和数据结构的基础知识,于是就有了现在这个开源项目。后来我学会了用 hugo 搭建网站,就搭建了一个开源项目的电子书网站,方便大家在线阅读。 + +在写算法书的这个过程中,我发现一个秘籍:**只有「输出」才是最好的学习方法**。这也是「费曼学习法」的一个应用。 + +在写算法内容的时候,如果对一个概念不理解,或者有点模糊,我是不可能把它写清楚,并且也让别人看明白的。只有在参考了大量的算法书籍和大佬的博客,把其中的概念或算法理解透彻了,彻底弄明白了之后,才能够转换为通俗易懂的文字,让大家也看明白。 + +并且在刷题的过程中,也会有很多朋友和群里小伙伴,跟我一起进行算法知识探讨,帮我指正错误或者提出建议。这些指正和建议,在很大程度上帮助我改进文章内容和提高自己对算法的理解。就好像是学生在写作业,有专业的老师在帮我批改作业,帮助我进步一样。 + +就这样,经过一段时间的努力,我从 2021 年 7 月开始,到 2022 年 7 月底,历时整整 1 年,在 LeetCode 上刷了 1000 多题,并且在刷题的过程总结了一些算法知识和数据结构知识,终于写完了这本算法书,也就是 **「算法通关手册」**。 + +## 2. 为什么要学习算法和数据结构 + +### 2.1 算法是程序员的底层能力 + +**「算法和数据结构」** 是计算机程序设计的重要理论技术基础,但很多程序员忽略了它的重要性。在日常开发工作中,最多的情况是使用成熟的开发框架,利用已经封装好的接口,进行 CRUD(增删改查)操作,似乎很少会需要自己实现相应的数据结构和算法。 + +况且工作中用到的编程语言、开发框架、开发平台,更新速度堪比摩尔定律。以前端为例,React 还没学明白呢,Vue 就火起来了。Vue 2.0 的文档还在研究呢,Vue 3.0 就发布了。很多时候,连新的技术还学不过来呢,哪还有时间去专门研究算法和数据结构呢。 + +诚然,语言、技术、框架固然重要,但背后的计算机算法和理论更为重要。因为语言、技术、框架的更新日新月异,但万变不离其宗的是背后的算法和理论,例如:**数据结构**、**算法**、**编译原理**、**计算机网络**、**计算机体系结构** 等等。任凭新技术如何变化,只要掌握了这些计算机科学的核心理论,就可以见招拆招,让自己立于不败之地。从此无论是看懂底层系统的设计原理、框架背后的设计思想,还是学习新技术、提升工作实战的效率,都可以做到得心应手。 + +**学习数据结构与算法的关键,在于掌握其中的思想和精髓,学会解决实际问题的方法。** + +### 2.2 算法是技术面试的必考内容 + +在互联网行业相关的技术面试中,**算法和数据结构知识** 几乎是所有公司的必考内容。众多知名互联网公司喜欢在面试中考察 LeetCode 上的算法题目,通常需要面试者对给定问题进行深入分析并提供解题思路。有时候,面试官还会要求面试者评估相关算法的时间复杂度和空间复杂度。面试官通过检验面试者对常用算法的熟悉程度和实现能力的方式,从而评估面试者解决实际问题的思维能力水平。 + +LeetCode 等平台上的算法题目已经成为行业标准。很多公司直接从这些平台选取题目或进行改编。通过系统性地练习这些题目,可以提高解决实际问题的能力。在面试中遇到类似问题时,就能更从容地应对。 + +学习算法需要循序渐进。从基础的数据结构开始,逐步掌握常见算法思想。每学习一个新概念,都要通过实际题目来巩固理解。这样积累下来,就能建立起完整的算法知识体系。 + +这本「算法通关手册」就是为了帮助读者系统学习算法知识而编写的。书中包含基础理论讲解和大量实战题目分析。通过理论学习和实践练习相结合的方式,读者可以真正掌握算法知识,提高解决问题的能力。无论是准备面试还是提升编程能力,这本书都能提供有价值的帮助。 + diff --git a/docs/00_preface/00_02_data_structures_algorithms.md b/docs/00_preface/00_02_data_structures_algorithms.md new file mode 100644 index 00000000..3805867f --- /dev/null +++ b/docs/00_preface/00_02_data_structures_algorithms.md @@ -0,0 +1,213 @@ +![程序=算法+数据结构](https://qcdn.itcharge.cn/images/202109092112373.png) + +> 数据结构是程序的骨架,而算法则是程序的灵魂。 + +**《算法 + 数据结构 = 程序》** 是 Pascal 语言之父 [Niklaus Emil Wirth](https://zh.wikipedia.org/wiki/尼克劳斯·维尔特) 写过的一本非常著名的书。而作为书名的这句话也成为了计算机科学的经典名句。可见,对于程序设计来说,算法和数据结构的关系密不可分。 + +在学习之前,首先我们要弄清楚什么是算法?什么是数据结构?为什么要学习算法和数据结构? + +简单来说,**「算法」就是解决问题的方法或者过程**。如果我们把问题看成是函数,那么算法就是将输入转换为输出的过程。**「数据结构」是数据的计算机表示和相应的一组操作**。**「程序」则是算法和数据结构的具体实现**。 + +如果我们把「程序设计」比作是做菜的话,那么「数据结构」就是食材和调料,「算法」则是不同的烹饪方式,或者可以看作是菜谱。不同的食材和调料,不同的烹饪方式,有着不同的排列组合。同样的东西,由不同的人做出来,味道自然也是千差万别。 + +至于为什么要学习算法和数据结构? + +还是拿做菜举例子。我们做菜,讲究的是「色香味俱全」。**程序设计也是如此,对于待解决的问题,我们追求的是:选择更加合适的「数据结构」,使用花费时间更少、占用空间更小的「算法」。** + +我们学习算法和数据结构,是为了学会在编程中从时间复杂度、空间复杂度方面考虑解决方案,训练自己的逻辑思维,从而写出高质量的代码,以此提升自己的编程技能,获取更高的工作回报。 + +当然,这就像是做菜,掌握了食材和调料,学会了烹饪方式,并不意味着你就会做出一盘很好吃的炒菜。同样,掌握了算法和数据结构并不意味着你就会写程序。这需要不断的琢磨和思考,并持续学习,才能成为一名优秀的 ~~厨师~~(程序员)。 + +## 1. 数据结构 + +> **数据结构(Data Structure)**:带有结构特性的数据元素的集合。 + +简单而言,**「数据结构」** 指的是:**数据的组织结构,用来组织、存储数据**。 + +展开来讲,数据结构研究的是数据的逻辑结构、物理结构以及它们之间的相互关系,并对这种结构定义相应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。 + +数据结构的作用,就是为了提高计算机硬件的利用率。比如说:操作系统想要查找应用程序 「Microsoft Word」 在硬盘中的哪一个位置存储。如果对硬盘全部扫描一遍的话肯定效率很低,但如果使用「B+ 树」作为索引,就能很容易的搜索到 `Microsoft Word` 这个单词,然后很快的定位到 「Microsoft Word」这个应用程序的文件信息,从而从文件信息中找到对应的磁盘位置。 + +学习数据结构,就是为了帮助我们了解和掌握计算机中的数据是以何种方式进行组织、存储的。 + +--- + +对于数据结构,我们可以按照数据的 **「逻辑结构」** 和 **「物理结构」** 来进行分类。 + +### 1.1 数据的逻辑结构 + +> **逻辑结构(Logical Structure)**:数据元素之间的相互关系。 + +根据元素之间具有的不同关系,通常我们可以将数据的逻辑结构分为以下四种: + +#### 1. 集合结构 + +> **集合结构**:数据元素同属于一个集合,除此之外无其他关系。 + +集合结构中的数据元素是无序的,并且每个数据元素都是唯一的,集合中没有相同的数据元素。集合结构很像数学意义上的「集合」。 + +![集合结构](https://qcdn.itcharge.cn/images/20240509150647.png) + +#### 2. 线性结构 + +> **线性结构**:数据元素之间是「一对一」关系。 + +线性结构中的数据元素(除了第一个和最后一个元素),左侧和右侧分别只有一个数据与其相邻。线性结构类型包括:数组、链表,以及由它们衍生出来的栈、队列、哈希表。 + +![线性结构](https://qcdn.itcharge.cn/images/20240509150709.png) + +#### 3. 树形结构 + +> **树形结构**:数据元素之间是「一对多」的层次关系。 + +最简单的树形结构是二叉树。这种结构可以简单的表示为:根, 左子树, 右子树。 左子树和右子树又有自己的子树。当然除了二叉树,树形结构类型还包括:多叉树、字典树等。 + +![树形结构](https://qcdn.itcharge.cn/images/20240509150724.png) + +#### 4. 图形结构 + +> **图形结构**:数据元素之间是「多对多」的关系。 + +图形结构是一种比树形结构更复杂的非线性结构,用于表示物件与物件之间的关系。一张图由一些小圆点(称为 **「顶点」** 或 **「结点」**)和连结这些圆点的直线或曲线(称为 **「边」**)组成。 + +在图形结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的。图形结构类型包括:无向图、有向图、连通图等。 + +![图形结构](https://qcdn.itcharge.cn/images/20240509150831.png) + +### 1.2 数据的物理结构 + +> **物理结构(Physical Structure)**:数据的逻辑结构在计算机中的存储方式。 + +计算机内有多种存储结构,采用最多的是这两种结构:**「顺序存储结构」**、**「链式存储结构」**。 + +#### 1. 顺序存储结构 + +> **顺序存储结构(Sequential Storage Structure)**:将数据元素存放在一片地址连续的存储单元里,数据元素之间的逻辑关系通过数据元素的存储地址来直接反映。 + +![顺序存储结构](https://qcdn.itcharge.cn/images/20240509150846.png) + +在顺序存储结构中,逻辑上相邻的数据元素在物理地址上也必然相邻 。 + +这种结构的优点是:简单、易理解,且实际占用最少的存储空间。缺点是:需要占用一片地址连续的存储单元;并且存储分配要事先进行;另外对于一些操作的时间效率较低(移动、删除元素等操作)。 + +#### 2. 链式存储结构 + +> **链式存储结构(Linked Storage Structure)**:将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。 + +![链式存储结构](https://qcdn.itcharge.cn/images/20240509150902.png) + +链式存储结构中,逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。链式存储结构中,一般将每个数据元素占用的若干单元的组合称为一个链结点。每个链结点不仅要存放一个数据元素的数据信息,还要存放一个指出这个数据元素在逻辑关系的直接后继元素所在链结点的地址,该地址被称为指针。换句话说,数据元素之间的逻辑关系是通过指针来间接反映的。 + +这种结构的优点是:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比顺序存储结构高(插入、移动、删除元素)。缺点是:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链式存储结构比顺序存储结构的空间开销大。 + +## 2. 算法 + +> **算法(Algorithm)**:解决特定问题求解步骤的准确而完整的描述,在计算机中表现为一系列指令的集合,算法代表着用系统的方法描述解决问题的策略机制。 + +简单而言,**「算法」** 指的就是解决问题的方法。 + +展开来讲,算法是某一系列运算步骤,它表达解决某一类计算问题的一般方法,对这类方法的任何一个输入,它可以按步骤一步一步计算,最终产生一个输出。它不依赖于任何一种语言,可以用 **自然语言、编程语言(Python、C、C++、Java 等)描述**,也可以用 **伪代码、流程图** 来表示。 + +下面我们举几个例子来说明什么是算法。 + +- 示例 1: + +> **问题描述**: +> +> - 从上海到北京,应该怎么去? +> +> **解决方法**: +> +> 1. 选择坐飞机,坐飞机用的时间最少,但费用最高。 +> 2. 选择坐长途汽车,坐长途汽车费用低,但花费时间长。 +> 3. 选择坐高铁或火车,花费时间不算太长,价格也不算太贵。 + +- 示例 2: + +> **问题描述**: +> +> - 如何计算 $1 + 2 + 3 + … + 100$ 的值? +> +> **解决方法**: +> +> 1. 用计算器从 $1$ 开始,不断向右依次加上 $2$,再加上 $3$,...,依次加到 $100$,得出结果为 $5050$。 +> 2. 根据高斯求和公式:**和 = (首项 + 末项) × 项数 ÷ 2**,直接算出结果为:$\frac{(1+100) \times 100}{2} = 5050$。 + +- 示例 3: + +> **问题描述**: +> +> - 如何对一个 $n$ 个整数构成的数组进行升序排序? +> +> **解决方法**: +> +> 1. 使用冒泡排序对 $n$ 个整数构成的数组进行升序排序。 +> 2. 选择插入排序、归并排序、快速排序等等其他排序算法对 $n$ 个整数构成的数组进行升序排序。 + +以上 $3$ 个示例中的解决方法都可以看做是算法。从上海去北京的解决方法可以看做是算法,对 $1 \sim 100$ 的数进行求和的计算方法也可以看做是算法。对数组进行排序的方法也可以看做是算法。并且从这 $3$ 个示例中可以看出对于一个特定的问题,往往有着不同的算法。 + +### 2.1 算法的基本特性 + +算法其实就是一系列的运算步骤,这些运算步骤可以解决特定的问题。除此之外,**算法** 应必须具备以下特性: + +1. **输入**:对于待解决的问题,都要以某种方式交给对应的算法。在算法开始之前最初赋给算法的参数称为输入。比如示例 $1$ 中的输入就是出发地和目的地的参数(北京,上海),示例 $3$ 中的输入就是 $n$ 个整数构成的数组。一个算法可以有多个输入,也可以没有输入。比如示例 $2$ 是对固定问题的求解,就可以看做没有输入。 +2. **输出**:算法是为了解决问题存在的,最终总需要返回一个结果。所以至少需要一个或多个参数作为算法的输出。比如示例 $1$ 中的输出就是最终选择的交通方式,示例 $2$ 中的输出就是和的结果。示例 $3$ 中的输出就是排好序的数组。 +3. **有穷性**:算法必须在有限的步骤内结束,并且应该在一个可接受的时间内完成。比如示例 $1$,如果我们选择五一从上海到北京去旅游,结果五一纠结了三天也没决定好怎么去北京,那么这个旅游计划也就泡汤了,这个算法自然也是不合理的。 +4. **确定性**:组成算法的每一条指令必须有着清晰明确的含义,不能令读者在理解时产生二义性或者多义性。就是说,算法的每一个步骤都必须准确定义而无歧义。 +5. **可行性**:算法的每一步操作必须具有可执行性,在当前环境条件下可以通过有限次运算实现。也就是说,每一步都能通过执行有限次数完成,并且可以转换为程序在计算机上运行并得到正确的结果。 + +### 2.2 算法追求的目标 + +研究算法的作用,就是为了使解决问题的方法变得更加高效。对于给定的问题,我们往往会有多种算法来解决。而不同算法的 **成本** 也是不同的。总体而言,一个优秀的算法至少应该追求以下两个目标: + +1. **所需运行时间更少(时间复杂度更低)**; +2. **占用内存空间更小(空间复杂度更低)**。 + +假设计算机执行一条命令的时间为 $1$ 纳秒(假定值),第一种算法需要执行 $100$ 纳秒,第二种算法则需要执行 $3$ 纳秒。如果不考虑占用内存空间的话,很明显第二种算法比第一种算法要好很多。 + +假设计算机一个内存单元的大小为一个字节,第一种算法需要占用 $3$ 个字节大小的内存空间,第二种算法则需要占用 $100$ 个字节大小的内存空间,如果不考虑运行时间的话,很明显第一种算法比第二种算法要好很多。 + +现实中算法,往往是需要同时从运行时间、占用空间两个方面考虑问题。当然,运行时间越少,占用空间越小的算法肯定是越好的,但总是会有各种各样的因素导致了运行时间和占用空间不可兼顾。比如,在程序运行时间过高时,我们可以考虑在空间上做文章,牺牲一定量的空间,来换取更短的运行时间。或者在程序对运行时间要求不是很高,而设备内存又有限的情况下,选择占用空间更小,但需要牺牲一定量的时间的算法。 + +当然,除了对运行时间和占用内存空间的追求外,一个好的算法还应该追求以下目标: + +1. **正确性**:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。 +2. **可读性**:可读性指的是算法遵循标识符命名规则,简洁易懂,注释语句恰当,方便自己和他人阅读,便于后期修改和调试。 +3. **健壮性**:健壮性指的是算法对非法数据以及操作有较好的反应和处理。 + +这 $3$ 个目标是算法的基本标准,是所有算法所必须满足的。一般我们对好的算法的评判标准就是上边提到的 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 + + +## 3. 总结 + +### 3.1 数据结构 + +数据结构可以分为 **「逻辑结构」** 和 **「物理结构」**。 + +- 逻辑结构可分为:**集合结构**、**线性结构**、**树形结构**、**图形结构**。 + +- 物理结构可分为:**顺序存储结构**、**链式存储结构**。 + +「逻辑结构」指的是数据之间的 **关系**,「物理结构」指的是这种关系 **在计算机中的表现形式**。 + +例如:线性表中的「栈」,其数据元素之间的关系是一对一的,除头和尾结点之外的每个结点都有唯一的前驱和唯一的后继,这体现的是逻辑结构。而对于栈中的结点来说,可以使用顺序存储(也就是 **顺序栈**)的方式存储在计算机中,其结构在计算机中的表现形式就是一段连续的存储空间,栈中每个结点和它的前驱结点、后继结点在物理上都是相邻的。当然,栈中的结点也可以使用链式存储(也即是 **链式栈**),每个结点和它的前驱结点、后继结点在物理上不一定相邻,每个结点是靠前驱结点的指针域来进行访问的。 + +### 3.2 算法 + +**「算法」** 指的就是解决问题的方法。算法是一系列的运算步骤,这些运算步骤可以解决特定的问题。 + +算法拥有 5 个基本特性:**输入**、**输出**、**有穷性**、**确定性**、**可行性**。 + +算法追求的目标有 5 个:**正确性**、**可读性**、**健壮性**、**所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 + +--- + +以上就是本篇的全部内容,我们将在下一篇文章具体讲解算法的「时间复杂度」和「空间复杂度」。 + +## 参考资料 + +- 【文章】[数据结构与算法 · 看云](https://www.kancloud.cn/zxliu/algorithm/2088786) +- 【书籍】大话数据结构——程杰 著 +- 【书籍】趣学算法——陈小玉 著 +- 【书籍】计算机程序设计艺术(第一卷)基本算法(第三版)——苏运霖 译 +- 【书籍】算法艺术与信息学竞赛——刘汝佳、黄亮 著 diff --git a/docs/00_preface/00_03_algorithm_complexity.md b/docs/00_preface/00_03_algorithm_complexity.md new file mode 100644 index 00000000..2e1e4e94 --- /dev/null +++ b/docs/00_preface/00_03_algorithm_complexity.md @@ -0,0 +1,313 @@ +## 1. 算法复杂度简介 + +> **算法复杂度(Algorithm complexity)**:在问题的输入规模为 $n$ 的条件下,程序的时间使用情况和空间使用情况。 + +「算法分析」的目的在于改进算法。正如上文中所提到的那样:算法所追求的就是 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。所以进行「算法分析」,就是从运行时间情况、空间使用情况两方面对算法进行分析。 + +比较两个算法的优劣通常有两种方法: + +- **事后统计**:将两个算法各编写一个可执行程序,交给计算机执行,记录下各自的运行时间和占用存储空间的实际大小,从中挑选出最好的算法。 +- **预先估算**:在算法设计出来之后,根据算法中包含的步骤,估算出算法的运行时间和占用空间。比较两个算法的估算值,从中挑选出最好的算法。 + +大多数情况下,我们会选择第 $2$ 种方式。因为第 $1$ 种方式的工作量实在太大,得不偿失。另外,即便是同一个算法,用不同的语言实现,在不同的计算机上运行,所需要的运行时间都不尽相同。所以我们一般采用预先估算的方法来衡量算法的好坏。 + +采用预先估算的方式下,编译语言、计算机运行速度都不是我们所考虑的对象。我们只关心随着问题规模 $n$ 扩大时,时间开销、空间开销的增长情况。 + +这里的 **「问题规模 $n$」** 指的是:算法问题输入的数据量大小。对于不同的算法,定义也不相同。 + +- 排序算法中:$n$ 表示需要排序的元素数量。 +- 查找算法中:$n$ 表示查找范围内的元素总数:比如数组大小、二维矩阵大小、字符串长度、二叉树节点数、图的节点数、图的边界点等。 +- 二进制计算相关算法中:$n$ 表示二进制的展开宽度。 + +一般来说,问题的输入规模越接近,相应的计算成本也越接近。而随着问题输入规模的扩大,计算成本也呈上升趋势。 + +接下来,我们将具体讲解「时间复杂度」和「空间复杂度」。 + +## 2. 时间复杂度 + +### 2.1 时间复杂度简介 + +> **时间复杂度(Time Complexity)**:在问题的输入规模为 $n$ 的条件下,算法运行所需要花费的时间,可以记作为 $T(n)$。 + +我们将 **基本操作次数** 作为时间复杂度的度量标准。换句话说,时间复杂度跟算法中基本操作次数的数量正相关。 + +- **基本操作** :算法执行中的每一条语句。每一次基本操作都可在常数时间内完成。 + +基本操作是一个运行时间不依赖于操作数的操作。 + +比如两个整数相加的操作,如果两个数的规模不大,运行时间不依赖于整数的位数,则相加操作就可以看做是基本操作。 + +反之,如果两个数的规模很大,相加操作依赖于两个数的位数,则两个数的相加操作不是一个基本操作,而每一位数的相加操作才是一个基本操作。 + +下面通过一个具体例子来说明一下如何计算时间复杂度。 + +```python +def algorithm(n): + fact = 1 # 执行 1 次 + for i in range(1, n + 1): # 执行 n 次 + fact *= i # 执行 n 次 + return fact # 执行 1 次 +``` + +在这个例子中: + +1. `fact = 1` 执行了 1 次。 +2. `for i in range(1, n + 1)` 执行了 n 次。 +3. `fact *= i` 执行了 n 次。 +4. `return fact` 执行了 1 次。 + +总执行次数为:$1 + n + n + 1 = 2 \times n + 2$,可以用一个函数 $f(n)$ 来表达语句的执行次数:$f(n) = 2 \times n + 2$。忽略掉 $f(n)$ 中的常数系数、低阶项、常数项,因此时间复杂度为 $O(n)$。 + +这个例子展示了如何从具体的执行次数推导出算法的时间复杂度。虽然实际执行次数是 $2 \times n + 2$,但在复杂度分析中,我们只关注增长最快的项,因此最终的时间复杂度是 $O(n)$。 + +时间复杂度的函数可以表示为:$T(n) = O(f(n))$。它表示的是随着问题规模 $n$ 的增大,算法执行时间的增长趋势跟 $f(n)$ 相同。$O$ 是一种渐进符号,与 $f(n)$ 成正比例关系,$T(n)$ 称作算法的 **渐进时间复杂度(Asymptotic Time Complexity)**,简称为 **时间复杂度**。 + +所谓「算法执行时间的增长趋势」是一个模糊的概念,通常我们要借助像上边公式中 $O$ 这样的「渐进符号」来表示时间复杂度。 + +### 2.2 渐进符号 + +> **渐进符号(Asymptotic Symbol)**:专门用来刻画函数的增长速度的。简单来说,渐进符号只保留了 **最高阶幂**,忽略了一个函数中增长较慢的部分,比如 **低阶幂**、**系数**、**常量**。因为当问题规模变的很大时,这几部分并不能左右增长趋势,所以可以忽略掉。 + +经常用到的渐进符号有三种: $\Theta$ 渐进紧确界符号、$O$ 渐进上界符号、$\Omega$ 渐进下界符号。接下来我们将依次讲解。 + +#### 2.2.1 $\Theta$ 渐进紧确界符号 + +> **$\Theta$ 渐进紧确界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = \Theta(g(n))$。存在正常量 $c_1$、$c_2$ 和 $n_0$,使得对于所有 $n \ge n_0$ 时,有 $0 \le c_1 \cdot g(n) \le f(n) \le c_2 \cdot g(n)$。 + +也就是说,如果函数 $f(n) = \Theta(g(n))$,那么我们能找到两个正数 $c_1$、$c_2$,使得 $f(n)$ 被 $c_1 \cdot g(n)$ 和 $c_2 \cdot g(n)$ 夹在中间。 + +例如:$T(n) = 3n^2 + 4n + 5 = \Theta(n^2)$,可以找到 $c_1 = 1$,$c_2 = 12$,$n_0 = 1$,使得对于所有 $n \ge 1$,都有 $n^2 \le 3n^2 + 4n + 5 \le 12n^2$。 + +#### 2.2.2 $O$ 渐进上界符号 + +> **$O$ 渐进上界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = O(g(n))$。存在常量 $c$,$n_0$,使得当 $n > n_0$ 时,有 $0 \le f(n) \le c \cdot g(n)$。 + +$\Theta$ 符号渐进地给出了一个函数的上界和下界,如果我们只知道一个函数的上界,可以使用 $O$ 渐进上界符号。 + +#### 2.2.3 $\Omega$ 渐进下界符号 + +> **$\Omega$ 渐进下界符号**:对于函数 $f(n)$ 和 $g(n)$,$f(n) = \Omega(g(n))$。存在常量 $c$,$n_0$,使得当 $n > n_0$ 时,有 $0 \le c \cdot g(n) \le f(n)$。 + +同样,如果我们只知道函数的下界,可以使用 $\Omega$ 渐进下界符号。 + +![$\Theta$、$O$ 和 $\Omega$ 记号对比](https://qcdn.itcharge.cn/images/202109092356694.png) + +### 2.3 时间复杂度计算 + +渐进符号可以渐进地描述一个函数的上界、下界,同时也可以描述算法执行时间的增长趋势。 + +在计算时间复杂度的时候,我们经常使用 $O$ 渐进上界符号。因为我们关注的通常是算法用时的上界,而不用关心其用时的下界。 + +那么具体应该如何计算时间复杂度呢? + +求解时间复杂度一般分为以下几个步骤: + +- **找出算法中的基本操作(基本语句)**:算法中执行次数最多的语句就是基本语句,通常是最内层循环的循环体部分。 +- **计算基本语句执行次数的数量级**:只需要计算基本语句执行次数的数量级,即保证函数中的最高次幂正确即可。像最高次幂的系数和低次幂可以忽略。 +- **用大 O 表示法表示时间复杂度**:将上一步中计算的数量级放入 O 渐进上界符号中。 + +同时,在求解时间复杂度还要注意一些原则: + +- **加法原则**:总的时间复杂度等于量级最大的基本语句的时间复杂度。 + +如果 $T_1(n) = O(f_1(n))$,$T_2(n) = O(f_2(n))$,$T(n) = T_1(n) + T_2(n)$,则 $T(n) = O(f(n)) = max(O(f_1(n)), \enspace O(f_2(n))) = O(max(f_1(n), \enspace f_2(n)))$。 + +- **乘法原则**:循环嵌套代码的复杂度等于嵌套内外基本语句的时间复杂度乘积。 + +如果 $T_1 = O(f_1(n))$,$T_2 = O(f_2(n))$,$T(n) = T_1(n) \times T_2(n)$,则 $T(n) = O(f(n)) = O(f_1(n)) \times O(f_2(n)) = O(f_1(n) \times f_2(n))$。 + +下面通过实例来说明如何计算时间复杂度。 + +#### 2.3.1 常数 $O(1)$ + +一般情况下,只要算法中不存在循环语句、递归语句,其时间复杂度都为 $O(1)$。 + +$O(1)$ 只是常数阶时间复杂度的一种表示方式,并不是指只执行了一行代码。只要代码的执行时间不随着问题规模 $n$ 的增大而增长,这样的算法时间复杂度都记为 $O(1)$。 + +```python +def algorithm(n): + a = 1 + b = 2 + res = a * b + n + return res +``` + +上述代码虽然有 $4$ 行代码,但时间复杂度也是 $O(1)$,而不是 $O(3)$。 + +#### 2.3.2 线性 $O(n)$ + +一般含有非嵌套循环,且单层循环下的语句执行次数为 $n$ 的算法涉及线性时间复杂度。这类算法随着问题规模 $n$ 的增大,对应计算次数呈线性增长。 + +```python +def algorithm(n): + sum = 0 + for i in range(n): + sum += 1 + return sum +``` + +上述代码中 `sum += 1` 的执行次数为 $n$ 次,所以这段代码的时间复杂度为 $O(n)$。 + + +#### 2.3.3 平方 $O(n^2)$ + +一般含有双层嵌套,且每层循环下的语句执行次数为 $n$ 的算法涉及平方时间复杂度。这类算法随着问题规模 $n$ 的增大,对应计算次数呈平方关系增长。 + +```python +def algorithm(n): + res = 0 + for i in range(n): + for j in range(n): + res += 1 + return res +``` + +上述代码中,`res += 1` 在两重循环中,根据时间复杂度的乘法原理,这段代码的执行次数为 $n^2$ 次,所以其时间复杂度为 $O(n^2)$。 + +#### 2.3.4 阶乘 $O(n!)$ + +阶乘时间复杂度一般出现在与「全排列」、「旅行商问题暴力解法」相关的算法中。这类算法随着问题规模 $n$ 的增大,对应计算次数呈阶乘关系增长。 + +```python +def permutations(arr, start, end): + if start == end: + print(arr) + return + + for i in range(start, end): + arr[i], arr[start] = arr[start], arr[i] + permutations(arr, start + 1, end) + arr[i], arr[start] = arr[start], arr[i] +``` + +上述代码中实现「全排列」使用了递归的方法。假设数组 $arr$ 长度为 $n$,第一层 `for` 循环执行了 $n$ 次,第二层 `for` 循环执行了 $n - 1$ 次。以此类推,最后一层 `for` 循环执行了 $1$ 次,将所有层 `for` 循环的执行次数累乘起来为 $n \times (n - 1) \times (n - 2) \times … \times 2 \times 1 = n!$ 次。则整个算法的 `for` 循环中基本语句的执行次数为 $n!$ 次,所以对应时间复杂度为 $O(n!)$。 + +#### 2.3.5 对数 $O(\log n)$ + +对数时间复杂度一般出现在「二分查找」、「分治」这种一分为二的算法中。这类算法随着问题规模 $n$ 的增大,对应的计算次数呈对数关系增长。 + +```python +def algorithm(n): + cnt = 1 + while cnt < n: + cnt *= 2 + return cnt +``` + +上述代码中 `cnt = 1` 的时间复杂度为 $O(1)$ 可以忽略不算。`while` 循环体中 $cnt$ 从 $1$ 开始,每循环一次都乘以 $2$。当大于等于 $n$ 时循环结束。变量 $cnt$ 的取值是一个等比数列:$2^0, 2^1, 2^2, …, 2^x$,根据 $2^x = n$,可以得出这段循环体的执行次数为 $\log_2n$,所以这段代码的时间复杂度为 $O(\log_2n)$。 + +因为 $\log_2 n = k \times \log_{10} n$,这里 $k \approx 3.322$,是一个常数系数,$\log_2 n$ 与 $\log_{10} n$ 之间差别比较小,可以忽略 $k$。并且 $\log_{10} n$ 也可以简写成 $\log n$,所以为了方便书写,通常我们将对数时间复杂度写作是 $O(\log n)$。 + +#### 2.3.6 线性对数 $O(n \times \log n)$ + + 线性对数一般出现在排序算法中,例如「快速排序」、「归并排序」、「堆排序」等。这类算法随着问题规模 $n$ 的增大,对应的计算次数呈线性对数关系增长。 + +```python +def algorithm(n): + cnt = 1 + res = 0 + while cnt < n: + cnt *= 2 + for i in range(n): + res += 1 + return res +``` + +上述代码中外层循环的时间复杂度为 $O(\log n)$,内层循环的时间复杂度为 $O(n)$,且两层循环相互独立,则总体时间复杂度为 $O(n \times \log n)$。 + +#### 2.3.7 常见时间复杂度关系 + +根据从小到大排序,常见的时间复杂度主要有:$O(1)$ < $O(\log n)$ < $O(n)$ < $O(n \times \log n)$ < $O(n^2)$ < $O(n^3)$ < $O(2^n)$ < $O(n!)$ < $O(n^n)$。 + +### 2.4 最佳、最坏、平均时间复杂度 + +时间复杂度是一个关于输入问题规模 $n$ 的函数。但是因为输入问题的内容不同,习惯将「时间复杂度」分为「最佳」、「最坏」、「平均」三种情况。这三种情况的具体含义如下: + +- **最佳时间复杂度**:每个输入规模下用时最短的输入所对应的时间复杂度。 +- **最坏时间复杂度**:每个输入规模下用时最长的输入所对应的时间复杂度。 +- **平均时间复杂度**:每个输入规模下所有可能的输入所对应的平均用时复杂度(随机输入下期望用时的复杂度)。 + +我们通过一个例子来分析下最佳、最坏、最差时间复杂度。 + +```python +def find(nums, val): + pos = -1 + for i in range(n): + if nums[i] == val: + pos = i + break + return pos +``` + +这段代码要实现的功能是:从一个整数数组 $nums$ 中查找值为 $val$ 的变量出现的位置。如果不考虑 `break` 语句,根据「2.3 时间复杂度计算」中讲的分析步骤,这个算法的时间复杂度是 $O(n)$,其中 $n$ 代表数组的长度。 + +但是如果考虑 `break` 语句,那么就需要考虑输入的内容了。如果数组中第 $1$ 个元素值就是 $val$,那么剩下 $n - 1$ 个数据都不要遍历了,那么时间复杂度就是 $O(1)$,即最佳时间复杂度为 $O(1)$。如果数组中不存在值为 $val$ 的变量,那么就需要把整个数组遍历一遍,时间复杂度就变成了 $O(n)$,即最差时间复杂度为 $O(n)$。 + +这样下来,时间复杂度就不唯一了。怎么办? + +我们都知道,最佳时间复杂度和最坏时间复杂度都是极端条件下的时间复杂度,发生的概率其实很小。为了能更好的表示正常情况下的复杂度,所以我们一般采用平均时间复杂度作为时间复杂度的计算方式。 + +还是刚才的例子,在数组 $nums$ 中查找变量值为 $val$ 的位置,总共有 $n + 1$ 种情况:「在数组的的 $0 \sim n - 1$ 个位置上」和「不在数组中」。我们将所有情况下,需要执行的语句次数累加起来,再除以 $n + 1$,就可以得到平均需要执行的语句次数,即:$\frac{1 + 2 + 3 + ... + n + n}{n + 1} = \frac{n(n + 3)}{2(n + 1)}$。将公式简化后,得到的平均时间复杂度就是 $O(n)$。 + +通常只有同一个算法在输入内容不同,不同时间复杂度有量级的差距时,我们才会通过三种时间复杂度表示法来区分。一般情况下,使用其中一种就可以满足需求了。 + +## 3. 空间复杂度 + +### 3.1 空间复杂度简介 + +> **空间复杂度(Space Complexity)**:在问题的输入规模为 $n$ 的条件下,算法所占用的空间大小,可以记作为 $S(n)$。一般将 **算法的辅助空间** 作为衡量空间复杂度的标准。 + +除了执行时间的长短,算法所需储存空间的多少也是衡量性能的一个重要方面。而在「2. 时间复杂度」中提到的渐进符号,也同样适用于空间复杂度的度量。空间复杂度的函数可以表示为 $S(n) = O(f(n))$,它表示的是随着问题规模 $n$ 的增大,算法所占空间的增长趋势跟 $f(n)$ 相同。 + +相比于算法的时间复杂度计算来说,算法的空间复杂度更容易计算,主要包括「局部变量(算法范围内定义的变量)所占用的存储空间」和「系统为实现递归(如果算法是递归的话)所使用的堆栈空间」两个部分。 + +下面通过实例来说明如何计算空间复杂度。 + +### 3.1 空间复杂度计算 + +#### 3.1.1 常数 $O(1)$ + +```python +def algorithm(n): + a = 1 + b = 2 + res = a * b + n + return res +``` + +上述代码中使用 $a$、$b$、$res$ 这 $3$ 个局部变量,其所占空间大小为常数阶,并不会随着问题规模 $n$ 的在增大而增大,所以该算法的空间复杂度为 $O(1)$。 + +#### 3.1.2 线性 $O(n)$ + +```python +def algorithm(n): + if n <= 0: + return 1 + return n * algorithm(n - 1) +``` + +上述代码采用了递归调用的方式。每次递归调用都占用了 $1$ 个栈帧空间,总共调用了 $n$ 次,所以该算法的空间复杂度为 $O(n)$。 + +#### 3.1.3 常见空间复杂度关系 + +根据从小到大排序,常见的算法复杂度主要有:$O(1)$ < $O(\log n)$ < $O(n)$ < $O(n^2)$ < $O(2^n)$ 等。 + +## 4. 总结 + +**「算法复杂度」** 包括 **「时间复杂度」** 和 **「空间复杂度」**,用来分析算法执行效率与输入问题规模 $n$ 的增长关系。通常采用 **「渐进符号」** 的形式来表示「算法复杂度」。 + +常见的时间复杂度有:$O(1)$、$O(\log n)$、$O(n)$、$O(n \times \log n)$、$O(n^2)$、$O(n^3)$、$O(2^n)$、$O(n!)$。 + +常见的空间复杂度有:$O(1)$、$O(\log n)$、$O(n)$、$O(n^2)$。 + +## 参考资料 + +- 【书籍】数据结构(C++ 语言版)- 邓俊辉 著 +- 【书籍】算法导论 第三版(中文版)- 殷建平等 译 +- 【书籍】算法艺术与信息学竞赛 - 刘汝佳、黄亮 著 +- 【书籍】数据结构(C 语言版)- 严蔚敏 著 +- 【书籍】趣学算法 - 陈小玉 著 +- 【文章】[复杂度分析 - 数据结构与算法之美 王争](https://time.geekbang.org/column/intro/126) +- 【文章】[算法复杂度(时间复杂度+空间复杂度)](https://www.biancheng.net/algorithm/complexity.html) +- 【文章】[算法基础 - 复杂度 - OI Wiki](https://oi-wiki.org/basic/complexity/) +- 【文章】[图解算法数据结构 - 算法复杂度 - LeetBook - 力扣](https://leetcode.cn/leetbook/read/illustration-of-algorithm/r84gmi/) diff --git a/docs/00_preface/00_04_leetcode_guide.md b/docs/00_preface/00_04_leetcode_guide.md new file mode 100644 index 00000000..18407a0a --- /dev/null +++ b/docs/00_preface/00_04_leetcode_guide.md @@ -0,0 +1,288 @@ +## 1. LeetCode 是什么 + +**「LeetCode」** 是一个代码在线评测平台(Online Judge),包含了 **算法**、**数据库**、**Shell**、**多线程** 等不同分类的题目,其中以算法题目为主。我们可以通过解决 LeetCode 题库中的问题来练习编程技能,以及提高算法能力。 + +LeetCode 上有 $3000+$ 道的编程问题,支持 $16+$ 种编程语言(C、C++、Java、Python 等),还有一个活跃的社区,可以用于分享技术话题、职业经历、题目交流等。 + +并且许多知名互联网公司在面试的时候喜欢考察 LeetCode 题目,通常会以手写代码的形式出现。需要面试者对给定问题进行分析并给出解答,有时还会要求面试者分析算法的时间复杂度和空间复杂度,以及算法思路。面试官通过考察面试者对常用算法的熟悉程度和实现能力来确定面试者解决问题的思维能力水平。 + +所以无论是面试国内还是国外的知名互联网公司,通过 LeetCode 刷题,充分准备好算法,对拿到一个好公司的好 offer 都是有帮助的。 + +## 2. LeetCode 新手入门 + +### 2.1 LeetCode 注册 + +1. 打开 LeetCode 中文主页,链接:[力扣(LeetCode)官网](https://leetcode.cn/)。 +2. 输入手机号,获取验证码。 +3. 输入验证码之后,点击「登录 / 注册」,就注册好了。 + +![LeetCode 注册页面](https://qcdn.itcharge.cn/images/20210901155409.png) + +### 2.2 LeetCode 题库 + +「[题库](https://leetcode.cn/problemset/algorithms/)」是 LeetCode 上最直接的练习入口,在这里可以根据题目的标签、难度、状态进行刷题。也可以按照随机一题开始刷题。 + +![LeetCode 题库页面](https://qcdn.itcharge.cn/images/20210901155423.png) + +#### 1. 题目标签 + +LeetCode 的题目涉及了许多算法和数据结构。有贪心,搜索,动态规划,链表,二叉树,哈希表等等,可以通过选择对应标签进行专项刷题,同时也可以看到对应专题的完成度情况。 + +![LeetCode 题目标签](https://qcdn.itcharge.cn/images/20210901155435.png) + +#### 2. 题目列表 + +LeetCode 提供了题目的搜索过滤功能。可以筛选相关题单、不同难易程度、题目完成状态、不同标签的题目。还可以根据题目编号、题解数目、通过率、难度、出现频率等进行排序。 + +![LeetCode 题目列表](https://qcdn.itcharge.cn/images/20210901155450.png) + +#### 3. 当前进度 + +当前进度提供了一个直观的进度展示。在这里可以看到自己的练习概况。进度会自动展现当前的做题情况。也可以点击「[进度设置](https://leetcode.cn/session/)」创建新的进度,在这里还可以修改、删除相关的进度。 + +![LeetCode 当前进度](https://qcdn.itcharge.cn/images/20210901155500.png) + +#### 4. 题目详情 + +从题目大相关题目点击进去,就可以看到这道题目的内容描述和代码编辑器。在这里还可以查看相关的题解和自己的提交记录。 + +![LeetCode 题目详情](https://qcdn.itcharge.cn/images/20210901155529.png) + +### 2.3 LeetCode 刷题语言 + +大厂在面试算法的时候考察的是基本功,用什么语言没有什么限制,也不会影响成绩。日常刷题建议使用自己熟悉的语言,或者语法简洁的语言刷题。 + +相对于 Java、Python 而言,C、C++ 相关的语法比较复杂,在做题的时候一方面需要思考思路,另一方面还要研究语法。并且复杂的语法也不利于看懂思路,耗费时间较多,不利于刷题效率。在面试的时候往往需要一个小时内尽可能的完成更多的题目,C++ 一旦语法出错很容易慌乱。当然 LeetCode 周赛的大神更偏向于使用 C++ 刷题,这是因为用 C++ 参加算法竞赛已经成为传统了,绝大多数的 OI / ACM 竞赛选手都是 C++ 大神。 + +就我个人经历而言,我大学参加 ACM 竞赛的时候,用的是 C、C++ 和一点点的 Java。现在刷 LeetCode 为了更高的刷题效率,选择了 Python。感觉用 Python 刷题能更加专注于算法与数据结构本身,也能获得更快的刷题效率。 + +> 人生苦短,我用 Python。 + +### 2.4 LeetCode 刷题流程 + +在「2.2 LeetCode 题库 —— 4. 题目详情」中我们介绍了题目的相关情况。 + +![LeetCode 题目详情](https://qcdn.itcharge.cn/images/20210901155529.png) + +可以看到左侧区域为题目内容描述区域,还可以看到题目的内容描述和一些示例数据。而右侧是代码编辑区域,代码编辑区域里边默认显示了待实现的方法。 + +我们需要在代码编辑器中根据方法给定的参数实现对应的算法,并返回题目要求的结果。然后还要经过「执行代码」测试结果,点击「提交」后,显示执行结果为「**通过**」时,才算完成一道题目。 + +![LeetCode 提交记录](https://qcdn.itcharge.cn/images/20210901155545.png) + +总结一下我们的刷题流程为: + +1. 在 LeetCode 题库中选择一道自己想要解决的题目。 +2. 查看题目左侧的题目描述,理解题目要求。 +3. 思考解决思路,并在右侧代码编辑区域实现对应的方法,并返回题目要求的结果。 +4. 如果实在想不出解决思路,可以查看题目相关的题解,努力理解他人的解题思路和代码。 +5. 点击「执行代码」按钮测试结果。 + - 如果输出结果与预期结果不符,则回到第 3 步重新思考解决思路,并改写代码。 +6. 如果输出结果与预期符合,则点击「提交」按钮。 + - 如果执行结果显示「编译出错」、「解答错误」、「执行出错」、「超出时间限制」、「超出内存限制」等情况,则需要回到第 3 步重新思考解决思路,或者思考特殊数据,并改写代码。 +7. 如果执行结果显示「通过」,恭喜你通过了这道题目。 + +接下来我们将通过「[2235. 两整数相加 - 力扣(LeetCode)](https://leetcode.cn/problems/add-two-integers/)」这道题目来讲解如何在 LeetCode 上刷题。 + +### 2.5 LeetCode 第一题 + +#### 2.5.1 题目链接 + +- [2235. 两整数相加 - 力扣(LeetCode)](https://leetcode.cn/problems/add-two-integers/) + +#### 2.5.2 题目大意 + +**描述**:给定两个整数 $num1$ 和 $num2$。 + +**要求**:返回这两个整数的和。 + +**说明**: + +- $-100 \le num1, num2 \le 100$。 + +**示例**: + +- 示例 1: + +```python +示例 1: +输入:num1 = 12, num2 = 5 +输出:17 +解释:num1 是 12,num2 是 5,它们的和是 12 + 5 = 17,因此返回 17。 +``` + +- 示例 2: + +```python +输入:num1 = -10, num2 = 4 +输出:-6 +解释:num1 + num2 = -6,因此返回 -6。 +``` + +#### 2.5.3 解题思路 + +##### 思路 1:直接计算 + +1. 直接计算整数 $num1$ 与 $num2$ 的和,返回 $num1 + num2$ 即可。 + +##### 思路 1:代码 + +```python +class Solution: + def sum(self, num1: int, num2: int) -> int: + return num1 + num2 +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + + +理解了上面这道题的题意,就可以试着自己编写代码,并尝试提交通过。如果提交结果显示「通过」,那么恭喜你完成了 LeetCode 上的第一题。虽然只是一道题,但这意味着刷题计划的开始!希望你能坚持下去,得到应有的收获。 + +## 3. LeetCode 刷题攻略 + +### 3.1 LeetCode 前期准备 + +如果你是一个对基础算法和数据结构完全不懂的小白,那么在刷 LeetCode 之前,建议先学习一下基础的 **「数据结构」** 和 **「算法」** 知识,这样在开始刷题的时候才不会那么痛苦。 + +基础的 **「数据结构」** 和 **「算法」** 知识包括: + +- **常考的数据结构**:**数组**、**字符串**、**链表**、**树(如二叉树)** 等。 +- **常考的算法**:**分治算法**、**贪心算法**、**穷举算法**、**回溯算法**、**动态规划** 等。 + +这个阶段推荐看一些经典的算法基础书来进行学习。这里推荐一下我看过的感觉不错的算法书: + +- 【书籍】「[算法(第 4 版)- 谢路云 译](https://book.douban.com/subject/19952400/)」 +- 【书籍】「[大话数据结构 - 程杰 著](https://book.douban.com/subject/6424904/)」 +- 【书籍】「[趣学算法 - 陈小玉 著](https://book.douban.com/subject/27109832/)」 +- 【书籍】「[算法图解 - 袁国忠 译](https://book.douban.com/subject/26979890/)」 +- 【书籍】「[算法竞赛入门经典(第 2 版) - 刘汝佳 著](https://book.douban.com/subject/25902102/)」 +- 【书籍】「[数据结构与算法分析 - 冯舜玺 译](https://book.douban.com/subject/1139426/)」 +- 【书籍】「[算法导论(原书第 3 版) - 殷建平 / 徐云 / 王刚 / 刘晓光 / 苏明 / 邹恒明 / 王宏志 译](https://book.douban.com/subject/20432061/)」 + +当然,也可以直接看我写的「算法通关手册」,欢迎指正和提出建议,万分感谢。 + +- 「算法通关手册」GitHub 地址:[https://github.com/itcharge/AlgoNote](https://github.com/itcharge/AlgoNote) +- 「算法通关手册」电子书网站地址:[https://algo.itcharge.cn](https://algo.itcharge.cn) + +### 3.2 LeetCode 刷题顺序 + +讲个笑话,从前有个人以为 LeetCode 的题目是按照难易程度排序的,所以他从「[1. 两数之和](https://leetcode.cn/problems/two-sum)」 开始刷题,结果他卡在了 「[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays)」这道困难题上。 + +LeetCode 的题目序号并不是按照难易程度进行排序的,所以除非硬核人士,不建议按照序号顺序刷题。如果是新手刷题的话,推荐先从「简单」难度等级的算法题开始刷题。等简单题上手熟练之后,再开始按照标签类别,刷中等难度的题。中等难度的题刷差不多之后,可以考虑刷面试题或者难题。 + +其实 LeetCode 官方网站上就有整理好的题目不错的刷题清单。链接为:[https://leetcode.cn/leetbook/](https://leetcode.cn/leetbook/)。可以先刷这里边的题目卡片。我这里也做了一个整理。 + +推荐刷题顺序和目录如下: + +[1. 初级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-easy/)、[2. 数组类算法](https://leetcode.cn/leetbook/detail/all-about-array/)、[3. 数组和字符串](https://leetcode.cn/leetbook/detail/array-and-string/)、[4. 链表类算法](https://leetcode.cn/leetbook/detail/linked-list/)、[5. 哈希表](https://leetcode.cn/leetbook/detail/hash-table/)、[6. 队列 & 栈](https://leetcode.cn/leetbook/detail/queue-stack/)、[7. 递归](https://leetcode.cn/leetbook/detail/recursion/)、[8. 二分查找](https://leetcode.cn/leetbook/detail/binary-search/)、[9. 二叉树](https://leetcode.cn/leetbook/detail/data-structure-binary-tree/)、[10. 中级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-medium/)、[11. 高级算法](https://leetcode.cn/leetbook/detail/top-interview-questions-hard/)、[12. 算法面试题汇总](https://leetcode.cn/leetbook/detail/top-interview-questions/)。 + +当然还可以通过官方新推出的「[学习计划 - 力扣](https://leetcode.cn/study-plan/)」按计划每天刷题。 + +或者直接按照我整理的分类刷题列表进行刷题: + +- LeetCode 分类刷题列表:[点击打开「LeetCode 分类刷题列表」](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md) + +正在准备面试、没有太多时间刷题的小伙伴,可以按照我总结的「LeetCode 面试最常考 100 题」、「LeetCode 面试最常考 200 题」进行刷题。 + +> **说明**:「LeetCode 面试最常考 100 题」、「LeetCode 面试最常考 200 题」是笔者根据「[CodeTop 企业题库](https://codetop.cc/home)」按频度从高到低进行筛选,并且去除了一部分 LeetCode 上没有的题目和重复题目后得到的题目清单。 + +- 「LeetCode 面试最常考 100 题:[点击打开「LeetCode 面试最常考 100 题」](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_07_interview_100_list.md) +- 「LeetCode 面试最常考 200 题:[点击打开「LeetCode 面试最常考 200 题」](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_08_interview_200_list.md) + +### 3.3 LeetCode 刷题技巧 + +下面分享一下我在刷题过程中用到的刷题技巧。简单来说,可以分为 $5$ 条: + +> 1. 五分钟思考法 +> 2. 重复刷题 +> 3. 按专题分类刷题 +> 4. 写解题报告 +> 5. 坚持刷题 + +#### 3.3.1 五分钟思考法 + +> **五分钟思考法**:如果一道题如果 $5$ 分钟之内有思路,就立即动手写代码解题。如果 $5$ 分钟之后还没有思路,就直接去看题解。然后根据题解的思路,自己去实现代码。如果发现自己看了题解也无法实现代码,就认真阅读题解的代码,并理解代码的逻辑。 + +这种刷题方法其实跟英语里边的背单词过程是类似的。 + +一开始零基础学英语的时候,先学最简单的字母,不用纠结为什么这个字母这么写。然后学习简单的单词,也不用去纠结这个单词为啥就是这个意思,学就完事。在掌握了基本词汇之后,再去学习词组,学习短句子,然后长句子,再然后再看文章。 + +而且,在学英语单词的时候,也不是学一遍就会了。而是不断的重复练习、重复记忆加深印象。 + +算法刷题也是一样,零基础刷题的时候,不要过分纠结怎么自己就想不出来算法的解法,怎么就想不到更加高效的方法。遇到没有思路的题目,老老实实去看题解区的高赞题解,尽可能的让自己快速入门。 + +#### 3.3.2 重复刷题 + +> **重复刷题**:遇见不会的题,多刷几遍,不断加深理解。 + +算法题有时候一遍刷过去,过的时间长了可能就忘了,看到之前做的题不能够立马想到解题思路。这其实还是跟背单词一样,单词也不是看一遍就完全记住了。所以题目刷完一遍并不是结束了,还需要不断的回顾。 + +而且,一道题目可能有多种解法,还可能有好的算法思路。 + +最开始做的时候,可能只能想到一种思路,再做第二遍的时候,很有可能会想到了新的解法,新的优化方式等等。 + +所以,算法题在做完一遍之后遇见不会的,还可以多刷几遍,不断加深理解。 + +#### 3.3.3 按专题分类刷题 + +> **按专题分类刷题**:按照不同专题分类刷题,既可以巩固刚学完的算法知识,还可以提高刷题效率。 + +在上边「3.2 LeetCode 刷题顺序」我们给出了刷题顺序和目录。这里的刷题顺序其实就是按照不同分类来进行排序的。 + +我们可以在学习相关算法和数据结构知识时,顺便做一下该算法和数据结构知识专题下对应的题目清单。比如在学习完「链表」相关的基础知识时,可以将「链表」相关的基础题目刷完,或者刷官方 LeetBook 清单 [4. 链表类算法](https://leetcode.cn/leetbook/detail/linked-list/) 中的对应题目。 + +按照专题分类刷题的第一个好处是:**可以巩固刚学完的算法知识。** 如果是第一次学习对应的算法知识,刚学完可能对里边的相关知识理解的不够透彻,或者说可能会遗漏一些关键知识点,这时候可以通过刷对应题目的方式来帮助我们巩固刚学完的算法知识。 + +按照专题分类刷题的第二个好处是:**可以提高刷题效率。** 因为同一类算法题目所用到的算法知识其实是相同或者相似的,同一种解题思路可以运用到多道题目中。通过不断求解同一类算法专题下的题目,可以大大的提升我们的刷题速度。 + +#### 3.3.4 写解题报告 + +>**写解题报告**:如果能够用简介清晰的语言让别人听懂这道题目的思路,那就说明你真正理解了这道题的解法。 + +刷算法题,有一个十分有用的技巧,就是 **「写解题报告」**。如果你刷完一道题,能把这道题的解题步骤,做题思路用通俗易懂的话写成解题报告,那么这道题就算是掌握了。这其实就相当于「费曼学习法」的思维。 + +这样,也可以减少刷题的遍数。如果在写题的时候遇到之前刷过的题,但一时之间没有思路的,就可以看看自己之前的解题报告。这样就节省了大量重复刷题的时间。 + +#### 3.3.5 坚持刷题 + +> **坚持刷题**:算法刷题没有捷径,只有不断的刷题、总结,再刷题,再总结。 + +千万不要相信很多机构宣传的「3 天带你精通数据结构」、「7 天从算法零基础到精通」能让你快速学会算法知识。 + +学习算法和数据结构知识,不能靠速成,只能靠不断的积累,一步一步的推敲算法步骤,一遍又一遍的理解算法思想,才能掌握一个又一个的算法知识。而且还要不断的去刷该算法对应专题下的题目,才能将算法知识应用到日常的解题过程中。这样才能算彻底掌握了一个算法或一种解题思路。 + +根据我过去一年多和小伙伴们一起刷题打卡的经验发现:**那些能够坚持每天刷题,并最终学会一整套「基础算法知识」和「基础数据结构知识」的人,总是少数人**。 + +大部分总会因为种种主观和客观原因而放弃了刷题(工作繁忙、学习任务繁重、个人精力有限、时间不足等)。 + +但不管怎么样,如果你当初选择了学习算法知识,选择了通过刷题来通过面试,以便获取更好的工作岗位。那我希望在达成自己的目标之前,可以一直坚持下去,去「刻意练习」。在刷题的过程中收获知识,通过刷题得到满足感,从而把刷题变成兴趣。 + +这些话有些鸡汤了,但都是我的心里话。希望大家能够一起坚持刷题,争取早日实现自己的目标。 + +## 4. 总结 + +### 4.1 LeetCode + +LeetCode 是一个在线编程练习平台,主要用于提升算法和编程能力。它包含大量题目,涵盖多种编程语言,适合准备技术面试。 + +新手可以从简单题目开始,逐步学习数据结构和算法。注册后,用户可以通过题库选择题目,按标签或难度分类练习。刷题时,建议使用熟悉的编程语言,如 Python,以提高效率。 + +### 4.2 刷题技巧 + +刷题需要坚持和重复练习。按专题分类刷题效果更好,可以巩固知识点。写解题报告有助于加深理解。 + +## 练习题目 + +- [2235. 两整数相加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/add-two-integers.md) +- [1929. 数组串联](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/concatenation-of-array.md) + +## 参考资料 + +- 【文章】[What is LeetCode? - Quora](https://www.quora.com/What-is-Leetcode) +- 【文章】[LeetCode 帮助中心 - 力扣(LeetCode)](https://support.leetcode-cn.com/hc/) +- 【回答】[刷 leetcode 使用 python 还是 c++? - 知乎](https://www.zhihu.com/question/319448129) +- 【回答】[刷完 LeetCode 是什么水平?能拿到什么水平的 offer? - 知乎](https://www.zhihu.com/question/32019460) + diff --git a/docs/00_preface/00_05_solutions_list.md b/docs/00_preface/00_05_solutions_list.md new file mode 100644 index 00000000..9f504ed7 --- /dev/null +++ b/docs/00_preface/00_05_solutions_list.md @@ -0,0 +1,1036 @@ +# LeetCode 题解(已完成 860 道) + +### 第 1 ~ 99 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-two-numbers.md) | 递归、链表、数学 | 中等 | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) | 双指针、字符串、动态规划 | 中等 | +| [0007. 整数反转](https://leetcode.cn/problems/reverse-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-integer.md) | 数学 | 中等 | +| [0008. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/string-to-integer-atoi.md) | 字符串 | 中等 | +| [0009. 回文数](https://leetcode.cn/problems/palindrome-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/palindrome-number.md) | 数学 | 简单 | +| [0010. 正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/regular-expression-matching.md) | 递归、字符串、动态规划 | 困难 | +| [0011. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/container-with-most-water.md) | 贪心、数组、双指针 | 中等 | +| [0012. 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/integer-to-roman.md) | 哈希表、数学、字符串 | 中等 | +| [0013. 罗马数字转整数](https://leetcode.cn/problems/roman-to-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/roman-to-integer.md) | 哈希表、数学、字符串 | 简单 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0016. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum-closest.md) | 数组、双指针、排序 | 中等 | +| [0017. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md) | 哈希表、字符串、回溯 | 中等 | +| [0018. 四数之和](https://leetcode.cn/problems/4sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/4sum.md) | 数组、双指针、排序 | 中等 | +| [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) | 链表、双指针 | 中等 | +| [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) | 栈、字符串 | 简单 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) | 字符串、动态规划、回溯 | 中等 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/swap-nodes-in-pairs.md) | 递归、链表 | 中等 | +| [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-nodes-in-k-group.md) | 递归、链表 | 困难 | +| [0026. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md) | 数组、双指针 | 简单 | +| [0027. 移除元素](https://leetcode.cn/problems/remove-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-element.md) | 数组、双指针 | 简单 | +| [0028. 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) | 双指针、字符串、字符串匹配 | 简单 | +| [0029. 两数相除](https://leetcode.cn/problems/divide-two-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/divide-two-integers.md) | 位运算、数学 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md) | 数组、二分查找 | 中等 | +| [0035. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-insert-position.md) | 数组、二分查找 | 简单 | +| [0036. 有效的数独](https://leetcode.cn/problems/valid-sudoku/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-sudoku.md) | 数组、哈希表、矩阵 | 中等 | +| [0037. 解数独](https://leetcode.cn/problems/sudoku-solver/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sudoku-solver.md) | 数组、哈希表、回溯、矩阵 | 困难 | +| [0038. 外观数列](https://leetcode.cn/problems/count-and-say/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/count-and-say.md) | 字符串 | 中等 | +| [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) | 数组、回溯 | 中等 | +| [0040. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum-ii.md) | 数组、回溯 | 中等 | +| [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/first-missing-positive.md) | 数组、哈希表 | 困难 | +| [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | +| [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/multiply-strings.md) | 数学、字符串、模拟 | 中等 | +| [0044. 通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/wildcard-matching.md) | 贪心、递归、字符串、动态规划 | 困难 | +| [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0046. 全排列](https://leetcode.cn/problems/permutations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) | 数组、回溯 | 中等 | +| [0047. 全排列 II](https://leetcode.cn/problems/permutations-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations-ii.md) | 数组、回溯、排序 | 中等 | +| [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) | 数组、数学、矩阵 | 中等 | +| [0049. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/group-anagrams.md) | 数组、哈希表、字符串、排序 | 中等 | +| [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) | 递归、数学 | 中等 | +| [0051. N 皇后](https://leetcode.cn/problems/n-queens/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/n-queens.md) | 数组、回溯 | 困难 | +| [0052. N 皇后 II](https://leetcode.cn/problems/n-queens-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/n-queens-ii.md) | 回溯 | 困难 | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) | 数组、矩阵、模拟 | 中等 | +| [0055. 跳跃游戏](https://leetcode.cn/problems/jump-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game.md) | 贪心、数组、动态规划 | 中等 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0058. 最后一个单词的长度](https://leetcode.cn/problems/length-of-last-word/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/length-of-last-word.md) | 字符串 | 简单 | +| [0059. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix-ii.md) | 数组、矩阵、模拟 | 中等 | +| [0061. 旋转链表](https://leetcode.cn/problems/rotate-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-list.md) | 链表、双指针 | 中等 | +| [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) | 数学、动态规划、组合数学 | 中等 | +| [0063. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths-ii.md) | 数组、动态规划、矩阵 | 中等 | +| [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) | 数组、动态规划、矩阵 | 中等 | +| [0066. 加一](https://leetcode.cn/problems/plus-one/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/plus-one.md) | 数组、数学 | 简单 | +| [0067. 二进制求和](https://leetcode.cn/problems/add-binary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-binary.md) | 位运算、数学、字符串、模拟 | 简单 | +| [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) | 数学、二分查找 | 简单 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) | 字符串、动态规划 | 中等 | +| [0073. 矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/set-matrix-zeroes.md) | 数组、哈希表、矩阵 | 中等 | +| [0074. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-a-2d-matrix.md) | 数组、二分查找、矩阵 | 中等 | +| [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) | 数组、双指针、排序 | 中等 | +| [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-window-substring.md) | 哈希表、字符串、滑动窗口 | 困难 | +| [0077. 组合](https://leetcode.cn/problems/combinations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combinations.md) | 回溯 | 中等 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0079. 单词搜索](https://leetcode.cn/problems/word-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/word-search.md) | 深度优先搜索、数组、字符串、回溯、矩阵 | 中等 | +| [0080. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md) | 数组、双指针 | 中等 | +| [0081. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md) | 数组、二分查找 | 中等 | +| [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md) | 链表、双指针 | 中等 | +| [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md) | 链表 | 简单 | +| [0084. 柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/largest-rectangle-in-histogram.md) | 栈、数组、单调栈 | 困难 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [0089. 格雷编码](https://leetcode.cn/problems/gray-code/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/gray-code.md) | 位运算、数学、回溯 | 中等 | +| [0090. 子集 II](https://leetcode.cn/problems/subsets-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets-ii.md) | 位运算、数组、回溯 | 中等 | +| [0091. 解码方法](https://leetcode.cn/problems/decode-ways/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/decode-ways.md) | 字符串、动态规划 | 中等 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/restore-ip-addresses.md) | 字符串、回溯 | 中等 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0095. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees-ii.md) | 树、二叉搜索树、动态规划、回溯、二叉树 | 中等 | +| [0096. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | +| [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | + + +### 第 100 ~ 199 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/symmetric-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | +| [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/balanced-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0112. 路径总和](https://leetcode.cn/problems/path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum-ii.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | +| [0115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/distinct-subsequences.md) | 字符串、动态规划 | 困难 | +| [0116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | +| [0117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | +| [0118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle.md) | 数组、动态规划 | 简单 | +| [0119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle-ii.md) | 数组、动态规划 | 简单 | +| [0120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/triangle.md) | 数组、动态规划 | 中等 | +| [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md) | 数组、动态规划 | 简单 | +| [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md) | 数组、动态规划 | 困难 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) | 双指针、字符串 | 简单 | +| [0127. 单词接龙](https://leetcode.cn/problems/word-ladder/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-ladder.md) | 广度优先搜索、哈希表、字符串 | 困难 | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | +| [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/surrounded-regions.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/palindrome-partitioning.md) | 字符串、动态规划、回溯 | 中等 | +| [0133. 克隆图](https://leetcode.cn/problems/clone-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/clone-graph.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | +| [0134. 加油站](https://leetcode.cn/problems/gas-station/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/gas-station.md) | 贪心、数组 | 中等 | +| [0135. 分发糖果](https://leetcode.cn/problems/candy/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/candy.md) | 贪心、数组 | 困难 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0137. 只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number-ii.md) | 位运算、数组 | 中等 | +| [0138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/copy-list-with-random-pointer.md) | 哈希表、链表 | 中等 | +| [0139. 单词拆分](https://leetcode.cn/problems/word-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | +| [0140. 单词拆分 II](https://leetcode.cn/problems/word-break-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break-ii.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划、回溯 | 困难 | +| [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) | 哈希表、链表、双指针 | 简单 | +| [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) | 哈希表、链表、双指针 | 中等 | +| [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reorder-list.md) | 栈、递归、链表、双指针 | 中等 | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0147. 对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/insertion-sort-list.md) | 链表、排序 | 中等 | +| [0148. 排序链表](https://leetcode.cn/problems/sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) | 链表、双指针、分治、排序、归并排序 | 中等 | +| [0149. 直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/max-points-on-a-line.md) | 几何、数组、哈希表、数学 | 困难 | +| [0150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md) | 栈、数组、数学 | 中等 | +| [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-words-in-a-string.md) | 双指针、字符串 | 中等 | +| [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-product-subarray.md) | 数组、动态规划 | 中等 | +| [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md) | 数组、二分查找 | 困难 | +| [0155. 最小栈](https://leetcode.cn/problems/min-stack/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) | 栈、设计 | 中等 | +| [0159. 至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/intersection-of-two-linked-lists.md) | 哈希表、链表、双指针 | 简单 | +| [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-peak-element.md) | 数组、二分查找 | 中等 | +| [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) | 数组、桶排序、基数排序、排序 | 中等 | +| [0166. 分数到小数](https://leetcode.cn/problems/fraction-to-recurring-decimal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/fraction-to-recurring-decimal.md) | 哈希表、数学、字符串 | 中等 | +| [0167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md) | 数组、双指针、二分查找 | 中等 | +| [0168. Excel 表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/excel-sheet-column-title.md) | 数学、字符串 | 简单 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | +| [0170. 两数之和 III - 数据结构设计](https://leetcode.cn/problems/two-sum-iii-data-structure-design/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-iii-data-structure-design.md) | 设计、数组、哈希表、双指针、数据流 | 简单 | +| [0171. Excel 表列序号](https://leetcode.cn/problems/excel-sheet-column-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/excel-sheet-column-number.md) | 数学、字符串 | 简单 | +| [0172. 阶乘后的零](https://leetcode.cn/problems/factorial-trailing-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/factorial-trailing-zeroes.md) | 数学 | 中等 | +| [0173. 二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-search-tree-iterator.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | +| [0179. 最大数](https://leetcode.cn/problems/largest-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/largest-number.md) | 贪心、数组、字符串、排序 | 中等 | +| [0188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md) | 数组、动态规划 | 困难 | +| [0189. 轮转数组](https://leetcode.cn/problems/rotate-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/rotate-array.md) | 数组、数学、双指针 | 中等 | +| [0190. 颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-bits.md) | 位运算、分治 | 简单 | +| [0191. 位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/number-of-1-bits.md) | 位运算、分治 | 简单 | +| [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) | 数组、动态规划 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | + + +### 第 200 ~ 299 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0201. 数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md) | 位运算 | 中等 | +| [0202. 快乐数](https://leetcode.cn/problems/happy-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/happy-number.md) | 哈希表、数学、双指针 | 简单 | +| [0203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/remove-linked-list-elements.md) | 递归、链表 | 简单 | +| [0204. 计数质数](https://leetcode.cn/problems/count-primes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-primes.md) | 数组、数学、枚举、数论 | 中等 | +| [0205. 同构字符串](https://leetcode.cn/problems/isomorphic-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/isomorphic-strings.md) | 哈希表、字符串 | 简单 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0207. 课程表](https://leetcode.cn/problems/course-schedule/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-trie-prefix-tree.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule-ii.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0211. 添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md) | 深度优先搜索、设计、字典树、字符串 | 中等 | +| [0212. 单词搜索 II](https://leetcode.cn/problems/word-search-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/word-search-ii.md) | 字典树、数组、字符串、回溯、矩阵 | 困难 | +| [0213. 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/house-robber-ii.md) | 数组、动态规划 | 中等 | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | +| [0217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate.md) | 数组、哈希表、排序 | 简单 | +| [0218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/the-skyline-problem.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | +| [0219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-ii.md) | 数组、哈希表、滑动窗口 | 简单 | +| [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-complete-tree-nodes.md) | 位运算、树、二分查找、二叉树 | 简单 | +| [0223. 矩形面积](https://leetcode.cn/problems/rectangle-area/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/rectangle-area.md) | 几何、数学 | 中等 | +| [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) | 栈、设计、队列 | 简单 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) | 栈、数学、字符串 | 中等 | +| [0231. 2 的幂](https://leetcode.cn/problems/power-of-two/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/power-of-two.md) | 位运算、递归、数学 | 简单 | +| [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-queue-using-stacks.md) | 栈、设计、队列 | 简单 | +| [0233. 数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-digit-one.md) | 递归、数学、动态规划 | 困难 | +| [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) | 栈、递归、链表、双指针 | 简单 | +| [0235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0237. 删除链表中的节点](https://leetcode.cn/problems/delete-node-in-a-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/delete-node-in-a-linked-list.md) | 链表 | 中等 | +| [0238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/product-of-array-except-self.md) | 数组、前缀和 | 中等 | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/search-a-2d-matrix-ii.md) | 数组、二分查找、分治、矩阵 | 中等 | +| [0241. 为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/different-ways-to-add-parentheses.md) | 递归、记忆化搜索、数学、字符串、动态规划 | 中等 | +| [0242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/valid-anagram.md) | 哈希表、字符串、排序 | 简单 | +| [0249. 移位字符串分组](https://leetcode.cn/problems/group-shifted-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/group-shifted-strings.md) | 数组、哈希表、字符串 | 中等 | +| [0257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/binary-tree-paths.md) | 树、深度优先搜索、字符串、回溯、二叉树 | 简单 | +| [0258. 各位相加](https://leetcode.cn/problems/add-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/add-digits.md) | 数学、数论、模拟 | 简单 | +| [0259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/3sum-smaller.md) | 数组、双指针、二分查找、排序 | 中等 | +| [0260. 只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/single-number-iii.md) | 位运算、数组 | 中等 | +| [0263. 丑数](https://leetcode.cn/problems/ugly-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/ugly-number.md) | 数学 | 简单 | +| [0264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/ugly-number-ii.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | +| [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | +| [0270. 最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/closest-binary-search-tree-value.md) | 树、深度优先搜索、二叉搜索树、二分查找、二叉树 | 简单 | +| [0278. 第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/first-bad-version.md) | 二分查找、交互 | 简单 | +| [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) | 广度优先搜索、数学、动态规划 | 中等 | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | +| [0285. 二叉搜索树中的中序后继](https://leetcode.cn/problems/inorder-successor-in-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/inorder-successor-in-bst.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0286. 墙与门](https://leetcode.cn/problems/walls-and-gates/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/walls-and-gates.md) | 广度优先搜索、数组、矩阵 | 中等 | +| [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-the-duplicate-number.md) | 位运算、数组、双指针、二分查找 | 中等 | +| [0288. 单词的唯一缩写](https://leetcode.cn/problems/unique-word-abbreviation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/unique-word-abbreviation.md) | 设计、数组、哈希表、字符串 | 中等 | +| [0289. 生命游戏](https://leetcode.cn/problems/game-of-life/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/game-of-life.md) | 数组、矩阵、模拟 | 中等 | +| [0290. 单词规律](https://leetcode.cn/problems/word-pattern/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/word-pattern.md) | 哈希表、字符串 | 简单 | +| [0292. Nim 游戏](https://leetcode.cn/problems/nim-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/nim-game.md) | 脑筋急转弯、数学、博弈 | 简单 | +| [0295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-median-from-data-stream.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | +| [0297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | + + +### 第 300 ~ 399 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) | 数组、二分查找、动态规划 | 中等 | +| [0303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-immutable.md) | 设计、数组、前缀和 | 简单 | +| [0304. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-2d-immutable.md) | 设计、数组、矩阵、前缀和 | 中等 | +| [0307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-mutable.md) | 设计、树状数组、线段树、数组 | 中等 | +| [0309. 买卖股票的最佳时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md) | 数组、动态规划 | 中等 | +| [0310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/minimum-height-trees.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0312. 戳气球](https://leetcode.cn/problems/burst-balloons/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/burst-balloons.md) | 数组、动态规划 | 困难 | +| [0315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | +| [0316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/remove-duplicate-letters.md) | 栈、贪心、字符串、单调栈 | 中等 | +| [0318. 最大单词长度乘积](https://leetcode.cn/problems/maximum-product-of-word-lengths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/maximum-product-of-word-lengths.md) | 位运算、数组、字符串 | 中等 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0324. 摆动排序 II](https://leetcode.cn/problems/wiggle-sort-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/wiggle-sort-ii.md) | 贪心、数组、分治、快速选择、排序 | 中等 | +| [0326. 3 的幂](https://leetcode.cn/problems/power-of-three/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/power-of-three.md) | 递归、数学 | 简单 | +| [0328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/odd-even-linked-list.md) | 链表 | 中等 | +| [0329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | +| [0334. 递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/increasing-triplet-subsequence.md) | 贪心、数组 | 中等 | +| [0336. 回文对](https://leetcode.cn/problems/palindrome-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/palindrome-pairs.md) | 字典树、数组、哈希表、字符串 | 困难 | +| [0337. 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/house-robber-iii.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | +| [0338. 比特位计数](https://leetcode.cn/problems/counting-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/counting-bits.md) | 位运算、动态规划 | 简单 | +| [0340. 至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0341. 扁平化嵌套列表迭代器](https://leetcode.cn/problems/flatten-nested-list-iterator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/flatten-nested-list-iterator.md) | 栈、树、深度优先搜索、设计、队列、迭代器 | 中等 | +| [0342. 4的幂](https://leetcode.cn/problems/power-of-four/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/power-of-four.md) | 位运算、递归、数学 | 简单 | +| [0343. 整数拆分](https://leetcode.cn/problems/integer-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) | 数学、动态规划 | 中等 | +| [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) | 双指针、字符串 | 简单 | +| [0345. 反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-vowels-of-a-string.md) | 双指针、字符串 | 简单 | +| [0346. 数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/moving-average-from-data-stream.md) | 设计、队列、数组、数据流 | 简单 | +| [0347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/top-k-frequent-elements.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | +| [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0351. 安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/android-unlock-patterns.md) | 位运算、动态规划、回溯、状态压缩 | 中等 | +| [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/russian-doll-envelopes.md) | 数组、二分查找、动态规划、排序 | 困难 | +| [0357. 统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-numbers-with-unique-digits.md) | 数学、动态规划、回溯 | 中等 | +| [0359. 日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/logger-rate-limiter.md) | 设计、哈希表、数据流 | 简单 | +| [0360. 有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sort-transformed-array.md) | 数组、数学、双指针、排序 | 中等 | +| [0367. 有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/valid-perfect-square.md) | 数学、二分查找 | 简单 | +| [0370. 区间加法](https://leetcode.cn/problems/range-addition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-addition.md) | 数组、前缀和 | 中等 | +| [0371. 两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sum-of-two-integers.md) | 位运算、数学 | 中等 | +| [0374. 猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower.md) | 二分查找、交互 | 简单 | +| [0375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md) | 数学、动态规划、博弈 | 中等 | +| [0376. 摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/wiggle-subsequence.md) | 贪心、数组、动态规划 | 中等 | +| [0377. 组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/combination-sum-iv.md) | 数组、动态规划 | 中等 | +| [0378. 有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix.md) | 数组、二分查找、矩阵、排序、堆(优先队列) | 中等 | +| [0380. O(1) 时间插入、删除和获取随机元素](https://leetcode.cn/problems/insert-delete-getrandom-o1/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/insert-delete-getrandom-o1.md) | 设计、数组、哈希表、数学、随机化 | 中等 | +| [0383. 赎金信](https://leetcode.cn/problems/ransom-note/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/ransom-note.md) | 哈希表、字符串、计数 | 简单 | +| [0384. 打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/shuffle-an-array.md) | 设计、数组、数学、随机化 | 中等 | +| [0386. 字典序排数](https://leetcode.cn/problems/lexicographical-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/lexicographical-numbers.md) | 深度优先搜索、字典树 | 中等 | +| [0387. 字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/first-unique-character-in-a-string.md) | 队列、哈希表、字符串、计数 | 简单 | +| [0389. 找不同](https://leetcode.cn/problems/find-the-difference/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/find-the-difference.md) | 位运算、哈希表、字符串、排序 | 简单 | +| [0391. 完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/perfect-rectangle.md) | 数组、扫描线 | 困难 | +| [0392. 判断子序列](https://leetcode.cn/problems/is-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/is-subsequence.md) | 双指针、字符串、动态规划 | 简单 | +| [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) | 栈、递归、字符串 | 中等 | +| [0395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters.md) | 哈希表、字符串、分治、滑动窗口 | 中等 | +| [0399. 除法求值](https://leetcode.cn/problems/evaluate-division/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/evaluate-division.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、字符串、最短路 | 中等 | + + +### 第 400 ~ 499 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/nth-digit.md) | 数学、二分查找 | 中等 | +| [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/frog-jump.md) | 数组、动态规划 | 困难 | +| [0404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sum-of-left-leaves.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0405. 数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md) | 位运算、数学、字符串 | 简单 | +| [0406. 根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/queue-reconstruction-by-height.md) | 树状数组、线段树、数组、排序 | 中等 | +| [0409. 最长回文串](https://leetcode.cn/problems/longest-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/longest-palindrome.md) | 贪心、哈希表、字符串 | 简单 | +| [0410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/split-array-largest-sum.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | +| [0412. Fizz Buzz](https://leetcode.cn/problems/fizz-buzz/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/fizz-buzz.md) | 数学、字符串、模拟 | 简单 | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | +| [0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/partition-equal-subset-sum.md) | 数组、动态规划 | 中等 | +| [0417. 太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/pacific-atlantic-water-flow.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | +| [0421. 数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md) | 位运算、字典树、数组、哈希表 | 中等 | +| [0424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/longest-repeating-character-replacement.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0425. 单词方块](https://leetcode.cn/problems/word-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/word-squares.md) | 字典树、数组、字符串、回溯 | 困难 | +| [0426. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | +| [0428. 序列化和反序列化 N 叉树](https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/serialize-and-deserialize-n-ary-tree.md) | 树、深度优先搜索、广度优先搜索、字符串 | 困难 | +| [0429. N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/n-ary-tree-level-order-traversal.md) | 树、广度优先搜索 | 中等 | +| [0430. 扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md) | 深度优先搜索、链表、双向链表 | 中等 | +| [0435. 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-overlapping-intervals.md) | 贪心、数组、动态规划、排序 | 中等 | +| [0437. 路径总和 III](https://leetcode.cn/problems/path-sum-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/path-sum-iii.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0443. 压缩字符串](https://leetcode.cn/problems/string-compression/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/string-compression.md) | 双指针、字符串 | 中等 | +| [0445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-two-numbers-ii.md) | 栈、链表、数学 | 中等 | +| [0447. 回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/number-of-boomerangs.md) | 数组、哈希表、数学 | 中等 | +| [0450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/delete-node-in-a-bst.md) | 树、二叉搜索树、二叉树 | 中等 | +| [0451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sort-characters-by-frequency.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | +| [0452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md) | 贪心、数组、排序 | 中等 | +| [0454. 四数相加 II](https://leetcode.cn/problems/4sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/4sum-ii.md) | 数组、哈希表 | 中等 | +| [0455. 分发饼干](https://leetcode.cn/problems/assign-cookies/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/assign-cookies.md) | 贪心、数组、双指针、排序 | 简单 | +| [0459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) | 字符串、字符串匹配 | 简单 | +| [0461. 汉明距离](https://leetcode.cn/problems/hamming-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/hamming-distance.md) | 位运算 | 简单 | +| [0463. 岛屿的周长](https://leetcode.cn/problems/island-perimeter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/island-perimeter.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | +| [0464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/can-i-win.md) | 位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 | 中等 | +| [0467. 环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md) | 字符串、动态规划 | 中等 | +| [0468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/validate-ip-address.md) | 字符串 | 中等 | +| [0473. 火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/matchsticks-to-square.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [0474. 一和零](https://leetcode.cn/problems/ones-and-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/ones-and-zeroes.md) | 数组、字符串、动态规划 | 中等 | +| [0480. 滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sliding-window-median.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | +| [0485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones.md) | 数组 | 简单 | +| [0486. 预测赢家](https://leetcode.cn/problems/predict-the-winner/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/predict-the-winner.md) | 递归、数组、数学、动态规划、博弈 | 中等 | +| [0487. 最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones-ii.md) | 数组、动态规划、滑动窗口 | 中等 | +| [0491. 非递减子序列](https://leetcode.cn/problems/non-decreasing-subsequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-decreasing-subsequences.md) | 位运算、数组、哈希表、回溯 | 中等 | +| [0494. 目标和](https://leetcode.cn/problems/target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) | 数组、动态规划、回溯 | 中等 | +| [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/next-greater-element-i.md) | 栈、数组、哈希表、单调栈 | 简单 | +| [0498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/diagonal-traverse.md) | 数组、矩阵、模拟 | 中等 | + + +### 第 500 ~ 599 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0501. 二叉搜索树中的众数](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-mode-in-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/next-greater-element-ii.md) | 栈、数组、单调栈 | 中等 | +| [0504. 七进制数](https://leetcode.cn/problems/base-7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/base-7.md) | 数学、字符串 | 简单 | +| [0506. 相对名次](https://leetcode.cn/problems/relative-ranks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/relative-ranks.md) | 数组、排序、堆(优先队列) | 简单 | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [0513. 找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-bottom-left-tree-value.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-largest-value-in-each-tree-row.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/longest-palindromic-subsequence.md) | 字符串、动态规划 | 中等 | +| [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/coin-change-ii.md) | 数组、动态规划 | 中等 | +| [0525. 连续数组](https://leetcode.cn/problems/contiguous-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/contiguous-array.md) | 数组、哈希表、前缀和 | 中等 | +| [0526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/beautiful-arrangement.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [0530. 二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-absolute-difference-in-bst.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/convert-bst-to-greater-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0539. 最小时间差](https://leetcode.cn/problems/minimum-time-difference/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-time-difference.md) | 数组、数学、字符串、排序 | 中等 | +| [0542. 01 矩阵](https://leetcode.cn/problems/01-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/01-matrix.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | +| [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0546. 移除盒子](https://leetcode.cn/problems/remove-boxes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/remove-boxes.md) | 记忆化搜索、数组、动态规划 | 困难 | +| [0547. 省份数量](https://leetcode.cn/problems/number-of-provinces/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/number-of-provinces.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0557. 反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md) | 双指针、字符串 | 简单 | +| [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subarray-sum-equals-k.md) | 数组、哈希表、前缀和 | 中等 | +| [0561. 数组拆分](https://leetcode.cn/problems/array-partition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/array-partition.md) | 贪心、数组、计数排序、排序 | 简单 | +| [0567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/permutation-in-string.md) | 哈希表、双指针、字符串、滑动窗口 | 中等 | +| [0575. 分糖果](https://leetcode.cn/problems/distribute-candies/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/distribute-candies.md) | 数组、哈希表 | 简单 | +| [0576. 出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/out-of-boundary-paths.md) | 动态规划 | 中等 | +| [0583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/delete-operation-for-two-strings.md) | 字符串、动态规划 | 中等 | +| [0589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md) | 栈、树、深度优先搜索 | 简单 | +| [0590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md) | 栈、树、深度优先搜索 | 简单 | +| [0599. 两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md) | 数组、哈希表、字符串 | 简单 | + + +### 第 600 ~ 699 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0600. 不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md) | 动态规划 | 困难 | +| [0611. 有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-triangle-number.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | +| [0616. 给字符串添加加粗标签](https://leetcode.cn/problems/add-bold-tag-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/add-bold-tag-in-string.md) | 字典树、数组、哈希表、字符串、字符串匹配 | 中等 | +| [0617. 合并二叉树](https://leetcode.cn/problems/merge-two-binary-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/merge-two-binary-trees.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0621. 任务调度器](https://leetcode.cn/problems/task-scheduler/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/task-scheduler.md) | 贪心、数组、哈希表、计数、排序、堆(优先队列) | 中等 | +| [0622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-circular-queue.md) | 设计、队列、数组、链表 | 中等 | +| [0633. 平方数之和](https://leetcode.cn/problems/sum-of-square-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/sum-of-square-numbers.md) | 数学、双指针、二分查找 | 中等 | +| [0639. 解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/decode-ways-ii.md) | 字符串、动态规划 | 困难 | +| [0642. 设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-search-autocomplete-system.md) | 深度优先搜索、设计、字典树、字符串、数据流、排序、堆(优先队列) | 困难 | +| [0643. 子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-average-subarray-i.md) | 数组、滑动窗口 | 简单 | +| [0647. 回文子串](https://leetcode.cn/problems/palindromic-substrings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/palindromic-substrings.md) | 双指针、字符串、动态规划 | 中等 | +| [0648. 单词替换](https://leetcode.cn/problems/replace-words/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/replace-words.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [0650. 两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/2-keys-keyboard.md) | 数学、动态规划 | 中等 | +| [0652. 寻找重复的子树](https://leetcode.cn/problems/find-duplicate-subtrees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-duplicate-subtrees.md) | 树、深度优先搜索、哈希表、二叉树 | 中等 | +| [0653. 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/two-sum-iv-input-is-a-bst.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 | 简单 | +| [0654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-binary-tree.md) | 栈、树、数组、分治、二叉树、单调栈 | 中等 | +| [0658. 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-k-closest-elements.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0664. 奇怪的打印机](https://leetcode.cn/problems/strange-printer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/strange-printer.md) | 字符串、动态规划 | 困难 | +| [0665. 非递减数列](https://leetcode.cn/problems/non-decreasing-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/non-decreasing-array.md) | 数组 | 中等 | +| [0669. 修剪二叉搜索树](https://leetcode.cn/problems/trim-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/trim-a-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) | 树状数组、线段树、数组、动态规划 | 中等 | +| [0674. 最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md) | 数组 | 简单 | +| [0676. 实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/implement-magic-dictionary.md) | 深度优先搜索、设计、字典树、哈希表、字符串 | 中等 | +| [0677. 键值映射](https://leetcode.cn/problems/map-sum-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/map-sum-pairs.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [0678. 有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-parenthesis-string.md) | 栈、贪心、字符串、动态规划 | 中等 | +| [0680. 验证回文串 II](https://leetcode.cn/problems/valid-palindrome-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-palindrome-ii.md) | 贪心、双指针、字符串 | 简单 | +| [0683. K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/k-empty-slots.md) | 树状数组、线段树、队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/redundant-connection.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0686. 重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) | 字符串、字符串匹配 | 中等 | +| [0687. 最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-univalue-path.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0688. 骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/knight-probability-in-chessboard.md) | 动态规划 | 中等 | +| [0690. 员工的重要性](https://leetcode.cn/problems/employee-importance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/employee-importance.md) | 树、深度优先搜索、广度优先搜索、数组、哈希表 | 中等 | +| [0691. 贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/stickers-to-spell-word.md) | 位运算、记忆化搜索、数组、哈希表、字符串、动态规划、回溯、状态压缩 | 困难 | +| [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0698. 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | + + +### 第 700 ~ 799 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-binary-search-tree.md) | 树、二叉搜索树、二叉树 | 简单 | +| [0701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md) | 树、二叉搜索树、二叉树 | 中等 | +| [0702. 搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md) | 数组、二分查找、交互 | 中等 | +| [0703. 数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | +| [0704. 二分查找](https://leetcode.cn/problems/binary-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) | 数组、二分查找 | 简单 | +| [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashset.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | +| [0706. 设计哈希映射](https://leetcode.cn/problems/design-hashmap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashmap.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | +| [0707. 设计链表](https://leetcode.cn/problems/design-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-linked-list.md) | 设计、链表 | 中等 | +| [0708. 循环有序列表的插入](https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-sorted-circular-linked-list.md) | 链表 | 中等 | +| [0709. 转换成小写字母](https://leetcode.cn/problems/to-lower-case/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/to-lower-case.md) | 字符串 | 简单 | +| [0713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/subarray-product-less-than-k.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0714. 买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md) | 贪心、数组、动态规划 | 中等 | +| [0715. Range 模块](https://leetcode.cn/problems/range-module/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/range-module.md) | 设计、线段树、有序集合 | 困难 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0719. 找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md) | 数组、双指针、二分查找、排序 | 困难 | +| [0720. 词典中最长的单词](https://leetcode.cn/problems/longest-word-in-dictionary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/longest-word-in-dictionary.md) | 字典树、数组、哈希表、字符串、排序 | 中等 | +| [0724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-pivot-index.md) | 数组、前缀和 | 简单 | +| [0727. 最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-window-subsequence.md) | 字符串、动态规划、滑动窗口 | 困难 | +| [0729. 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-i.md) | 设计、线段树、数组、二分查找、有序集合 | 中等 | +| [0731. 我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-ii.md) | 设计、线段树、数组、二分查找、有序集合、前缀和 | 中等 | +| [0732. 我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-iii.md) | 设计、线段树、二分查找、有序集合、前缀和 | 困难 | +| [0733. 图像渲染](https://leetcode.cn/problems/flood-fill/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/flood-fill.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | +| [0735. 小行星碰撞](https://leetcode.cn/problems/asteroid-collision/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/asteroid-collision.md) | 栈、数组、模拟 | 中等 | +| [0738. 单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/monotone-increasing-digits.md) | 贪心、数学 | 中等 | +| [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) | 栈、数组、单调栈 | 中等 | +| [0744. 寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md) | 数组、二分查找 | 简单 | +| [0746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/min-cost-climbing-stairs.md) | 数组、动态规划 | 简单 | +| [0752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/open-the-lock.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | +| [0758. 字符串中的加粗单词](https://leetcode.cn/problems/bold-words-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/bold-words-in-string.md) | 字典树、数组、哈希表、字符串、字符串匹配 | 中等 | +| [0763. 划分字母区间](https://leetcode.cn/problems/partition-labels/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/partition-labels.md) | 贪心、哈希表、双指针、字符串 | 中等 | +| [0765. 情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/couples-holding-hands.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | +| [0766. 托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/toeplitz-matrix.md) | 数组、矩阵 | 简单 | +| [0771. 宝石与石头](https://leetcode.cn/problems/jewels-and-stones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/jewels-and-stones.md) | 哈希表、字符串 | 简单 | +| [0778. 水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/swim-in-rising-water.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | +| [0779. 第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/k-th-symbol-in-grammar.md) | 位运算、递归、数学 | 中等 | +| [0783. 二叉搜索树节点最小距离](https://leetcode.cn/problems/minimum-distance-between-bst-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-distance-between-bst-nodes.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0784. 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/letter-case-permutation.md) | 位运算、字符串、回溯 | 中等 | +| [0785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/is-graph-bipartite.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0788. 旋转数字](https://leetcode.cn/problems/rotated-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotated-digits.md) | 数学、动态规划 | 中等 | +| [0795. 区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md) | 数组、双指针 | 中等 | +| [0796. 旋转字符串](https://leetcode.cn/problems/rotate-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) | 字符串、字符串匹配 | 简单 | +| [0797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/all-paths-from-source-to-target.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | + + +### 第 800 ~ 899 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0800. 相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/similar-rgb-color.md) | 数学、字符串、枚举 | 简单 | +| [0801. 使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md) | 数组、动态规划 | 困难 | +| [0802. 找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/find-eventual-safe-states.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0803. 打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/bricks-falling-when-hit.md) | 并查集、数组、矩阵 | 困难 | +| [0804. 唯一摩尔斯密码词](https://leetcode.cn/problems/unique-morse-code-words/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/unique-morse-code-words.md) | 数组、哈希表、字符串 | 简单 | +| [0806. 写字符串需要的行数](https://leetcode.cn/problems/number-of-lines-to-write-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/number-of-lines-to-write-string.md) | 数组、字符串 | 简单 | +| [0811. 子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/subdomain-visit-count.md) | 数组、哈希表、字符串、计数 | 中等 | +| [0814. 二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/binary-tree-pruning.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0819. 最常见的单词](https://leetcode.cn/problems/most-common-word/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/most-common-word.md) | 数组、哈希表、字符串、计数 | 简单 | +| [0820. 单词的压缩编码](https://leetcode.cn/problems/short-encoding-of-words/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/short-encoding-of-words.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [0821. 字符的最短距离](https://leetcode.cn/problems/shortest-distance-to-a-character/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-distance-to-a-character.md) | 数组、双指针、字符串 | 简单 | +| [0824. 山羊拉丁文](https://leetcode.cn/problems/goat-latin/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/goat-latin.md) | 字符串 | 简单 | +| [0830. 较大分组的位置](https://leetcode.cn/problems/positions-of-large-groups/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/positions-of-large-groups.md) | 字符串 | 简单 | +| [0832. 翻转图像](https://leetcode.cn/problems/flipping-an-image/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/flipping-an-image.md) | 位运算、数组、双指针、矩阵、模拟 | 简单 | +| [0834. 树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/sum-of-distances-in-tree.md) | 树、深度优先搜索、图、动态规划 | 困难 | +| [0836. 矩形重叠](https://leetcode.cn/problems/rectangle-overlap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/rectangle-overlap.md) | 几何、数学 | 简单 | +| [0841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/keys-and-rooms.md) | 深度优先搜索、广度优先搜索、图 | 中等 | +| [0844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/backspace-string-compare.md) | 栈、双指针、字符串、模拟 | 简单 | +| [0845. 数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/longest-mountain-in-array.md) | 数组、双指针、动态规划、枚举 | 中等 | +| [0846. 一手顺子](https://leetcode.cn/problems/hand-of-straights/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/hand-of-straights.md) | 贪心、数组、哈希表、排序 | 中等 | +| [0847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md) | 位运算、广度优先搜索、图、动态规划、状态压缩 | 困难 | +| [0850. 矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/rectangle-area-ii.md) | 线段树、数组、有序集合、扫描线 | 困难 | +| [0851. 喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/loud-and-rich.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | +| [0852. 山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md) | 数组、二分查找 | 中等 | +| [0860. 柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/lemonade-change.md) | 贪心、数组 | 简单 | +| [0861. 翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/score-after-flipping-matrix.md) | 贪心、位运算、数组、矩阵 | 中等 | +| [0862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md) | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0867. 转置矩阵](https://leetcode.cn/problems/transpose-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/transpose-matrix.md) | 数组、矩阵、模拟 | 简单 | +| [0868. 二进制间距](https://leetcode.cn/problems/binary-gap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/binary-gap.md) | 位运算 | 简单 | +| [0872. 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/leaf-similar-trees.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0873. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md) | 数组、哈希表、动态规划 | 中等 | +| [0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/koko-eating-bananas.md) | 数组、二分查找 | 中等 | +| [0876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/middle-of-the-linked-list.md) | 链表、双指针 | 简单 | +| [0877. 石子游戏](https://leetcode.cn/problems/stone-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/stone-game.md) | 数组、数学、动态规划、博弈 | 中等 | +| [0881. 救生艇](https://leetcode.cn/problems/boats-to-save-people/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/boats-to-save-people.md) | 贪心、数组、双指针、排序 | 中等 | +| [0884. 两句话中的不常见单词](https://leetcode.cn/problems/uncommon-words-from-two-sentences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/uncommon-words-from-two-sentences.md) | 哈希表、字符串、计数 | 简单 | +| [0886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/possible-bipartition.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/super-egg-drop.md) | 数学、二分查找、动态规划 | 困难 | +| [0889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0892. 三维形体的表面积](https://leetcode.cn/problems/surface-area-of-3d-shapes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/surface-area-of-3d-shapes.md) | 几何、数组、数学、矩阵 | 简单 | +| [0897. 递增顺序搜索树](https://leetcode.cn/problems/increasing-order-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/increasing-order-search-tree.md) | 栈、树、深度优先搜索、二叉搜索树、二叉树 | 简单 | + + +### 第 900 ~ 999 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0900. RLE 迭代器](https://leetcode.cn/problems/rle-iterator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/rle-iterator.md) | 设计、数组、计数、迭代器 | 中等 | +| [0901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/online-stock-span.md) | 栈、设计、数据流、单调栈 | 中等 | +| [0902. 最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md) | 数组、数学、字符串、二分查找、动态规划 | 困难 | +| [0904. 水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/fruit-into-baskets.md) | 数组、哈希表、滑动窗口 | 中等 | +| [0908. 最小差值 I](https://leetcode.cn/problems/smallest-range-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/smallest-range-i.md) | 数组、数学 | 简单 | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/maximum-sum-circular-subarray.md) | 队列、数组、分治、动态规划、单调队列 | 中等 | +| [0919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/complete-binary-tree-inserter.md) | 树、广度优先搜索、设计、二叉树 | 中等 | +| [0921. 使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md) | 栈、贪心、字符串 | 中等 | +| [0925. 长按键入](https://leetcode.cn/problems/long-pressed-name/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/long-pressed-name.md) | 双指针、字符串 | 简单 | +| [0932. 漂亮数组](https://leetcode.cn/problems/beautiful-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/beautiful-array.md) | 数组、数学、分治 | 中等 | +| [0933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/number-of-recent-calls.md) | 设计、队列、数据流 | 简单 | +| [0935. 骑士拨号器](https://leetcode.cn/problems/knight-dialer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/knight-dialer.md) | 动态规划 | 中等 | +| [0938. 二叉搜索树的范围和](https://leetcode.cn/problems/range-sum-of-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/range-sum-of-bst.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0946. 验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/validate-stack-sequences.md) | 栈、数组、模拟 | 中等 | +| [0947. 移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md) | 深度优先搜索、并查集、图、哈希表 | 中等 | +| [0953. 验证外星语词典](https://leetcode.cn/problems/verifying-an-alien-dictionary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/verifying-an-alien-dictionary.md) | 数组、哈希表、字符串 | 简单 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0959. 由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/regions-cut-by-slashes.md) | 深度优先搜索、广度优先搜索、并查集、数组、哈希表、矩阵 | 中等 | +| [0968. 监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/binary-tree-cameras.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0973. 最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/k-closest-points-to-origin.md) | 几何、数组、数学、分治、快速选择、排序、堆(优先队列) | 中等 | +| [0974. 和可被 K 整除的子数组](https://leetcode.cn/problems/subarray-sums-divisible-by-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/subarray-sums-divisible-by-k.md) | 数组、哈希表、前缀和 | 中等 | +| [0976. 三角形的最大周长](https://leetcode.cn/problems/largest-perimeter-triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/largest-perimeter-triangle.md) | 贪心、数组、数学、排序 | 简单 | +| [0977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/squares-of-a-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [0978. 最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/longest-turbulent-subarray.md) | 数组、动态规划、滑动窗口 | 中等 | +| [0982. 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md) | 位运算、数组、哈希表 | 困难 | +| [0990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/satisfiability-of-equality-equations.md) | 并查集、图、数组、字符串 | 中等 | +| [0992. K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/subarrays-with-k-different-integers.md) | 数组、哈希表、计数、滑动窗口 | 困难 | +| [0993. 二叉树的堂兄弟节点](https://leetcode.cn/problems/cousins-in-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/cousins-in-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0995. K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | +| [0999. 可以被一步捕获的棋子数](https://leetcode.cn/problems/available-captures-for-rook/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/available-captures-for-rook.md) | 数组、矩阵、模拟 | 简单 | + + +### 第 1000 ~ 1099 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1000. 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md) | 数组、动态规划、前缀和 | 困难 | +| [1002. 查找共用字符](https://leetcode.cn/problems/find-common-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/find-common-characters.md) | 数组、哈希表、字符串 | 简单 | +| [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/max-consecutive-ones-iii.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [1005. K 次取反后最大化的数组和](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/maximize-sum-of-array-after-k-negations.md) | 贪心、数组、排序 | 简单 | +| [1008. 前序遍历构造二叉搜索树](https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal.md) | 栈、树、二叉搜索树、数组、二叉树、单调栈 | 中等 | +| [1009. 十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/complement-of-base-10-integer.md) | 位运算 | 简单 | +| [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md) | 数组、二分查找 | 中等 | +| [1012. 至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/numbers-with-repeated-digits.md) | 数学、动态规划 | 困难 | +| [1014. 最佳观光组合](https://leetcode.cn/problems/best-sightseeing-pair/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/best-sightseeing-pair.md) | 数组、动态规划 | 中等 | +| [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/number-of-enclaves.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [1021. 删除最外层的括号](https://leetcode.cn/problems/remove-outermost-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-outermost-parentheses.md) | 栈、字符串 | 简单 | +| [1023. 驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/camelcase-matching.md) | 字典树、数组、双指针、字符串、字符串匹配 | 中等 | +| [1025. 除数博弈](https://leetcode.cn/problems/divisor-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/divisor-game.md) | 脑筋急转弯、数学、动态规划、博弈 | 简单 | +| [1028. 从先序遍历还原二叉树](https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/recover-a-tree-from-preorder-traversal.md) | 树、深度优先搜索、字符串、二叉树 | 困难 | +| [1029. 两地调度](https://leetcode.cn/problems/two-city-scheduling/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-city-scheduling.md) | 贪心、数组、排序 | 中等 | +| [1034. 边界着色](https://leetcode.cn/problems/coloring-a-border/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/coloring-a-border.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | +| [1035. 不相交的线](https://leetcode.cn/problems/uncrossed-lines/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/uncrossed-lines.md) | 数组、动态规划 | 中等 | +| [1037. 有效的回旋镖](https://leetcode.cn/problems/valid-boomerang/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/valid-boomerang.md) | 几何、数组、数学 | 简单 | +| [1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/binary-search-tree-to-greater-sum-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [1039. 多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md) | 数组、动态规划 | 中等 | +| [1041. 困于环中的机器人](https://leetcode.cn/problems/robot-bounded-in-circle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/robot-bounded-in-circle.md) | 数学、字符串、模拟 | 中等 | +| [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md) | 栈、字符串 | 简单 | +| [1049. 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/last-stone-weight-ii.md) | 数组、动态规划 | 中等 | +| [1051. 高度检查器](https://leetcode.cn/problems/height-checker/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/height-checker.md) | 数组、计数排序、排序 | 简单 | +| [1052. 爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/grumpy-bookstore-owner.md) | 数组、滑动窗口 | 中等 | +| [1065. 字符串的索引对](https://leetcode.cn/problems/index-pairs-of-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/index-pairs-of-a-string.md) | 字典树、数组、字符串、排序 | 简单 | +| [1079. 活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/letter-tile-possibilities.md) | 哈希表、字符串、回溯、计数 | 中等 | +| [1081. 不同字符的最小子序列](https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/smallest-subsequence-of-distinct-characters.md) | 栈、贪心、字符串、单调栈 | 中等 | +| [1089. 复写零](https://leetcode.cn/problems/duplicate-zeros/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/duplicate-zeros.md) | 数组、双指针 | 简单 | +| [1091. 二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/shortest-path-in-binary-matrix.md) | 广度优先搜索、数组、矩阵 | 中等 | +| [1095. 山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/find-in-mountain-array.md) | 数组、二分查找、交互 | 困难 | +| [1099. 小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-sum-less-than-k.md) | 数组、双指针、二分查找、排序 | 简单 | + + +### 第 1100 ~ 1199 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1100. 长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [1103. 分糖果 II](https://leetcode.cn/problems/distribute-candies-to-people/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/distribute-candies-to-people.md) | 数学、模拟 | 简单 | +| [1108. IP 地址无效化](https://leetcode.cn/problems/defanging-an-ip-address/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/defanging-an-ip-address.md) | 字符串 | 简单 | +| [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/corporate-flight-bookings.md) | 数组、前缀和 | 中等 | +| [1110. 删点成林](https://leetcode.cn/problems/delete-nodes-and-return-forest/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/delete-nodes-and-return-forest.md) | 树、深度优先搜索、数组、哈希表、二叉树 | 中等 | +| [1122. 数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/relative-sort-array.md) | 数组、哈希表、计数排序、排序 | 简单 | +| [1136. 并行课程](https://leetcode.cn/problems/parallel-courses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/parallel-courses.md) | 图、拓扑排序 | 中等 | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) | 字符串、动态规划 | 中等 | +| [1151. 最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md) | 数组、滑动窗口 | 中等 | +| [1155. 掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md) | 动态规划 | 中等 | +| [1161. 最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/maximum-level-sum-of-a-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [1176. 健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/diet-plan-performance.md) | 数组、滑动窗口 | 简单 | +| [1184. 公交站间的距离](https://leetcode.cn/problems/distance-between-bus-stops/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/distance-between-bus-stops.md) | 数组 | 简单 | + + +### 第 1200 ~ 1299 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1202. 交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/smallest-string-with-swaps.md) | 深度优先搜索、广度优先搜索、并查集、数组、哈希表、字符串、排序 | 中等 | +| [1208. 尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/get-equal-substrings-within-budget.md) | 字符串、二分查找、前缀和、滑动窗口 | 中等 | +| [1217. 玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md) | 贪心、数组、数学 | 简单 | +| [1220. 统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/count-vowels-permutation.md) | 动态规划 | 困难 | +| [1227. 飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/airplane-seat-assignment-probability.md) | 脑筋急转弯、数学、动态规划、概率与统计 | 中等 | +| [1229. 安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/meeting-scheduler.md) | 数组、双指针、排序 | 中等 | +| [1232. 缀点成线](https://leetcode.cn/problems/check-if-it-is-a-straight-line/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/check-if-it-is-a-straight-line.md) | 几何、数组、数学 | 简单 | +| [1245. 树的直径](https://leetcode.cn/problems/tree-diameter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/tree-diameter.md) | 树、深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [1247. 交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md) | 贪心、数学、字符串 | 中等 | +| [1253. 重构 2 行二进制矩阵](https://leetcode.cn/problems/reconstruct-a-2-row-binary-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/reconstruct-a-2-row-binary-matrix.md) | 贪心、数组、矩阵 | 中等 | +| [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/number-of-closed-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [1261. 在受污染的二叉树中查找元素](https://leetcode.cn/problems/find-elements-in-a-contaminated-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/find-elements-in-a-contaminated-binary-tree.md) | 树、深度优先搜索、广度优先搜索、设计、哈希表、二叉树 | 中等 | +| [1266. 访问所有点的最小时间](https://leetcode.cn/problems/minimum-time-visiting-all-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-time-visiting-all-points.md) | 几何、数组、数学 | 简单 | +| [1268. 搜索推荐系统](https://leetcode.cn/problems/search-suggestions-system/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/search-suggestions-system.md) | 字典树、数组、字符串、二分查找、排序、堆(优先队列) | 中等 | +| [1281. 整数的各位积和之差](https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer.md) | 数学 | 简单 | +| [1296. 划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md) | 贪心、数组、哈希表、排序 | 中等 | + + +### 第 1300 ~ 1399 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1300. 转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md) | 数组、二分查找、排序 | 中等 | +| [1305. 两棵二叉搜索树中的所有元素](https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/all-elements-in-two-binary-search-trees.md) | 树、深度优先搜索、二叉搜索树、二叉树、排序 | 中等 | +| [1310. 子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/xor-queries-of-a-subarray.md) | 位运算、数组、前缀和 | 中等 | +| [1313. 解压缩编码列表](https://leetcode.cn/problems/decompress-run-length-encoded-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/decompress-run-length-encoded-list.md) | 数组 | 简单 | +| [1317. 将整数转换为两个无零整数的和](https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers.md) | 数学 | 简单 | +| [1319. 连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [1324. 竖直打印单词](https://leetcode.cn/problems/print-words-vertically/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/print-words-vertically.md) | 数组、字符串、模拟 | 中等 | +| [1338. 数组大小减半](https://leetcode.cn/problems/reduce-array-size-to-the-half/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/reduce-array-size-to-the-half.md) | 贪心、数组、哈希表、排序、堆(优先队列) | 中等 | +| [1343. 大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md) | 数组、滑动窗口 | 中等 | +| [1344. 时钟指针的夹角](https://leetcode.cn/problems/angle-between-hands-of-a-clock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/angle-between-hands-of-a-clock.md) | 数学 | 中等 | +| [1347. 制造字母异位词的最小步骤数](https://leetcode.cn/problems/minimum-number-of-steps-to-make-two-strings-anagram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram.md) | 哈希表、字符串、计数 | 中等 | +| [1349. 参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/maximum-students-taking-exam.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | +| [1358. 包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [1362. 最接近的因数](https://leetcode.cn/problems/closest-divisors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/closest-divisors.md) | 数学 | 中等 | +| [1381. 设计一个支持增量操作的栈](https://leetcode.cn/problems/design-a-stack-with-increment-operation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/design-a-stack-with-increment-operation.md) | 栈、设计、数组 | 中等 | + + +### 第 1400 ~ 1499 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1400. 构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/construct-k-palindrome-strings.md) | 贪心、哈希表、字符串、计数 | 中等 | +| [1408. 数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) | 数组、字符串、字符串匹配 | 简单 | +| [1422. 分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-score-after-splitting-a-string.md) | 字符串、前缀和 | 简单 | +| [1423. 可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md) | 数组、前缀和、滑动窗口 | 中等 | +| [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | +| [1446. 连续字符](https://leetcode.cn/problems/consecutive-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/consecutive-characters.md) | 字符串 | 简单 | +| [1447. 最简分数](https://leetcode.cn/problems/simplified-fractions/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/simplified-fractions.md) | 数学、字符串、数论 | 中等 | +| [1449. 数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md) | 数组、动态规划 | 困难 | +| [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md) | 数组 | 简单 | +| [1451. 重新排列句子中的单词](https://leetcode.cn/problems/rearrange-words-in-a-sentence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/rearrange-words-in-a-sentence.md) | 字符串、排序 | 中等 | +| [1456. 定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md) | 字符串、滑动窗口 | 中等 | +| [1476. 子矩形查询](https://leetcode.cn/problems/subrectangle-queries/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/subrectangle-queries.md) | 设计、数组、矩阵 | 中等 | +| [1480. 一维数组的动态和](https://leetcode.cn/problems/running-sum-of-1d-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/running-sum-of-1d-array.md) | 数组、前缀和 | 简单 | +| [1482. 制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md) | 数组、二分查找 | 中等 | +| [1486. 数组异或操作](https://leetcode.cn/problems/xor-operation-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/xor-operation-in-an-array.md) | 位运算、数学 | 简单 | +| [1491. 去掉最低工资和最高工资后的工资平均值](https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary.md) | 数组、排序 | 简单 | +| [1493. 删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md) | 数组、动态规划、滑动窗口 | 中等 | +| [1496. 判断路径是否相交](https://leetcode.cn/problems/path-crossing/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/path-crossing.md) | 哈希表、字符串 | 简单 | + + +### 第 1500 ~ 1599 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1502. 判断能否形成等差数列](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/can-make-arithmetic-progression-from-sequence.md) | 数组、排序 | 简单 | +| [1507. 转变日期格式](https://leetcode.cn/problems/reformat-date/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/reformat-date.md) | 字符串 | 简单 | +| [1523. 在区间范围内统计奇数数目](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/count-odd-numbers-in-an-interval-range.md) | 数学 | 简单 | +| [1534. 统计好三元组](https://leetcode.cn/problems/count-good-triplets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/count-good-triplets.md) | 数组、枚举 | 简单 | +| [1547. 切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md) | 数组、动态规划、排序 | 困难 | +| [1551. 使数组中所有元素相等的最小操作数](https://leetcode.cn/problems/minimum-operations-to-make-array-equal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-operations-to-make-array-equal.md) | 数学 | 中等 | +| [1556. 千位分隔数](https://leetcode.cn/problems/thousand-separator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/thousand-separator.md) | 字符串 | 简单 | +| [1561. 你可以获得的最大硬币数目](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/maximum-number-of-coins-you-can-get.md) | 贪心、数组、数学、博弈、排序 | 中等 | +| [1567. 乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/maximum-length-of-subarray-with-positive-product.md) | 贪心、数组、动态规划 | 中等 | +| [1582. 二进制矩阵中的特殊位置](https://leetcode.cn/problems/special-positions-in-a-binary-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/special-positions-in-a-binary-matrix.md) | 数组、矩阵 | 简单 | +| [1584. 连接所有点的最小费用](https://leetcode.cn/problems/min-cost-to-connect-all-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/min-cost-to-connect-all-points.md) | 并查集、图、数组、最小生成树 | 中等 | +| [1593. 拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md) | 哈希表、字符串、回溯 | 中等 | +| [1595. 连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | + + +### 第 1600 ~ 1699 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1603. 设计停车系统](https://leetcode.cn/problems/design-parking-system/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/design-parking-system.md) | 设计、计数、模拟 | 简单 | +| [1605. 给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md) | 贪心、数组、矩阵 | 中等 | +| [1614. 括号的最大嵌套深度](https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-nesting-depth-of-the-parentheses.md) | 栈、字符串 | 简单 | +| [1617. 统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md) | 位运算、树、动态规划、状态压缩、枚举 | 困难 | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | +| [1641. 统计字典序元音字符串的数目](https://leetcode.cn/problems/count-sorted-vowel-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-sorted-vowel-strings.md) | 数学、动态规划、组合数学 | 中等 | +| [1646. 获取生成数组中的最大值](https://leetcode.cn/problems/get-maximum-in-generated-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/get-maximum-in-generated-array.md) | 数组、模拟 | 简单 | +| [1647. 字符频次唯一的最小删除次数](https://leetcode.cn/problems/minimum-deletions-to-make-character-frequencies-unique/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique.md) | 贪心、哈希表、字符串、排序 | 中等 | +| [1657. 确定两个字符串是否接近](https://leetcode.cn/problems/determine-if-two-strings-are-close/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/determine-if-two-strings-are-close.md) | 哈希表、字符串、计数、排序 | 中等 | +| [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md) | 数组、哈希表、二分查找、前缀和、滑动窗口 | 中等 | +| [1672. 最富有客户的资产总量](https://leetcode.cn/problems/richest-customer-wealth/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/richest-customer-wealth.md) | 数组、矩阵 | 简单 | +| [1695. 删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-erasure-value.md) | 数组、哈希表、滑动窗口 | 中等 | +| [1698. 字符串的不同子字符串个数](https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/number-of-distinct-substrings-in-a-string.md) | 字典树、字符串、后缀数组、哈希函数、滚动哈希 | 中等 | + + +### 第 1700 ~ 1799 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1710. 卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-units-on-a-truck.md) | 贪心、数组、排序 | 简单 | +| [1716. 计算力扣银行的钱](https://leetcode.cn/problems/calculate-money-in-leetcode-bank/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/calculate-money-in-leetcode-bank.md) | 数学 | 简单 | +| [1720. 解码异或后的数组](https://leetcode.cn/problems/decode-xored-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/decode-xored-array.md) | 位运算、数组 | 简单 | +| [1726. 同积元组](https://leetcode.cn/problems/tuple-with-same-product/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/tuple-with-same-product.md) | 数组、哈希表、计数 | 中等 | +| [1736. 替换隐藏数字得到的最晚时间](https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/latest-time-by-replacing-hidden-digits.md) | 贪心、字符串 | 简单 | +| [1742. 盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md) | 哈希表、数学、计数 | 简单 | +| [1749. 任意子数组和的绝对值的最大值](https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-absolute-sum-of-any-subarray.md) | 数组、动态规划 | 中等 | +| [1763. 最长的美好子字符串](https://leetcode.cn/problems/longest-nice-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/longest-nice-substring.md) | 位运算、哈希表、字符串、分治、滑动窗口 | 简单 | +| [1779. 找到最近的有相同 X 或 Y 坐标的点](https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate.md) | 数组 | 简单 | +| [1790. 仅执行一次字符串交换能否使两个字符串相等](https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal.md) | 哈希表、字符串、计数 | 简单 | +| [1791. 找出星型图的中心节点](https://leetcode.cn/problems/find-center-of-star-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/find-center-of-star-graph.md) | 图 | 简单 | + + +### 第 1800 ~ 1899 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1822. 数组元素积的符号](https://leetcode.cn/problems/sign-of-the-product-of-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/sign-of-the-product-of-an-array.md) | 数组、数学 | 简单 | +| [1827. 最少操作使数组递增](https://leetcode.cn/problems/minimum-operations-to-make-the-array-increasing/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-operations-to-make-the-array-increasing.md) | 贪心、数组 | 简单 | +| [1833. 雪糕的最大数量](https://leetcode.cn/problems/maximum-ice-cream-bars/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/maximum-ice-cream-bars.md) | 贪心、数组、计数排序、排序 | 中等 | +| [1844. 将所有数字用字符替换](https://leetcode.cn/problems/replace-all-digits-with-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/replace-all-digits-with-characters.md) | 字符串 | 简单 | +| [1858. 包含所有前缀的最长单词](https://leetcode.cn/problems/longest-word-with-all-prefixes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/longest-word-with-all-prefixes.md) | 深度优先搜索、字典树、数组、字符串 | 中等 | +| [1859. 将句子排序](https://leetcode.cn/problems/sorting-the-sentence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/sorting-the-sentence.md) | 字符串、排序 | 简单 | +| [1876. 长度为三且各字符不同的子字符串](https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/substrings-of-size-three-with-distinct-characters.md) | 哈希表、字符串、计数、滑动窗口 | 简单 | +| [1877. 数组中最大数对和的最小值](https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimize-maximum-pair-sum-in-array.md) | 贪心、数组、双指针、排序 | 中等 | +| [1879. 两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md) | 位运算、数组、动态规划、状态压缩 | 困难 | +| [1893. 检查是否区域内所有整数都被覆盖](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md) | 数组、哈希表、前缀和 | 简单 | +| [1897. 重新分配字符使所有字符串都相等](https://leetcode.cn/problems/redistribute-characters-to-make-all-strings-equal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/redistribute-characters-to-make-all-strings-equal.md) | 哈希表、字符串、计数 | 简单 | + + +### 第 1900 ~ 1999 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1903. 字符串中的最大奇数](https://leetcode.cn/problems/largest-odd-number-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/largest-odd-number-in-string.md) | 贪心、数学、字符串 | 简单 | +| [1921. 消灭怪物的最大数量](https://leetcode.cn/problems/eliminate-maximum-number-of-monsters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/eliminate-maximum-number-of-monsters.md) | 贪心、数组、排序 | 中等 | +| [1925. 统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/count-square-sum-triples.md) | 数学、枚举 | 简单 | +| [1929. 数组串联](https://leetcode.cn/problems/concatenation-of-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/concatenation-of-array.md) | 数组、模拟 | 简单 | +| [1930. 长度为 3 的不同回文子序列](https://leetcode.cn/problems/unique-length-3-palindromic-subsequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/unique-length-3-palindromic-subsequences.md) | 位运算、哈希表、字符串、前缀和 | 中等 | +| [1936. 新增的最少台阶数](https://leetcode.cn/problems/add-minimum-number-of-rungs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/add-minimum-number-of-rungs.md) | 贪心、数组 | 中等 | +| [1941. 检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md) | 哈希表、字符串、计数 | 简单 | +| [1947. 最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1984. 学生分数的最小差值](https://leetcode.cn/problems/minimum-difference-between-highest-and-lowest-of-k-scores/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores.md) | 数组、排序、滑动窗口 | 简单 | +| [1986. 完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1991. 找到数组的中间位置](https://leetcode.cn/problems/find-the-middle-index-in-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/find-the-middle-index-in-array.md) | 数组、前缀和 | 简单 | +| [1994. 好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/the-number-of-good-subsets.md) | 位运算、数组、数学、动态规划、状态压缩 | 困难 | + + +### 第 2000 ~ 2099 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2011. 执行操作后的变量值](https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/final-value-of-variable-after-performing-operations.md) | 数组、字符串、模拟 | 简单 | +| [2023. 连接后等于目标字符串的字符串对](https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target.md) | 数组、哈希表、字符串、计数 | 中等 | +| [2050. 并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/parallel-courses-iii.md) | 图、拓扑排序、数组、动态规划 | 困难 | + + +### 第 2100 ~ 2199 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2156. 查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) | 字符串、滑动窗口、哈希函数、滚动哈希 | 困难 | +| [2172. 数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/maximum-and-sum-of-array.md) | 位运算、数组、动态规划、状态压缩 | 困难 | + + +### 第 2200 ~ 2299 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2235. 两整数相加](https://leetcode.cn/problems/add-two-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/add-two-integers.md) | 数学 | 简单 | +| [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md) | 树、深度优先搜索、图、拓扑排序、数组、字符串 | 困难 | +| [2249. 统计圆内格点数目](https://leetcode.cn/problems/count-lattice-points-inside-a-circle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md) | 几何、数组、哈希表、数学、枚举 | 中等 | +| [2276. 统计区间中的整数数目](https://leetcode.cn/problems/count-integers-in-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/count-integers-in-intervals.md) | 设计、线段树、有序集合 | 困难 | + + +### 第 2300 ~ 2399 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2376. 统计特殊整数](https://leetcode.cn/problems/count-special-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2300-2399/count-special-integers.md) | 数学、动态规划 | 困难 | + + +### 第 2400 ~ 2499 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2427. 公因子的数目](https://leetcode.cn/problems/number-of-common-factors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2400-2499/number-of-common-factors.md) | 数学、枚举、数论 | 简单 | + + +### 第 2500 ~ 2599 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2538. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md) | 树、深度优先搜索、数组、动态规划 | 困难 | +| [2585. 获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/number-of-ways-to-earn-points.md) | 数组、动态规划 | 困难 | + + +### 第 2700 ~ 2799 题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2719. 统计整数数目](https://leetcode.cn/problems/count-of-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2700-2799/count-of-integers.md) | 数学、字符串、动态规划 | 困难 | + + +### LCR 系列 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [LCR 001. 两数相除](https://leetcode.cn/problems/xoh6Oh/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xoh6Oh.md) | 数学 | 简单 | +| [LCR 002. 二进制求和](https://leetcode.cn/problems/JFETK5/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/JFETK5.md) | 位运算、数学、字符串、模拟 | 简单 | +| [LCR 003. 比特位计数](https://leetcode.cn/problems/w3tCBm/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/w3tCBm.md) | 位运算、动态规划 | 简单 | +| [LCR 004. 只出现一次的数字 II](https://leetcode.cn/problems/WGki4K/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WGki4K.md) | 位运算、数组 | 中等 | +| [LCR 005. 最大单词长度乘积](https://leetcode.cn/problems/aseY1I/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/aseY1I.md) | 位运算、数组、字符串 | 中等 | +| [LCR 006. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/kLl5u1/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/kLl5u1.md) | 数组、双指针、二分查找 | 简单 | +| [LCR 007. 三数之和](https://leetcode.cn/problems/1fGaJU/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/1fGaJU.md) | 数组、双指针、排序 | 中等 | +| [LCR 008. 长度最小的子数组](https://leetcode.cn/problems/2VG8Kg/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2VG8Kg.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [LCR 009. 乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ZVAVXX.md) | 数组、滑动窗口 | 中等 | +| [LCR 010. 和为 K 的子数组](https://leetcode.cn/problems/QTMn0o/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QTMn0o.md) | 数组、哈希表、前缀和 | 中等 | +| [LCR 011. 连续数组](https://leetcode.cn/problems/A1NYOS/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/A1NYOS.md) | 数组、哈希表、前缀和 | 中等 | +| [LCR 012. 寻找数组的中心下标](https://leetcode.cn/problems/tvdfij/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/tvdfij.md) | 数组、前缀和 | 简单 | +| [LCR 013. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/O4NDxx/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/O4NDxx.md) | 设计、数组、矩阵、前缀和 | 中等 | +| [LCR 016. 无重复字符的最长子串](https://leetcode.cn/problems/wtcaE1/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/wtcaE1.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [LCR 017. 最小覆盖子串](https://leetcode.cn/problems/M1oyTv/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/M1oyTv.md) | 哈希表、字符串、滑动窗口 | 困难 | +| [LCR 018. 验证回文串](https://leetcode.cn/problems/XltzEq/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/XltzEq.md) | 双指针、字符串 | 简单 | +| [LCR 019. 验证回文串 II](https://leetcode.cn/problems/RQku0D/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/RQku0D.md) | 贪心、双指针、字符串 | 简单 | +| [LCR 020. 回文子串](https://leetcode.cn/problems/a7VOhD/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/a7VOhD.md) | 字符串、动态规划 | 中等 | +| [LCR 021. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/SLwz0R/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/SLwz0R.md) | 链表、双指针 | 中等 | +| [LCR 022. 环形链表 II](https://leetcode.cn/problems/c32eOV/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/c32eOV.md) | 哈希表、链表、双指针 | 中等 | +| [LCR 023. 相交链表](https://leetcode.cn/problems/3u1WK4/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/3u1WK4.md) | 哈希表、链表、双指针 | 简单 | +| [LCR 024. 反转链表](https://leetcode.cn/problems/UHnkqh/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/UHnkqh.md) | 递归、链表 | 简单 | +| [LCR 025. 两数相加 II](https://leetcode.cn/problems/lMSNwu/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lMSNwu.md) | 栈、链表、数学 | 中等 | +| [LCR 026. 重排链表](https://leetcode.cn/problems/LGjMqU/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/LGjMqU.md) | 栈、递归、链表、双指针 | 中等 | +| [LCR 027. 回文链表](https://leetcode.cn/problems/aMhZSa/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/aMhZSa.md) | 栈、递归、链表、双指针 | 简单 | +| [LCR 028. 扁平化多级双向链表](https://leetcode.cn/problems/Qv1Da2/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Qv1Da2.md) | 深度优先搜索、链表、双向链表 | 中等 | +| [LCR 029. 循环有序列表的插入](https://leetcode.cn/problems/4ueAj6/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/4ueAj6.md) | 链表 | 中等 | +| [LCR 030. O(1) 时间插入、删除和获取随机元素](https://leetcode.cn/problems/FortPu/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/FortPu.md) | 设计、数组、哈希表、数学、随机化 | 中等 | +| [LCR 031. LRU 缓存](https://leetcode.cn/problems/OrIXps/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/OrIXps.md) | 设计、哈希表、链表、双向链表 | 中等 | +| [LCR 032. 有效的字母异位词](https://leetcode.cn/problems/dKk3P7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dKk3P7.md) | 哈希表、字符串、排序 | 简单 | +| [LCR 033. 字母异位词分组](https://leetcode.cn/problems/sfvd7V/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/sfvd7V.md) | 数组、哈希表、字符串、排序 | 中等 | +| [LCR 034. 验证外星语词典](https://leetcode.cn/problems/lwyVBB/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lwyVBB.md) | 数组、哈希表、字符串 | 简单 | +| [LCR 035. 最小时间差](https://leetcode.cn/problems/569nqc/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/569nqc.md) | 数组、数学、字符串、排序 | 中等 | +| [LCR 036. 逆波兰表达式求值](https://leetcode.cn/problems/8Zf90G/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/8Zf90G.md) | 栈、数组、数学 | 中等 | +| [LCR 037. 行星碰撞](https://leetcode.cn/problems/XagZNi/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/XagZNi.md) | 栈、数组、模拟 | 中等 | +| [LCR 038. 每日温度](https://leetcode.cn/problems/iIQa4I/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/iIQa4I.md) | 栈、数组、单调栈 | 中等 | +| [LCR 039. 柱状图中最大的矩形](https://leetcode.cn/problems/0ynMMM/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0ynMMM.md) | 栈、数组、单调栈 | 困难 | +| [LCR 041. 数据流中的移动平均值](https://leetcode.cn/problems/qIsx9U/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qIsx9U.md) | 设计、队列、数组、数据流 | 简单 | +| [LCR 042. 最近的请求次数](https://leetcode.cn/problems/H8086Q/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/H8086Q.md) | 设计、队列、数据流 | 简单 | +| [LCR 043. 完全二叉树插入器](https://leetcode.cn/problems/NaqhDT/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NaqhDT.md) | 树、广度优先搜索、设计、二叉树 | 中等 | +| [LCR 044. 在每个树行中找最大值](https://leetcode.cn/problems/hPov7L/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/hPov7L.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [LCR 045. 找树左下角的值](https://leetcode.cn/problems/LwUNpT/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/LwUNpT.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [LCR 046. 二叉树的右视图](https://leetcode.cn/problems/WNC0Lk/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WNC0Lk.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [LCR 047. 二叉树剪枝](https://leetcode.cn/problems/pOCWxh/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/pOCWxh.md) | 树、深度优先搜索、二叉树 | 中等 | +| [LCR 048. 二叉树的序列化与反序列化](https://leetcode.cn/problems/h54YBf/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/h54YBf.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | +| [LCR 049. 求根节点到叶节点数字之和](https://leetcode.cn/problems/3Etpl5/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/3Etpl5.md) | 树、深度优先搜索、二叉树 | 中等 | +| [LCR 050. 路径总和 III](https://leetcode.cn/problems/6eUYwP/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/6eUYwP.md) | 树、深度优先搜索、二叉树 | 中等 | +| [LCR 051. 二叉树中的最大路径和](https://leetcode.cn/problems/jC7MId/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jC7MId.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [LCR 052. 递增顺序搜索树](https://leetcode.cn/problems/NYBBNL/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NYBBNL.md) | 栈、树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [LCR 053. 二叉搜索树中的中序后继](https://leetcode.cn/problems/P5rCT8/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/P5rCT8.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [LCR 054. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/w6cpku/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/w6cpku.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [LCR 055. 二叉搜索树迭代器](https://leetcode.cn/problems/kTOapQ/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/kTOapQ.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | +| [LCR 056. 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/opLdQZ/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/opLdQZ.md) | 数组、滑动窗口 | 简单 | +| [LCR 057. 存在重复元素 III](https://leetcode.cn/problems/7WqeDu/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7WqeDu.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 中等 | +| [LCR 059. 数据流中的第 K 大元素](https://leetcode.cn/problems/jBjn9C/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jBjn9C.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | +| [LCR 060. 前 K 个高频元素](https://leetcode.cn/problems/g5c51o/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/g5c51o.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | +| [LCR 062. 实现 Trie (前缀树)](https://leetcode.cn/problems/QC3q1f/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QC3q1f.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [LCR 063. 单词替换](https://leetcode.cn/problems/UhWRSj/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/UhWRSj.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [LCR 064. 实现一个魔法字典](https://leetcode.cn/problems/US1pGT/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/US1pGT.md) | 深度优先搜索、设计、字典树、哈希表、字符串 | 中等 | +| [LCR 065. 单词的压缩编码](https://leetcode.cn/problems/iSwD2y/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/iSwD2y.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [LCR 066. 键值映射](https://leetcode.cn/problems/z1R5dt/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/z1R5dt.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [LCR 067. 数组中两个数的最大异或值](https://leetcode.cn/problems/ms70jA/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ms70jA.md) | 位运算、字典树、数组、哈希表 | 中等 | +| [LCR 068. 搜索插入位置](https://leetcode.cn/problems/N6YdxV/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/N6YdxV.md) | 数组、二分查找 | 简单 | +| [LCR 072. x 的平方根](https://leetcode.cn/problems/jJ0w9p/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jJ0w9p.md) | 数学、二分查找 | 简单 | +| [LCR 073. 爱吃香蕉的狒狒](https://leetcode.cn/problems/nZZqjQ/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/nZZqjQ.md) | 数组、二分查找 | 中等 | +| [LCR 074. 合并区间](https://leetcode.cn/problems/SsGoHC/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/SsGoHC.md) | 数组、排序 | 中等 | +| [LCR 075. 数组的相对排序](https://leetcode.cn/problems/0H97ZC/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0H97ZC.md) | 数组、哈希表、计数排序、排序 | 简单 | +| [LCR 076. 数组中的第 K 个最大元素](https://leetcode.cn/problems/xx4gT2/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xx4gT2.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | +| [LCR 077. 排序链表](https://leetcode.cn/problems/7WHec2/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7WHec2.md) | 链表、双指针、分治、排序、归并排序 | 中等 | +| [LCR 078. 合并 K 个升序链表](https://leetcode.cn/problems/vvXgSW/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vvXgSW.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [LCR 079. 子集](https://leetcode.cn/problems/TVdhkn/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/TVdhkn.md) | 位运算、数组、回溯 | 中等 | +| [LCR 080. 组合](https://leetcode.cn/problems/uUsW3B/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/uUsW3B.md) | 数组、回溯 | 中等 | +| [LCR 081. 组合总和](https://leetcode.cn/problems/Ygoe9J/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Ygoe9J.md) | 数组、回溯 | 中等 | +| [LCR 082. 组合总和 II](https://leetcode.cn/problems/4sjJUc/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/4sjJUc.md) | 数组、回溯 | 中等 | +| [LCR 083. 全排列](https://leetcode.cn/problems/VvJkup/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/VvJkup.md) | 数组、回溯 | 中等 | +| [LCR 084. 全排列 II](https://leetcode.cn/problems/7p8L0Z/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7p8L0Z.md) | 数组、回溯 | 中等 | +| [LCR 085. 括号生成](https://leetcode.cn/problems/IDBivT/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/IDBivT.md) | 字符串、动态规划、回溯 | 中等 | +| [LCR 086. 分割回文串](https://leetcode.cn/problems/M99OJA/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/M99OJA.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | +| [LCR 087. 复原 IP 地址](https://leetcode.cn/problems/0on3uN/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0on3uN.md) | 字符串、回溯 | 中等 | +| [LCR 088. 使用最小花费爬楼梯](https://leetcode.cn/problems/GzCJIP/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/GzCJIP.md) | 数组、动态规划 | 简单 | +| [LCR 089. 打家劫舍](https://leetcode.cn/problems/Gu0c2T/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Gu0c2T.md) | 数组、动态规划 | 中等 | +| [LCR 090. 打家劫舍 II](https://leetcode.cn/problems/PzWKhm/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/PzWKhm.md) | 数组、动态规划 | 中等 | +| [LCR 093. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/Q91FMA/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Q91FMA.md) | 数组、哈希表、动态规划 | 中等 | +| [LCR 095. 最长公共子序列](https://leetcode.cn/problems/qJnOS7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qJnOS7.md) | 字符串、动态规划 | 中等 | +| [LCR 097. 不同的子序列](https://leetcode.cn/problems/21dk04/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/21dk04.md) | 字符串、动态规划 | 困难 | +| [LCR 098. 不同路径](https://leetcode.cn/problems/2AoeFn/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2AoeFn.md) | 数学、动态规划、组合数学 | 中等 | +| [LCR 101. 分割等和子集](https://leetcode.cn/problems/NUPfPr/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NUPfPr.md) | 数学、字符串、模拟 | 简单 | +| [LCR 102. 目标和](https://leetcode.cn/problems/YaVDxD/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/YaVDxD.md) | 数组、动态规划、回溯 | 中等 | +| [LCR 103. 零钱兑换](https://leetcode.cn/problems/gaM7Ch/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gaM7Ch.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [LCR 104. 组合总和 Ⅳ](https://leetcode.cn/problems/D0F0SV/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/D0F0SV.md) | 数组、动态规划 | 中等 | +| [LCR 105. 岛屿的最大面积](https://leetcode.cn/problems/ZL6zAn/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ZL6zAn.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [LCR 106. 判断二分图](https://leetcode.cn/problems/vEAB3K/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vEAB3K.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [LCR 107. 01 矩阵](https://leetcode.cn/problems/2bCMpM/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2bCMpM.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | +| [LCR 108. 单词接龙](https://leetcode.cn/problems/om3reC/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/om3reC.md) | 广度优先搜索、哈希表、字符串 | 困难 | +| [LCR 109. 打开转盘锁](https://leetcode.cn/problems/zlDJc7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zlDJc7.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | +| [LCR 111. 除法求值](https://leetcode.cn/problems/vlzXQL/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vlzXQL.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 | +| [LCR 112. 矩阵中的最长递增路径](https://leetcode.cn/problems/fpTFWP/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fpTFWP.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | +| [LCR 113. 课程表 II](https://leetcode.cn/problems/QA2IGt/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QA2IGt.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [LCR 116. 省份数量](https://leetcode.cn/problems/bLyHh0/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bLyHh0.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [LCR 118. 冗余连接](https://leetcode.cn/problems/7LpjUW/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7LpjUW.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [LCR 119. 最长连续序列](https://leetcode.cn/problems/WhsWhI/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WhsWhI.md) | 并查集、数组、哈希表 | 中等 | +| [LCR 120. 寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md) | 数组、哈希表、排序 | 简单 | +| [LCR 121. 寻找目标值 - 二维数组](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-wei-shu-zu-zhong-de-cha-zhao-lcof.md) | 数组、二分查找、分治、矩阵 | 中等 | +| [LCR 122. 路径加密](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ti-huan-kong-ge-lcof.md) | 字符串 | 简单 | +| [LCR 123. 图书整理 I](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md) | 栈、递归、链表、双指针 | 简单 | +| [LCR 124. 推理二叉树](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zhong-jian-er-cha-shu-lcof.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [LCR 125. 图书整理 II](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md) | 栈、设计、队列 | 简单 | +| [LCR 126. 斐波那契数](https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fei-bo-na-qi-shu-lie-lcof.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [LCR 127. 跳跃训练](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qing-wa-tiao-tai-jie-wen-ti-lcof.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [LCR 128. 库存管理 I](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof.md) | 数组、二分查找 | 简单 | +| [LCR 129. 字母迷宫](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ju-zhen-zhong-de-lu-jing-lcof.md) | 数组、字符串、回溯、矩阵 | 中等 | +| [LCR 130. 衣橱整理](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | +| [LCR 131. 砍竹子 I](https://leetcode.cn/problems/jian-sheng-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jian-sheng-zi-lcof.md) | 数学、动态规划 | 中等 | +| [LCR 133. 位 1 的个数](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-jin-zhi-zhong-1de-ge-shu-lcof.md) | 位运算 | 简单 | +| [LCR 134. Pow(x, n)](https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zhi-de-zheng-shu-ci-fang-lcof.md) | 递归、数学 | 中等 | +| [LCR 135. 报数](https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof.md) | 数组、数学 | 简单 | +| [LCR 136. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shan-chu-lian-biao-de-jie-dian-lcof.md) | 链表 | 简单 | +| [LCR 139. 训练计划 I](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md) | 数组、双指针、排序 | 简单 | +| [LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md) | 链表、双指针 | 简单 | +| [LCR 141. 训练计划 III](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fan-zhuan-lian-biao-lcof.md) | 递归、链表 | 简单 | +| [LCR 142. 训练计划 IV](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-bing-liang-ge-pai-xu-de-lian-biao-lcof.md) | 递归、链表 | 简单 | +| [LCR 143. 子结构判断](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-de-zi-jie-gou-lcof.md) | 树、深度优先搜索、二叉树 | 中等 | +| [LCR 144. 翻转二叉树](https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-jing-xiang-lcof.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 145. 判断对称二叉树](https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dui-cheng-de-er-cha-shu-lcof.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 146. 螺旋遍历二维数组](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shun-shi-zhen-da-yin-ju-zhen-lcof.md) | 数组、矩阵、模拟 | 简单 | +| [LCR 147. 最小栈](https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bao-han-minhan-shu-de-zhan-lcof.md) | 栈、设计 | 简单 | +| [LCR 148. 验证图书取出顺序](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zhan-de-ya-ru-dan-chu-xu-lie-lcof.md) | 栈、数组、模拟 | 中等 | +| [LCR 149. 彩灯装饰记录 I](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-lcof.md) | 树、广度优先搜索、二叉树 | 中等 | +| [LCR 150. 彩灯装饰记录 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof.md) | 树、广度优先搜索、二叉树 | 简单 | +| [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md) | 树、广度优先搜索、二叉树 | 中等 | +| [LCR 152. 验证二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md) | 栈、树、二叉搜索树、递归、数组、二叉树、单调栈 | 中等 | +| [LCR 153. 二叉树中和为目标值的路径](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | +| [LCR 154. 复杂链表的复制](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fu-za-lian-biao-de-fu-zhi-lcof.md) | 哈希表、链表 | 中等 | +| [LCR 155. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | +| [LCR 156. 序列化与反序列化二叉树](https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xu-lie-hua-er-cha-shu-lcof.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | +| [LCR 157. 套餐内商品的排列顺序](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zi-fu-chuan-de-pai-lie-lcof.md) | 字符串、回溯 | 中等 | +| [LCR 158. 库存管理 II](https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof.md) | 数组、哈希表、分治、计数、排序 | 简单 | +| [LCR 159. 库存管理 III](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | +| [LCR 160. 数据流中的中位数](https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-ju-liu-zhong-de-zhong-wei-shu-lcof.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | +| [LCR 161. 连续天数的最高销售额](https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-xu-zi-shu-zu-de-zui-da-he-lcof.md) | 数组、分治、动态规划 | 简单 | +| [LCR 163. 找到第 k 位数字](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof.md) | 数学、二分查找 | 中等 | +| [LCR 164. 破解闯关密码](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md) | 贪心、字符串、排序 | 中等 | +| [LCR 165. 解密数字](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof.md) | 字符串、动态规划 | 中等 | +| [LCR 166. 珠宝的最高价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/li-wu-de-zui-da-jie-zhi-lcof.md) | 数组、动态规划、矩阵 | 中等 | +| [LCR 167. 招式拆解 I](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [LCR 168. 丑数](https://leetcode.cn/problems/chou-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/chou-shu-lcof.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | +| [LCR 169. 招式拆解 II](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof.md) | 队列、哈希表、字符串、计数 | 简单 | +| [LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | +| [LCR 171. 训练计划 V](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof.md) | 哈希表、链表、双指针 | 简单 | +| [LCR 172. 统计目标成绩的出现次数](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof.md) | 数组、二分查找 | 简单 | +| [LCR 173. 点名](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/que-shi-de-shu-zi-lcof.md) | 位运算、数组、哈希表、数学、二分查找 | 简单 | +| [LCR 174. 寻找二叉搜索树中的目标节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [LCR 175. 计算二叉树的深度](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-shen-du-lcof.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 176. 判断是否为平衡二叉树](https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ping-heng-er-cha-shu-lcof.md) | 树、深度优先搜索、二叉树 | 简单 | +| [LCR 177. 撞色搭配](https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof.md) | 位运算、数组 | 中等 | +| [LCR 179. 查找总价格为目标值的两个商品](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-liang-ge-shu-zi-lcof.md) | 数组、双指针、二分查找 | 简单 | +| [LCR 180. 文件组合](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md) | 数学、双指针、枚举 | 简单 | +| [LCR 181. 字符串中的单词反转](https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fan-zhuan-dan-ci-shun-xu-lcof.md) | 双指针、字符串 | 简单 | +| [LCR 182. 动态口令](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zuo-xuan-zhuan-zi-fu-chuan-lcof.md) | 数学、双指针、字符串 | 简单 | +| [LCR 183. 望远镜中最高的海拔](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/hua-dong-chuang-kou-de-zui-da-zhi-lcof.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [LCR 184. 设计自助结算系统](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dui-lie-de-zui-da-zhi-lcof.md) | 设计、队列、单调队列 | 中等 | +| [LCR 186. 文物朝代判断](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md) | 数组、排序 | 简单 | +| [LCR 187. 破冰游戏](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md) | 递归、数学 | 简单 | +| [LCR 188. 买卖芯片的最佳时机](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gu-piao-de-zui-da-li-run-lcof.md) | 数组、动态规划 | 中等 | +| [LCR 189. 设计机械累加器](https://leetcode.cn/problems/qiu-12n-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qiu-12n-lcof.md) | 位运算、递归、脑筋急转弯 | 中等 | +| [LCR 190. 加密运算](https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof.md) | 位运算、数学 | 简单 | +| [LCR 191. 按规则计算统计结果](https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gou-jian-cheng-ji-shu-zu-lcof.md) | 数组、前缀和 | 中等 | +| [LCR 192. 把字符串转换成整数 (atoi)](https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof.md) | 字符串 | 中等 | +| [LCR 193. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [LCR 194. 二叉树的最近公共祖先](https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof.md) | 树、深度优先搜索、二叉树 | 简单 | + + +### 面试题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [面试题 01.07. 旋转矩阵](https://leetcode.cn/problems/rotate-matrix-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/rotate-matrix-lcci.md) | 数组、数学、矩阵 | 中等 | +| [面试题 01.08. 零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/zero-matrix-lcci.md) | 数组、哈希表、矩阵 | 中等 | +| [面试题 02.02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/kth-node-from-end-of-list-lcci.md) | 链表、双指针 | 简单 | +| [面试题 02.05. 链表求和](https://leetcode.cn/problems/sum-lists-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sum-lists-lcci.md) | 递归、链表、数学 | 中等 | +| [面试题 02.06. 回文链表](https://leetcode.cn/problems/palindrome-linked-list-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/palindrome-linked-list-lcci.md) | 栈、递归、链表、双指针 | 简单 | +| [面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/intersection-of-two-linked-lists-lcci.md) | 哈希表、链表、双指针 | 简单 | +| [面试题 02.08. 环路检测](https://leetcode.cn/problems/linked-list-cycle-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/linked-list-cycle-lcci.md) | 哈希表、链表、双指针 | 中等 | +| [面试题 03.02. 栈的最小值](https://leetcode.cn/problems/min-stack-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/min-stack-lcci.md) | 栈、设计 | 简单 | +| [面试题 03.04. 化栈为队](https://leetcode.cn/problems/implement-queue-using-stacks-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/implement-queue-using-stacks-lcci.md) | 栈、设计、队列 | 简单 | +| [面试题 04.02. 最小高度树](https://leetcode.cn/problems/minimum-height-tree-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/minimum-height-tree-lcci.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | +| [面试题 04.05. 合法二叉搜索树](https://leetcode.cn/problems/legal-binary-search-tree-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/legal-binary-search-tree-lcci.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [面试题 04.06. 后继者](https://leetcode.cn/problems/successor-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/successor-lcci.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [面试题 04.08. 首个共同祖先](https://leetcode.cn/problems/first-common-ancestor-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/first-common-ancestor-lcci.md) | 树、深度优先搜索、二叉树 | 中等 | +| [面试题 04.12. 求和路径](https://leetcode.cn/problems/paths-with-sum-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/paths-with-sum-lcci.md) | 树、深度优先搜索、二叉树 | 中等 | +| [面试题 08.04. 幂集](https://leetcode.cn/problems/power-set-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/power-set-lcci.md) | 位运算、数组、回溯 | 中等 | +| [面试题 08.07. 无重复字符串的排列组合](https://leetcode.cn/problems/permutation-i-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/permutation-i-lcci.md) | 字符串、回溯 | 中等 | +| [面试题 08.08. 有重复字符串的排列组合](https://leetcode.cn/problems/permutation-ii-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/permutation-ii-lcci.md) | 字符串、回溯 | 中等 | +| [面试题 08.09. 括号](https://leetcode.cn/problems/bracket-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/bracket-lcci.md) | 字符串、动态规划、回溯 | 中等 | +| [面试题 08.10. 颜色填充](https://leetcode.cn/problems/color-fill-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/color-fill-lcci.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | +| [面试题 08.12. 八皇后](https://leetcode.cn/problems/eight-queens-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/eight-queens-lcci.md) | 数组、回溯 | 困难 | +| [面试题 10.01. 合并排序的数组](https://leetcode.cn/problems/sorted-merge-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sorted-merge-lcci.md) | 数组、双指针、排序 | 简单 | +| [面试题 10.02. 变位词组](https://leetcode.cn/problems/group-anagrams-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/group-anagrams-lcci.md) | 数组、哈希表、字符串、排序 | 中等 | +| [面试题 10.09. 排序矩阵查找](https://leetcode.cn/problems/sorted-matrix-search-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sorted-matrix-search-lcci.md) | 数组、二分查找、分治、矩阵 | 中等 | +| [面试题 16.02. 单词频率](https://leetcode.cn/problems/words-frequency-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/words-frequency-lcci.md) | 设计、字典树、数组、哈希表、字符串 | 中等 | +| [面试题 16.05. 阶乘尾数](https://leetcode.cn/problems/factorial-zeros-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/factorial-zeros-lcci.md) | 数学 | 简单 | +| [面试题 16.26. 计算器](https://leetcode.cn/problems/calculator-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/calculator-lcci.md) | 栈、数学、字符串 | 中等 | +| [面试题 17.06. 2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/number-of-2s-in-range-lcci.md) | 递归、数学、动态规划 | 困难 | +| [面试题 17.14. 最小K个数](https://leetcode.cn/problems/smallest-k-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/smallest-k-lcci.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | +| [面试题 17.15. 最长单词](https://leetcode.cn/problems/longest-word-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/longest-word-lcci.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [面试题 17.17. 多次搜索](https://leetcode.cn/problems/multi-search-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/multi-search-lcci.md) | 字典树、数组、哈希表、字符串、字符串匹配、滑动窗口 | 中等 | + + diff --git a/docs/00_preface/00_06_categories_list.md b/docs/00_preface/00_06_categories_list.md new file mode 100644 index 00000000..8c01d8c3 --- /dev/null +++ b/docs/00_preface/00_06_categories_list.md @@ -0,0 +1,1167 @@ +# LeetCode 题解(按分类排序,推荐刷题列表 ★★★) + +## 第 1 章 数组 + +### 数组基础题目 + +#### 数组操作题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0189. 轮转数组](https://leetcode.cn/problems/rotate-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/rotate-array.md) | 数组、数学、双指针 | 中等 | +| [0066. 加一](https://leetcode.cn/problems/plus-one/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/plus-one.md) | 数组、数学 | 简单 | +| [0724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-pivot-index.md) | 数组、前缀和 | 简单 | +| [0485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones.md) | 数组 | 简单 | +| [0238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/product-of-array-except-self.md) | 数组、前缀和 | 中等 | + + +#### 二维数组题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/diagonal-traverse.md) | 数组、矩阵、模拟 | 中等 | +| [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) | 数组、数学、矩阵 | 中等 | +| [0073. 矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/set-matrix-zeroes.md) | 数组、哈希表、矩阵 | 中等 | +| [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) | 数组、矩阵、模拟 | 中等 | +| [0059. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix-ii.md) | 数组、矩阵、模拟 | 中等 | +| [0289. 生命游戏](https://leetcode.cn/problems/game-of-life/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/game-of-life.md) | 数组、矩阵、模拟 | 中等 | + + +### 排序算法题目 + +#### 冒泡排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [LCR 164. 破解闯关密码](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md) | 贪心、字符串、排序 | 中等 | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | + + +#### 选择排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | + + +#### 插入排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) | 数组、双指针、排序 | 中等 | + + +#### 希尔排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0506. 相对名次](https://leetcode.cn/problems/relative-ranks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/relative-ranks.md) | 数组、排序、堆(优先队列) | 简单 | + + +#### 归并排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | +| [0315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | + + +#### 快速排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | + + +#### 堆排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | +| [LCR 159. 库存管理 III](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | + + +#### 计数排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [1122. 数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/relative-sort-array.md) | 数组、哈希表、计数排序、排序 | 简单 | + + +#### 桶排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | +| [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) | 数组、桶排序、基数排序、排序 | 中等 | + + +#### 基数排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) | 数组、桶排序、基数排序、排序 | 中等 | +| [0561. 数组拆分](https://leetcode.cn/problems/array-partition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/array-partition.md) | 贪心、数组、计数排序、排序 | 简单 | + + +#### 其他排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate.md) | 数组、哈希表、排序 | 简单 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0179. 最大数](https://leetcode.cn/problems/largest-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/largest-number.md) | 贪心、数组、字符串、排序 | 中等 | +| [0384. 打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/shuffle-an-array.md) | 设计、数组、数学、随机化 | 中等 | +| [LCR 164. 破解闯关密码](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md) | 贪心、字符串、排序 | 中等 | + + +### 二分查找题目 + +#### 二分下标题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0704. 二分查找](https://leetcode.cn/problems/binary-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) | 数组、二分查找 | 简单 | +| [0374. 猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower.md) | 二分查找、交互 | 简单 | +| [0035. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-insert-position.md) | 数组、二分查找 | 简单 | +| [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md) | 数组、二分查找 | 中等 | +| [0167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md) | 数组、双指针、二分查找 | 中等 | +| [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md) | 数组、二分查找 | 困难 | +| [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0081. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md) | 数组、二分查找 | 中等 | +| [0278. 第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/first-bad-version.md) | 二分查找、交互 | 简单 | +| [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-peak-element.md) | 数组、二分查找 | 中等 | +| [0852. 山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md) | 数组、二分查找 | 中等 | +| [1095. 山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/find-in-mountain-array.md) | 数组、二分查找、交互 | 困难 | +| [0744. 寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md) | 数组、二分查找 | 简单 | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0074. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-a-2d-matrix.md) | 数组、二分查找、矩阵 | 中等 | +| [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/search-a-2d-matrix-ii.md) | 数组、二分查找、分治、矩阵 | 中等 | + + +#### 二分答案题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) | 数学、二分查找 | 简单 | +| [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-the-duplicate-number.md) | 位运算、数组、双指针、二分查找 | 中等 | +| [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) | 递归、数学 | 中等 | +| [0367. 有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/valid-perfect-square.md) | 数学、二分查找 | 简单 | +| [1300. 转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md) | 数组、二分查找、排序 | 中等 | +| [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/nth-digit.md) | 数学、二分查找 | 中等 | + + +#### 复杂的二分查找问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/koko-eating-bananas.md) | 数组、二分查找 | 中等 | +| [0410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/split-array-largest-sum.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | +| [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0658. 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-k-closest-elements.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | +| [0270. 最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/closest-binary-search-tree-value.md) | 树、深度优先搜索、二叉搜索树、二分查找、二叉树 | 简单 | +| [0702. 搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md) | 数组、二分查找、交互 | 中等 | +| [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-the-duplicate-number.md) | 位运算、数组、双指针、二分查找 | 中等 | +| [0719. 找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md) | 数组、双指针、二分查找、排序 | 困难 | +| [0259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/3sum-smaller.md) | 数组、双指针、二分查找、排序 | 中等 | +| [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md) | 数组、二分查找 | 中等 | +| [1482. 制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md) | 数组、二分查找 | 中等 | + + +### 双指针题目 + +#### 对撞指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md) | 数组、双指针、二分查找 | 中等 | +| [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) | 双指针、字符串 | 简单 | +| [0345. 反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-vowels-of-a-string.md) | 双指针、字符串 | 简单 | +| [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) | 双指针、字符串 | 简单 | +| [0011. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/container-with-most-water.md) | 贪心、数组、双指针 | 中等 | +| [0611. 有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-triangle-number.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0016. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum-closest.md) | 数组、双指针、排序 | 中等 | +| [0018. 四数之和](https://leetcode.cn/problems/4sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/4sum.md) | 数组、双指针、排序 | 中等 | +| [0259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/3sum-smaller.md) | 数组、双指针、二分查找、排序 | 中等 | +| [0658. 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-k-closest-elements.md) | 数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) | 中等 | +| [1099. 小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-sum-less-than-k.md) | 数组、双指针、二分查找、排序 | 简单 | +| [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) | 数组、双指针、排序 | 中等 | +| [0360. 有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sort-transformed-array.md) | 数组、数学、双指针、排序 | 中等 | +| [0977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/squares-of-a-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [0881. 救生艇](https://leetcode.cn/problems/boats-to-save-people/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/boats-to-save-people.md) | 贪心、数组、双指针、排序 | 中等 | +| [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | +| [0443. 压缩字符串](https://leetcode.cn/problems/string-compression/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/string-compression.md) | 双指针、字符串 | 中等 | + + +#### 快慢指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0026. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md) | 数组、双指针 | 简单 | +| [0080. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md) | 数组、双指针 | 中等 | +| [0027. 移除元素](https://leetcode.cn/problems/remove-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-element.md) | 数组、双指针 | 简单 | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | +| [0845. 数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/longest-mountain-in-array.md) | 数组、双指针、动态规划、枚举 | 中等 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [0719. 找出第 K 小的数对距离](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md) | 数组、双指针、二分查找、排序 | 困难 | +| [0334. 递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/increasing-triplet-subsequence.md) | 贪心、数组 | 中等 | +| [0978. 最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/longest-turbulent-subarray.md) | 数组、动态规划、滑动窗口 | 中等 | +| [LCR 139. 训练计划 I](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md) | 数组、双指针、排序 | 简单 | + + +#### 分离双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0925. 长按键入](https://leetcode.cn/problems/long-pressed-name/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/long-pressed-name.md) | 双指针、字符串 | 简单 | +| [0844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/backspace-string-compare.md) | 栈、双指针、字符串、模拟 | 简单 | +| [1229. 安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/meeting-scheduler.md) | 数组、双指针、排序 | 中等 | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | +| [0392. 判断子序列](https://leetcode.cn/problems/is-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/is-subsequence.md) | 双指针、字符串、动态规划 | 简单 | + + +### 滑动窗口题目 + +#### 固定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1343. 大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md) | 数组、滑动窗口 | 中等 | +| [0643. 子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-average-subarray-i.md) | 数组、滑动窗口 | 简单 | +| [1052. 爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/grumpy-bookstore-owner.md) | 数组、滑动窗口 | 中等 | +| [1423. 可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md) | 数组、前缀和、滑动窗口 | 中等 | +| [1456. 定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md) | 字符串、滑动窗口 | 中等 | +| [0567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/permutation-in-string.md) | 哈希表、双指针、字符串、滑动窗口 | 中等 | +| [1100. 长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [1151. 最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md) | 数组、滑动窗口 | 中等 | +| [1176. 健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/diet-plan-performance.md) | 数组、滑动窗口 | 简单 | +| [0438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0995. K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | +| [0683. K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/k-empty-slots.md) | 树状数组、线段树、队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0480. 滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sliding-window-median.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | + + +#### 不定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0674. 最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md) | 数组 | 简单 | +| [0485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones.md) | 数组 | 简单 | +| [0487. 最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones-ii.md) | 数组、动态规划、滑动窗口 | 中等 | +| [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-window-substring.md) | 哈希表、字符串、滑动窗口 | 困难 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/max-consecutive-ones-iii.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md) | 数组、哈希表、二分查找、前缀和、滑动窗口 | 中等 | +| [0424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/longest-repeating-character-replacement.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [1695. 删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-erasure-value.md) | 数组、哈希表、滑动窗口 | 中等 | +| [1208. 尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/get-equal-substrings-within-budget.md) | 字符串、二分查找、前缀和、滑动窗口 | 中等 | +| [1493. 删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md) | 数组、动态规划、滑动窗口 | 中等 | +| [0727. 最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-window-subsequence.md) | 字符串、动态规划、滑动窗口 | 困难 | +| [0159. 至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0340. 至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0795. 区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md) | 数组、双指针 | 中等 | +| [0992. K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/subarrays-with-k-different-integers.md) | 数组、哈希表、计数、滑动窗口 | 困难 | +| [0713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/subarray-product-less-than-k.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0904. 水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/fruit-into-baskets.md) | 数组、哈希表、滑动窗口 | 中等 | +| [1358. 包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0467. 环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md) | 字符串、动态规划 | 中等 | +| [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | + + +## 第 2 章 链表 + +### 链表基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0707. 设计链表](https://leetcode.cn/problems/design-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-linked-list.md) | 设计、链表 | 中等 | +| [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md) | 链表 | 简单 | +| [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md) | 链表、双指针 | 中等 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-nodes-in-k-group.md) | 递归、链表 | 困难 | +| [0203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/remove-linked-list-elements.md) | 递归、链表 | 简单 | +| [0328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/odd-even-linked-list.md) | 链表 | 中等 | +| [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) | 栈、递归、链表、双指针 | 简单 | +| [0430. 扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md) | 深度优先搜索、链表、双向链表 | 中等 | +| [0138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/copy-list-with-random-pointer.md) | 哈希表、链表 | 中等 | +| [0061. 旋转链表](https://leetcode.cn/problems/rotate-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-list.md) | 链表、双指针 | 中等 | + + +### 链表排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0148. 排序链表](https://leetcode.cn/problems/sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) | 链表、双指针、分治、排序、归并排序 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0147. 对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/insertion-sort-list.md) | 链表、排序 | 中等 | + + +### 链表双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) | 哈希表、链表、双指针 | 简单 | +| [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) | 哈希表、链表、双指针 | 中等 | +| [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/intersection-of-two-linked-lists.md) | 哈希表、链表、双指针 | 简单 | +| [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) | 链表、双指针 | 中等 | +| [0876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/middle-of-the-linked-list.md) | 链表、双指针 | 简单 | +| [LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md) | 链表、双指针 | 简单 | +| [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reorder-list.md) | 栈、递归、链表、双指针 | 中等 | +| [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-two-numbers.md) | 递归、链表、数学 | 中等 | +| [0445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-two-numbers-ii.md) | 栈、链表、数学 | 中等 | + + +## 第 3 章 栈、队列、哈希表 + +### 栈基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md) | 栈、字符串 | 简单 | +| [0155. 最小栈](https://leetcode.cn/problems/min-stack/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) | 栈、设计 | 中等 | +| [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) | 栈、字符串 | 简单 | +| [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) | 栈、数学、字符串 | 中等 | +| [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) | 栈、数组、单调栈 | 中等 | +| [0150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md) | 栈、数组、数学 | 中等 | +| [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-queue-using-stacks.md) | 栈、设计、队列 | 简单 | +| [LCR 125. 图书整理 II](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md) | 栈、设计、队列 | 简单 | +| [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) | 栈、递归、字符串 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0946. 验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/validate-stack-sequences.md) | 栈、数组、模拟 | 中等 | +| [LCR 123. 图书整理 I](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md) | 栈、递归、链表、双指针 | 简单 | +| [0071. 简化路径](https://leetcode.cn/problems/simplify-path/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/simplify-path.md) | 栈、字符串 | 中等 | + + +### 单调栈题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) | 栈、数组、单调栈 | 中等 | +| [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/next-greater-element-i.md) | 栈、数组、哈希表、单调栈 | 简单 | +| [0503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/next-greater-element-ii.md) | 栈、数组、单调栈 | 中等 | +| [0901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/online-stock-span.md) | 栈、设计、数据流、单调栈 | 中等 | +| [0084. 柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/largest-rectangle-in-histogram.md) | 栈、数组、单调栈 | 困难 | +| [0316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/remove-duplicate-letters.md) | 栈、贪心、字符串、单调栈 | 中等 | +| [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | +| [0085. 最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximal-rectangle.md) | 栈、数组、动态规划、矩阵、单调栈 | 困难 | +| [0862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md) | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | + + +### 队列基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-circular-queue.md) | 设计、队列、数组、链表 | 中等 | +| [0346. 数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/moving-average-from-data-stream.md) | 设计、队列、数组、数据流 | 简单 | +| [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) | 栈、设计、队列 | 简单 | + + +### 优先队列题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0703. 数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | +| [0347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/top-k-frequent-elements.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | +| [0451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sort-characters-by-frequency.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | +| [0973. 最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/k-closest-points-to-origin.md) | 几何、数组、数学、分治、快速选择、排序、堆(优先队列) | 中等 | +| [1296. 划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md) | 贪心、数组、哈希表、排序 | 中等 | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-median-from-data-stream.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/the-skyline-problem.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | + + +### 哈希表题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashset.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | +| [0706. 设计哈希映射](https://leetcode.cn/problems/design-hashmap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashmap.md) | 设计、数组、哈希表、链表、哈希函数 | 简单 | +| [0217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate.md) | 数组、哈希表、排序 | 简单 | +| [0219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-ii.md) | 数组、哈希表、滑动窗口 | 简单 | +| [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) | 数组、桶排序、有序集合、排序、滑动窗口 | 困难 | +| [1941. 检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md) | 哈希表、字符串、计数 | 简单 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0383. 赎金信](https://leetcode.cn/problems/ransom-note/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/ransom-note.md) | 哈希表、字符串、计数 | 简单 | +| [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | +| [0036. 有效的数独](https://leetcode.cn/problems/valid-sudoku/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-sudoku.md) | 数组、哈希表、矩阵 | 中等 | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0018. 四数之和](https://leetcode.cn/problems/4sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/4sum.md) | 数组、双指针、排序 | 中等 | +| [0454. 四数相加 II](https://leetcode.cn/problems/4sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/4sum-ii.md) | 数组、哈希表 | 中等 | +| [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/first-missing-positive.md) | 数组、哈希表 | 困难 | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | +| [0202. 快乐数](https://leetcode.cn/problems/happy-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/happy-number.md) | 哈希表、数学、双指针 | 简单 | +| [0242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/valid-anagram.md) | 哈希表、字符串、排序 | 简单 | +| [0205. 同构字符串](https://leetcode.cn/problems/isomorphic-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/isomorphic-strings.md) | 哈希表、字符串 | 简单 | +| [0442. 数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/find-all-duplicates-in-an-array.md) | 数组、哈希表 | 中等 | +| [LCR 186. 文物朝代判断](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md) | 数组、排序 | 简单 | +| [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | +| [LCR 120. 寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md) | 数组、哈希表、排序 | 简单 | +| [0451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sort-characters-by-frequency.md) | 哈希表、字符串、桶排序、计数、排序、堆(优先队列) | 中等 | +| [0049. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/group-anagrams.md) | 数组、哈希表、字符串、排序 | 中等 | +| [0599. 两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md) | 数组、哈希表、字符串 | 简单 | +| [0387. 字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/first-unique-character-in-a-string.md) | 队列、哈希表、字符串、计数 | 简单 | +| [0447. 回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/number-of-boomerangs.md) | 数组、哈希表、数学 | 中等 | +| [0149. 直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/max-points-on-a-line.md) | 几何、数组、哈希表、数学 | 困难 | +| [0359. 日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/logger-rate-limiter.md) | 设计、哈希表、数据流 | 简单 | +| [0811. 子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/subdomain-visit-count.md) | 数组、哈希表、字符串、计数 | 中等 | + + +## 第 4 章 字符串 + +### 字符串基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) | 双指针、字符串 | 简单 | +| [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) | 双指针、字符串、动态规划 | 中等 | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) | 双指针、字符串 | 简单 | +| [0557. 反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md) | 双指针、字符串 | 简单 | +| [0049. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/group-anagrams.md) | 数组、哈希表、字符串、排序 | 中等 | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | +| [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-words-in-a-string.md) | 双指针、字符串 | 中等 | +| [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/multiply-strings.md) | 数学、字符串、模拟 | 中等 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | + + +### 单模式串匹配题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0028. 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) | 双指针、字符串、字符串匹配 | 简单 | +| [0459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) | 字符串、字符串匹配 | 简单 | +| [0686. 重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) | 字符串、字符串匹配 | 中等 | +| [1668. 最大重复子字符串](https://leetcode.cn/problems/maximum-repeating-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-repeating-substring.md) | 字符串、动态规划、字符串匹配 | 简单 | +| [0796. 旋转字符串](https://leetcode.cn/problems/rotate-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) | 字符串、字符串匹配 | 简单 | +| [1408. 数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) | 数组、字符串、字符串匹配 | 简单 | +| [2156. 查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) | 字符串、滑动窗口、哈希函数、滚动哈希 | 困难 | + + +### 字典树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-trie-prefix-tree.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [0677. 键值映射](https://leetcode.cn/problems/map-sum-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/map-sum-pairs.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [0648. 单词替换](https://leetcode.cn/problems/replace-words/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/replace-words.md) | 字典树、数组、哈希表、字符串 | 中等 | +| [0642. 设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-search-autocomplete-system.md) | 深度优先搜索、设计、字典树、字符串、数据流、排序、堆(优先队列) | 困难 | +| [0211. 添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md) | 深度优先搜索、设计、字典树、字符串 | 中等 | +| [0421. 数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md) | 位运算、字典树、数组、哈希表 | 中等 | +| [0212. 单词搜索 II](https://leetcode.cn/problems/word-search-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/word-search-ii.md) | 字典树、数组、字符串、回溯、矩阵 | 困难 | +| [0425. 单词方块](https://leetcode.cn/problems/word-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/word-squares.md) | 字典树、数组、字符串、回溯 | 困难 | +| [0336. 回文对](https://leetcode.cn/problems/palindrome-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/palindrome-pairs.md) | 字典树、数组、哈希表、字符串 | 困难 | +| [1023. 驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/camelcase-matching.md) | 字典树、数组、双指针、字符串、字符串匹配 | 中等 | +| [0676. 实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/implement-magic-dictionary.md) | 深度优先搜索、设计、字典树、哈希表、字符串 | 中等 | +| [0440. 字典序的第K小数字](https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/k-th-smallest-in-lexicographical-order.md) | 字典树 | 困难 | + + +## 第 5 章 树 + +### 二叉树的遍历题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/symmetric-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0112. 路径总和](https://leetcode.cn/problems/path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum-ii.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | +| [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | +| [0117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md) | 树、深度优先搜索、广度优先搜索、链表、二叉树 | 中等 | +| [0297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | +| [0114. 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/flatten-binary-tree-to-linked-list.md) | 栈、树、深度优先搜索、链表、二叉树 | 中等 | + + +### 二叉树的还原题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | + + +### 二叉搜索树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0173. 二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-search-tree-iterator.md) | 栈、树、设计、二叉搜索树、二叉树、迭代器 | 中等 | +| [0700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-binary-search-tree.md) | 树、二叉搜索树、二叉树 | 简单 | +| [0701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md) | 树、二叉搜索树、二叉树 | 中等 | +| [0450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/delete-node-in-a-bst.md) | 树、二叉搜索树、二叉树 | 中等 | +| [0703. 数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md) | 树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) | 简单 | +| [LCR 174. 寻找二叉搜索树中的目标节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0230. 二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-smallest-element-in-a-bst.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0426. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | +| [0108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md) | 树、二叉搜索树、数组、分治、二叉树 | 简单 | +| [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/balanced-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | + + +### 线段树题目 + +#### 单点更新题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-immutable.md) | 设计、数组、前缀和 | 简单 | +| [0307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-mutable.md) | 设计、树状数组、线段树、数组 | 中等 | +| [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/russian-doll-envelopes.md) | 数组、二分查找、动态规划、排序 | 困难 | + + +#### 区间更新题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0370. 区间加法](https://leetcode.cn/problems/range-addition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-addition.md) | 数组、前缀和 | 中等 | +| [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/corporate-flight-bookings.md) | 数组、前缀和 | 中等 | +| [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md) | 数组 | 简单 | +| [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) | 树状数组、线段树、数组、动态规划 | 中等 | +| [1310. 子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/xor-queries-of-a-subarray.md) | 位运算、数组、前缀和 | 中等 | +| [1851. 包含每个查询的最小区间](https://leetcode.cn/problems/minimum-interval-to-include-each-query/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-interval-to-include-each-query.md) | 数组、二分查找、排序、扫描线、堆(优先队列) | 困难 | + + +#### 区间合并题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0729. 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-i.md) | 设计、线段树、数组、二分查找、有序集合 | 中等 | +| [0731. 我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-ii.md) | 设计、线段树、数组、二分查找、有序集合、前缀和 | 中等 | +| [0732. 我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-iii.md) | 设计、线段树、二分查找、有序集合、前缀和 | 困难 | + + +#### 扫描线问题题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/the-skyline-problem.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | +| [0391. 完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/perfect-rectangle.md) | 数组、扫描线 | 困难 | +| [0850. 矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/rectangle-area-ii.md) | 线段树、数组、有序集合、扫描线 | 困难 | + + +### 树状数组题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-immutable.md) | 设计、数组、前缀和 | 简单 | +| [0307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-mutable.md) | 设计、树状数组、线段树、数组 | 中等 | +| [0315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | +| [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md) | 数组 | 简单 | +| [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/russian-doll-envelopes.md) | 数组、二分查找、动态规划、排序 | 困难 | +| [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) | 树状数组、线段树、数组、动态规划 | 中等 | +| [1310. 子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/xor-queries-of-a-subarray.md) | 位运算、数组、前缀和 | 中等 | +| [1893. 检查是否区域内所有整数都被覆盖](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md) | 数组、哈希表、前缀和 | 简单 | + + +### 并查集题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/satisfiability-of-equality-equations.md) | 并查集、图、数组、字符串 | 中等 | +| [0547. 省份数量](https://leetcode.cn/problems/number-of-provinces/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/number-of-provinces.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/redundant-connection.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [1319. 连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0765. 情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/couples-holding-hands.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | +| [0399. 除法求值](https://leetcode.cn/problems/evaluate-division/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/evaluate-division.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、字符串、最短路 | 中等 | +| [0959. 由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/regions-cut-by-slashes.md) | 深度优先搜索、广度优先搜索、并查集、数组、哈希表、矩阵 | 中等 | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | +| [0778. 水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/swim-in-rising-water.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | +| [1202. 交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/smallest-string-with-swaps.md) | 深度优先搜索、广度优先搜索、并查集、数组、哈希表、字符串、排序 | 中等 | +| [0947. 移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md) | 深度优先搜索、并查集、图、哈希表 | 中等 | +| [0803. 打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/bricks-falling-when-hit.md) | 并查集、数组、矩阵 | 困难 | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | + + +## 第 6 章 图论 + +### 深度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/all-paths-from-source-to-target.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0133. 克隆图](https://leetcode.cn/problems/clone-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/clone-graph.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | +| [0494. 目标和](https://leetcode.cn/problems/target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) | 数组、动态规划、回溯 | 中等 | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md) | 栈、树、深度优先搜索 | 简单 | +| [0590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md) | 栈、树、深度优先搜索 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/keys-and-rooms.md) | 深度优先搜索、广度优先搜索、图 | 中等 | +| [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/redundant-connection.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0802. 找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/find-eventual-safe-states.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/is-graph-bipartite.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/possible-bipartition.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [0130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/surrounded-regions.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0417. 太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/pacific-atlantic-water-flow.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | +| [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/number-of-enclaves.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/number-of-closed-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [1034. 边界着色](https://leetcode.cn/problems/coloring-a-border/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/coloring-a-border.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | +| [LCR 130. 衣橱整理](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | +| [0529. 扫雷游戏](https://leetcode.cn/problems/minesweeper/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minesweeper.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 中等 | + + +### 广度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/all-paths-from-source-to-target.md) | 深度优先搜索、广度优先搜索、图、回溯 | 中等 | +| [0286. 墙与门](https://leetcode.cn/problems/walls-and-gates/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/walls-and-gates.md) | 广度优先搜索、数组、矩阵 | 中等 | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/open-the-lock.md) | 广度优先搜索、数组、哈希表、字符串 | 中等 | +| [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) | 广度优先搜索、数学、动态规划 | 中等 | +| [0133. 克隆图](https://leetcode.cn/problems/clone-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/clone-graph.md) | 深度优先搜索、广度优先搜索、图、哈希表 | 中等 | +| [0733. 图像渲染](https://leetcode.cn/problems/flood-fill/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/flood-fill.md) | 深度优先搜索、广度优先搜索、数组、矩阵 | 简单 | +| [0542. 01 矩阵](https://leetcode.cn/problems/01-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/01-matrix.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | +| [LCR 130. 衣橱整理](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md) | 深度优先搜索、广度优先搜索、动态规划 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md) | 树、广度优先搜索、二叉树 | 中等 | + + +### 图的拓扑排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0207. 课程表](https://leetcode.cn/problems/course-schedule/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule-ii.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [1136. 并行课程](https://leetcode.cn/problems/parallel-courses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/parallel-courses.md) | 图、拓扑排序 | 中等 | +| [2050. 并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/parallel-courses-iii.md) | 图、拓扑排序、数组、动态规划 | 困难 | +| [0802. 找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/find-eventual-safe-states.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0851. 喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/loud-and-rich.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | + + +### 图的最小生成树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1584. 连接所有点的最小费用](https://leetcode.cn/problems/min-cost-to-connect-all-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/min-cost-to-connect-all-points.md) | 并查集、图、数组、最小生成树 | 中等 | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | +| [0778. 水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/swim-in-rising-water.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | + + +### 单源最短路径题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0407. 接雨水 II](https://leetcode.cn/problems/trapping-rain-water-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/trapping-rain-water-ii.md) | 广度优先搜索、数组、矩阵、堆(优先队列) | 困难 | +| [0743. 网络延迟时间](https://leetcode.cn/problems/network-delay-time/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/network-delay-time.md) | 深度优先搜索、广度优先搜索、图、最短路、堆(优先队列) | 中等 | +| [0787. K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/cheapest-flights-within-k-stops.md) | 深度优先搜索、广度优先搜索、图、动态规划、最短路、堆(优先队列) | 中等 | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 中等 | +| [1786. 从第一个节点出发到最后一个节点的受限路径数](https://leetcode.cn/problems/number-of-restricted-paths-from-first-to-last-node/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/number-of-restricted-paths-from-first-to-last-node.md) | 图、拓扑排序、动态规划、最短路、堆(优先队列) | 中等 | + + +### 多源最短路径题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0815. 公交路线](https://leetcode.cn/problems/bus-routes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/bus-routes.md) | 广度优先搜索、数组、哈希表 | 困难 | +| [1162. 地图分析](https://leetcode.cn/problems/as-far-from-land-as-possible/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/as-far-from-land-as-possible.md) | 广度优先搜索、数组、动态规划、矩阵 | 中等 | + + +### 次短路径题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2045. 到达目的地的第二短时间](https://leetcode.cn/problems/second-minimum-time-to-reach-destination/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/second-minimum-time-to-reach-destination.md) | 广度优先搜索、图、最短路 | 困难 | + + +### 差分约束系统题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0995. K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md) | 位运算、队列、数组、前缀和、滑动窗口 | 困难 | +| [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/corporate-flight-bookings.md) | 数组、前缀和 | 中等 | + + +### 二分图基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/is-graph-bipartite.md) | 深度优先搜索、广度优先搜索、并查集、图 | 中等 | + + +### 二分图最大匹配题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [LCP 04. 覆盖](https://leetcode.cn/problems/broken-board-dominoes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCP/broken-board-dominoes.md) | 位运算、图、数组、动态规划、状态压缩 | 困难 | +| [1947. 最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1595. 连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | + + +## 第 7 章 基础算法 + +### 枚举算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0204. 计数质数](https://leetcode.cn/problems/count-primes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-primes.md) | 数组、数学、枚举、数论 | 中等 | +| [2427. 公因子的数目](https://leetcode.cn/problems/number-of-common-factors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2400-2499/number-of-common-factors.md) | 数学、枚举、数论 | 简单 | +| [1925. 统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/count-square-sum-triples.md) | 数学、枚举 | 简单 | +| [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md) | 数组 | 简单 | +| [1620. 网络信号最好的坐标](https://leetcode.cn/problems/coordinate-with-maximum-network-quality/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/coordinate-with-maximum-network-quality.md) | 数组、枚举 | 中等 | +| [LCR 180. 文件组合](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md) | 数学、双指针、枚举 | 简单 | +| [0800. 相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/similar-rgb-color.md) | 数学、字符串、枚举 | 简单 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subarray-sum-equals-k.md) | 数组、哈希表、前缀和 | 中等 | + + +### 递归算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) | 双指针、字符串 | 简单 | +| [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/swap-nodes-in-pairs.md) | 递归、链表 | 中等 | +| [0118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle.md) | 数组、动态规划 | 简单 | +| [0119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle-ii.md) | 数组、动态规划 | 简单 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) | 递归、数学 | 中等 | +| [0779. 第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/k-th-symbol-in-grammar.md) | 位运算、递归、数学 | 中等 | +| [0095. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees-ii.md) | 树、二叉搜索树、动态规划、回溯、二叉树 | 中等 | +| [LCR 187. 破冰游戏](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md) | 递归、数学 | 简单 | + + +### 分治算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0241. 为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/different-ways-to-add-parentheses.md) | 递归、记忆化搜索、数学、字符串、动态规划 | 中等 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | +| [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) | 递归、数学 | 中等 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | +| [LCR 152. 验证二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md) | 栈、树、二叉搜索树、递归、数组、二叉树、单调栈 | 中等 | + + +### 回溯算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0046. 全排列](https://leetcode.cn/problems/permutations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) | 数组、回溯 | 中等 | +| [0047. 全排列 II](https://leetcode.cn/problems/permutations-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations-ii.md) | 数组、回溯、排序 | 中等 | +| [0037. 解数独](https://leetcode.cn/problems/sudoku-solver/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sudoku-solver.md) | 数组、哈希表、回溯、矩阵 | 困难 | +| [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) | 字符串、动态规划、回溯 | 中等 | +| [0017. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md) | 哈希表、字符串、回溯 | 中等 | +| [0784. 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/letter-case-permutation.md) | 位运算、字符串、回溯 | 中等 | +| [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) | 数组、回溯 | 中等 | +| [0040. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum-ii.md) | 数组、回溯 | 中等 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0090. 子集 II](https://leetcode.cn/problems/subsets-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets-ii.md) | 位运算、数组、回溯 | 中等 | +| [0473. 火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/matchsticks-to-square.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1593. 拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md) | 哈希表、字符串、回溯 | 中等 | +| [1079. 活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/letter-tile-possibilities.md) | 哈希表、字符串、回溯、计数 | 中等 | +| [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/restore-ip-addresses.md) | 字符串、回溯 | 中等 | +| [0079. 单词搜索](https://leetcode.cn/problems/word-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/word-search.md) | 深度优先搜索、数组、字符串、回溯、矩阵 | 中等 | +| [0679. 24 点游戏](https://leetcode.cn/problems/24-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/24-game.md) | 数组、数学、回溯 | 困难 | + + +### 贪心算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0455. 分发饼干](https://leetcode.cn/problems/assign-cookies/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/assign-cookies.md) | 贪心、数组、双指针、排序 | 简单 | +| [0860. 柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/lemonade-change.md) | 贪心、数组 | 简单 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0435. 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-overlapping-intervals.md) | 贪心、数组、动态规划、排序 | 中等 | +| [0452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md) | 贪心、数组、排序 | 中等 | +| [0055. 跳跃游戏](https://leetcode.cn/problems/jump-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game.md) | 贪心、数组、动态规划 | 中等 | +| [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0561. 数组拆分](https://leetcode.cn/problems/array-partition/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/array-partition.md) | 贪心、数组、计数排序、排序 | 简单 | +| [1710. 卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-units-on-a-truck.md) | 贪心、数组、排序 | 简单 | +| [1217. 玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md) | 贪心、数组、数学 | 简单 | +| [1247. 交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md) | 贪心、数学、字符串 | 中等 | +| [1400. 构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/construct-k-palindrome-strings.md) | 贪心、哈希表、字符串、计数 | 中等 | +| [0921. 使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md) | 栈、贪心、字符串 | 中等 | +| [1029. 两地调度](https://leetcode.cn/problems/two-city-scheduling/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-city-scheduling.md) | 贪心、数组、排序 | 中等 | +| [1605. 给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md) | 贪心、数组、矩阵 | 中等 | +| [0135. 分发糖果](https://leetcode.cn/problems/candy/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/candy.md) | 贪心、数组 | 困难 | +| [0134. 加油站](https://leetcode.cn/problems/gas-station/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/gas-station.md) | 贪心、数组 | 中等 | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0376. 摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/wiggle-subsequence.md) | 贪心、数组、动态规划 | 中等 | +| [0738. 单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/monotone-increasing-digits.md) | 贪心、数学 | 中等 | +| [0402. 移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/remove-k-digits.md) | 栈、贪心、字符串、单调栈 | 中等 | +| [0861. 翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/score-after-flipping-matrix.md) | 贪心、位运算、数组、矩阵 | 中等 | +| [0670. 最大交换](https://leetcode.cn/problems/maximum-swap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-swap.md) | 贪心、数学 | 中等 | + + +### 位运算题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0504. 七进制数](https://leetcode.cn/problems/base-7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/base-7.md) | 数学、字符串 | 简单 | +| [0405. 数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md) | 位运算、数学、字符串 | 简单 | +| [0190. 颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-bits.md) | 位运算、分治 | 简单 | +| [1009. 十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/complement-of-base-10-integer.md) | 位运算 | 简单 | +| [0191. 位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/number-of-1-bits.md) | 位运算、分治 | 简单 | +| [0371. 两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sum-of-two-integers.md) | 位运算、数学 | 中等 | +| [0089. 格雷编码](https://leetcode.cn/problems/gray-code/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/gray-code.md) | 位运算、数学、回溯 | 中等 | +| [0201. 数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md) | 位运算 | 中等 | +| [0338. 比特位计数](https://leetcode.cn/problems/counting-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/counting-bits.md) | 位运算、动态规划 | 简单 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0137. 只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number-ii.md) | 位运算、数组 | 中等 | +| [0260. 只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/single-number-iii.md) | 位运算、数组 | 中等 | +| [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | +| [1349. 参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/maximum-students-taking-exam.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | +| [0645. 错误的集合](https://leetcode.cn/problems/set-mismatch/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/set-mismatch.md) | 位运算、数组、哈希表、排序 | 简单 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0090. 子集 II](https://leetcode.cn/problems/subsets-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets-ii.md) | 位运算、数组、回溯 | 中等 | + + +## 第 8 章 动态规划 + +### 动态规划基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) | 数学、动态规划、组合数学 | 中等 | + + +### 记忆化搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md) | 数学、动态规划、博弈 | 中等 | +| [0494. 目标和](https://leetcode.cn/problems/target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) | 数组、动态规划、回溯 | 中等 | +| [0576. 出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/out-of-boundary-paths.md) | 动态规划 | 中等 | +| [0087. 扰乱字符串](https://leetcode.cn/problems/scramble-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/scramble-string.md) | 字符串、动态规划 | 困难 | +| [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/frog-jump.md) | 数组、动态规划 | 困难 | +| [0552. 学生出勤记录 II](https://leetcode.cn/problems/student-attendance-record-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/student-attendance-record-ii.md) | 动态规划 | 困难 | +| [0913. 猫和老鼠](https://leetcode.cn/problems/cat-and-mouse/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/cat-and-mouse.md) | 图、拓扑排序、记忆化搜索、数学、动态规划、博弈 | 困难 | +| [0329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | + + +### 线性 DP 题目 + +#### 单串线性 DP 问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) | 数组、二分查找、动态规划 | 中等 | +| [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) | 树状数组、线段树、数组、动态规划 | 中等 | +| [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/russian-doll-envelopes.md) | 数组、二分查找、动态规划、排序 | 困难 | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-product-subarray.md) | 数组、动态规划 | 中等 | +| [0918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/maximum-sum-circular-subarray.md) | 队列、数组、分治、动态规划、单调队列 | 中等 | +| [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) | 数组、动态规划 | 中等 | +| [0213. 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/house-robber-ii.md) | 数组、动态规划 | 中等 | +| [0740. 删除并获得点数](https://leetcode.cn/problems/delete-and-earn/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/delete-and-earn.md) | 数组、哈希表、动态规划 | 中等 | +| [1388. 3n 块披萨](https://leetcode.cn/problems/pizza-with-3n-slices/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/pizza-with-3n-slices.md) | 贪心、数组、动态规划、堆(优先队列) | 困难 | +| [0873. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md) | 数组、哈希表、动态规划 | 中等 | +| [1027. 最长等差数列](https://leetcode.cn/problems/longest-arithmetic-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/longest-arithmetic-subsequence.md) | 数组、哈希表、二分查找、动态规划 | 中等 | +| [1055. 形成字符串的最短路径](https://leetcode.cn/problems/shortest-way-to-form-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/shortest-way-to-form-string.md) | 贪心、双指针、字符串、二分查找 | 中等 | +| [0368. 最大整除子集](https://leetcode.cn/problems/largest-divisible-subset/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/largest-divisible-subset.md) | 数组、数学、动态规划、排序 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0413. 等差数列划分](https://leetcode.cn/problems/arithmetic-slices/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/arithmetic-slices.md) | 数组、动态规划、滑动窗口 | 中等 | +| [0091. 解码方法](https://leetcode.cn/problems/decode-ways/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/decode-ways.md) | 字符串、动态规划 | 中等 | +| [0639. 解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/decode-ways-ii.md) | 字符串、动态规划 | 困难 | +| [0132. 分割回文串 II](https://leetcode.cn/problems/palindrome-partitioning-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/palindrome-partitioning-ii.md) | 字符串、动态规划 | 困难 | +| [1220. 统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/count-vowels-permutation.md) | 动态规划 | 困难 | +| [0338. 比特位计数](https://leetcode.cn/problems/counting-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/counting-bits.md) | 位运算、动态规划 | 简单 | +| [0801. 使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md) | 数组、动态规划 | 困难 | +| [0871. 最低加油次数](https://leetcode.cn/problems/minimum-number-of-refueling-stops/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/minimum-number-of-refueling-stops.md) | 贪心、数组、动态规划、堆(优先队列) | 困难 | +| [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0813. 最大平均值和的分组](https://leetcode.cn/problems/largest-sum-of-averages/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/largest-sum-of-averages.md) | 数组、动态规划、前缀和 | 中等 | +| [0887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/super-egg-drop.md) | 数学、二分查找、动态规划 | 困难 | +| [0256. 粉刷房子](https://leetcode.cn/problems/paint-house/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/paint-house.md) | 数组、动态规划 | 中等 | +| [0265. 粉刷房子 II](https://leetcode.cn/problems/paint-house-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/paint-house-ii.md) | 数组、动态规划 | 困难 | +| [1473. 粉刷房子 III](https://leetcode.cn/problems/paint-house-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/paint-house-iii.md) | 数组、动态规划 | 困难 | +| [0975. 奇偶跳](https://leetcode.cn/problems/odd-even-jump/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/odd-even-jump.md) | 栈、数组、动态规划、有序集合、排序、单调栈 | 困难 | +| [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/frog-jump.md) | 数组、动态规划 | 困难 | +| [1478. 安排邮筒](https://leetcode.cn/problems/allocate-mailboxes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/allocate-mailboxes.md) | 数组、数学、动态规划、排序 | 困难 | +| [1230. 抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/toss-strange-coins.md) | 数组、数学、动态规划、概率与统计 | 中等 | +| [0410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/split-array-largest-sum.md) | 贪心、数组、二分查找、动态规划、前缀和 | 困难 | +| [1751. 最多可以参加的会议数目 II](https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-number-of-events-that-can-be-attended-ii.md) | 数组、二分查找、动态规划、排序 | 困难 | +| [1787. 使所有区间的异或结果为零](https://leetcode.cn/problems/make-the-xor-of-all-segments-equal-to-zero/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/make-the-xor-of-all-segments-equal-to-zero.md) | 位运算、数组、动态规划 | 困难 | +| [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md) | 数组、动态规划 | 简单 | +| [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md) | 数组、动态规划 | 困难 | +| [0188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md) | 数组、动态规划 | 困难 | +| [0309. 买卖股票的最佳时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md) | 数组、动态规划 | 中等 | +| [0714. 买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md) | 贪心、数组、动态规划 | 中等 | + + +#### 双串线性 DP 问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) | 字符串、动态规划 | 中等 | +| [0712. 两个字符串的最小ASCII删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-ascii-delete-sum-for-two-strings.md) | 字符串、动态规划 | 中等 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/delete-operation-for-two-strings.md) | 字符串、动态规划 | 中等 | +| [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) | 字符串、动态规划 | 中等 | +| [0044. 通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/wildcard-matching.md) | 贪心、递归、字符串、动态规划 | 困难 | +| [0010. 正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/regular-expression-matching.md) | 递归、字符串、动态规划 | 困难 | +| [0097. 交错字符串](https://leetcode.cn/problems/interleaving-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/interleaving-string.md) | 字符串、动态规划 | 中等 | +| [0115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/distinct-subsequences.md) | 字符串、动态规划 | 困难 | +| [0087. 扰乱字符串](https://leetcode.cn/problems/scramble-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/scramble-string.md) | 字符串、动态规划 | 困难 | + + +#### 矩阵线性 DP 问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle.md) | 数组、动态规划 | 简单 | +| [0119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle-ii.md) | 数组、动态规划 | 简单 | +| [0120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/triangle.md) | 数组、动态规划 | 中等 | +| [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) | 数组、动态规划、矩阵 | 中等 | +| [0174. 地下城游戏](https://leetcode.cn/problems/dungeon-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/dungeon-game.md) | 数组、动态规划、矩阵 | 困难 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0931. 下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-falling-path-sum.md) | 数组、动态规划、矩阵 | 中等 | +| [0576. 出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/out-of-boundary-paths.md) | 动态规划 | 中等 | +| [0085. 最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximal-rectangle.md) | 栈、数组、动态规划、矩阵、单调栈 | 困难 | +| [0363. 矩形区域不超过 K 的最大数值和](https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/max-sum-of-rectangle-no-larger-than-k.md) | 数组、二分查找、矩阵、有序集合、前缀和 | 困难 | +| [面试题 17.24. 最大子矩阵](https://leetcode.cn/problems/max-submatrix-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/max-submatrix-lcci.md) | 数组、动态规划、矩阵、前缀和 | 困难 | +| [1444. 切披萨的方案数](https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-ways-of-cutting-a-pizza.md) | 记忆化搜索、数组、动态规划、矩阵、前缀和 | 困难 | + + +#### 无串线性 DP 问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0650. 两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/2-keys-keyboard.md) | 数学、动态规划 | 中等 | +| [0264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/ugly-number-ii.md) | 哈希表、数学、动态规划、堆(优先队列) | 中等 | +| [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) | 广度优先搜索、数学、动态规划 | 中等 | +| [0343. 整数拆分](https://leetcode.cn/problems/integer-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) | 数学、动态规划 | 中等 | + + +### 背包问题题目 + +#### 0-1 背包问题题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/partition-equal-subset-sum.md) | 数组、动态规划 | 中等 | +| [0494. 目标和](https://leetcode.cn/problems/target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) | 数组、动态规划、回溯 | 中等 | +| [1049. 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/last-stone-weight-ii.md) | 数组、动态规划 | 中等 | + + +#### 完全背包问题题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) | 广度优先搜索、数学、动态规划 | 中等 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/coin-change-ii.md) | 数组、动态规划 | 中等 | +| [0139. 单词拆分](https://leetcode.cn/problems/word-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | +| [0377. 组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/combination-sum-iv.md) | 数组、动态规划 | 中等 | +| [0638. 大礼包](https://leetcode.cn/problems/shopping-offers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/shopping-offers.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | +| [1449. 数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md) | 数组、动态规划 | 困难 | + + +#### 多重背包问题题目 + +#### 分组背包问题题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1155. 掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md) | 动态规划 | 中等 | +| [2585. 获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/number-of-ways-to-earn-points.md) | 数组、动态规划 | 困难 | + + +#### 多维背包问题题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0474. 一和零](https://leetcode.cn/problems/ones-and-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/ones-and-zeroes.md) | 数组、字符串、动态规划 | 中等 | +| [0879. 盈利计划](https://leetcode.cn/problems/profitable-schemes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/profitable-schemes.md) | 数组、动态规划 | 困难 | +| [1995. 统计特殊四元组](https://leetcode.cn/problems/count-special-quadruplets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/count-special-quadruplets.md) | 数组、哈希表、枚举 | 简单 | + + +### 区间 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0486. 预测赢家](https://leetcode.cn/problems/predict-the-winner/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/predict-the-winner.md) | 递归、数组、数学、动态规划、博弈 | 中等 | +| [0312. 戳气球](https://leetcode.cn/problems/burst-balloons/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/burst-balloons.md) | 数组、动态规划 | 困难 | +| [0877. 石子游戏](https://leetcode.cn/problems/stone-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/stone-game.md) | 数组、数学、动态规划、博弈 | 中等 | +| [1000. 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md) | 数组、动态规划、前缀和 | 困难 | +| [1547. 切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md) | 数组、动态规划、排序 | 困难 | +| [0664. 奇怪的打印机](https://leetcode.cn/problems/strange-printer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/strange-printer.md) | 字符串、动态规划 | 困难 | +| [1039. 多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md) | 数组、动态规划 | 中等 | +| [0546. 移除盒子](https://leetcode.cn/problems/remove-boxes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/remove-boxes.md) | 记忆化搜索、数组、动态规划 | 困难 | +| [0375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md) | 数学、动态规划、博弈 | 中等 | +| [0678. 有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-parenthesis-string.md) | 栈、贪心、字符串、动态规划 | 中等 | +| [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) | 双指针、字符串、动态规划 | 中等 | +| [0516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/longest-palindromic-subsequence.md) | 字符串、动态规划 | 中等 | +| [0730. 统计不同回文子序列](https://leetcode.cn/problems/count-different-palindromic-subsequences/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/count-different-palindromic-subsequences.md) | 字符串、动态规划 | 困难 | +| [2104. 子数组范围和](https://leetcode.cn/problems/sum-of-subarray-ranges/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/sum-of-subarray-ranges.md) | 栈、数组、单调栈 | 中等 | + + +### 树形 DP 题目 + +#### 固定根的树形 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [1245. 树的直径](https://leetcode.cn/problems/tree-diameter/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/tree-diameter.md) | 树、深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md) | 树、深度优先搜索、图、拓扑排序、数组、字符串 | 困难 | +| [0687. 最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-univalue-path.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0337. 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/house-robber-iii.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | +| [0333. 最大二叉搜索子树](https://leetcode.cn/problems/largest-bst-subtree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/largest-bst-subtree.md) | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 中等 | +| [1617. 统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md) | 位运算、树、动态规划、状态压缩、枚举 | 困难 | +| [2538. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md) | 树、深度优先搜索、数组、动态规划 | 困难 | +| [1569. 将子数组重新排序得到同一个二叉搜索树的方案数](https://leetcode.cn/problems/number-of-ways-to-reorder-array-to-get-same-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/number-of-ways-to-reorder-array-to-get-same-bst.md) | 树、并查集、二叉搜索树、记忆化搜索、数组、数学、分治、动态规划、二叉树、组合数学 | 困难 | +| [1372. 二叉树中的最长交错路径](https://leetcode.cn/problems/longest-zigzag-path-in-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/longest-zigzag-path-in-a-binary-tree.md) | 树、深度优先搜索、动态规划、二叉树 | 中等 | +| [1373. 二叉搜索子树的最大键值和](https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/maximum-sum-bst-in-binary-tree.md) | 树、深度优先搜索、二叉搜索树、动态规划、二叉树 | 困难 | +| [0968. 监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/binary-tree-cameras.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [1273. 删除树节点](https://leetcode.cn/problems/delete-tree-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/delete-tree-nodes.md) | 树、深度优先搜索、广度优先搜索、数组 | 中等 | +| [1519. 子树中标签相同的节点数](https://leetcode.cn/problems/number-of-nodes-in-the-sub-tree-with-the-same-label/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/number-of-nodes-in-the-sub-tree-with-the-same-label.md) | 树、深度优先搜索、广度优先搜索、哈希表、计数 | 中等 | + + +#### 不定根的树形 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/minimum-height-trees.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0834. 树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/sum-of-distances-in-tree.md) | 树、深度优先搜索、图、动态规划 | 困难 | +| [2581. 统计可能的树根数目](https://leetcode.cn/problems/count-number-of-possible-root-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/count-number-of-possible-root-nodes.md) | 树、深度优先搜索、数组、哈希表、动态规划 | 困难 | + + +### 状态压缩 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1879. 两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md) | 位运算、数组、动态规划、状态压缩 | 困难 | +| [2172. 数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/maximum-and-sum-of-array.md) | 位运算、数组、动态规划、状态压缩 | 困难 | +| [1947. 最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1595. 连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | +| [1494. 并行课程 II](https://leetcode.cn/problems/parallel-courses-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/parallel-courses-ii.md) | 位运算、图、动态规划、状态压缩 | 困难 | +| [1655. 分配重复整数](https://leetcode.cn/problems/distribute-repeating-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/distribute-repeating-integers.md) | 位运算、数组、动态规划、回溯、状态压缩 | 困难 | +| [1986. 完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [1434. 每个人戴不同帽子的方案数](https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-ways-to-wear-different-hats-to-each-other.md) | 位运算、数组、动态规划、状态压缩 | 困难 | +| [1799. N 次操作后的最大分数和](https://leetcode.cn/problems/maximize-score-after-n-operations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximize-score-after-n-operations.md) | 位运算、数组、数学、动态规划、回溯、状态压缩、数论 | 困难 | +| [1681. 最小不兼容性](https://leetcode.cn/problems/minimum-incompatibility/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-incompatibility.md) | 位运算、数组、动态规划、状态压缩 | 困难 | +| [0526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/beautiful-arrangement.md) | 位运算、数组、动态规划、回溯、状态压缩 | 中等 | +| [0351. 安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/android-unlock-patterns.md) | 位运算、动态规划、回溯、状态压缩 | 中等 | +| [0464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/can-i-win.md) | 位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 | 中等 | +| [0847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md) | 位运算、广度优先搜索、图、动态规划、状态压缩 | 困难 | +| [0638. 大礼包](https://leetcode.cn/problems/shopping-offers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/shopping-offers.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | +| [1994. 好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/the-number-of-good-subsets.md) | 位运算、数组、数学、动态规划、状态压缩 | 困难 | +| [1349. 参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/maximum-students-taking-exam.md) | 位运算、数组、动态规划、状态压缩、矩阵 | 困难 | +| [0698. 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md) | 位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 | 中等 | +| [0943. 最短超级串](https://leetcode.cn/problems/find-the-shortest-superstring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/find-the-shortest-superstring.md) | 位运算、数组、字符串、动态规划、状态压缩 | 困难 | +| [0691. 贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/stickers-to-spell-word.md) | 位运算、记忆化搜索、数组、哈希表、字符串、动态规划、回溯、状态压缩 | 困难 | +| [0982. 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md) | 位运算、数组、哈希表 | 困难 | + + +### 计数 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) | 数学、动态规划、组合数学 | 中等 | +| [0063. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths-ii.md) | 数组、动态规划、矩阵 | 中等 | +| [0343. 整数拆分](https://leetcode.cn/problems/integer-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) | 数学、动态规划 | 中等 | +| [0096. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | +| [1259. 不相交的握手](https://leetcode.cn/problems/handshakes-that-dont-cross/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/handshakes-that-dont-cross.md) | 数学、动态规划 | 困难 | +| [0790. 多米诺和托米诺平铺](https://leetcode.cn/problems/domino-and-tromino-tiling/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/domino-and-tromino-tiling.md) | 动态规划 | 中等 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/min-cost-climbing-stairs.md) | 数组、动态规划 | 简单 | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) | 记忆化搜索、数学、动态规划 | 简单 | + + +### 数位 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [2376. 统计特殊整数](https://leetcode.cn/problems/count-special-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2300-2399/count-special-integers.md) | 数学、动态规划 | 困难 | +| [0357. 统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-numbers-with-unique-digits.md) | 数学、动态规划、回溯 | 中等 | +| [1012. 至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/numbers-with-repeated-digits.md) | 数学、动态规划 | 困难 | +| [0902. 最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md) | 数组、数学、字符串、二分查找、动态规划 | 困难 | +| [0788. 旋转数字](https://leetcode.cn/problems/rotated-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotated-digits.md) | 数学、动态规划 | 中等 | +| [0600. 不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md) | 动态规划 | 困难 | +| [0233. 数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-digit-one.md) | 递归、数学、动态规划 | 困难 | +| [2719. 统计整数数目](https://leetcode.cn/problems/count-of-integers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2700-2799/count-of-integers.md) | 数学、字符串、动态规划 | 困难 | +| [0248. 中心对称数 III](https://leetcode.cn/problems/strobogrammatic-number-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/strobogrammatic-number-iii.md) | 递归、数组、字符串 | 困难 | +| [1088. 易混淆数 II](https://leetcode.cn/problems/confusing-number-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/confusing-number-ii.md) | 数学、回溯 | 困难 | +| [1067. 范围内的数字计数](https://leetcode.cn/problems/digit-count-in-range/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/digit-count-in-range.md) | 数学、动态规划 | 困难 | +| [1742. 盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md) | 哈希表、数学、计数 | 简单 | +| [面试题 17.06. 2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/number-of-2s-in-range-lcci.md) | 递归、数学、动态规划 | 困难 | + + +### 概率 DP 题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0688. 骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/knight-probability-in-chessboard.md) | 动态规划 | 中等 | +| [0808. 分汤](https://leetcode.cn/problems/soup-servings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/soup-servings.md) | 数学、动态规划、概率与统计 | 中等 | +| [0837. 新 21 点](https://leetcode.cn/problems/new-21-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/new-21-game.md) | 数学、动态规划、滑动窗口、概率与统计 | 中等 | +| [1230. 抛掷硬币](https://leetcode.cn/problems/toss-strange-coins/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/toss-strange-coins.md) | 数组、数学、动态规划、概率与统计 | 中等 | +| [1467. 两个盒子中球的颜色数相同的概率](https://leetcode.cn/problems/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/probability-of-a-two-boxes-having-the-same-number-of-distinct-balls.md) | 数组、数学、动态规划、回溯、组合数学、概率与统计 | 困难 | +| [1227. 飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/airplane-seat-assignment-probability.md) | 脑筋急转弯、数学、动态规划、概率与统计 | 中等 | +| [1377. T 秒后青蛙的位置](https://leetcode.cn/problems/frog-position-after-t-seconds/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/frog-position-after-t-seconds.md) | 树、深度优先搜索、广度优先搜索、图 | 困难 | +| [LCR 185. 统计结果概率](https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/nge-tou-zi-de-dian-shu-lcof.md) | 数学、动态规划、概率与统计 | 中等 | + + diff --git a/docs/00_preface/00_07_interview_100_list.md b/docs/00_preface/00_07_interview_100_list.md new file mode 100644 index 00000000..18d65b8f --- /dev/null +++ b/docs/00_preface/00_07_interview_100_list.md @@ -0,0 +1,419 @@ +# LeetCode 面试最常考 100 题(按分类排序) + +## 第 1 章 数组 + +### 数组基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) | 数组、矩阵、模拟 | 中等 | +| [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) | 数组、数学、矩阵 | 中等 | + + +### 排序算法题目 + +#### 选择排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | + + +#### 希尔排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 归并排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | + + +#### 快速排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | + + +#### 堆排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | + + +#### 计数排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 桶排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 其他排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0179. 最大数](https://leetcode.cn/problems/largest-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/largest-number.md) | 贪心、数组、字符串、排序 | 中等 | + + +### 二分查找题目 + +#### 二分下标题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0704. 二分查找](https://leetcode.cn/problems/binary-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) | 数组、二分查找 | 简单 | +| [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md) | 数组、二分查找 | 中等 | +| [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-peak-element.md) | 数组、二分查找 | 中等 | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/search-a-2d-matrix-ii.md) | 数组、二分查找、分治、矩阵 | 中等 | + + +#### 二分答案题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) | 数学、二分查找 | 简单 | + + +### 双指针题目 + +#### 对撞指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | + + +#### 快慢指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | + + +#### 分离双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | + + +### 滑动窗口题目 + +#### 固定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | + + +#### 不定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-window-substring.md) | 哈希表、字符串、滑动窗口 | 困难 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | + + +## 第 2 章 链表 + +### 链表基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md) | 链表 | 简单 | +| [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md) | 链表、双指针 | 中等 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-nodes-in-k-group.md) | 递归、链表 | 困难 | +| [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) | 栈、递归、链表、双指针 | 简单 | + + +### 链表排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0148. 排序链表](https://leetcode.cn/problems/sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) | 链表、双指针、分治、排序、归并排序 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | + + +### 链表双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) | 哈希表、链表、双指针 | 简单 | +| [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) | 哈希表、链表、双指针 | 中等 | +| [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/intersection-of-two-linked-lists.md) | 哈希表、链表、双指针 | 简单 | +| [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) | 链表、双指针 | 中等 | +| [LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md) | 链表、双指针 | 简单 | +| [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reorder-list.md) | 栈、递归、链表、双指针 | 中等 | +| [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-two-numbers.md) | 递归、链表、数学 | 中等 | + + +## 第 3 章 栈、队列、哈希表 + +### 栈基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0155. 最小栈](https://leetcode.cn/problems/min-stack/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) | 栈、设计 | 中等 | +| [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) | 栈、字符串 | 简单 | +| [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) | 栈、数学、字符串 | 中等 | +| [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-queue-using-stacks.md) | 栈、设计、队列 | 简单 | +| [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) | 栈、递归、字符串 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | + + +### 单调栈题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | + + +### 队列基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) | 栈、设计、队列 | 简单 | + + +### 优先队列题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | + + +### 哈希表题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/first-missing-positive.md) | 数组、哈希表 | 困难 | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | + + +## 第 4 章 字符串 + +### 字符串基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) | 双指针、字符串、动态规划 | 中等 | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | +| [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-words-in-a-string.md) | 双指针、字符串 | 中等 | +| [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/multiply-strings.md) | 数学、字符串、模拟 | 中等 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | + + +## 第 5 章 树 + +### 二叉树的遍历题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0112. 路径总和](https://leetcode.cn/problems/path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum-ii.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | +| [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/symmetric-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | + + +### 二叉树的还原题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | + + +### 二叉搜索树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/balanced-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | + + +### 并查集题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | + + +## 第 6 章 图论 + +### 深度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | + + +### 广度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | + + +## 第 7 章 基础算法 + +### 枚举算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | + + +### 递归算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/swap-nodes-in-pairs.md) | 递归、链表 | 中等 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | + + +### 分治算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | + + +### 回溯算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0046. 全排列](https://leetcode.cn/problems/permutations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) | 数组、回溯 | 中等 | +| [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) | 字符串、动态规划、回溯 | 中等 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) | 数组、回溯 | 中等 | +| [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/restore-ip-addresses.md) | 字符串、回溯 | 中等 | + + +### 贪心算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) | 贪心、数组、动态规划 | 中等 | + + +### 位运算题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | + + +## 第 8 章 动态规划 + +### 动态规划题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md) | 数组、动态规划 | 简单 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) | 数组、二分查找、动态规划 | 中等 | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) | 字符串、动态规划 | 中等 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) | 数组、动态规划、矩阵 | 中等 | +| [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) | 字符串、动态规划 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) | 数学、动态规划、组合数学 | 中等 | +| [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-product-subarray.md) | 数组、动态规划 | 中等 | +| [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) | 数组、动态规划 | 中等 | + + +## 补充题目 + +#### 设计数据结构题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0146. LRU 缓存](https://leetcode.cn/problems/lru-cache/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/lru-cache.md) | 设计、哈希表、链表、双向链表 | 中等 | + + +#### 模拟题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0008. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/string-to-integer-atoi.md) | 字符串 | 中等 | +| [0165. 比较版本号](https://leetcode.cn/problems/compare-version-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/compare-version-numbers.md) | 双指针、字符串 | 中等 | +| [0468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/validate-ip-address.md) | 字符串 | 中等 | + + +#### 思维锻炼题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0031. 下一个排列](https://leetcode.cn/problems/next-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/next-permutation.md) | 数组、双指针 | 中等 | +| [0470. 用 Rand7() 实现 Rand10()](https://leetcode.cn/problems/implement-rand10-using-rand7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/implement-rand10-using-rand7.md) | 数学、拒绝采样、概率与统计、随机化 | 中等 | + + + +## 参考资料 + +- 【清单】[CodeTop 企业题库](https://codetop.cc/home) diff --git a/docs/00_preface/00_08_interview_200_list.md b/docs/00_preface/00_08_interview_200_list.md new file mode 100644 index 00000000..32360029 --- /dev/null +++ b/docs/00_preface/00_08_interview_200_list.md @@ -0,0 +1,598 @@ +# LeetCode 面试最常考 200 题(按分类排序) + +## 第 1 章 数组 + +### 数组基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0189. 轮转数组](https://leetcode.cn/problems/rotate-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/rotate-array.md) | 数组、数学、双指针 | 中等 | +| [0498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/diagonal-traverse.md) | 数组、矩阵、模拟 | 中等 | +| [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) | 数组、数学、矩阵 | 中等 | +| [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) | 数组、矩阵、模拟 | 中等 | +| [0059. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix-ii.md) | 数组、矩阵、模拟 | 中等 | + + +### 排序算法题目 + +#### 冒泡排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | + + +#### 选择排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | + + +#### 插入排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) | 数组、双指针、排序 | 中等 | + + +#### 希尔排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 归并排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | +| [LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | + + +#### 快速排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | + + +#### 堆排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | +| [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) | 数组、分治、快速选择、排序、堆(优先队列) | 中等 | +| [LCR 159. 库存管理 III](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md) | 数组、分治、快速选择、排序、堆(优先队列) | 简单 | + + +#### 计数排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 桶排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) | 数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 | 中等 | + + +#### 基数排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) | 数组、桶排序、基数排序、排序 | 中等 | + + +#### 其他排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0179. 最大数](https://leetcode.cn/problems/largest-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/largest-number.md) | 贪心、数组、字符串、排序 | 中等 | +| [0384. 打乱数组](https://leetcode.cn/problems/shuffle-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/shuffle-an-array.md) | 设计、数组、数学、随机化 | 中等 | +| [LCR 164. 破解闯关密码](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md) | 贪心、字符串、排序 | 中等 | + + +### 二分查找题目 + +#### 二分下标题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0704. 二分查找](https://leetcode.cn/problems/binary-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) | 数组、二分查找 | 简单 | +| [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md) | 数组、二分查找 | 中等 | +| [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md) | 数组、二分查找 | 困难 | +| [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) | 数组、二分查找 | 中等 | +| [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-peak-element.md) | 数组、二分查找 | 中等 | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0074. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-a-2d-matrix.md) | 数组、二分查找、矩阵 | 中等 | +| [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/search-a-2d-matrix-ii.md) | 数组、二分查找、分治、矩阵 | 中等 | + + +#### 二分答案题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) | 数学、二分查找 | 简单 | +| [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-the-duplicate-number.md) | 位运算、数组、双指针、二分查找 | 中等 | +| [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) | 递归、数学 | 中等 | +| [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/nth-digit.md) | 数学、二分查找 | 中等 | + + +#### 复杂的二分查找问题 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) | 数组、哈希表、双指针、二分查找、排序 | 简单 | + + +### 双指针题目 + +#### 对撞指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0611. 有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-triangle-number.md) | 贪心、数组、双指针、二分查找、排序 | 中等 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0016. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum-closest.md) | 数组、双指针、排序 | 中等 | +| [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) | 双指针、字符串 | 简单 | +| [0011. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/container-with-most-water.md) | 贪心、数组、双指针 | 中等 | +| [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) | 数组、双指针、排序 | 中等 | +| [LCR 139. 训练计划 I](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md) | 数组、双指针、排序 | 简单 | +| [0443. 压缩字符串](https://leetcode.cn/problems/string-compression/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/string-compression.md) | 双指针、字符串 | 中等 | + + +#### 快慢指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0026. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md) | 数组、双指针 | 简单 | +| [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) | 数组、双指针 | 简单 | +| [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) | 数组、双指针、排序 | 简单 | + + +#### 分离双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | + + +### 滑动窗口题目 + +#### 固定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | + + +#### 不定长度窗口题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-window-substring.md) | 哈希表、字符串、滑动窗口 | 困难 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | +| [0862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md) | 队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/max-consecutive-ones-iii.md) | 数组、二分查找、前缀和、滑动窗口 | 中等 | + + +## 02. 链表 + +### 链表基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md) | 链表 | 简单 | +| [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md) | 链表、双指针 | 中等 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-nodes-in-k-group.md) | 递归、链表 | 困难 | +| [0328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/odd-even-linked-list.md) | 链表 | 中等 | +| [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) | 栈、递归、链表、双指针 | 简单 | +| [0138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/copy-list-with-random-pointer.md) | 哈希表、链表 | 中等 | +| [0061. 旋转链表](https://leetcode.cn/problems/rotate-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-list.md) | 链表、双指针 | 中等 | + + +### 链表排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0148. 排序链表](https://leetcode.cn/problems/sort-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) | 链表、双指针、分治、排序、归并排序 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | + + +### 链表双指针题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) | 哈希表、链表、双指针 | 简单 | +| [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) | 哈希表、链表、双指针 | 中等 | +| [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/intersection-of-two-linked-lists.md) | 哈希表、链表、双指针 | 简单 | +| [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) | 链表、双指针 | 中等 | +| [LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md) | 链表、双指针 | 简单 | +| [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reorder-list.md) | 栈、递归、链表、双指针 | 中等 | +| [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-two-numbers.md) | 递归、链表、数学 | 中等 | +| [0445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-two-numbers-ii.md) | 栈、链表、数学 | 中等 | + + +## 第 3 章 栈、队列、哈希表 + +### 栈基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md) | 栈、字符串 | 简单 | +| [0155. 最小栈](https://leetcode.cn/problems/min-stack/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) | 栈、设计 | 中等 | +| [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) | 栈、字符串 | 简单 | +| [0224. 基本计算器](https://leetcode.cn/problems/basic-calculator/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator.md) | 栈、递归、数学、字符串 | 困难 | +| [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) | 栈、数学、字符串 | 中等 | +| [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-queue-using-stacks.md) | 栈、设计、队列 | 简单 | +| [LCR 125. 图书整理 II](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md) | 栈、设计、队列 | 简单 | +| [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) | 栈、递归、字符串 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) | 栈、数组、单调栈 | 中等 | +| [0071. 简化路径](https://leetcode.cn/problems/simplify-path/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/simplify-path.md) | 栈、字符串 | 中等 | + + +### 单调栈题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) | 栈、数组、单调栈 | 中等 | +| [0503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/next-greater-element-ii.md) | 栈、数组、单调栈 | 中等 | +| [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) | 栈、数组、双指针、动态规划、单调栈 | 困难 | +| [0085. 最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximal-rectangle.md) | 栈、数组、动态规划、矩阵、单调栈 | 困难 | + + +### 队列基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) | 栈、设计、队列 | 简单 | + + +### 优先队列题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/top-k-frequent-elements.md) | 数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) | 中等 | +| [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) | 队列、数组、滑动窗口、单调队列、堆(优先队列) | 困难 | +| [0295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-median-from-data-stream.md) | 设计、双指针、数据流、排序、堆(优先队列) | 困难 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | + + +### 哈希表题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0015. 三数之和](https://leetcode.cn/problems/3sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) | 数组、双指针、排序 | 中等 | +| [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/first-missing-positive.md) | 数组、哈希表 | 困难 | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/valid-anagram.md) | 哈希表、字符串、排序 | 简单 | +| [0442. 数组中重复的数据](https://leetcode.cn/problems/find-all-duplicates-in-an-array/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/find-all-duplicates-in-an-array.md) | 数组、哈希表 | 中等 | +| [LCR 186. 文物朝代判断](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md) | 数组、排序 | 简单 | +| [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | +| [LCR 120. 寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md) | 数组、哈希表、排序 | 简单 | + + +## 第 4 章 字符串 + +### 字符串基础题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) | 双指针、字符串 | 简单 | +| [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) | 双指针、字符串、动态规划 | 中等 | +| [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) | 哈希表、字符串、滑动窗口 | 中等 | +| [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) | 双指针、字符串 | 简单 | +| [0557. 反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md) | 双指针、字符串 | 简单 | +| [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) | 数学、字符串、模拟 | 简单 | +| [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-words-in-a-string.md) | 双指针、字符串 | 中等 | +| [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/multiply-strings.md) | 数学、字符串、模拟 | 中等 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | + + +### 单模式串匹配题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) | 字符串、字符串匹配 | 简单 | + + +### 字典树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-trie-prefix-tree.md) | 设计、字典树、哈希表、字符串 | 中等 | +| [0440. 字典序的第K小数字](https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/k-th-smallest-in-lexicographical-order.md) | 字典树 | 困难 | + + +## 第 5 章 树 + +### 二叉树的遍历题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/symmetric-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0112. 路径总和](https://leetcode.cn/problems/path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum-ii.md) | 树、深度优先搜索、回溯、二叉树 | 中等 | +| [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md) | 树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 | 困难 | +| [0114. 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/flatten-binary-tree-to-linked-list.md) | 栈、树、深度优先搜索、链表、二叉树 | 中等 | + + +### 二叉树的还原题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | +| [0106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md) | 树、数组、哈希表、分治、二叉树 | 中等 | + + +### 二叉搜索树题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/delete-node-in-a-bst.md) | 树、二叉搜索树、二叉树 | 中等 | +| [LCR 174. 寻找二叉搜索树中的目标节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 简单 | +| [0230. 二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-smallest-element-in-a-bst.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | +| [0426. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md) | 栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 | 中等 | +| [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/balanced-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | + + +### 并查集题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) | 并查集、数组、哈希表 | 中等 | + + +## 第 6 章 图论 + +### 深度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) | 栈、树、深度优先搜索、二叉树 | 简单 | +| [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md) | 树、深度优先搜索、二叉树 | 中等 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) | 树、深度优先搜索、二叉树 | 简单 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | + + +### 广度优先搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) | 深度优先搜索、广度优先搜索、并查集、数组、矩阵 | 中等 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0207. 课程表](https://leetcode.cn/problems/course-schedule/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | +| [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | +| [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) | 树、广度优先搜索、二叉树 | 中等 | +| [0572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subtree-of-another-tree.md) | 树、深度优先搜索、二叉树、字符串匹配、哈希函数 | 简单 | +| [0100. 相同的树](https://leetcode.cn/problems/same-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md) | 树、广度优先搜索、二叉树 | 中等 | + + +### 图的拓扑排序题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule-ii.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 | + + +## 第 7 章 基础算法 + +### 枚举算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0001. 两数之和](https://leetcode.cn/problems/two-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) | 数组、哈希表 | 简单 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subarray-sum-equals-k.md) | 数组、哈希表、前缀和 | 中等 | + + +### 递归算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/swap-nodes-in-pairs.md) | 递归、链表 | 中等 | +| [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) | 递归、链表 | 简单 | +| [0092. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) | 链表 | 中等 | +| [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) | 递归、链表 | 简单 | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) | 树、深度优先搜索、动态规划、二叉树 | 困难 | +| [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 简单 | +| [LCR 187. 破冰游戏](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md) | 递归、数学 | 简单 | + + +### 分治算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) | 数组、二分查找、分治 | 困难 | +| [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) | 链表、分治、堆(优先队列)、归并排序 | 困难 | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0169. 多数元素](https://leetcode.cn/problems/majority-element/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) | 数组、哈希表、分治、计数、排序 | 简单 | +| [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) | 字典树、数组、字符串 | 简单 | +| [LCR 152. 验证二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md) | 栈、树、二叉搜索树、递归、数组、二叉树、单调栈 | 中等 | + + +### 回溯算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0046. 全排列](https://leetcode.cn/problems/permutations/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) | 数组、回溯 | 中等 | +| [0047. 全排列 II](https://leetcode.cn/problems/permutations-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations-ii.md) | 数组、回溯、排序 | 中等 | +| [0037. 解数独](https://leetcode.cn/problems/sudoku-solver/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sudoku-solver.md) | 数组、哈希表、回溯、矩阵 | 困难 | +| [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) | 字符串、动态规划、回溯 | 中等 | +| [0078. 子集](https://leetcode.cn/problems/subsets/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) | 位运算、数组、回溯 | 中等 | +| [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) | 数组、回溯 | 中等 | +| [0040. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum-ii.md) | 数组、回溯 | 中等 | +| [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/restore-ip-addresses.md) | 字符串、回溯 | 中等 | +| [0079. 单词搜索](https://leetcode.cn/problems/word-search/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/word-search.md) | 深度优先搜索、数组、字符串、回溯、矩阵 | 中等 | +| [0679. 24 点游戏](https://leetcode.cn/problems/24-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/24-game.md) | 数组、数学、回溯 | 困难 | + + +### 贪心算法题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) | 数组、分治、动态规划 | 中等 | +| [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) | 数组、排序 | 中等 | +| [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0055. 跳跃游戏](https://leetcode.cn/problems/jump-game/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game.md) | 贪心、数组、动态规划 | 中等 | +| [0402. 移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/remove-k-digits.md) | 栈、贪心、字符串、单调栈 | 中等 | +| [0135. 分发糖果](https://leetcode.cn/problems/candy/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/candy.md) | 贪心、数组 | 困难 | +| [0134. 加油站](https://leetcode.cn/problems/gas-station/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/gas-station.md) | 贪心、数组 | 中等 | +| [0670. 最大交换](https://leetcode.cn/problems/maximum-swap/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-swap.md) | 贪心、数学 | 中等 | + + +### 位运算题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) | 位运算、数组 | 简单 | +| [0191. 位1的个数](https://leetcode.cn/problems/number-of-1-bits/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/number-of-1-bits.md) | 位运算、分治 | 简单 | +| [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) | 位运算、数组、哈希表、数学、二分查找、排序 | 简单 | + + +## 第 8 章 动态规划 + +### 动态规划题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) | 记忆化搜索、数学、动态规划 | 简单 | +| [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) | 递归、记忆化搜索、数学、动态规划 | 简单 | +| [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md) | 数组、动态规划 | 简单 | +| [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) | 广度优先搜索、数组、动态规划 | 中等 | +| [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/coin-change-ii.md) | 数组、动态规划 | 中等 | +| [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) | 数组、二分查找、动态规划 | 中等 | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) | 字符串、动态规划 | 中等 | +| [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | +| [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) | 数组、动态规划、矩阵 | 中等 | +| [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) | 字符串、动态规划 | 中等 | +| [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) | 栈、字符串、动态规划 | 困难 | +| [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) | 数组、动态规划、矩阵 | 中等 | +| [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) | 数学、动态规划、组合数学 | 中等 | +| [0063. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths-ii.md) | 数组、动态规划、矩阵 | 中等 | +| [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-product-subarray.md) | 数组、动态规划 | 中等 | +| [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) | 数组、动态规划 | 中等 | +| [0213. 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/house-robber-ii.md) | 数组、动态规划 | 中等 | +| [0091. 解码方法](https://leetcode.cn/problems/decode-ways/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/decode-ways.md) | 字符串、动态规划 | 中等 | +| [0010. 正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/regular-expression-matching.md) | 递归、字符串、动态规划 | 困难 | +| [0678. 有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-parenthesis-string.md) | 栈、贪心、字符串、动态规划 | 中等 | +| [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) | 贪心、数组、动态规划 | 中等 | +| [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) | 树状数组、线段树、数组、动态规划 | 中等 | +| [0139. 单词拆分](https://leetcode.cn/problems/word-break/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break.md) | 字典树、记忆化搜索、数组、哈希表、字符串、动态规划 | 中等 | +| [0044. 通配符匹配](https://leetcode.cn/problems/wildcard-matching/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/wildcard-matching.md) | 贪心、递归、字符串、动态规划 | 困难 | +| [0120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/triangle.md) | 数组、动态规划 | 中等 | +| [0096. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees.md) | 树、二叉搜索树、数学、动态规划、二叉树 | 中等 | +| [0887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/super-egg-drop.md) | 数学、二分查找、动态规划 | 困难 | +| [0097. 交错字符串](https://leetcode.cn/problems/interleaving-string/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/interleaving-string.md) | 字符串、动态规划 | 中等 | +| [0516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/longest-palindromic-subsequence.md) | 字符串、动态规划 | 中等 | + + +### 记忆化搜索题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 | 困难 | + + +## 补充题目 + +#### 设计数据结构题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0146. LRU 缓存](https://leetcode.cn/problems/lru-cache/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/lru-cache.md) | 设计、哈希表、链表、双向链表 | 中等 | +| [0460. LFU 缓存](https://leetcode.cn/problems/lfu-cache/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/lfu-cache.md) | 设计、哈希表、链表、双向链表 | 困难 | + + +#### 数学题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0007. 整数反转](https://leetcode.cn/problems/reverse-integer/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-integer.md) | 数学 | 中等 | +| [0009. 回文数](https://leetcode.cn/problems/palindrome-number/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/palindrome-number.md) | 数学 | 简单 | +| [LCR 187. 破冰游戏](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md) | 递归、数学 | 简单 | +| [0168. Excel 表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/excel-sheet-column-title.md) | 数学、字符串 | 简单 | +| [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/nth-digit.md) | 数学、二分查找 | 中等 | + + +#### 模拟题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0008. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/string-to-integer-atoi.md) | 字符串 | 中等 | +| [0165. 比较版本号](https://leetcode.cn/problems/compare-version-numbers/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/compare-version-numbers.md) | 双指针、字符串 | 中等 | +| [0468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/validate-ip-address.md) | 字符串 | 中等 | +| [0086. 分隔链表](https://leetcode.cn/problems/partition-list/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/partition-list.md) | 链表、双指针 | 中等 | + + +#### 前缀和 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subarray-sum-equals-k.md) | 数组、哈希表、前缀和 | 中等 | + + +#### 思维锻炼题目 + +| 标题 | 题解 | 标签 | 难度 | +| :--- | :--- | :--- | :--- | +| [0031. 下一个排列](https://leetcode.cn/problems/next-permutation/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/next-permutation.md) | 数组、双指针 | 中等 | +| [0556. 下一个更大元素 III](https://leetcode.cn/problems/next-greater-element-iii/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/next-greater-element-iii.md) | 数学、双指针、字符串 | 中等 | +| [0470. 用 Rand7() 实现 Rand10()](https://leetcode.cn/problems/implement-rand10-using-rand7/) | [题解](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/implement-rand10-using-rand7.md) | 数学、拒绝采样、概率与统计、随机化 | 中等 | + + + +## 参考资料 + +- 【清单】[CodeTop 企业题库](https://codetop.cc/home) diff --git a/docs/00_preface/index.md b/docs/00_preface/index.md new file mode 100644 index 00000000..e640b2da --- /dev/null +++ b/docs/00_preface/index.md @@ -0,0 +1,10 @@ +# 本章内容 + +- [0.1 前言](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_01_preface.md) +- [0.2 算法与数据结构](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_02_data_structures_algorithms.md) +- [0.3 算法复杂度](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_03_algorithm_complexity.md) +- [0.4 LeetCode 入门与攻略](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_04_leetcode_guide.md) +- [0.5 LeetCode 题解(字典序排序,850+ 道题解)](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_05_solutions_list.md) +- [0.6 LeetCode 题解(按分类排序,推荐刷题列表 ★★★)](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md) +- [0.7 LeetCode 面试最常考 100 题(按分类排序)](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_07_interview_100_list.md) +- [0.8 LeetCode 面试最常考 200 题(按分类排序)](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_08_interview_200_list.md) \ No newline at end of file diff --git a/docs/01_array/01_01_array_basic.md b/docs/01_array/01_01_array_basic.md new file mode 100644 index 00000000..2ab3d233 --- /dev/null +++ b/docs/01_array/01_01_array_basic.md @@ -0,0 +1,256 @@ +## 1. 数组简介 + +### 1.1 数组定义 + +> **数组(Array)**:一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。 + +简单来说,**「数组」** 是实现线性表的顺序结构存储的基础。 + +以整数数组为例,数组的存储方式如下图所示。 + +![数组](https://qcdn.itcharge.cn/images/202405091955166.png) + +如上图所示,假设数据元素的个数为 $n$,则数组中的每一个数据元素都有自己的下标索引,下标索引从 $0$ 开始,到 $n - 1$ 结束。数组中的每一个「下标索引」,都有一个与之相对应的「数据元素」。 + +从上图还可以看出,数组在计算机中的表示,就是一片连续的存储单元。数组中的每一个数据元素都占有一定的存储单元,每个存储单元都有自己的内存地址,并且元素之间是紧密排列的。 + +我们还可以从两个方面来解释一下数组的定义。 + +> 1. **线性表**:线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前、后两个方向。数组就是一种线性表结构,此外,栈、队列、链表都是线性表结构。 +> 2. **连续的内存空间**:线性表有两种存储结构:「顺序存储结构」和「链式存储结构」。其中,「顺序存储结构」是指占用的内存空间是连续的,相邻数据元素之间,物理内存上的存储位置也相邻。数组也是采用了顺序存储结构,并且存储的数据都是相同类型的。 + +综合这两个角度,数组就可以看做是:使用了「顺序存储结构」的「线性表」的一种实现方式。 + +### 1.2 如何随机访问数据元素 + +数组的一个最大特点是:**可以进行随机访问**。即数组可以根据下标,直接定位到某一个元素存放的位置。 + +那么,计算机是如何实现根据下标随机访问数组元素的? + +计算机给一个数组分配了一组连续的存储空间,其中第一个元素开始的地址被称为 **「首地址」**。每个数据元素都有对应的下标索引和内存地址,计算机通过地址来访问数据元素。当计算机需要访问数组的某个元素时,会通过 **「寻址公式」** 计算出对应元素的内存地址,然后访问地址对应的数据元素。 + +寻址公式如下:**下标 $i$ 对应的数据元素地址 = 数据首地址 + $i$ × 单个数据元素所占内存大小**。 + +### 1.3 多维数组 + +上面介绍的数组只有一个维度,称为一维数组,其数据元素也是单下标变量。但是在实际问题中,很多信息是二维或者是多维的,一维数组已经满足不了我们的需求,所以就有了多维数组。 + +以二维数组为例,数组的形式如下图所示。 + +![二维数组](https://qcdn.itcharge.cn/images/202405091957859.png) + +二维数组是一个由 $m$ 行 $n$ 列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即 **「数组的数组」**。二维数组的第一维度表示行,第二维度表示列。 + +我们可以将二维数组看做是一个矩阵,并处理矩阵的相关问题,比如转置矩阵、矩阵相加、矩阵相乘等等。 + +### 1.4 不同编程语言中数组的实现 + +在具体的编程语言中,数组这个数据结构的实现方式具有一定差别。 + +C / C++ 语言中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如: + +```C++ +int arr[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; +``` + +Java 中的数组跟数据结构定义中的数组不太一样。Java 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如: + +```Java +int[][] arr = new int[3][]{ {1,2,3}, {4,5}, {6,7,8,9}}; +``` + +原生 Python 中其实没有数组的概念,而是使用了类似 Java 中的 ArrayList 容器类数据结构,叫做列表。通常我们把列表来作为 Python 中的数组使用。Python 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如: + +```python +arr = ['python', 'java', ['asp', 'php'], 'c'] +``` + +## 2. 数组的基本操作 + +数据结构的操作一般涉及到增、删、改、查共 $4$ 种情况,下面我们一起来看一下数组的这 $4$ 种基本操作。 + +### 2.1 访问元素 + +> **访问数组中第 $i$ 个元素**: +> +> 1. 只需要检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。超出范围的访问为非法访问。 +> 2. 当位置合法时,由给定下标得到元素的值。 + +```python +# 从数组 nums 中读取下标为 i 的数据元素值 +def value(nums, i): + if 0 <= i <= len(nums) - 1: + print(nums[i]) + +arr = [0, 5, 2, 3, 7, 1, 6] +value(arr, 3) +``` + +「访问数组元素」的操作不依赖于数组中元素个数,因此,「访问数组元素」的时间复杂度为 $O(1)$。 + +### 2.2 查找元素 + +> **查找数组中元素值为 $val$ 的位置**: +> +> 1. 建立一个基于下标的循环,每次将 $val$ 与当前数据元素 $nums[i]$ 进行比较。 +> 2. 在找到元素的时候返回元素下标。 +> 3. 遍历完找不到时可以返回一个特殊值(例如 $-1$)。 + +```python +# 从数组 nums 中查找元素值为 val 的数据元素第一次出现的位置 +def find(nums, val): + for i in range(len(nums)): + if nums[i] == val: + return i + return -1 + +arr = [0, 5, 2, 3, 7, 1, 6] +print(find(arr, 5)) +``` + +在「查找元素」的操作中,如果数组无序,那么我们只能通过将 $val$ 与数组中的数据元素逐一对比的方式进行查找,也称为线性查找。而线性查找操作依赖于数组中元素个数,因此,「查找元素」的时间复杂度为 $O(n)$。 + +### 2.3 插入元素 + +插入元素操作分为两种:「在数组尾部插入值为 $val$ 的元素」和「在数组第 $i$ 个位置上插入值为 $val$ 的元素」。 + +> **在数组尾部插入值为 $val$ 的元素**: +> +> 1. 如果数组尾部容量不满,则直接把 $val$ 放在数组尾部的空闲位置,并更新数组的元素计数值。 +> 2. 如果数组容量满了,则插入失败。不过,Python 中的 list 列表做了其他处理,当数组容量满了,则会开辟新的空间进行插入。 + +Python 中的 list 列表直接封装了尾部插入操作,直接调用 `append` 方法即可。 + +![插入元素](https://qcdn.itcharge.cn/images/20210916222517.png) + +```python +arr = [0, 5, 2, 3, 7, 1, 6] +val = 4 +arr.append(val) +print(arr) +``` + +「在数组尾部插入元素」的操作不依赖数组个数,因此,「在数组尾部插入元素」的时间复杂度为 $O(1)$。 + +> **在数组第 $i$ 个位置上插入值为 $val$ 的元素**: +> +> 1. 先检查插入下标 $i$ 是否合法,即 $0 \le i \le len(nums)$。 +> 2. 确定合法位置后,通常情况下第 $i$ 个位置上已经有数据了(除非 $i == len(nums)$),要把第 $i \sim len(nums) - 1$ 位置上的元素依次向后移动。 +> 3. 然后再在第 $i$ 个元素位置赋值为 $val$,并更新数组的元素计数值。 + +Python 中的 list 列表直接封装了中间插入操作,直接调用 `insert` 方法即可。 + +![插入中间元素](https://qcdn.itcharge.cn/images/20210916224032.png) + +```python +arr = [0, 5, 2, 3, 7, 1, 6] +i, val = 2, 4 +arr.insert(i, val) +print(arr) +``` + +「在数组中间位置插入元素」的操作中,由于移动元素的操作次数跟元素个数有关,因此,「在数组中间位置插入元素」的最坏和平均时间复杂度都是 $O(n)$。 + +### 2.4 改变元素 + +> **将数组中第 $i$ 个元素值改为 $val$**: +> +> 1. 需要先检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。 +> 2. 然后将第 $i$ 个元素值赋值为 $val$。 + +![改变元素](https://qcdn.itcharge.cn/images/20210916224722.png) + +```python +def change(nums, i, val): + if 0 <= i <= len(nums) - 1: + nums[i] = val + +arr = [0, 5, 2, 3, 7, 1, 6] +i, val = 2, 4 +change(arr, i, val) +print(arr) +``` + +「改变元素」的操作跟访问元素操作类似,访问操作不依赖于数组中元素个数,因此,「改变元素」的时间复杂度为 $O(1)$。 + +### 2.5 删除元素 + +删除元素分为三种情况:「删除数组尾部元素」、「删除数组第 $i$ 个位置上的元素」、「基于条件删除元素」。 + +> **删除数组尾部元素**: +> +> 1. 只需将元素计数值减一即可。 + +Python 中的 list 列表直接封装了删除数组尾部元素的操作,只需要调用 `pop` 方法即可。 + +![删除尾部元素](https://qcdn.itcharge.cn/images/20210916233914.png) + +```python +arr = [0, 5, 2, 3, 7, 1, 6] +arr.pop() +print(arr) +``` + +「删除数组尾部元素」的操作,不依赖于数组中的元素个数,因此,「删除数组尾部元素」的时间复杂度为 $O(1)$。 + +> **删除数组第 $i$ 个位置上的元素**: +> +> 1. 先检查下标 $i$ 是否合法,即 $0 \le i \le len(nums) - 1$。 +> 2. 如果下标合法,则将第 $i + 1$ 个位置到第 $len(nums) - 1$ 位置上的元素依次向左移动。 +> 3. 删除后修改数组的元素计数值。 + +Python 中的 list 列表直接封装了删除数组中间元素的操作,只需要以下标作为参数调用 `pop` 方法即可。 + +![删除中间元素](https://qcdn.itcharge.cn/images/20210916234013.png) + +``` +arr = [0, 5, 2, 3, 7, 1, 6] +i = 3 +arr.pop(i) +print(arr) +``` + +「删除数组中间位置元素」的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此,「删除数组中间位置元素」的最坏和平均时间复杂度都是 $O(n)$。 + +> **基于条件删除元素**:这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。 + +```python +arr = [0, 5, 2, 3, 7, 1, 6] +arr.remove(5) +print(arr) +``` + +「基于条件删除元素」的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此,「基于条件删除元素」的最坏和平均时间复杂度都是 $O(n)$。 + +--- + +到这里,有关数组的基础知识就介绍完了。下面进行一下总结。 + +## 3. 总结 + +数组是一种简单的数据结构。它使用连续的内存空间存储相同类型的数据。数组的最大特点的支持随机访问,可以根据下标快速访问元素。 + +访问数组元素、修改元素的时间复杂度是 $O(1)$。在数组尾部插入、删除元素的时间复杂度也是 $O(1)$。在数组中间插入、删除元素的时间复杂度是 $O(n)$。 + +不同编程语言中数组的实现可能不同。C/C++ 的数组严格使用连续内存,Java 和 Python 的数组灵活性更高。 + +## 4. 练习题目 + +- [0066. 加一](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/plus-one.md) +- [0724. 寻找数组的中心下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-pivot-index.md) +- [0189. 轮转数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/rotate-array.md) +- [0048. 旋转图像](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) +- [0054. 螺旋矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) +- [0498. 对角线遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/diagonal-traverse.md) + +- [数组基础题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%95%B0%E7%BB%84%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[数据结构中的数组和不同语言中数组的区别 - CSDN 博客](https://blog.csdn.net/sinat_14913533/article/details/102763573) +- 【文章】[数组理论基础 - 代码随想录](https://programmercarl.com/数组理论基础.html#数组理论基础) +- 【文章】[Python 与 Java 中容器对比:List - 知乎](https://zhuanlan.zhihu.com/p/120312437) +- 【文章】[什么是数组 - 漫画算法 - 小灰的算法之旅 - 力扣](https://leetcode.cn/leetbook/read/journey-of-algorithm/5ozchs/) +- 【文章】[数组 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/intro/100017301) +- 【书籍】数据结构教程 第 2 版 - 唐发根 著 +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 diff --git a/docs/01_array/01_02_array_sort.md b/docs/01_array/01_02_array_sort.md new file mode 100644 index 00000000..e24125b8 --- /dev/null +++ b/docs/01_array/01_02_array_sort.md @@ -0,0 +1,54 @@ +# 1.2 数组排序 + +数组排序是计算机科学中最基本的问题之一。排序算法有很多种,每种算法都有其特点和适用场景。 + +## 1. 排序算法的分类 + +排序算法可以按照不同的标准进行分类: + +1. 按照时间复杂度分类: + - 简单排序算法:时间复杂度为 $O(n^2)$,如冒泡排序、选择排序、插入排序 + - 高级排序算法:时间复杂度为 $O(n \log n)$,如快速排序、归并排序、堆排序 + - 线性排序算法:时间复杂度为 $O(n)$,如计数排序、桶排序、基数排序 + +2. 按照空间复杂度分类: + - 原地排序算法:空间复杂度为 $O(1)$,如冒泡排序、选择排序、插入排序、快速排序、堆排序 + - 非原地排序算法:空间复杂度为 $O(n)$,如归并排序、计数排序、桶排序、基数排序 + +3. 按照稳定性分类: + - 稳定排序算法:相等元素的相对顺序在排序后保持不变,如冒泡排序、插入排序、归并排序、计数排序、桶排序、基数排序 + - 不稳定排序算法:相等元素的相对顺序在排序后可能改变,如选择排序、快速排序、堆排序 + +## 2. 排序算法的评价指标 + +评价一个排序算法的好坏,主要从以下几个方面考虑: + +1. 时间复杂度:算法执行所需的时间 +2. 空间复杂度:算法执行所需的额外空间 +3. 稳定性:相等元素的相对顺序是否保持不变 +4. 原地性:是否需要在原数组之外开辟额外空间 +5. 自适应性:算法是否能够利用输入数据的特性来提高效率 + +## 3. 常见排序算法 + +常见的排序算法包括: + +1. 冒泡排序:通过相邻元素比较和交换,将最大元素逐步"冒泡"到数组末尾 +2. 选择排序:每次从未排序区间选择最小元素,放到已排序区间末尾 +3. 插入排序:将未排序区间的元素插入到已排序区间的合适位置 +4. 快速排序:选择一个基准元素,将数组分为两部分,递归排序 +5. 归并排序:将数组分成两半,分别排序后合并 +6. 堆排序:利用堆这种数据结构进行排序 +7. 计数排序:统计每个元素出现的次数,按顺序输出 +8. 桶排序:将元素分到有限数量的桶中,对每个桶单独排序 +9. 基数排序:按照元素的位数进行排序 + +## 4. 排序算法的选择 + +在实际应用中,选择合适的排序算法需要考虑以下因素: + +1. 数据规模:小规模数据可以使用简单排序算法,大规模数据需要使用高级排序算法 +2. 数据特征:如果数据基本有序,插入排序效率较高;如果数据分布均匀,快速排序效率较高 +3. 空间限制:如果空间有限,应该选择原地排序算法 +4. 稳定性要求:如果需要保持相等元素的相对顺序,应该选择稳定排序算法 +5. 硬件环境:不同的硬件环境可能适合不同的排序算法 diff --git a/docs/01_array/01_03_array_bubble_sort.md b/docs/01_array/01_03_array_bubble_sort.md new file mode 100644 index 00000000..d1153069 --- /dev/null +++ b/docs/01_array/01_03_array_bubble_sort.md @@ -0,0 +1,119 @@ +## 1. 冒泡排序算法思想 + +> **冒泡排序(Bubble Sort)基本思想**: +> +> 经过多次迭代,通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 + +这个过程就像水底的气泡一样从底部向上「冒泡」到水面,这也是冒泡排序法名字的由来。 + +接下来,我们使用「冒泡」的方式来模拟一下这个过程。 + +1. 首先将数组想象是一排「泡泡」,元素值的大小与泡泡的大小成正比。 +2. 然后从左到右依次比较相邻的两个「泡泡」: + 1. 如果左侧泡泡大于右侧泡泡,则交换两个泡泡的位置。 + 2. 如果左侧泡泡小于等于右侧泡泡,则两个泡泡保持不变。 +3. 这 $1$ 趟遍历完成之后,最大的泡泡就会放置到所有泡泡的最右侧,就像是「泡泡」从水底向上浮到了水面。 + +::: tabs#bubble + +@tab <1> + +![冒泡排序 1](https://qcdn.itcharge.cn/images/202308152226863.png) + +@tab <2> + +![冒泡排序 2](https://qcdn.itcharge.cn/images/202308152227763.png) + +@tab <3> + +![冒泡排序 3](https://qcdn.itcharge.cn/images/202308152227002.png) + +@tab <4> + +![冒泡排序 4](https://qcdn.itcharge.cn/images/202308152227621.png) + +@tab <5> + +![冒泡排序 5](https://qcdn.itcharge.cn/images/202308152227175.png) + +@tab <6> + +![冒泡排序 6](https://qcdn.itcharge.cn/images/202308152227578.png) + +@tab <7> + +![冒泡排序 7](https://qcdn.itcharge.cn/images/202308152228488.png) + +::: + +## 2. 冒泡排序算法步骤 + +假设数组的元素个数为 $n$ 个,则冒泡排序的算法步骤如下: + +1. 第 $1$ 趟「冒泡」:对前 $n$ 个元素执行「冒泡」,从而使第 $1$ 个值最大的元素放置在正确位置上。 + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换。 + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,如果前者大于后者,则两者交换位置,否则不交换。 + 3. 依次类推,直到第 $n - 1$ 个元素与第 $n$ 个元素比较(或交换)为止。 + 4. 经过第 $1$ 趟排序,使得 $n$ 个元素中第 $i$ 个值最大元素被安置在第 $n$ 个位置上。 +2. 第 $2$ 趟「冒泡」:对前 $n - 1$ 个元素执行「冒泡」,从而使第 $2$ 个值最大的元素放置在正确位置上。 + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换。 + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,若前者大于后者,则两者交换位置,否则不交换。 + 3. 依次类推,直到对 $n - 2$ 个元素与第 $n - 1$ 个元素比较(或交换)为止。 + 4. 经过第 $2$ 趟排序,使得数组中第 $2$ 个值最大元素被安置在第 $n$ 个位置上。 +3. 依次类推,重复上述「冒泡」过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 + +我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下冒泡排序算法的整个步骤。 + +![冒泡排序算法步骤](https://qcdn.itcharge.cn/images/20230816154510.png) + +## 3. 冒泡排序代码实现 + +```python +class Solution: + def bubbleSort(self, nums: [int]) -> [int]: + # 第 i 趟「冒泡」 + for i in range(len(nums) - 1): + flag = False # 是否发生交换的标志位 + # 从数组中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较 + for j in range(len(nums) - i - 1): + # 相邻两个元素进行比较,如果前者大于后者,则交换位置 + if nums[j] > nums[j + 1]: + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True + if not flag: # 此趟遍历未交换任何元素,直接跳出 + break + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.bubbleSort(nums) +``` + +## 4. 冒泡排序算法分析 + +- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),只需经过 $1$ 趟排序,总共经过 $n$ 次元素之间的比较,并且不移动元素,算法就可以结束排序。因此,冒泡排序算法的最佳时间复杂度为 $O(n)$。 +- **最坏时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列,或者最小值元素处在序列的最后),则需要进行 $n$ 趟排序,总共进行 $∑^n_{i=2}(i−1) = \frac{n(n−1)}{2}$ 次元素之间的比较,因此,冒泡排序算法的最坏时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(1)$。冒泡排序为原地排序算法,只用到指针变量 $i$、$j$ 以及标志位 $flag$ 等常数项的变量。 +- **冒泡排序适用情况**:冒泡排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,冒泡排序方法比较适合于参加排序序列的数据量较小的情况,尤其是当序列的初始状态为基本有序的情况。 +- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变相等元素的相对顺序,因此,冒泡排序法是一种 **稳定排序算法**。 + +## 5. 总结 + +冒泡排序是一种简单的排序算法。它通过多次比较相邻元素并交换位置,将较大的元素逐步移动到数组末尾。 + +冒泡排序的时间复杂度取决于数据情况。最好情况下是 $O(n)$,最坏情况下是 $O(n^2)$。它只需要常数级别的额外空间,空间复杂度是 $O(1)$。 + +冒泡排序适合数据量小的场景。当数据基本有序时,它的效率较高。由于交换只在相邻元素间进行,冒泡排序是稳定的排序算法。 + +冒泡排序容易实现,但效率较低。在实际应用中,通常选择更高效的排序算法处理大规模数据。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md)(冒泡排序会超时,仅作练习) +- [0283. 移动零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md)(冒泡排序会超时,仅作练习) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[11.3. 冒泡排序 - Hello 算法](https://www.hello-algo.com/chapter_sorting/bubble_sort/) \ No newline at end of file diff --git a/docs/01_array/01_04_array_selection_sort.md b/docs/01_array/01_04_array_selection_sort.md new file mode 100644 index 00000000..b3e48d63 --- /dev/null +++ b/docs/01_array/01_04_array_selection_sort.md @@ -0,0 +1,99 @@ +## 1. 选择排序算法思想 + +> **选择排序(Selection Sort)基本思想**: +> +> 将数组分为两个区间:左侧为已排序区间,右侧为未排序区间。每趟从未排序区间中选择一个值最小的元素,放到已排序区间的末尾,从而将该元素划分到已排序区间。 + +选择排序是一种简单直观的排序算法,其思想简单,代码也相对容易。 + +## 2. 选择排序算法步骤 + +假设数组的元素个数为 $n$ 个,则选择排序的算法步骤如下: + +1. 初始状态下,无已排序区间,未排序区间为 $[0, n - 1]$。 +2. 第 $1$ 趟选择: + 1. 遍历未排序区间 $[0, n - 1]$,使用变量 $min\underline{\hspace{0.5em}}i$ 记录区间中值最小的元素位置。 + 2. 将 $min\underline{\hspace{0.5em}}i$ 与下标为 $0$ 处的元素交换位置。如果下标为 $0$ 处元素就是值最小的元素位置,则不用交换。 + 3. 此时,$[0, 0]$ 为已排序区间,$[1, n - 1]$(总共 $n - 1$ 个元素)为未排序区间。 +3. 第 $2$ 趟选择: + 1. 遍历未排序区间 $[1, n - 1]$,使用变量 $min\underline{\hspace{0.5em}}i$ 记录区间中值最小的元素位置。 + 2. 将 $min\underline{\hspace{0.5em}}i$ 与下标为 $1$ 处的元素交换位置。如果下标为 $1$ 处元素就是值最小的元素位置,则不用交换。 + 3. 此时,$[0, 1]$ 为已排序区间,$[2, n - 1]$(总共 $n - 2$ 个元素)为未排序区间。 +4. 依次类推,对剩余未排序区间重复上述选择过程,直到所有元素都划分到已排序区间,排序结束。 + +我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下选择排序的整个步骤。 + +::: tabs#selectionSort + +@tab <1> + +![选择排序 1](https://qcdn.itcharge.cn/images/20230816155042.png) + +@tab <2> + +![选择排序 2](https://qcdn.itcharge.cn/images/20230816155017.png) + +@tab <3> + +![选择排序 3](https://qcdn.itcharge.cn/images/20230816154955.png) + +@tab <4> + +![选择排序 4](https://qcdn.itcharge.cn/images/20230816154924.png) + +@tab <5> + +![选择排序 5](https://qcdn.itcharge.cn/images/20230816154859.png) + +@tab <6> + +![选择排序 6](https://qcdn.itcharge.cn/images/20230816154836.png) + +@tab <7> + +![选择排序 7](https://qcdn.itcharge.cn/images/20230816153324.png) + +::: + +## 3. 选择排序代码实现 + +```python +class Solution: + def selectionSort(self, nums: [int]) -> [int]: + for i in range(len(nums) - 1): + # 记录未排序区间中最小值的位置 + min_i = i + for j in range(i + 1, len(nums)): + if nums[j] < nums[min_i]: + min_i = j + # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 + if i != min_i: + nums[i], nums[min_i] = nums[min_i], nums[i] + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.selectionSort(nums) +``` + +## 4. 选择排序算法分析 + +- **时间复杂度**:$O(n^2)$。排序法所进行的元素之间的比较次数与序列的原始状态无关,时间复杂度总是 $O(n^2)$。 + - 这是因为无论序列中元素的初始排列状态如何,第 $i$ 趟排序要找出值最小元素都需要进行 $n − i$ 次元素之间的比较。因此,整个排序过程需要进行的元素之间的比较次数都相同,为 $∑^n_{i=2}(i - 1) = \frac{n(n−1)}{2}$ 次。 +- **空间复杂度**:$O(1)$。选择排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及最小值位置 $min\underline{\hspace{0.5em}}i$ 等常数项的变量。 +- **选择排序适用情况**:选择排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序,因此在空间复杂度要求较高时,可以考虑选择排序。 + +- **排序稳定性**:由于值最小元素与未排序区间第 $1$ 个元素的交换动作是在不相邻的元素之间进行的,因此很有可能会改变相等元素的相对顺序,因此,选择排序法是一种 **不稳定排序算法**。 + +## 5. 总结 + +选择排序是一种简单的排序算法。它的工作原理是将数组分成已排序和未排序两部分。每次从未排序部分找到最小的元素,放到已排序部分的末尾。这个过程重复进行,直到所有元素排序完成。 + +选择排序的时间复杂度是 $O(n^2)$,因为它需要进行 $ \frac{n(n−1)}{2}$ 次比较。空间复杂度是 $O(1)$,因为它只需要常数级的额外空间。选择排序是不稳定的排序算法,因为在交换过程中可能改变相等元素的相对顺序。 + +选择排序适合小规模数据的排序。它的优点是实现简单,不需要额外空间。缺点是效率较低,不适合大规模数据。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md)(选择排序会超时,仅作练习) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_05_array_insertion_sort.md b/docs/01_array/01_05_array_insertion_sort.md new file mode 100644 index 00000000..8ab06c9b --- /dev/null +++ b/docs/01_array/01_05_array_insertion_sort.md @@ -0,0 +1,74 @@ +## 1. 插入排序算法思想 + +> **插入排序(Insertion Sort)基本思想**: +> +> 将数组分为两个区间:左侧为有序区间,右侧为无序区间。每趟从无序区间取出一个元素,然后将其插入到有序区间的适当位置。 +> + +插入排序在每次插入一个元素时,该元素会在有序区间找到合适的位置,因此每次插入后,有序区间都会保持有序。 + +## 2. 插入排序算法步骤 + +假设数组的元素个数为 $n$ 个,则插入排序的算法步骤如下: + +1. 初始状态下,有序区间为 $[0, 0]$,无序区间为 $[1, n - 1]$。 +2. 第 $1$ 趟插入: + 1. 取出无序区间 $[1, n - 1]$ 中的第 $1$ 个元素,即 $nums[1]$。 + 2. 从右到左遍历有序区间中的元素,将比 $nums[1]$ 大的元素向后移动 $1$ 位。 + 3. 如果遇到小于或等于 $nums[1]$ 的元素时,说明找到了插入位置,将 $nums[1]$ 插入到该位置。 + 4. 插入元素后有序区间变为 $[0, 1]$,无序区间变为 $[2, n - 1]$。 +3. 第 $2$ 趟插入: + 1. 取出无序区间 $[2, n - 1]$ 中的第 $1$ 个元素,即 $nums[2]$。 + 2. 从右到左遍历有序区间中的元素,将比 $nums[2]$ 大的元素向后移动 $1$ 位。 + 3. 如果遇到小于或等于 $nums[2]$ 的元素时,说明找到了插入位置,将 $nums[2]$ 插入到该位置。 + 4. 插入元素后有序区间变为 $[0, 2]$,无序区间变为 $[3, n - 1]$。 +4. 依次类推,对剩余无序区间中的元素重复上述插入过程,直到所有元素都插入到有序区间中,排序结束。 + +我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下插入排序算法的整个步骤。 + +![插入排序算法步骤](http://qcdn.itcharge.cn/images/20230816175619.png) + +## 3. 插入排序代码实现 + +```python +class Solution: + def insertionSort(self, nums: [int]) -> [int]: + # 遍历无序区间 + for i in range(1, len(nums)): + temp = nums[i] + j = i + # 从右至左遍历有序区间 + while j > 0 and nums[j - 1] > temp: + # 将有序区间中插入位置右侧的元素依次右移一位 + nums[j] = nums[j - 1] + j -= 1 + # 将该元素插入到适当位置 + nums[j] = temp + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.insertionSort(nums) +``` + +## 4. 插入排序算法分析 + +- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时区间已经是升序排列),每个元素只进行一次元素之间的比较,因而总的比较次数最少,为 $∑^n_{i = 2}1 = n − 1$,并不需要移动元素(记录),这是最好的情况。 +- **最差时间复杂度**:$O(n^2)$。最差的情况下(初始时区间已经是降序排列),每个元素 $nums[i]$ 都要进行 $i - 1$ 次元素之间的比较,元素之间总的比较次数达到最大值,为 $∑^n_{i=2}(i − 1) = \frac{n(n−1)}{2}$。 +- **平均时间复杂度**:$O(n^2)$。如果区间的初始情况是随机的,即参加排序的区间中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $\frac{n^2}{4}$。由此得知,插入排序算法的平均时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(1)$。插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量等常数项的变量。 +- **排序稳定性**:在插入操作过程中,每次都讲元素插入到相等元素的右侧,并不会改变相等元素的相对顺序。因此,插入排序方法是一种 **稳定排序算法**。 + +## 5. 总结 + +插入排序是一种简单直观的排序算法。它的工作原理是将数组分为有序区间和无序区间,每次从无序区间取出一个元素,插入到有序区间的正确位置。这个过程重复进行,直到所有元素都排好序。 + +插入排序的时间复杂度取决于数据的初始顺序。最好的情况是数组已经有序,时间复杂度为 $O(n)$。最坏的情况是数组完全逆序,时间复杂度为 $O(n^2)$。平均时间复杂度也是 $O(n^2)$。空间复杂度是 $O(1)$,因为排序是在原地进行的。 + +插入排序是稳定的排序算法,因为它不会改变相等元素的相对顺序。这个算法适合小规模数据或基本有序的数据排序。对于大规模数据,插入排序的效率不如快速排序等更高级的算法。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md)(插入排序会超时,仅作练习) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_06_array_shell_sort.md b/docs/01_array/01_06_array_shell_sort.md new file mode 100644 index 00000000..866ba1c4 --- /dev/null +++ b/docs/01_array/01_06_array_shell_sort.md @@ -0,0 +1,104 @@ +## 1. 希尔排序算法思想 + +> **希尔排序(Shell Sort)基本思想**: +> +> 将整个数组切按照一定的间隔取值划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 $1$,对整个数组进行插入排序。 +> + +## 2. 希尔排序算法步骤 + +假设数组的元素个数为 $n$ 个,则希尔排序的算法步骤如下: + +1. 确定一个元素间隔数 $gap$。 +2. 将参加排序的数组按此间隔数从第 $1$ 个元素开始一次分成若干个子数组,即分别将所有位置相隔为 $gap$ 的元素视为一个子数组。 +3. 在各个子数组中采用某种排序算法(例如插入排序算法)进行排序。 +4. 减少间隔数,并重新将整个数组按新的间隔数分成若干个子数组,再分别对各个子数组进行排序。 +5. 依次类推,直到间隔数 $gap$ 值为 $1$,最后进行一次排序,排序结束。 + +我们以 $[7, 2, 6, 8, 0, 4, 1, 5, 9, 3]$ 为例,演示一下希尔排序的整个步骤。 + +::: tabs#shellSort + +@tab <1> + +![希尔排序 1](https://qcdn.itcharge.cn/images/202308162132060.png) + +@tab <2> + +![希尔排序 2](https://qcdn.itcharge.cn/images/202308162132189.png) + +@tab <3> + +![希尔排序 3](https://qcdn.itcharge.cn/images/202308162132870.png) + +@tab <4> + +![希尔排序 4](https://qcdn.itcharge.cn/images/202308162132322.png) + +@tab <5> + +![希尔排序 5](https://qcdn.itcharge.cn/images/202308162132881.png) + +@tab <6> + +![希尔排序 6](https://qcdn.itcharge.cn/images/202308162132386.png) + +@tab <7> + +![希尔排序 7](https://qcdn.itcharge.cn/images/202308162132898.png) + +::: + +## 3. 希尔排序代码实现 + +```python +class Solution: + def shellSort(self, nums: [int]) -> [int]: + size = len(nums) + gap = size // 2 + # 按照 gap 分组 + while gap > 0: + # 对每组元素进行插入排序 + for i in range(gap, size): + # temp 为每组中无序数组第 1 个元素 + temp = nums[i] + j = i + # 从右至左遍历每组中的有序数组元素 + while j >= gap and nums[j - gap] > temp: + # 将每组有序数组中插入位置右侧的元素依次在组中右移一位 + nums[j] = nums[j - gap] + j -= gap + # 将该元素插入到适当位置 + nums[j] = temp + # 缩小 gap 间隔 + gap = gap // 2 + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.shellSort(nums) +``` + +## 4. 希尔排序算法分析 + +- **时间复杂度**:介于 $O(n \times \log^2 n)$ 与 $O(n^2)$ 之间。 + - 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,而比较次数与 $gap_i$ 之间的依赖关系比较复杂,不太容易给出完整的数学分析。 + - 本文采用 $gap_i = \lfloor gap_{i-1}/2 \rfloor$ 的方法缩小间隔数,对于具有 $n$ 个元素的数组,如果 $gap_1 = \lfloor n/2 \rfloor$,则经过 $p = \lfloor \log_2 n \rfloor$ 趟排序后就有 $gap_p = 1$,因此,希尔排序方法的排序总躺数为 $\lfloor \log_2 n \rfloor$。 + - 从算法中也可以看到,外层 `while gap > 0` 的循环次数为 $\log n$ 数量级,内层插入排序算法循环次数为 $n$ 数量级。当子数组分得越多时,子数组内的元素就越少,内层循环的次数也就越少;反之,当所分的子数组个数减少时,子数组内的元素也随之增多,但整个数组也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n \times \log^2 n)$ 与 $O(n^2)$ 之间。 + +- **空间复杂度**:$O(1)$。希尔排序中用到的插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量、间隔数 $gap$ 等常数项的变量。 +- **排序稳定性**:在一次插入排序是稳定的,不会改变相等元素的相对顺序,但是在不同的插入排序中,相等元素可能在各自的插入排序中移动。因此,希尔排序方法是一种 **不稳定排序算法**。 + +## 5. 总结 + +希尔排序是插入排序的改进版本。它通过将数组分成多个子数组进行排序,逐步缩小间隔,最终对整个数组进行一次插入排序。这种方法减少了数据移动的次数,提高了排序效率。 + +希尔排序的时间复杂度取决于间隔序列的选择。使用常见的间隔序列时,时间复杂度在 $O(n \log n)$ 到 $O(n^2)$ 之间。空间复杂度是 $O(1)$,因为排序是在原地进行的。 + +希尔排序是不稳定的排序算法,因为在不同的子数组排序过程中,相等元素的相对顺序可能改变。希尔排序适合中等规模的数据排序,比简单插入排序更快,但比快速排序等高级算法稍慢。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0506. 相对名次](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/relative-ranks.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_07_array_merge_sort.md b/docs/01_array/01_07_array_merge_sort.md new file mode 100644 index 00000000..9fce7622 --- /dev/null +++ b/docs/01_array/01_07_array_merge_sort.md @@ -0,0 +1,95 @@ +## 1. 归并排序算法思想 + +> **归并排序(Merge Sort)基本思想**: +> +> 采用经典的分治策略,先递归地将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。 + +## 2. 归并排序算法步骤 + +假设数组的元素个数为 $n$ 个,则归并排序的算法步骤如下: + +1. **分解过程**:先递归地将当前数组平均分成两半,直到子数组长度为 $1$。 + 1. 找到数组中心位置 $mid$,从中心位置将数组分成左右两个子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$。 + 2. 对左右两个子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$ 分别进行递归分解。 + 3. 最终将数组分解为 $n$ 个长度均为 $1$ 的有序子数组。 +2. **归并过程**:从长度为 $1$ 的有序子数组开始,依次将有序数组两两合并,直到合并成一个长度为 $n$ 的有序数组。 + 1. 使用数组变量 $nums$ 存放合并后的有序数组。 + 2. 使用两个指针 $left\underline{\hspace{0.5em}}i$、$right\underline{\hspace{0.5em}}i$ 分别指向两个有序子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$ 的开始位置。 + 3. 比较两个指针指向的元素,将两个有序子数组中较小元素依次存入到结果数组 $nums$ 中,并将指针移动到下一位置。 + 4. 重复步骤 $3$,直到某一指针到达子数组末尾。 + 5. 将另一个子数组中的剩余元素存入到结果数组 $nums$ 中。 + 6. 返回合并后的有序数组 $nums$。 + +我们以 $[0, 5, 7, 3, 1, 6, 8, 4]$ 为例,演示一下归并排序算法的整个步骤。 + +![归并排序算法步骤](http://qcdn.itcharge.cn/images/20230817103814.png) + +## 3. 归并排序代码实现 + +```python +class Solution: + # 合并过程 + def merge(self, left_nums: [int], right_nums: [int]): + nums = [] + left_i, right_i = 0, 0 + while left_i < len(left_nums) and right_i < len(right_nums): + # 将两个有序子数组中较小元素依次插入到结果数组中 + if left_nums[left_i] < right_nums[right_i]: + nums.append(left_nums[left_i]) + left_i += 1 + else: + nums.append(right_nums[right_i]) + right_i += 1 + + # 如果左子数组有剩余元素,则将其插入到结果数组中 + while left_i < len(left_nums): + nums.append(left_nums[left_i]) + left_i += 1 + + # 如果右子数组有剩余元素,则将其插入到结果数组中 + while right_i < len(right_nums): + nums.append(right_nums[right_i]) + right_i += 1 + + # 返回合并后的结果数组 + return nums + + # 分解过程 + def mergeSort(self, nums: [int]) -> [int]: + # 数组元素个数小于等于 1 时,直接返回原数组 + if len(nums) <= 1: + return nums + + mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 + left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 + right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 + return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 + + def sortArray(self, nums: [int]) -> [int]: + return self.mergeSort(nums) +``` + +## 4. 归并排序算法分析 + +- **时间复杂度**:$O(n \times \log n)$。归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度乘积。子算法 `merge(left_nums, right_nums):` 的时间复杂度是 $O(n)$,因此,归并排序算法总的时间复杂度为 $O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。归并排序方法需要用到与参加排序的数组同样大小的辅助空间。因此,算法的空间复杂度为 $O(n)$。 +- **排序稳定性**:因为在两个有序子数组的归并过程中,如果两个有序数组中出现相等元素,`merge(left_nums, right_nums):` 算法能够使前一个数组中那个相等元素先被复制,从而确保这两个元素的相对顺序不发生改变。因此,归并排序算法是一种 **稳定排序算法**。 + +## 5. 总结 + +归并排序采用分治策略,将数组不断拆分为更小的子数组进行排序,再将有序子数组合并成完整的有序数组。这种方法保证了排序的稳定性。 + +归并排序的时间复杂度是 $O(n \log n)$,这是因为它需要 $\log n$ 次分解,每次合并需要 $O(n)$ 时间。空间复杂度是 $O(n)$,因为合并过程需要额外的存储空间。 + +归并排序是稳定的排序算法,在合并过程中相等元素的相对顺序不会改变。它适合处理大规模数据,但需要额外的存储空间是其缺点。归并排序常用于外部排序场景。 + +## 练习题目 + + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0088. 合并两个有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) +- [LCR 170. 交易逆序对的总数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md) +- [0315. 计算右侧小于当前元素的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md) + + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_08_array_quick_sort.md b/docs/01_array/01_08_array_quick_sort.md new file mode 100644 index 00000000..a701de70 --- /dev/null +++ b/docs/01_array/01_08_array_quick_sort.md @@ -0,0 +1,156 @@ +## 1. 快速排序算法思想 + +> **快速排序(Quick Sort)基本思想**: +> +> 采用经典的分治策略,选择数组中某个元素作为基准数,通过一趟排序将数组分为独立的两个子数组,一个子数组中所有元素值都比基准数小,另一个子数组中所有元素值都比基准数大。然后再按照同样的方式递归的对两个子数组分别进行快速排序,以达到整个数组有序。 +> + +## 2. 快速排序算法步骤 + +假设数组的元素个数为 $n$ 个,则快速排序的算法步骤如下: + +1. **哨兵划分**:选取一个基准数,将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。 + 1. 从当前数组中找到一个基准数 $pivot$(这里以当前数组第 $1$ 个元素作为基准数,即 $pivot = nums[low]$)。 + 2. 使用指针 $i$ 指向数组开始位置,指针 $j$ 指向数组末尾位置。 + 3. 从右向左移动指针 $j$,找到第 $1$ 个小于基准值的元素。 + 4. 从左向右移动指针 $i$,找到第 $1$ 个大于基准数的元素。 + 5. 交换指针 $i$、指针 $j$ 指向的两个元素位置。 + 6. 重复第 $3 \sim 5$ 步,直到指针 $i$ 和指针 $j$ 相遇时停止,最后将基准数放到两个子数组交界的位置上。 +2. **递归分解**:完成哨兵划分之后,对划分好的左右子数组分别进行递归排序。 + 1. 按照基准数的位置将数组拆分为左右两个子数组。 + 2. 对每个子数组分别重复「哨兵划分」和「递归分解」,直到各个子数组只有 $1$ 个元素,排序结束。 + +我们以 $[4, 7, 5, 2, 6, 1, 3]$ 为例,演示一下快速排序的整个步骤。 + +我们先来看一下单次「哨兵划分」的过程。 + +::: tabs#partition + +@tab <1> + +![哨兵划分 1](https://qcdn.itcharge.cn/images/20230818175908.png) + +@tab <2> + +![哨兵划分 2](https://qcdn.itcharge.cn/images/20230818175922.png) + +@tab <3> + +![哨兵划分 3](https://qcdn.itcharge.cn/images/20230818175952.png) + +@tab <4> + +![哨兵划分 4](https://qcdn.itcharge.cn/images/20230818180001.png) + +@tab <5> + +![哨兵划分 5](https://qcdn.itcharge.cn/images/20230818180009.png) + +@tab <6> + +![哨兵划分 6](https://qcdn.itcharge.cn/images/20230818180019.png) + +@tab <7> + +![哨兵划分 7](https://qcdn.itcharge.cn/images/20230818180027.png) + +::: + +在经过一次「哨兵划分」过程之后,数组就被划分为左子数组、基准数、右子树组三个独立部分。接下来只要对划分好的左右子数组分别进行递归排序即可完成排序。快速排序算法的整个步骤如下: + +![快速排序算法步骤](https://qcdn.itcharge.cn/images/20230818153642.png) + +## 3. 快速排序代码实现 + +```python +import random + +class Solution: + # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 + def randomPartition(self, nums: [int], low: int, high: int) -> int: + # 随机挑选一个基准数 + i = random.randint(low, high) + # 将基准数与最低位互换 + nums[i], nums[low] = nums[low], nums[i] + # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + return self.partition(nums, low, high) + + # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 + def partition(self, nums: [int], low: int, high: int) -> int: + # 以第 1 位元素为基准数 + pivot = nums[low] + + i, j = low, high + while i < j: + # 从右向左找到第 1 个小于基准数的元素 + while i < j and nums[j] >= pivot: + j -= 1 + # 从左向右找到第 1 个大于基准数的元素 + while i < j and nums[i] <= pivot: + i += 1 + # 交换元素 + nums[i], nums[j] = nums[j], nums[i] + + # 将基准节点放到正确位置上 + nums[i], nums[low] = nums[low], nums[i] + # 返回基准数的索引 + return i + + def quickSort(self, nums: [int], low: int, high: int) -> [int]: + if low < high: + # 按照基准数的位置,将数组划分为左右两个子数组 + pivot_i = self.randomPartition(nums, low, high) + # 对左右两个子数组分别进行递归快速排序 + self.quickSort(nums, low, pivot_i - 1) + self.quickSort(nums, pivot_i + 1, high) + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.quickSort(nums, 0, len(nums) - 1) +``` + +## 4. 快速排序算法分析 + +快速排序算法的时间复杂度主要跟基准数的选择有关。本文中是将当前数组中第 $1$ 个元素作为基准值。 + +在这种选择下,如果参加排序的元素初始时已经有序的情况下,快速排序方法花费的时间最长。也就是会得到最坏时间复杂度。 + +在这种情况下,第 $1$ 趟排序经过 $n - 1$ 次比较以后,将第 $1$ 个元素仍然确定在原来的位置上,并得到 $1$ 个长度为 $n - 1$ 的子数组。第 $2$ 趟排序进过 $n - 2$ 次比较以后,将第 $2$ 个元素确定在它原来的位置上,又得到 $1$ 个长度为 $n - 2$ 的子数组。 + +最终总的比较次数为 $(n − 1) + (n − 2) + … + 1 = \frac{n(n − 1)}{2}$。因此这种情况下的时间复杂度为 $O(n^2)$,也是最坏时间复杂度。 + +我们可以改进一下基准数的选择。如果每次我们选中的基准数恰好能将当前数组平分为两份,也就是刚好取到当前数组的中位数。 + +在这种选择下,每一次都将数组从 $n$ 个元素变为 $\frac{n}{2}$ 个元素。此时的时间复杂度公式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n)$。根据主定理可以得出 $T(n) = O(n \times \log n)$,也是最佳时间复杂度。 + +而在平均情况下,我们可以从当前数组中随机选择一个元素作为基准数。这样,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log n)$,也就是平均时间复杂度。 + +下面来总结一下: + +- **最佳时间复杂度**:$O(n \times \log n)$。每一次选择的基准数都是当前数组的中位数,此时算法时间复杂度满足的递推式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n)$,由主定理可得 $T(n) = O(n \times \log n)$。 +- **最坏时间复杂度**:$O(n^2)$。每一次选择的基准数都是数组的最终位置上的值,此时算法时间复杂度满足的递推式为 $T(n) = T(n - 1) + \Theta(n)$,累加可得 $T(n) = O(n^2)$。 +- **平均时间复杂度**:$O(n \times \log n)$。在平均情况下,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序数组的首、尾位置。最坏的情况下,空间复杂度为 $O(n)$。如果对算法进行一些改写,在一趟排序之后比较被划分所得到的两个子数组的长度,并且首先对长度较短的子数组进行快速排序,这时候需要的空间复杂度可以达到 $O(log_2 n)$。 +- **排序稳定性**:在进行哨兵划分时,基准数可能会被交换至相等元素的右侧。因此,快速排序是一种 **不稳定排序算法**。 + +## 5. 总结 + +快速排序使用分治策略,通过选取基准数将数组分成两部分,一部分比基准数小,一部分比基准数大。然后对这两部分递归地进行相同操作。 + +快速排序的时间复杂度取决于基准数的选择。最好情况是每次都能平分数组,时间复杂度为 $O(n \log n)$。最坏情况是每次选择的基准数都是极值,时间复杂度为 $O(n^2)$。平均时间复杂度为 $O(n \log n)$。空间复杂度为 $O(n)$,优化后可达 $O(\log n)$。 + +快速排序是不稳定的排序算法,因为在交换过程中可能改变相等元素的相对顺序。它的优势在于平均情况下效率高,适合处理大规模数据。但最坏情况下的性能较差,可以通过随机选择基准数来优化。 + +快速排序在实际应用中很常见,是许多编程语言内置排序函数的实现基础。它的效率通常比其他 $O(n \log n)$ 算法更高,因为它的常数因子较小。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0169. 多数元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[快速排序 - OI Wiki](https://oi-wiki.org/basic/quick-sort/) diff --git a/docs/01_array/01_09_array_heap_sort.md b/docs/01_array/01_09_array_heap_sort.md new file mode 100644 index 00000000..73530b41 --- /dev/null +++ b/docs/01_array/01_09_array_heap_sort.md @@ -0,0 +1,387 @@ +## 1. 堆结构 + +「堆排序(Heap sort)」是一种基于「堆结构」实现的高效排序算法。在介绍「堆排序」之前,我们先来了解一下什么是「堆结构」。 + +### 1.1 堆的定义 + +> **堆(Heap)**:一种满足以下两个条件之一的完全二叉树: +> +> - **大顶堆(Max Heap)**:任意节点值 ≥ 其子节点值。 +> - **小顶堆(Min Heap)**:任意节点值 ≤ 其子节点值。 + +![堆结构](https://qcdn.itcharge.cn/images/20230823133321.png) + +### 1.2 堆的存储结构 + +堆的逻辑结构就是一颗完全二叉树。如下图所示: + +![堆的逻辑结构](https://qcdn.itcharge.cn/images/202405092006120.png) + +而我们在「07.树 - 01.二叉树 - 01.树与二叉树的基础知识」章节中学过,对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构(数组)的形式来表示完全二叉树,能够充分利用存储空间。如下图所示: + +![使用顺序存储结构(数组)表示堆](https://qcdn.itcharge.cn/images/202405092007823.png) + +当我们使用顺序存储结构(即数组)来表示堆时,堆中元素的节点编号与数组的索引关系为: + +- 如果某二叉树节点(非叶子节点)的下标为 $i$,那么其左孩子节点下标为 $2 \times i + 1$,右孩子节点下标为 $2 \times i + 2$。 +- 如果某二叉树节点(非根结点)的下标为 $i$,那么其根节点下标为 $\lfloor \frac{i - 1}{2} \rfloor$(向下取整)。 + +```python +class MaxHeap: + def __init__(self): + self.max_heap = [] +``` + +### 1.3 访问堆顶元素 + +> **访问堆顶元素**:指的是从堆结构中获取位于堆顶的元素。 + +在堆中,堆顶元素位于根节点,当我们使用顺序存储结构(即数组)来表示堆时,堆顶元素就是数组的首个元素。 + +```python +class MaxHeap: + ...... + def peek(self) -> int: + # 大顶堆为空 + if not self.max_heap: + return None + # 返回堆顶元素 + return self.max_heap[0] +``` + +访问堆顶元素不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 + +### 1.4 向堆中插入元素 + +> **向堆中插入元素**:指的将一个新的元素添加到堆中,调整堆结构,以保持堆的特性不变。 + +向堆中插入元素的步骤如下: + +1. 将新元素添加到堆的末尾,保持完全二叉树的结构。 +2. 从新插入的元素节点开始,将该节点与其父节点进行比较。 + 1. 如果新节点的值大于其父节点的值,则交换它们,以保持最大堆的特性。 + 2. 如果新节点的值小于等于其父节点的值,说明已满足最大堆的特性,此时结束。 +3. 重复上述比较和交换步骤,直到新节点不再大于其父节点,或者达到了堆的根节点。 + +这个过程称为「上移调整(Shift Up)」。因为新插入的元素会逐步向堆的上方移动,直到找到了合适的位置,保持堆的有序性。 + +::: tabs#heapPush + +@tab <1> + +![向堆中插入元素1](https://qcdn.itcharge.cn/images/20230831111022.png) + +@tab <2> + +![向堆中插入元素2](https://qcdn.itcharge.cn/images/20230831111036.png) + +@tab <3> + +![向堆中插入元素3](https://qcdn.itcharge.cn/images/20230831111052.png) + +@tab <4> + +![向堆中插入元素4](https://qcdn.itcharge.cn/images/20230831111103.png) + +@tab <5> + +![向堆中插入元素5](https://qcdn.itcharge.cn/images/20230831112321.png) + +@tab <6> + +![向堆中插入元素6](https://qcdn.itcharge.cn/images/20230831112328.png) + +@tab <7> + +![向堆中插入元素7](https://qcdn.itcharge.cn/images/20230831134124.png) + +::: + +```python +class MaxHeap: + ...... + def push(self, val: int): + # 将新元素添加到堆的末尾 + self.max_heap.append(val) + + size = len(self.max_heap) + # 从新插入的元素节点开始,进行上移调整 + self.__shift_up(size - 1) + + def __shift_up(self, i: int): + while (i - 1) // 2 >= 0 and self.max_heap[i] > self.max_heap[(i - 1) // 2]: + self.max_heap[i], self.max_heap[(i - 1) // 2] = self.max_heap[(i - 1) // 2], self.max_heap[i] + i = (i - 1) // 2 +``` + +在最坏情况下,「向堆中插入元素」的时间复杂度为 $O(\log n)$,其中 $n$ 是堆中元素的数量,这是因为堆的高度是 $\log n$。 + +### 1.5 删除堆顶元素 + +> **删除堆顶元素**:指的是从堆中移除位于堆顶的元素,并重新调整对结果,以保持堆的特性不变。 + +删除堆顶元素的步骤如下: + +1. 将堆顶元素(即根节点)与堆的末尾元素交换。 +2. 移除堆末尾的元素(之前的堆顶),即将其从堆中剔除。 +3. 从新的堆顶元素开始,将其与其较大的子节点进行比较。 + 1. 如果当前节点的值小于其较大的子节点,则将它们交换。这一步是为了将新的堆顶元素「下沉」到适当的位置,以保持最大堆的特性。 + 2. 如果当前节点的值大于等于其较大的子节点,说明已满足最大堆的特性,此时结束。 +4. 重复上述比较和交换步骤,直到新的堆顶元素不再小于其子节点,或者达到了堆的底部。 + +这个过程称为「下移调整(Shift Down)」。因为新的堆顶元素会逐步向堆的下方移动,直到找到了合适的位置,保持堆的有序性。 + +::: tabs#heapPop + +@tab <1> + +![删除堆顶元素 1](https://qcdn.itcharge.cn/images/20230831134148.png) + +@tab <2> + +![删除堆顶元素 2](https://qcdn.itcharge.cn/images/20230831134156.png) + +@tab <3> + +![删除堆顶元素 3](https://qcdn.itcharge.cn/images/20230831134205.png) + +@tab <4> + +![删除堆顶元素 4](https://qcdn.itcharge.cn/images/20230831134214.png) + +@tab <5> + +![删除堆顶元素 5](https://qcdn.itcharge.cn/images/20230831134221.png) + +@tab <6> + +![删除堆顶元素 6](https://qcdn.itcharge.cn/images/20230831134229.png) + +@tab <7> + +![删除堆顶元素 7](https://qcdn.itcharge.cn/images/20230831134237.png) + +::: + +```python +class MaxHeap: + ...... + def pop(self) -> int: + # 堆为空 + if not self.max_heap: + raise IndexError("堆为空") + + size = len(self.max_heap) + self.max_heap[0], self.max_heap[size - 1] = self.max_heap[size - 1], self.max_heap[0] + # 删除堆顶元素 + val = self.max_heap.pop() + # 节点数减 1 + size -= 1 + + # 下移调整 + self.__shift_down(0, size) + + # 返回堆顶元素 + return val + + + def __shift_down(self, i: int, n: int): + while 2 * i + 1 < n: + # 左右子节点编号 + left, right = 2 * i + 1, 2 * i + 2 + + # 找出左右子节点中的较大值节点编号 + if 2 * i + 2 >= n: + # 右子节点编号超出范围(只有左子节点 + larger = left + else: + # 左子节点、右子节点都存在 + if self.max_heap[left] >= self.max_heap[right]: + larger = left + else: + larger = right + + # 将当前节点值与其较大的子节点进行比较 + if self.max_heap[i] < self.max_heap[larger]: + # 如果当前节点值小于其较大的子节点,则将它们交换 + self.max_heap[i], self.max_heap[larger] = self.max_heap[larger], self.max_heap[i] + i = larger + else: + # 如果当前节点值大于等于于其较大的子节点,此时结束 + break +``` + +「删除堆顶元素」的时间复杂度通常为$O(\log n)$,其中 $n$ 是堆中元素的数量,因为堆的高度是 $\log n$。 + +## 2. 堆排序 + +### 2.1 堆排序算法思想 + +> **堆排序(Heap sort)基本思想**: +> +> 借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 + +### 2.2 堆排序算法步骤 + +1. **构建初始大顶堆**: + 1. 定义一个数组实现的堆结构,将原始数组的元素依次存入堆结构的数组中(初始顺序不变)。 + 2. 从数组的中间位置开始,从右至左,依次通过「下移调整」将数组转换为一个大顶堆。 + +2. **交换元素,调整堆**: + 1. 交换堆顶元素(第 $1$ 个元素)与末尾(最后 $1$ 个元素)的位置,交换完成后,堆的长度减 $1$。 + 2. 交换元素之后,由于堆顶元素发生了改变,需要从根节点开始,对当前堆进行「下移调整」,使其保持堆的特性。 + +3. **重复交换和调整堆**: + 1. 重复第 $2$ 步,直到堆的大小为 $1$ 时,此时大顶堆的数组已经完全有序。 + +::: tabs#heapSortBuildMaxHeap + +@tab <1> + +![1. 构建初始大顶堆 1](https://qcdn.itcharge.cn/images/20230831151620.png) + +@tab <2> + +![1. 构建初始大顶堆 2](https://qcdn.itcharge.cn/images/20230831151641.png) + +@tab <3> + +![1. 构建初始大顶堆 3](https://qcdn.itcharge.cn/images/20230831151703.png) + +@tab <4> + +![1. 构建初始大顶堆 4](https://qcdn.itcharge.cn/images/20230831151715.png) + +@tab <5> + +![1. 构建初始大顶堆 5](https://qcdn.itcharge.cn/images/20230831151725.png) + +@tab <6> + +![1. 构建初始大顶堆 6](https://qcdn.itcharge.cn/images/20230831151735.png) + +@tab <7> + +![1. 构建初始大顶堆 7](https://qcdn.itcharge.cn/images/20230831151749.png) + +::: + +::: tabs#heapSortExchangeVal + +@tab <1> + +![2. 交换元素,调整堆 1](https://qcdn.itcharge.cn/images/20230831162335.png) + +@tab <2> + +![2. 交换元素,调整堆 2](https://qcdn.itcharge.cn/images/20230831162346.png) + +@tab <3> + +![2. 交换元素,调整堆 3](https://qcdn.itcharge.cn/images/20230831162359.png) + +@tab <4> + +![2. 交换元素,调整堆 4](https://qcdn.itcharge.cn/images/20230831162408.png) + +@tab <5> + +![2. 交换元素,调整堆 5](https://qcdn.itcharge.cn/images/20230831162416.png) + +@tab <6> + +![2. 交换元素,调整堆 6](https://qcdn.itcharge.cn/images/20230831162424.png) + +@tab <7> + +![2. 交换元素,调整堆 7](https://qcdn.itcharge.cn/images/20230831162431.png) + +@tab <8> + +![2. 交换元素,调整堆 8](https://qcdn.itcharge.cn/images/20230831162440.png) + +@tab <9> + +![2. 交换元素,调整堆 9](https://qcdn.itcharge.cn/images/20230831162449.png) + +@tab <10> + +![2. 交换元素,调整堆 10](https://qcdn.itcharge.cn/images/20230831162457.png) + +@tab <11> + +![https://qcdn.](https://qcdn.itcharge.cn/images/20230831162505.png) + +@tab <12> + +![2. 交换元素,调整堆 12](https://qcdn.itcharge.cn/images/20230831162512.png) + +::: + +### 2.3 堆排序代码实现 + +```python +class MaxHeap: + ...... + def __buildMaxHeap(self, nums: [int]): + size = len(nums) + # 先将数组 nums 的元素按顺序添加到 max_heap 中 + for i in range(size): + self.max_heap.append(nums[i]) + + # 从最后一个非叶子节点开始,进行下移调整 + for i in range((size - 2) // 2, -1, -1): + self.__shift_down(i, size) + + def maxHeapSort(self, nums: [int]) -> [int]: + # 根据数组 nums 建立初始堆 + self.__buildMaxHeap(nums) + + size = len(self.max_heap) + for i in range(size - 1, -1, -1): + # 交换根节点与当前堆的最后一个节点 + self.max_heap[0], self.max_heap[i] = self.max_heap[i], self.max_heap[0] + # 从根节点开始,对当前堆进行下移调整 + self.__shift_down(0, i) + + # 返回排序后的数组 + return self.max_heap + +class Solution: + def maxHeapSort(self, nums: [int]) -> [int]: + return MaxHeap().maxHeapSort(nums) + + def sortArray(self, nums: [int]) -> [int]: + return self.maxHeapSort(nums) + +print(Solution().sortArray([10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14])) +``` + +### 2.4 堆排序算法分析 + +- **时间复杂度**:$O(n \times \log n)$。 + - 堆积排序的时间主要花费在两个方面:「建立初始堆」和「下移调整」。 + - 设原始数组所对应的完全二叉树深度为 $d$,算法由两个独立的循环组成: + 1. 在第 $1$ 个循环构造初始堆积时,从 $i = d - 1$ 层开始,到 $i = 1$ 层为止,对每个分支节点都要调用一次调整堆算法,而一次调整堆算法,对于第 $i$ 层一个节点到第 $d$ 层上建立的子堆积,所有节点可能移动的最大距离为该子堆积根节点移动到最后一层(第 $d$ 层) 的距离,即 $d - i$。而第 $i$ 层上节点最多有 $2^{i-1}$ 个,所以每一次调用调整堆算法的最大移动距离为 $2^{i-1} * (d-i)$。因此,堆积排序算法的第 $1$ 个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和,即:$\sum_{i = d - 1}^1 2^{i-1} (d-i) = \sum_{j = 1}^{d-1} 2^{d-j-1} \times j = \sum_{j = 1}^{d-1} 2^{d-1} \times {j \over 2^j} \le n \times \sum_{j = 1}^{d-1} {j \over 2^j} < 2 \times n$。这一部分的时间花费为 $O(n)$。 + 2. 在第 $2$ 个循环中,每次调用调整堆算法一次,节点移动的最大距离为这棵完全二叉树的深度 $d = \lfloor \log_2(n) \rfloor + 1$,一共调用了 $n - 1$ 次调整堆算法,所以,第 $2$ 个循环的时间花费为 $(n-1)(\lfloor \log_2 (n)\rfloor + 1) = O(n \times \log n)$。 + - 因此,堆积排序的时间复杂度为 $O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。由于在堆积排序中只需要一个记录大小的辅助空间,因此,堆积排序的空间复杂度为:$O(1)$。 +- **排序稳定性**:在进行「下移调整」时,相等元素的相对位置可能会发生变化。因此,堆排序是一种 **不稳定排序算法**。 + +## 5. 总结 + +堆排序利用堆结构进行排序。堆是一种完全二叉树,分为大顶堆和小顶堆。大顶堆中每个节点的值都大于等于其子节点值,小顶堆中每个节点的值都小于等于其子节点值。 + +堆排序分为两个主要步骤:构建初始堆和交换调整堆。首先将数组构建成大顶堆,然后重复取出堆顶元素(最大值)并调整堆结构。每次取出堆顶元素后,将堆末尾元素移到堆顶,再进行下移调整保持堆的性质。 + +堆排序的时间复杂度为 $O(n \log n)$。构建初始堆需要 $O(n)$ 时间,每次调整堆需要 $O(\log n)$ 时间,共进行 $n$ 次调整。空间复杂度为 $O(1)$,因为排序是原地进行的。 + +堆排序是不稳定的排序算法,因为在调整堆的过程中可能改变相等元素的相对顺序。它的优势在于不需要额外空间,适合处理大规模数据。但相比快速排序,堆排序的常数因子较大,实际应用中可能稍慢。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0215. 数组中的第K个最大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) +- [LCR 159. 库存管理 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_10_array_counting_sort.md b/docs/01_array/01_10_array_counting_sort.md new file mode 100644 index 00000000..37e8292d --- /dev/null +++ b/docs/01_array/01_10_array_counting_sort.md @@ -0,0 +1,78 @@ +## 1. 计数排序算法思想 + +> **计数排序(Counting Sort)基本思想**: +> +> 通过统计数组中每个元素在数组中出现的次数,根据这些统计信息将数组元素有序的放置到正确位置,从而达到排序的目的。 + +## 2. 计数排序算法步骤 + +1. **计算排序范围**:遍历数组,找出待排序序列中最大值元素 $nums\underline{\hspace{0.5em}}max$ 和最小值元素 $nums\underline{\hspace{0.5em}}min$,计算出排序范围为 $nums\underline{\hspace{0.5em}}max - nums\underline{\hspace{0.5em}}min + 1$。 +2. **定义计数数组**:定义一个大小为排序范围的计数数组 $counts$,用于统计每个元素的出现次数。其中: + 1. 数组的索引值 $num - nums\underline{\hspace{0.5em}}min$ 表示元素的值为 $num$。 + 2. 数组的值 $counts[num - nums\underline{\hspace{0.5em}}min]$ 表示元素 $num$ 的出现次数。 + +3. **对数组元素进行计数统计**:遍历待排序数组 $nums$,对每个元素在计数数组中进行计数,即将待排序数组中「每个元素值减去最小值」作为索引,将「对计数数组中的值」加 $1$,即令 $counts[num - nums\underline{\hspace{0.5em}}min]$ 加 $1$。 +4. **生成累积计数数组**:从 $counts$ 中的第 $1$ 个元素开始,每一项累家前一项和。此时 $counts[num - nums\underline{\hspace{0.5em}}min]$ 表示值为 $num$ 的元素在排序数组中最后一次出现的位置。 +5. **逆序填充目标数组**:逆序遍历数组 $nums$,将每个元素 $num$ 填入正确位置。 + 1. 将其填充到结果数组 $res$ 的索引 $counts[num - nums\underline{\hspace{0.5em}}min]$ 处。 + 2. 放入后,令累积计数数组中对应索引减 $1$,从而得到下个元素 $num$ 的放置位置。 + +我们以 $[3, 0, 4, 2, 5, 1, 3, 1, 4, 5]$ 为例,演示一下计数排序算法的整个步骤。 + +![计数排序算法步骤](https://qcdn.itcharge.cn/images/20230822135634.png) + +## 3. 计数排序代码实现 + +```python +class Solution: + def countingSort(self, nums: [int]) -> [int]: + # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min + nums_min, nums_max = min(nums), max(nums) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 + size = nums_max - nums_min + 1 + counts = [0 for _ in range(size)] + + # 统计值为 num 的元素出现的次数 + for num in nums: + counts[num - nums_min] += 1 + + # 生成累积计数数组 + for i in range(1, size): + counts[i] += counts[i - 1] + + # 反向填充目标数组 + res = [0 for _ in range(len(nums))] + for i in range(len(nums) - 1, -1, -1): + num = nums[i] + # 根据累积计数数组,将 num 放在数组对应位置 + res[counts[num - nums_min] - 1] = num + # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 + counts[nums[i] - nums_min] -= 1 + + return res + + def sortArray(self, nums: [int]) -> [int]: + return self.countingSort(nums) +``` + +## 4. 计数排序算法分析 + +- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序数组的值域。 +- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。由于用于计数的数组 $counts$ 的长度取决于待排序数组中数据的范围(大小等于待排序数组最大值减去最小值再加 $1$)。所以计数排序算法对于数据范围很大的数组,需要大量的内存。 +- **计数排序适用情况**:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。 +- **排序稳定性**:由于向结果数组中填充元素时使用的是逆序遍历,可以避免改变相等元素之间的相对顺序。因此,计数排序是一种 **稳定排序算法**。 + +## 5. 总结 + +计数排序通过统计元素出现次数来实现排序。它先找出数组中的最大值和最小值,确定排序范围。然后统计每个元素的出现次数,计算累积次数,最后根据统计信息将元素放到正确位置。 + +计数排序的时间复杂度是 $O(n + k)$,其中 $n$ 是元素个数,$k$ 是数值范围。空间复杂度是 $O(k)$。当 $k$ 值较小时效率很高,但当数值范围很大时会消耗较多内存。 + +计数排序适合整数排序,不适合字符串等复杂数据。它是稳定的排序算法,能保持相等元素的原始顺序。在实际应用中,计数排序常用于数据范围不大的整数排序场景。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [1122. 数组的相对排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/relative-sort-array.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_11_array_bucket_sort.md b/docs/01_array/01_11_array_bucket_sort.md new file mode 100644 index 00000000..ed8d3d08 --- /dev/null +++ b/docs/01_array/01_11_array_bucket_sort.md @@ -0,0 +1,82 @@ +## 1. 桶排序算法思想 + +> **桶排序(Bucket Sort)基本思想**: +> +> 将待排序数组中的元素分散到若干个「桶」中,然后对每个桶中的元素再进行单独排序。 + +## 2. 桶排序算法步骤 + +1. **确定桶的数量**:根据待排序数组的值域范围,将数组划分为 $k$ 个桶,每个桶可以看做是一个范围区间。 +2. **分配元素**:遍历待排序数组元素,将每个元素根据大小分配到对应的桶中。 +3. **对每个桶进行排序**:对每个非空桶内的元素单独排序(使用插入排序、归并排序、快排排序等算法)。 +4. **合并桶内元素**:将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。 + +我们以 $[39, 49, 8, 13, 22, 15, 10, 30, 5, 44]$ 为例,演示一下桶排序算法的整个步骤。 + +![桶排序算法步骤](https://qcdn.itcharge.cn/images/20230822153701.png) + +## 3. 桶排序代码实现 + +```python +class Solution: + def insertionSort(self, nums: [int]) -> [int]: + # 遍历无序区间 + for i in range(1, len(nums)): + temp = nums[i] + j = i + # 从右至左遍历有序区间 + while j > 0 and nums[j - 1] > temp: + # 将有序区间中插入位置右侧的元素依次右移一位 + nums[j] = nums[j - 1] + j -= 1 + # 将该元素插入到适当位置 + nums[j] = temp + + return nums + + def bucketSort(self, nums: [int], bucket_size=5) -> [int]: + # 计算待排序序列中最大值元素 nums_max、最小值元素 nums_min + nums_min, nums_max = min(nums), max(nums) + # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 + bucket_count = (nums_max - nums_min) // bucket_size + 1 + # 定义桶数组 buckets + buckets = [[] for _ in range(bucket_count)] + + # 遍历待排序数组元素,将每个元素根据大小分配到对应的桶中 + for num in nums: + buckets[(num - nums_min) // bucket_size].append(num) + + # 对每个非空桶内的元素单独排序,排序之后,按照区间顺序依次合并到 res 数组中 + res = [] + for bucket in buckets: + self.insertionSort(bucket) + res.extend(bucket) + + # 返回结果数组 + return res + + def sortArray(self, nums: [int]) -> [int]: + return self.bucketSort(nums) +``` + +## 4. 桶排序算法分析 + +- **时间复杂度**:$O(n)$。当输入元素个数为 $n$,桶的个数是 $m$ 时,每个桶里的数据就是 $k = \frac{n}{m}$ 个。每个桶内排序的时间复杂度为 $O(k \times \log k)$。$m$ 个桶就是 $m \times O(k \times \log k) = m \times O(\frac{n}{m} \times \log \frac{n}{m}) = O(n \times \log \frac{n}{m})$。当桶的个数 $m$ 接近于数据个数 $n$ 时,$\log \frac{n}{m}$ 就是一个较小的常数,所以排序桶排序时间复杂度接近于 $O(n)$。 +- **空间复杂度**:$O(n + m)$。由于桶排序使用了辅助空间,所以桶排序的空间复杂度是 $O(n + m)$。 +- **排序稳定性**:桶排序的稳定性取决于桶内使用的排序算法。如果桶内使用稳定的排序算法(比如插入排序算法),并且在合并桶的过程中保持相等元素的相对顺序不变,则桶排序是一种 **稳定排序算法**。反之,则桶排序是一种 **不稳定排序算法**。 + +## 5. 总结 + +桶排序将元素分散到多个桶中,每个桶单独排序后再合并。它先确定桶的数量和范围,把元素分配到对应桶中,对每个桶排序,最后合并所有桶。 + +桶排序的时间复杂度接近 $O(n)$,在数据分布均匀时效率很高。空间复杂度是 $O(n + m)$,需要额外存储桶。排序的稳定性取决于桶内使用的排序算法。 + +桶排序适合数据分布均匀的情况,当数据集中在少数桶时会降低效率。实际应用中常用于外部排序和处理大数据量的场景。合理设置桶的数量和范围对性能很重要。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0220. 存在重复元素 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) +- [0164. 最大间距](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_12_array_radix_sort.md b/docs/01_array/01_12_array_radix_sort.md new file mode 100644 index 00000000..f21b2783 --- /dev/null +++ b/docs/01_array/01_12_array_radix_sort.md @@ -0,0 +1,73 @@ +## 1. 基数排序算法思想 + +> **基数排序(Radix Sort)基本思想**: +> +> 将整数按位数切割成不同的数字,然后从低位开始,依次到高位,逐位进行排序,从而达到排序的目的。 + +## 2. 基数排序算法步骤 + +基数排序算法可以采用「最低位优先法(Least Significant Digit First)」或者「最高位优先法(Most Significant Digit first)」。最常用的是「最低位优先法」。 + +下面我们以最低位优先法为例,讲解一下算法步骤。 + +1. **确定排序的最大位数**:遍历数组元素,获取数组最大值元素,并取得对应位数。 +2. **从最低位(个位)开始,到最高位为止,逐位对每一位进行排序**: + 1. 定义一个长度为 $10$ 的桶数组 $buckets$,每个桶分别代表 $0 \sim 9$ 中的 $1$ 个数字。 + 2. 按照每个元素当前位上的数字,将元素放入对应数字的桶中。 + 3. 清空原始数组,然后按照桶的顺序依次取出对应元素,重新加入到原始数组中。 + + +我们以 $[692, 924, 969, 503, 871, 704, 542, 436]$ 为例,演示一下基数排序算法的整个步骤。 + +![基数排序算法步骤](https://qcdn.itcharge.cn/images/20230822171758.png) + +## 3. 基数排序代码实现 + +```python +class Solution: + def radixSort(self, nums: [int]) -> [int]: + # 桶的大小为所有元素的最大位数 + size = len(str(max(nums))) + + # 从最低位(个位)开始,逐位遍历每一位 + for i in range(size): + # 定义长度为 10 的桶数组 buckets,每个桶分别代表 0 ~ 9 中的 1 个数字。 + buckets = [[] for _ in range(10)] + # 遍历数组元素,按照每个元素当前位上的数字,将元素放入对应数字的桶中。 + for num in nums: + buckets[num // (10 ** i) % 10].append(num) + # 清空原始数组 + nums.clear() + # 按照桶的顺序依次取出对应元素,重新加入到原始数组中。 + for bucket in buckets: + for num in bucket: + nums.append(num) + + # 完成排序,返回结果数组 + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.radixSort(nums) +``` + +## 4. 基数排序算法分析 + +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 +- **排序稳定性**:基数排序采用的桶排序是稳定的。基数排序是一种 **稳定排序算法**。 + +## 5. 总结 + +基数排序按照数字的每一位进行排序。它从最低位开始,逐位比较,直到最高位。每次排序时,将数字分配到 $0 \sim 9$ 的桶中,然后按顺序收集。 + +基数排序的时间复杂度是 $O(n \times k)$,$n$ 是元素数量,$k$ 是最大位数。空间复杂度是 $O(n + k)$。它是稳定的排序算法,能保持相同数字的相对顺序。 + +基数排序适合处理位数不多的整数排序。当数字范围很大但位数较少时效率较高。实际应用中常用于电话号码、身份证号等固定位数数据的排序。 + +## 练习题目 + +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0164. 最大间距](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) +- [0561. 数组拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/array-partition.md) + +- [排序算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_13_array_binary_search_01.md b/docs/01_array/01_13_array_binary_search_01.md new file mode 100644 index 00000000..f77ca0ac --- /dev/null +++ b/docs/01_array/01_13_array_binary_search_01.md @@ -0,0 +1,166 @@ +## 1. 二分查找算法介绍 + +### 1.1 二分查找算法简介 + +> **二分查找算法(Binary Search Algorithm)**:也叫做折半查找算法、对数查找算法,是一种用于在有序数组中查找特定元素的高效搜索算法。 + +二分查找的基本算法思想为:通过确定目标元素所在的区间范围,反复将查找范围减半,直到找到元素或找不到该元素为止。 + +### 1.2 二分查找算法步骤 + +以下是二分查找算法的基本步骤: + +1. **初始化**:首先,确定要查找的有序数据集合。可以是一个数组或列表,确保其中的元素按照升序或者降序排列。 +2. **确定查找范围**:将整个有序数组集合的查找范围确定为整个数组范围区间,即左边界 $left$ 和右边界 $right$。 +3. **计算中间元素**:根据 $mid = \lfloor (left + right) / 2 \rfloor$ 计算出中间元素下标位置 $mid$。 +4. **比较中间元素**:将目标元素 $target$ 与中间元素 $nums[mid]$ 进行比较: + 1. 如果 $target == nums[mid]$,说明找到 $target$,因此返回中间元素的下标位置 $mid$。 + 2. 如果 $target < nums[mid]$,说明目标元素在左半部分($[left, mid - 1]$),更新右边界为中间元素的前一个位置,即 $right = mid - 1$。 + 3. 如果 $target > nums[mid]$,说明目标元素在右半部分($[mid + 1, right]$),更新左边界为中间元素的后一个位置,即 $left = mid + 1$。 + +5. 重复步骤 $3 \sim 4$,直到找到目标元素时返回中间元素下标位置,或者查找范围缩小为空(左边界大于右边界),表示目标元素不存在,此时返回 $-1$。 + +举个例子来说,以在有序数组 $[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]$ 中查找目标元素 $6$ 来说,使用二分查找算法的步骤如下: + +1. **确定查找范围**:初始时左边界 $left = 0$(数组的起始位置),$right = 10$(数组的末尾位置)。此时查找范围为 $[0, 10]$。 +2. **计算中间元素**:中间元素下标位置 $mid = (0 + 10) \div 2 = 5$,对应元素为 $nums[5] == 5$。 +3. **比较中间元素**:因为 $6 > nums[5]$,所以目标元素可能在右半部分,更新左边界为中间元素的后一个位置,即 $left = 6$。此时查找范围为 $[6, 10]$。 +4. **计算中间元素**:中间元素下标位置 $mid = (6 + 10) \div 2 = 8$,对应元素为 $nums[8] == 8$。 +5. **比较中间元素**:因为 $6 < nums[8]$,所以目标元素可能在左半部分,更新右边界为中间元素的前一个位置,即 $right = 7$。此时查找范围为 $[6, 7]$。 +6. **计算中间元素**:中间元素下标位置 $mid = (6 + 7) \div 2 = 6$(向下取整),对应元素为 $nums[6] == 6$。 +7. **比较中间元素**:因为 $6 == nums[6]$,正好是我们正在查找的目标元素,此时返回中间元素的下标位置,算法结束。 + +于是我们发现,对于一个长度为 $10$ 的有序数组,我们只进行了 $3$ 次查找就找到了目标元素。而如果是按照顺序依次遍历数组,则在最坏情况下,我们可能需要查找 $10$ 次才能找到目标元素。 + +::: tabs#BinarySearch + +@tab <1> + +![二分查找算法 1](https://qcdn.itcharge.cn/images/20230907144632.png) + +@tab <2> + +![二分查找算法 2](https://qcdn.itcharge.cn/images/20230906133742.png) + +@tab <3> + +![二分查找算法 3](https://qcdn.itcharge.cn/images/20230906133758.png) + +@tab <4> + +![二分查找算法 4](https://qcdn.itcharge.cn/images/20230906133809.png) + +@tab <5> + +![二分查找算法 5](https://qcdn.itcharge.cn/images/20230906133820.png) + +@tab <6> + +![二分查找算法 6](https://qcdn.itcharge.cn/images/20230906133830.png) + +@tab <7> + +![二分查找算法 7](https://qcdn.itcharge.cn/images/20230906133839.png) + +@tab <8> + +![二分查找算法 8](https://qcdn.itcharge.cn/images/20230906133848.png) + +::: + +### 1.2 二分查找算法思想 + +二分查找算法是经典的 **「减而治之」** 的思想。 + +这里的 **「减」** 是减少问题规模的意思,**「治」** 是解决问题的意思。**「减」** 和 **「治」** 结合起来的意思就是 **「排除法解决问题」**。即:**每一次查找,排除掉一定不存在目标元素的区间,在剩下可能存在目标元素的区间中继续查找。** + +每一次通过一些条件判断,将待搜索的区间逐渐缩小,以达到「减少问题规模」的目的。而于问题的规模是有限的,经过有限次的查找,最终会查找到目标元素或者查找失败。 + +## 2. 简单二分查找 + +下面通过一个简单的例子来讲解下二分查找的思路和代码。 + +- 题目链接:[704. 二分查找](https://leetcode.cn/problems/binary-search/) + +### 2.1 题目大意 + +**描述**:给定一个升序的数组 $nums$,和一个目标值 $target$。 + +**要求**:返回 $target$ 在数组中的位置,如果找不到,则返回 $-1$。 + +**说明**: + +- 你可以假设 $nums$ 中的所有元素是不重复的。 +- $n$ 将在 $[1, 10000]$ 之间。 +- $nums$ 的每个元素都将在 $[-9999, 9999]$之间。 + +**示例**: + +```python +输入: nums = [-1,0,3,5,9,12], target = 9 +输出: 4 +解释: 9 出现在 nums 中并且下标为 4 + +输入: nums = [-1,0,3,5,9,12], target = 2 +输出: -1 +解释: 2 不存在 nums 中因此返回 -1 +``` + +### 2.2 解题思路 + +#### 思路 1:二分查找 + +1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 +2. 取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 + 1. 如果 $target == nums[mid]$,则返回中心位置。 + 2. 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 + 3. 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 +3. 如果左边界大于右边界,查找范围缩小为空,说明目标元素不存在,此时返回 $-1$。 + +#### 思路 1:代码 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + # 在区间 [left, right] 内查找 target + while left <= right: + # 取区间中间节点 + mid = (left + right) // 2 + # 如果找到目标值,则直接返回中心位置 + if nums[mid] == target: + return mid + # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 + elif nums[mid] < target: + left = mid + 1 + # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 + else: + right = mid - 1 + # 未搜索到元素,返回 -1 + return -1 +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + +## 3. 总结 + +二分查找是一种高效的搜索算法,适用于有序数组。它的工作原理是通过不断将搜索范围减半来快速定位目标元素。 + +二分查找的步骤包括初始化查找范围、计算中间元素、比较中间元素与目标值,并根据比较结果调整查找范围。如果中间元素等于目标值,返回其位置。如果中间元素小于目标值,搜索右半部分。如果中间元素大于目标值,搜索左半部分。重复这个过程直到找到目标值或确定目标值不存在。 + +二分查找的时间复杂度是 $O(\log n)$,比顺序查找的 $O(n)$ 更快。它的空间复杂度是 $O(1)$,因为它不需要额外的存储空间。 + +二分查找的思想是减而治之,通过排除不可能的区域来缩小搜索范围。这种方法在有序数据集中非常有效,可以显著提高搜索效率。 + +## 练习题目 + +- [0704. 二分查找](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) +- [0374. 猜数字大小](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower.md) +- [0035. 搜索插入位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-insert-position.md) +- [0167. 两数之和 II - 输入有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md) + +- [二分查找题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/01_array/01_14_array_binary_search_02.md b/docs/01_array/01_14_array_binary_search_02.md new file mode 100644 index 00000000..2f2cbf79 --- /dev/null +++ b/docs/01_array/01_14_array_binary_search_02.md @@ -0,0 +1,274 @@ +## 3. 二分查找细节 + +从上篇文章的例子中我们了解了二分查找的思路和具体代码。但是真正在解决二分查找题目的时候还需要考虑更多细节。比如说以下几个问题: + +1. **区间的开闭问题**:区间应该是左闭右闭区间 $[left, right]$,还是左闭右开区间 $[left, right)$? +2. **$mid$ 的取值问题**:$mid = \lfloor \frac{left + right}{2} \rfloor$,还是 $mid = \lfloor \frac{left + right + 1}{2} \rfloor$? +3. **出界条件的判断**:$left \le right$,还是 $left < right$? +4. **搜索区间范围的选择**:$left = mid + 1$、$right = mid - 1$、 $left = mid$、$right = mid$ 应该怎么写? + +下面依次进行讲解。 + +### 3.1 区间的开闭问题 + +左闭右闭区间、左闭右开区间指的是初始待查找区间的范围。 + +- **左闭右闭区间**:初始化时,$left = 0$,$right = len(nums) - 1$。 + - $left$ 为数组第一个元素位置,$right$ 为数组最后一个元素位置。 + - 区间 $[left, right]$ 左右边界上的点都能取到。 + +- **左闭右开区间**:初始化时,$left = 0$,$right = len(nums)$。 + - $left$ 为数组第一个元素位置,$right$ 为数组最后一个元素的下一个位置。 + - 区间 $[left, right)$ 左边界点能取到,而右边界上的点不能取到。 + + +关于二分查找算法的左闭右闭区间、左闭右开区间,其实在网上都有对应的代码。但是相对来说,左闭右开区间这种写法在解决问题的过程中,会使得问题变得复杂,需要考虑的情况更多,所以不建议使用左闭右开区间这种写法,而是建议:**全部使用「左闭右闭区间」这种写法**。 + +### 3.2 $mid$ 的取值问题 + +在二分查找的实际问题中,最常见的 $mid$ 取值公式有两个: + +1. `mid = (left + right) // 2`。 +2. `mid = (left + right + 1) // 2 `。 + +式子中 `//` 所代表的含义是「中间数向下取整」。当待查找区间中的元素个数为奇数个,使用这两种取值公式都能取到中间元素的下标位置。 + +而当待查找区间中的元素个数为偶数时,使用 `mid = (left + right) // 2` 式子我们能取到中间靠左边元素的下标位置,使用 `mid = (left + right + 1) // 2` 式子我们能取到中间靠右边元素的下标位置。 + +::: tabs#mid + +@tab <1> + +![mid 取值问题 1](https://qcdn.itcharge.cn/images/20230906153359.png) + +@tab <2> + +![mid 取值问题 2](https://qcdn.itcharge.cn/images/20230906153409.png) + +::: + +把这两个公式分别代入到 [704. 二分查找](https://leetcode.cn/problems/binary-search/) 的代码中试一试,发现都能通过题目评测。这是为什么呢? + +因为二分查找算法的思路是:根据每次选择中间位置上的数值来决定下一次在哪个区间查找元素。每一次选择的元素位置可以是中间位置,但并不是一定非得是区间中间位置元素,靠左一些、靠右一些、甚至区间三分之一、五分之一处等等,都是可以的。比如说 `mid = (left + right) * 1 // 5` 也是可以的。 + +但一般来说,取区间中间位置在平均意义下所达到的效果最好。同时这样写最简单。而对于这两个取值公式,大多数时候是选择第一个公式。不过,有些情况下,是需要考虑第二个公式的,我们会在「4.2 排除法」中进行讲解。 + +除了上面提到的这两种写法,我们还经常能看到下面两个公式: + +1. `mid = left + (right - left) // 2`。 +2. `mid = left + (right - left + 1) // 2`。 + +这两个公式其实分别等同于之前两个公式,可以看做是之前两个公式的另一种写法。这种写法能够防止整型溢出问题(Python 语言中整型不会溢出,其他语言可能会有整型溢出问题)。 + +在 $left + right$ 的数据量不会超过整型变量最大值时,这两种写法都没有问题。在 $left + right$ 的数据量可能会超过整型变量最大值时,最好使用第二种写法。所以,为了统一和简化二分查找算法的写法,建议统一写成第二种写法: + +1. `mid = left + (right - left) // 2`。 +2. `mid = left + (right - left + 1) // 2`。 + +### 3.3 出界条件的判断 + +二分查找算法的写法中,`while` 语句出界判断条件通常有两种: + +1. `left <= right`。 +2. `left < right`。 + +我们究竟应该使用哪一种写法呢? + +我们先来判断一下导致 `while` 语句出界的条件是什么。 + +1. 如果判断语句为 `left <= right`,并且查找的元素不在有序数组中,则 `while` 语句的出界条件是 `left > right`,也就是 `left == right + 1`,写成区间形式就是 $[right + 1, right]$,此时待查找区间为空,待查找区间中没有元素存在,此时终止循环时,可以直接返回 $-1$。 + - 比如说区间 $[3, 2]$, 此时左边界大于右边界,直接终止循环,返回 $-1$ 即可。 +2. 如果判断语句为`left < right`,并且查找的元素不在有序数组中,则 `while` 语句出界条件是 `left == right`,写成区间形式就是 $[right, right]$。此时区间不为空,待查找区间还有一个元素存在,我们并不能确定查找的元素不在这个区间中,此时终止循环时,如果直接返回 $-1$ 就是错误的。 + - 比如说区间 $[2, 2]$,如果元素 $nums[2]$ 刚好就是目标元素 $target$,此时终止循环,返回 $-1$ 就漏掉了这个元素。 + +但是如果我们还是想要使用 `left < right` 的话,怎么办? + +可以在出界之后增加一层判断,判断 $left$ 所指向位置是否等于目标元素,如果是的话就返回 $left$,如果不是的话返回 $-1$。即: + +```python +# ... + while left < right: + # ... + return left if nums[left] == target else -1 +``` + +此外,`while` 判断语句用 `left < right` 有一个好处,就是在跳出循环的时候,一定是 `left == right`,我们就不用判断此时应该返回 $left$ 还是 $right$ 了。 + +### 3.4 搜索区间范围的选择 + +在进行区间范围选择的时候,通常有三种写法: + +1. `left = mid + 1`,`right = mid - 1`。 +2. `left = mid + 1 `,`right = mid`。 +3. `left = mid`,`right = mid - 1`。 + +我们到底应该如何确定搜索区间范围呢? + +这是二分查找的一个难点,写错了很容易造成死循环,或者得不到正确结果。 + +这其实跟二分查找算法的两种不同思路和三种写法有关。 + +- 思路 1:「直接法」—— 在循环体中找到元素后直接返回结果。 +- 思路 2:「排除法」—— 在循环体中排除目标元素一定不存在区间。 + +接下来我们具体讲解下这两种思路。 + +## 4. 二分查找两种思路 + +### 4.1 直接法 + +> **直接法思想**:一旦我们在循环体中找到元素就直接返回结果。 + +这种思路比较简单,其实我们在上篇 「2. 简单二分查找 - [704. 二分查找](https://leetcode.cn/problems/binary-search/)」 中就已经用过了。这里再看一下思路和代码: + +#### 思路 1:直接法 + +1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 +2. 取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 + 1. 如果 $target == nums[mid]$,则返回中心位置。 + 2. 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 + 3. 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 +3. 如果左边界大于右边界,查找范围缩小为空,说明目标元素不存在,此时返回 $-1$。 + +#### 思路 1:代码 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + # 在区间 [left, right] 内查找 target + while left <= right: + # 取区间中间节点 + mid = left + (right - left) // 2 + # 如果找到目标值,则直接范围中心位置 + if nums[mid] == target: + return mid + # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 + elif nums[mid] < target: + left = mid + 1 + # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 + else: + right = mid - 1 + # 未搜索到元素,返回 -1 + return -1 +``` + +#### 思路 1:细节 + +- 这种思路是在一旦循环体中找到元素就直接返回。 +- 循环可以继续的条件是 `left <= right`。 +- 如果一旦退出循环,则说明这个区间内一定不存在目标元素。 + +### 4.2 排除法 + +> **排除法思想**:在循环体中排除目标元素一定不存在区间。 + +#### 思路 2:排除法 + +1. 设定左右边界为数组两端,即 $left = 0$,$right = len(nums) - 1$,代表待查找区间为 $[left, right]$(左闭右闭区间)。 +2. 取两个节点中心位置 $mid$,比较目标元素和中间元素的大小,先将目标元素一定不存在的区间排除。 +3. 然后在剩余区间继续查找元素,继续根据条件排除目标元素一定不存在的区间。 +4. 直到区间中只剩下最后一个元素,然后再判断这个元素是否是目标元素。 + +根据排除法的思路,我们可以写出来两种代码。 + +#### 思路 2:代码 1 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + # 在区间 [left, right] 内查找 target + while left < right: + # 取区间中间节点 + mid = left + (right - left) // 2 + # nums[mid] 小于目标值,排除掉不可能区间 [left, mid],在 [mid + 1, right] 中继续搜索 + if nums[mid] < target: + left = mid + 1 + # nums[mid] 大于等于目标值,目标元素可能在 [left, mid] 中,在 [left, mid] 中继续搜索 + else: + right = mid + # 判断区间剩余元素是否为目标元素,不是则返回 -1 + return left if nums[left] == target else -1 +``` + +#### 思路 2:代码 2 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + # 在区间 [left, right] 内查找 target + while left < right: + # 取区间中间节点 + mid = left + (right - left + 1) // 2 + # nums[mid] 大于目标值,排除掉不可能区间 [mid, right],在 [left, mid - 1] 中继续搜索 + if nums[mid] > target: + right = mid - 1 + # nums[mid] 小于等于目标值,目标元素可能在 [mid, right] 中,在 [mid, right] 中继续搜索 + else: + left = mid + # 判断区间剩余元素是否为目标元素,不是则返回 -1 + return left if nums[left] == target else -1 +``` + +#### 思路 2:细节 + +- 判断语句是 `left < right`。这样在退出循环时,一定有`left == right` 成立,就不用判断应该返回 $left$ 还是 $right$ 了。此时只需要判断 $nums[left]$ 是否为目标元素即可。 +- 在循环体中,比较目标元素和中间元素的大小之后,优先将目标元素一定不存在的区间排除,然后再从剩余区间中确定下一次查找区间的范围。 +- 在将目标元素一定不存在的区间排除之后,它的对立面(即 `else` 部分)一般就不需要再考虑区间范围了,直接取上一个区间的相反区间。如果上一个区间是 $[mid + 1, right]$,那么相反区间就是 $[left, mid]$。如果上一个区间是 $[left, mid - 1]$,那么相反区间就是 $[mid, right]$。 +- 为了避免陷入死循环,当区分被划分为 $[left, mid - 1]$ 与 $[mid, right]$ 两部分时,**$mid$ 取值要向上取整**。即 `mid = left + (right - left + 1) // 2`。因为如果当区间中只剩下两个元素时(此时 `right = left + 1`),一旦进入 `left = mid` 分支,区间就不会再缩小了,下一次循环的查找区间还是 $[left, right]$,就陷入了死循环。 + - 比如左边界 $left = 5$,右边界 $right = 6$,此时查找区间为 $[5, 6]$,$mid = 5 + (6 - 5) // 2 = 5$,如果进入 $left = mid$ 分支,那么下次查找区间仍为 $[5, 6]$,区间不再缩小,陷入死循环。 + - 这种情况下,$mid$ 应该向上取整,$mid = 5 + (6 - 5 + 1) // 2 = 6$,如果进入 $left = mid$ 分支,则下次查找区间为 $[6, 6]$。 + + +- 关于边界设置可以记忆为:只要看到 `left = mid` 就向上取整。或者记为: + - `left = mid + 1`、`right = mid` 和 `mid = left + (right - left) // 2` 一定是配对出现的。 + - `right = mid - 1`、`left = mid` 和 `mid = left + (right - left + 1) // 2` 一定是配对出现的。 + +### 4.3 两种思路适用范围 + +- **直接法**:因为判断语句是 `left <= right`,有时候要考虑返回是 $left$ 还是 $right$。循环体内有 3 个分支,并且一定有一个分支用于退出循环或者直接返回。这种思路适合解决简单题目。即要查找的元素性质简单,数组中都是非重复元素,且 `==`、`>`、`<` 的情况非常好写的时候。 +- **排除法**:更加符合二分查找算法的减治思想。每次排除目标元素一定不存在的区间,达到减少问题规模的效果。然后在可能存在的区间内继续查找目标元素。这种思路适合解决复杂题目。比如查找一个数组里可能不存在的元素,找边界问题,可以使用这种思路。 + +## 5. 总结 + +二分查找的细节问题包括区间开闭、mid取值、循环条件和搜索范围的选择。 + +**区间开闭**:建议使用左闭右闭区间,这样逻辑更简单,减少出错可能。 + +**mid取值**:通常使用 `mid = left + (right - left) // 2`,防止整型溢出。在某些情况下,如 `left = mid` 时,需向上取整,避免死循环。 + +**循环条件**: +- `left <= right`:适用于直接法,循环结束时若未找到目标,直接返回 $-1$。 +- `left < right`:适用于排除法,循环结束时需额外判断 `nums[left]` 是否为目标值。 + +**搜索范围选择**: +- 直接法:`left = mid + 1` 或 `right = mid - 1`,明确缩小范围。 +- 排除法:根据情况选择 `left = mid + 1` 或 `right = mid`,以及 `right = mid - 1` 或 `left = mid`,确保每次排除无效区间。 + +**两种思路**: +- **直接法**:简单直接,适合查找明确存在的元素。 +- **排除法**:更通用,适合复杂问题,如边界查找或不确定元素是否存在的情况。 + +掌握这些细节能更灵活地应用二分查找,避免常见错误。 + +## 练习题目 + +- [0278. 第一个错误的版本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/first-bad-version.md) +- [0069. x 的平方根](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) +- [1011. 在 D 天内送达包裹的能力](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md) +- [0033. 搜索旋转排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) +- [0153. 寻找旋转排序数组中的最小值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) + +- [二分查找题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[Learning-Algorithms-with-Leetcode - 第 3.1 节 二分查找算法](https://www.yuque.com/liweiwei1419/algo/wkmtx4) +- 【博文】[二分法的细节加细节 你真的应该搞懂!!!_小马的博客](https://blog.csdn.net/xiao_jj_jj/article/details/106018702) +- 【课程】[零起步学算法 - LeetBook - 二分查找的基本思想:减而治之](https://leetcode.cn/leetbook/read/learning-algorithms-with-leetcode/xsz9zc/) +- 【题解】[二分查找算法细节详解,顺便写了首诗 - LeetCode](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/) \ No newline at end of file diff --git a/docs/01_array/01_15_array_two_pointers.md b/docs/01_array/01_15_array_two_pointers.md new file mode 100644 index 00000000..af319093 --- /dev/null +++ b/docs/01_array/01_15_array_two_pointers.md @@ -0,0 +1,513 @@ +## 1. 双指针简介 + +> **双指针(Two Pointers)**:指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 + +在数组的区间问题上,暴力算法的时间复杂度往往是 $O(n^2)$。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 $O(n)$。 + +## 2. 对撞指针 + +> **对撞指针**:指的是两个指针 $left$、$right$ 分别指向序列第一个元素和最后一个元素,然后 $left$ 指针不断递增,$right$ 不断递减,直到两个指针的值相撞(即 $left == right$),或者满足其他要求的特殊条件为止。 + +![对撞指针](https://qcdn.itcharge.cn/images/202405092155032.png) + +### 2.1 对撞指针求解步骤 + +1. 使用两个指针 $left$,$right$。$left$ 指向序列第一个元素,即:$left = 0$,$right$ 指向序列最后一个元素,即:$right = len(nums) - 1$。 +2. 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,$left += 1$。当满足另外一定条件时,将右指针左移,$right -= 1$。 +3. 直到两指针相撞(即 $left == right$),或者满足其他要求的特殊条件时,跳出循环体。 + +### 2.2 对撞指针伪代码模板 + +```python +left, right = 0, len(nums) - 1 + +while left < right: + if 满足要求的特殊条件: + return 符合条件的值 + elif 一定条件 1: + left += 1 + elif 一定条件 2: + right -= 1 + +return 没找到 或 找到对应值 +``` + +### 2.3 对撞指针适用范围 + +对撞指针一般用来解决有序数组或者字符串问题: + +- 查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题。 +- 字符串反转问题:反转字符串、回文数、颠倒二进制等问题。 + +下面我们根据具体例子来讲解如何使用对撞指针来解决问题。 + +### 2.4 两数之和 II - 输入有序数组 + +#### 2.4.1 题目链接 + +- [167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) + +#### 2.4.2 题目大意 + +**描述**:给定一个下标从 $1$ 开始计数、升序排列的整数数组:$numbers$ 和一个目标值 $target$。 + +**要求**:从数组中找出满足相加之和等于 $target$ 的两个数,并返回两个数在数组中下的标值。 + +**说明**: + +- $2 \le numbers.length \le 3 * 10^4$。 +- $-1000 \le numbers[i] \le 1000$。 +- $numbers$ 按非递减顺序排列。 +- $-1000 \le target \le 1000$。 +- 仅存在一个有效答案。 + +**示例**: + +- 示例 1: + +```python +输入:numbers = [2,7,11,15], target = 9 +输出:[1,2] +解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 +``` + +- 示例 2: + +```python +输入:numbers = [2,3,4], target = 6 +输出:[1,3] +解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。 +``` + +#### 2.4.3 解题思路 + +这道题如果暴力遍历数组,从中找到相加之和等于 $target$ 的两个数,时间复杂度为 $O(n^2)$,可以尝试一下。 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + size = len(numbers) + for i in range(size): + for j in range(i + 1, size): + if numbers[i] + numbers[j] == target: + return [i + 1, j + 1] + return [-1, -1] +``` + +结果不出意外的超时了。所以我们要想办法减少时间复杂度。 + +##### 思路 1:对撞指针 + +可以考虑使用对撞指针来减少时间复杂度。具体做法如下: + +1. 使用两个指针 $left$,$right$。$left$ 指向数组第一个值最小的元素位置,$right$ 指向数组值最大元素位置。 +2. 判断两个位置上的元素的和与目标值的关系。 + 1. 如果元素和等于目标值,则返回两个元素位置。 + 2. 如果元素和大于目标值,则让 $right$ 左移,继续检测。 + 3. 如果元素和小于目标值,则让 $left$ 右移,继续检测。 +3. 直到 $left$ 和 $right$ 移动到相同位置停止检测。 +4. 如果最终仍没找到,则返回 $[-1, -1]$。 + +##### 思路 1:代码 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + left = 0 + right = len(numbers) - 1 + while left < right: + total = numbers[left] + numbers[right] + if total == target: + return [left + 1, right + 1] + elif total < target: + left += 1 + else: + right -= 1 + return [-1, -1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + +### 2.5 验证回文串 + +#### 2.5.1 题目链接 + +- [125. 验证回文串 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-palindrome/) + +#### 2.5.2 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 + +**说明**: + +- 回文串:正着读和反着读都一样的字符串。 +- $1 \le s.length \le 2 * 10^5$。 +- $s$ 仅由可打印的 ASCII 字符组成。 + +**示例**: + +```python +输入: "A man, a plan, a canal: Panama" +输出:true +解释:"amanaplanacanalpanama" 是回文串。 + + +输入:"race a car" +输出:false +解释:"raceacar" 不是回文串。 +``` + +#### 2.5.3 解题思路 + +##### 思路 1:对撞指针 + +1. 使用两个指针 $left$,$right$。$left$ 指向字符串开始位置,$right$ 指向字符串结束位置。 +2. 判断两个指针对应字符是否是字母或数字。 通过 $left$ 右移、$right$ 左移的方式过滤掉字母和数字以外的字符。 +3. 然后判断 $s[start]$ 是否和 $s[end]$ 相等(注意大小写)。 + 1. 如果相等,则将 $left$ 右移、$right$ 左移,继续进行下一次过滤和判断。 + 2. 如果不相等,则说明不是回文串,直接返回 $False$。 +4. 如果遇到 $left == right$,跳出循环,则说明该字符串是回文串,返回 $True$。 + +##### 思路 1:代码 + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + left = 0 + right = len(s) - 1 + + while left < right: + if not s[left].isalnum(): + left += 1 + continue + if not s[right].isalnum(): + right -= 1 + continue + + if s[left].lower() == s[right].lower(): + left += 1 + right -= 1 + else: + return False + return True +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(len(s))$。 +- **空间复杂度**:$O(len(s))$。 + +### 2.6 盛最多水的容器 + +#### 2.6.1 题目链接 + +- [11. 盛最多水的容器 - 力扣(LeetCode)](https://leetcode.cn/problems/container-with-most-water/) + +#### 2.6.2 题目大意 + +**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n$,每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 + +**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 + +**说明**: + +- $n == height.length$。 +- $2 \le n \le 10^5$。 +- $0 \le height[i] \le 10^4$。 + +**示例**: + +![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) + +```python +输入:[1,8,6,2,5,4,8,3,7] +输出:49 +解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +``` + +#### 2.6.3 解题思路 + +##### 思路 1:对撞指针 + +从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由「左右两端直线中较低直线的高度 * 两端直线之间的距离」所决定的。所以我们应该使得「」,这样才能使盛水面积尽可能的大。 + +可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: + +1. 使用两个指针 $left$,$right$。$left$ 指向数组开始位置,$right$ 指向数组结束位置。 +2. 计算 $left$ 和 $right$ 所构成的面积值,同时维护更新最大面积值。 +3. 判断 $left$ 和 $right$ 的高度值大小。 + 1. 如果 $left$ 指向的直线高度比较低,则将 $left$ 指针右移。 + 2. 如果 $right$ 指向的直线高度比较低,则将 $right$ 指针左移。 +4. 如果遇到 $left == right$,跳出循环,最后返回最大的面积。 + +##### 思路 1:代码 + +```python +class Solution: + def maxArea(self, height: List[int]) -> int: + left = 0 + right = len(height) - 1 + ans = 0 + while left < right: + area = min(height[left], height[right]) * (right-left) + ans = max(ans, area) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 3. 快慢指针 + +> **快慢指针**:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。 + +![快慢指针](https://qcdn.itcharge.cn/images/202405092156465.png) + +### 3.1 快慢指针求解步骤 + +1. 使用两个指针 $slow$、$fast$。$slow$ 一般指向序列第一个元素,即:$slow = 0$,$fast$ 一般指向序列第二个元素,即:$fast = 1$。 +2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 $slow += 1$。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 $fast += 1$。 +3. 到快指针移动到数组尾端(即 $fast == len(nums) - 1$),或者两指针相交,或者满足其他特殊条件时跳出循环体。 + +### 3.2 快慢指针伪代码模板 + +```python +slow = 0 +fast = 1 +while 没有遍历完: + if 满足要求的特殊条件: + slow += 1 + fast += 1 +return 合适的值 +``` + +### 3.3 快慢指针适用范围 + +快慢指针一般用于处理数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。关于链表相关的双指针做法我们到链表章节再详细讲解。 + +下面我们根据具体例子来讲解如何使用快慢指针来解决问题。 + +### 3.4 删除有序数组中的重复项 + +#### 3.4.1 题目链接 + +- [26. 删除有序数组中的重复项 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) + +#### 3.4.2 题目大意 + +**描述**:给定一个有序数组 $nums$。 + +**要求**:删除数组 $nums$ 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 + +**说明**: + +- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,2] +输出:2, nums = [1,2,_] +解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 +``` + +- 示例 2: + +```python +输入:nums = [0,0,1,1,1,2,2,3,3,4] +输出:5, nums = [0,1,2,3,4] +解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 +``` + +#### 3.4.3 解题思路 + +##### 思路 1:快慢指针 + +因为数组是有序的,那么重复的元素一定会相邻。 + +删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: + +1. 定义两个快慢指针 $slow$,$fast$。其中 $slow$ 指向去除重复元素后的数组的末尾位置。$fast$ 指向当前元素。 +2. 令 $slow$ 在后, $fast$ 在前。令 $slow = 0$,$fast = 1$。 +3. 比较 $slow$ 位置上元素值和 $fast$ 位置上元素值是否相等。 + - 如果不相等,则将 $slow$ 右移一位,将 $fast$ 指向位置的元素复制到 $slow$ 位置上。 +4. 将 $fast$ 右移 $1$ 位。 +5. 重复上述 $3 \sim 4$ 步,直到 $fast$ 等于数组长度。 +6. 返回 $slow + 1$ 即为新数组长度。 + +##### 思路 1:代码 + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + if len(nums) <= 1: + return len(nums) + + slow, fast = 0, 1 + + while (fast < len(nums)): + if nums[slow] != nums[fast]: + slow += 1 + nums[slow] = nums[fast] + fast += 1 + + return slow + 1 +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 4. 分离双指针 + +> **分离双指针**:两个指针分别属于不同的数组,两个指针分别在两个数组中移动。 + +![分离双指针](https://qcdn.itcharge.cn/images/202405092157828.png) + +### 4.1 分离双指针求解步骤 + +1. 使用两个指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$。$left\underline{\hspace{0.5em}}1$ 指向第一个数组的第一个元素,即:$left\underline{\hspace{0.5em}}1 = 0$,$left\underline{\hspace{0.5em}}2$ 指向第二个数组的第一个元素,即:$left\underline{\hspace{0.5em}}2 = 0$。 +2. 当满足一定条件时,两个指针同时右移,即 $left\underline{\hspace{0.5em}}1 += 1$、$left\underline{\hspace{0.5em}}2 += 1$。 +3. 当满足另外一定条件时,将 $left\underline{\hspace{0.5em}}1$ 指针右移,即 $left\underline{\hspace{0.5em}}1 += 1$。 +4. 当满足其他一定条件时,将 $left\underline{\hspace{0.5em}}2$ 指针右移,即 $left\underline{\hspace{0.5em}}2 += 1$。 +5. 当其中一个数组遍历完时或者满足其他特殊条件时跳出循环体。 + +### 4.2 分离双指针伪代码模板 + +```python +left_1 = 0 +left_2 = 0 + +while left_1 < len(nums1) and left_2 < len(nums2): + if 一定条件 1: + left_1 += 1 + left_2 += 1 + elif 一定条件 2: + left_1 += 1 + elif 一定条件 3: + left_2 += 1 +``` + +### 4.3 分离双指针使用范围 + +分离双指针一般用于处理有序数组合并,求交集、并集问题。 + +下面我们根据具体例子来讲解如何使用分离双指针来解决问题。 + +### 4.4 两个数组的交集 + +#### 4.4.1 题目链接 + +- [349. 两个数组的交集 - 力扣(LeetCode)](https://leetcode.cn/problems/intersection-of-two-arrays/) + +#### 4.4.2 题目大意 + +**描述**:给定两个数组 $nums1$ 和 $nums2$。 + +**要求**:返回两个数组的交集。重复元素只计算一次。 + +**说明**: + +- $1 \le nums1.length, nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2] +示例 2: +``` + +- 示例 2: + +```python +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[9,4] +解释:[4,9] 也是可通过的 +``` + +#### 4.4.3 解题思路 + +##### 思路 1:分离双指针 + +1. 对数组 $nums1$、$nums2$ 先排序。 +2. 使用两个指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$。$left\underline{\hspace{0.5em}}1$ 指向第一个数组的第一个元素,即:$left\underline{\hspace{0.5em}}1 = 0$,$left\underline{\hspace{0.5em}}2$ 指向第二个数组的第一个元素,即:$left\underline{\hspace{0.5em}}2 = 0$。 +3. 如果 $nums1[left\underline{\hspace{0.5em}}1] == nums2[left\underline{\hspace{0.5em}}2]$,则将其加入答案数组(注意去重),并将 $left\underline{\hspace{0.5em}}1$ 和 $left\underline{\hspace{0.5em}}2$ 右移。 +4. 如果 $nums1[left\underline{\hspace{0.5em}}1] < nums2[left\underline{\hspace{0.5em}}2]$,则将 $left\underline{\hspace{0.5em}}1$ 右移。 +5. 如果 $nums1[left\underline{\hspace{0.5em}}1] > nums2[left\underline{\hspace{0.5em}}2]$,则将 $left\underline{\hspace{0.5em}}2$ 右移。 +6. 最后返回答案数组。 + +##### 思路 1:代码 + +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums1.sort() + nums2.sort() + + left_1 = 0 + left_2 = 0 + res = [] + while left_1 < len(nums1) and left_2 < len(nums2): + if nums1[left_1] == nums2[left_2]: + if nums1[left_1] not in res: + res.append(nums1[left_1]) + left_1 += 1 + left_2 += 1 + elif nums1[left_1] < nums2[left_2]: + left_1 += 1 + elif nums1[left_1] > nums2[left_2]: + left_2 += 1 + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 5. 双指针总结 + +双指针分为「对撞指针」、「快慢指针」、「分离双指针」。 + +- **对撞指针**:两个指针方向相反。适合解决查找有序数组中满足某些约束条件的一组元素问题、字符串反转问题。 +- **快慢指针**:两个指针方向相同。适合解决数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。 +- **分离双指针**:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。 + +双指针算法能有效降低时间复杂度,通常将暴力解法的 $O(n^2)$ 优化为 $O(n)$。关键在于利用数据的有序性或问题的单调性,通过指针移动排除不可能的情况,减少不必要的计算。掌握双指针技巧能高效解决许多数组和链表问题。 + +## 练习题目 + +- [0344. 反转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) +- [0345. 反转字符串中的元音字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-vowels-of-a-string.md) +- [0015. 三数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) +- [0027. 移除元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-element.md) +- [0080. 删除有序数组中的重复项 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md) +- [0925. 长按键入](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/long-pressed-name.md) + +- [双指针题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8F%8C%E6%8C%87%E9%92%88%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[双指针算法之快慢指针 (yanyusoul.com)](https://yanyusoul.com/blog/cs/algorithms_fast-slow-points/) +- 【博文】[双指针算法各类基础题型总结 - 掘金](https://juejin.cn/post/6855129006451687431) +- 【博文】[双指针 - 力扣加加 - 努力做西湖区最好的算法题解](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/91/two-pointers#zuo-you-duan-dian-zhi-zhen) +- 【博文】[LeetCode分类专题(四)——双指针和滑动窗口1 - iwehdio - 博客园](https://www.cnblogs.com/iwehdio/p/14434988.html) +- 【博文】[双指针算法各类基础题型总结 - 掘金](https://juejin.cn/post/6855129006451687431) diff --git a/docs/01_array/01_16_array_sliding_window.md b/docs/01_array/01_16_array_sliding_window.md new file mode 100644 index 00000000..addc58ed --- /dev/null +++ b/docs/01_array/01_16_array_sliding_window.md @@ -0,0 +1,291 @@ +## 1. 滑动窗口算法介绍 + +在计算机网络中,滑动窗口协议(Sliding Window Protocol)是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。我们所要讲解的滑动窗口算法也是利用了同样的特性。 + +> **滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 + +- **滑动操作**:窗口可按照一定方向进行移动。最常见的是向右侧移动。 +- **缩放操作**:对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度。 + +滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以将滑动窗口看做是快慢指针的一种特殊形式。 + +![滑动窗口](https://qcdn.itcharge.cn/images/202405092203225.png) + +## 2. 滑动窗口适用范围 + +滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。 + +按照窗口长度的固定情况,我们可以将滑动窗口题目分为以下两种: + +- **固定长度窗口**:窗口大小是固定的。 +- **不定长度窗口**:窗口大小是不固定的。 + - 求解最大的满足条件的窗口。 + - 求解最小的满足条件的窗口。 + + +下面来分别讲解一下这两种类型题目。 + +## 3. 固定长度滑动窗口 + +> **固定长度滑动窗口算法(Fixed Length Sliding Window)**:在给定数组 / 字符串上维护一个固定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 + +![固定长度滑动窗口](https://qcdn.itcharge.cn/images/202405092204712.png) + +### 3.1 固定长度滑动窗口算法步骤 + +假设窗口的固定大小为 $window\underline{\hspace{0.5em}}size$。 + +1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。 +2. 当窗口未达到 $window\underline{\hspace{0.5em}}size$ 大小时,不断移动 $right$,先将数组前 $window\underline{\hspace{0.5em}}size$ 个元素填入窗口中,即 `window.append(nums[right])`。 +2. 当窗口达到 $window\underline{\hspace{0.5em}}size$ 大小时,即满足 `right - left + 1 >= window_size` 时,判断窗口内的连续元素是否满足题目限定的条件。 + 1. 如果满足,再根据要求更新最优解。 + 2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $window\underline{\hspace{0.5em}}size$。 +3. 向右移动 $right$,将元素填入窗口中,即 `window.append(nums[right])`。 +4. 重复 $2 \sim 4$ 步,直到 $right$ 到达数组末尾。 + +### 3.2 固定长度滑动窗口代码模板 + +```python +left = 0 +right = 0 + +while right < len(nums): + window.append(nums[right]) + + # 超过窗口大小时,缩小窗口,维护窗口中始终为 window_size 的长度 + if right - left + 1 >= window_size: + # ... 维护答案 + window.popleft() + left += 1 + + # 向右侧增大窗口 + right += 1 +``` + +下面我们根据具体例子来讲解一下如何使用固定窗口大小的滑动窗口来解决问题。 + +### 3.3 大小为 K 且平均值大于等于阈值的子数组数目 + +#### 3.3.1 题目链接 + +- [1343. 大小为 K 且平均值大于等于阈值的子数组数目 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) + +#### 3.3.2 题目大意 + +**描述**:给定一个整数数组 $arr$ 和两个整数 $k$ 和 $threshold$ 。 + +**要求**:返回长度为 $k$ 且平均值大于等于 $threshold$ 的子数组数目。 + +**说明**: + +- $1 \le arr.length \le 10^5$。 +- $1 \le arr[i] \le 10^4$。 +- $1 \le k \le arr.length$。 +- $0 \le threshold \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 +输出:3 +解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。 +``` + +- 示例 2: + +```python +输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 +输出:6 +解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 +``` + +#### 3.3.3 解题思路 + +##### 思路 1:滑动窗口(固定长度) + +这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 $k$。具体做法如下: + +1. $ans$ 用来维护答案数目。$window\underline{\hspace{0.5em}}sum$ 用来维护窗口中元素的和。 +2. $left$ 、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$。 +3. 向右移动 $right$,先将 $k$ 个元素填入窗口中,即 `window_sum += arr[right]`。 +4. 当窗口元素个数为 $k$ 时,即满足 `right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 $threshold$。 + 1. 如果满足,则答案数目加 $1$。 + 2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 +5. 重复 $3 \sim 4$ 步,直到 $right$ 到达数组末尾。 +6. 最后输出答案数目。 + +##### 思路 1:代码 + +```python +class Solution: + def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int: + left = 0 + right = 0 + window_sum = 0 + ans = 0 + + while right < len(arr): + window_sum += arr[right] + + if right - left + 1 >= k: + if window_sum >= k * threshold: + ans += 1 + window_sum -= arr[left] + left += 1 + + right += 1 + + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 4. 不定长度滑动窗口 + +> **不定长度滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 + +![不定长度滑动窗口](https://qcdn.itcharge.cn/images/202405092206553.png) + +### 4.1 不定长度滑动窗口算法步骤 + +1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素。即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。 +2. 将区间最右侧元素添加入窗口中,即 `window.add(s[right])`。 +3. 然后向右移动 $right$,从而增大窗口长度,即 `right += 1`。直到窗口中的连续元素满足要求。 +4. 此时,停止增加窗口大小。转向不断将左侧元素移出窗口,即 `window.popleft(s[left])`。 +5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`。直到窗口中的连续元素不再满足要求。 +6. 重复 2 ~ 5 步,直到 $right$ 到达序列末尾。 + +### 4.2 不定长度滑动窗口代码模板 + +```python +left = 0 +right = 0 + +while right < len(nums): + window.append(nums[right]) + + while 窗口需要缩小: + # ... 可维护答案 + window.popleft() + left += 1 + + # 向右侧增大窗口 + right += 1 +``` + +### 4.3 [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) + +#### 4.3.1 题目链接 + +- [3. 无重复字符的最长子串 - 力扣(LeetCode)](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) + +#### 4.3.2 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中不含有重复字符的最长子串的长度。 + +**说明**: + +- $0 \le s.length \le 5 * 10^4$。 +- $s$ 由英文字母、数字、符号和空格组成。 + +**示例**: + +- 示例 1: + +```python +输入: s = "abcabcbb" +输出: 3 +解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 +``` + +- 示例 2: + +```python +输入: s = "bbbbb" +输出: 1 +解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 +``` + +#### 4.3.3 解题思路 + +##### 思路 1:滑动窗口(不定长度) + +用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。 + +1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 +2. 一开始,$left$、$right$ 都指向 $0$。 +3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。 +4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$。 +5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +6. 输出无重复字符的最长子串长度。 + +##### 思路 1:代码 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + left = 0 + right = 0 + window = dict() + ans = 0 + + while right < len(s): + if s[right] not in window: + window[s[right]] = 1 + else: + window[s[right]] += 1 + + while window[s[right]] > 1: + window[s[left]] -= 1 + left += 1 + + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 + +## 5. 总结 + +滑动窗口算法用于解决数组或字符串中的连续子区间问题。 + +**固定长度窗口**:窗口大小不变。适用于需要检查固定长度子区间的问题,如计算特定长度子数组的平均值。 + +**不定长度窗口**:窗口大小可变。适用于寻找满足条件的最长或最短子区间,如无重复字符的最长子串。 + +滑动窗口通过维护窗口的左右边界来减少重复计算,将时间复杂度从 $O(n^2)$ 优化到 $O(n)$。 + +使用滑动窗口时需要注意: +- 窗口的初始位置 +- 窗口扩展和收缩的条件 +- 如何更新答案 +- 边界情况的处理 + +掌握滑动窗口算法能高效解决许多子区间相关问题。 + +## 练习题目 + +- [0643. 子数组最大平均数 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-average-subarray-i.md) +- [0674. 最长连续递增序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md) +- [1004. 最大连续1的个数 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/max-consecutive-ones-iii.md) + +- [滑动窗口题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【答案】[TCP 协议的滑动窗口具体是怎样控制流量的? - 知乎](https://www.zhihu.com/question/32255109/answer/68558623) +- 【博文】[滑动窗口算法基本原理与实践 - huansky - 博客园](https://www.cnblogs.com/huansky/p/13488234.html) +- 【博文】[滑动窗口(Sliding Window)- lucifer.ren](https://lucifer.ren/leetcode/thinkings/slide-window.html) + diff --git a/docs/01_array/index.md b/docs/01_array/index.md new file mode 100644 index 00000000..0117cdf2 --- /dev/null +++ b/docs/01_array/index.md @@ -0,0 +1,18 @@ +## 本章内容 + +- [1.1 数组基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_01_array_basic.md) +- [1.2 数组排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_02_array_sort.md) +- [1.3 数组冒泡排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_03_array_bubble_sort.md) +- [1.4 数组选择排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_04_array_selection_sort.md) +- [1.5 数组插入排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array//01_05_array_insertion_sort.md) +- [1.6 数组希尔排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_06_array_shell_sort.md) +- [1.7 数组归并排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_07_array_merge_sort.md) +- [1.8 数组快速排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_08_array_quick_sort.md) +- [1.9 数组堆排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_09_array_heap_sort.md) +- [1.10 数组计数排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_10_array_counting_sort.md) +- [1.11 数组桶排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_11_array_bucket_sort.md) +- [1.12 数组基数排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_12_array_radix_sort.md) +- [1.13 数组二分查找(一)](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_13_array_binary_search_01.md) +- [1.14 数组二分查找(二)](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_14_array_binary_search_02.md) +- [1.15 数组双指针](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_15_array_two_pointers.md) +- [1.16 数组滑动窗口](https://github.com/ITCharge/AlgoNote/tree/main/docs/01_array/01_16_array_sliding_window.md) \ No newline at end of file diff --git a/docs/02_linked_list/02_01_linked_list_basic.md b/docs/02_linked_list/02_01_linked_list_basic.md new file mode 100644 index 00000000..c88cf04a --- /dev/null +++ b/docs/02_linked_list/02_01_linked_list_basic.md @@ -0,0 +1,379 @@ +## 1. 链表简介 + +### 1.1 链表定义 + +> **链表(Linked List)**:一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。 + +简单来说,**「链表」** 是实现线性表链式存储结构的基础。 + +以单链表为例,链表的存储方式如下图所示。 + +![链表](https://qcdn.itcharge.cn/images/202405092229936.png) + +如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 $next$」。 + +在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。 + +我们先来简单介绍一下链表结构的优缺点: + +- **优点**:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。 + +- **缺点**:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。 + +接下来我们来介绍一下除了单链表之外,链表的其他几种类型。 + +### 1.2 双向链表 + +> **双向链表(Doubly Linked List)**:链表的一种,也叫做双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。 + +- **双向链表特点**:从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。 + +![双向链表](https://qcdn.itcharge.cn/images/202405092230869.png) + +### 1.3 循环链表 + +> **循环链表(Circular linked list)**:链表的一种。它的最后一个链节点指向头节点,形成一个环。 + +- **循环链表特点**:从循环链表的任何一个节点出发都能找到任何其他节点。 + +![循环链表](https://qcdn.itcharge.cn/images/202405092230094.png) + +接下来我们以最基本的「单链表」为例,介绍一下链表的基本操作。 + +## 2. 链表的基本操作 + +数据结构的操作一般涉及到增、删、改、查 4 种情况,链表的操作也基本上是这 4 种情况。我们一起来看一下链表的基本操作。 + +### 2.1 链表的结构定义 + +链表是由链节点通过 $next$ 链接而构成的,我们可以先定义一个简单的「链节点类」,再来定义完整的「链表类」。 + +- **链节点类(即 ListNode 类)**:使用成员变量 $val$ 表示数据元素的值,使用指针变量 $next$ 表示后继指针。 + +- **链表类(即 LinkedList 类)**:使用一个链节点变量 $head$ 来表示链表的头节点。 + +我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 Python 里可以将其设置为 $None$,其他语言也有类似的惯用值,比如 $NULL$、$nil$、$0$ 等。 + +**「链节点以及链表结构定义」** 的代码如下: + +```python +# 链节点类 +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +# 链表类 +class LinkedList: + def __init__(self): + self.head = None +``` + +### 2.2 建立一个线性链表 + +> **建立一个线性链表**:根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。 +> +> 1. 从所给线性表中取出第 $1$ 个数据元素,建立链表头节点。然后依次获取表中的数据元素。 +> 2. 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。 +> 3. 插入完毕之后返回第 $1$ 个链节点(即头节点)的地址。 + +**「建立一个线性链表」** 的代码如下: + +```python +# 根据 data 初始化一个新链表 +def create(self, data): + if not data: + return + self.head = ListNode(data[0]) + cur = self.head + for i in range(1, len(data)): + node = ListNode(data[i]) + cur.next = node + cur = cur.next +``` + +「建立一个线性链表」的操作依赖于线性表的数据元素个数,因此,「建立一个线性链表」的时间复杂度为 $O(n)$,$n$ 为线性表长度。 + +### 2.3 求线性链表的长度 + +> **求线性链表长度**:使用指针变量 $cur$ 顺着链表 $next$ 指针进行移动,并使用计数器 $count$ 记录元素个数。 +> +> 1. 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。 +> 2. 顺着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 +> 3. 等 $cur$ 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。 + +**「求线性链表长度」** 的代码如下: + +```python +# 获取线性链表长度 +def length(self): + count = 0 + cur = self.head + while cur: + count += 1 + cur = cur.next + return count +``` + +「求线性链表长度」的操作依赖于链表的链节点个数,操作的次数为 $n$,因此,「求线性链表长度」的时间复杂度为 $O(n)$,$n$ 为链表长度。 + +### 2.4 查找元素 + +> **在链表中查找值为 $val$ 的元素**:从头节点 $head$ 开始,沿着链表节点逐一进行查找。如果查找成功,返回被查找节点的地址;否则返回 $None$。 +> +> 1. 让指针变量 $cur$ 指向链表的第 $1$ 个链节点。 +> 2. 顺着链节点的 $next$ 指针遍历链表,如果遇到 $cur.val == val$,则返回当前指针变量 $cur$。 +> 3. 如果 $cur$ 指向为空时也未找到,则该链表中没有值为 $val$ 的元素,则返回 $None$。 + +**「在链表中查找值为 $val$ 的元素」** 的代码如下: + +```python +# 查找元素:在链表中查找值为 val 的元素 +def find(self, val): + cur = self.head + while cur: + if val == cur.val: + return cur + cur = cur.next + + return None +``` + +「在链表中查找值为 $val$ 的元素」的操作依赖于链表的链节点个数,因此,「在链表中查找值为 $val$ 的元素」的时间复杂度为 $O(n)$,$n$ 为链表长度。 + +### 2.5 插入元素 + +链表中插入元素操作分为三种: + +- **链表头部插入元素**:在链表第 $1$ 个链节点之前插入值为 $val$ 的链节点。 +- **链表尾部插入元素**:在链表最后 $1$ 个链节点之后插入值为 $val$ 的链节点。 +- **链表中间插入元素**:在链表第 $i$ 个链节点之前插入值为 $val$ 的链节点。 + +接下来我们分别讲解一下。 + +#### 2.5.1 链表头部插入元素 + +> **链表头部插入元素**:在链表第 $1$ 个链节点之前插入值为 $val$ 的链节点。 +> +> 1. 先创建一个值为 $val$ 的链节点 $node$。 +> 2. 然后将 $node$ 的 $next$ 指针指向链表的头节点 $head$。 +> 3. 再将链表的头节点 $head$ 指向 $node$。 + +![链表头部插入元素](https://qcdn.itcharge.cn/images/202405092231514.png) + +**「链表头部插入元素」** 的代码如下: + +```python +# 链表头部插入元素 +def insertFront(self, val): + node = ListNode(val) + node.next = self.head + self.head = node +``` + +「链表头部插入元素」的操作与链表的长度无关,因此,「链表头部插入元素」的时间复杂度为 $O(1)$。 + +#### 2.5.2 链表尾部插入元素 + +> **链表尾部插入元素**:在链表最后 $1$ 个链节点之后插入值为 $val$ 的链节点。 +> +> 1. 先创建一个值为 $val$ 的链节点 $node$。 +> 2. 使用指针 $cur$ 指向链表的头节点 $head$。 +> 3. 通过链节点的 $next$ 指针移动 $cur$ 指针,从而遍历链表,直到 $cur.next$ 为 $None$。 +> 4. 令 $cur.next$ 指向将新的链节点 $node$。 + +![链表尾部插入元素](https://qcdn.itcharge.cn/images/202405092232023.png) + +**「链表尾部插入元素」** 的代码如下: + +```python +# 链表尾部插入元素 +def insertRear(self, val): + node = ListNode(val) + cur = self.head + while cur.next: + cur = cur.next + cur.next = node +``` + +「链表尾部插入元素」的操作需要将 $cur$ 从链表头部移动到尾部,操作次数是 $n$ 次,因此,「链表尾部插入元素」的时间复杂度是 $O(n)$。 + +#### 2.5.3 链表中间插入元素 + +> **链表中间插入元素**:在链表第 $i$ 个链节点之前插入值为 $val$ 的链节点。 +> +> 1. 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。 +> 2. 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 +> 3. 当遍历到第 $index - 1$ 个链节点时停止遍历。 +> 4. 创建一个值为 $val$ 的链节点 $node$。 +> 5. 将 $node.next$ 指向 $cur.next$。 +> 6. 然后令 $cur.next$ 指向 $node$。 + +![链表中间插入元素](https://qcdn.itcharge.cn/images/202405092232900.png) + +**「链表中间插入元素」** 的代码如下: + +```python +# 链表中间插入元素 +def insertInside(self, index, val): + count = 0 + cur = self.head + while cur and count < index - 1: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + node = ListNode(val) + node.next = cur.next + cur.next = node +``` + +「链表中间插入元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间插入元素」的时间复杂度是 $O(n)$。 + +### 2.6 改变元素 + +> **将链表中第 $i$ 个元素值改为 $val$**:首先要先遍历到第 $i$ 个链节点,然后直接更改第 $i$ 个链节点的元素值。具体做法如下: +> +> 1. 使用指针变量 $cur$ 和一个计数器 $count$。令 $cur$ 指向链表的头节点,$count$ 初始值赋值为 $0$。 +> 2. 沿着链节点的 $next$ 指针遍历链表,指针变量 $cur$ 每指向一个链节点,计数器就做一次计数。 +> 3. 当遍历到第 $index$ 个链节点时停止遍历。 +> 4. 直接更改 $cur$ 的值 $val$。 + +**「将链表中第 $i$ 个元素值改为 $val$」** 的代码如下: + +```python +# 改变元素:将链表中第 i 个元素值改为 val +def change(self, index, val): + count = 0 + cur = self.head + while cur and count < index: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + cur.val = val +``` + +「将链表中第 $i$ 个元素值改为 $val$」需要将 $cur$ 从链表头部移动到第 $i$ 个链节点,操作的平均时间复杂度是 $O(n)$,因此,「将链表中第 $i$ 个元素值改为 $val$」的时间复杂度是 $O(n)$。 + +### 2.7 删除元素 + +链表的删除元素操作与链表的查找元素操作一样,同样分为三种情况: + +- **链表头部删除元素**:删除链表的第 $1$ 个链节点。 +- **链表尾部删除元素**:删除链表末尾最后 $1$ 个链节点。 +- **链表中间删除元素**:删除链表第 $i$ 个链节点。 + +接下来我们分别讲解一下。 + +#### 2.7.1 链表头部删除元素 + +> **链表头部删除元素**:删除链表的第 $1$ 个链节点。 +> +> 1. 直接将 $self.head$ 沿着 $next$ 指针向右移动一步即可。 + +![链表头部删除元素](https://qcdn.itcharge.cn/images/202405092231281.png) + +**「链表头部删除元素」** 的代码如下: + +```python +# 链表头部删除元素 +def removeFront(self): + if self.head: + self.head = self.head.next +``` + +「链表头部删除元」只涉及到 $1$ 步移动操作,因此,「链表头部删除元素」的时间复杂度为 $O(1)$。 + +#### 2.7.2 链表尾部删除元素 + +> **链表尾部删除元素**:删除链表末尾最后 $1$ 个链节点。 +> +> 1. 先使用指针变量 $cur$ 沿着 $next$ 指针移动到倒数第 $2$ 个链节点。 +> 2. 然后将此节点的 $next$ 指针指向 $None$ 即可。 + +![链表尾部删除元素](https://qcdn.itcharge.cn/images/202405092232050.png) + +**「链表尾部删除元素」** 的代码如下: + +```python +# 链表尾部删除元素 +def removeRear(self): + if not self.head or not self.head.next: + return 'Error' + + cur = self.head + while cur.next.next: + cur = cur.next + cur.next = None +``` + +「链表尾部删除元素」的操作涉及到移动到链表尾部,操作次数为 $n - 2$ 次,因此,「链表尾部删除元素」的时间复杂度为 $O(n)$。 + +#### 2.7.3 链表中间删除元素 + +> **链表中间删除元素**:删除链表第 $i$ 个链节点。 +> +> 1. 先使用指针变量 $cur$ 移动到第 $i - 1$ 个位置的链节点。 +> 2. 然后将 $cur$ 的 $next$ 指针,指向要第 $i$ 个元素的下一个节点即可。 + +![链表中间删除元素](https://qcdn.itcharge.cn/images/202405092233332.png) + +**「链表中间删除元素」** 的代码如下: + +```python +# 链表中间删除元素 +def removeInside(self, index): + count = 0 + cur = self.head + + while cur.next and count < index - 1: + count += 1 + cur = cur.next + + if not cur: + return 'Error' + + del_node = cur.next + cur.next = del_node.next +``` + +「链表中间删除元素」的操作需要将 $cur$ 从链表头部移动到第 $i$ 个链节点之前,操作的平均时间复杂度是 $O(n)$,因此,「链表中间删除元素」的时间复杂度是 $O(n)$。 + +--- + +到这里,有关链表的基础知识就介绍完了。下面进行一下总结。 + +## 3. 总结 + +链表是最基础、最简单的数据结构。**「链表」** 是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。 + +链表最大的优点在于可以灵活的添加和删除元素。 + +- 链表进行访问元素、改变元素操作的时间复杂度为 $O(n)$。 +- 链表进行头部插入、头部删除元素操作的时间复杂度是 $O(1)$。 +- 链表进行尾部插入、尾部删除操作的时间复杂度是 $O(n)$。 +- 链表在普通情况下进行插入、删除元素操作的时间复杂度为 $O(n)$。 + +## 练习题目 + +- [0707. 设计链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-linked-list.md) +- [0206. 反转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) +- [0203. 移除链表元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/remove-linked-list-elements.md) +- [0328. 奇偶链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/odd-even-linked-list.md) +- [0234. 回文链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) +- [0138. 随机链表的复制](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/copy-list-with-random-pointer.md) + +- [链表基础题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[链表理论基础 - 代码随想录](https://programmercarl.com/链表理论基础.html#链表理论基础) +- 【文章】[什么是链表 - 漫画算法 - 小灰的算法之旅 - 力扣](https://leetcode.cn/leetbook/read/journey-of-algorithm/5ozchs/) +- 【文章】[链表 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41013) +- 【书籍】数据结构教程 第 2 版 - 唐发根 著 +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 diff --git a/docs/02_linked_list/02_02_linked_list_sort.md b/docs/02_linked_list/02_02_linked_list_sort.md new file mode 100644 index 00000000..b566bb44 --- /dev/null +++ b/docs/02_linked_list/02_02_linked_list_sort.md @@ -0,0 +1,51 @@ +## 1. 链表排序简介 + +在数组排序中,常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 + +而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠 `next` 指针从头部顺序遍历,所以相对于数组排序问题来说,链表排序问题会更加复杂一点。 + +下面先来总结一下适合链表排序与不适合链表排序的算法: + +- 适合链表的排序算法:**冒泡排序**、**选择排序**、**插入排序**、**归并排序**、**快速排序**、**计数排序**、**桶排序**、**基数排序**。 +- 不适合链表的排序算法:**希尔排序**。 +- 可以用于链表排序但不建议使用的排序算法:**堆排序**。 + +> 希尔排序为什么不适合链表排序? + +**希尔排序**:希尔排序中经常涉及到对序列中第 $i + gap$ 的元素进行操作,其中 $gap$ 是希尔排序中当前的步长。而链表不支持随机访问的特性,导致这种操作不适合链表,因而希尔排序算法不适合进行链表排序。 + +> 为什么不建议使用堆排序? + +**堆排序**:堆排序所使用的最大堆 / 最小堆结构本质上是一棵完全二叉树。而完全二叉树适合采用顺序存储结构(数组)。因为数组存储的完全二叉树可以很方便的通过下标序号来确定父亲节点和孩子节点,并且可以极大限度的节省存储空间。 + +而链表用在存储完全二叉树的时候,因为不支持随机访问的特性,导致其寻找子节点和父亲节点会比较耗时,如果增加指向父亲节点的变量,又会浪费大量存储空间。所以堆排序算法不适合进行链表排序。 + +如果一定要对链表进行堆排序,则可以使用额外的数组空间表示堆结构。然后将链表中各个节点的值依次添加入堆结构中,对数组进行堆排序。排序后,再按照堆中元素顺序,依次建立链表节点,构建新的链表并返回新链表头节点。 + +> 需要用到额外的辅助空间进行排序的算法 + +刚才我们说到如果一定要对链表进行堆排序,则需要使用额外的数组空间。除此之外,计数排序、桶排序、基数排序都需要用到额外的数组空间。 + +接下来,我们将对适合链表排序的 8 种算法进行一一讲解。当然,这些排序算法不用完全掌握,重点是掌握 **「链表插入排序」**、**「链表归并排序」** 这两种排序算法。 + +## 2. 常见链表排序算法 + +链表排序常用的方法有冒泡排序、选择排序、插入排序、归并排序、快速排序、计数排序、桶排序、基数排序。 + +- **链表冒泡排序**:每次比较相邻两个节点的值,大的往后移。重复多次,直到链表有序。时间复杂度 $O(n^2)$,空间复杂度 $O(1)$。 +- **链表选择排序**:每次从未排序部分找到最小的节点,和当前节点交换。重复操作,直到链表有序。时间复杂度 $O(n^2)$,空间复杂度 $O(1)$。 +- **链表插入排序**:每次取一个节点,插入到已排序部分的合适位置。不断重复,直到全部节点有序。时间复杂度 $O(n^2)$,空间复杂度 $O(1)$。 +- **链表归并排序**:把链表分成两半,递归排序,再合并。适合链表,时间复杂度 O$(n \log n)$,空间复杂度 $O(1)$。 +- **链表快速排序**:选一个基准值,把小于基准的放左边,大于的放右边。对两边递归排序。时间复杂度 $O(n \log n)$,空间复杂度 $O(1)$。 +- **链表计数排序**:先找最大最小值,用数组统计每个值出现次数,再按顺序重建链表。适合值域不大时用。时间复杂度 $O(n + k)$,空间复杂度 $O(k)$。 +- **链表桶排序**:把节点分到不同的桶里,每个桶内单独排序,再合并所有桶。适合数据分布均匀时用。时间复杂度 $O(n)$,空间复杂度 $O(n + m)$。 +- **链表基数排序**:按个位、十位、百位等分多轮,把节点分到不同的桶里,再合并。适合数字位数不多时用。时间复杂度 $O(n \times k)$,空间复杂度 $O(n + k)$。 + +## 参考资料 + +- 【文章】[单链表的冒泡排序_zhao_miao的博客 - CSDN博客](https://blog.csdn.net/zhao_miao/article/details/81708454) +- 【文章】[链表排序总结(全)(C++)- 阿祭儿 - CSDN博客](https://blog.csdn.net/qq_32523711/article/details/107402873) +- 【题解】[快排、冒泡、选择排序实现列表排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/kuai-pai-mou-pao-xuan-ze-pai-xu-shi-xian-ula7/) +- 【题解】[归并排序+快速排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/gui-bing-pai-xu-kuai-su-pai-xu-by-datacruiser/) +- 【题解】[排序链表(递归+迭代)详解 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/pai-xu-lian-biao-di-gui-die-dai-xiang-jie-by-cherr/) +- 【题解】[Sort List (归并排序链表) - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) diff --git a/docs/02_linked_list/02_03_linked_list_bubble_sort.md b/docs/02_linked_list/02_03_linked_list_bubble_sort.md new file mode 100644 index 00000000..4e55fcc8 --- /dev/null +++ b/docs/02_linked_list/02_03_linked_list_bubble_sort.md @@ -0,0 +1,53 @@ +## 1. 链表冒泡排序算法描述 + +1. 使用三个指针 `node_i`、`node_j` 和 `tail`。其中 `node_i` 用于控制外循环次数,循环次数为链节点个数(链表长度)。`node_j` 和 `tail` 用于控制内循环次数和循环结束位置。 +2. 排序开始前,将 `node_i` 、`node_j` 置于头节点位置。`tail` 指向链表末尾,即 `None`。 +3. 比较链表中相邻两个元素 `node_j.val` 与 `node_j.next.val` 的值大小,如果 `node_j.val > node_j.next.val`,则值相互交换。否则不发生交换。然后向右移动 `node_j` 指针,直到 `node_j.next == tail` 时停止。 +4. 一次循环之后,将 `tail` 移动到 `node_j` 所在位置。相当于 `tail` 向左移动了一位。此时 `tail` 节点右侧为链表中最大的链节点。 +5. 然后移动 `node_i` 节点,并将 `node_j` 置于头节点位置。然后重复第 3、4 步操作。 +6. 直到 `node_i` 节点移动到链表末尾停止,排序结束。 +7. 返回链表的头节点 `head`。 + +## 2. 链表冒泡排序算法实现代码 + +```python +class Solution: + def bubbleSort(self, head: ListNode): + node_i = head + tail = None + # 外层循环次数为 链表节点个数 + while node_i: + node_j = head + while node_j and node_j.next != tail: + if node_j.val > node_j.next.val: + # 交换两个节点的值 + node_j.val, node_j.next.val = node_j.next.val, node_j.val + node_j = node_j.next + # 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表 + tail = node_j + node_i = node_i.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.bubbleSort(head) +``` + +## 3. 链表冒泡排序算法复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +## 4. 总结 + +链表冒泡排序使用三个指针进行操作。`node_i` 控制外层循环次数,`node_j` 和 `tail` 控制内层循环。每次比较相邻节点的值,需要交换时就交换。每次内循环结束后,最大的节点会移动到链表末尾。 + +这个算法的时间复杂度是 $O(n^2)$,因为需要进行两层循环。空间复杂度是 $O(1)$,因为只使用了固定数量的指针变量,没有使用额外空间。 + +链表冒泡排序适合小规模数据排序。对于大规模数据,其他排序算法可能更高效。实现时需要注意指针移动和节点交换的操作。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表冒泡排序会超时,仅做练习) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_04_linked_list_selection_sort.md b/docs/02_linked_list/02_04_linked_list_selection_sort.md new file mode 100644 index 00000000..33d53773 --- /dev/null +++ b/docs/02_linked_list/02_04_linked_list_selection_sort.md @@ -0,0 +1,46 @@ +## 1. 链表选择排序算法描述 + +1. 使用两个指针 `node_i`、`node_j`。`node_i` 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。 +2. 使用 `min_node` 记录当前未排序链表中值最小的链节点。 +3. 每一趟排序开始时,先令 `min_node = node_i`(即暂时假设链表中 `node_i` 节点为值最小的节点,经过比较后再确定最小值节点位置)。 +4. 然后依次比较未排序链表中 `node_j.val` 与 `min_node.val` 的值大小。如果 `node_j.val < min_node.val`,则更新 `min_node` 为 `node_j`。 +5. 这一趟排序结束时,未排序链表中最小值节点为 `min_node`,如果 `node_i != min_node`,则将 `node_i` 与 `min_node` 值进行交换。如果 `node_i == min_node`,则不用交换。 +6. 排序结束后,继续向右移动 `node_i`,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 `node_i` 进行比较和交换,直到 `node_i == None` 或者 `node_i.next == None` 时,停止排序。 +7. 返回链表的头节点 `head`。 + +## 2. 链表选择排序实现代码 + +```python +class Solution: + def sectionSort(self, head: ListNode): + node_i = head + # node_i 为当前未排序链表的第一个链节点 + while node_i and node_i.next: + # min_node 为未排序链表中的值最小节点 + min_node = node_i + node_j = node_i.next + while node_j: + if node_j.val < min_node.val: + min_node = node_j + node_j = node_j.next + # 交换值最小节点与未排序链表中第一个节点的值 + if node_i != min_node: + node_i.val, min_node.val = min_node.val, node_i.val + node_i = node_i.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.sectionSort(head) +``` + +## 3. 链表选择排序算法复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表选择排序会超时,仅做练习) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_05_linked_list_insertion_sort.md b/docs/02_linked_list/02_05_linked_list_insertion_sort.md new file mode 100644 index 00000000..4041a6d0 --- /dev/null +++ b/docs/02_linked_list/02_05_linked_list_insertion_sort.md @@ -0,0 +1,59 @@ + + +## 1. 链表插入排序算法描述 + +1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 +2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 +3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 +4. 比较 `sorted_list` 和 `cur` 的节点值。 + + - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 + - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 + +5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 +6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 + +## 2. 链表插入排序实现代码 + +```python +class Solution: + def insertionSort(self, head: ListNode): + if not head or not head.next: + return head + + dummy_head = ListNode(-1) + dummy_head.next = head + sorted_list = head + cur = head.next + + while cur: + if sorted_list.val <= cur.val: + # 将 cur 插入到 sorted_list 之后 + sorted_list = sorted_list.next + else: + prev = dummy_head + while prev.next.val <= cur.val: + prev = prev.next + # 将 cur 到链表中间 + sorted_list.next = cur.next + cur.next = prev.next + prev.next = cur + cur = sorted_list.next + + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.insertionSort(head) +``` + +## 3. 链表插入排序算法复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表插入排序会超时,仅做练习) +- [0147. 对链表进行插入排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/insertion-sort-list.md) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_06_linked_list_merge_sort.md b/docs/02_linked_list/02_06_linked_list_merge_sort.md new file mode 100644 index 00000000..3a3a4edd --- /dev/null +++ b/docs/02_linked_list/02_06_linked_list_merge_sort.md @@ -0,0 +1,71 @@ +## 1. 链表归并排序算法描述 + +1. **分割环节**:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。 + 1. 使用快慢指针 `fast = head.next`、`slow = head`,让 `fast` 每次移动 `2` 步,`slow` 移动 `1` 步,移动到链表末尾,从而找到链表中心链节点,即 `slow`。 + 2. 从中心位置将链表从中心位置分为左右两个链表 `left_head` 和 `right_head`,并从中心位置将其断开,即 `slow.next = None`。 + 3. 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。 +2. **归并环节**:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。 + 1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `cur` 指向 `dummy_head` 用于遍历。 + 2. 比较两个链表头节点 `left` 和 `right` 的值大小。将较小的头节点加入到合并后的链表中,并向后移动该链表的头节点指针。 + 3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 + 4. 将剩余链表插入到合并后的链表中。 + 5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后的头节点返回。 + +## 2. 链表归并排序实现代码 + +```python +class Solution: + def merge(self, left, right): + # 归并环节 + dummy_head = ListNode(-1) + cur = dummy_head + while left and right: + if left.val <= right.val: + cur.next = left + left = left.next + else: + cur.next = right + right = right.next + cur = cur.next + + if left: + cur.next = left + elif right: + cur.next = right + + return dummy_head.next + + def mergeSort(self, head: ListNode): + # 分割环节 + if not head or not head.next: + return head + + # 快慢指针找到中心链节点 + slow, fast = head, head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # 断开左右链节点 + left_head, right_head = head, slow.next + slow.next = None + + # 归并操作 + return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.mergeSort(head) +``` + +## 3. 链表归并排序算法复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) +- [0021. 合并两个有序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) +- [0023. 合并 K 个升序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_07_linked_list_quick_sort.md b/docs/02_linked_list/02_07_linked_list_quick_sort.md new file mode 100644 index 00000000..ad7c755f --- /dev/null +++ b/docs/02_linked_list/02_07_linked_list_quick_sort.md @@ -0,0 +1,55 @@ +## 1. 链表快速排序算法描述 + +1. 从链表中找到一个基准值 `pivot`,这里以头节点为基准值。 +2. 然后通过快慢指针 `node_i`、`node_j` 在链表中移动,使得 `node_i` 之前的节点值都小于基准值,`node_i` 之后的节点值都大于基准值。从而把数组拆分为左右两个部分。 +3. 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。 + +## 2. 链表快速排序实现代码 + +```python +class Solution: + def partition(self, left: ListNode, right: ListNode): + # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 + if left == right or left.next == right: + return left + # 选择头节点为基准节点 + pivot = left.val + # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 + node_i, node_j = left, left.next + + while node_j != right: + # 发现一个小与基准值的元素 + if node_j.val < pivot: + # 因为 node_i 之前节点都小于基准值,所以先将 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) + node_i = node_i.next + # 将小于基准值的元素 node_j 与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 + node_i.val, node_j.val = node_j.val, node_i.val + node_j = node_j.next + # 将基准节点放到正确位置上 + node_i.val, left.val = left.val, node_i.val + return node_i + + def quickSort(self, left: ListNode, right: ListNode): + if left == right or left.next == right: + return left + pi = self.partition(left, right) + self.quickSort(left, pi) + self.quickSort(pi.next, right) + return left + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + if not head or not head.next: + return head + return self.quickSort(head, None) +``` + +## 3. 链表快速排序算法复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表快速排序会超时,仅做练习) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_08_linked_list_counting_sort.md b/docs/02_linked_list/02_08_linked_list_counting_sort.md new file mode 100644 index 00000000..146e40b8 --- /dev/null +++ b/docs/02_linked_list/02_08_linked_list_counting_sort.md @@ -0,0 +1,59 @@ +## 1. 链表计数排序算法描述 + +1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 +2. 使用数组 `counts` 存储节点出现次数。 +3. 再次使用 `cur` 指针遍历一遍链表。将链表中每个值为 `cur.val` 的节点出现次数,存入数组对应第 `cur.val - list_min` 项中。 +4. 反向填充目标链表: + 1. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 + 2. 从小到大遍历一遍数组 `counts`。对于每个 `counts[i] != 0` 的元素建立一个链节点,值为 `i + list_min`,将其插入到 `cur.next` 上。并向右移动 `cur`。同时 `counts[i] -= 1`。直到 `counts[i] == 0` 后继续向后遍历数组 `counts`。 +5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 + +## 2. 链表计数排序代码实现 + +```python +class Solution: + def countingSort(self, head: ListNode): + if not head: + return head + + # 找出链表中最大值 list_max 和最小值 list_min + list_min, list_max = float('inf'), float('-inf') + cur = head + while cur: + if cur.val < list_min: + list_min = cur.val + if cur.val > list_max: + list_max = cur.val + cur = cur.next + + size = list_max - list_min + 1 + counts = [0 for _ in range(size)] + + cur = head + while cur: + counts[cur.val - list_min] += 1 + cur = cur.next + + dummy_head = ListNode(-1) + cur = dummy_head + for i in range(size): + while counts[i]: + cur.next = ListNode(i + list_min) + counts[i] -= 1 + cur = cur.next + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.countingSort(head) +``` + +## 3. 链表计数排序算法复杂度分析 + +- **时间复杂度**:$O(n + k)$,其中 $k$ 代表待排序链表中所有元素的值域。 +- **空间复杂度**:$O(k)$。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_09_linked_list_bucket_sort.md b/docs/02_linked_list/02_09_linked_list_bucket_sort.md new file mode 100644 index 00000000..15f00c61 --- /dev/null +++ b/docs/02_linked_list/02_09_linked_list_bucket_sort.md @@ -0,0 +1,117 @@ +## 1. 链表桶排序算法描述 + + 1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 + 2. 通过 `(最大值 - 最小值) / 每个桶的大小` 计算出桶的个数,即 `bucket_count = (list_max - list_min) // bucket_size + 1` 个桶。 + 3. 定义数组 `buckets` 为桶,桶的个数为 `bucket_count` 个。 + 4. 使用 `cur` 指针再次遍历一遍链表,将每个元素装入对应的桶中。 + 5. 对每个桶内的元素单独排序,可以使用链表插入排序、链表归并排序、链表快速排序等算法。 + 6. 最后按照顺序将桶内的元素拼成新的链表,并返回。 + +## 2. 链表桶排序代码实现 + + ```python +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + # 将链表节点值 val 添加到对应桶 buckets[index] 中 + def insertion(self, buckets, index, val): + if not buckets[index]: + buckets[index] = ListNode(val) + return + + node = ListNode(val) + node.next = buckets[index] + buckets[index] = node + + # 归并环节 + def merge(self, left, right): + dummy_head = ListNode(-1) + cur = dummy_head + while left and right: + if left.val <= right.val: + cur.next = left + left = left.next + else: + cur.next = right + right = right.next + cur = cur.next + + if left: + cur.next = left + elif right: + cur.next = right + + return dummy_head.next + + def mergeSort(self, head: ListNode): + # 分割环节 + if not head or not head.next: + return head + + # 快慢指针找到中心链节点 + slow, fast = head, head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # 断开左右链节点 + left_head, right_head = head, slow.next + slow.next = None + + # 归并操作 + return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) + + def bucketSort(self, head: ListNode, bucket_size=5): + if not head: + return head + + # 找出链表中最大值 list_max 和最小值 list_min + list_min, list_max = float('inf'), float('-inf') + cur = head + while cur: + if cur.val < list_min: + list_min = cur.val + if cur.val > list_max: + list_max = cur.val + cur = cur.next + + # 计算桶的个数,并定义桶 + bucket_count = (list_max - list_min) // bucket_size + 1 + buckets = [None for _ in range(bucket_count)] + + # 将链表节点值依次添加到对应桶中 + cur = head + while cur: + index = (cur.val - list_min) // bucket_size + self.insertion(buckets, index, cur.val) + cur = cur.next + + dummy_head = ListNode(-1) + cur = dummy_head + # 将元素依次出桶,并拼接成有序链表 + for bucket_head in buckets: + bucket_cur = self.mergeSort(bucket_head) + while bucket_cur: + cur.next = bucket_cur + cur = cur.next + bucket_cur = bucket_cur.next + + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.bucketSort(head) + ``` + +## 3. 链表桶排序算法复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 + +## 练习题目 + +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_10_linked_list_radix_sort.md b/docs/02_linked_list/02_10_linked_list_radix_sort.md new file mode 100644 index 00000000..8ff70d47 --- /dev/null +++ b/docs/02_linked_list/02_10_linked_list_radix_sort.md @@ -0,0 +1,56 @@ +## 1. 链表基数排序算法描述 + +1. 使用 `cur` 指针遍历链表,获取节点值位数最长的位数 `size`。 +2. 从个位到高位遍历位数。因为 `0` ~ `9` 共有 `10` 位数字,所以建立 `10` 个桶。 +3. 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。 +4. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 +5. 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。 +6. 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。 +7. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 + +## 2. 链表基数排序代码实现 + +```python +class Solution: + def radixSort(self, head: ListNode): + # 计算位数最长的位数 + size = 0 + cur = head + while cur: + val_len = len(str(cur.val)) + if val_len > size: + size = val_len + cur = cur.next + + # 从个位到高位遍历位数 + for i in range(size): + buckets = [[] for _ in range(10)] + cur = head + while cur: + # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中 + buckets[cur.val // (10 ** i) % 10].append(cur.val) + cur = cur.next + + # 生成新的链表 + dummy_head = ListNode(-1) + cur = dummy_head + for bucket in buckets: + for num in bucket: + cur.next = ListNode(num) + cur = cur.next + head = dummy_head.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.radixSort(head) +``` + +## 3. 链表基数排序算法复杂度分析 + +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 + +## 练习题目 + +- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/02_11_linked_list_two_pointers.md b/docs/02_linked_list/02_11_linked_list_two_pointers.md new file mode 100644 index 00000000..5163347c --- /dev/null +++ b/docs/02_linked_list/02_11_linked_list_two_pointers.md @@ -0,0 +1,416 @@ +## 1. 双指针简介 + +在数组双指针中我们已经学习过了双指针的概念。这里再来复习一下。 + +> **双指针(Two Pointers)**:指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞时针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 + +而在单链表中,因为遍历节点只能顺着 `next` 指针方向进行,所以对于链表而言,一般只会用到「快慢指针」和「分离双指针」。其中链表的「快慢指针」又分为「起点不一致的快慢指针」和「步长不一致的快慢指针」。这几种类型的双指针所解决的问题也各不相同,下面我们一一进行讲解。 + +## 2. 起点不一致的快慢指针 + +>**起点不一致的快慢指针**:指的是两个指针从同一侧开始遍历链表,但是两个指针的起点不一样。 快指针 `fast` 比慢指针 `slow` 先走 `n` 步,直到快指针移动到链表尾端时为止。 + +### 2.1 起点不一致的快慢指针求解步骤 + +1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点,即:`slow = head`,`fast = head`。 +2. 先将快指针向右移动 `n` 步。然后再同时向右移动快、慢指针。 +3. 等到快指针移动到链表尾部(即 `fast == None`)时跳出循环体。 + +### 2.2 起点不一致的快慢指针伪代码模板 + +```python +slow = head +fast = head + +while n: + fast = fast.next + n -= 1 +while fast: + fast = fast.next + slow = slow.next +``` + +### 2.3 起点不一致的快慢指针适用范围 + +起点不一致的快慢指针主要用于找到链表中倒数第 k 个节点、删除链表倒数第 N 个节点等。 + +### 2.4 删除链表的倒数第 N 个结点 + +#### 2.4.1 题目链接 + +- [19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) + +#### 2.4.2 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。 + +**说明**: + +- 要求使用一次遍历实现。 +- 链表中结点的数目为 `sz`。 +- $1 \le sz \le 30$。 +- $0 \le Node.val \le 100$。 +- $1 \le n \le sz$。 + +**示例**: + +![](https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg) + +```python +输入:head = [1,2,3,4,5], n = 2 +输出:[1,2,3,5] + + +输入:head = [1], n = 1 +输出:[] +``` + +#### 2.4.3 解题思路 + +##### 思路 1:快慢指针 + +常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 + +如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `n` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `n` 个节点位置。将该位置上的节点删除即可。 + +需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 + +##### 思路 1:代码 + +```python +class Solution: + def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: + newHead = ListNode(0, head) + fast = head + slow = newHead + while n: + fast = fast.next + n -= 1 + while fast: + fast = fast.next + slow = slow.next + slow.next = slow.next.next + return newHead.next +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 3. 步长不一致的快慢指针 + +> **步长不一致的快慢指针**:指的是两个指针从同一侧开始遍历链表,两个指针的起点一样,但是步长不一致。例如,慢指针 `slow` 每次走 `1` 步,快指针 `fast` 每次走两步。直到快指针移动到链表尾端时为止。 + +### 3.1 步长不一致的快慢指针求解步骤 + +1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 +2. 在循环体中将快、慢指针同时向右移动,但是快、慢指针的移动步长不一致。比如将慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 +3. 等到快指针移动到链表尾部(即 `fast == None`)时跳出循环体。 + +### 3.2 步长不一致的快慢指针伪代码模板 + +```python +fast = head +slow = head + +while fast and fast.next: + slow = slow.next + fast = fast.next.next +``` + +### 3.3 步长不一致的快慢指针适用范围 + +步长不一致的快慢指针适合寻找链表的中点、判断和检测链表是否有环、找到两个链表的交点等问题。 + +### 3.4 链表的中间结点 + +#### 3.4.1 题目链接 + +- [876. 链表的中间结点 - 力扣(LeetCode)](https://leetcode.cn/problems/middle-of-the-linked-list/) + +#### 3.4.2 题目大意 + +**描述**:给定一个单链表的头节点 `head`。 + +**要求**:返回链表的中间节点。如果有两个中间节点,则返回第二个中间节点。 + +**说明**: + +- 给定链表的结点数介于 `1` 和 `100` 之间。 + +**示例**: + +```python +输入:[1,2,3,4,5] +输出:此列表中的结点 3 (序列化形式:[3,4,5]) +解释:返回的结点值为 3 。 +注意,我们返回了一个 ListNode 类型的对象 ans,这样: +ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. + + +输入:[1,2,3,4,5,6] +输出:此列表中的结点 4 (序列化形式:[4,5,6]) +解释:由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 +``` + +#### 3.4.3 解题思路 + +##### 思路 1:单指针 + +先遍历一遍链表,统计一下节点个数为 `n`,再遍历到 `n / 2` 的位置,返回中间节点。 + +##### 思路 1:代码 + +```python +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + n = 0 + curr = head + while curr: + n += 1 + curr = curr.next + k = 0 + curr = head + while k < n // 2: + k += 1 + curr = curr.next + return curr +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +##### 思路 2:快慢指针 + +使用步长不一致的快慢指针进行一次遍历找到链表的中间节点。具体做法如下: + +1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 +2. 在循环体中将快、慢指针同时向右移动。其中慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 +3. 等到快指针移动到链表尾部(即 `fast == Node`)时跳出循环体,此时 `slow` 指向链表中间位置。 +4. 返回 `slow` 指针。 + +##### 思路 2:代码 + +```python +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + fast = head + slow = head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + return slow +``` + +##### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 3.5 判断链表中是否含有环 + +#### 3.5.1 题目链接 + +- [141. 环形链表 - 力扣(LeetCode)](https://leetcode.cn/problems/linked-list-cycle/) + +#### 3.5.2 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:判断链表中是否有环。如果有环则返回 `True`,否则返回 `False`。 + +**说明**: + +- 链表中节点的数目范围是 $[0, 10^4]$。 +- $-10^5 \le Node.val \le 10^5$。 +- `pos` 为 `-1` 或者链表中的一个有效索引。 + +**示例**: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) + +```python +输入:head = [3,2,0,-4], pos = 1 +输出:True +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) + +```python +输入:head = [1,2], pos = 0 +输出:True +解释:链表中有一个环,其尾部连接到第一个节点。 +``` + +#### 3.5.3 解题思路 + +##### 思路 1:哈希表 + +最简单的思路是遍历所有节点,每次遍历节点之前,使用哈希表判断该节点是否被访问过。如果访问过就说明存在环,如果没访问过则将该节点添加到哈希表中,继续遍历判断。 + +##### 思路 1:代码 + +```python +class Solution: + def hasCycle(self, head: ListNode) -> bool: + nodeset = set() + + while head: + if head in nodeset: + return True + nodeset.add(head) + head = head.next + return False +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +##### 思路 2:快慢指针(Floyd 判圈算法) + +这种方法类似于在操场跑道跑步。两个人从同一位置同时出发,如果跑道有环(环形跑道),那么快的一方总能追上慢的一方。 + +基于上边的想法,Floyd 用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 + +##### 思路 2:代码 + +```python +class Solution: + def hasCycle(self, head: ListNode) -> bool: + if head == None or head.next == None: + return False + + slow = head + fast = head.next + + while slow != fast: + if fast == None or fast.next == None: + return False + slow = slow.next + fast = fast.next.next + + return True +``` + +##### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 4. 分离双指针 + +> **分离双指针**:两个指针分别属于不同的链表,两个指针分别在两个链表中移动。 + +### 4.1 分离双指针求解步骤 + +1. 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个链表头节点,即:`left_1 = list1`,`left_2` 指向第二个链表头节点,即:`left_2 = list2`。 +2. 当满足一定条件时,两个指针同时右移,即 `left_1 = left_1.next`、`left_2 = left_2.next`。 +3. 当满足另外一定条件时,将 `left_1` 指针右移,即 `left_1 = left_1.next`。 +4. 当满足其他一定条件时,将 `left_2` 指针右移,即 `left_2 = left_2.next`。 +5. 当其中一个链表遍历完时或者满足其他特殊条件时跳出循环体。 + +### 4.2 分离双指针伪代码模板 + +```python +left_1 = list1 +left_2 = list2 + +while left_1 and left_2: + if 一定条件 1: + left_1 = left_1.next + left_2 = left_2.next + elif 一定条件 2: + left_1 = left_1.next + elif 一定条件 3: + left_2 = left_2.next +``` + +### 4.3 分离双指针适用范围 + +分离双指针一般用于有序链表合并等问题。 + +### 4.4 合并两个有序链表 + +#### 4.4.1 题目链接 + +- [21. 合并两个有序链表 - 力扣(LeetCode)](https://leetcode.cn/problems/merge-two-sorted-lists/) + +#### 4.4.2 题目大意 + +**描述**:给定两个升序链表的头节点 `list1` 和 `list2`。 + +**要求**:将其合并为一个升序链表。 + +**说明**: + +- 两个链表的节点数目范围是 $[0, 50]$。 +- $-100 \le Node.val \le 100$。 +- `list1` 和 `list2` 均按 **非递减顺序** 排列 + +**示例**: + +![](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) + +```python +输入:list1 = [1,2,4], list2 = [1,3,4] +输出:[1,1,2,3,4,4] + + +输入:list1 = [], list2 = [] +输出:[] +``` + +#### 4.4.3 解题思路 + +##### 思路 1:归并排序 + +利用归并排序的思想,具体步骤如下: + +1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `curr` 指向 `dummy_head` 用于遍历。 +2. 然后判断 `list1` 和 `list2` 头节点的值,将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 +3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 +4. 将剩余链表链接到合并后的链表中。 +5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后有序链表的头节点返回。 + +##### 思路 1:代码 + +```python +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + dummy_head = ListNode(-1) + + curr = dummy_head + while list1 and list2: + if list1.val <= list2.val: + curr.next = list1 + list1 = list1.next + else: + curr.next = list2 + list2 = list2.next + curr = curr.next + + curr.next = list1 if list1 is not None else list2 + + return dummy_head.next +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0141. 环形链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) +- [0142. 环形链表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) +- [0019. 删除链表的倒数第 N 个结点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) + +- [链表双指针题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E5%8F%8C%E6%8C%87%E9%92%88%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/02_linked_list/index.md b/docs/02_linked_list/index.md new file mode 100644 index 00000000..9e76ebae --- /dev/null +++ b/docs/02_linked_list/index.md @@ -0,0 +1,13 @@ +## 本章内容 + +- [2.1 链表基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_01_linked_list_basic.md) +- [2.2 链表排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_02_linked_list_sort.md) +- [2.3 链表冒泡排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_03_linked_list_bubble_sort.md) +- [2.4 链表选择排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_04_linked_list_selection_sort.md) +- [2.5 链表插入排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_05_linked_list_insertion_sort.md) +- [2.6 链表归并排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_06_linked_list_merge_sort.md) +- [2.7 链表快速排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_07_linked_list_quick_sort.md) +- [2.8 链表计数排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_08_linked_list_counting_sort.md) +- [2.9 链表桶排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_09_linked_list_bucket_sort.md) +- [2.10 链表基数排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_10_linked_list_radix_sort.md) +- [2.11 链表双指针](https://github.com/ITCharge/AlgoNote/tree/main/docs/02_linked_list/02_11_linked_list_two_pointers.md) diff --git a/docs/03_stack_queue_hash_table/03_01_stack_basic.md b/docs/03_stack_queue_hash_table/03_01_stack_basic.md new file mode 100644 index 00000000..6b081616 --- /dev/null +++ b/docs/03_stack_queue_hash_table/03_01_stack_basic.md @@ -0,0 +1,367 @@ +## 1. 栈简介 + +> **栈(Stack)**:也叫做「堆栈」,是一种线性表数据结构,是一种只允许在表的一端进行插入和删除操作的线性表。 + +我们把栈中允许插入和删除的一端称为 **「栈顶(top)」**;另一端则称为 **「栈底(bottom)」**。当表中没有任何数据元素时,称之为 **「空栈」**。 + +栈有两种基本操作:**「插入操作」** 和 **「删除操作」**。 + +- 栈的插入操作又称为「入栈」或者「进栈」。 +- 栈的删除操作又称为「出栈」或者「退栈」。 + +![栈结构](https://qcdn.itcharge.cn/images/202405092243204.png) + +简单来说,栈是一种 **「后进先出(Last In First Out)」** 的线性表,简称为 **「LIFO 结构」**。 + +我们可以从两个方面来解释一下栈的定义: + +- 第一个方面是 **「线性表」**。 + +栈首先是一个线性表,栈中元素具有前驱后继的线性关系。栈中元素按照 $a_1, a_2, ... , a_n$ 的次序依次进栈。栈顶元素为 $a_n$。 + +- 第二个方面是 **「后进先出原则」**。 + +根据栈的定义,每次删除的总是栈中当前的栈顶元素,即最后进入栈的元素。而在进栈时,最先进入栈的元素一定在栈底,最后进入栈的元素一定在栈顶。也就是说,元素进入栈或者退出退栈是按照「后进先出(Last In First Out)」的原则进行的。 + +## 2. 栈的顺序存储与链式存储 + +和线性表类似,栈有两种存储表示方法:**「顺序栈」** 和 **「链式栈」**。 + +- **「顺序栈」**:即栈的顺序存储结构。利用一组地址连续的存储单元依次存放自栈底到栈顶的元素,同时使用指针 $top$ 指示栈顶元素在顺序栈中的位置。 +- **「链式栈」**:即栈的链式存储结构。利用单链表的方式来实现栈。栈中元素按照插入顺序依次插入到链表的第一个节点之前,并使用栈顶指针 $top$ 指示栈顶元素,$top$ 永远指向链表的头节点位置。 + +在描述栈的顺序存储与链式存储具体实现之前,我们先来看看栈具有哪些基本操作。 + +### 2.1 栈的基本操作 + +栈作为一种线性表来说,理论上应该具备线性表所有的操作特性,但由于「后进先出」的特殊性,所以针对栈的操作进行了一些变化。尤其是插入操作和删除操作,改为了入栈(push)和出栈(pop)。 + +栈的基本操作如下: + +- **初始化空栈**:创建一个空栈,定义栈的大小 $size$,以及栈顶元素指针 $top$。 + +- **判断栈是否为空**:当栈为空时,返回 $True$。当栈不为空时,返回 $False$。一般只用于栈中删除操作和获取当前栈顶元素操作中。 + +- **判断栈是否已满**:当栈已满时,返回 $True$,当栈未满时,返回 $False$。一般只用于顺序栈中插入元素和获取当前栈顶元素操作中。 + +- **插入元素(进栈、入栈)**:相当于在线性表最后元素后面插入一个新的数据元素。并改变栈顶指针 $top$ 的指向位置。 + +- **删除元素(出栈、退栈)**:相当于在线性表最后元素后面删除最后一个数据元素。并改变栈顶指针 $top$ 的指向位置。 +- **获取栈顶元素**:相当于获取线性表中最后一个数据元素。与插入元素、删除元素不同的是,该操作并不改变栈顶指针 $top$ 的指向位置。 + +接下来我们来看一下栈的顺序存储与链式存储两种不同的实现方式。 + +### 2.2 栈的顺序存储实现 + +栈最简单的实现方式就是借助于一个数组来描述栈的顺序存储结构。在 Python 中我们可以借助列表 $list$ 来实现。这种采用顺序存储结构的栈也被称为 **「顺序栈」**。 + +#### 2.2.1 栈的顺序存储基本描述 + +![栈的顺序存储](https://qcdn.itcharge.cn/images/202405092243306.png) + +我们约定 $self.top$ 指向栈顶元素所在位置。 + +- **初始化空栈**:使用列表创建一个空栈,定义栈的大小 $self.size$,并令栈顶元素指针 $self.top$ 指向 $-1$,即 $self.top = -1$。 +- **判断栈是否为空**:当 $self.top == -1$ 时,说明栈为空,返回 $True$,否则返回 $False$。 +- **判断栈是否已满**:当 $self.top == self.size - 1$,说明栈已满,返回 $True$,否则返回返回 $False$。 +- **插入元素(进栈、入栈)**:先判断栈是否已满,已满直接抛出异常。如果栈未满,则在 $self.stack$ 末尾插入新的数据元素,并令 $self.top$ 向右移动 $1$ 位。 +- **删除元素(出栈、退栈)**:先判断栈是否为空,为空直接抛出异常。如果栈不为空,则删除 $self.stack$ 末尾的数据元素,并令 $self.top$ 向左移动 $1$ 位。 +- **获取栈顶元素**:先判断栈是否为空,为空直接抛出异常。不为空则返回 $self.top$ 指向的栈顶元素,即 $self.stack[self.top]$。 + +#### 2.2.2 栈的顺序存储实现代码 + +```python +class Stack: + # 初始化空栈 + def __init__(self, size=100): + self.stack = [] + self.size = size + self.top = -1 + + # 判断栈是否为空 + def is_empty(self): + return self.top == -1 + + # 判断栈是否已满 + def is_full(self): + return self.top + 1 == self.size + + # 入栈操作 + def push(self, value): + if self.is_full(): + raise Exception('Stack is full') + else: + self.stack.append(value) + self.top += 1 + + # 出栈操作 + def pop(self): + if self.is_empty(): + raise Exception('Stack is empty') + else: + self.stack.pop() + self.top -= 1 + + # 获取栈顶元素 + def peek(self): + if self.is_empty(): + raise Exception('Stack is empty') + else: + return self.stack[self.top] +``` + +### 2.3 栈的链式存储实现 + +栈的顺序存储结构保留着顺序存储分配空间的固有缺陷,即在栈满或者其他需要重新调整存储空间时需要移动大量元素。为此,栈可以采用链式存储方式来实现。在 Python 中我们通过构造链表节点 $Node$ 的方式来实现。这种采用链式存储结构的栈也被称为 **「链式栈」**。 + +![栈的链式存储](https://qcdn.itcharge.cn/images/202405092243367.png) + +#### 2.3.1 栈的链式存储基本描述 + +我们约定 $self.top$ 指向栈顶元素所在位置。 + +- **初始化空栈**:使用列表创建一个空栈,并令栈顶元素指针 $self.top$ 指向 $None$,即 $self.top = None$。 +- **判断栈是否为空**:当 $self.top == None$ 时,说明栈为空,返回 $True$,否则返回 $False$。 +- **插入元素(进栈、入栈)**:创建值为 $value$ 的链表节点,插入到链表头节点之前,并令栈顶指针 $self.top$ 指向新的头节点。 +- **删除元素(出栈、退栈)**:先判断栈是否为空,为空直接抛出异常。如果栈不为空,则先使用变量 $cur$ 存储当前栈顶指针 $self.top$ 指向的头节点,然后令 $self.top$ 沿着链表移动 $1$ 位,然后再删除之前保存的 $cur$ 节点。 +- **获取栈顶元素**:先判断栈是否为空,为空直接抛出异常。不为空则返回 $self.top$ 指向的栈顶节点的值,即 $self.top.value$。 + +#### 2.3.2 栈的链式存储实现代码 + +```python +class Node: + def __init__(self, value): + self.value = value + self.next = None + +class Stack: + # 初始化空栈 + def __init__(self): + self.top = None + + # 判断栈是否为空 + def is_empty(self): + return self.top == None + + # 入栈操作 + def push(self, value): + cur = Node(value) + cur.next = self.top + self.top = cur + + # 出栈操作 + def pop(self): + if self.is_empty(): + raise Exception('Stack is empty') + else: + cur = self.top + self.top = self.top.next + del cur + + # 获取栈顶元素 + def peek(self): + if self.is_empty(): + raise Exception('Stack is empty') + else: + return self.top.value +``` + +## 3. 栈的应用 + +栈是算法和程序中最常用的辅助结构,其的应用十分广泛。栈基本应用于两个方面: + +- 使用栈可以很方便的保存和取用信息,因此长被用作算法和程序中的辅助存储结构,临时保存信息,供后面操作中使用。 + - 例如:操作系统中的函数调用栈,浏览器中的前进、后退功能。 +- 栈的后进先出规则,可以保证特定的存取顺序。 + - 例如:翻转一组元素的顺序、铁路列车车辆调度。 + +下面我们来讲解一下栈应用的典型例子。 + +### 3.1 括号匹配问题 + +#### 3.1.1 题目链接 + +- [20. 有效的括号 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-parentheses/) + +#### 3.1.2 题目大意 + +**描述**:给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串 $s$。 + +**要求**:判断字符串 $s$ 是否有效(即括号是否匹配)。 + +**说明**: + +- 有效字符串需满足: + 1. 左括号必须用相同类型的右括号闭合。 + 2. 左括号必须以正确的顺序闭合。 + +**示例**: + +```python +输入:s = "()" +输出:True + + +输入:s = "()[]{}" +输出:True +``` + +#### 3.2.3 解题思路 + +##### 思路 1:栈 + +括号匹配是「栈」的经典应用。我们可以用栈来解决这道题。具体做法如下: + +1. 先判断一下字符串的长度是否为偶数。因为括号是成对出现的,所以字符串的长度应为偶数,可以直接判断长度为奇数的字符串不匹配。如果字符串长度为奇数,则说明字符串 $s$ 中的括号不匹配,直接返回 $False$。 +2. 使用栈 $stack$ 来保存未匹配的左括号。然后依次遍历字符串 $s$ 中的每一个字符。 + 1. 如果遍历到左括号时,将其入栈。 + 2. 如果遍历到右括号时,先看栈顶元素是否是与当前右括号相同类型的左括号。 + 1. 如果是与当前右括号相同类型的左括号,则令其出栈,继续向前遍历。 + 2. 如果不是与当前右括号相同类型的左括号,则说明字符串 $s$ 中的括号不匹配,直接返回 $False$。 +3. 遍历完,还要再判断一下栈是否为空。 + 1. 如果栈为空,则说明字符串 $s$ 中的括号匹配,返回 $True$。 + 2. 如果栈不为空,则说明字符串 $s$ 中的括号不匹配,返回 $False$。 + +##### 思路 1:代码 + +```python +class Solution: + def isValid(self, s: str) -> bool: + if len(s) % 2 == 1: + return False + stack = list() + for ch in s: + if ch == '(' or ch == '[' or ch == '{': + stack.append(ch) + elif ch == ')': + if len(stack) !=0 and stack[-1] == '(': + stack.pop() + else: + return False + elif ch == ']': + if len(stack) !=0 and stack[-1] == '[': + stack.pop() + else: + return False + elif ch == '}': + if len(stack) !=0 and stack[-1] == '{': + stack.pop() + else: + return False + if len(stack) == 0: + return True + else: + return False +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 3.2 表达式求值问题 + +#### 3.2.1 题目链接 + +- [227. 基本计算器 II - 力扣(LeetCode)](https://leetcode.cn/problems/basic-calculator-ii/) + +#### 3.2.2 题目大意 + +**描述**:给定一个字符串表达式 $s$,表达式中所有整数为非负整数,运算符只有 `+`、`-`、`*`、`/`,没有括号。 + +**要求**:实现一个基本计算器来计算并返回它的值。 + +**说明**: + +- $1 \le s.length \le 3 * 10^5$。 +- $s$ 由整数和算符(`+`、`-`、`*`、`/`)组成,中间由一些空格隔开。 +- $s$ 表示一个有效表达式。 +- 表达式中的所有整数都是非负整数,且在范围 $[0, 2^{31} - 1]$ 内。 +- 题目数据保证答案是一个 32-bit 整数。 + +**示例**: + +```python +输入:s = "3+2*2" +输出:7 + + +输入:s = " 3/2 " +输出:1 +``` + +#### 3.2.3 解题思路 + +##### 思路 1:栈 + +计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 + +可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 + +具体做法: + +1. 遍历字符串 $s$,使用变量 $op$ 来标记数字之前的运算符,默认为 `+`。 +2. 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 + 1. 如果 $op$ 为 `+`,则将 $num$ 压入栈中。 + 2. 如果 $op$ 为 `-`,则将 $-num$ 压入栈中。 + 3. 如果 $op$ 为 `*`,则将栈顶元素 $top$ 取出,计算 $top \times num$,并将计算结果压入栈中。 + 4. 如果 $op$ 为 `/`,则将栈顶元素 $top$ 取出,计算 $int(top / num)$,并将计算结果压入栈中。 +3. 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 $op$。 +4. 最后将栈中整数进行累加,并返回结果。 + +##### 思路 1:代码 + +```python +class Solution: + def calculate(self, s: str) -> int: + size = len(s) + stack = [] + op = '+' + index = 0 + while index < size: + if s[index] == ' ': + index += 1 + continue + if s[index].isdigit(): + num = ord(s[index]) - ord('0') + while index + 1 < size and s[index+1].isdigit(): + index += 1 + num = 10 * num + ord(s[index]) - ord('0') + if op == '+': + stack.append(num) + elif op == '-': + stack.append(-num) + elif op == '*': + top = stack.pop() + stack.append(top * num) + elif op == '/': + top = stack.pop() + stack.append(int(top / num)) + elif s[index] in "+-*/": + op = s[index] + index += 1 + return sum(stack) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0155. 最小栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) +- [0020. 有效的括号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) +- [0227. 基本计算器 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) +- [0150. 逆波兰表达式求值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md) +- [0394. 字符串解码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) +- [0946. 验证栈序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/validate-stack-sequences.md) + +- [栈基础题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%A0%88%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 +- 【书籍】数据结构教程 第 3 版 - 唐发根 著 +- 【书籍】大话数据结构 程杰 著 +- 【文章】[栈 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41222) diff --git a/docs/03_stack_queue_hash_table/03_02_monotone_stack.md b/docs/03_stack_queue_hash_table/03_02_monotone_stack.md new file mode 100644 index 00000000..4fc4b9be --- /dev/null +++ b/docs/03_stack_queue_hash_table/03_02_monotone_stack.md @@ -0,0 +1,284 @@ +## 1. 单调栈简介 + +> **单调栈(Monotone Stack)**:一种特殊的栈。在栈的「先进后出」规则基础上,要求「从 **栈顶** 到 **栈底** 的元素是单调递增(或者单调递减)」。其中满足从栈顶到栈底的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」。 + +注意:这里定义的顺序是从「栈顶」到「栈底」。有的文章里是反过来的。本文全文以「栈顶」到「栈底」的顺序为基准来描述单调栈。 + +### 1.1 单调递增栈 + +> **单调递增栈**:只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。 +> +> 这样就保证了:栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的。 + +单调递增栈的入栈、出栈过程如下: + +- 假设当前进栈元素为 $x$,如果 $x$ 比栈顶元素小,则直接入栈。 +- 否则从栈顶开始遍历栈中元素,把小于 $x$ 或者等于 $x$ 的元素弹出栈,直到遇到一个大于 $x$ 的元素为止,然后再把 $x$ 压入栈中。 + +下面我们以数组 $[2, 7, 5, 4, 6, 3, 4, 2]$ 为例,模拟一下「单调递增栈」的进栈、出栈过程。具体过程如下: + +- 数组元素:$[2, 7, 5, 4, 6, 3, 4, 2]$,遍历顺序为从左到右。 + +| 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作 用 | +| :-----: | :--------: | ---------------------- | ------------------- | ------------------------------------- | +| 1 | 2 | 2 入栈 | [2] | 元素 2 的左侧无比 2 大的元素 | +| 2 | 7 | 2 出栈,7 入栈 | [7] | 元素 7 的左侧无比 7 大的元素 | +| 3 | 5 | 5 入栈 | [7, 5] | 元素 5 的左侧第一个比 5 大的元素为:7 | +| 4 | 4 | 4 入栈 | [7, 5, 4] | 元素 4 的左侧第一个比 4 大的元素为:5 | +| 5 | 6 | 4 出栈,5 出栈,6 入栈 | [7, 6] | 元素 6 的左侧第一个比 6 大的元素为:7 | +| 6 | 3 | 3 入栈 | [7, 6, 3] | 元素 3 的左侧第一个比 3 大的元素为:6 | +| 7 | 4 | 3 出栈,4 入栈 | [7, 6, 4] | 元素 4 的左侧第一个比 4 大的元素为:6 | +| 8 | 2 | 2 入栈 | [7, 6, 4, 2] | 元素 2 的左侧第一个比 2 大的元素为:4 | + +最终栈中元素为 $[7, 6, 4, 2]$。因为从栈顶(右端)到栈底(左侧)元素的顺序为 $2, 4, 6, 7$,满足递增关系,所以这是一个单调递增栈。 + +我们以上述过程第 5 步为例,所对应的图示过程为: + +![](https://qcdn.itcharge.cn/images/20220107101219.png) + + +### 1.2 单调递减栈 + +> **单调递减栈**:只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈。 +> +> 这样就保证了:栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递减的。 + +单调递减栈的入栈、出栈过程如下: + +- 假设当前进栈元素为 $x$,如果 $x$ 比栈顶元素大,则直接入栈。 +- 否则从栈顶开始遍历栈中元素,把大于 $x$ 或者等于 $x$ 的元素弹出栈,直到遇到一个小于 $x$ 的元素为止,然后再把 $x$ 压入栈中。 + +下面我们以数组 $[4, 3, 2, 5, 7, 4, 6, 8]$ 为例,模拟一下「单调递减栈」的进栈、出栈过程。具体过程如下: + +- 数组元素:$[4, 3, 2, 5, 7, 4, 6, 8]$,遍历顺序为从左到右。 + +| 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作用 | +| :-----: | :--------: | ---------------------- | ------------------- | ------------------------------------- | +| 1 | 4 | 4 入栈 | [4] | 元素 4 的左侧无比 4 小的元素 | +| 2 | 3 | 4 出栈,3 入栈 | [3] | 元素 3 的左侧无比 3 小的元素 | +| 3 | 2 | 3 出栈,2 入栈 | [2] | 元素 2 的左侧无比 2 小的元素 | +| 4 | 5 | 5 入栈 | [2, 5] | 元素 5 的左侧第一个比 5 小的元素是:2 | +| 5 | 7 | 7 入栈 | [2, 5, 7] | 元素 7 的左侧第一个比 7 小的元素是:5 | +| 6 | 4 | 7 出栈,5 出栈,4 入栈 | [2, 4] | 元素 4 的左侧第一个比 4 小的元素是:2 | +| 7 | 6 | 6 入栈 | [2, 4, 6] | 元素 6 的左侧第一个比 6 小的元素是:4 | +| 8 | 8 | 8 入栈 | [2, 4, 6, 8] | 元素 8 的左侧第一个比 8 小的元素是:6 | + +最终栈中元素为 $[2, 4, 6, 8]$。因为从栈顶(右端)到栈底(左侧)元素的顺序为 $8, 6, 4, 2$,满足递减关系,所以这是一个单调递减栈。 + +我们以上述过程第 6 步为例,所对应的图示过程为: + +![](https://qcdn.itcharge.cn/images/20220107102446.png) + +## 2. 单调栈适用场景 + +单调栈可以在时间复杂度为 $O(n)$ 的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。 + +所以单调栈一般用于解决一下几种问题: + +- 寻找左侧第一个比当前元素大的元素。 +- 寻找左侧第一个比当前元素小的元素。 +- 寻找右侧第一个比当前元素大的元素。 +- 寻找右侧第一个比当前元素小的元素。 + +下面分别说一下这几种问题的求解方法。 + +### 2.1 寻找左侧第一个比当前元素大的元素 + +- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增): + - 一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。 + - 如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。 + + +### 2.2 寻找左侧第一个比当前元素小的元素 + +- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减): + - 一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。 + - 如果插入时的栈为空,则说明左侧不存在比当前元素小的元素。 + + +### 2.3 寻找右侧第一个比当前元素大的元素 + +- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增): + - 一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。 + - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素。 + +- 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增): + - 一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。 + - 如果插入时的栈为空,则说明右侧不存在比当前元素大的元素。 + + +### 2.4 寻找右侧第一个比当前元素小的元素 + +- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减): + - 一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。 + - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。 + +- 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减): + - 一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。 + - 如果插入时的栈为空,则说明右侧不存在比当前元素小的元素。 + + +上边的分类解法有点绕口,可以简单记为以下条规则: + +- 无论哪种题型,都建议从左到右遍历元素。 + +- 查找 **「比当前元素大的元素」** 就用 **单调递增栈**,查找 **「比当前元素小的元素」** 就用 **单调递减栈**。 +- 从 **「左侧」** 查找就看 **「插入栈」** 时的栈顶元素,从 **「右侧」** 查找就看 **「弹出栈」** 时即将插入的元素。 + +## 3. 单调栈模板 + +以从左到右遍历元素为例,介绍一下构造单调递增栈和单调递减栈的模板。 + +### 3.1 单调递增栈模板 + +```python +def monotoneIncreasingStack(nums): + stack = [] + for num in nums: + while stack and num >= stack[-1]: + stack.pop() + stack.append(num) +``` + +### 3.2 单调递减栈模板 + +```python +def monotoneDecreasingStack(nums): + stack = [] + for num in nums: + while stack and num <= stack[-1]: + stack.pop() + stack.append(num) +``` + +## 4. 单调栈的应用 + +### 4.1 下一个更大元素 I + +#### 4.1.1 题目链接 + +- [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) + +#### 4.1.2 题目大意 + +给定两个没有重复元素的数组 $nums1$ 和 $nums2$ ,其中 $nums1$ 是 $nums2$ 的子集。 + +要求:找出 $nums1$ 中每个元素在 $nums2$ 中的下一个比其大的值。 + +- $nums1$ 中数字 $x$ 的下一个更大元素是指:$x$ 在 $nums2$ 中对应位置的右边的第一个比 $x$ 大的元素。如果不存在,对应位置输出 $-1$。 + +#### 4.1.3 解题思路 + +第一种思路是根据题意直接暴力求解。遍历 $nums1$ 中的每一个元素。对于 $nums1$ 的每一个元素 $nums1[i]$,再遍历一遍 $nums2$,查找 $nums2$ 中对应位置右边第一个比 $nums1[i]$ 大的元素。这种解法的时间复杂度是 $O(n^2)$。 + +第二种思路是使用单调递增栈。因为 $nums1$ 是 $nums2$ 的子集,所以我们可以先遍历一遍 $nums2$,并构造单调递增栈,求出 $nums2$ 中每个元素右侧下一个更大的元素。然后将其存储到哈希表中。然后再遍历一遍 $nums1$,从哈希表中取出对应结果,存放到答案数组中。这种解法的时间复杂度是 $O(n)$。具体做法如下: + +- 使用数组 $res$ 存放答案。使用 $stack$ 表示单调递增栈。使用哈希表 $num\underline{\hspace{0.5em}}map$ 用于存储 $nums2$ 中下一个比当前元素大的数值,映射关系为 **当前元素值:下一个比当前元素大的数值**。 +- 遍历数组 $nums2$,对于当前元素: + - 如果当前元素值较小,则直接让当前元素值入栈。 + - 如果当前元素值较大,则一直出栈,直到当前元素值小于栈顶元素。 + - 出栈时,出栈元素是第一个大于当前元素值的元素。则将其映射到 $num\underline{\hspace{0.5em}}map$ 中。 + +- 遍历完数组 $nums2$,建立好所有元素下一个更大元素的映射关系之后,再遍历数组 $nums1$。 +- 从 $num\underline{\hspace{0.5em}}map$ 中取出对应的值,将其加入到答案数组中。 +- 最终输出答案数组 $res$。 + +#### 4.1.4 代码 + +```python +class Solution: + def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: + res = [] + stack = [] + num_map = dict() + for num in nums2: + while stack and num > stack[-1]: + num_map[stack[-1]] = num + stack.pop() + stack.append(num) + + for num in nums1: + res.append(num_map.get(num, -1)) + return res +``` + +### 4.2 每日温度 + +#### 4.2.1 题目链接 + +- [739. 每日温度 - 力扣(LeetCode)](https://leetcode.cn/problems/daily-temperatures/) + +#### 4.2.2 题目大意 + +**描述**:给定一个列表 $temperatures$,$temperatures[i]$ 表示第 $i$ 天的气温。 + +**要求**:输出一个列表,列表上每个位置代表「如果要观测到更高的气温,至少需要等待的天数」。如果之后的气温不再升高,则用 $0$ 来代替。 + +**说明**: + +- $1 \le temperatures.length \le 10^5$。 +- $30 \le temperatures[i] \le 100$。 + +**示例**: + +```python +输入: temperatures = [73,74,75,71,69,72,76,73] +输出: [1,1,4,2,1,1,0,0] + + +输入: temperatures = [30,40,50,60] +输出: [1,1,1,0] +``` + +#### 4.2.3 解题思路 + +题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置右侧找到第一个比当前元素更大的元素。求「该元素」与「右侧第一个比当前元素更大的元素」之间的距离,将所有距离保存为数组返回结果。 + +最简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 + +更好的方式使用「单调递增栈」,栈中保存元素的下标。 + +##### 思路 1:单调栈 + +1. 首先,将答案数组 $ans$ 全部赋值为 0。然后遍历数组每个位置元素。 +2. 如果栈为空,则将当前元素的下标入栈。 +3. 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 +4. 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组 $ans$ 中保存起来,判断栈顶元素。 +5. 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 +6. 最后输出答案数组 $ans$。 + +##### 思路 1:代码 + +```python +class Solution: + def dailyTemperatures(self, T: List[int]) -> List[int]: + n = len(T) + stack = [] + ans = [0 for _ in range(n)] + for i in range(n): + while stack and T[i] > T[stack[-1]]: + index = stack.pop() + ans[index] = (i-index) + stack.append(i) + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0496. 下一个更大元素 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/next-greater-element-i.md) +- [0739. 每日温度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) +- [0316. 去除重复字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/remove-duplicate-letters.md) + +- [单调栈题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E8%B0%83%E6%A0%88%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[动画:什么是单调栈?_- 吴师兄学编程](https://www.cxyxiaowu.com/450.html) +- 【博文】[单调栈 - OI Wiki](https://oi-wiki.org/ds/monotonous-stack/) +- 【博文】[单调栈解题模板秒杀八道题 - lucifer 的网络博客](https://lucifer.ren/blog/2020/11/03/monotone-stack/) +- 【博文】[理解单调栈与单调队列 - Hopefully Sky 的博客](https://blog.csdn.net/fuzhongmin05/article/details/118090554) \ No newline at end of file diff --git a/docs/03_stack_queue_hash_table/03_03_queue_basic.md b/docs/03_stack_queue_hash_table/03_03_queue_basic.md new file mode 100644 index 00000000..ad0f9319 --- /dev/null +++ b/docs/03_stack_queue_hash_table/03_03_queue_basic.md @@ -0,0 +1,345 @@ +## 1. 队列简介 + +> **队列(Queue)**:一种线性表数据结构,是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表。 + +我们把队列中允许插入的一端称为 **「队尾(rear)」**;把允许删除的另一端称为 **「队头(front)」**。当表中没有任何数据元素时,称之为 **「空队」**。 + +队列有两种基本操作:**「插入操作」** 和 **「删除操作」**。 + +- 队列的插入操作又称为「入队」。 +- 队列的删除操作又称为「出队」。 + +![队列结构](https://qcdn.itcharge.cn/images/202405092254785.png) + +简单来说,队列是一种 **「先进先出(First In First Out)」** 的线性表,简称为 **「FIFO 结构」**。 + +我们可以从两个方面来解释一下队列的定义: + +- 第一个方面是 **「线性表」**。 + +队列首先是一个线性表,队列中元素具有前驱后继的线性关系。队列中元素按照 $a_1, a_2, ... , a_n$ 的次序依次入队。队头元素为 $a_1$,队尾元素为 $a_n$。 + +- 第二个方面是 **「先进先出原则」**。 + +根据队列的定义,最先进入队列的元素在队头,最后进入队列的元素在队尾。每次从队列中删除的总是队头元素,即最先进入队列的元素。也就是说,元素进入队列或者退出队列是按照「先进先出(First In First Out)」的原则进行的。 + +## 2. 队列的顺序存储与链式存储 + +和线性表类似,队列有两种存储表示方法:**「顺序存储的队列」** 和 **「链式存储的队列」**。 + +- **「顺序存储的队列」**:利用一组地址连续的存储单元依次存放队列中从队头到队尾的元素,同时使用指针 $front$ 指向队头元素在队列中的位置,使用指针 $rear$ 指示队尾元素在队列中的位置。 +- **「链式存储的队列」**:利用单链表的方式来实现队列。队列中元素按照插入顺序依次插入到链表的第一个节点之后,并使用队头指针 $front$ 指向链表头节点位置,也就是队头元素,$rear$ 指向链表尾部位置,也就是队尾元素。 + +注意:$front$ 和 $rear$ 的指向位置并不完全固定。有时候算法设计上的方便以及代码简洁,也会使 $front$ 指向队头元素所在位置的前一个位置。$rear$ 也可能指向队尾元素在队列位置的下一个位置。具体还是要看算法是如何实现的。 + +在描述队列的顺序存储与链式存储具体实现之前,我们先来看看队列具有哪些基本操作。 + +### 2.1 队列的基本操作 + +- **初始化空队列**:创建一个空队列,定义队列的大小 $size$,以及队头元素指针 $front$,队尾指针 $rear$。 + +- **判断队列是否为空**:当队列为空时,返回 $True$。当队列不为空时,返回 $False$。一般只用于「出队操作」和「获取队头元素操作」中。 + +- **判断队列是否已满**:当队列已满时,返回 $True$,当队列未满时,返回 $False$。一般只用于顺序队列中插入元素操作中。 + +- **插入元素(入队)**:相当于在线性表最后一个数据元素后面插入一个新的数据元素。并改变队尾指针 $rear$ 的指向位置。 + +- **删除元素(出队)**:相当于在线性表中删除第一个数据元素。并改变队头指针 $front$ 的指向位置。 +- **获取队头元素**:相当于获取线性表中第一个数据元素。与插入元素(入队)、删除元素(出队)不同的是,该操作并不改变队头指针 $front$ 的指向位置。 +- **获取队尾元素**:相当于获取线性表中最后一个数据元素。与插入元素(入队)、删除元素(出队)不同的是,该操作并不改变队尾指针 $rear$ 的指向位置。 + +接下来我们来看一下队列的顺序存储与链式存储两种不同的实现方式。 + +### 2.2 队列的顺序存储实现 + +队列最简单的实现方式就是借助于一个数组来描述队列的顺序存储结构。在 Python 中我们可以借助列表 $list$ 来实现。 + +#### 2.2.1 队列的顺序存储基本描述 + +![队列的顺序存储](https://qcdn.itcharge.cn/images/202405092254909.png) + +为了算法设计上的方便以及算法本身的简单,我们约定:队头指针 $self.front$ 指向队头元素所在位置的前一个位置,而队尾指针 $self.rear$ 指向队尾元素所在位置。 + +- **初始化空队列**:创建一个空队列 $self.queue$,定义队列大小 $self.size$。令队头指针 $self.front$ 和队尾指针 $self.rear$ 都指向 $-1$。即 $self.front = self.rear = -1$。 +- **判断队列是否为空**:根据 $self.front$ 和 $self.rear$ 的指向位置关系进行判断。如果队头指针 $self.front$ 和队尾指针 $self.rear$ 相等,则说明队列为空。否则,队列不为空。 +- **判断队列是否已满**:如果 $self.rear$ 指向队列最后一个位置,即 $self.rear == self.size - 1$,则说明队列已满。否则,队列未满。 +- **插入元素(入队)**:先判断队列是否已满,已满直接抛出异常。如果队列不满,则将队尾指针 $self.rear$ 向右移动一位,并进行赋值操作。此时 $self.rear$ 指向队尾元素。 +- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果队列不为空,则将队头指针 $self.front$ 指向元素赋值为 $None$,并将 $self.front$ 向右移动一位。 +- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果队列不为空,因为 $self.front$ 指向队头元素所在位置的前一个位置,所以队头元素在 $self.front$ 后面一个位置上,返回 $self.queue[self.front + 1]$。 +- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 $self.rear$ 指向队尾元素所在位置,所以直接返回 $self.queue[self.rear]$。 + +#### 2.2.2 队列的顺序存储实现代码 + +```python +class Queue: + # 初始化空队列 + def __init__(self, size=100): + self.size = size + self.queue = [None for _ in range(size)] + self.front = -1 + self.rear = -1 + + # 判断队列是否为空 + def is_empty(self): + return self.front == self.rear + + # 判断队列是否已满 + def is_full(self): + return self.rear + 1 == self.size + + # 入队操作 + def enqueue(self, value): + if self.is_full(): + raise Exception('Queue is full') + else: + self.rear += 1 + self.queue[self.rear] = value + + # 出队操作 + def dequeue(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + self.front += 1 + return self.queue[self.front] + + # 获取队头元素 + def front_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + return self.queue[self.front + 1] + + # 获取队尾元素 + def rear_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + return self.queue[self.rear] +``` + +### 2.3 循环队列的顺序存储实现 + +在「2.2 队列的顺序存储实现」中,如果队列中第 $0$ ~ $size - 1$ 位置均被队列元素占用时,此时队列已满(即 $self.rear == self.size - 1$),再进行入队操作就会抛出队列已满的异常。 + +而由于出队操作总是删除当前的队头元素,将 $self.front$ 进行右移,而插入操作又总是在队尾进行。经过不断的出队、入队操作,队列的变化就像是使队列整体向右移动。 + +当队尾指针满足 $self.rear == self.size - 1$ 条件时,此时再进行入队操作就会抛出队列已满的异常。而之前因为出队操作而产生空余位置也没有利用上,这就造成了「假溢出」问题。 + +为了解决「假溢出」问题,有两种做法: + +- 第一种:每一次删除队头元素之后,就将整个队列往前移动 $1$ 个位置。其代码如下所示: + +```python +# 出队操作 +def dequeue(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + value = self.queue[0] + for i in range(self.rear): + self.queue[i] = self.queue[i + 1] + return value +``` + +这种情况下,队头指针似乎用不到了。因为队头指针总是在队列的第 $0$ 个位置。但是因为删除操作涉及到整个队列元素的移动,所以每次删除操作的时间复杂度就从 $O(1)$ 变为了 $O(n)$。所以这种方式不太可取。 + +- 第二种:将队列想象成为头尾相连的循环表,利用数学中的求模运算,使得空间得以重复利用,这样就解决了问题。 + +在进行插入操作时,如果队列的第 $self.size - 1$ 个位置被占用之后,只要队列前面还有可用空间,新的元素加入队列时就可以从第 $0$ 个位置开始继续插入。 + +我们约定:$self.size$ 为循环队列的最大元素个数。队头指针 $self.front$ 指向队头元素所在位置的前一个位置,而队尾指针 $self.rear$ 指向队尾元素所在位置。则: + +1. **插入元素(入队)时**:队尾指针循环前进 $1$ 个位置,即 $self.rear = (self.rear + 1) \mod self.size$。 +2. **删除元素(出队)时**:队头指针循环前进 $1$ 个位置,即 $self.front = (self.front + 1) \mod self.size$。 + +> **注意**: +> +> - 循环队列在一开始初始化,队列为空时,满足条件$self.front == self.rear$。 +> - 而当充满队列后,仍满足条件 $self.front == self.rear$。 +> +> 这种情况下就无法判断「队列为空」还是「队列为满」了。 + +为了区分循环队列中「队列为空」还是「队列已满」的情况,有多种处理方式: + +- **方式 1**:增加表示队列中元素个数的变量 $self.count$,用来以区分队列已满还是队列为空。在入队、出队过程中不断更新元素个数 $self.count$ 的值。 + - 队列已满条件为:队列中元素个数等于队列整体容量,即 $self.count == self.size$。 + - 队空为空条件为:队列中元素个数等于 $0$,即 $self.count == 0$。 +- **方式 2**:增加标记变量 $self.tag$,用来以区分队列已满还是队列为空。 + - 队列已满条件为:$self.tag == 1$ 的情况下,因插入导致 $self.front == self.rear$。 + - 队列为空条件为:在 $self.tag == 0$ 的情况下,因删除导致 $self.front == self.rear$。 +- **方式 3**:特意空出来一个位置用于区分队列已满还是队列为空。入队时少用一个队列单元,即约定以「队头指针在队尾指针的下一位置」作为队满的标志。 + - 队列已满条件为:队头指针在队尾指针的下一位置,即 $(self.rear + 1) \mod self.size == self.front$。 + - 队列为空条件为:队头指针等于队尾指针,即 $self.front == self.rear$。 + +#### 2.3.1 循环队列的顺序存储基本描述 + +下面我们以「方式 3」中特意空出来一个位置的处理方式为例,对循环队列的顺序存储做一下基本描述。 + +![循环队列的顺序存储](https://qcdn.itcharge.cn/images/202405092254537.png) + +我们约定:$self.size$ 为循环队列的最大元素个数。队头指针 $self.front$ 指向队头元素所在位置的前一个位置,而队尾指针 $self.rear$ 指向队尾元素所在位置。 + +- **初始化空队列**:创建一个空队列,定义队列大小为 $self.size + 1$。令队头指针 $self.front$ 和队尾指针 $self.rear$ 都指向 $0$。即 $self.front = self.rear = 0$。 +- **判断队列是否为空**:根据 $self.front$ 和 $self.rear$ 的指向位置进行判断。根据约定,如果队头指针 $self.front$ 和队尾指针 $self.rear$ 相等,则说明队列为空。否则,队列不为空。 +- **判断队列是否已满**:队头指针在队尾指针的下一位置,即 $(self.rear + 1) \mod self.size == self.front$,则说明队列已满。否则,队列未满。 +- **插入元素(入队)**:先判断队列是否已满,已满直接抛出异常。如果不满,则将队尾指针 $self.rear$ 向右循环移动一位,并进行赋值操作。此时 $self.rear$ 指向队尾元素。 +- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果不为空,则将队头指针 $self.front$ 指向元素赋值为 $None$,并将 $self.front$ 向右循环移动一位。 +- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 $self.front$ 指向队头元素所在位置的前一个位置,所以队头元素在 $self.front$ 后一个位置上,返回 $self.queue[(self.front + 1) \mod self.size]$。 +- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 $self.rear$ 指向队尾元素所在位置,所以直接返回 $self.queue[self.rear]$。 + +#### 2.3.2 循环队列的顺序存储实现代码 + +```python +class Queue: + # 初始化空队列 + def __init__(self, size=100): + self.size = size + 1 + self.queue = [None for _ in range(size + 1)] + self.front = 0 + self.rear = 0 + + # 判断队列是否为空 + def is_empty(self): + return self.front == self.rear + + # 判断队列是否已满 + def is_full(self): + return (self.rear + 1) % self.size == self.front + + # 入队操作 + def enqueue(self, value): + if self.is_full(): + raise Exception('Queue is full') + else: + self.rear = (self.rear + 1) % self.size + self.queue[self.rear] = value + + # 出队操作 + def dequeue(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + self.queue[self.front] = None + self.front = (self.front + 1) % self.size + return self.queue[self.front] + + # 获取队头元素 + def front_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + value = self.queue[(self.front + 1) % self.size] + return value + + # 获取队尾元素 + def rear_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + value = self.queue[self.rear] + return value +``` + +### 2.3 队列的链式存储实现 + +对于在使用过程中数据元素变动较大,或者说频繁进行插入和删除操作的数据结构来说,采用链式存储结构比顺序存储结构更加合适。 + +所以我们可以采用链式存储结构来实现队列。 + +1. 我们用一个线性链表来表示队列,队列中的每一个元素对应链表中的一个链节点。 +2. 再把线性链表的第 $1$ 个节点定义为队头指针 $front$,在链表最后的链节点建立指针 $rear$ 作为队尾指针。 +3. 最后限定只能在链表队头进行删除操作,在链表队尾进行插入操作,这样整个线性链表就构成了一个队列。 + +#### 2.3.1 队列的链式存储基本描述 + +![队列的链式存储](https://qcdn.itcharge.cn/images/202405092255125.png) + +我们约定:队头指针 $self.front$ 指向队头元素所在位置的前一个位置,而队尾指针 $self.rear$ 指向队尾元素所在位置。 + +- **初始化空队列**:建立一个链表头节点 $self.head$,令队头指针 $self.front$ 和队尾指针 $self.rear$ 都指向 $head$。即 $self.front = self.rear = head$。 +- **判断队列是否为空**:根据 $self.front$ 和 $self.rear$ 的指向位置进行判断。根据约定,如果队头指针 $self.front$ 等于队尾指针 $self.rear$,则说明队列为空。否则,队列不为空。 +- **插入元素(入队)**:创建值为 $value$ 的链表节点,插入到链表末尾,并令队尾指针 $self.rear$ 沿着链表移动 $1$ 位到链表末尾。此时 $self.rear$ 指向队尾元素。 +- **删除元素(出队)**:先判断队列是否为空,为空直接抛出异常。如果不为空,则获取队头指针 $self.front$ 下一个位置节点上的值,并将 $self.front$ 沿着链表移动 $1$ 位。如果 $self.front$ 下一个位置是 $self.rear$,则说明队列为空,此时,将 $self.rear$ 赋值为 $self.front$,令其相等。 +- **获取队头元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 $self.front$ 指向队头元素所在位置的前一个位置,所以队头元素在 $self.front$ 后一个位置上,返回 $self.front.next.value$。 +- **获取队尾元素**:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 $self.rear$ 指向队尾元素所在位置,所以直接返回 $self.rear.value$。 + +#### 2.3.2 队列的链式存储实现代码 + +```python +class Node: + def __init__(self, value): + self.value = value + self.next = None + +class Queue: + # 初始化空队列 + def __init__(self): + head = Node(0) + self.front = head + self.rear = head + + # 判断队列是否为空 + def is_empty(self): + return self.front == self.rear + + # 入队操作 + def enqueue(self, value): + node = Node(value) + self.rear.next = node + self.rear = node + + # 出队操作 + def dequeue(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + node = self.front.next + self.front.next = node.next + if self.rear == node: + self.rear = self.front + value = node.value + del node + return value + + # 获取队头元素 + def front_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + return self.front.next.value + + # 获取队尾元素 + def rear_value(self): + if self.is_empty(): + raise Exception('Queue is empty') + else: + return self.rear.value +``` + +## 3. 队列的应用 + +队列是算法和程序中最常用的辅助结构,其应用十分广泛。比如现实生活中的排队买票、银行办理业务挂号等等。队列在计算机科学领域的应用主要提现在以下两个方面: + +1. 解决计算机的主机与外部设备之间速度不匹配的问题。 + - 比如解决主机与打印机之间速度不匹配问题。主机输出数据给计算机打印,输出数据的速度比打印数据的速度要快很多,如果直接把数据送给打印机进行打印,由于速度不匹配,显然行不通。为此,可以设置一个打印数据缓存队列,将要打印的数据依次写入缓存队列中。然后打印机从缓冲区中按照先进先出的原则依次取出数据并且打印。这样即保证了打印数据的正确,又提高了主机的效率。 +2. 解决由于多用户引起的系统资源竞争的问题。 + - 比如说一个带有多终端的计算机系统,当有多个用户需要各自运行各自的程序时,就分别通过终端向操作系统提出占用 CPU 的请求。操作系统通常按照每个请求在时间上的先后顺序将它们排成一个队列,每次把 CPU 分配给队头请求的用户使用;当相应的程序运行结束或用完规定的时间间隔之后,将其退出队列,再把 CPU 分配给新的队头请求的用户使用。这样既能满足多用户的请求,又能使 CPU 正常运行。 + - 再比如 Linux 中的环形缓存、高性能队列 Disruptor,都用到了循环并发队列。iOS 多线程中的 GCD、NSOperationQueue 都用到了队列结构。 + +## 练习题目 + +- [0622. 设计循环队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-circular-queue.md) +- [0346. 数据流中的移动平均值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/moving-average-from-data-stream.md) +- [0225. 用队列实现栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) + +- [队列基础题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%98%9F%E5%88%97%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 +- 【书籍】数据结构教程 第 3 版 - 唐发根 著 +- 【书籍】大话数据结构 程杰 著 +- 【文章】[数据结构之 python 实现队列的链式存储 - 不服输的南瓜的博客](https://blog.csdn.net/weixin_40283816/article/details/87952682) +- 【文章】[顺序存储的循环队列判空判满判长_- ccxcuixia](https://blog.csdn.net/baidu_41304382/article/details/108091899) +- 【文章】[队列 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41330) \ No newline at end of file diff --git a/docs/03_stack_queue_hash_table/03_04_priority_queue.md b/docs/03_stack_queue_hash_table/03_04_priority_queue.md new file mode 100644 index 00000000..4a6d48a4 --- /dev/null +++ b/docs/03_stack_queue_hash_table/03_04_priority_queue.md @@ -0,0 +1,410 @@ +## 1. 优先队列简介 + +> **优先队列(Priority Queue)**:一种特殊的队列。在优先队列中,元素被赋予优先级,当访问队列元素时,具有最高优先级的元素最先删除。 + +优先队列与普通队列最大的不同点在于 **出队顺序**。 + +- 普通队列的出队顺序跟入队顺序相关,符合「先进先出(First in, First out)」的规则。 +- 优先队列的出队顺序跟入队顺序无关,优先队列是按照元素的优先级来决定出队顺序的。优先级高的元素优先出队,优先级低的元素后出队。优先队列符合 **「最高级先出(First in, Largest out)」** 的规则。 + +优先队列的示例图如下所示。 + +![优先队列](https://qcdn.itcharge.cn/images/202405092258900.png) + +## 2. 优先队列的适用场景 + +优先队列的应用场景非常多,比如: + +- **数据压缩**:赫夫曼编码算法; +- **最短路径算法**:Dijkstra 算法; +- **最小生成树算法**:Prim 算法; +- **任务调度器**:根据优先级执行系统任务; +- **事件驱动仿真**:顾客排队算法; +- **排序问题**:查找第 k 个最小元素。 + +很多语言都提供了优先级队列的实现。比如,Java 的 `PriorityQueue`,C++ 的 `priority_queue` 等。Python 中也可以通过 `heapq` 来实现优先队列。下面我们来讲解一下优先队列的实现。 + +## 3. 优先队列的实现方式 + +优先队列所涉及的基本操作跟普通队列差不多,主要是 **「入队操作」** 和 **「出队操作」**。 + +而优先队列的实现方式也有很多种,除了使用「数组(顺序存储)实现」与「链表(链式存储)实现」之外,我们最常用的是使用 **「二叉堆结构实现」**优先队列。以下是三种方案的介绍和总结。 + +- **数组(顺序存储)实现优先队列**:入队操作直接插入到数组队尾,时间复杂度为 $O(1)$。出队操作需要遍历整个数组,找到优先级最高的元素,返回并删除该元素,时间复杂度为 $O(n)$。 +- **链表(链式存储)实现优先队列**:链表中的元素按照优先级排序,入队操作需要为待插入元素创建节点,并在链表中找到合适的插入位置,时间复杂度为 $O(n)$。出队操作直接返回链表队头元素,并删除队头元素,时间复杂度为 $O(1)$。 +- **二叉堆结构实现优先队列**:构建一个二叉堆结构,二叉堆按照优先级进行排序。入队操作就是将元素插入到二叉堆中合适位置,时间复杂度为 $O(\log_2n)$。出队操作则返回二叉堆中优先级最大节点并删除,时间复杂度也是 $O(\log n)$。 + +下面是三种结构实现的优先队列入队操作和出队操作的时间复杂度总结。 + +| | 入队操作时间复杂度 | 出队操作(取出优先级最高的元素)时间复杂度 | +| ---- | ------------------ | ------------------------------------------ | +| 堆 | $O(\log n)$ | $O(\log n)$ | +| 数组 | $O(1)$ | $O(n)$ | +| 链表 | $O(n)$ | $O(1)$ | + +从上面的表格可以看出,使用「二叉堆」这种数据结构来实现优先队列是比较高效的。下面我们来讲解一下二叉堆实现的优先队列。 + +## 4. 二叉堆实现的优先队列 + +我们曾经在「01. 数组 - 02. 数组排序 - 07. 堆排序」中介绍过二叉堆,这里再简单介绍一下。 + +### 4.1 二叉堆的定义 + +二叉堆:符合以下两个条件之一的完全二叉树: + +- 大顶堆:根节点值 ≥ 子节点值。 +- 小顶堆:根节点值 ≤ 子节点值。 + +### 4.2 二叉堆的基本操作 + +二叉堆主要涉及两个基本操作:「堆调整方法」和「将数组构建为二叉堆方法」。 + +- **堆调整方法 `heapAdjust`**:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下: + - 从根节点开始,自上而下地调整节点的位置,使其成为堆积。即把序号为 $i$ 的节点与其左子树节点(序号为 $2 \times i$)、右子树节点(序号为 $2 \times i + 1$)中值最大的节点交换位置。 + + - 因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是,从当前节点的左右子树节点开始,自上而下继续进行类似的调整。 + + - 如此下去直到整棵完全二叉树成为一个大顶堆。 + +- **将数组构建为二叉堆方法(初始堆建立方法) `heapify`**: +- 如果原始序列对应的完全二叉树(不一定是堆)的深度为 $d$,则从 $d - 1$ 层最右侧分支节点(序号为 $\lfloor \frac{n}{2} \rfloor$)开始,初始时令 $i = \lfloor \frac{n}{2} \rfloor$,调用堆调整算法。 + +- 每调用一次堆调整算法,执行一次 $i = i - 1$,直到 $i == 1$ 时,再调用一次,就把原始数组构建为了一个二叉堆。 + +### 4.3 优先队列的基本操作 + +在「3. 优先队列的实现方式」中我们已经提到过,优先队列所涉及的基本操作主要是 **「入队操作」** 和 **「出队操作」**。 + +- **入队操作 `heappush`**: + - 先将待插入元素 $value$ 插入到数组 $nums$ 末尾。 + - 如果完全二叉树的深度为 $d$,则从 $d - 1$ 层开始最右侧分支节点(序号为 $\lfloor \frac{n}{2} \rfloor$)开始,初始时令 $i = \lfloor \frac{n}{2} \rfloor$,从下向上依次查找插入位置。 + - 遇到 $value$ 小于当前根节点时,将其插入到当前位置。否则继续向上寻找插入位置。 + - 如果找到插入位置或者到达根位置,将 $value$ 插入该位置。 +- **出队操作 `heappop`**: + - 交换数组 $nums$ 首尾元素,此时 $nums$ 尾部就是值最大(优先级最高)的元素,将其从 $nums$ 中弹出,并保存起来。 + - 弹出后,对 $nums$ 剩余元素调用堆调整算法,将其调整为大顶堆。 + +### 4.4 手写二叉堆实现优先队列 + +通过手写二叉堆的方式实现优先队列。主要实现了以下五种方法: + +- `heapAdjust`:将完全二叉树调整为二叉堆。 +- `heapify`: 将数组构建为二叉堆方法(初始堆建立方法)。 +- `heappush`:向堆中添加元素,也是优先队列的入队操作。 +- `heappop`:删除堆顶元素,也是优先队列的出队操作,弹出优先队列中优先级最高的元素。 +- `heapSort`:堆排序。 + +```python +class Heapq: + # 堆调整方法:调整为大顶堆 + def heapAdjust(self, nums: [int], index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子结点 + max_index = index + if nums[left] > nums[max_index]: + max_index = left + if right <= end and nums[right] > nums[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 将数组构建为二叉堆 + def heapify(self, nums: [int]): + size = len(nums) + # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + # 调用调整堆函数 + self.heapAdjust(nums, i, size - 1) + + # 入队操作 + def heappush(self, nums: list, value): + nums.append(value) + size = len(nums) + i = size - 1 + # 寻找插入位置 + while (i - 1) // 2 >= 0: + cur_root = (i - 1) // 2 + # value 小于当前根节点,则插入到当前位置 + if nums[cur_root] > value: + break + # 继续向上查找 + nums[i] = nums[cur_root] + i = cur_root + # 找到插入位置或者到达根位置,将其插入 + nums[i] = value + + # 出队操作 + def heappop(self, nums: list) -> int: + size = len(nums) + nums[0], nums[-1] = nums[-1], nums[0] + # 得到最大值(堆顶元素)然后调整堆 + top = nums.pop() + if size > 0: + self.heapAdjust(nums, 0, size - 2) + + return top + + # 升序堆排序 + def heapSort(self, nums: [int]): + self.heapify(nums) + size = len(nums) + for i in range(size): + nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] + self.heapAdjust(nums, 0, size - i - 2) + return nums +``` + +### 4.5 使用 heapq 模块实现优先队列 + +Python 中的 `heapq` 模块提供了优先队列算法。函数 `heapq.heappush()` 用于在队列 $queue$ 上插入一个元素。`heapq.heappop()` 用于在队列 $queue$ 上删除一个元素。 + +需要注意的是:`heapq.heappop()` 函数总是返回「最小的」的元素。所以我们在使用 `heapq.heappush()` 时,将优先级设置为负数,这样就使得元素可以按照优先级从高到低排序, 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。这样做的目的是为了 `heapq.heappop()` 每次弹出的元素都是优先级最高的元素。 + +```python +import heapq + +class PriorityQueue: + def __init__(self): + self.queue = [] + self.index = 0 + + def push(self, item, priority): + heapq.heappush(self.queue, (-priority, self.index, item)) + self.index += 1 + + def pop(self): + return heapq.heappop(self.queue)[-1] +``` + +## 5. 优先队列的应用 + +### 5.1 滑动窗口最大值 + +#### 5.1.1 题目链接 + +- [239. 滑动窗口最大值 - 力扣(LeetCode)](https://leetcode.cn/problems/sliding-window-maximum/) + +#### 5.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$,再给定一个整数 $k$,表示为大小为 $k$ 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 $k$ 个数字,滑动窗口每次只能向右移动一位。 + +**要求**:返回滑动窗口中的最大值。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $1 \le k \le nums.length$。 + +**示例**: + +```python +输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 +输出:[3,3,5,5,6,7] +解释: +滑动窗口的位置 最大值 +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 + + +输入:nums = [1], k = 1 +输出:[1] +``` + +#### 5.1.3 解题思路 + +暴力求解的话,需要使用二重循环遍历,其时间复杂度为 $O(n \times k)$。根据题目给定的数据范围,肯定会超时。 + +我们可以使用优先队列来做。 + +##### 思路 1:优先队列 + +1. 初始的时候将前 $k$ 个元素加入优先队列的二叉堆中。存入优先队列的是数组值与索引构成的元组。优先队列将数组值作为优先级。 +2. 然后滑动窗口从第 $k$ 个元素开始遍历,将当前数组值和索引的元组插入到二叉堆中。 +3. 当二叉堆堆顶元素的索引已经不在滑动窗口的范围中时,即 $q[0][1] \le i - k$ 时,不断删除堆顶元素,直到最大值元素的索引在滑动窗口的范围中。 +4. 将最大值加入到答案数组中,继续向右滑动。 +5. 滑动结束时,输出答案数组。 + +##### 思路 1:代码 + +```python +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + size = len(nums) + q = [(-nums[i], i) for i in range(k)] + heapq.heapify(q) + res = [-q[0][0]] + + for i in range(k, size): + heapq.heappush(q, (-nums[i], i)) + while q[0][1] <= i - k: + heapq.heappop(q) + res.append(-q[0][0]) + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(k)$。 + +### 5.2 前 K 个高频元素 + +#### 5.2.1 题目链接 + +- [347. 前 K 个高频元素 - 力扣(LeetCode)](https://leetcode.cn/problems/top-k-frequent-elements/) + +#### 5.2.2 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 + +**要求**:返回出现频率前 $k$ 高的元素。可以按任意顺序返回答案。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $k$ 的取值范围是 $[1, \text{ 数组中不相同的元素的个数}]$。 +- 题目数据保证答案唯一,换句话说,数组中前 $k$ 个高频元素的集合是唯一的。 + +**示例**: + +```python +输入: nums = [1,1,1,2,2,3], k = 2 +输出: [1,2] + + +输入: nums = [1], k = 1 +输出: [1] +``` + +#### 5.2.3 解题思路 + +##### 思路 1:哈希表 + 优先队列 + +1. 使用哈希表记录下数组中各个元素的频数。 +2. 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +3. 使用二叉堆构建优先队列,优先级为元素频数。此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +4. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(\log n)$。 + - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 +5. 不断重复第 4 步,直到 $k$ 次结束。调整 $k$ 次的时间复杂度 $O(n \times \log n)$。 + +##### 思路 1:代码 + +```python +class Heapq: + # 堆调整方法:调整为大顶堆 + def heapAdjust(self, nums: [int], nums_dict, index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子结点 + max_index = index + if nums_dict[nums[left]] > nums_dict[nums[max_index]]: + max_index = left + if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 将数组构建为二叉堆 + def heapify(self, nums: [int], nums_dict): + size = len(nums) + # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + # 调用调整堆函数 + self.heapAdjust(nums, nums_dict, i, size - 1) + + # 入队操作 + def heappush(self, nums: list, nums_dict, value): + nums.append(value) + size = len(nums) + i = size - 1 + # 寻找插入位置 + while (i - 1) // 2 >= 0: + cur_root = (i - 1) // 2 + # value 小于当前根节点,则插入到当前位置 + if nums_dict[nums[cur_root]] > nums_dict[value]: + break + # 继续向上查找 + nums[i] = nums[cur_root] + i = cur_root + # 找到插入位置或者到达根位置,将其插入 + nums[i] = value + + # 出队操作 + def heappop(self, nums: list, nums_dict) -> int: + size = len(nums) + nums[0], nums[-1] = nums[-1], nums[0] + # 得到最大值(堆顶元素)然后调整堆 + top = nums.pop() + if size > 0: + self.heapAdjust(nums, nums_dict, 0, size - 2) + + return top + +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + # 统计元素频数 + nums_dict = dict() + for num in nums: + if num in nums_dict: + nums_dict[num] += 1 + else: + nums_dict[num] = 1 + + # 使用 set 方法去重,得到新数组 + new_nums = list(set(nums)) + size = len(new_nums) + + heap = Heapq() + queue = [] + for num in new_nums: + heap.heappush(queue, nums_dict, num) + + res = [] + for i in range(k): + res.append(heap.heappop(queue, nums_dict)) + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0215. 数组中的第K个最大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) +- [0347. 前 K 个高频元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/top-k-frequent-elements.md) +- [0451. 根据字符出现频率排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sort-characters-by-frequency.md) + +- [优先队列题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[浅入浅出数据结构(15)—— 优先队列(堆) - NSpt - 博客园](https://www.cnblogs.com/mm93/p/7481782.html) +- 【博文】[堆(Heap)和优先队列(Priority Queue) - 简书](https://www.jianshu.com/p/859e5fb89eb7) +- 【博文】[漫画:什么是优先队列?- 吴师兄学编程](https://www.cxyxiaowu.com/5417.html) +- 【博文】[Python3,手写一个堆及其简易功能,并实现优先队列,最小堆任务调度等 - pythonstrat 的博客](https://blog.csdn.net/pythonstrat/article/details/119378788) +- 【文档】[实现一个优先级队列 - python3-cookbook 3.0.0 文档](https://python3-cookbook.readthedocs.io/zh_CN/latest/c01/p05_implement_a_priority_queue.html) +- 【文档】[heapq - 堆队列算法 - Python 3.10.1 文档](https://docs.python.org/zh-cn/3/library/heapq.html) +- 【题解】[239. 滑动窗口最大值 (优先队列&单调栈) - 滑动窗口最大值 - 力扣](https://leetcode.cn/problems/sliding-window-maximum/solution/239-hua-dong-chuang-kou-zui-da-zhi-you-x-9qur/) diff --git a/Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md b/docs/03_stack_queue_hash_table/03_05_bidirectional_queue.md similarity index 100% rename from Contents/06.String/03.String-Multi-Pattern-Matching/04.AC-Automaton-List.md rename to docs/03_stack_queue_hash_table/03_05_bidirectional_queue.md diff --git a/docs/03_stack_queue_hash_table/03_06_hash_table.md b/docs/03_stack_queue_hash_table/03_06_hash_table.md new file mode 100644 index 00000000..8bac1355 --- /dev/null +++ b/docs/03_stack_queue_hash_table/03_06_hash_table.md @@ -0,0 +1,178 @@ +## 1. 哈希表简介 + +> **哈希表(Hash Table)**:也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。 +> +> 哈希表通过「键 $key$」和「映射函数 $Hash(key)$」计算出对应的「值 $value$」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。 + +哈希表的关键思想是使用哈希函数,将键 $key$ 映射到对应表的某个区块中。我们可以将算法思想分为两个部分: + +- **向哈希表中插入一个关键码值**:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。 +- **在哈希表中搜索一个关键码值**:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。 + +哈希表的原理示例图如下所示: + +![哈希表](https://qcdn.itcharge.cn/images/202405092317578.png) + +在上图例子中,我们使用 $value = Hash(key) = key // 1000$ 作为哈希函数。$//$ 符号代表整除。我们以这个例子来说明一下哈希表的插入和查找策略。 + +- **向哈希表中插入一个关键码值**:通过哈希函数解析关键字,并将对应值存放到该区块中。 + - 比如:$0138$ 通过哈希函数 $Hash(key) = 0138 // 1000 = 0$,得出应将 $0138$ 分配到 $0$ 所在的区块中。 +- **在哈希表中搜索一个关键码值**:通过哈希函数解析关键字,并在特定的区块搜索该关键字对应的值。 + - 比如:查找 $2321$,通过哈希函数,得出 $2321$ 应该在 $2$ 所对应的区块中。然后我们从 $2$ 对应的区块中继续搜索,并在 $2$ 对应的区块中成功找到了 $2321$。 + - 比如:查找 $3214$,通过哈希函数,得出 $3214$ 应该在 $3$ 所对应的区块中。然后我们从 $3$ 对应的区块中继续搜索,但并没有找到对应值,则说明 $3214$ 不在哈希表中。 + +哈希表在生活中的应用也很广泛,其中一个常见例子就是「查字典」。 + +比如为了查找 **「赞」** 这个字的具体意思,我们在字典中根据这个字的拼音索引 `zan`,查找到对应的页码为 $599$。然后我们就可以翻到字典的第 $599$ 页查看 **「赞」** 字相关的解释了。 + +![查字典](https://qcdn.itcharge.cn/images/20220111174223.png) + +在这个例子中: + +- 存放所有拼音和对应地址的表可以看做是 **「哈希表」**。 +- **「赞」** 字的拼音索引 `zan` 可以看做是哈希表中的 **「关键字 $key$」**。 +- 根据拼音索引 $zan$ 来确定字对应页码的过程可以看做是哈希表中的 **「哈希函数 $Hash(key)$」**。 +- 查找到的对应页码 $599$ 可以看做是哈希表中的 **「哈希地址 $value$」**。 + +## 2. 哈希函数 + +> **哈希函数(Hash Function)**:将哈希表中元素的关键键值映射为元素存储位置的函数。 + +哈希函数是哈希表中最重要的部分。一般来说,哈希函数会满足以下几个条件: + +- 哈希函数应该易于计算,并且尽量使计算出来的索引值均匀分布。 +- 哈希函数计算得到的哈希值是一个固定长度的输出值。 +- 如果 $Hash(key1) \ne Hash(key2)$,那么 $key1$、$key2$ 一定不相等。 +- 如果 $Hash(key1) == Hash(key2)$,那么 $key1$、$key2$ 可能相等,也可能不相等(会发生哈希碰撞)。 + +在哈希表的实际应用中,关键字的类型除了数字类,还有可能是字符串类型、浮点数类型、大整数类型,甚至还有可能是几种类型的组合。一般我们会将各种类型的关键字先转换为整数类型,再通过哈希函数,将其映射到哈希表中。 + +而关于整数类型的关键字,通常用到的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。下面我们介绍几个常用的哈希函数方法。 + +### 2.1 直接定址法 + +- **直接定址法**:取关键字本身 / 关键字的某个线性函数值作为哈希地址。即:$Hash(key) = key$ 或者 $Hash(key) = a \times key + b$,其中 $a$ 和 $b$ 为常数。 + +这种方法计算最简单,且不会产生冲突。适合于关键字分布基本连续的情况,如果关键字分布不连续,空位较多,则会造成存储空间的浪费。 + +举一个例子,假设我们有一个记录了从 $1$ 岁到 $100$ 岁的人口数字统计表。其中年龄为关键字,哈希函数取关键字自身,如下表所示。 + +| 年龄 | 1 | 2 | 3 | ... | 25 | 26 | 27 | ... | 100 | +| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 人数 | 3000 | 2000 | 5000 | ... | 1050 | ... | ... | ... | ... | + +比如我们想要查询 $25$ 岁的人有多少,则只要查询表中第 $25$ 项即可。 + +### 2.2 除留余数法 + +- **除留余数法**:假设哈希表的表长为 $m$,取一个不大于 $m$ 但接近或等于 $m$ 的质数 $p$,利用取模运算,将关键字转换为哈希地址。即:$Hash(key) = key \mod p$,其中 $p$ 为不大于 $m$ 的质数。 + +这也是一种简单且常用的哈希函数方法。其关键点在于 $p$ 的选择。根据经验而言,一般 $p$ 取素数或者 $m$,这样可以尽可能的减少冲突。 + +比如我们需要将 $7$ 个数 $[432, 5, 128, 193, 92, 111, 88]$ 存储在 $11$ 个区块中(长度为 $11$ 的数组),通过除留余数法将这 $7$ 个数应分别位于如下地址: + +| 索引 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| :--: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| 数据 | 88 | 111 | | 432 | 92 | 5 | 193 | | 128 | | | + +### 2.3 平方取中法 + +- **平方取中法**:先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。 + - 比如:$Hash(key) = (key \times key) // 100 \mod 1000$,先计算平方,去除末尾的 $2$ 位数,再取中间 $3$ 位数作为哈希地址。 + +这种方法因为关键字平方值的中间几位数和原关键字的每一位数都相关,所以产生的哈希地址也比较均匀,有利于减少冲突的发生。 + +### 2.4 基数转换法 + +- **基数转换法**:将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。 + - 比如,将关键字看做是 $13$ 进制的数,再将其转变为 $10$ 进制的数,将其作为哈希地址。 + +以 $343246$ 为例,哈希地址计算方式如下: + +$343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4 \times 13^1 + 6 \times 13^0 = 1235110_{10}$ + +## 3. 哈希冲突 + +> **哈希冲突(Hash Collision)**:不同的关键字通过同一个哈希函数可能得到同一哈希地址,即 $key1 \ne key2$,而 $Hash(key1) == Hash(key2)$,这种现象称为哈希冲突。 + +理想状态下,我们的哈希函数是完美的一对一映射,即一个关键字($key$)对应一个值($value$),不需要处理冲突。但是一般情况下,不同的关键字 $key$ 可能对应了同一个值 $value$,这就发生了哈希冲突。 + +设计再好的哈希函数也无法完全避免哈希冲突。所以就需要通过一定的方法来解决哈希冲突问题。常用的哈希冲突解决方法主要是两类:**「开放地址法(Open Addressing)」** 和 **「链地址法(Chaining)」**。 + +### 3.1 开放地址法 + +> **开放地址法(Open Addressing)**:指的是将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的单元,直到找到空的单元为止。 + +当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:$H(i) = (Hash(key) + F(i)) \mod m$,$i = 1, 2, 3, ..., n (n ≤ m - 1)$。 +- $H(i)$ 是在处理冲突中得到的地址序列。即在第 1 次冲突($i = 1$)时经过处理得到一个新地址 $H(1)$,如果在 $H(1)$ 处仍然发生冲突($i = 2$)时经过处理时得到另一个新地址 $H(2)$ …… 如此下去,直到求得的 $H(n)$ 不再发生冲突。 +- $Hash(key)$ 是哈希函数,$m$ 是哈希表表长,对哈希表长取余的目的是为了使得到的下一个地址一定落在哈希表中。 +- $F(i)$ 是冲突解决方法,取法可以有以下几种: + - 线性探测法:$F(i) = 1, 2, 3, ..., m - 1$。 + - 二次探测法:$F(i) = 1^2, -1^2, 2^2, -2^2, ..., \pm n^2(n \le m / 2)$。 + - 伪随机数序列:$F(i) = \text{伪随机数序列}$。 + + +举个例子说说明一下如何用以上三种冲突解决方法处理冲突,并得到新地址 $H(i)$。例如,在长度为 $11$ 的哈希表中已经填有关键字分别为 $28$、$49$、$18$ 的记录(哈希函数为 $Hash(key) = key \mod 11$)。现在将插入关键字为 $38$ 的新纪录。根据哈希函数得到的哈希地址为 $5$,产生冲突。接下来分别使用这三种冲突解决方法处理冲突。 + +- 使用线性探测法:得到下一个地址 $H(1) = (5 + 1) \mod 11 = 6$,仍然冲突;继续求出 $H(2) = (5 + 2) \mod 11 = 7$,仍然冲突;继续求出 $H(3) = (5 + 3) \mod 11 = 8$,$8$ 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 $8$ 的位置。 +- 使用二次探测法:得到下一个地址 $H(1) = (5 + 1 \times 1) \mod 11 = 6$,仍然冲突;继续求出 $H(2) = (5 - 1 \times 1) \mod 11 = 4$,$4$ 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 $4$ 的位置。 +- 使用伪随机数序列:假设伪随机数为 $9$,则得到下一个地址 $H(1) = (9 + 5) \mod 11 = 3$,$3$ 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 $3$ 的位置。 + +使用这三种方法处理冲突的结果如下图所示: + +![开放地址法](https://qcdn.itcharge.cn/images/202405092318809.png) + +### 3.2 链地址法 + +> **链地址法(Chaining)**:将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。 + +链地址法是一种更加常用的哈希冲突解决方法。相比于开放地址法,链地址法更加简单。 + +我们假设哈希函数产生的哈希地址区间为 $[0, m - 1]$,哈希表的表长为 $m$。则可以将哈希表定义为一个有 $m$ 个头节点组成的链表指针数组 $T$。 + +- 这样在插入关键字的时候,我们只需要通过哈希函数 $Hash(key)$ 计算出对应的哈希地址 $i$,然后将其以链表节点的形式插入到以 $T[i]$ 为头节点的单链表中。在链表中插入位置可以在表头或表尾,也可以在中间。如果每次插入位置为表头,则插入操作的时间复杂度为 $O(1)$。 + +- 而在在查询关键字的时候,我们只需要通过哈希函数 $Hash(key)$ 计算出对应的哈希地址 $i$,然后将对应位置上的链表整个扫描一遍,比较链表中每个链节点的键值与查询的键值是否一致。查询操作的时间复杂度跟链表的长度 $k$ 成正比,也就是 $O(k)$。对于哈希地址比较均匀的哈希函数来说,理论上讲,$k = n // m$,其中 $n$ 为关键字的个数,$m$ 为哈希表的表长。 + +举个例子来说明如何使用链地址法处理冲突。假设现在要存入的关键字集合 $keys = [88, 60, 65, 69, 90, 39, 07, 06, 14, 44, 52, 70, 21, 45, 19, 32]$。再假定哈希函数为 $Hash(key) = key \mod 13$,哈希表的表长 $m = 13$,哈希地址范围为 $[0, m - 1]$。将这些关键字使用链地址法处理冲突,并按顺序加入哈希表中(图示为插入链表表尾位置),最终得到的哈希表如下图所示。 + +![链地址法](https://qcdn.itcharge.cn/images/202405092319327.png) + +相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间(主要是链节点占用空间)。但它可以减少在进行插入和查找具有相同哈希地址的关键字的操作过程中的平均查找长度。这是因为在链地址法中,待比较的关键字都是具有相同哈希地址的元素,而在开放地址法中,待比较的关键字不仅包含具有相同哈希地址的元素,而且还包含哈希地址不相同的元素。 + +## 4. 哈希表总结 + +本文讲解了一些比较基础、偏理论的哈希表知识。包含哈希表的定义,哈希函数、哈希冲突以及哈希冲突的解决方法。 + +- **哈希表(Hash Table)**:通过键 $key$ 和一个映射函数 $Hash(key)$ 计算出对应的值 $value$,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。 +- **哈希函数(Hash Function)**:将哈希表中元素的关键键值映射为元素存储位置的函数。 +- **哈希冲突(Hash Collision)**:不同的关键字通过同一个哈希函数可能得到同一哈希地址。 + +哈希表的两个核心问题是:**「哈希函数的构建」** 和 **「哈希冲突的解决方法」**。 + +- 常用的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。 +- 常用的哈希冲突的解决方法有两种:开放地址法和链地址法。 + +## 练习题目 + +- [0217. 存在重复元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate.md) +- [0219. 存在重复元素 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-ii.md) +- [0036. 有效的数独](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-sudoku.md) +- [0349. 两个数组的交集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) +- [0350. 两个数组的交集 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) +- [0706. 设计哈希映射](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashmap.md) + +- [哈希表题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%93%88%E5%B8%8C%E8%A1%A8%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[哈希算法及 python 字典的实现 – Tim's Path](https://xiaoxubeii.github.io/articles/hash/) +- 【文章】[哈希表 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/hash-table/xh8uld/) +- 【文章】[漫画算法 - 小灰的算法之旅 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/journey-of-algorithm/5o13c3/) +- 【博文】[面试官:哈希表都不知道,你是怎么看懂 HashMap 的? - 掘金](https://juejin.cn/post/6876105622274703368) +- 【博文】[散列表 | Frank's Blog](https://frankfang.cn/article/202104852) +- 【博文】[哈希表 - OI Wiki](https://oi-wiki.org/ds/hash/) +- 【博文】[散列表(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/64233) +- 【书籍】数据结构(C 语言版)- 严蔚敏 著 +- 【书籍】数据结构教程(第 3 版)- 唐发根 著 +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 diff --git a/docs/03_stack_queue_hash_table/index.md b/docs/03_stack_queue_hash_table/index.md new file mode 100644 index 00000000..ee936ab3 --- /dev/null +++ b/docs/03_stack_queue_hash_table/index.md @@ -0,0 +1,8 @@ +## 本章内容 + +- [3.1 栈基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_01_stack_basic.md) +- [3.2 单调栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_02_monotone_stack.md) +- [3.3 队列基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_03_queue_basic.md) +- [3.4 优先队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_04_priority_queue.md) +- [3.5 双端队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_05_bidirectional_queue.md) +- [3.6 哈希表](https://github.com/ITCharge/AlgoNote/tree/main/docs/03_stack_queue_hash_table/03_06_hash_table.md) \ No newline at end of file diff --git a/docs/04_string/04_01_string_basic.md b/docs/04_string/04_01_string_basic.md new file mode 100644 index 00000000..6a50e756 --- /dev/null +++ b/docs/04_string/04_01_string_basic.md @@ -0,0 +1,201 @@ +## 1. 字符串简介 + +> **字符串(String)**:简称为串,是由零个或多个字符组成的有限序列。一般记为 $s = a_1a_2…a_n (0 \le n ⪇ \infty)$。 + +下面介绍一下字符串相关的一些重要概念。 + +- **字符串名称**:字符串定义中的 $s$ 就是字符串的名称。 +- **字符串的值**:$a_1a_2…a_n$ 组成的字符序列就是字符串的值,一般用双引号括起来。 +- **字符变量**:字符串每一个位置上的元素都是一个字符变量。字符 $a_i$ 可以是字母、数字或者其他字符。$i$ 是该字符在字符串中的位置。 +- **字符串的长度**:字符串中字符的数目 $n$ 称为字符串的长度。 +- **空串**:零个字符构成的串也成为 **「空字符串(Null String)」**,它的长度为 $0$,可以表示为 `""`。 +- **子串**:字符串中任意个连续的字符组成的子序列称为该字符串的 **「子串(Substring)」**。并且有两种特殊子串,起始于位置为 $0$、长度为 $k$ 的子串称为 **「前缀(Prefix)」**。而终止于位置 $n - 1$、长度为 $k$ 的子串称为 **「后缀(Suffix)」**。 +- **主串**:包含子串的字符串相应的称为 **「主串」**。 + +举个例子来说明一下: + +```python +str = "Hello World" +``` + +在示例代码中,$str$ 是一个字符串的变量名称,`Hello World` 则是该字符串的值,字符串的长度为 $11$。该字符串的表示如下图所示: + +![字符串](https://qcdn.itcharge.cn/images/20240511114722.png) + +可以看出来,字符串和数组有很多相似之处。比如同样使用 **名称[下标]** 的方式来访问一个字符。 + +之所以单独讨论字符串是因为: + +- 字符串中的数据元素都是字符,结构相对简单,但规模可能比较庞大。 +- 经常需要把字符串作为一个整体来使用和处理。操作对象一般不是某个数据元素,而是一组数据元素(整个字符串或子串)。 +- 经常需要考虑多个字符串之间的操作。比如:字符串之间的连接、比较操作。 + +根据字符串的特点,我们可以将字符串问题分为以下几种: + +- 字符串匹配问题。 +- 子串相关问题。 +- 前缀 / 后缀相关问题; +- 回文串相关问题。 +- 子序列相关问题。 + +## 2. 字符串的比较 + +### 2.1 字符串的比较操作 + +两个数字之间很容易比较大小,例如 $1 < 2$。而字符串之间的比较相对来说复杂一点。字符串之间的大小取决于它们按顺序排列字符的前后顺序。 + +比如字符串 `str1 = "abc"` 和 `str2 = "acc"`,它们的第一个字母都是 $a$,而第二个字母,由于字母 $b$ 比字母 $c$ 要靠前,所以 $b < c$,于是我们可以说 `"abc" < "acd" `,也可以说 $str1 < str2$。 + +字符串之间的比较是通过组成字符串的字符之间的「字符编码」来决定的。而字符编码指的是字符在对应字符集中的序号。 + +我们先来考虑一下如何判断两个字符串是否相等。 + +如果说两个字符串 $str1$ 和 $str2$ 相等,则必须满足两个条件: + +1. 字符串 $str1$ 和字符串 $str2$ 的长度相等。 +2. 字符串 $str1$ 和字符串 $str2$ 对应位置上的各个字符都相同。 + +下面我们再来考虑一下如何判断两个字符串的大小。 + +而对于两个不相等的字符串,我们可以以下面的规则定义两个字符串的大小: + +- 从两个字符串的第 $0$ 个位置开始,依次比较对应位置上的字符编码大小。 + - 如果 $str1[i]$ 对应的字符编码等于 $str2[i]$ 对应的字符编码,则比较下一位字符。 + - 如果 $str1[i]$ 对应的字符编码小于 $str2[i]$ 对应的字符编码,则说明 $str1 < str2$。比如:`"abc" < "acc"`。 + - 如果 $str1[i]$ 对应的字符编码大于 $str2[i]$ 对应的字符编码,则说明 $str1 > str2$。比如:`"bcd" > "bad"`。 +- 如果比较到某一个字符串末尾,另一个字符串仍有剩余: + - 如果字符串 $str1$ 的长度小于字符串 $str2$,即 $len(str1) < len(str2)$。则 $str1 < str2$。比如:`"abc" < "abcde"`。 + - 如果字符串 $str1$ 的长度大于字符串 $str2$,即 $len(str1) > len(str2)$。则 $str1 > str2$。比如:`"abcde" > "abc"`。 +- 如果两个字符串每一个位置上的字符对应的字符编码都相等,且长度相同,则说明 $str1 == str2$,比如:`"abcd" == "abcd"`。 + +按照上面的规则,我们可以定义一个 `strcmp` 方法,并且规定: + +- 当 $str1 < str2$ 时,`strcmp` 方法返回 $-1$。 +- 当 $str1 == str2$ 时,`strcmp` 方法返回 $0$。 +- 当 $str1 > str2$ 时,`strcmp` 方法返回 $1$。 + +`strcmp` 方法对应的具体代码如下: + +```python +def strcmp(str1, str2): + index1, index2 = 0, 0 + while index1 < len(str1) and index2 < len(str2): + if ord(str1[index1]) == ord(str2[index2]): + index1 += 1 + index2 += 1 + elif ord(str1[index1]) < ord(str2[index2]): + return -1 + else: + return 1 + + if len(str1) < len(str2): + return -1 + elif len(str1) > len(str2): + return 1 + else: + return 0 +``` + +上面关于字符串大小的定义有点复杂,其实字符串比较大小最简单的例子就是「查英语词典」。在英语词典的目录中,前面的单词要比后面的单词小。我们将英语词典中的每个英语单词都当做是一个字符串,那么查找单词的过程,其实就是比较字符串大小的过程。 + +### 2.2 字符串的字符编码 + +刚才我们提到了字符编码,这里我们也稍微介绍一下字符串中常用的字符编码标准。 + +以计算机中常用字符使用的 ASCII 编码为例。最早的时候,人们制定了一个包含 $127$ 个字符的编码表 ASCII 到计算机系统中。ASCII 编码表中的字符包含了大小写的英文字母、数字和一些符号。每个字符对应一个编码,比如大写字母 $A$ 的编码是 $65$,小写字母 $a$ 的编码是 $97$。 + +ASCII 编码可以解决以英语为主的语言,可是无法满足中文编码。为了解决中文编码,我国制定了 GB2312、GBK、GB18030 等中文编码标准,将中文编译进去。但是世界上有上百种语言和文字,各国有各国的标准,就会不可避免的产生冲突,于是就有了 Unicode 编码。Unicode 编码最常用的就是 UTF-8 编码,UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 $1 \sim 6$ 个字节,常用的英文字母被编码成 $1$ 个字节,汉字通常是 $3$ 个字节。 + +## 3. 字符串的存储结构 + +字符串的存储结构跟线性表相同,分为「顺序存储结构」和「链式存储结构」。 + +### 3.1 字符串的顺序存储结构 + +与线性表的顺序存储结构相似,字符串的顺序存储结构也是使用一组地址连续的存储单元依次存放串中的各个字符。按照预定义的大小,为每个定义的字符串变量分配一个固定长度的存储区域。一般是用定长数组来定义。 + +字符串的顺序存储结构如下图所示。 + +![字符串的顺序存储](https://qcdn.itcharge.cn/images/20240511114747.png) + +如上图所示,字符串的顺序存储中每一个字符元素都有自己的下标索引,下标所以从 $0$ 开始,到 $\text{字符串长度} - 1$ 结束。字符串中每一个「下标索引」,都有一个与之对应的「字符元素」。 + +跟数组类似,字符串也支持随机访问。即字符串可以根据下标,直接定位到某一个字符元素存放的位置。 + +### 3.2 字符串的链式存储结构 + +字符串的存储也可以采用链式存储结构,即采用一个线性链表来存储一个字符串。字符串的链节点包含一个用于存放字符的 $data$ 变量,和指向下一个链节点的指针变量 $next$。这样,一个字符串就可以用一个线性链表来表示。 + +在字符串的链式存储结构中,每个链节点可以仅存放一个字符,也可以存放多个字符。通常情况下,链节点的字符长度为 $1$ 或者 $4$,这是为了避免浪费空间。当链节点的字符长度为 $4$ 时,由于字符串的长度不一定是 $4$ 的倍数,因此字符串所占用的链节点中最后那个链节点的 $data$ 变量可能没有占满,我们可以用 `#` 或其他不属于字符集的特殊字符将其补全。 + +字符串的链式存储结构图下图所示。 + +![字符串的链式存储](https://qcdn.itcharge.cn/images/20240511114804.png) + +如上图所示,字符串的链式存储将一组任意的存储单元串联在一起。链节点之间的逻辑关系是通过指针来间接反映的。 + +### 3.3 不同语言中的字符串 + +- C 语言中的字符串是使用空字符 `\0` 结尾的字符数组。`\0` 符号用于标记字符串的结束。C 语言的标准库 `string.h` 头文件中提供了各种操作字符串的函数。 +- C++ 语言中除了提供 C 风格的字符串,还引入了 `string` 类类型。`string` 类处理起字符串来会方便很多,完全可以代替 C 语言中的字符数组或字符串指针。 +- Java 语言的标准库中也提供了 `String` 类作为字符串库。 +- Python 语言中使用 `str` 对象来代表字符串。`str` 对象一种不可变类型对象。即 `str` 类型创建的字符串对象在定义之后,无法更改字符串的长度,也无法改变或删除字符串中的字符。 + +## 4. 字符串匹配问题 + +> **字符串匹配(String Matching)**:又称模式匹配(Pattern Matching)。可以简单理解为,给定字符串 $T$ 和 $p$,在主串 $T$ 中寻找子串 $p$。主串 $T$ 又被称为文本串,子串 $p$ 又被称为模式串(`Pattern`)。 + +在字符串问题中,最重要的问题之一就是字符串匹配问题。而按照模式串的个数,我们可以将字符串匹配问题分为:「单模式串匹配问题」和「多模式串匹配问题」。 + +### 4.1 单模式串匹配问题 + +> **单模式匹配问题(Single Pattern Matching)**:给定一个文本串 $T = t_1t_2...t_n$,再给定一个特定模式串 $p = p_1p_2...p_n$。要求从文本串 $T$ 找出特定模式串 $p$ 的所有出现位置。 + +有很多算法可以解决单模式匹配问题。而根据在文本中搜索模式串方式的不同,我们可以将单模式匹配算法分为以下几种: + +- **基于前缀搜索方法**:在搜索窗口内从前向后(沿着文本的正向)逐个读入文本字符,搜索窗口中文本和模式串的最长公共前缀。 + - 著名的「Knuth-Morris-Pratt (KMP) 算法」和更快的「Shift-Or 算法」使用的就是这种方法。 +- **基于后缀搜索方法**:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索窗口中文本和模式串的最长公共后缀。使用这种搜索算法可以跳过一些文本字符,从而具有亚线性的平均时间复杂度。 + - 最著名的「Boyer-Moore 算法」,以及「Horspool 算法」、「Sunday(Boyer-Moore 算法的简化)算法」都使用了这种方法。 +- **基于子串搜索方法**:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索满足「既是窗口中文本的后缀,也是模式串的子串」的最长字符串。与后缀搜索方法一样,使用这种搜索方法也具有亚线性的平均时间复杂度。这种方法的主要缺点在于需要识别模式串的所有子串,这是一个非常复杂的问题。 + - 「Rabin-Karp 算法」、「Backward Dawg Matching(BDM)算法」、「Backward Nondeterministtic Dawg Matching(BNDM)算法」和 「Backward Oracle Matching(BOM)算法」 使用的就是这种思想。其中,「Rabin-Karp 算法」使用了基于散列的子串搜索算法。 + +### 4.2 多模式串匹配问题 + +> **多模式匹配问题(Multi Pattern Matching)**:给定一个文本串 $T = t_1t_2...t_n$,再给定一组模式串 $P = {p^1, p^2, ... ,p^r}$,其中每个模式串 $p^i$ 是定义在有限字母表上的字符串 $p^i = p^i_1p^i_2...p^i_n$。要求从文本串 $T$ 中找到模式串集合 $P$ 中所有模式串 $p^i$ 的所有出现位置。 + +模式串集合 $P$ 中的一些字符串可能是集合中其他字符串的子串、前缀、后缀,或者完全相等。解决多模式串匹配问题最简单的方法是利用「单模式串匹配算法」搜索 $r$ 遍。这将导致预处理阶段的最坏时间复杂度为 $O(|P|)$,搜索阶段的最坏时间复杂度为 $O(r \times n)$。 + +如果使用「单模式串匹配算法」解决多模式匹配问题,那么根据在文本中搜索模式串方式的不同,我们也可以将多模式串匹配算法分为以下三种: + +- **基于前缀搜索方法**:搜索从前向后(沿着文本的正向)进行,逐个读入文本字符,使用在 $P$ 上构建的自动机进行识别。对于每个文本位置,计算既是已读入文本的后缀,同时也是 $P$ 中某个模式串的前缀的最长字符串。 + - 著名的 「Aho-Corasick Automaton(AC 自动机)算法」、「Multiple Shift-And 算法」使用的这种方法。 +- **基于后缀搜索方法**:搜索从后向前(沿着文本的反向)进行,搜索模式串的后缀。根据后缀的下一次出现位置来移动当前文本位置。这种方法可以避免读入所有的文本字符。 + - 「Commentz-Walter(Boyer-Moore 算法的扩展算法)算法」 、「Set Horspool(Commentz-Walter 算法的简化算法)算法」、「Wu-Manber 算法」都使用了这种方法。 +- **基于子串搜索方法**:搜索从后向前(沿着文本的反向)进行,在模式串的长度为 $min(len(p^i))$ 的前缀中搜索子串,以此决定当前文本位置的移动。这种方法也可以避免读入所有的文本字符。 + - 「Multiple BNDM 算法」、「Set Backward Dawg Matching(SBDM)算法」、「Set Backwrad Oracle Matching(SBOM)算法」都使用了这种方法。 + +需要注意的是,以上所介绍的多模式串匹配算法大多使用了一种基本的数据结构:**「字典树(Trie Tree)」**。著名的 **「Aho-Corasick Automaton (AC 自动机) 算法」** 就是在「KMP 算法」的基础上,与「字典树」结构相结合而诞生的。而「AC 自动机算法」也是多模式串匹配算法中最有效的算法之一。 + +所以学习多模式匹配算法,重点是要掌握 **「字典树」** 和 **「AC 自动机算法」** 。 + +## 练习题目 + +- [0125. 验证回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) +- [0344. 反转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) +- [0557. 反转字符串中的单词 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md) + + +- [字符串基础题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】数据结构(C 语言版)- 严蔚敏 著 +- 【书籍】大话数据结构 - 程杰 著 +- 【书籍】数据结构教程(第 3 版)唐发根 著 +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【博文】[字符串和编码 - 廖雪峰的官方网站 ](https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896) +- 【文章】[数组和字符串 - LeetBook - 力扣](https://leetcode.cn/leetbook/read/array-and-string/c9lnm/) +- 【文章】[字符串部分简介 - OI Wiki](https://oi-wiki.org/string/) +- 【文章】[解密 Python 中字符串的底层实现,以及相关操作 - 古明地盆 - 博客园](https://www.cnblogs.com/traditional/p/13455962.html) \ No newline at end of file diff --git a/docs/04_string/04_02_string_single_pattern_matching.md b/docs/04_string/04_02_string_single_pattern_matching.md new file mode 100644 index 00000000..050b313b --- /dev/null +++ b/docs/04_string/04_02_string_single_pattern_matching.md @@ -0,0 +1,143 @@ +## 1. 单模式串匹配概述 + +> **单模式串匹配**:是指在文本串 $T$ 中查找一个模式串 $p$ 的所有出现位置的问题。这是字符串匹配中最基础、最经典的问题。 +> +> - **问题定义**:给定一个长度为 $n$ 的文本串 $T$ 和一个长度为 $m$ 的模式串 $p$,找出模式串 $p$ 在文本串 $T$ 中所有出现的位置。 + +### 1.1 问题描述 + +单模式串匹配问题是字符串匹配的基础问题,其形式化定义如下: + +- **输入**:文本串 $T = T[0...n-1]$,模式串 $p = p[0...m-1]$ +- **输出**:所有满足 $T[i...i+m-1] = p[0...m-1]$ 的位置 $i$ +- **目标**:高效地找到所有匹配位置 + +### 1.2 应用场景 + +单模式串匹配在计算机科学和实际应用中有广泛的应用: + +- **文本编辑器**:查找和替换功能 +- **生物信息学**:DNA序列匹配 +- **网络安全**:入侵检测系统中的模式识别 +- **数据挖掘**:文本挖掘和信息检索 +- **编译原理**:词法分析中的关键字识别 + +## 2. 单模式串匹配算法分类 + +根据算法的核心思想和复杂度,单模式串匹配算法可以分为以下几类: + +### 2.1 朴素算法 + +**Brute Force 算法(暴力匹配算法)** +- **时间复杂度**:$O(n \times m)$ +- **空间复杂度**:$O(1)$ +- **特点**:简单直观,但效率较低 +- **适用场景**:模式串较短或对性能要求不高的场景 + +### 2.2 基于哈希的算法 + +**Rabin Karp 算法** +- **时间复杂度**:平均 $O(n + m)$,最坏 $O(n \times m)$ +- **空间复杂度**:$O(1)$ +- **特点**:使用滚动哈希,平均性能较好 +- **适用场景**:需要处理多个模式串或对哈希冲突不敏感的场景 + +### 2.3 基于有限状态机的算法 + +**KMP 算法(Knuth-Morris-Pratt 算法)** +- **时间复杂度**:$O(n + m)$ +- **空间复杂度**:$O(m)$ +- **特点**:利用失配信息避免重复比较 +- **适用场景**:对性能要求较高的场景 + +### 2.4 基于启发式规则的算法 + +**Boyer Moore 算法** +- **时间复杂度**:平均 $O(n/m)$,最坏 $O(n \times m)$ +- **空间复杂度**:$O(k)$($k$ 为字符集大小) +- **特点**:从右到左比较,跳跃能力强 +- **适用场景**:模式串较长,字符集较小的场景 + +**Horspool 算法** +- **时间复杂度**:平均 $O(n)$,最坏 $O(n \times m)$ +- **空间复杂度**:$O(k)$ +- **特点**:BM算法的简化版本,实现简单 +- **适用场景**:需要简单实现的场景 + +**Sunday 算法** +- **时间复杂度**:平均 $O(n)$,最坏 $O(n \times m)$ +- **空间复杂度**:$O(k)$ +- **特点**:从左到右比较,跳跃能力强 +- **适用场景**:需要从左到右匹配的场景 + +## 3. 算法复杂度对比 + +| 算法 | 预处理时间 | 匹配时间 | 空间复杂度 | 特点 | +|------|------------|----------|------------|------| +| Brute Force | $O(1)$ | $O(n \times m)$ | $O(1)$ | 简单直观 | +| Rabin Karp | $O(m)$ | 平均 $O(n)$,最坏 $O(n \times m)$ | $O(1)$ | 滚动哈希 | +| KMP | $O(m)$ | $O(n)$ | $O(m)$ | 失配信息 | +| Boyer Moore | $O(m + k)$ | 平均 $O(n/m)$,最坏 $O(n \times m)$ | $O(k)$ | 启发式跳跃 | +| Horspool | $O(m + k)$ | 平均 $O(n)$,最坏 $O(n \times m)$ | $O(k)$ | BM简化版 | +| Sunday | $O(m + k)$ | 平均 $O(n)$,最坏 $O(n \times m)$ | $O(k)$ | 从左到右 | + +## 4. 算法选择策略 + +### 4.1 根据应用场景选择 + +- **简单应用**:选择 Brute Force 算法,代码简单,易于理解和维护 +- **一般应用**:选择 KMP 算法,性能稳定,实现相对简单 +- **高性能应用**:选择 Boyer Moore 算法,平均性能最优 +- **多模式串应用**:选择 Rabin Karp 算法,易于扩展到多模式串 + +### 4.2 根据数据特征选择 + +- **模式串较短($m < 10$)**:Brute Force 算法足够 +- **模式串较长($m > 50$)**:Boyer Moore 算法优势明显 +- **字符集较小**:Boyer Moore、Horspool、Sunday 算法效果好 +- **字符集较大**:KMP 算法更稳定 + +### 4.3 根据实现复杂度选择 + +- **快速原型**:Brute Force 或 Horspool 算法 +- **生产环境**:KMP 或 Boyer Moore 算法 +- **教学演示**:Brute Force 或 KMP 算法 + +## 5. 实际应用中的考虑 + +### 5.1 内存使用 + +- **嵌入式系统**:选择空间复杂度低的算法 +- **大规模文本处理**:考虑算法的缓存友好性 + +### 5.2 预处理开销 + +- **一次性匹配**:预处理开销相对不重要 +- **多次匹配**:预处理开销分摊后影响较小 + +### 5.3 字符集特性 + +- **ASCII 字符**:所有算法都适用 +- **Unicode 字符**:需要考虑字符编码问题 +- **二进制数据**:需要特殊处理 + +## 6. 总结 + +单模式串匹配是字符串算法的基础问题,不同的算法各有优缺点: + +- **Brute Force**:最简单,适合学习和简单应用 +- **Rabin Karp**:适合多模式串和哈希应用 +- **KMP**:理论最优,实际应用广泛 +- **Boyer Moore**:平均性能最优,适合长模式串 +- **Horspool/Sunday**:BM的简化版本,实现简单 + +## 练习题目 + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】算法导论 - Thomas H. Cormen 等著 +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187) +- 【文章】[字符串匹配算法总结 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html) diff --git a/docs/04_string/04_03_string_brute_force.md b/docs/04_string/04_03_string_brute_force.md new file mode 100644 index 00000000..d90bd989 --- /dev/null +++ b/docs/04_string/04_03_string_brute_force.md @@ -0,0 +1,64 @@ +## 1. Brute Force 算法介绍 + +> **Brute Force 算法**:简称为 BF 算法。中文意思是暴力匹配算法,也可以叫做朴素匹配算法。 +> +> - **BF 算法思想**:对于给定文本串 $T$ 与模式串 $p$,从文本串的第一个字符开始与模式串 $p$ 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 $T$ 的第二个字符起重新和模式串 $p$ 进行比较。依次类推,直到模式串 $p$ 中每个字符依次与文本串 $T$ 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 + +![朴素匹配算法](https://qcdn.itcharge.cn/images/20240511154456.png) + +## 2. Brute Force 算法步骤 + +1. 对于给定的文本串 $T$ 与模式串 $p$,求出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 同时遍历文本串 $T$ 和模式串 $p$,先将 $T[0]$ 与 $p[0]$ 进行比较。 + 1. 如果相等,则继续比较 $T[1]$ 和 $p[1]$。以此类推,一直到模式串 $p$ 的末尾 $p[m - 1]$ 为止。 + 2. 如果不相等,则将文本串 $T$ 移动到上次匹配开始位置的下一个字符位置,模式串 $p$ 则回退到开始位置,再依次进行比较。 +3. 当遍历完文本串 $T$ 或者模式串 $p$ 的时候停止搜索。 + +## 3. Brute Force 算法代码实现 + +```python +def bruteForce(T: str, p: str) -> int: + n, m = len(T), len(p) + + i, j = 0, 0 # i 表示文本串 T 的当前位置,j 表示模式串 p 的当前位置 + while i < n and j < m: # i 或 j 其中一个到达尾部时停止搜索 + if T[i] == p[j]: # 如果相等,则继续进行下一个字符匹配 + i += 1 + j += 1 + else: + i = i - (j - 1) # 如果匹配失败则将 i 移动到上次匹配开始位置的下一个位置 + j = 0 # 匹配失败 j 回退到模式串开始位置 + + if j == m: + return i - j # 匹配成功,返回匹配的开始位置 + else: + return -1 # 匹配失败,返回 -1 +``` + +## 4. Brute Force 算法分析 + +BF 算法非常简单,容易理解,但其效率很低。主要是因为在匹配过程中可能会出现回溯:当遇到一对字符不同时,模式串 $p$ 直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开始比较。 + +在回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致 BF 算法的效率很低。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 $m$ 次字符对比,总共需要进行 $n - m + 1$ 轮比较,总的比较次数为 $m \times (n - m + 1) $。所以 BF 算法的最坏时间复杂度为 $O(m \times n)$。 + +在最理想的情况下(第一次匹配直接匹配成功),BF 算法的最佳时间复杂度是 $O(m)$。 + +在一般情况下,根据等概率原则,平均搜索次数为 $\frac{(n + m)}{2}$,所以 Brute Force 算法的平均时间复杂度为 $O(n \times m)$。 + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 +- 【文章】[动画:什么是 BF 算法 ?- 吴师兄学编程](https://www.cxyxiaowu.com/560.html) +- 【文章】[BF 算法(普通模式匹配算法)及 C 语言实现 - 数据结构与算法教程](http://data.biancheng.net/view/12.html) +- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187) diff --git a/docs/04_string/04_04_string_rabin_karp.md b/docs/04_string/04_04_string_rabin_karp.md new file mode 100644 index 00000000..f7cbf381 --- /dev/null +++ b/docs/04_string/04_04_string_rabin_karp.md @@ -0,0 +1,115 @@ +## 1. Rabin Karp 算法介绍 + +> **Rabin Karp 算法**:简称为 RK 算法。是由它的两位发明者 Michael Oser Rabin 和 Richard Manning Karp 的名字来命名的。RK 算法是他们在 1987 年提出的、使用哈希函数以在文本中搜寻单个模式串的字符串搜索算法。 +> +> - **Rabin Karp 算法思想**:对于给定文本串 $T$ 与模式串 $p$,通过滚动哈希算快速筛选出与模式串 $p$ 不匹配的文本位置,然后在其余位置继续检查匹配项。 + +## 2. Rabin Karp 算法步骤 + +### 2.1 Rabin Karp 算法整体步骤 + +1. 对于给定的文本串 $T$ 与模式串 $p$,求出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 通过滚动哈希算法求出模式串 $p$ 的哈希值 $hash\underline{\hspace{0.5em}}p$。 +3. 再通过滚动哈希算法对文本串 $T$ 中 $n - m + 1$ 个子串分别求哈希值 $hash\underline{\hspace{0.5em}}t$。 +4. 然后逐个与模式串的哈希值比较大小。 + 1. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash\underline{\hspace{0.5em}}p$ 不同,则说明两者不匹配,则继续向后匹配。 + 2. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash\underline{\hspace{0.5em}}p$ 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 + 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 + 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 +5. 比较到末尾,如果仍未成功匹配,则说明文本串 $T$ 中不包含模式串 $p$,方法返回 $-1$。 + +### 2.2 滚动哈希算法 + +实现 RK 算法中一个重要步骤是 **「滚动哈希算法」**,通过滚动哈希算法,将每次计算子串哈希值的复杂度从 $O(m)$ 降到了 $O(1)$,从而提升了整个算法效率。 + +RK 算法中的滚动哈希算法主要是利用了 **「Rabin fingerprint 思想」**。这种算法思想利用了子串中每一位字符的哈希值,并且还可以根据上一个子串的哈希值,快速计算相邻子串的哈希值,从而使得每次计算子串哈希值的时间复杂度降为了 $O(1)$。 + +下面我们用一个例子来解释一下这种算法思想。 + +假设给定的字符串的字符集中只包含 $d$ 种字符,那么我们就可以用一个 $d$ 进制数表示子串的哈希值。 + +举个例子,假如字符串只包含 $a \sim z$ 这 $26$ 个小写字母,那么我们就可以用 $26$ 进制数来表示一个字符串,$a$ 表示为 $0$,$b$ 表示为 $1$,以此类推,$z$ 就用 $25$ 表示。 + +比如 `"cat"` 的哈希值就可以表示为: + +$$\begin{aligned} Hash(cat) &= c \times 26^2 + a \times 26^1 + t \times 26^0 \cr &= 2 \times 26^2 + 0 \times 26^1 + 19 \times 26^0 \cr &= 1371 \end{aligned}$$ + +这种按位计算哈希值的哈希函数有一个特点:在计算相邻子串时,可以利用上一个子串的哈希值。 + +比如说 $cat$ 的相邻子串为 `"ate"`。按照刚才哈希函数计算,可以得出 `"ate"` 的哈希值为: + +$$\begin{aligned} Hash(ate) &= a \times 26^2 + t \times 26^1 + e \times 26^0 \cr &= 0 \times 26^2 + 19 \times 26^1 + 4 \times 26^0 \cr &= 498 \end{aligned}$$ + +如果利用上一个子串 `"cat"` 的哈希值计算 `"ate"`,则 `"ate"` 的哈希值为: + +$$\begin{aligned} Hash(ate) &= (Hash(cat) - c \times 26^2) \times 26 + e \times 26^0 \cr &= (1371 - 2 \times 26^2) \times 26 + 4 \times 26^0 \cr &= 498 \end{aligned}$$ + +可以看出,这两种方式计算出的哈希值是相同的。但是第二种计算方式不需要再遍历子串,只需要进行一位字符的计算即可得出整个子串的哈希值。这样每次计算子串哈希值的时间复杂度就降到了 $O(1)$。然后我们就可以通过滚动哈希算法快速计算出子串的哈希值了。 + +我们将上面的规律扩展总结一下。 + +给定的文本串 $T$ 与模式串 $p$,求出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。字符串字符种类数为 $d$,则: + +- 模式串 $p$ 的哈希值计算方式为:$Hash(p) = p_0 \times d^{m - 1} + p_1 \times d^{m - 2} + … + p_{m-1} \times d^{0}$。 +- 文本串中起始于位置 $0$,长度为 $m$ 的子串 $T_{[0,m-1]}$ 对应哈希值计算方法为:$Hash(T_{[0, m - 1]}) = T_0 \times d^{m - 1} + T_1 \times d^{m - 2} + ... + T_{m - 1} \times d^0$。 +- 已知子串的哈希值 $Hash(T_{[i,i + m - 1]})$,将子串向右移动一位的子串对应哈希值计算方法为:$Hash(T_{[i + 1, i + m]}) = [Hash(T_{[i, i + m - 1]}) - T_i \times d^{m - 1}] \times d + T_{i + m} \times d^{0}$。 + +因为哈希值过大会造成溢出,所以我们在计算过程中还要对结果取模。取模的值应该尽可能大,并且应该是质数,这样才能减少哈希碰撞的概率。 + +## 3. Rabin Karp 算法代码实现 + +```python +# T 为文本串,p 为模式串,d 为字符集的字符种类数,q 为质数 +def rabinKarp(T: str, p: str, d, q) -> int: + n, m = len(T), len(p) + if n < m: + return -1 + + hash_p, hash_t = 0, 0 + + for i in range(m): + hash_p = (hash_p * d + ord(p[i])) % q # 计算模式串 p 的哈希值 + hash_t = (hash_t * d + ord(T[i])) % q # 计算文本串 T 中第一个子串的哈希值 + + power = pow(d, m - 1) % q # power 用于移除字符哈希时 + + for i in range(n - m + 1): + if hash_p == hash_t: # 检查模式串 p 的哈希值和子串的哈希值 + match = True # 如果哈希值相等,验证模式串和子串每个字符是否完全相同(避免哈希冲突) + for j in range(m): + if T[i + j] != p[j]: + match = False # 模式串和子串某个字符不相等,验证失败,跳出循环 + break + if match: # 如果模式串和子串每个字符是否完全相同,返回匹配开始位置 + return i + if i < n - m: # 计算下一个相邻子串的哈希值 + hash_t = (hash_t - power * ord(T[i])) % q # 移除字符 T[i] + hash_t = (hash_t * d + ord(T[i + m])) % q # 增加字符 T[i + m] + hash_t = (hash_t + q) % q # 确保 hash_t >= 0 + + return -1 +``` + +## 4. RK 算法分析 + +RK 算法可以看做是 BF 算法的一种改进。在 BF 算法中,每一个字符都需要进行比较。而在 RK 算法中,判断模式串的哈希值与每个子串的哈希值之间是否相等的时间复杂度为 $O(1)$。总共需要比较 $n - m + 1$ 个子串的哈希值,所以 RK 算法的整体时间复杂度为 $O(n)$。跟 BF 算法相比,RK 算法的效率提高了很多。 + +但是如果存在冲突的情况下,算法的效率会降低。最坏情况是每一次比较模式串的哈希值和子串的哈希值时都相等,但是每一次都会出现冲突,那么每一次都需要验证模式串和子串每个字符是否完全相同,那么总的比较次数就是 $m \times (n - m + 1)$,时间复杂度就会退化为 $O(m \times n)$。 + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著 +- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187) +- 【文章】[字符串匹配算法 - Rabin Karp 算法 - coolcao 的小站](https://coolcao.com/2020/08/20/rabin-karp/) +- 【问答】[string - Python: Rabin-Karp algorithm hashing - Stack Overflow](https://stackoverflow.com/questions/22216948/python-rabin-karp-algorithm-hashing) \ No newline at end of file diff --git a/docs/04_string/04_05_string_kmp.md b/docs/04_string/04_05_string_kmp.md new file mode 100644 index 00000000..cfc3f9c9 --- /dev/null +++ b/docs/04_string/04_05_string_kmp.md @@ -0,0 +1,165 @@ +## 1. KMP 算法介绍 + +> **KMP 算法**:全称叫做 **「Knuth Morris Pratt 算法」**,是由它的三位发明者 Donald Knuth、James H. Morris、 Vaughan Pratt 的名字来命名的。KMP 算法是他们三人在 1977 年联合发表的。 +> +> - **KMP 算法思想**:对于给定文本串 $T$ 与模式串 $p$,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 + +### 1.1 朴素匹配算法的缺陷 + +在朴素匹配算法的匹配过程中,我们分别用指针 $i$ 和指针 $j$ 指示文本串 $T$ 和模式串 $p$ 中当前正在对比的字符。当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,$j$ 回退到开始位置,$i$ 回退到之前匹配开始位置的下一个位置上,然后开启新一轮的匹配,如图所示。 + +![朴素匹配算法](https://qcdn.itcharge.cn/images/20240511154456.png) + +这样,在 Brute Force 算法中,如果从文本串 $T[i]$ 开始的这一趟字符串比较失败了,算法会直接开始尝试从 $T[i + 1]$ 开始比较。如果 $i$ 已经比较到了后边位置,则该操作相当于将指针 $i$ 进行了回退操作。 + +那么有没有哪种算法,可以让 $i$ 不发生回退,一直向右移动呢? + +### 1.2 KMP 算法的改进 + +如果我们可以通过每一次的失配而得到一些「信息」,并且这些「信息」可以帮助我们跳过那些不可能匹配成功的位置,那么我们就能大大减少模式串与文本串的匹配次数,从而达到快速匹配的目的。 + +每一次失配所告诉我们的信息是:**主串的某一个子串等于模式串的某一个前缀**。 + +这个信息的意思是:如果文本串 $T[i: i + m]$ 与模式串 $p$ 的失配是下标位置 $j$ 上发生的,那么文本串 $T$ 从下标位置 $i$ 开始连续的 $j - 1$ 个字符,一定与模式串 $p$ 的前 $j - 1$ 个字符一模一样,即:$T[i: i + j] == p[0: j]$。 + +但是知道这个信息有什么用呢? + +以刚才图中的例子来说,文本串的子串 $T[i: i + m]$ 与模式串 $p$ 的失配是在第 $5$ 个位置发生的,那么: + +- 文本串 $T$ 从下标位置 $i$ 开始连续的 $5$ 个字符,一定与模式串 $p$ 的前 $5$ 个字符一模一样,即:`"ABCAB" == "ABCAB"`。 +- 而模式串的前 $5$ 个字符中,前 $2$ 位前缀和后 $2$ 位后缀又是相同的,即 `"AB" == "AB"`。 + +所以根据上面的信息,我们可以推出:文本串子串的后 $2$ 位后缀和模式串子串的前 $2$ 位是相同的,即 $T[i + 3: i + 5] == p[0: 2]$,而这部分(即下图中的蓝色部分)是之前已经比较过的,不需要再比较了,可以直接跳过。 + +那么我们就可以将文本串中的 $T[i + 5]$ 对准模式串中的 $p[2]$,继续进行对比。这样 $i$ 就不再需要回退了,可以一直向右移动匹配下去。在这个过程中,我们只需要将模式串 $j$ 进行回退操作即可。 + +![KMP 匹配算法移动过程 1](https://qcdn.itcharge.cn/images/20240511155900.png) + +KMP 算法就是使用了这样的思路,对模式串 $p$ 进行了预处理,计算出一个 **「部分匹配表」**,用一个数组 $next$ 来记录。然后在每次失配发生时,不回退文本串的指针 $i$,而是根据「部分匹配表」中模式串失配位置 $j$ 的前一个位置的值,即 $next[j - 1]$ 的值来决定模式串可以向右移动的位数。 + +比如上述示例中模式串 $p$ 是在 $j = 5$ 的位置上发生失配的,则说明文本串的子串 $T[i: i + 5]$ 和模式串 $p[0: 5]$ 的字符是一致的,即 `"ABCAB" == "ABCAB"`。而根据「部分匹配表」中 $next[4] == 2$,所以不用回退 $i$,而是将 $j$ 移动到下标为 $2$ 的位置,让 $T[i + 5]$ 直接对准 $p[2]$,然后继续进行比对。 + +### 1.3 next 数组 + +上文提到的「部分匹配表」,也叫做「前缀表」,在 KMP 算法中使用 $next$ 数组存储。$next[j]$ 表示的含义是:**记录下标 j 之前(包括 j)的模式串 $p$ 中,最长相等前后缀的长度。** + +简单而言,就是求:**模式串 $p$ 的子串 $p[0: j + 1]$ 中,使得「前 k 个字符」恰好等于「后 k 个字符」的「最长的 $k$」**。当然子串 $p[0: j + 1]$ 本身不参与比较。 + +举个例子来说明一下,以 `p = "ABCABCD"` 为例。 + +- $next[0] = 0$,因为 `"A"` 中无有相同前缀后缀,最大长度为 $0$。 +- $next[1] = 0$,因为 `"AB"` 中无相同前缀后缀,最大长度为 $0$。 +- $next[2] = 0$,因为 `"ABC"` 中无相同前缀后缀,最大长度为 $0$。 +- $next[3] = 1$,因为 `"ABCA"` 中有相同的前缀后缀 `"A"`,最大长度为 $1$。 +- $next[4] = 2$,因为 `"ABCAB"` 中有相同的前缀后缀 `"AB"`,最大长度为 $2$。 +- $next[5] = 3$,因为 `"ABCABC"` 中有相同的前缀后缀 `"ABC"`,最大长度为 $3$。 +- $next[6] = 0$,因为 `"ABCABCD"` 中无相同前缀后缀,最大长度为 $0$。 + +同理也可以计算出 `"ABCABDEF"` 的前缀表为 $[0, 0, 0, 1, 2, 0, 0, 0]$。`"AABAAAB"` 的前缀表为 $[0, 1, 0, 1, 2, 2, 3]$。`"ABCDABD"` 的前缀表为 $[0, 0, 0, 0, 1, 2, 0]$。 + +在之前的例子中,当 $p[5]$ 和 $T[i + 5]$ 匹配失败后,根据模式串失配位置 $j$ 的前一个位置的值,即 $next[4] = 2$,我们直接让 $T[i + 5]$ 直接对准了 $p[2]$,然后继续进行比对,如下图所示。 + +![KMP 匹配算法移动过程 2](https://qcdn.itcharge.cn/images/20240511161310.png) + +**但是这样移动的原理是什么?** + +其实在上文 **「1.2 KMP 算法的改进」** 中的例子中我们提到过了。现在我们将其延伸总结一下,其实这个过程就是利用了前缀表进行模式串移动的原理,具体推论如下。 + +如果文本串 $T[i: i + m]$ 与模式串 $p$ 的失配是在第 $j$ 个下标位置发生的,那么: + +- 文本串 $T$ 从下标位置 $i$ 开始连续的 $j$ 个字符,一定与模式串 $p$ 的前 $j$ 个字符一模一样,即:$T[i: i + j] == p[0: j]$。 +- 而如果模式串 $p$ 的前 $j$ 个字符中,前 $k$ 位前缀和后 $k$ 位后缀相同,即 $p[0: k] == p[j - k: j]$,并且要保证 $k$ 要尽可能长。 + +可以推出:文本串子串的后 $k$ 位后缀和模式串子串的前 $k$ 位是相同的,即 $T[i + j - k: i + j] == p[0: k]$(这部分是已经比较过的),不需要再比较了,可以直接跳过。 + +那么我们就可以将文本串中的 $T[i + j]$ 对准模式串中的 $p[k]$,继续进行对比。这里的 $k$ 其实就是 $next[j - 1]$。 + +## 2. KMP 算法步骤 + +### 3.1 next 数组的构造 + +我们可以通过递推的方式构造 $next$ 数组。 + +- 我们把模式串 $p$ 拆分成 $left$、$right$ 两部分。$left$ 表示前缀串开始所在的下标位置,$right$ 表示后缀串开始所在的下标位置,起始时 $left = 0$,$right = 1$。 +- 比较一下前缀串和后缀串是否相等。通过比较 $p[left]$ 和 $p[right]$ 来进行判断。 +- 如果 $p[left] != p[right]$,说明当前的前后缀不相同。则让后缀开始位置 $k$ 不动,前缀串开始位置 $left$ 不断回退到 $next[left - 1]$ 位置,直到 $p[left] == p[right]$ 为止。 +- 如果 $p[left] == p[right]$,说明当前的前后缀相同,则可以先让 $left += 1$,此时 $left$ 既是前缀下一次进行比较的下标位置,又是当前最长前后缀的长度。 +- 记录下标 $right$ 之前的模式串 $p$ 中,最长相等前后缀的长度为 $left$,即 $next[right] = left$。 + +### 3.2 KMP 算法整体步骤 + +1. 根据 $next$ 数组的构造步骤生成「前缀表」$next$。 +2. 使用两个指针 $i$、$j$,其中 $i$ 指向文本串中当前匹配的位置,$j$ 指向模式串中当前匹配的位置。初始时,$i = 0$,$j = 0$。 +3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 $j = next[j - 1]$,直到 $j == 0$ 时或前缀匹配成功时停止回退。 +4. 如果当前模式串前缀匹配成功,则令模式串向右移动 $1$ 位,即 $j += 1$。 +5. 如果当前模式串 **完全** 匹配成功,则返回模式串 $p$ 在文本串 $T$ 中的开始位置,即 $i - j + 1$。 +6. 如果还未完全匹配成功,则令文本串向右移动 $1$ 位,即 $i += 1$,然后继续匹配。 +7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 $-1$。 + +## 3. KMP 算法代码实现 + +```python +# 生成 next 数组 +# next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度 +def generateNext(p: str): + m = len(p) + next = [0 for _ in range(m)] # 初始化数组元素全部为 0 + + left = 0 # left 表示前缀串开始所在的下标位置 + for right in range(1, m): # right 表示后缀串开始所在的下标位置 + while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退 + left = next[left - 1] # left 进行回退操作 + if p[left] == p[right]: # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度 + left += 1 + next[right] = left # 记录前缀长度,更新 next[right], 结束本次循环, right += 1 + + return next + +# KMP 匹配算法,T 为文本串,p 为模式串 +def kmp(T: str, p: str) -> int: + n, m = len(T), len(p) + + next = generateNext(p) # 生成 next 数组 + + j = 0 # j 为模式串中当前匹配的位置 + for i in range(n): # i 为文本串中当前匹配的位置 + while j > 0 and T[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退 + j = next[j - 1] + if T[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配 + j += 1 + if j == m: # 当前模式串完全匹配成功,返回匹配开始位置 + return i - j + 1 + return -1 # 匹配失败,返回 -1 + +print(kmp("abbcfdddbddcaddebc", "ABCABCD")) +print(kmp("abbcfdddbddcaddebc", "bcf")) +print(kmp("aaaaa", "bba")) +print(kmp("mississippi", "issi")) +print(kmp("ababbbbaaabbbaaa", "bbbb")) +``` + +## 4. KMP 算法分析 + +- KMP 算法在构造前缀表阶段的时间复杂度为 $O(m)$,其中 $m$ 是模式串 $p$ 的长度。 +- KMP 算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标 $i$ 并没有进行回退,可以看出匹配阶段的时间复杂度是 $O(n)$,其中 $n$ 是文本串 $T$ 的长度。 +- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n \times m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。 + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 +- 【博文】[从头到尾彻底理解 KMP - 结构之法 算法之道 - CSDN博客](https://blog.csdn.net/v_JULY_v/article/details/7041827?spm=1001.2014.3001.5502) +- 【博文】[字符串匹配的 KMP 算法 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html) +- 【题解】[多图预警 - 详解 KMP 算法 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/duo-tu-yu-jing-xiang-jie-kmp-suan-fa-by-w3c9c/) +- 【题解】[「代码随想录」KMP算法详解 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/dai-ma-sui-xiang-lu-kmpsuan-fa-xiang-jie-mfbs/) diff --git a/docs/04_string/04_06_string_boyer_moore.md b/docs/04_string/04_06_string_boyer_moore.md new file mode 100644 index 00000000..4588486a --- /dev/null +++ b/docs/04_string/04_06_string_boyer_moore.md @@ -0,0 +1,346 @@ +## 1. Boyer Moore 算法介绍 + +> **Boyer Moore 算法**:简称为 BM 算法,是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中,比 KMP 算法要快 3~5 倍。 +> +> - **BM 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +BM 算法的精髓在于使用了两种不同的启发策略来计算后移位数:**「坏字符规则(The Bad Character Rule)」** 和 **「好后缀规则(The Good Suffix Shift Rule)」**。 + +这两种启发策略的计算过程只与模式串 $p$ 相关,而与文本串 $T$ 无关。因此在对模式串 $p$ 进行预处理时,可以预先生成「坏字符规则后移表」和「好后缀规则后移表」,然后在匹配的过程中,只需要比较一下两种策略下最大的后移位数进行后移即可。 + +同时,还需要注意一点。BM 算法在移动模式串的时候和常规匹配算法一样是从左到右进行,但是在进行比较的时候是从右到左,即基于后缀进行比较。 + +下面我们来讲解一下 BF 算法中的两种不同启发策略:「坏字符规则」和「好后缀规则」。 + +## 2. Boyer Moore 算法启发策略 + +### 2.1 坏字符规则 + +> **坏字符规则(The Bad Character Rule)**:当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,则称文本串 $T$ 中这个失配字符为 **「坏字符」**,此时模式串 $p$ 可以快速向右移动。 + +「坏字符规则」的移动位数分为两种情况: + +- **情况 1:坏字符出现在模式串 $p$ 中**。 + - 这种情况下,可将模式串中最后一次出现的坏字符与文本串中的坏字符对齐,如下图所示。 + - **向右移动位数 = 坏字符在模式串中的失配位置 - 坏字符在模式串中最后一次出现的位置**。 + +![情况 1:坏字符出现在模式串 p 中](https://qcdn.itcharge.cn/images/20240511164026.png) + +- **情况 2:坏字符没有出现在模式串 $p$ 中**。 + - 这种情况下,可将模式串向右移动一位,如下图所示。 + - **向右移动位数 = 坏字符在模式串中的失配位置 + 1**。 + +![情况 2:坏字符没有出现在模式串 p 中](https://qcdn.itcharge.cn/images/20240511164048.png) + +### 2.2 好后缀规则 + +> **好后缀规则(The Good Suffix Shift Rule)**:当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,则称文本串 $T$ 中已经匹配好的字符串为 **「好后缀」**,此时模式串 $p$ 可以快速向右移动。 + +「好后缀规则」的移动方式分为三种情况: + +- **情况 1:模式串中有子串匹配上好后缀**。 + - 这种情况下,移动模式串,让该子串和好后缀对齐即可。如果超过一个子串匹配上好后缀,则选择最右侧的子串对齐,如下图所示。 + - **向右移动位数 = 好后缀的最后一个字符在模式串中的位置 - 匹配的子串最后一个字符出现的位置**。 + +![情况 1:模式串中有子串匹配上好后缀](https://qcdn.itcharge.cn/images/20240511164101.png) + +- **情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀**。 + - 这种情况下,我们需要在模式串的前缀中寻找一个最长前缀,该前缀等于好后缀的后缀。找到该前缀后,让该前缀和好后缀的后缀对齐。 + - **向右移动位数 = 好后缀的后缀的最后一个字符在模式串中的位置 - 最长前缀的最后一个字符出现的位置**。 + +![情况 2:模式串中无子串匹配上好后缀, 但有最长前缀匹配好后缀的后缀](https://qcdn.itcharge.cn/images/20240511164112.png) + +- **情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配**。 + - 可将模式串整个右移。 + - **向右移动位数 = 模式串的长度**。 + +![情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配](https://qcdn.itcharge.cn/images/20240511164124.png) + +## 3. Boyer Moore 算法匹配过程示例 + +下面我们根据 J Strother Moore 教授给出的例子,先来介绍一下 BF 算法的匹配过程,顺便加深对 **「坏字符规则」** 和 **「好后缀规则」** 的理解。 + +::: tabs#Boyer-Moore + +@tab <1> + +假设文本串为 `"HERE IS A SIMPLE EXAMPLE"`,模式串为 `"EXAMPLE"`,如下图所示。 + +![Boyer Moore 算法步骤 1](https://qcdn.itcharge.cn/images/20220127164130.png) + +@tab <2> + +首先,令模式串与文本串的头部对齐,然后从模式串的尾部开始逐位比较,如下图所示。 + +![Boyer Moore 算法步骤 2](https://qcdn.itcharge.cn/images/20220127164140.png) + +可以看出来,`'S'` 与 `'E'` 不匹配。这时候,不匹配的字符 `'S'` 就被称为「坏字符(Bad Character)」,对应着模式串的第 $6$ 位。并且 `'S'` 并不包含在模式串 `"EXAMPLE"` 中(相当于`'S'` 在模式串中最后一次出现的位置是 $-1$)。根据「坏字符规则」,可以把模式串直接向右移动 $6 - (-1) = 7$​ 位,即将文本串中 `'S'` 的后一位上。 + +@tab <3> + +将模式串向右移动 $7$ 位。然后依然从模式串尾部开始比较,发现 `'P'` 和 `'E'` 不匹配,则 `'P'` 是坏字符,如下图所示。 + +![Boyer Moore 算法步骤 3](https://qcdn.itcharge.cn/images/20220127164151.png) + +但是 `'P'` 包含在模式串 `"EXAMPLE"` 中,`'P'` 这个坏字符在模式串中的失配位置是第 $6$ 位,并且在模式串中最后一次出现的位置是 $4$(编号从 $0$​ 开始)。 + +@tab <4> + +根据「坏字符规则」,可以将模式串直接向右移动 $6 - 4 = 2$ 位,将文本串的 `'P'` 和模式串中的 `'P'` 对齐,如下图所示。 + +![Boyer Moore 算法步骤 4](https://qcdn.itcharge.cn/images/20220127164202.png) + +@tab <5> + +我们继续从尾部开始逐位比较。先比较文本串的 `'E'` 和模式串的 `'E'`,如下图所示。可以看出文本串的 `'E'` 和模式串的 `'E'` 匹配,则 `"E"` 为好后缀,`"E"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 + +![Boyer Moore 算法步骤 5](https://qcdn.itcharge.cn/images/20220127164212.png) + +@tab <6> + +继续比较前面一位,即文本串的 `'L'` 和模式串的 `'L'`,如下图所示。可以看出文本串的 `'L'` 和模式串的 `'L'` 匹配。则 `"LE"` 为好后缀,`"LE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 + +![Boyer Moore 算法步骤 6](https://qcdn.itcharge.cn/images/20220127164222.png) + +@tab <7> + +继续比较前面一位,即文本串中的 `'P'` 和模式串中的 `'P'`,如下图所示。可以看出文本串中的 `'P'` 和模式串中的 `'P'` 匹配,则 `"PLE"` 为好后缀,`"PLE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 + +![Boyer Moore 算法步骤 7](https://qcdn.itcharge.cn/images/20220127164232.png) + +@tab <8> + +继续比较前面一位,即文本串中的 `'M'` 和模式串中的 `'M'`,如下图所示。可以看出文本串中的 `'M'` 和模式串中的 `'M'` 匹配,则 `"MPLE"` 为好后缀。`"MPLE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 + +![Boyer Moore 算法步骤 8](https://qcdn.itcharge.cn/images/20220127164241.png) + +@tab <9> + +继续比较前面一位,即文本串中的 `'I'` 和模式串中的 `'A'`,如下图所示。可以看出文本串中的 `'I'` 和模式串中的 `'A'` 不匹配。 + +![Boyer Moore 算法步骤 9-1](https://qcdn.itcharge.cn/images/20220127164251.png) + +此时,如果按照「坏字符规则」,模式串应该向右移动 $2 - (-1) = 3$ 位。但是根据「好后缀规则」,我们还有更好的移动方法。 + +在好后缀 `"MPLE"` 和好后缀的后缀 `"PLE"`、`"LE"`、`"E"` 中,只有好后缀的后缀 `"E"` 和模式串中的前缀 `"E"` 相匹配,符合好规则的第二种情况。好后缀的后缀 `"E"` 的最后一个字符在模式串中的位置为 $6$,最长前缀 `"E"`的最后一个字符出现的位置为 $0$,则根据「好后缀规则」,可以将模式串直接向右移动 $6 - 0 = 6$ 位。如下图所示。 + +![Boyer Moore 算法步骤 9-2](https://qcdn.itcharge.cn/images/20220127164301.png) + +@tab <10> + +继续从模式串的尾部开始逐位比较,如下图所示。 + +可以看出,`'P'` 与`'E'` 不匹配,`'P'` 是坏字符。根据「坏字符规则」,可以将模式串直接向右移动 $6 - 4 = 2$ 位,如下图所示。 + +![Boyer Moore 算法步骤 10](https://qcdn.itcharge.cn/images/20220127164312.png) + +@tab <11> + +继续从模式串的尾部开始逐位比较,发现模式串全部匹配,于是搜索结束,返回模式串在文本串中的位置。 + +::: + +## 4. Boyer Moore 算法步骤 + +整个 BM 算法步骤描述如下: + +1. 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 先对模式串 $p$ 进行预处理,生成坏字符位置表 $bc\underline{\hspace{0.5em}}table$ 和好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}talbe$。 +3. 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串末尾位置,即 $j = m - 1$,然后从模式串末尾位置开始进行逐位比较。 + 1. 如果文本串对应位置 $T[i + j]$ 上的字符与 $p[j]$ 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + 2. 如果文本串对应位置 $T[i + j]$ 上的字符与 $p[j]$ 不相同,则: + 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 $bad\underline{\hspace{0.5em}}move$。 + 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 $good\underline{\hspace{0.5em}}mode$。 + 3. 取两种移动距离的最大值,然后对模式串进行移动,即 $i += max(bad\underline{\hspace{0.5em}}move, good\underline{\hspace{0.5em}}move)$。 +4. 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 + +## 5. Boyer Moore 算法代码实现 + +BM 算法的匹配过程实现起来并不是很难,而整个算法实现的难点在于预处理阶段的「生成坏字符位置表」和「生成好后缀规则后移位数表」这两步上。尤其是「生成好后缀规则后移位数表」,实现起来十分复杂。下面我们一一进行讲解。 + +### 5.1 生成坏字符位置表代码实现 + +生成坏字符位置表的代码实现比较简单。具体步骤如下: + +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示坏字符 $bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右位置。 + +- 遍历模式串,以当前字符 $p[i]$ 为键,所在位置下标为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现的最右侧位置。 + +这样如果在 BM 算法的匹配过程中,如果 $bad\underline{\hspace{0.5em}}char$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令 $bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右侧位置为 $-1$。如果 $bad\underline{\hspace{0.5em}}char$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,$bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右侧位置就是 $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$。这样就可以根据公式计算出可以向右移动的位数了。 + +生成坏字符位置表的代码如下: + +```python +# 生成坏字符位置表 +# bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 +def generateBadCharTable(p: str): + bc_table = dict() + + for i in range(len(p)): + bc_table[p[i]] = i # 更新坏字符在模式串中最后一次出现的位置 + return bc_table +``` + +### 5.2 生成好后缀规则后移位数表代码实现 + +为了生成好后缀规则后移位数表,我们需要先定义一个后缀数组 $suffix$,其中 $suffix[i] = s$ 表示为以下标 $i$ 为结尾的子串与模式串后缀匹配的最大长度为 $s$。即满足 $p[i-s...i] == p[m-1-s, m-1]$ 的最大长度为 $s$。 + +构建 $suffix$ 数组的代码如下: + +```python +# 生成 suffix 数组 +# suffix[i] 表示为以下标 i 为结尾的子串与模式串后缀匹配的最大长度 +def generageSuffixArray(p: str): + m = len(p) + suffix = [m for _ in range(m)] # 初始化时假设匹配的最大长度为 m + for i in range(m - 2, -1, -1): # 子串末尾从 m - 2 开始 + start = i # start 为子串开始位置 + while start >= 0 and p[start] == p[m - 1 - i + start]: + start -= 1 # 进行后缀匹配,start 为匹配到的子串开始位置 + suffix[i] = i - start # 更新以下标 i 为结尾的子串与模式串后缀匹配的最大长度 + return suffix +``` + +有了 $suffix$ 数组,我们就可以在此基础上定义好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}list$。我们使用一个数组来表示好后缀规则后移位数表。其中 $gs\underline{\hspace{0.5em}}list[j]$ 表示在 $j$ 下标处遇到坏字符时,可根据好规则向右移动的距离。 + +由 「2.2 好后缀规则」 中可知,好后缀规则的移动方式可以分为三种情况。 + +- 情况 1:模式串中有子串匹配上好后缀。 +- 情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀。 +- 情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配。 + +这 3 种情况中,情况 2 和情况 3 可以合并,因为情况 3 可以看做是匹配到的最长前缀长度为 $0$。而如果遇到一个坏字符同时满足多种情况,则我们应该选择满足情况中最小的移动距离才不会漏掉可能匹配的情况,比如说当模式串中既有子串可以匹配上好后缀,又有前缀可以匹配上好后缀的后缀,则应该按照前者的方式移动模式串。 + +- 为了得到精确的 $gs\underline{\hspace{0.5em}}list[j]$​,我们可以先假定所有情况都为情况 3,即 $gs\underline{\hspace{0.5em}}list[i] = m$​。 +- 然后通过后缀和前缀匹配的方法,更新情况 2 下 $gs\underline{\hspace{0.5em}}list$ 中坏字符位置处的值,即 $gs\underline{\hspace{0.5em}}list[j] = m - 1 - i$,其中 $j$ 是好后缀前的坏字符位置,$i$ 是最长前缀的末尾位置,$m - 1 - i$ 是可向右移动的距离。 +- 最后再计算情况 1 下 $gs\underline{\hspace{0.5em}}list$ 中坏字符位置处的值,更新在好后缀的左端点处($m - 1 - suffix[i]$ 处)遇到坏字符可向后移动位数,即 $gs\underline{\hspace{0.5em}}list[m - 1 - suffix[i]] = m - 1 - i$。 + +生成好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}list$ 代码如下: + +```python +# 生成好后缀规则后移位数表 +# gs_list[j] 表示在 j 下标处遇到坏字符时,可根据好规则向右移动的距离 +def generageGoodSuffixList(p: str): + # 好后缀规则后移位数表 + # 情况 1: 模式串中有子串匹配上好后缀 + # 情况 2: 模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀 + # 情况 3: 模式串中无子串匹配上好后缀,也找不到前缀匹配 + + m = len(p) + gs_list = [m for _ in range(m)] # 情况 3:初始化时假设全部为情况 3 + suffix = generageSuffixArray(p) # 生成 suffix 数组 + + j = 0 # j 为好后缀前的坏字符位置 + for i in range(m - 1, -1, -1): # 情况 2:从最长的前缀开始检索 + if suffix[i] == i + 1: # 匹配到前缀,即 p[0...i] == p[m-1-i...m-1] + while j < m - 1 - i: + if gs_list[j] == m: + gs_list[j] = m - 1 - i # 更新在 j 处遇到坏字符可向后移动位数 + j += 1 + + for i in range(m - 1): # 情况 1:匹配到子串, p[i-s...i] == p[m-1-s, m-1] + gs_list[m - 1 - suffix[i]] = m - 1 - i # 更新在好后缀的左端点处遇到坏字符可向后移动位数 + return gs_list +``` + +### 5.3 Boyer Moore 算法整体代码实现 + +```python +# BM 匹配算法 +def boyerMoore(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) # 生成坏字符位置表 + gs_list = generageGoodSuffixList(p) # 生成好后缀规则后移位数表 + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: # 进行后缀匹配,跳出循环说明出现坏字符 + j -= 1 + if j < 0: + return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 + bad_move = j - bc_table.get(T[i + j], -1) # 坏字符规则下的后移位数 + good_move = gs_list[j] # 好后缀规则下的后移位数 + i += max(bad_move, good_move) # 取两种规则下后移位数的最大值进行移动 + return -1 + + +# 生成坏字符位置表 +# bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 +def generateBadCharTable(p: str): + bc_table = dict() + + for i in range(len(p)): + bc_table[p[i]] = i # 更新坏字符在模式串中最后一次出现的位置 + return bc_table + +# 生成好后缀规则后移位数表 +# gs_list[j] 表示在 j 下标处遇到坏字符时,可根据好规则向右移动的距离 +def generageGoodSuffixList(p: str): + # 好后缀规则后移位数表 + # 情况 1: 模式串中有子串匹配上好后缀 + # 情况 2: 模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀 + # 情况 3: 模式串中无子串匹配上好后缀,也找不到前缀匹配 + + m = len(p) + gs_list = [m for _ in range(m)] # 情况 3:初始化时假设全部为情况 3 + suffix = generageSuffixArray(p) # 生成 suffix 数组 + + j = 0 # j 为好后缀前的坏字符位置 + for i in range(m - 1, -1, -1): # 情况 2:从最长的前缀开始检索 + if suffix[i] == i + 1: # 匹配到前缀,即 p[0...i] == p[m-1-i...m-1] + while j < m - 1 - i: + if gs_list[j] == m: + gs_list[j] = m - 1 - i # 更新在 j 处遇到坏字符可向后移动位数 + j += 1 + + for i in range(m - 1): # 情况 1:匹配到子串 p[i-s...i] == p[m-1-s, m-1] + gs_list[m - 1 - suffix[i]] = m - 1 - i # 更新在好后缀的左端点处遇到坏字符可向后移动位数 + return gs_list + +# 生成 suffix 数组 +# suffix[i] 表示为以下标 i 为结尾的子串与模式串后缀匹配的最大长度 +def generageSuffixArray(p: str): + m = len(p) + suffix = [m for _ in range(m)] # 初始化时假设匹配的最大长度为 m + for i in range(m - 2, -1, -1): # 子串末尾从 m - 2 开始 + start = i # start 为子串开始位置 + while start >= 0 and p[start] == p[m - 1 - i + start]: + start -= 1 # 进行后缀匹配,start 为匹配到的子串开始位置 + suffix[i] = i - start # 更新以下标 i 为结尾的子串与模式串后缀匹配的最大长度 + return suffix + +print(boyerMoore("abbcfdddbddcaddebc", "aaaaa")) +print(boyerMoore("", "")) +``` + +## 6. Boyer Moore 算法分析 + +- BM 算法在预处理阶段的时间复杂度为 $O(n + \sigma)$,其中 $\sigma$ 是字符集的大小。 +- BM 算法在搜索阶段最好情况是每次匹配时,模式串 $p$ 中不存在与文本串 $T$ 中第一个匹配的字符。这时的时间复杂度为 $O(n / m)$。 +- BM 算法在搜索阶段最差情况是文本串 $T$ 中有多个重复的字符,并且模式串 $p$ 中有 $m - 1$ 个相同字符前加一个不同的字符组成。这时的时间复杂度为 $O(m * n)$。 +- 当模式串 $p$ 是非周期性的,在最坏情况下,BM 算法最多需要进行 $3 * n$ 次字符比较操作。 + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【文章】[不用找了,学习 BM 算法,这篇就够了(思路+详注代码)- BoCong-Deng 的博客](https://blog.csdn.net/DBC_121/article/details/105569440) +- 【文章】[字符串匹配的 Boyer-Moore 算法 - 阮一峰的网络日志](https://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html) +- 【文章】[ bm 算法好后缀 java 实现 - 长笛小号的博客 - CSDN博客](https://blog.csdn.net/weixin_29217235/article/details/114488027) +- 【文章】[BM算法详解 - 简单爱_wxg - 博客园](https://www.cnblogs.com/wxgblogs/p/5701101.html) +- 【文章】[grep 之字符串搜索算法 Boyer-Moore 由浅入深 - Alexia(minmin) - 博客园](https://www.cnblogs.com/lanxuezaipiao/p/3452579.html) +- 【文章】[字符串匹配基础(中)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71525) +- 【代码】[BM算法 附有解释 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/bmsuan-fa-fu-you-jie-shi-by-wen-198/) \ No newline at end of file diff --git a/docs/04_string/04_07_string_horspool.md b/docs/04_string/04_07_string_horspool.md new file mode 100644 index 00000000..33554449 --- /dev/null +++ b/docs/04_string/04_07_string_horspool.md @@ -0,0 +1,114 @@ +## 1.1 Horspool 算法介绍 + +> **Horspool 算法**:是一种在字符串中查找子串的算法,它是由 Nigel Horspool 教授于 1980 年出版的,是首个对 Boyer Moore 算法进行简化的算法。 +> +> - **Horspool 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +可以看出,Horspool 算法思想和 Boyer Moore 算法思想是一致的。Horspool 算法是在 Boyer Moore 算法思想基础上改进了「坏字符规则」。当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,可以模式串 $p$ 快速向右移动。 + +遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: + +- **情况 1:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符 $T[i + m - 1]$ 出现在模式串 $p$ 中**。 + - 这种情况下,可将 $T[i + m - 1]$ 与模式串中最后一次出现的该字符对齐,如下图所示。 + - **向右移动位数 = 模式串最后一个字符的位置 - T[i + m - 1] 在模式串中最后一次出现的位置**。 + - 注意:模式串最后一个字符的位置其实就是「模式串长度 - 1」。 + +![Horspool 算法情况 1](https://qcdn.itcharge.cn/images/20240511165106.png) + +- **情况 2:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符 $T[i + m - 1]$ 没有出现在模式串 $p$ 中**。 + - 这种情况下,可将模式串整个右移,如下图所示。 + - **向右移动位数 = 整个模式串长度**。 + +![Horspool 算法情况 2](https://qcdn.itcharge.cn/images/20240511165122.png) + +## 2. Horspool 算法步骤 + +整个 Horspool 算法步骤描述如下: + +1. 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 先对模式串 $p$ 进行预处理,生成后移位数表 $bc\underline{\hspace{0.5em}}table$。 +3. 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串末尾位置,即 $j = m - 1$,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + 2. 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 不同,则: + 1. 根据后移位数表 $bc\underline{\hspace{0.5em}}table$ 和模式串末尾位置对应的文本串上的字符 $T[i + m - 1]$ ,计算出可移动距离 $bc\underline{\hspace{0.5em}}table[T[i + m - 1]]$,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 + +## 3. Horspool 算法代码实现 + +### 3.1 后移位数表代码实现 + +生成后移位数表的代码实现比较简单,跟 Boyer Moore 算法中生成坏字符位置表的代码差不多。具体步骤如下: + +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示表示遇到坏字符可以向右移动的距离。 +- 遍历模式串,以当前字符 $p[i]$ 为键,可以向右移动的距离($m - 1 - i$)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 + +如果在 Horspool 算法的匹配过程中,如果 $T[i + m - 1]$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令其为 $m$,表示可以将模式串整个右移。如果 $T[i + m - 1]$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,可移动距离就是 $bc\underline{\hspace{0.5em}}table[T[i + m - 1]]$ 。这样就能计算出可以向右移动的位数了。 + +生成后移位数表的代码如下: + +```python +# 生成后移位数表 +# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 +def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m - 1): # 迭代到 m - 2 + bc_table[p[i]] = m - 1 - i # 更新遇到坏字符可向右移动的距离 + return bc_table +``` + +### 3.2 Horspool 算法整体代码实现 + +```python +# horspool 算法,T 为文本串,p 为模式串 +def horspool(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) # 生成后移位数表 + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: # 进行后缀匹配,跳出循环说明出现坏字符 + j -= 1 + if j < 0: + return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 + i += bc_table.get(T[i + m - 1], m) # 通过后移位数表,向右进行进行快速移动 + return -1 # 匹配失败 + +# 生成后移位数表 +# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 +def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m - 1): # 迭代到 m - 2 + bc_table[p[i]] = m - 1 - i # 更新遇到坏字符可向右移动的距离 + return bc_table + +print(horspool("abbcfdddbddcaddebc", "aaaaa")) +print(horspool("abbcfdddbddcaddebc", "bcf")) +``` + +## 4. Horspool 算法分析 + +- Horspool 算法在平均情况下的时间复杂度为 $O(n)$,但是在最坏情况下时间复杂度会退化为 $O(n * m)$。 + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【博文】[字符串模式匹配算法:BM、Horspool、Sunday、KMP、KR、AC算法 - schips - 博客园](https://www.cnblogs.com/schips/p/11098041.html) + diff --git a/docs/04_string/04_08_string_sunday.md b/docs/04_string/04_08_string_sunday.md new file mode 100644 index 00000000..0bb79d8d --- /dev/null +++ b/docs/04_string/04_08_string_sunday.md @@ -0,0 +1,114 @@ +## 1. Sunday 算法介绍 + +**「Sunday 算法」** 是一种在字符串中查找子串的算法,是 Daniel M.Sunday 于1990年提出的字符串模式匹配算法。 + +> **Sunday 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +Sunday 算法思想跟 Boyer Moore 算法思想类似。不同的是,Sunday 算法匹配顺序是从左向右,并且在模式串 $p$ 匹配失败时关注的是文本串 $T$ 中参加匹配的末尾字符的下一位字符。当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,可以将模式串 $p$ 快速向右移动。 + +遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: + +- **情况 1:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符下一个位置的字符 $T[i + m]$ 出现在模式串 $p$ 中**。 + - 这种情况下,可将$T[i + m]$ 与模式串中最后一次出现的该字符对齐,如下图所示。 + - **向右移动位数 = 文本串 $T$ 中与模式串 $p$ 尾部位置的下一个位置 $T[i + m]$ 在模式串中最后一次出现的位置**。 + - 注意:文本串 $T$ 中与模式串 $p$ 尾部位置的下一个位置其实就是「模式串长度」。 + +![Sunday 算法情况 1](https://qcdn.itcharge.cn/images/20240511165526.png) + +- **情况 2:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符下一个位置的字符 $T[i + m]$ 没有出现在模式串 $p$ 中**。 + - 这种情况下,可将模式串整个右移,如下图所示。 + - **向右移动位数 = 整个模式串长度 + 1**。 + +![Sunday 算法情况 2](https://qcdn.itcharge.cn/images/20240511165540.png) + +## 2. Sunday 算法步骤 + +整个 Horspool 算法步骤描述如下: + +- 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +- 先对模式串 $p$ 进行预处理,生成后移位数表 $bc\underline{\hspace{0.5em}}table$。 +- 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串开始,即 $j = 0$,然后从模式串开始位置开始比较。 + - 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 相同,则继续比较后一位字符。 + - 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + - 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 不同,则: + - 根据后移位数表 $bc\underline{\hspace{0.5em}}table$ 和模式串末尾位置对应的文本串上的字符 $T[i + m]$ ,计算出可移动距离 $bc\underline{\hspace{0.5em}}table[T[i + m]]$,然后将模式串进行后移。 +- 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 + +## 3. Sunday 算法代码实现 + +### 3.1 后移位数表代码实现 + +生成后移位数表的代码实现比较简单,跟 Horspool 算法中生成后移位数表的代码差不多。具体步骤如下: + +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示表示遇到坏字符可以向右移动的距离。 +- 遍历模式串,以当前字符 $p[i]$ 为键,可以向右移动的距离($m - i$)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 + +如果在 Sunday 算法的匹配过程中,如果 $T[i + m]$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令其为 $m + 1$,表示可以将模式串整个右移到上一次匹配末尾后边两个位置上。如果 $T[i + m]$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,可移动距离就是 $bc\underline{\hspace{0.5em}}table[T[i + m]]$ 。这样就能计算出可以向右移动的位数了。 + +生成后移位数表的代码如下: + +```python +# 生成后移位数表 +# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 +def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m): # 迭代到最后一个位置 m - 1 + bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 + return bc_table +``` + +### 3.2 Sunday 算法整体代码实现 + +```python +# sunday 算法,T 为文本串,p 为模式串 +def sunday(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) # 生成后移位数表 + + i = 0 + while i <= n - m: + j = 0 + if T[i: i + m] == p: + return i # 匹配完成,返回模式串 p 在文本串 T 的位置 + if i + m >= n: + return -1 + i += bc_table.get(T[i + m], m + 1) # 通过后移位数表,向右进行进行快速移动 + return -1 # 匹配失败 + +# 生成后移位数表 +# bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 +def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m): # 迭代到最后一个位置 m - 1 + bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 + return bc_table + +print(sunday("abbcfdddbddcaddebc", "aaaaa")) +print(sunday("abbcfdddbddcaddebc", "bcf")) +``` + +## 4. Sunday 算法分析 + +- Sunday 算法在平均情况下的时间复杂度为 $O(n)$,但是在最坏情况下时间复杂度会退化为 $O(n * m)$。 + +## 参考资料 + +- 【书籍】柔性字符串匹配 - 中科院计算所网络信息安全研究组 译 +- 【博文】[字符串模式匹配算法:BM、Horspool、Sunday、KMP、KR、AC算法 - schips - 博客园](https://www.cnblogs.com/schips/p/11098041.html) +- 【博文】[字符串匹配——Sunday 算法 - Switch 的博客 - CSDN 博客](https://blog.csdn.net/q547550831/article/details/51860017) + +## 练习题目 + +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) + +- [单模式串匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%A8%A1%E5%BC%8F%E4%B8%B2%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/04_string/04_09_string_multi_pattern_matching.md b/docs/04_string/04_09_string_multi_pattern_matching.md new file mode 100644 index 00000000..943a9cf6 --- /dev/null +++ b/docs/04_string/04_09_string_multi_pattern_matching.md @@ -0,0 +1,62 @@ +# 多模式串匹配 + +## 1. 多模式串匹配的定义 + +多模式串匹配(Multiple Pattern Matching)是指在文本串中同时查找多个模式串的问题。与单模式串匹配(如 KMP 算法)不同,多模式串匹配需要在一个文本串中同时查找多个模式串的所有出现位置。 + +## 2. 主要算法介绍 + +### 2.1 字典树(Trie) + +字典树是一种树形数据结构,用于高效地存储和检索字符串集合。它的主要特点是: + +- 每个节点代表一个字符 +- 从根节点到某个节点的路径上的字符连接起来,就是该节点对应的字符串 +- 每个节点的所有子节点包含的字符都不相同 + +字典树的主要应用: +- 字符串检索 +- 词频统计 +- 字符串排序 +- 前缀匹配 + +### 2.2 AC 自动机(Aho-Corasick) + +AC 自动机是在字典树的基础上,结合了 KMP 算法的思想,用于多模式串匹配的算法。它的主要特点是: + +- 基于字典树构建 +- 添加了失败指针(fail pointer),类似于 KMP 的 next 数组 +- 可以同时匹配多个模式串 +- 时间复杂度为 O(n + k),其中 n 是文本串长度,k 是所有模式串的总长度 + +AC 自动机的主要应用: +- 敏感词过滤 +- 病毒特征码匹配 +- 文本分析 + +### 2.3 后缀数组(Suffix Array) + +后缀数组是一种数据结构,用于高效地处理字符串的后缀。它的主要特点是: + +- 将字符串的所有后缀按字典序排序 +- 可以快速查找子串 +- 支持最长公共前缀(LCP)查询 + +后缀数组的主要应用: +- 字符串匹配 +- 最长重复子串 +- 最长公共子串 +- 字符串压缩 + +## 3. 算法比较 + +| 算法 | 预处理时间复杂度 | 匹配时间复杂度 | 空间复杂度 | 适用场景 | +|------|----------------|--------------|------------|----------| +| 字典树 | O(k) | O(m) | O(k) | 前缀匹配、字符串集合操作 | +| AC 自动机 | O(k) | O(n + k) | O(k) | 多模式串精确匹配 | +| 后缀数组 | O(n log n) | O(m + log n) | O(n) | 子串匹配、后缀操作 | + +其中: +- n 为文本串长度 +- m 为模式串长度 +- k 为所有模式串的总长度 diff --git a/docs/04_string/04_10_trie.md b/docs/04_string/04_10_trie.md new file mode 100644 index 00000000..9ef8e618 --- /dev/null +++ b/docs/04_string/04_10_trie.md @@ -0,0 +1,232 @@ +## 1. 字典树简介 + +> **字典树(Trie)**:又称为前缀树、单词查找树,是一种树形结构。顾名思义,就是一个像字典一样的树。它是字典的一种存储方式。字典中的每个单词在字典树中表现为一条从根节点出发的路径,路径相连的边上的字母连起来就形成对应的字符串。 + +例如下图就是一棵字典树,其中包含有 `"a"`、`"abc"`、`"acb"`、`"acc"`、`"ach"`、`"b"`、`"chb"` 这 7 个单词。 + +![字典树](https://qcdn.itcharge.cn/images/20240511165918.png) + +从图中可以发现,这棵字典树用边来表示字母,从根节点到树上某一节点的路径就代表了一个单词。比如 $1 \rightarrow 2 \rightarrow 6 \rightarrow 10$ 表示的就是单词 `"acc"`。为了清楚地判断某节点路径是否表示一个单词,我们还可以在每个单词对应路径的结束位置增加一个结束标记 $end$(图中红色节点),表示从根节点到这里有一个单词。 + +字典树的结构比较简单,其本质上就是一个用于字符串快速检索的多叉树,树上每个节点都包含多字符指针。将从根节点到某一节点路径上经过的字符连接起来,就是该节点对应的字符串。 + +**字典树设计的核心思想 **:利用空间换时间,利用字符串的公共前缀来降低查询时间的开销,最大限度的减少无谓的字符串比较,以达到提高效率的目的。 + +下面我们来归纳一下 **字典树的基本性质**: + +- 根节点不包含字符,除根节点外,每个节点都只包含一个字符。 +- 从根节点到某一节点,路径航经过的字符串连接起来,就是该节点对应的字符串。 +- 每个节点的所有子节点包含的字符串都不相同。 + +## 2. 字典树的基本操作 + +字典树的基本操作有 **创建**、**插入**、**查找** 和 **删除**。其中删除操作是最不常用,我们这里主要介绍字典树的创建、插入和查找。 + +### 2.1 字典树的结构 + +#### 2.1.1 字典树的节点结构 + +首先我们先来定义一下字典树的节点结构。 + +上面说到字典树是一棵多叉树,这个 **「多叉」** 的意思是一个节点可以有多个子节点。而多叉的实现方式可以使用数组实现,也可以使用哈希表实现。接下来我们来介绍一下这两种节点结构。 + +- 如果字符串所涉及的字符集合只包含小写英文字母的话,我们可以使用一个长度为 $26$ 的数组来表示当前节点的多个子节点,如下面代码所示。 + +```python +class Node: # 字符节点 + def __init__(self): # 初始化字符节点 + self.children = [None for _ in range(26)] # 初始化子节点 + self.isEnd = False # isEnd 用于标记单词结束 +``` + +代码中,$self.children$ 使用数组实现,表示该节点的所有子节点。$isEnd$ 则用于标记单词是否结束。 + +这样,如果我们在插入单词时,需要先将单词中的字符转换为数字,再创建对应的字符节点,并将其映射到长度为 $26$ 数组中。 + +- 如果所涉及的字符集合不仅包含小写字母,还包含大写字母和其他字符,我们可以使用哈希表来表示当前节点的多个子节点,如下面代码所示。 + +```python +class Node: # 字符节点 + def __init__(self): # 初始化字符节点 + self.children = dict() # 初始化子节点 + self.isEnd = False # isEnd 用于标记单词结束 +``` + +代码中,$self.children$ 使用哈希表实现,表示该节点的所有子节点。$isEnd$ 则用于标记单词是否结束。这样,如果我们在插入单词时,直接根据单词中的字符创建对应的字符节点,并将其插入到对应的哈希表中。 + +下面为了统一代码和编写方便,本文代码全部以哈希表的形式来表示当前节点的多个子节点。 + +#### 2.1.2 字典树的基本结构 + +定义完了字典树的字符结构,下面我们定义下字典树的基本结构。在字典树的初始化操作时,定义一个根节点。并且这个根节点不用保存字符。在后续进行插入操作、查找操作都是从字典树的根节点开始的。字典树的基本结构代码如下。 + +```python +class Trie: # 字典树 + + # 初始化字典树 + def __init__(self): # 初始化字典树 + self.root = Node() # 初始化根节点(根节点不保存字符) +``` + +### 2.2 字典树的创建和插入操作 + +字典树的创建指的是将字符串数组中的所有字符串都插⼊字典树中。而插⼊操作指的是将⼀个字符串插⼊字典树中。 + +#### 2.2.1 字典树的插入操作 + +在讲解字典树的创建之前,我们先来看一下如何在字典树中插入一个单词。具体步骤如下: + +- 依次遍历单词中的字符 $ch$,并从字典树的根节点的子节点位置开始进行插入操作(根节点不包含字符)。 +- 如果当前节点的子节点中,不存在键为 $ch$ 的节点,则建立一个节点,并将其保存到当前节点的子节点中,即 `cur.children[ch] = Node()`,然后令当前节点指向新建立的节点,然后继续处理下一个字符。 +- 如果当前节点的子节点中,存在键为 $ch$ 的节点,则直接令当前节点指向键为 $ch$ 的节点,继续处理下一个字符。 +- 在单词处理完成时,将当前节点标记为单词结束。 + +```python +# 向字典树中插入一个单词 +def insert(self, word: str) -> None: + cur = self.root + for ch in word: # 遍历单词中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + cur.children[ch] = Node() # 建立一个节点,并将其保存到当前节点的子节点 + cur = cur.children[ch] # 令当前节点指向新建立的节点,继续处理下一个字符 + cur.isEnd = True # 单词处理完成时,将当前节点标记为单词结束 +``` + +#### 2.2.2 字典树的创建操作 + +字典树的创建比较简单,具体步骤如下: + +- 首先初始化一个字典树,即 `trie = Trie()`。 +- 然后依次遍历字符串中的所有单词,将其一一插入到字典树中。 + +```python +trie = Trie() +for word in words: + trie.insert(word) +``` + +### 2.3 字典树的查找操作 + +#### 2.3.1 字典树的查找单词操作 + +在字典树中查找某个单词是否存在,其实和字典树的插入操作差不多。具体操作如下: + +- 依次遍历单词中的字符,并从字典树的根节点位置开始进行查找操作。 +- 如果当前节点的子节点中,不存在键为 $ch$ 的节点,则说明不存在该单词,直接返回 $False$。 +- 如果当前节点的子节点中,存在键为 $ch$ 的节点,则令当前节点指向新建立的节点,然后继续查找下一个字符。 +- 在单词处理完成时,判断当前节点是否有单词结束标记,如果有,则说明字典树中存在该单词,返回 $True$。否则,则说明字典树中不存在该单词,返回 $False$。 + +```python +# 查找字典树中是否存在一个单词 +def search(self, word: str) -> bool: + cur = self.root + for ch in word: # 遍历单词中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + return False # 直接返回 False + cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 + + return cur.isEnd # 判断是否有单词结束标记 +``` + +#### 2.3.2 字典树的查找前缀操作 + +在字典树中查找某个前缀是否存在,和字典树的查找单词操作一样,不同点在于最后不需要判断是否有单词结束标记。 + +```python +# 查找字典树中是否存在一个前缀 +def startsWith(self, prefix: str) -> bool: + cur = self.root + for ch in prefix: # 遍历前缀中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + return False # 直接返回 False + cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 + return True # 查找成功 +``` + +## 3. 字典树的实现代码 + +```python +class Node: # 字符节点 + def __init__(self): # 初始化字符节点 + self.children = dict() # 初始化子节点 + self.isEnd = False # isEnd 用于标记单词结束 + + +class Trie: # 字典树 + + # 初始化字典树 + def __init__(self): # 初始化字典树 + self.root = Node() # 初始化根节点(根节点不保存字符) + + # 向字典树中插入一个单词 + def insert(self, word: str) -> None: + cur = self.root + for ch in word: # 遍历单词中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + cur.children[ch] = Node() # 建立一个节点,并将其保存到当前节点的子节点 + cur = cur.children[ch] # 令当前节点指向新建立的节点,继续处理下一个字符 + cur.isEnd = True # 单词处理完成时,将当前节点标记为单词结束 + + # 查找字典树中是否存在一个单词 + def search(self, word: str) -> bool: + cur = self.root + for ch in word: # 遍历单词中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + return False # 直接返回 False + cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 + + return cur.isEnd # 判断是否有单词结束标记 + + # 查找字典树中是否存在一个前缀 + def startsWith(self, prefix: str) -> bool: + cur = self.root + for ch in prefix: # 遍历前缀中的字符 + if ch not in cur.children: # 如果当前节点的子节点中,不存在键为 ch 的节点 + return False # 直接返回 False + cur = cur.children[ch] # 令当前节点指向新建立的节点,然后继续查找下一个字符 + return True # 查找成功 +``` + +## 4. 字典树的算法分析 + +假设单词的长度为 $n$,前缀的长度为 $m$,字符集合的维度为 $d$,则: + +- **插入一个单词**:时间复杂度为 $O(n)$;如果使用数组,则空间复杂度为 $O(d^n)$,如果使用哈希表实现,则空间复杂度为 $O(n)$。 +- **查找一个单词**:时间复杂度为 $O(n)$;空间复杂度为 $O(1)$。 +- **查找一个前缀**:时间复杂度为 $O(m)$;空间复杂度为 $O(1)$。 + +## 5. 字典树的应用 + +字典树一个典型的应用场景就是:在搜索引擎中输入部分内容之后,搜索引擎就会自动弹出一些关联的相关搜索内容。我们可以从中直接选择自己想要搜索的内容,而不用将所有内容都输入进去。这个功能从一定程度上节省了我们的搜索时间。 + +例如下图,当我们输入「字典树」后,底下会出现一些以「字典树」为前缀的相关搜索内容。 + +![字典树的应用](https://qcdn.itcharge.cn/images/20220210134829.png) + +这个功能实现的基本原理就是字典树。当然,像 Google、必应、百度这样的搜索引擎,在这个功能能的背后肯定做了大量的改进和优化,但它的底层最基本的原理就是「字典树」这种数据结构。 + +除此之外,我们可以把字典树的应用分为以下几种: + +- **字符串检索**:事先将已知的⼀些字符串(字典)的有关信息存储到字典树⾥, 查找⼀些字符串是否出现过、出现的频率。 +- **前缀统计**:统计⼀个串所有前缀单词的个数,只需统计从根节点到叶子节点路径上单词出现的个数,也可以判断⼀个单词是否为另⼀个单词的前缀。 +- **最长公共前缀问题**:利用字典树求解多个字符串的最长公共前缀问题。将⼤量字符串都存储到⼀棵字典树上时, 可以快速得到某些字符串的公共前缀。对所有字符串都建⽴字典树,两个串的最长公共前缀的长度就是它们所在节点最近公共祖先的长度,于是转变为最近公共祖先问题。 +- **字符串排序**:利⽤字典树进⾏串排序。例如,给定多个互不相同的仅由⼀个单词构成的英⽂名,将它们按字典序从⼩到⼤输出。采⽤数组⽅式创建字典树,字典树中每个节点的所有⼦节点都是按照其字母⼤⼩排序的。然后对字典树进⾏先序遍历,输出的相应字符串就是按字典序排序的结果。 + +## 练习题目 + +- [0208. 实现 Trie (前缀树)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-trie-prefix-tree.md) +- [0677. 键值映射](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/map-sum-pairs.md) +- [1023. 驼峰式匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/camelcase-matching.md) +- [0211. 添加与搜索单词 - 数据结构设计](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md) +- [0648. 单词替换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/replace-words.md) +- [0676. 实现一个魔法字典](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/implement-magic-dictionary.md) + +- [字典树题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%AD%97%E5%85%B8%E6%A0%91%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】算法训练营 陈小玉 著 +- 【书籍】ACM-ICPC 程序设计系列 算法设计与实现 陈宇 吴昊 主编 +- 【博文】[Trie 树 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/72414) +- 【博文】[一文搞懂字典树](https://segmentfault.com/a/1190000040801084) +- 【博文】[字典树 (Trie) - OI Wiki](https://oi-wiki.org/string/trie/) diff --git a/docs/04_string/04_11_ac_automaton.md b/docs/04_string/04_11_ac_automaton.md new file mode 100644 index 00000000..fd2d3550 --- /dev/null +++ b/docs/04_string/04_11_ac_automaton.md @@ -0,0 +1,136 @@ +## 1. AC 自动机简介 + +> **AC 自动机(Aho-Corasick Automaton)**:该算法在 1975 年产生于贝尔实验室,是最著名的多模式匹配算法之一。简单来说,AC 自动机是以 **字典树(Trie)** 的结构为基础,结合 **KMP 算法思想** 建立的。 + +AC 自动机的构造有 3 个步骤: + +1. 构造一棵字典树(Trie),作为 AC 自动机的搜索数据结构。 +2. 利用 KMP 算法思想,构造失配指针。使得当前字符失配时可以通过失配指针跳转到具有最长公共前后缀的字符位置上继续匹配。 +3. 扫描文本串进行匹配。 + +## 2. AC 自动机原理 + +接下来我们以一个例子来说明一下 AC 自动机的原理。 + +> 描述:给定 5 个单词,分别是 `say`、`she`、`shr`、`he`、`her`,再给定一个文本串 `yasherhs`。 +> +> 要求:计算出有多少个单词在文本串中出现过。 + +### 2.1 构造一棵字典树(Trie) + +首先我们需要建立一棵字典树。字典树是一种树形数据结构,用于高效地存储和检索字符串集合。每个节点代表一个字符,从根节点到某个节点的路径上的字符连接起来,就是该节点对应的字符串。 + +对于给定的 5 个单词,构造的字典树如下: + +``` + root + / \ + s h + / \ | + a h e + / / \ \ + y e r r +``` + +### 2.2 构造失配指针 + +失配指针(fail pointer)是 AC 自动机的核心。当在字典树中匹配失败时,失配指针指向另一个节点,该节点对应的字符串是当前节点对应字符串的最长后缀。 + +失配指针的构造过程: +1. 根节点的失配指针指向空 +2. 对于每个节点,其失配指针指向其父节点的失配指针指向的节点的对应子节点 +3. 如果对应子节点不存在,则继续沿着失配指针向上查找 + +### 2.3 扫描文本串 + +扫描文本串的过程: +1. 从根节点开始,按照文本串的字符顺序在字典树中移动 +2. 如果当前字符匹配成功,继续移动到下一个字符 +3. 如果当前字符匹配失败,通过失配指针跳转到另一个节点继续匹配 +4. 当到达某个单词的结束节点时,说明找到了一个匹配的单词 + +## 3. AC 自动机的应用 + +AC 自动机在以下场景中有着广泛的应用: + +1. **多模式字符串匹配**:在文本中查找多个模式串 +2. **敏感词过滤**:检测文本中是否包含敏感词 +3. **DNA序列分析**:在生物信息学中用于DNA序列的模式匹配 +4. **网络入侵检测**:检测网络数据包中的恶意模式 +5. **拼写检查**:检查文本中的拼写错误 + +## 4. AC 自动机的实现 + +### 4.1 时间复杂度 + +- 构建字典树:O(Σ|P|),其中 P 是所有模式串的集合 +- 构建失配指针:O(Σ|P|) +- 文本串匹配:O(n + k),其中 n 是文本串长度,k 是匹配的模式串数量 + +### 4.2 空间复杂度 + +- O(Σ|P|),其中 Σ 是字符集大小 + +## 5. 代码实现 + +```python +class TrieNode: + def __init__(self): + self.children = {} # 子节点 + self.fail = None # 失配指针 + self.is_end = False # 是否是单词结尾 + self.word = "" # 存储完整的单词 + +class AC_Automaton: + def __init__(self): + self.root = TrieNode() + + def add_word(self, word): + node = self.root + for char in word: + if char not in node.children: + node.children[char] = TrieNode() + node = node.children[char] + node.is_end = True + node.word = word + + def build_fail_pointers(self): + queue = [] + # 将根节点的子节点的失配指针指向根节点 + for char, node in self.root.children.items(): + node.fail = self.root + queue.append(node) + + # 广度优先搜索构建失配指针 + while queue: + current = queue.pop(0) + for char, child in current.children.items(): + fail = current.fail + while fail and char not in fail.children: + fail = fail.fail + child.fail = fail.children[char] if fail else self.root + queue.append(child) + + def search(self, text): + result = [] + current = self.root + + for char in text: + while current is not self.root and char not in current.children: + current = current.fail + if char in current.children: + current = current.children[char] + + # 检查当前节点是否是某个单词的结尾 + temp = current + while temp is not self.root: + if temp.is_end: + result.append(temp.word) + temp = temp.fail + + return result +``` + +## 6. 总结 + +AC 自动机是一种高效的多模式匹配算法,它通过结合字典树和 KMP 算法的思想,实现了在文本串中快速查找多个模式串的功能。虽然其实现相对复杂,但在需要多模式匹配的场景下,AC 自动机提供了最优的时间复杂度。 \ No newline at end of file diff --git a/docs/04_string/04_12_suffix_array.md b/docs/04_string/04_12_suffix_array.md new file mode 100644 index 00000000..71b35211 --- /dev/null +++ b/docs/04_string/04_12_suffix_array.md @@ -0,0 +1,136 @@ +## 1. 后缀数组简介 + +> **后缀数组(Suffix Array)**:是一种高效处理字符串后缀相关问题的数据结构。它将字符串的所有后缀按字典序排序,并记录每个后缀在原串中的起始位置,便于实现高效的子串查找、最长重复子串、最长公共子串等操作。 + +后缀数组常与 **LCP(Longest Common Prefix)数组** 配合使用,进一步提升字符串处理的效率。 + +--- + +## 2. 基本原理与定义 + +给定一个长度为 $n$ 的字符串 $S$,其后缀数组 $SA$ 是一个长度为 $n$ 的整数数组,$SA[i]$ 表示 $S$ 的第 $i$ 小后缀在原串中的起始下标。 + +例如: + +> $S = "banana"$ +> +> $S$ 的所有后缀及其下标: +> - 0: banana +> - 1: anana +> - 2: nana +> - 3: ana +> - 4: na +> - 5: a +> +> 按字典序排序后: +> 1. a (5) +> 2. ana (3) +> 3. anana (1) +> 4. banana (0) +> 5. na (4) +> 6. nana (2) +> +> 所以 $SA = [5, 3, 1, 0, 4, 2]$ + +--- + +## 3. 后缀数组的构建方法 + +后缀数组的构建有多种方法,常见的有: + +- **朴素排序法**:直接生成所有后缀并排序,时间复杂度 $O(n^2 \log n)$,适合短串。 +- **倍增算法**:利用基数排序思想,时间复杂度 $O(n \log n)$。 +- **DC3/Skew 算法**:线性时间 $O(n)$ 构建,适合大数据量。 + +### 3.1 朴素法(适合理解原理) + +```python +# 朴素法构建后缀数组 +S = "banana" +suffixes = [(S[i:], i) for i in range(len(S))] +suffixes.sort() +SA = [idx for (suf, idx) in suffixes] +print(SA) # 输出: [5, 3, 1, 0, 4, 2] +``` + +### 3.2 倍增算法(常用高效实现) + +```python +def build_suffix_array(s): + n = len(s) + k = 1 + rank = [ord(c) for c in s] + tmp = [0] * n + sa = list(range(n)) + while True: + sa.sort(key=lambda x: (rank[x], rank[x + k] if x + k < n else -1)) + tmp[sa[0]] = 0 + for i in range(1, n): + tmp[sa[i]] = tmp[sa[i-1]] + \ + ((rank[sa[i]] != rank[sa[i-1]]) or + (rank[sa[i]+k] if sa[i]+k < n else -1) != (rank[sa[i-1]+k] if sa[i-1]+k < n else -1)) + rank = tmp[:] + if rank[sa[-1]] == n-1: + break + k <<= 1 + return sa + +# 示例 +S = "banana" +print(build_suffix_array(S)) # 输出: [5, 3, 1, 0, 4, 2] +``` + +--- + +## 4. LCP(最长公共前缀)数组 + +> **LCP 数组**:LCP[i] 表示 $SA[i]$ 和 $SA[i-1]$ 所指向的两个后缀的最长公共前缀长度。 + +LCP 数组常用于: +- 快速查找最长重复子串 +- 计算不同子串个数 +- 字符串压缩等 + +### LCP 数组的构建 + +```python +def build_lcp(s, sa): + n = len(s) + rank = [0] * n + for i in range(n): + rank[sa[i]] = i + h = 0 + lcp = [0] * n + for i in range(n): + if rank[i] == 0: + lcp[0] = 0 + else: + j = sa[rank[i] - 1] + while i + h < n and j + h < n and s[i + h] == s[j + h]: + h += 1 + lcp[rank[i]] = h + if h > 0: + h -= 1 + return lcp + +# 示例 +S = "banana" +SA = build_suffix_array(S) +LCP = build_lcp(S, SA) +print(LCP) # 输出: [0, 1, 3, 0, 0, 2] +``` + +## 5. 算法复杂度分析 + +- **朴素法**:$O(n^2 \log n)$ +- **倍增法**:$O(n \log n)$ +- **DC3/Skew**:$O(n)$ +- **LCP 构建**:$O(n)$ + +## 参考资料 + +- 《算法竞赛进阶指南》—— 胡策 +- 《算法竞赛入门经典》—— 刘汝佳 +- [OI Wiki - 后缀数组](https://oi-wiki.org/string/sa/) + + diff --git a/docs/04_string/index.md b/docs/04_string/index.md new file mode 100644 index 00000000..831e5155 --- /dev/null +++ b/docs/04_string/index.md @@ -0,0 +1,14 @@ +## 本章内容 + +- [4.1 字符串基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_01_string_basic.md) +- [4.2 单模式串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_02_string_single_pattern_matching.md) +- [4.3 Brute Force 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_03_string_brute_force.md) +- [4.4 Rabin Karp 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_04_string_rabin_karp.md) +- [4.5 KMP 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_05_string_kmp.md) +- [4.6 Boyer Moore 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_06_string_boyer_moore.md) +- [4.7 Horspool 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_07_string_horspool.md) +- [4.8 Sunday 算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_08_string_sunday.md) +- [4.9 多模式串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_09_string_multi_pattern_matching.md) +- [4.10 字典树](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_10_trie.md) +- [4.11 AC 自动机](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_11_ac_automaton.md) +- [4.12 后缀数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/04_string/04_12_suffix_array.md) diff --git a/docs/05_tree/05_01_tree_basic.md b/docs/05_tree/05_01_tree_basic.md new file mode 100644 index 00000000..25235fc8 --- /dev/null +++ b/docs/05_tree/05_01_tree_basic.md @@ -0,0 +1,205 @@ +## 1. 树简介 + +### 1.1 树的定义 + +> **树(Tree)**:由 $n \ge 0$ 个节点与节点之间的关系组成的有限集合。当 $n = 0$ 时称为空树,当 $n > 0$ 时称为非空树。 + +之所以把这种数据结构称为「树」是因为这种数据结构看起来就像是一棵倒挂的树,也就是说数据结构中的「树」是根朝上,而叶朝下的。如下图所示。 + +![树](https://qcdn.itcharge.cn/images/20240511171215.png) + +「树」具有以下的特点: + +- 有且仅有一个节点没有前驱节点,该节点被称为树的 **「根节点(Root)」** 。 +- 除了根节点以外,每个节点有且仅有一个直接前驱节点。 +- 包括根节点在内,每个节点可以有多个后继节点。 +- 当 $n > 1$ 时,除了根节点之外的其他节点,可分为 $m(m > 0)$ 个互不相交的有限集合 $T_1, T_2, ..., T_m$,其中每一个集合本身又是一棵树,并且被称为根的 **「子树(SubTree)」**。 + +如下图所示,红色节点 $A$ 是根节点,除了根节点之外,还有 $3$ 棵互不相交的子树 $T_1(B, E, H, I, G)$、$T_2(C)$、$T_3(D, F, G, K)$。 + +![树与子树](https://qcdn.itcharge.cn/images/20240511171233.png) + +### 1.2 树的相关术语 + +下面我们来介绍一下树结构中的一些基本术语。 + +#### 1.2.1 节点分类 + +**「树的节点」** 由一个数据元素和若干个指向其子树的树的分支组成。而节点所含有的子树个数称为 **「节点的度」**。度为 $0$ 的节点称为 **「叶子节点」** 或者 **「终端节点」**,度不为 $0$ 的节点称为 **「分支节点」** 或者 **「非终端节点」**。树中各节点的最大度数称为 **「树的度」**。 + +![节点分类](https://qcdn.itcharge.cn/images/20240511171300.png) + +- **树的节点**:由一个数据元素和若干个指向其子树的树的分支组成。 +- **节点的度**:一个节点所含有的子树个数。 +- **叶子节点(终端节点)**:度为 $0$ 的节点。例如图中叶子节点为 $C$、$H$、$I$、$G$、$F$、$K$。 +- **分支节点(非终端节点)**:度不为 $0$ 的节点。例如图中分支节点为 $A$、$B$、$D$、$E$、$G$。 +- **树的度**:树中节点的最大度数。例如图中树的度为 $3$。 + +#### 1.2.2 节点间关系 + +一个节点的子树的根节点称为该节点的 **「孩子节点」**,相应的,该节点称为孩子的 **「父亲节点」**。同一个父亲节点的孩子节点之间互称为 **「兄弟节点」**。 + +![节点间关系](https://qcdn.itcharge.cn/images/20240511171311.png) + +- **孩子节点(子节点)**:一个节点含有的子树的根节点称为该节点的子节点。例如图中 $B$ 是 $A$ 的孩子节点。 +- **父亲节点(父节点)**:如果一个节点含有子节点,则这个节点称为其子节点的父节点。例如图中 $B$ 是 $E$ 的父亲节点。 +- **兄弟节点**:具有相同父节点的节点互称为兄弟节点。例如图中 $F$、$G$ 互为兄弟节点。 + +#### 1.2.3 树的其他术语 + +**「节点的层次」** 是从根节点开始定义,将根节点作为第 1 层,根的孩子节点作为第 2 层,以此类推,如果某个节点在第 $i$ 层,则其孩子节点在第 $i + 1$ 层。而父亲节点在同一层的节点互为 **「堂兄弟节点」**。树中所有节点最大的层数称为 **「树的深度」** 或 **「树的高度」**。树中,两个节点之间所经过节点序列称为 **「路径」**,两个节点之间路径上经过的边数称为 **「路径长度」**。 + +![树的其他术语](https://qcdn.itcharge.cn/images/20240511171325.png) + +- **节点的层次**:从根节点开始定义,根为第 $1$ 层,根的子节点为第 $2$ 层,以此类推。 +- **树的深度(高度)**:所有节点中最大的层数。例如图中树的深度为 $4$。 +- **堂兄弟节点**:父节点在同一层的节点互为堂兄弟。例如图中 $J$、$K$ 互为堂兄弟节点。 +- **路径**:树中两个节点之间所经过的节点序列。例如图中 $E$ 到 $G$ 的路径为 $E - B - A - D - G$。 +- **路径长度**:两个节点之间路径上经过的边数。例如图中 $E$ 到 $G$ 的路径长度为 $4$。 +- **节点的祖先**:从该节点到根节点所经过的所有节点,被称为该节点的祖先。例如图中 $H$ 的祖先为 $E$、$B$、$A$。 +- **节点的子孙**:节点的子树中所有节点被称为该节点的子孙。例如图中 $D$ 的子孙为 $F$、$G$、$K$。 + +### 1.3 树的分类 + +根据节点的子树是否可以互换位置,我们可以将树分为两种类型:**「有序树」** 和 **「无序树」**。 + +如果将树中节点的各个子树看做是从左到右是依次有序的(即不能互换),则称该树为 **「有序树」**。反之,如果节点的各个子树可以互换位置,则成该树为 **「无序树」**。 + +- **有序树**:节点的各个⼦树从左⾄右有序, 不能互换位置。 +- **无序树**:节点的各个⼦树可互换位置。 + +## 2. 二叉树简介 + +### 2.1 二叉树的定义 + +> **二叉树(Binary Tree)**:树中各个节点的度不大于 $2$ 个的有序树,称为二叉树。通常树中的分支节点被称为 **「左子树」** 或 **「右子树」**。二叉树的分支具有左右次序,不能随意互换位置。 + +下图就是一棵二叉树。 + +![二叉树](https://qcdn.itcharge.cn/images/20240511171342.png) + +二叉树也可以使用递归方式来定义,即二叉树满足以下两个要求之一: + +- **空树**:二叉树是一棵空树。 +- **非空树**:二叉树是由一个根节点和两棵互不相交的子树 $T_1$、$T_2$,分别称为根节点的左子树、右子树组成的非空树;并且 $T_1$、$T_2$ 本身都是二叉树。 + +⼆叉树是种特殊的树,它最多有两个⼦树,分别为左⼦树和右⼦树,并且两个子树是有序的,不可以互换。也就是说,在⼆叉树中不存在度⼤于 $2$ 的节点。 + +二叉树在逻辑上可以分为 $5$ 种基本形态,如下图所示。 + +![二叉树的形态](https://qcdn.itcharge.cn/images/20220218164839.png) + +### 2.2 特殊的二叉树 + +下面我们来介绍一些特殊的二叉树。 + +#### 2.2.1 满二叉树 + +> **满二叉树(Full Binary Tree)**:如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,则称该二叉树为满二叉树。 + +满二叉树满足以下特点: + +- 叶子节点只出现在最下面一层。 +- 非叶子节点的度一定为 $2$。 +- 在同等深度的二叉树中,满二叉树的节点个数最多,叶子节点个数最多。 + +如果我们对满二叉树的节点进行编号,根节点编号为 $1$,然后按照层次依次向下,每一层从左至右的顺序进行编号。则深度为 $k$ 的满二叉树最后一个节点的编号为 $2^k - 1$。 + +我们可以来看几个例子。 + +![满二叉树与非满二叉树](https://qcdn.itcharge.cn/images/20220218173007.png) + +#### 2.2.2 完全二叉树 + +> **完全二叉树(Complete Binary Tree)**:如果叶子节点只能出现在最下面两层,并且最下层的叶子节点都依次排列在该层最左边的位置上,具有这种特点的二叉树称为完全二叉树。 + +完全二叉树满足以下特点: + +- 叶子节点只能出现在最下面两层。 +- 最下层的叶子节点一定集中在该层最左边的位置上。 +- 倒数第二层如果有叶子节点,则该层的叶子节点一定集中在右边的位置上。 +- 如果节点的度为 $1$,则该节点只有左孩子节点,即不存在只有右孩子节点的情况。 +- 同等节点数的二叉树中,完全二叉树的深度最小。 + +完全二叉树也可以使用类似满二叉树的节点编号的方式来定义。即从根节点编号为 $1$ 开始,按照层次从上至下,每一层从左至右进行编号。对于深度为 $i$ 且有 $n$ 个节点的二叉树,当且仅当每一个节点都与深度为 $k$ 的满二叉树中编号从 $1$ 至 $n$ 的节点意义对应时,该二叉树为完全二叉树。 + +我们可以来看几个例子。 + +![完全二叉树与非完全二叉树](https://qcdn.itcharge.cn/images/20220218174000.png) + +#### 2.2.3 二叉搜索树 + +> **二叉搜索树(Binary Search Tree)**:也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树: +> +> - 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。 +> - 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。 +> - 任意节点的左子树、右子树均为二叉搜索树。 + +如图所示,这 $3$ 棵树都是二叉搜索树。 + +![二叉搜索树](https://qcdn.itcharge.cn/images/20240511171406.png) + +#### 2.2.4 平衡二叉搜索树 + +> **平衡二叉搜索树(Balanced Binary Tree)**:一种结构平衡的二叉搜索树。即叶节点高度差的绝对值不超过 $1$,并且左右两个子树都是一棵平衡二叉搜索树。平衡二叉树可以在 $O(logn)$ 内完成插入、查找和删除操作。最早被发明的平衡二叉搜索树为 **「AVL 树(Adelson-Velsky and Landis Tree))」**。 +> +> AVL 树满足以下性质: +> +> - 空二叉树是一棵 AVL 树。 +> - 如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且 $|h(ls) - h(rs)| \le 1$,$h(ls)$ 是左子树的高度,$h(rs)$ 是右子树的高度。 +> - AVL 树的高度为 $O(log n)$。 + +如图所示,前 $2$ 棵树是平衡二叉搜索树,最后一棵树不是平衡二叉搜索树,因为这棵树的左右子树的高度差的绝对值超过了 $1$。 + +![平衡二叉树与非平衡二叉树](https://qcdn.itcharge.cn/images/20220221103552.png) + +### 2.3 二叉树的存储结构 + +二叉树的存储结构分为两种:「顺序存储结构」和「链式存储结构」,下面进行一一讲解。 + +#### 2.3.1 二叉树的顺序存储结构 + +其实,堆排序、优先队列中的二叉堆结构,采用的就是二叉树的顺序存储结构。 + +二叉树的顺序存储结构使用一维数组来存储二叉树中的节点,节点存储位置则采用完全二叉树的节点层次编号,按照层次从上至下,每一层从左至右的顺序依次存放二叉树的数据元素。在进行顺序存储时,如果对应的二叉树节点不存在,则设置为「空节点」。 + +下图为二叉树的顺序存储结构。 + +![二叉树的顺序存储结构](https://qcdn.itcharge.cn/images/20240511171423.png) + +从图中我们也可以看出节点之间的逻辑关系。 + +- 如果某二叉树节点(非叶子节点)的下标为 $i$,那么其左孩子节点下标为 $2 * i + 1$,右孩子节点下标为 $2 * i + 2$。 +- 如果某二叉树节点(非根节点)的下标为 $i$,那么其根节点下标为 $(i - 1) // 2$。$//$ 表示整除。 + +对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构比较合适,它能充分利用存储空间;而对于一般二叉树,如果需要设置很多的「空节点」,则采用顺序存储结构就会浪费很多存储空间。并且,由于顺序存储结构固有的一些缺陷,会使得二叉树的插入、删除等操作不方便,效率也比较低。对于二叉树来说,当树的形态和大小经常发生动态变化时,更适合采用链式存储结构。 + +#### 2.3.2 二叉树的链式存储结构 + +二叉树采用链式存储结构时,每个链节点包含一个用于数据域 $val$,存储节点信息;还包含两个指针域 $left$ 和 $right$,分别指向左右两个孩子节点,当左孩子或者右孩子不存在时,相应指针域值为空。二叉链节点结构如下图所示。 + +![二叉链节点](https://qcdn.itcharge.cn/images/20240511171434.png) + +二叉链节点结构的对应代码为: + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right +``` + +下面我们将值为 $[1, 2, 3, 4, 5, 6, 7]$ 的二叉树使用链式存储结构进行存储,即为下图所示。 + +![二叉树的链式存储结构](https://qcdn.itcharge.cn/images/20240511171446.png) + +二叉树的链表存储结构具有灵活、方便的特点。节点的最大数目只受系统最大可存储空间的限制。一般情况下,二叉树的链表存储结构比顺序存储结构更省空间(用于存储指针域的空间开销只是二叉树中节点数的线性函数),而且对于二叉树实施相关操作也很方便,因此,一般我们使用链式存储结构来存储二叉树。 + +## 参考链接 + +- 【书籍】数据结构教程 第 3 版 - 唐发根 著 +- 【书籍】大话数据结构 程杰 著 +- 【书籍】算法训练营 陈小玉 著 +- 【博文】[二叉树理论基础 - 代码随想录](https://programmercarl.com/二叉树理论基础.html) +- 【博文】[二叉树基础 - 袁厨的算法小屋](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/二叉树/二叉树基础.md) diff --git a/docs/05_tree/05_02_binary_tree_traverse.md b/docs/05_tree/05_02_binary_tree_traverse.md new file mode 100644 index 00000000..b22704a2 --- /dev/null +++ b/docs/05_tree/05_02_binary_tree_traverse.md @@ -0,0 +1,328 @@ +## 1. 二叉树的遍历简介 + +> **二叉树的遍历**:指的是从根节点出发,按照某种次序依次访问二叉树中所有节点,使得每个节点被访问一次且仅被访问一次。 + +在二叉树的一些实际问题中,经常需要按照一定顺序对二叉树中每个节点逐个进行访问一次,用以查找具有某一特点的节点或者全部节点,然后对这些满足要求的节点进行处理。这里所说的「访问」就是指对该节点进行某种操作,例如:依次输出节点的数据信息、统计满足某条件的节点总数等等。 + +回顾二叉树的递归定义可以知道,二叉树是由根节点和左子树、右子树构成的。因此,如果能依次遍历这 $3$ 个部分,就可以遍历整个二叉树。 + +如果利用深度优先搜索的方式,并且根据访问顺序次序的不同,我们可以分为 $6$ 种遍历方式,而如果限制先左子树后右子树的遍历顺序,则总共有 $3$ 种遍历方式:分别为 **「二叉树的前序遍历」**、**「二叉树的中序遍历」** 和 **「二叉树的后续遍历」**。 + +而如果使用广度优先搜索的方式,则可以按照层序方式(按照层次从上至下,每一层从左至右)对二叉树进行遍历,这种方式叫做 **「二叉树的层序遍历」**。 + +## 2. 二叉树的前序遍历 + +> 二叉树的前序遍历规则为: +> +> - 如果二叉树为空,则返回。 +> - 如果二叉树不为空,则: +> 1. 访问根节点。 +> 2. 以前序遍历的方式遍历根节点的左子树。 +> 3. 以前序遍历的方式遍历根节点的右子树。 + +从二叉树的前序遍历规则可以看出:前序遍历过程是一个递归过程。在遍历任何一棵子树时仍然是按照先访问根节点,然后遍历子树根节点的左子树,最后再遍历子树根节点的右子树的顺序进行遍历。 + +如下图所示,该二叉树的前序遍历顺序为:$A - B - D - H - I - E - C - F - J - G - K$。 + +![二叉树的前序遍历](https://qcdn.itcharge.cn/images/20240511171628.png) + +### 2.1 二叉树的前序遍历递归实现 + +二叉树的前序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先访问根节点。 +3. 然后递归遍历左子树。 +4. 最后递归遍历右子树。 + +二叉树的前序遍历递归实现代码如下: + +```python +class Solution: + def preorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + + def preorder(root): + if not root: + return + res.append(root.val) + preorder(root.left) + preorder(root.right) + + preorder(root) + return res +``` + +### 2.2 二叉树的前序遍历显式栈实现 + +二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 $stack$ 来模拟递归的过程。 + +前序遍历的顺序为:根节点 - 左子树 - 右子树,而根据栈的「先入后出」特点,所以入栈的顺序应该为:先放入右子树,再放入左子树。这样可以保证最终遍历顺序为前序遍历顺序。 + +二叉树的前序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个栈,将根节点入栈。 +3. 当栈不为空时: + 1. 弹出栈顶元素 $node$,并访问该元素。 + 2. 如果 $node$ 的右子树不为空,则将 $node$ 的右子树入栈。 + 3. 如果 $node$ 的左子树不为空,则将 $node$ 的左子树入栈。 + + 二叉树的前序遍历显式栈实现代码如下: + +```python +class Solution: + def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + if not root: # 二叉树为空直接返回 + return [] + + res = [] + stack = [root] + + while stack: # 栈不为空 + node = stack.pop() # 弹出根节点 + res.append(node.val) # 访问根节点 + if node.right: + stack.append(node.right) # 右子树入栈 + if node.left: + stack.append(node.left) # 左子树入栈 + + return res +``` + +## 3. 二叉树的中序遍历 + +> 二叉树的中序遍历规则为: +> +> - 如果二叉树为空,则返回。 +> - 如果二叉树不为空,则: +> 1. 以中序遍历的方式遍历根节点的左子树。 +> 2. 访问根节点。 +> 3. 以中序遍历的方式遍历根节点的右子树。 + +从二叉树的中序遍历规则可以看出:中序遍历过程也是一个递归过程。在遍历任何一棵子树时仍然是按照先遍历子树根节点的左子树,然后访问根节点,最后再遍历子树根节点的右子树的顺序进行遍历。 + +如下图所示,该二叉树的中序遍历顺序为:$H - D - I - B - E - A - F - J - C - K - G$。 + +![二叉树的中序遍历](https://qcdn.itcharge.cn/images/20240511171643.png) + +### 3.1 二叉树的中序遍历递归实现 + +二叉树的中序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先递归遍历左子树。 +3. 然后访问根节点。 +4. 最后递归遍历右子树。 + +二叉树的中序遍历递归实现代码如下: + +```python +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def inorder(root): + if not root: + return + inorder(root.left) + res.append(root.val) + inorder(root.right) + + inorder(root) + return res +``` + +### 3.2 二叉树的中序遍历显式栈实现 + +我们可以使用一个显式栈 $stack$ 来模拟二叉树的中序遍历递归的过程。 + +与前序遍历不同,访问根节点要放在左子树遍历完之后。因此我们需要保证:**在左子树访问之前,当前节点不能提前出栈**。 + +我们应该从根节点开始,循环遍历左子树,不断将当前子树的根节点放入栈中,直到当前节点无左子树时,从栈中弹出该节点并进行处理。 + +然后再访问该元素的右子树,并进行上述循环遍历左子树的操作。这样可以保证最终遍历顺序为中序遍历顺序。 + +二叉树的中序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个空栈。 +3. 当根节点或者栈不为空时: + 1. 如果当前节点不为空,则循环遍历左子树,并不断将当前子树的根节点入栈。 + 1. 如果当前节点为空,说明当前节点无左子树,则弹出栈顶元素 $node$,并访问该元素,然后尝试访问该节点的右子树。 + + 二叉树的中序遍历显式栈实现代码如下: + +```python +class Solution: + def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + if not root: # 二叉树为空直接返回 + return [] + + res = [] + stack = [] + + while root or stack: # 根节点或栈不为空 + while root: + stack.append(root) # 将当前树的根节点入栈 + root = root.left # 找到最左侧节点 + + node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 + res.append(node.val) # 访问该节点 + root = node.right # 尝试访问该节点的右子树 + return res +``` + +## 4. 二叉树的后序遍历 + +> 二叉树的后序遍历规则为: +> +> - 如果二叉树为空,则返回。 +> - 如果二叉树不为空,则: +> 1. 以后序遍历的方式遍历根节点的左子树。 +> 2. 以后序遍历的方式遍历根节点的右子树。 +> 3. 访问根节点。 + +从二叉树的后序遍历规则可以看出:后序遍历过程也是一个递归过程。在遍历任何一棵子树时仍然是按照先遍历子树根节点的左子树,然后遍历子树根节点的右子树,最后再访问根节点的顺序进行遍历。 + +如下图所示,该二叉树的后序遍历顺序为:$H - I - D - E - B - J - F - K - G - C - A$。 + +![二叉树的后序遍历](https://qcdn.itcharge.cn/images/20240511171658.png) + +### 4.1 二叉树的后序遍历递归实现 + +二叉树的后序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先递归遍历左子树。 +3. 然后递归遍历右子树。 +4. 最后访问根节点。 + +二叉树的后序遍历递归实现代码如下: + +```python +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def postorder(root): + if not root: + return + postorder(root.left) + postorder(root.right) + res.append(root.val) + + postorder(root) + return res +``` + +### 4.2 二叉树的后序遍历显式栈实现 + +我们可以使用一个显式栈 $stack$ 来模拟二叉树的后序遍历递归的过程。 + +与前序、中序遍历不同,在后序遍历中,根节点的访问要放在左右子树访问之后。因此,我们要保证:**在左右孩子节点访问结束之前,当前节点不能提前出栈**。 + +我们应该从根节点开始,先将根节点放入栈中,然后依次遍历左子树,不断将当前子树的根节点放入栈中,直到遍历到左子树最左侧的那个节点,从栈中弹出该元素,并判断该元素的右子树是否已经访问完毕,如果访问完毕,则访问该元素。如果未访问完毕,则访问该元素的右子树。 + +二叉树的后序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个空栈,使用 $prev$ 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕。 +3. 当根节点或者栈不为空时,从当前节点开始: + 1. 如果当前节点有左子树,则不断遍历左子树,并将当前根节点压入栈中。 + 2. 如果当前节点无左子树,则弹出栈顶元素 $node$。 + 2. 如果栈顶元素 $node$ 无右子树(即 `not node.right`)或者右子树已经访问完毕(即 `node.right == prev`),则访问该元素,然后记录前一节点,并将当前节点标记为空节点。 + 2. 如果栈顶元素有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子树。 + +```python +class Solution: + def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + res = [] + stack = [] + prev = None # 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕 + + while root or stack: # 根节点或栈不为空 + while root: + stack.append(root) # 将当前树的根节点入栈 + root = root.left # 继续访问左子树,找到最左侧节点 + + node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 + + # 如果当前节点无右子树或者右子树访问完毕 + if not node.right or node.right == prev: + res.append(node.val)# 访问该节点 + prev = node # 记录前一节点 + root = None # 将当前根节点标记为空 + else: + stack.append(node) # 右子树尚未访问完毕,将当前节点重新压回栈中 + root = node.right # 继续访问右子树 + + return res +``` + +## 5. 二叉树的层序遍历 + +> 二叉树的层序遍历规则为: +> +> - 如果二叉树为空,则返回。 +> - 如果二叉树不为空,则: +> 1. 先依次访问二叉树第 $1$ 层的节点。 +> 2. 然后依次访问二叉树第 $2$ 层的节点。 +> 3. …… +> 4. 依次下去,最后依次访问二叉树最下面一层的节点。 + +从二叉树的层序遍历规则可以看出:遍历过程是一个广度优先搜索过程。在遍历的时候是按照第 $1$ 层、第 $2$ 层、…… 最后一层依次遍历的,而同一层节点则是按照从左至右的顺序依次访问的。 + +如下图所示,该二叉树的后序遍历顺序为:$A - B - C - D - E - F - G - H - I - J - K$。 + +![二叉树的层序遍历](https://qcdn.itcharge.cn/images/20240511175431.png) + +二叉树的层序遍历是通过队列来实现的。具体步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 令根节点入队。 +3. 当队列不为空时,求出当前队列长度 $s_i$。 +4. 依次从队列中取出这 $s_i$ 个元素,并对这 $s_i$ 个元素依次进行访问。然后将其左右孩子节点入队,然后继续遍历下一层节点。 +5. 当队列为空时,结束遍历。 + +二叉树的层序遍历代码实现如下: + +```python +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + while queue: + level = [] + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + level.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(level) + return order +``` + +## 练习题目 + +- [0144. 二叉树的前序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) +- [0094. 二叉树的中序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) +- [0145. 二叉树的后序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) +- [0102. 二叉树的层序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) +- [0104. 二叉树的最大深度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) +- [0112. 路径总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) + +- [二叉树的遍历题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%81%8D%E5%8E%86%E9%A2%98%E7%9B%AE) + +## 参考资料 + +1. 【书籍】数据结构教程 第 3 版 - 唐发根 著 +2. 【书籍】大话数据结构 程杰 著 +3. 【书籍】算法训练营 陈小玉 著 +3. 【题解】[LeetCode 二叉树前序遍历(递归法 + 非递归法)- 二叉树的前序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-preorder-traversal/solution/acm-xuan-shou-tu-jie-leetcode-er-cha-shu-pqpz/) +3. 【题解】[二叉树遍历通解(递归和迭代解法)- 完全模拟递归 - 二叉树的后序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-postorder-traversal/solution/bian-li-tong-jie-by-long_wotu/) +3. 【题解】[迭代后序遍历 - 二叉树的后序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-postorder-traversal/solution/die-dai-hou-xu-bian-li-by-wang-mo-ji-98ob/) diff --git a/docs/05_tree/05_03_binary_tree_reduction.md b/docs/05_tree/05_03_binary_tree_reduction.md new file mode 100644 index 00000000..4803395d --- /dev/null +++ b/docs/05_tree/05_03_binary_tree_reduction.md @@ -0,0 +1,175 @@ +## 1. 二叉树的还原简介 + +> **二叉树的还原**:指的是通过二叉树的遍历序列,还原出对应的二叉树。 + +从二叉树的遍历过程可以看出,给定一棵非空二叉树,它的前序、中序、后续遍历所得到的遍历序列都是唯一的。那么反过来,如果已知节点的某种遍历序列,能否确定这棵二叉树呢?并且确定的二叉树是否是唯一的呢? + +我们先来回顾一下二叉树的前序遍历、中序遍历、后序遍历规则。 + +- 非空二叉树的前序遍历规则: + 1. 访问根节点。 + 2. 以前序遍历的方式遍历根节点的左子树。 + 3. 以前序遍历的方式遍历根节点的右子树。 +- 非空二叉树的中序遍历规则: + 1. 以中序遍历的方式遍历根节点的左子树。 + 2. 访问根节点。 + 3. 以中序遍历的方式遍历根节点的右子树。 +- 非空二叉树的后序遍历规则: + 1. 以后序遍历的方式遍历根节点的左子树。 + 2. 以后序遍历的方式遍历根节点的右子树。 + 3. 访问根节点。 + +先来看二叉树的前序遍历,前序遍历过程中首先访问的是根节点,所以通过前序遍历序列,我们可以确定序列的第 $1$ 个节点肯定是根节点。但是从第 $2$ 个节点开始就不确定它是根节点的左子树还是根节点的右子树了。所以单凭前序遍历序列是无法恢复一棵二叉树的。 + +再来看二叉树的后序遍历,后序遍历也是只能确定序列的最后一个节点为根节点,而无法确定其他节点在二叉树中的位置。所以单凭后序遍历序列也是无法恢复一棵二叉树的。 + +最后我们来看二叉树的中序遍历,中序遍历是先遍历根节点的左子树,然后访问根节点,最后遍历根节点的右子树。这样,根节点在中序遍历序列中必然将中序序列分割成前后两个子序列,其中前一个子序列是根节点的左子树的中序遍历序列,后一个子序列是根节点的右子树的中序遍历序列。当然单凭中序遍历序列也是无法恢复一棵二叉树的。 + +但是如果我们可以将「前序遍历序列」和「中序遍历序列」相结合,那么我们就可以通过上面中序遍历序列中的两个子序列,在前序遍历序列中找到对应的左子序列和右子序列。在前序遍历序列中,左子序列的第 $1$ 个节点是左子树的根节点,右子序列的第 $1$ 个节点是右子树的根节点。这样,就确定了二叉树的 $3$ 个节点。 + +同时,左子树和右子树的根节点在中序遍历序列中又可以将左子序列和右子序列分别划分成两个子序列。如此递归下去,当确定了前序遍历序列中的所有节点时,我们就得到了一棵二叉树。 + +还有一个问题,通过前序序列和中序序列还原的二叉树是唯一的吗? + +这个唯一性可以利用归纳法加以证明。感兴趣的读者可以试试自己证明或者参考有关资料。 + +通过上述过程说明:**如果已知一棵二叉树的前序序列和中序序列,可以唯一地确定这棵二叉树。** + +同理,**如果已知一棵二叉树的中序序列和后序序列,也可以唯一地确定这棵二叉树。** 方法和通过二叉树的前序序列和中序序列构造二叉树类似,唯一不同点在于二叉树的根节点是根据后序遍历序列的最后一个元素确定的。 + +类似的,**已知二叉树的「中序遍历序列」和「层序遍历序列」,也可以唯一地确定一棵二叉树。** + +需要注意的是:**如果已知二叉树的「前序遍历序列」和「后序遍历序列」,是不能唯一地确定一棵二叉树的。** 这是因为没有中序遍历序列无法确定左右部分,也就无法进行子序列的分割。 + +只有二叉树中每个节点度为 $2$ 或者 $0$ 的时候,已知前序遍历序列和后序遍历序列,才能唯一地确定一颗二叉树,如果二叉树中存在度为 $1$ 的节点时是无法唯一地确定一棵二叉树的,这是因为我们无法判断该节点是左子树还是右子树。 + +## 2. 从前序与中序遍历序列构造二叉树 + +- **描述**:已知一棵二叉树的前序遍历序列和中序遍历序列。 +- **要求**:构造出该二叉树。 +- **注意**:假设树中没有重复的元素。 + +### 2.1 从前序与中序遍历序列构造二叉树实现过程 + +前序遍历的顺序是:根节点 - 左子树 - 右子树。中序遍历的顺序是:左子树 - 根节点 - 右子树。 + +根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。 + +此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: + +1. 从前序遍历顺序中得到当前根节点的位置在 $postorder[0]$。 +2. 通过在中序遍历中查找上一步根节点对应的位置 $inorder[k]$,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 +3. 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 2.2 从前序与中序遍历序列构造二叉树实现代码 + +```python +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: + def createTree(preorder, inorder, n): + if n == 0: + return None + k = 0 + while preorder[0] != inorder[k]: + k += 1 + node = TreeNode(inorder[k]) + node.left = createTree(preorder[1: k + 1], inorder[0: k], k) + node.right = createTree(preorder[k + 1:], inorder[k + 1:], n - k - 1) + return node + return createTree(preorder, inorder, len(inorder)) +``` + +## 3. 从中序与后序遍历序列构造二叉树 + +- **描述**:已知一棵二叉树的中序遍历序列和后序遍历序列。 +- **要求**:构造出该二叉树。 +- **注意**:假设树中没有重复的元素。 + +### 3.1 从中序与后序遍历序列构造二叉树实现过程 + +中序遍历的顺序是:左子树 - 根节点 - 右子树。后序遍历的顺序是:左子树 - 右子树 - 根节点。 + +根据后序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。 + +此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: + +1. 从后序遍历顺序中当前根节点的位置在 $postorder[n-1]$。 +2. 通过在中序遍历中查找上一步根节点对应的位置 $inorder[k]$,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 +3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 3.2 从中序与后序遍历序列构造二叉树实现代码 + +```python +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: + def createTree(inorder, postorder, n): + if n == 0: + return None + k = 0 + while postorder[n - 1] != inorder[k]: + k += 1 + node = TreeNode(inorder[k]) + node.right = createTree(inorder[k + 1: n], postorder[k: n - 1], n - k - 1) + node.left = createTree(inorder[0: k], postorder[0: k], k) + return node + return createTree(inorder, postorder, len(postorder)) +``` + +## 4. 从前序与后序遍历序列构造二叉树 + +前边我们说过:**已知二叉树的前序遍历序列和后序遍历序列,是不能唯一地确定一棵二叉树的。** 而如果不要求构造的二叉树是唯一的,只要求构造出一棵二叉树,还是可以进行构造的。 + +- **描述**:已知一棵二叉树的前序遍历序列和后序遍历序列。 + +- **要求**:重构并返回该二叉树。 + +- **注意**:假设树中没有重复的元素。如果存在多个答案,则可以返回其中任意一个。 + +### 4.1 从前序与后序遍历序列构造二叉树实现过程 + +我们可以默认指定前序遍历序列的第 $2$ 个值为左子树的根节点,由此递归划分左右子序列。具体操作步骤如下: + +1. 从前序遍历序列中可知当前根节点的位置在 $preorder[0]$。 + +2. 前序遍历序列的第 $2$ 个值为左子树的根节点,即 $preorder[1]$。通过在后序遍历中查找上一步根节点对应的位置 $postorder[k]$(该节点右侧为右子树序列),从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 + +3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 + +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 4.2 从前序与后序遍历序列构造二叉树实现代码 + +```python +class Solution: + def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode: + def createTree(preorder, postorder, n): + if n == 0: + return None + node = TreeNode(preorder[0]) + if n == 1: + return node + k = 0 + while postorder[k] != preorder[1]: + k += 1 + node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1) + node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2) + return node + return createTree(preorder, postorder, len(preorder)) +``` + +## 练习题目 + +- [0105. 从前序与中序遍历序列构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) +- [0106. 从中序与后序遍历序列构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md) +- [0889. 根据前序和后序遍历构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md) + +- [二叉树的还原题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%BF%98%E5%8E%9F%E9%A2%98%E7%9B%AE) + +## 参考资料 + +1. 【书籍】数据结构教程 第 3 版 - 唐发根 著 +2. 【书籍】算法训练营 陈小玉 著 +3. 【博文】[二叉树的构造系列 - 知乎](https://zhuanlan.zhihu.com/p/346336665) +4. 【评论】[889. 根据前序和后序遍历构造二叉树 - 力扣)](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/comments/) \ No newline at end of file diff --git a/docs/05_tree/05_04_binary_search_tree.md b/docs/05_tree/05_04_binary_search_tree.md new file mode 100644 index 00000000..8e97b8fa --- /dev/null +++ b/docs/05_tree/05_04_binary_search_tree.md @@ -0,0 +1,208 @@ +## 1. 二叉搜索树简介 + +> **二叉搜索树(Binary Search Tree)**:也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树: +> +> - 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。 +> - 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。 +> - 任意节点的左子树、右子树均为二叉搜索树。 + +如图所示,这 $3$ 棵树都是二叉搜索树。 + +![二叉搜索树](https://qcdn.itcharge.cn/images/20240511171406.png) + +二叉树具有一个特性,即:**左子树的节点值 < 根节点值 < 右子树的节点值**。 + +根据这个特性,如果我们以中序遍历的方式遍历整个二叉搜索树时,会得到一个递增序列。例如,一棵二叉搜索树的中序遍历序列如下图所示。 + +## 2. 二叉搜索树的查找 + +> **二叉搜索树的查找**:在二叉搜索树中查找值为 $val$ 的节点。 + +### 2.1 二叉搜索树的查找算法步骤 + +按照二叉搜索树的定义,在进行元素查找时,我们只需要根据情况判断需要往左还是往右走。这样,每次根据情况判断都会缩小查找范围,从而提高查找效率。二叉树的查找步骤如下: + +1. 如果二叉搜索树为空,则查找失败,结束查找,并返回空指针节点 $None$。 +2. 如果二叉搜索树不为空,则将要查找的值 $val$ 与二叉搜索树根节点的值 $root.val$ 进行比较: + 1. 如果 $val == root.val$,则查找成功,结束查找,返回被查找到的节点。 + 2. 如果 $val < root.val$,则递归查找左子树。 + 3. 如果 $val > root.val$,则递归查找右子树。 + +### 2.2 二叉搜索树的查找代码实现 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + +class Solution: + def searchBST(self, root: TreeNode, val: int) -> TreeNode: + if not root: + return None + + if val == root.val: + return root + elif val < root.val: + return self.searchBST(root.left, val) + else: + return self.searchBST(root.right, val) +``` + +### 2.3 二叉搜索树的查找算法分析 + +- 二叉搜索树的查找时间复杂度和树的形态有关。 +- 在最好情况下,二叉搜索树的形态与二分查找的判定树相似。每次查找都可以所辖一半搜索范围。查找路径最多从根节点到叶子节点,比较次数最多为树的高度 $\log_2 n$。在最好情况下查找的时间复杂度为 $O(\log_2 n)$。 +- 在最坏情况下,二叉搜索树的形态为单支树,即只有左子树或者只有右子树。每次查找的搜索范围都缩小为 $n - 1$,退化为顺序查找,在最坏情况下时间复杂度为 $O(n)$。 +- 在平均情况下,二叉搜索树的平均查找长度为 $ASL = [(n + 1) / n] * /log_2(n+1) - 1$。所以二分搜索树的查找平均时间复杂度为 $O(log_2 n)$。 + +## 3. 二叉搜索树的插入 + +> **二叉搜索树的插入**:在二叉搜索树中插入一个值为 $val$ 的节点(假设当前二叉搜索树中不存在值为 $val$ 的节点)。 + +### 3.1 二叉搜索树的插入算法步骤 + +二叉搜索树的插入操作与二叉树的查找操作过程类似,具体步骤如下: + +1. 如果二叉搜索树为空,则创建一个值为 $val$ 的节点,并将其作为二叉搜索树的根节点。 +2. 如果二叉搜索树不为空,则将待插入的值 $val$ 与二叉搜索树根节点的值 $root.val$ 进行比较: + 1. 如果 $val < root.val$,则递归将值为 $val$ 的节点插入到左子树中。 + 2. 如果 $val > root.val$,则递归将值为 $val$ 的节点插入到右子树中。 + +> **注意**:二叉搜索树不允许存在重复节点,否则将违反其定义。因此,如果带插入节点在树中已存在,则不执行插入操作,直接返回。 + +### 3.2 二叉搜索树的插入代码实现 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + +class Solution: + def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: + if root == None: + return TreeNode(val) + + if val < root.val: + root.left = self.insertIntoBST(root.left, val) + if val > root.val: + root.right = self.insertIntoBST(root.right, val) + return root +``` + +## 4. 二叉搜索树的创建 + +> **二叉搜索树的创建**:根据数组序列中的元素值,建立一棵二叉搜索树。 + +### 4.1 二叉搜索树的创建算法步骤 + +二叉搜索树的创建操作是从空树开始,按照给定数组元素的值,依次进行二叉搜索树的插入操作,最终得到一棵二叉搜索树。具体算法步骤如下: + +1. 初始化二叉搜索树为空树。 +2. 遍历数组元素,将数组元素值 $nums[i]$ 依次插入到二叉搜索树中。 +3. 将数组中全部元素值插入到二叉搜索树中之后,返回二叉搜索树的根节点。 + +### 4.2 二叉搜索树的创建代码实现 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + +class Solution: + def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: + if root == None: + return TreeNode(val) + + if val < root.val: + root.left = self.insertIntoBST(root.left, val) + if val > root.val: + root.right = self.insertIntoBST(root.right, val) + return root + def buildBST(self, nums) -> TreeNode: + root = TreeNode(val) + for num in nums: + self.insertIntoBST(root, num) + return root +``` + +## 5. 二叉搜索树的删除 + +> **二叉搜索树的删除**:在二叉搜索树中删除值为 $val$ 的节点。 + +### 5.1 二叉搜索树的删除算法步骤 + +在二叉搜索树中删除元素,首先要找到待删除节点,然后执行删除操作。根据待删除节点所在位置的不同,可以分为 $3$ 种情况: + +1. 被删除节点的左子树为空。则令其右子树代替被删除节点的位置。 +2. 被删除节点的右子树为空。则令其左子树代替被删除节点的位置。 +3. 被删除节点的左右子树均不为空,则根据二叉搜索树的中序遍历有序性,删除该节点时,可以使用其直接前驱(或直接后继)代替被删除节点的位置。 + +- **直接前驱**:在中序遍历中,节点 $p$ 的直接前驱为其左子树的最右侧的叶子节点。 +- **直接后继**:在中序遍历中,节点 $p$ 的直接后继为其右子树的最左侧的叶子节点。 + +二叉搜索树的删除算法步骤如下: + +1. 如果当前节点为空,则返回当前节点。 +2. 如果当前节点值大于 $val$,则递归去左子树中搜索并删除,此时 $root.left$ 也要跟着递归更新。 +3. 如果当前节点值小于 $val$,则递归去右子树中搜索并删除,此时 $root.right$ 也要跟着递归更新。 +4. 如果当前节点值等于 $val$,则该节点就是待删除节点。 + 1. 如果当前节点的左子树为空,则删除该节点之后,则右子树代替当前节点位置,返回右子树。 + 2. 如果当前节点的右子树为空,则删除该节点之后,则左子树代替当前节点位置,返回左子树。 + 3. 如果当前节点的左右子树都有,则将左子树转移到右子树最左侧的叶子节点位置上,然后右子树代替当前节点位置。 + +### 5.2 二叉搜索树的删除代码实现 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + +class Solution: + def deleteNode(self, root: TreeNode, val: int) -> TreeNode: + if not root: + return root + + if root.val > val: + root.left = self.deleteNode(root.left, val) + return root + elif root.val < val: + root.right = self.deleteNode(root.right, val) + return root + else: + if not root.left: + return root.right + elif not root.right: + return root.left + else: + curr = root.right + while curr.left: + curr = curr.left + curr.left = root.left + return root.right +``` + +## 练习题目 + +- [0700. 二叉搜索树中的搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-binary-search-tree.md) +- [0701. 二叉搜索树中的插入操作](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md) +- [0450. 删除二叉搜索树中的节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/delete-node-in-a-bst.md) +- [0098. 验证二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) +- [0108. 将有序数组转换为二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md) +- [0235. 二叉搜索树的最近公共祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md) + +- [二叉搜索树题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】算法训练营 陈小玉 著 +- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 +- 【书籍】算法竞赛进阶指南 - 李煜东 著 +- 【博文】[7.4 二叉搜索树 - Hello 算法](https://www.hello-algo.com/chapter_tree/binary_search_tree/) diff --git a/docs/05_tree/05_05_segment_tree_01.md b/docs/05_tree/05_05_segment_tree_01.md new file mode 100644 index 00000000..09789901 --- /dev/null +++ b/docs/05_tree/05_05_segment_tree_01.md @@ -0,0 +1,341 @@ +## 1. 线段树简介 + +### 1.1 线段树的定义 + +> **线段树(Segment Tree)**:一种基于分治思想的二叉树,用于在区间上进行信息统计。它的每一个节点都对应一个区间 $[left, right]$ ,$left$、$right$ 通常是整数。每一个叶子节点表示了一个单位区间(长度为 $1$),叶子节点对应区间上 $left == right$。每一个非叶子节点 $[left, right]$ 的左子节点表示的区间都为 $[left, (left + right) / 2]$,右子节点表示的的区间都为 $[(left + right) / 2 + 1, right]$。 + +线段树是一棵平衡二叉树,树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父亲节点的区间二等分之后的其中一个子区间。当有 $n$ 个元素时,对区间的操作(单点更新、区间更新、区间查询等)可以在 $O(\log_2n)$ 的时间复杂度内完成。 + +如下图所示,这是一棵区间为 $[0, 7]$ 的线段树。 + +![区间 [0, 7] 对应的线段树](https://qcdn.itcharge.cn/images/20240511173328.png) + +### 1.2 线段树的特点 + +根据上述描述,我们可以总结一下线段树的特点: + +1. 线段树的每个节点都代表一个区间。 +2. 线段树具有唯一的根节点,代表的区间是整个统计范围,比如 $[1, n]$。 +3. 线段树的每个叶子节点都代表一个长度为 $1$ 的单位区间 $[x, x]$。 +4. 对于每个内部节点 $[left, right]$,它的左子节点是 $[left, mid]$,右子节点是 $[mid + 1, right]$。其中 $mid = (left + right) / 2$(向下取整)。 + +## 2. 线段树的构建 + +### 2.1 线段树的存储结构 + +之前我们学习过二叉树的两种存储结构,一种是「链式存储结构」,另一种是「顺序存储结构」。线段树也可以使用这两种存储结构来实现。 + +由于线段树近乎是完全二叉树,所以很适合用「顺序存储结构」来实现。 + +我们可以采用与完全二叉树类似的编号方法来对线段树进行编号,方法如下: + +- 根节点的编号为 $0$。 +- 如果某二叉树节点(非叶子节点)的下标为 $i$,那么其左孩子节点下标为 $2 \times i + 1$,右孩子节点下标为 $2 \times i + 2$。 +- 如果某二叉树节点(非根节点)的下标为 $i$,那么其父节点下标为 $(i - 1) // 2$,$//$ 表示整除。 + +这样我们就能使用一个数组来保存线段树。那么这个数组的大小应该设置为多少才合适? + +- 在理想情况下,$n$ 个单位区间构成的线段树是一棵满二叉树,节点数为 $n + n/2 + n/4 + ... + 2 + 1 = 2 \times n - 1$ 个。 因为 $2 \times n - 1 < 2 \times n$,所以在理想情况下,只需要使用一个大小为 $2 \times n$ 的数组来存储线段树就足够了。 +- 但是在一般情况下,有些区间元素需要开辟新的一层来存储元素。线段树的深度为 $\lceil \log_2n \rceil$,最坏情况下叶子节点(包括无用的节点)的数量为 $2^{\lceil \log_2n \rceil}$ 个,总节点数为 $2^{\lceil \log_2n \rceil + 1} - 1$ 个,可以近似看做是 $4 * n$,所以我们可以使用一个大小为 $4 \times n$ 的数组来存储线段树。 + +### 2.2 线段树的构建方法 + +![线段树父子节点下标关系](https://qcdn.itcharge.cn/images/20240511173417.png) + +通过上图可知:下标为 $i$ 的节点的孩子节点下标为 $2 \times i + 1$ 和 $2 \times i + 2$。所以线段树十分适合采用递归的方法来创建。具体步骤如下: + +1. 如果是叶子节点($left == right$),则节点的值就是对应位置的元素值。 +2. 如果是非叶子节点,则递归创建左子树和右子树。 +3. 节点的区间值(区间和、区间最大值、区间最小值)等于该节点左右子节点元素值的对应计算结果。 + +线段树的构建实现代码如下: + +```python +# 线段树的节点类 +class TreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [TreeNode() for _ in range(4 * self.size)] # 维护 TreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 构建线段树,节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + # 向上更新下标为 index 的节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) +``` + +这里的 `function` 指的是线段树区间合并的聚合方法。可以根据题意进行变化,常见的操作有求和、取最大值、取最小值等等。 + +## 3. 线段树的基本操作 + +线段树的基本操作主要涉及到单点更新、区间查询和区间更新操作。下面我们来进行一一讲解。 + +### 3.1 线段树的单点更新 + +> **线段树的单点更新**:修改一个元素的值,例如将 $nums[i]$ 修改为 $val$。 + +我们可以采用递归的方式进行单点更新,具体步骤如下: + +1. 如果是叶子节点,满足 $left == right$,则更新该节点的值。 +2. 如果是非叶子节点,则判断应该在左子树中更新,还是应该在右子树中更新。 +3. 在对应的左子树或右子树中更新节点值。 +4. 左右子树更新返回之后,向上更新节点的区间值(区间和、区间最大值、区间最小值等),区间值等于该节点左右子节点元素值的聚合计算结果。 + +线段树的单点更新实现代码如下: + +```python + # 单点更新,将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0, 0, self.size - 1) + + # 单点更新,将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] + def __update_point(self, i, val, index, left, right): + if self.tree[index].left == self.tree[index].right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index, left, mid) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index, mid + 1, right) + self.__pushup(index) # 向上更新节点的区间值 +``` + +### 3.2 线段树的区间查询 + +> **线段树的区间查询**:查询一个区间为 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 的区间值。 + +我们可以采用递归的方式进行区间查询,具体步骤如下: + +1. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 完全覆盖了当前节点所在区间 $[left, right]$ ,即 $left \ge q\underline{\hspace{0.5em}}left$ 并且 $right \le q\underline{\hspace{0.5em}}right$,则返回该节点的区间值。 +2. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与当前节点所在区间 $[left, right]$ 毫无关系,即 $right < q\underline{\hspace{0.5em}}left$ 或者 $left > q\underline{\hspace{0.5em}}right$,则返回 $0$。 +3. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与当前节点所在区间有交集,则: + 1. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与左子节点所在区间 $[left, mid]$ 有交集,即 $q\underline{\hspace{0.5em}}left \le mid$,则在当前节点的左子树中进行查询并保存查询结果 $res\underline{\hspace{0.5em}}left$。 + 2. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与右子节点所在区间 $[mid + 1, right]$ 有交集,即 $q\underline{\hspace{0.5em}}right > mid$,则在当前节点的右子树中进行查询并保存查询结果 $res\underline{\hspace{0.5em}}right$。 + 3. 最后返回左右子树元素区间值的聚合计算结果。 + +线段树的区间查询代码如下: + +```python + # 区间查询,查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0, 0, self.size - 1) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index, left, right): + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index, left, mid) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index, mid + 1, right) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 +``` + +### 3.3 线段树的区间更新 + +> **线段树的区间更新**:对 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 区间进行更新,例如将 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 区间内所有元素都更新为 $val$。 + +#### 3.3.1 延迟标记 + +线段树在进行单点更新、区间查询时,区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 在线段树上会被分成 $O(\log_2n)$ 个小区间(节点),从而在 $O(\log_2n)$ 的时间复杂度内完成操作。 + +而在「区间更新」操作中,如果某个节点区间 $[left, right]$ 被修改区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 完全覆盖,则以该节点为根的整棵子树中所有节点的区间值都要发生变化,如果逐一进行更新的话,将使得一次区间更新操作的时间复杂度增加到 $O(n)$。 + +设想这一种情况:如果我们在一次执行更新操作时,发现当前节点区间 $[left, right]$ 被修改区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 完全覆盖,然后逐一更新了区间 $[left, right]$ 对应子树中的所有节点,但是在后续的区间查询操作中却根本没有用到 $[left, right]$ 作为候选答案,则更新 $[left, right]$ 对应子树的工作就是徒劳的。 + +如果我们减少更新的次数和时间复杂度,应该怎么办? + +我们可以向线段树的节点类中增加一个 **「延迟标记」**,标识为 **「该区间曾经被修改为 $val$,但其子节点区间值尚未更新」**。也就是说除了在进行区间更新时,将区间子节点的更新操作延迟到 **「在后续操作中递归进入子节点时」** 再执行。这样一来,每次区间更新和区间查询的时间复杂度都降低到了 $O(\log_2n)$。 + +使用「延迟标记」的区间更新步骤为: + +1. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 完全覆盖了当前节点所在区间 $[left, right]$ ,即 $left \ge q\underline{\hspace{0.5em}}left$ 并且 $right \le q\underline{\hspace{0.5em}}right$,则更新当前节点所在区间的值,并将当前节点的延迟标记为区间值。 +2. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与当前节点所在区间 $[left, right]$ 毫无关系,即 $right < q\underline{\hspace{0.5em}}left$ 或者 $left > q\underline{\hspace{0.5em}}right$,则直接返回。 +3. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与当前节点所在区间有交集,则: + 1. 如果当前节点使用了「延迟标记」,即延迟标记不为 $None$,则将当前区间的更新操作应用到该节点的子节点上(即向下更新)。 + 2. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与左子节点所在区间 $[left, mid]$ 有交集,即 $q\underline{\hspace{0.5em}}left \le mid$,则在当前节点的左子树中更新区间值。 + 3. 如果区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 与右子节点所在区间 $[mid + 1, right]$ 有交集,即 $q\underline{\hspace{0.5em}}right > mid$,则在当前节点的右子树中更新区间值。 + 4. 左右子树更新返回之后,向上更新节点的区间值(区间和、区间最大值、区间最小值),区间值等于该节点左右子节点元素值的对应计算结果。 + +#### 3.3.2 向下更新 + +上面提到了如果当前节点使用了「延迟标记」,即延迟标记不为 $None$,则将当前区间的更新操作应用到该节点的子节点上(即向下更新)。这里描述一下向下更新的具体步骤: + +1. 更新左子节点值和左子节点懒惰标记为 $val$。 +2. 更新右子节点值和右子节点懒惰标记为 $val$。 +3. 将当前节点的懒惰标记更新为 $None$。 + +使用「延迟标记」的区间更新实现代码如下: + +```python + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1) + + # 区间更新 + def __update_interval(self, q_left, q_right, val, index, left, right): + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + interval_size = (right - left + 1) # 当前节点所在区间大小 + self.tree[index].val = interval_size * val # 当前节点所在区间每个元素值改为 val + self.tree[index].lazy_tag = val # 将当前节点的延迟标记为区间值 + return + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index, left, mid) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index, mid + 1, right) + + self.__pushup(index) + + # 向下更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if not lazy_tag: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + self.tree[left_index].lazy_tag = lazy_tag # 更新左子节点懒惰标记 + left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) + self.tree[left_index].val = lazy_tag * left_size # 更新左子节点值 + + self.tree[right_index].lazy_tag = lazy_tag # 更新右子节点懒惰标记 + right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) + self.tree[right_index].val = lazy_tag * right_size # 更新右子节点值 + + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 +``` + +> **注意**:有些题目中不是将 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 区间更新为 $val$,而是将 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 区间中每一个元素值在原值基础增加或减去 $val$。 +> +> 对于这种情况,我们可以更改一下「延迟标记」的定义。改变为: **「该区间曾经变化了 $val$,但其子节点区间值尚未更新」**。并更改对应的代码逻辑。 + +使用「延迟标记」的区间增减更新实现代码如下: + +```python + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1) + + # 区间更新 + def __update_interval(self, q_left, q_right, val, index, left, right): + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 +# interval_size = (right - left + 1) # 当前节点所在区间大小 +# self.tree[index].val = interval_size * val # 当前节点所在区间每个元素值改为 val +# self.tree[index].lazy_tag = val # 将当前节点的延迟标记为区间值 + + if self.tree[index].lazy_tag: + self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val + else: + self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val + interval_size = (right - left + 1) # 当前节点所在区间大小 + self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val + return + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index, left, mid) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index, mid + 1, right) + + self.__pushup(index) + + # 向下更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if not lazy_tag: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + if self.tree[left_index].lazy_tag: + self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + self.tree[left_index].lazy_tag = lazy_tag + left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) + self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag + + if self.tree[right_index].lazy_tag: + self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + self.tree[right_index].lazy_tag = lazy_tag + right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) + self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag + + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 +``` + +## 练习题目 + +- [线段树题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E7%BA%BF%E6%AE%B5%E6%A0%91%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 +- 【书籍】算法训练营 陈小玉 著 +- 【博文】[史上最详细的线段树教程 - 知乎](https://zhuanlan.zhihu.com/p/34150142) +- 【博文】[线段树 Segment Tree 实战 - halfrost](https://halfrost.com/segment_tree/) +- 【博文】[线段树 - OI Wiki](https://oi-wiki.org/ds/seg/) +- 【博文】[线段树的 python 实现 - 年糕的博客 - CSDN博客](https://blog.csdn.net/qq_33935895/article/details/102806357) +- 【博文】[线段树 从入门到进阶 - Dijkstra·Liu - 博客园](https://www.cnblogs.com/dijkstra2003/p/9676729.html) + diff --git a/docs/05_tree/05_06_segment_tree_02.md b/docs/05_tree/05_06_segment_tree_02.md new file mode 100644 index 00000000..7851b63f --- /dev/null +++ b/docs/05_tree/05_06_segment_tree_02.md @@ -0,0 +1,203 @@ +## 1. 线段树的常见题型 + +### 1.1 RMQ 问题 + +> **RMQ 问题**:Range Maximum / Minimum Query 的缩写,指的是对于长度为 $n$ 的数组序列 $nums$,回答若干个询问问题 `RMQ(nums, q_left, q_right)`,要求返回数组序列 $nums$ 在区间 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 中的最大(最小)值。也就是求区间最大(最小)值问题。 + +假设查询次数为 $q$,则使用朴素算法解决 RMQ 问题的时间复杂度为 $O(q \times n)$。而使用线段树解决 RMQ 问题的时间复杂度为 $O(q \times n) \sim Q(q \times \log_2n)$ 之间。 + +### 1.2 单点更新,区间查询问题 + +> **单点更新,区间查询问题**: +> +> 1. 修改某一个元素的值。 +> 2. 查询区间为 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 的区间值。 + +这类问题直接使用「3.1 线段树的单点更新」和「3.2 线段树的区间查询」即可解决。 + +### 1.3 区间更新,区间查询问题 + +> **区间更新,区间查询问题**: +> +> 1. 修改某一个区间的值。 +> 2. 查询区间为 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 的区间值。 + +这类问题直接使用「3.3 线段树的区间更新」和「3.2 线段树的区间查询」即可解决。 + +### 1.4 区间合并问题 + +> **区间合并,区间查询问题**: +> +> 1. 修改某一个区间的值。 +> 2. 查询区间为 $[q\underline{\hspace{0.5em}}left, q\underline{\hspace{0.5em}}right]$ 中满足条件的连续最长区间值。 + +这类问题需要在「3.3 线段树的区间更新」和「3.2 线段树的区间查询」的基础上增加变动,在进行向上更新时需要对左右子节点的区间进行合并。 + +### 1.5 扫描线问题 + +> **扫描线问题**:虚拟扫描线或扫描面来解决欧几里德空间中的各种问题,一般被用来解决图形面积,周长等问题。 +> +> 主要思想为:想象一条线(通常是一条垂直线)在平面上扫过或移动,在某些点停止。几何操作仅限于几何对象,无论何时停止,它们都与扫描线相交或紧邻扫描线,并且一旦线穿过所有对象,就可以获得完整的解。 + +这类问题通常坐标跨度很大,需要先对每条扫描线的坐标进行离散化处理,将 $y$ 坐标映射到 $0, 1, 2, ...$ 中。然后将每条竖线的端点作为区间范围,使用线段树存储每条竖线的信息($x$ 坐标、是左竖线还是右竖线等),然后再进行区间合并,并统计相关信息。 + +## 2. 线段树的拓展 + +### 2.1 动态开点线段树 + +在有些情况下,线段树需要维护的区间很大(例如 $[1, 10^9]$),在实际中用到的节点却很少。 + +如果使用之前数组形式实现线段树,则需要 $4 \times n$ 大小的空间,空间消耗有点过大了。 + +这时候我们就可以使用动态开点的思想来构建线段树。 + +动态开点线段树的算法思想如下: + +- 开始时只建立一个根节点,代表整个区间。 +- 当需要访问线段树的某棵子树(某个子区间)时,再建立代表这个子区间的节点。 + +动态开点线段树实现代码如下: + +```python +# 线段树的节点类 +class TreeNode: + def __init__(self, left=-1, right=-1, val=0): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = None # 区间左节点 + self.rightNode = None # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + def __init__(self, function): + self.tree = TreeNode(0, int(1e9)) + self.function = function # function 是一个函数,左右区间的聚合方法 + + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + leftNode = node.leftNode + rightNode = node.rightNode + if leftNode and rightNode: + node.val = self.function(leftNode.val, rightNode.val) + + # 单点更新,将 nums[i] 更改为 val + def update_point(self, i, val): + self.__update_point(i, val, self.tree) + + # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] + def __update_point(self, i, val, node): + if node.left == node.right: + node.val = val # 叶子节点,节点值修改为 val + return + + if i <= node.mid: # 在左子树中更新节点值 + if not node.leftNode: + node.leftNode = TreeNode(node.left, node.mid) + self.__update_point(i, val, node.leftNode) + else: # 在右子树中更新节点值 + if not node.rightNode: + node.rightNode = TreeNode(node.mid + 1, node.right) + self.__update_point(i, val, node.rightNode) + self.__pushup(node) # 向上更新节点的区间值 + + # 区间查询,查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + if not node.leftNode: + node.leftNode = TreeNode(node.left, node.mid) + res_left = self.__query_interval(q_left, q_right, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + if not node.rightNode: + node.rightNode = TreeNode(node.mid + 1, node.right) + res_right = self.__query_interval(q_left, q_right, node.rightNode) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间更新 + def __update_interval(self, q_left, q_right, val, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if node.lazy_tag: + node.lazy_tag += val # 将当前节点的延迟标记增加 val + else: + node.lazy_tag = val # 将当前节点的延迟标记增加 val + interval_size = (node.right - node.left + 1) # 当前节点所在区间大小 + node.val += val * interval_size # 当前节点所在区间每个元素值增加 val + return + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + if q_left <= node.mid: # 在左子树中更新区间值 + if not node.leftNode: + node.leftNode = TreeNode(node.left, node.mid) + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + if not node.rightNode: + node.rightNode = TreeNode(node.mid + 1, node.right) + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + lazy_tag = node.lazy_tag + if not node.lazy_tag: + return + + if not node.leftNode: + node.leftNode = TreeNode(node.left, node.mid) + if not node.rightNode: + node.rightNode = TreeNode(node.mid + 1, node.right) + + if node.leftNode.lazy_tag: + node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 + left_size = (node.leftNode.right - node.leftNode.left + 1) + node.leftNode.val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag + + if node.rightNode.lazy_tag: + node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 + right_size = (node.rightNode.right - node.rightNode.left + 1) + node.rightNode.val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag + + node.lazy_tag = None # 更新当前节点的懒惰标记 +``` + +## 练习题目 + +- [线段树题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E7%BA%BF%E6%AE%B5%E6%A0%91%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 +- 【书籍】算法训练营 陈小玉 著 +- 【博文】[史上最详细的线段树教程 - 知乎](https://zhuanlan.zhihu.com/p/34150142) +- 【博文】[线段树 Segment Tree 实战 - halfrost](https://halfrost.com/segment_tree/) +- 【博文】[线段树 - OI Wiki](https://oi-wiki.org/ds/seg/) +- 【博文】[线段树的 python 实现 - 年糕的博客 - CSDN博客](https://blog.csdn.net/qq_33935895/article/details/102806357) +- 【博文】[线段树 从入门到进阶 - Dijkstra·Liu - 博客园](https://www.cnblogs.com/dijkstra2003/p/9676729.html) + diff --git a/docs/05_tree/05_07_binary_indexed_tree.md b/docs/05_tree/05_07_binary_indexed_tree.md new file mode 100644 index 00000000..5d1a87ad --- /dev/null +++ b/docs/05_tree/05_07_binary_indexed_tree.md @@ -0,0 +1,235 @@ +## 1. 树状数组简介 + +### 1.1 树状数组的定义 + +> **树状数组(Binary Indexed Tree)**:也因其发明者命名为 Fenwick 树,最早 Peter M. Fenwick 于 1994 年以 A New Data Structure for Cumulative Frequency Tables 为题发表在 SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和,区间和。它可以以 $O(\log n)$ 的时间得到任意前缀 $\sum_{i=1}^{j}A[i], 1 \le j \le n$,并同时支持在 $O(\log n)$ 时间内支持动态单点值的修改。空间复杂度为 $O(n)$。 + +### 1.2 树状数组的原理 + +树状数组的核心思想是利用二进制数的特性,将前缀和分解成多个子区间的和。具体来说: + +1. 每个节点存储的是以该节点为根的子树中所有节点的和 +2. 对于任意一个数 $x$,其二进制表示中最低位的 1 所在的位置决定了该节点在树中的层级 +3. 通过位运算可以快速找到需要更新的节点和需要求和的区间 + +例如,对于数组 $[1, 2, 3, 4, 5]$,其树状数组的结构如下: +``` + [15] + / \ + [3] [12] + / \ / \ +[1] [2] [3] [9] + / \ + [4] [5] +``` + +## 2. 树状数组的基本操作 + +### 2.1 树状数组的建立 + +```python +class BinaryIndexedTree: + def __init__(self, n): + self.n = n + self.tree = [0] * (n + 1) + + def lowbit(self, x): + return x & (-x) + + def build(self, arr): + for i in range(len(arr)): + self.update(i + 1, arr[i]) +``` + +### 2.2 树状数组的修改 + +```python +def update(self, index, val): + while index <= self.n: + self.tree[index] += val + index += self.lowbit(index) +``` + +### 2.3 树状数组的求和 + +```python +def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res +``` + +## 3. 树状数组的应用 + +### 3.1 单点更新 + 区间求值 + +这是树状数组最基本的应用,可以高效地: +1. 修改某个位置的值 +2. 查询任意区间的和 + +时间复杂度: +- 单点更新:$O(\log n)$ +- 区间查询:$O(\log n)$ + +具体实现: +```python +class BinaryIndexedTree: + def __init__(self, n): + self.n = n + self.tree = [0] * (n + 1) + + def lowbit(self, x): + return x & (-x) + + def update(self, index, val): + while index <= self.n: + self.tree[index] += val + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + + def query_range(self, left, right): + return self.query(right) - self.query(left - 1) + +# 使用示例 +def example_single_point_update(): + # 初始化数组 [1, 2, 3, 4, 5] + arr = [1, 2, 3, 4, 5] + n = len(arr) + bit = BinaryIndexedTree(n) + + # 构建树状数组 + for i in range(n): + bit.update(i + 1, arr[i]) + + # 单点更新:将第3个元素加2 + bit.update(3, 2) # arr[2] += 2 + + # 查询区间和:查询[2,4]的和 + sum_range = bit.query_range(2, 4) + print(f"区间[2,4]的和为:{sum_range}") # 输出:区间[2,4]的和为:11 +``` + +### 3.2 区间更新 + 单点求值 + +通过差分数组的思想,可以实现: +1. 区间更新:将区间 $[l, r]$ 的所有值加上 $val$ +2. 单点查询:查询某个位置的值 + +时间复杂度: +- 区间更新:$O(\log n)$ +- 单点查询:$O(\log n)$ + +具体实现: +```python +class RangeUpdateBIT: + def __init__(self, n): + self.n = n + self.tree = [0] * (n + 1) + + def lowbit(self, x): + return x & (-x) + + def update(self, index, val): + while index <= self.n: + self.tree[index] += val + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + + def range_update(self, left, right, val): + # 在left位置加上val + self.update(left, val) + # 在right+1位置减去val + self.update(right + 1, -val) + +# 使用示例 +def example_range_update(): + # 初始化数组 [0, 0, 0, 0, 0] + n = 5 + bit = RangeUpdateBIT(n) + + # 区间更新:[2,4]区间所有元素加3 + bit.range_update(2, 4, 3) + + # 单点查询:查询第3个元素的值 + value = bit.query(3) + print(f"第3个元素的值为:{value}") # 输出:第3个元素的值为:3 +``` + +### 3.3 求逆序对数 + +利用树状数组可以高效求解数组中的逆序对数量: +1. 对数组进行离散化处理 +2. 从后向前遍历,统计每个数前面比它大的数的个数 +3. 将统计结果累加得到逆序对总数 + +时间复杂度:$O(n \log n)$ + +具体实现: +```python +class InversionCountBIT: + def __init__(self, n): + self.n = n + self.tree = [0] * (n + 1) + + def lowbit(self, x): + return x & (-x) + + def update(self, index, val): + while index <= self.n: + self.tree[index] += val + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + + def count_inversions(self, arr): + # 离散化处理 + sorted_arr = sorted(set(arr)) + rank = {val: i + 1 for i, val in enumerate(sorted_arr)} + + # 从后向前遍历,统计逆序对 + count = 0 + for i in range(len(arr) - 1, -1, -1): + # 查询当前数前面比它大的数的个数 + count += self.query(rank[arr[i]] - 1) + # 更新当前数的出现次数 + self.update(rank[arr[i]], 1) + + return count + +# 使用示例 +def example_inversion_count(): + # 测试数组 [5, 2, 6, 1, 3] + arr = [5, 2, 6, 1, 3] + n = len(arr) + bit = InversionCountBIT(n) + + # 计算逆序对数量 + inversions = bit.count_inversions(arr) + print(f"数组中的逆序对数量为:{inversions}") # 输出:数组中的逆序对数量为:6 +``` + +## 参考资料 + +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 +- 【书籍】算法训练营 陈小玉 著 +- 【博文】[聊聊树状数组 Binary Indexed Tree - halfrost](https://halfrost.com/binary_indexed_tree/) +- 【博文】[树状数组学习笔记 - AcWing](https://www.acwing.com/blog/content/80/) diff --git a/docs/05_tree/05_08_union_find.md b/docs/05_tree/05_08_union_find.md new file mode 100644 index 00000000..ce0f3a3b --- /dev/null +++ b/docs/05_tree/05_08_union_find.md @@ -0,0 +1,586 @@ +## 1. 并查集简介 + +### 1.1 并查集的定义 + +> **并查集(Union Find)**:一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。不交集指的是一系列没有重复元素的集合。 +> +> 并查集主要支持两种操作: +> +> - **合并(Union)**:将两个集合合并成一个集合。 +> - **查找(Find)**:确定某个元素属于哪个集合。通常是返回集合内的一个「代表元素」。 + +简单来说,并查集就是用来处理集合的合并和集合的查询。 + +- 并查集中的「集」指的就是我们初中所学的集合概念,在这里指的是不相交的集合,即一系列没有重复元素的集合。 +- 并查集中的「并」指的就是集合的并集操作,将两个集合合并之后就变成一个集合。合并操作如下所示: + +```python +{1, 3, 5, 7} U {2, 4, 6, 8} = {1, 2, 3, 4, 5, 6, 7, 8} +``` + +- 并查集中的「查」是对于集合中存放的元素来说的,通常我们需要查询两个元素是否属于同一个集合。 + +如果我们只是想知道一个元素是否在集合中,可以通过 Python 或其他语言中的 `set` 集合来解决。而如果我们想知道两个元素是否属于同一个集合,则仅用一个 `set` 集合就很难做到了。这就需要用到我们接下来要讲解的「并查集」结构。 + +根据上文描述,我们就可以定义一下「并查集」结构所支持的操作接口: + +- **合并 `union(x, y)`**:将集合 $x$ 和集合 $y$ 合并成一个集合。 +- **查找 `find(x)`**:查找元素 $x$ 属于哪个集合。 +- **查找 `is_connected(x, y)`**:查询元素 $x$ 和 $y$ 是否在同一个集合中。 + +### 1.2 并查集的两种实现思路 + +下面我们来讲解一下并查集的两种实现思路:一种是使用「快速查询」思路、基于数组结构实现的并查集;另一种是使用「快速合并」思路、基于森林实现的并查集。 + +#### 1.2.1 快速查询:基于数组实现 + +如果我们希望并查集的查询效率高一些,那么我们就可以侧重于查询操作。 + +在使用「快速查询」思路实现并查集时,我们可以使用一个「数组结构」来表示集合中的元素。数组元素和集合元素是一一对应的,我们可以将数组的索引值作为每个元素的集合编号,称为 $id$。然后可以对数组进行以下操作来实现并查集: + +- **当初始化时**:将数组下标索引值作为每个元素的集合编号。所有元素的 $id$ 都是唯一的,代表着每个元素单独属于一个集合。 +- **合并操作时**:需要将其中一个集合中的所有元素 $id$ 更改为另一个集合中的 $id$,这样能够保证在合并后一个集合中所有元素的 $id$ 均相同。 +- **查找操作时**:如果两个元素的 $id$ 一样,则说明它们属于同一个集合;如果两个元素的 $id$ 不一样,则说明它们不属于同一个集合。 + +举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。 + +![基于数组实现:初始化操作](https://qcdn.itcharge.cn/images/20240513150949.png) + +从上图中可以看出:数组的每个下标索引值对应一个元素的集合编号,代表着每个元素单独属于一个集合。 + +当我们进行一系列的合并操作后,比如合并后变为 $\left\{ 0 \right\}, \left\{ 1, 2, 3 \right\}, \left\{ 4 \right\}, \left\{5, 6\right\}, \left\{ 7 \right\}$,合并操作的结果如下图所示。 + +![基于数组实现:合并操作](https://qcdn.itcharge.cn/images/20240513151310.png) + +从上图中可以看出,在进行一系列合并操作后,下标为 $1$、$2$、$3$ 的元素集合编号是一致的,说明这 $3$ 个元素同属于一个集合。同理下标为 $5$ 和 $6$ 的元素则同属于另一个集合。 + +在快速查询的实现思路中,单次查询操作的时间复杂度是 $O(1)$,而单次合并操作的时间复杂度为 $O(n)$(每次合并操作需要遍历数组)。两者的时间复杂度相差得比较大,完全牺牲了合并操作的性能。因此,这种并查集的实现思路并不常用。 + +- 使用「快速查询」思路实现并查集代码如下所示: + +```python +class UnionFind: + def __init__(self, n): # 初始化:将每个元素的集合编号初始化为数组下标索引 + self.ids = [i for i in range(n)] + + def find(self, x): # 查找元素所属集合编号内部实现方法 + return self.ids[x] + + def union(self, x, y): # 合并操作:将集合 x 和集合 y 合并成一个集合 + x_id = self.find(x) + y_id = self.find(y) + + if x_id == y_id: # x 和 y 已经同属于一个集合 + return False + + for i in range(len(self.ids)): # 将两个集合的集合编号改为一致 + if self.ids[i] == y_id: + self.ids[i] = x_id + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +#### 1.2.2 快速合并:基于森林实现 + +因为快速查询的实现思路中,合并操作的效率比较低。所以我们现在的重点是提高合并操作的效率。 + +在使用「快速合并」思路实现并查集时,我们可以使用「一个森林(若干棵树)」来存储所有集合。每一棵树代表一个集合,树上的每个节点都是一个元素,树根节点为这个集合的代表元素。 + +> **注意**:与普通的树形结构(父节点指向子节点)不同的是,基于森林实现的并查集中,树中的子节点是指向父节点的。 + +此时,我们仍然可以使用一个数组 $fa$ 来记录这个森林。我们用 $fa[x]$ 来保存 $x$ 的父节点的集合编号,代表着元素节点 $x$ 指向父节点 $fa[x]$。 + +当初始化时,$fa[x]$ 值赋值为下标索引 $x$。在进行合并操作时,只需要将两个元素的树根节点相连接(`fa[root1] = root2`)即可。而在进行查询操作时,只需要查看两个元素的树根节点是否一致,就能知道两个元素是否属于同一个集合。 + +总结一下,我们可以对数组 $fa$ 进行以下操作来实现并查集: + +- **当初始化时**:将数组 $fa$​ 的下标索引作为每个元素的集合编号。所有元素的根节点的集合编号都不一样,代表着每个元素单独属于一个集合。 +- **合并操作时**:需要将两个集合的树根节点相连接。即令其中一个集合的树根节点指向另一个集合的树根节点(`fa[root1] = root2`),这样合并后当前集合中的所有元素的树根节点均为同一个。 +- **查找操作时**:分别从两个元素开始,通过数组 $fa$ 存储的值,不断递归访问元素的父节点,直到到达树根节点。如果两个元素的树根节点一样,则说明它们属于同一个集合;如果两个元素的树根节点不一样,则说明它们不属于同一个集合。 + +举个例子来说明一下,我们使用数组来表示一系列集合元素 $\left\{0\right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4 \right\}, \left\{ 5 \right\}, \left\{ 6 \right\}, \left\{ 7 \right\}$,初始化时如下图所示。 + +![基于森林实现:初始化操作](https://qcdn.itcharge.cn/images/20240513151548.png) + +从上图中可以看出:$fa$ 数组的每个下标索引值对应一个元素的集合编号,代表着每个元素属于一个集合。 + +当我们进行一系列的合并操作后,比如 `union(4, 5)`、`union(6, 7)`、`union(4, 7)` 操作后变为 $\left\{ 0 \right\}, \left\{ 1 \right\}, \left\{ 2 \right\}, \left\{ 3 \right\}, \left\{ 4, 5, 6, 7 \right\}$​,合并操作的步骤及结果如下图所示。 + +::: tabs#union + +@tab <1> + +- 合并 $(4, 5)$:令 $4$ 的根节点指向 $5$,即将 $fa[4]$ 更改为 $5$。 + +![基于森林实现:合并操作 1](https://qcdn.itcharge.cn/images/20240513154015.png) + +@tab <2> + +- 合并 $(6, 7)$:令 $6$ 的根节点指向 $7$,即将 $fa[6]$ 更改为 $7$。 + +![基于森林实现:合并操作 2](https://qcdn.itcharge.cn/images/20240513154022.png) + +@tab <3> + +- 合并 $(4, 7)$:令 $4$ 的的根节点指向 $7$,即将 $fa[fa[4]]$(也就是 $fa[5]$)更改为 $7$。 + +![基于森林实现:合并操作 3](https://qcdn.itcharge.cn/images/20240513154030.png) + +::: + +从上图中可以看出,在进行一系列合并操作后,`fa[fa[4]] == fa[5] == fa[6] == f[7]`,即 $4$、$5$、$6$、$7$ 的元素根节点编号都是 $4$,说明这 $4$ 个元素同属于一个集合。 + +- 使用「快速合并」思路实现并查集代码如下所示: + +```python +class UnionFind: + def __init__(self, n): # 初始化:将每个元素的集合编号初始化为数组 fa 的下标索引 + self.fa = [i for i in range(n)] + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +## 2. 路径压缩 + +在集合很大或者树很不平衡时,使用上述「快速合并」思路实现并查集的代码效率很差,最坏情况下,树会退化成一条链,单次查询的时间复杂度高达 $O(n)$。并查集的最坏情况如下图所示。 + +![并查集最坏情况](https://qcdn.itcharge.cn/images/20240513154732.png) + +为了避免出现最坏情况,一个常见的优化方式是「路径压缩」。 + +> **路径压缩(Path Compression)**:在从底向上查找根节点过程中,如果此时访问的节点不是根节点,则我们可以把这个节点尽量向上移动一下,从而减少树的层树。这个过程就叫做路径压缩。 + +路径压缩有两种方式:一种叫做「隔代压缩」;另一种叫做「完全压缩」。 + +### 2.1 隔代压缩 + +> **隔代压缩**:在查询时,两步一压缩,一直循环执行「把当前节点指向它的父亲节点的父亲节点」这样的操作,从而减小树的深度。 + +下面是一个「隔代压缩」的例子。 + +![路径压缩:隔代压缩](https://qcdn.itcharge.cn/images/20240513154745.png) + +- 隔代压缩的查找代码如下: + +```python +def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩 + x = self.fa[x] + return x # 返回元素根节点的集合编号 +``` + +### 2.2 完全压缩 + +> **完全压缩**:在查询时,把被查询的节点到根节点的路径上的所有节点的父节点设置为根节点,从而减小树的深度。也就是说,在向上查询的同时,把在路径上的每个节点都直接连接到根上,以后查询时就能直接查询到根节点。 + +相比较于「隔代压缩」,「完全压缩」压缩的更加彻底。下面是一个「完全压缩」的例子。 + +![路径压缩:完全压缩](https://qcdn.itcharge.cn/images/20240513154759.png) + +- 完全压缩的查找代码如下: + +```python +def find(self, x): # 查找元素根节点的集合编号内部实现方法 + if self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.find(self.fa[x]) # 完全压缩优化 + return self.fa[x] +``` + +## 3. 按秩合并 + +因为路径压缩只在查询时进行,并且只压缩一棵树上的路径,所以并查集最终的结构仍然可能是比较复杂的。为了避免这种情况,另一个优化方式是「按秩合并」。 + +> **按秩合并(Union By Rank)**:指的是在每次合并操作时,都把「秩」较小的树根节点指向「秩」较大的树根节点。 + +这里的「秩」有两种定义,一种定义指的是树的深度;另一种定义指的是树的大小(即集合节点个数)。无论采用哪种定义,集合的秩都记录在树的根节点上。 + +按秩合并也有两种方式:一种叫做「按深度合并」;另一种叫做「按大小合并」。 + +### 3.1 按深度合并 + +> **按深度合并(Unoin By Rank)**:在每次合并操作时,都把「深度」较小的树根节点指向「深度」较大的树根节点。 + +我们用一个数组 $rank$ 记录每个根节点对应的树的深度(如果不是根节点,其 $rank$ 值相当于以它作为根节点的子树的深度)。 + +初始化时,将所有元素的 $rank$ 值设为 $1$。在合并操作时,比较两个根节点,把 $rank$ 值较小的根节点指向 $rank$ 值较大的根节点上合并。 + +下面是一个「按深度合并」的例子。 + +![按秩合并:按深度合并](https://qcdn.itcharge.cn/images/20240513154814.png) + +- 按深度合并的实现代码如下: + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 + self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 + else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # 向任意一方合并即可 + self.rank[root_y] += 1 # 因为层数相同,被合并的树必然层数会 +1 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +### 3.2 按大小合并 + +> **按大小合并(Unoin By Size)**:这里的大小指的是集合节点个数。在每次合并操作时,都把「集合节点个数」较少的树根节点指向「集合节点个数」较大的树根节点。 + +我们用一个数组 $size$ 记录每个根节点对应的集合节点个数(如果不是根节点,其 $size$ 值相当于以它作为根节点的子树的集合节点个数)。 + +初始化时,将所有元素的 $size$ 值设为 $1$。在合并操作时,比较两个根节点,把 $size$ 值较小的根节点指向 $size$ 值较大的根节点上合并。 + +下面是一个「按大小合并」的例子。 + +![按秩合并:按大小合并](https://qcdn.itcharge.cn/images/20240513154835.png) + +- 按大小合并的实现代码如下: + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + self.size = [1 for i in range(n)] # 每个元素的集合个数初始化为 1 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + if self.size[root_x] < self.size[root_y]: # x 对应的集合元素个数 小于 y 对应的集合元素个数 + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + self.size[root_y] += self.size[root_x] # y 的根节点对应的集合元素个数 累加上 x 的根节点对应的集合元素个数 + elif self.size[root_x] > self.size[root_y]: # x 对应的集合元素个数 大于 y 对应的集合元素个数 + self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 + self.size[root_x] += self.size[root_y] # x 的根节点对应的集合元素个数 累加上 y 的根节点对应的集合元素个数 + else: # x 对应的集合元素个数 小于 y 对应的集合元素个数 + self.fa[root_x] = root_y # 向任意一方合并即可 + self.size[root_y] += self.size[root_x] + + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +### 3.3 按秩合并的注意点 + +看过「按深度合并」和「按大小合并」的实现代码后,大家可能会产生一个疑问:为什么在路径压缩的过程中不用更新 $rank$ 值或者 $size$ 值呢? + +其实,代码中的 $rank$ 值或者 $size$ 值并不完全是树中真实的深度或者集合元素个数。 + +这是因为当我们在代码中引入路径压缩之后,维护真实的深度或者集合元素个数就会变得比较难。此时我们使用的 $rank$ 值或者 $size$ 值更像是用于当前节点排名的一个标志数字,只在合并操作的过程中,用于比较两棵树的权值大小。 + +换句话说,我们完全可以不知道每个节点的具体深度或者集合元素个数,只要能够保证每两个节点之间的深度或者集合元素个数关系可以通过 $rank$ 值或者 $size$ 值正确的表达即可。 + +而根据路径压缩的过程,$rank$ 值或者 $size$ 值只会不断的升高,而不可能降低到比原先深度更小的节点或者集合元素个数更少的节点还要小。所以,$rank$ 值或者 $size$ 值足够用于比较两个节点的权值,进而选择合适的方式进行合并操作。 + +## 4. 并查集的算法分析 + +首先我们来分析一下并查集的空间复杂度。在代码中,我们主要使用了数组 $fa$ 来存储集合中的元素。如果使用了「按秩合并」的优化方式,还会使用数组 $rank$ 或者数组 $size$ 来存放权值。因为空间复杂度取决于元素个数,不难得出空间复杂度为 $O(n)$。 + +在同时使用了「路径压缩」和「按秩合并」的情况下,并查集的合并操作和查找操作的时间复杂度可以接近于 $O(1)$。最坏情况下的时间复杂度是 $O(m \times \alpha(n))$。这里的 $m$ 是合并操作和查找操作的次数,$\alpha(n)$ 是 Ackerman 函数的某个反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。 + +总结一下: + +- 并查集的空间复杂度:$O(n)$。 +- 并查集的时间复杂度:$O(m \times \alpha(n))$。 + +## 5. 并查集的最终实现代码 + +根据我自己的做题经验和网上大佬的经验,我使用并查集的策略(仅供参考)是这样:使用「隔代压缩」,一般不使用「按秩合并」。 + +这样选择的原因是既能保证代码简单易写,又能得到不错的性能。如果这样写的性能还不够好的话,再考虑使用「按秩合并」。 + +在有些题目中,还会遇到需要查询集合的个数或者集合中元素个数的情况,可以根据题目具体要求再做相应的更改。 + +- 使用「隔代压缩」,不使用「按秩合并」的并查集最终实现代码: + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +- 使用「隔代压缩」,使用「按秩合并」的并查集最终实现代码: + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + self.rank = [1 for i in range(n)] # 每个元素的深度初始化为 1 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + if self.rank[root_x] < self.rank[root_y]: # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + elif self.rank[root_x] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度 + self.fa[root_y] = root_x # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点 + else: # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度 + self.fa[root_x] = root_y # 向任意一方合并即可 + self.rank[root_y] += 1 # 因为层数相同,被合并的树必然层数会 +1 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) +``` + +## 6. 并查集的应用 + +并查集通常用来求解不同元素之间的关系问题,比如判断两个人是否是亲戚关系、两个点之间时候存在至少一条路径连接。或者用来求解集合的个数、集合中元素的个数等等。 + +### 6.1 等式方程的可满足性 + +#### 6.1.1 题目链接 + +- [990. 等式方程的可满足性 - 力扣(LeetCode)](https://leetcode.cn/problems/satisfiability-of-equality-equations/) + +#### 6.1.2 题目大意 + +**描述**:给定一个由字符串方程组成的数组 $equations$,每个字符串方程 $equations[i]$ 的长度为 $4$,有以下两种形式组成:`a==b` 或 `a!=b`。$a$ 和 $b$ 是小写字母,表示单字母变量名。 + +**要求**:判断所有的字符串方程是否能同时满足,如果能同时满足,返回 $True$,否则返回 $False$。 + +**说明**: + +- $1 \le equations.length \le 500$。 +- $equations[i].length == 4$。 +- $equations[i][0]$ 和 $equations[i][3]$ 是小写字母。 +- $equations[i][1]$ 要么是 `'='`,要么是 `'!'`。 +- $equations[i][2]$ 是 `'='`。 + +**示例**: + +```python +输入 ["a==b","b!=a"] +输出 False +解释 如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。 +``` + +#### 6.1.3 解题思路 + +字符串方程只有 `==` 或者 `!=`,可以考虑将相等的遍历划分到相同集合中,然后再遍历所有不等式方程,看方程的两个变量是否在之前划分的相同集合中,如果在则说明不满足。 + +这就需要用到并查集,具体操作如下: + +- 遍历所有等式方程,将等式两边的单字母变量顶点进行合并。 +- 遍历所有不等式方程,检查不等式两边的单字母遍历是不是在一个连通分量中,如果在则返回 $False$,否则继续扫描。如果所有不等式检查都没有矛盾,则返回 $True$。 + +#### 6.1.4 代码 + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) + +class Solution: + def equationsPossible(self, equations: List[str]) -> bool: + union_find = UnionFind(26) + for eqation in equations: + if eqation[1] == "=": + index1 = ord(eqation[0]) - 97 + index2 = ord(eqation[3]) - 97 + union_find.union(index1, index2) + + for eqation in equations: + if eqation[1] == "!": + index1 = ord(eqation[0]) - 97 + index2 = ord(eqation[3]) - 97 + if union_find.is_connected(index1, index2): + return False + return True +``` + +### 6.2 省份数量 + +#### 6.2.1 题目链接 + +- [547. 省份数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-provinces/) + +#### 6.2.2 题目大意 + +**描述**:有 $n$ 个城市,其中一些彼此相连,另一些没有相连。如果城市 $a$ 与城市 $b$ 直接相连,且城市 $b$ 与城市 $c$ 直接相连,那么城市 $a$ 与城市 $c$ 间接相连。 + +「省份」是由一组直接或间接链接的城市组成,组内不含有其他没有相连的城市。 + +现在给定一个 $n \times n$ 的矩阵 $isConnected$ 表示城市的链接关系。其中 `isConnected[i][j] = 1` 表示第 $i$ 个城市和第 $j$ 个城市直接相连,`isConnected[i][j] = 0` 表示第 $i$ 个城市和第 $j$ 个城市没有相连。 + +**要求**:根据给定的城市关系,返回「省份」的数量。 + +**说明**: + +- $1 \le n \le 200$。 +- $n == isConnected.length$。 +- $n == isConnected[i].length$。 +- $isConnected[i][j]$ 为 $1$ 或 $0$。 +- $isConnected[i][i] == 1$。 +- $isConnected[i][j] == isConnected[j][i]$。 + +**示例**: + +- 如图所示: + +![](https://assets.leetcode.com/uploads/2020/12/24/graph1.jpg) + +```python +输入 isConnected = [[1,1,0],[1,1,0],[0,0,1]] +输出 2 +``` + +#### 6.2.3 解题思路 + +具体做法如下: +- 遍历矩阵 $isConnected$。如果 `isConnected[i][j] = 1`,将 $i$ 节点和 $j$ 节点相连。 +- 然后判断每个城市节点的根节点,然后统计不重复的根节点有多少个,也就是集合个数,即为「省份」的数量。 + +#### 6.2.4 代码 + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) + +class Solution: + def findCircleNum(self, isConnected: List[List[int]]) -> int: + size = len(isConnected) + union_find = UnionFind(size) + for i in range(size): + for j in range(i + 1, size): + if isConnected[i][j] == 1: + union_find.union(i, j) + + res = set() + for i in range(size): + res.add(union_find.find(i)) + return len(res) +``` + +## 练习题目 + +- [0990. 等式方程的可满足性](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/satisfiability-of-equality-equations.md) +- [1202. 交换字符串中的元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/smallest-string-with-swaps.md) +- [0947. 移除最多的同行或同列石头](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md) +- [0547. 省份数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/number-of-provinces.md) +- [0684. 冗余连接](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/redundant-connection.md) +- [0765. 情侣牵手](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/couples-holding-hands.md) + +- [并查集题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%B9%B6%E6%9F%A5%E9%9B%86%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[并查集 - OI Wiki](https://oi-wiki.org/ds/dsu/) +- 【博文】[并查集 - LeetBook - 力扣](https://leetcode.cn/leetbook/detail/disjoint-set/) +- 【博文】[并查集概念及用法分析 - 掘金](https://juejin.cn/post/6844903954774491149) +- 【博文】[数据结构之并查集 - 端碗吹水的技术博客](https://blog.51cto.com/zero01/2609695) +- 【博文】[并查集复杂度 - OI Wiki](https://oi-wiki.org/ds/dsu-complexity/) +- 【题解】[使用并查集处理不相交集合问题(Java、Python) - 等式方程的可满足性 - 力扣](https://leetcode.cn/problems/satisfiability-of-equality-equations/solution/shi-yong-bing-cha-ji-chu-li-bu-xiang-jiao-ji-he-we/) +- 【书籍】算法训练营 - 陈小玉 著 +- 【书籍】算法 第 4 版 - 谢路云 译 +- 【书籍】算法竞赛进阶指南 - 李煜东 著 +- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 diff --git a/docs/05_tree/index.md b/docs/05_tree/index.md new file mode 100644 index 00000000..80ec4609 --- /dev/null +++ b/docs/05_tree/index.md @@ -0,0 +1,10 @@ +## 本章内容 + +- [5.1 树与二叉树基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_01_tree_basic.md) +- [5.2 二叉树的遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_02_binary_tree_traverse.md) +- [5.3 二叉树的还原](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_03_binary_tree_reduction.md) +- [5.4 二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_04_binary_search_tree.md) +- [5.5 线段树(一)](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_05_segment_tree_01.md) +- [5.6 线段树(二)](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_06_segment_tree_02.md) +- [5.7 树状数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_07_binary_indexed_tree.md) +- [5.8 并查集](https://github.com/ITCharge/AlgoNote/tree/main/docs/05_tree/05_08_union_find.md) \ No newline at end of file diff --git a/docs/06_graph/06_01_graph_basic.md b/docs/06_graph/06_01_graph_basic.md new file mode 100644 index 00000000..04c7c98e --- /dev/null +++ b/docs/06_graph/06_01_graph_basic.md @@ -0,0 +1,154 @@ +## 1. 图的定义 + +> **图(Graph)**:由顶点集合 $V$ 与边集合 $E$(顶点之间的关系)构成的数据结构。图的形式化定义为 $G = (V, E)$。 + +- **顶点(Vertex)**:图中的基本元素,通常称为顶点,表示对象或节点。顶点的集合 $V$ 是有限非空集合,包含 $n > 0$ 个顶点。如下面的示意图所示,通常我们使用圆圈来表示顶点。 +- **边(Edge)**:顶点之间的关系或连接。边的形式化定义为:$e = \langle u, v \rangle$,表示从 $u$ 到 $v$ 的一条边,其中 $u$ 称为起始点,$v$ 称为终止点。如下面的示意图所示,通常我们使用连接两个顶点的线段来表示边。 + +![](https://qcdn.itcharge.cn/images/20220307145142.png) + +- **子图(Sub Graph)**:对于图 $G = (V, E)$ 与 $G^{'} = (V^{'}, E^{'})$,如果满足 $V^{'} \subseteq V$,$E^{'} \subseteq E$,则称图 $G^{'}$ 是图 $G$ 的一个子图。直观的说,子图是由原图的一部分顶点和边组成的,同时边的两端顶点必须属于子图的顶点集合 $V^{'}$。特别地,根据定义,图 $G$ 本身也是其一个子图。在下图中,我们展示了一个图 $G$ 及其子图 $G^{'}$。 + +![](https://qcdn.itcharge.cn/images/20220317163120.png) + +## 2. 图的分类 + +### 2.1 无向图和有向图 + +根据边是否具有方向性,图可以分为两种类型:「无向图」和「有向图」。 + +> **无向图(Undirected Graph)**:如果图中每条边都没有方向性,则称为无向图。例如,表示朋友关系或者城市间双向行驶的路线图常用无向图建模。 + +在无向图中,每条边都是由两个顶点组成的无序对。例如下图左侧中的顶点 $v_1$ 和顶点 $v_2$ 之间的边记为 $(v_1, v_2)$ 或 $(v_2, v_1)$。 + +> **有向图(Directed Graph)**:如果图中的每条边都具有方向性,则称为有向图。例如,表示任务流程的流程图或网络请求的依赖图是典型的有向图。 + +在有向图中,有向边(又称弧)是由两个顶点组成的有序对,例如下图右侧中从顶点 $v_1$ 到顶点 $v_2$ 的弧,记为 $\langle v_1, v_2 \rangle$,$v_1$ 被称为弧尾,$v_2$ 被称为弧头,如下图所示。 + +![](https://qcdn.itcharge.cn/images/20220307160017.png) + +如果图中有 $n$ 个顶点,则根据图的类型,其边(或弧)的最大数量可以定义如下: + +- **无向图中边的最大数量**:在无向图中,任意两个顶点之间最多存在一条边,因此最多可以有 $\frac{n \times (n - 1)}{2}$ 条边。具有 $\frac{n \times (n - 1)}{2}$ 条边的无向图称为 **「完全无向图(Completed Undirected Graph)」**。 + +- **有向图中边的最大数量**:在有向图中,任意两个顶点之间可以存在一对方向相反的弧,因此最多可以有 $n \times (n - 1)$ 条弧。具有 $n \times (n - 1)$ 条弧的有向图称为 **「完全有向图(Completed Directed Graph)」**。 + +下图展示了两个示例:左侧为包含 $4$ 个顶点的完全无向图,右侧为包含 $4$ 个顶点的完全有向图。 + +![](https://qcdn.itcharge.cn/images/20220308151436.png) + +下面介绍一下无向图和有向图中一个重要概念 **「顶点的度」**。 + +> **顶点的度**:与该顶点 $v_i$ 相关联的边的数量,记为 $TD(v_i)$。 + +- **无向图中顶点的度**:在无向图中,顶点的都是与该顶点相连的边的数量。例如,在上图左侧的完全无向图中,顶点 $v_3$ 的度为 $3$,因为有 $3$ 个其他的顶点与 $v_3$ 相连接。 + +- **有向图中顶点的度**:在有向图中,顶点的度可以分为「出度」和「入度」两个部分。 + - **出度(Out Degree)**:以该顶点 $v_i$ 为出发点的边的条数,记为 $OD(v_i)$。 + - **入度(In Degree)**:以该顶点 $v_i$ 为终止点的边的条数,记为 $ID(v_i)$。 + +在有向图中,顶点 $v_i$ 的度是该点出度和入度之和,即:$TD(v_i) = OD(v_i) + ID(v_i)$。 + +例如,在上图右侧的完全有向图中,顶点 $v_3$ 的出度为 $3$,入度为 $3$,因此顶点 $v_3$ 的度为 $3 + 3 = 6$。 + +### 2.2 环形图和无环图 + +> **路径** :图中的一个重要概念,对于图 $G = (V, E)$,如果存在顶点序列 $v_{i_0}, v_{i_1}, v_{i_2}, …, v_{i_m}$,并且每对相邻的顶点都有图中的边连接,即 $(v_{i_0}, v_{i_1}), (v_{i_1}, v_{i_2}), …, (v_{i_{m-1}}, v_{i_m}) \in E$(对于有向图则是 $\langle v_{i_0}, v_{i_1} \rangle, \langle v_{i_1}, v_{i_2} \rangle, …, \langle v_{i_{m-1}}, v_{i_m} \rangle \in E$),则称该顶点序列为从顶点 $v_{i_0}$ 和顶点 $v_{i_m}$ 之间的一条路径,其中 $v_{i_0}$ 是这条路径的起始点,$v_{i_m}$ 是这条路径的终止点。 + +简而言之,如果顶点 $v_{i_0}$ 可以通过一系列的顶点和边到达顶点 $v_{i_m}$,则称这两个顶点之间有一条路径,其中经过的顶点序列则称为两个顶点之间的路径。 + +- **环(Circle)**:如果一条路径的起始点和终止点相同(即 $v_{i_0} == v_{i_m}$ ),则称这条路径为「回路」或「环」。 + +- **简单路径**:顶点序列中顶点不重复出现的路径称为「简单路径」。 + +根据图中是否有环,我们可以将图分为「环形图」和「无环图」。 + +- **环形图(Circular Graph)**:如果图中存在至少一条环路,则该图称为「环形图」。 +- **无环图(Acyclic Graph)**:如果图中不存在环路,则该图称为「无环图」。 + +在有向图中,如果不存在环路,则将该图称为「有向无环图(Directed Acyclic Graph, DAG)」。有向无环图因其独特的拓扑结构,广泛应用于诸如动态规划、最短路径问题、数据压缩等算法场景。 + +下图展示了四种图的类型:无向无环图、无向环形图、有向无环图和有向环形图。在有向环形图中,顶点 $v_1$、$v_2$、$v_3$ 与相连的边构成了一个环。 + +![环形图和无环图](https://qcdn.itcharge.cn/images/20220317115641.png) + +### 2.3 连通图和非连通图 + +#### 2.3.1 连通无向图 + +在无向图中,如果存在一条从顶点 $v_i$ 到顶点 $v_j$ 的路径,则称顶点 $v_i$ 和 $v_j$ 是连通的。 + +- **连通无向图**:如果无向图中任意两个顶点之间都是连通的(即任意两个顶点之间都有路径连接),则称该图为「连通无向图」。 +- **非连通无向图**:如果无向图中存在至少一对顶点之间没有任何路径连接,则称该图为「非连通无向图」。 + +下图展示了两种情况: + +- 在左侧图中,顶点 $v_1$ 与所有其他顶点 $v_2$、$v_3$、$v_4$、$v_5$、$v_6$ 都是连通的,因此该图为连通无向图。 +- 在右侧图中,顶点 $v_1$ 与 $v_2$、$v_3$、$v_4$ 是连通的,但与 $v_5$、$v_6$ 没有任何路径连接,因此该图为非连通无向图。 + +![](https://qcdn.itcharge.cn/images/20220317163249.png) + +#### 2.3.2 无向图的连通分量 + +在无向图中,某些图可能不是连通的,但它们的子图可能是连通的。这样的子图称为「连通子图」。对于其中某个连通子图,如果不存在任何包含他的更大连通子图,则该连通子图称为「连通分量」。 + +- **连通子图**:如果无向图的子图是连通的,则该子图称为连通子图。 +- **连通分量**:无向图中的一个极大连通子图(不存在任何包含它的更大的连通子图)称为该图的连通分量。 +- **极⼤连通⼦图**:无向图中的一个连通子图,且不存在包含它的更大的连通子图。 + +例如,上图右侧的非连通无向图中,尽管整体图是非连通的,但顶点 $v_1$、$v_2$、$v_3$、$v_4$ 与其相连的边构成的子图是连通的,并且不存在任何包含它的更大的连通子图,因此该子图是原图的一个连通分量。类似地,顶点 $v_5$、$v_6$ 与其相连的边也构成了原图的另一个连通分量。 + +#### 2.3.3 强连通有向图 + +在有向图中,如果从顶点 $v_i$ 到 $v_j$ 存在路径,且从顶点 $v_j$ 到 $v_i$ 也有路径,则称顶点 $v_i$ 与 $v_j$ 是「强连通」的。 + +- **强连通有向图**:如果图中任意两个顶点 $v_i$ 和 $v_j$ 都满足从 $v_i$ 到 $v_j$ 和从 $v_j$ 到 $v_i$ 均有路径,则称该图为「强连通有向图」。 +- **非强连通有向图**:如果图中存在至少一对顶点之间没有路径连接(即无法相互到达),则称该图为「非强连通有向图」。 + +下图展示了两种情况: + +- 左侧图中,任意两个顶点之间都存在路径,因此该图为强连通有向图。 +- 右侧图中,顶点 $v_7$ 无法通过路径到达其他顶点,因此该图为非强连通有向图。 + +![](https://qcdn.itcharge.cn/images/20220317133500.png) + +#### 2.3.4 有向图的强连通分量 + +在有向图中,「强联通分量」是指其内部任意两个顶点之间都强连通的极大强连通子图。以下是具体定义: + +- **强连通子图**:有向图的一个子图,且该子图中任意两个顶点都是强连通的。 +- **极⼤强连通⼦图**:如果一个强联通子图不能被包含在任何更大的强连通子图中,则称其为极大强连通子图。 +- **强连通分量**:有向图中的一个极⼤强连通⼦图,称为该图的强连通分量。 + +举个例子来解释一下。 + +例如,上图右侧的非强连通有向图,其本身不是强连通的(因为顶点 $v_7$ 无法通过路径到达其他顶点)。但顶点 $v_1$、$v_2$、$v_3$、$v_4$、$v_5$、$v_6$ 与它们之间的边构成了一个强连通子图(即上图的左侧图),且不存在包含它的更大的强连通子图,因此这是右侧图的一个强连通分量。类似地,顶点 $v_7$ 构成了一个只有一个顶点的强连通子图,因此它自身也是右侧图的一个强连通分量。 + +### 2.4 带权图 + +有时,图不仅需要表示顶点之间是否存在某种关系,还需要表示这一关系的具体细节。有时候我们需要给边赋予一些数据信息,这些数据信息被称为 **权**。在具体应用中,权值可以具有某种具体意义,比如权值可以代表距离、时间以及价格等不同属性。 + +- **带权图**:如果图的每条边都被赋以⼀个权值,则该图称为带权图。权值通常表示一个非负实数,但在某些场景下也可以是负数。 +- **网络**:带权的连通⽆向图被称为⽹络。 + +在下面的示意图中,我们给出了一个带权图的例子。 + +![](https://qcdn.itcharge.cn/images/20220317135207.png) + +### 2.5 稠密图和稀疏图 + +根据图中边的稀疏程度,我们可以将图分为「稠密图」和「稀疏图」。这是一个模糊的概念,目前为止还没有给出一个量化的定义。 + +- **稠密图(Dense Graph)**:有很多条边或弧(边的条数 $e$ 接近于完全图的边数)的图称为稠密图。 +- **稀疏图(Sparse Graph)**:有很少条边或弧(边的条数 $e$ 远小于完全图的边数,如 $e < n \times \log_2n$)的图称为稀疏图。 + +## 参考资料 + +- 【书籍】ACM-ICPC 程序设计系列 - 图论及应用 \- 陈宇 吴昊 主编 +- 【书籍】数据结构教程 第 3 版 - 唐发根 著 +- 【书籍】大话数据结构 - 程杰 著 +- 【书籍】算法训练营 - 陈小玉 著 +- 【书籍】Python 数据结构与算法分析 第 2 版 - 布拉德利·米勒 戴维·拉努姆 著 +- 【博文】[图的基础知识 | 小浩算法](https://www.geekxh.com/1.99.其他补充题目/50.html) +- 【博文】[链式前向星及其简单应用 | Malash's Blog](https://malash.me/200910/linked-forward-star/) + diff --git a/docs/06_graph/06_02_graph_structure.md b/docs/06_graph/06_02_graph_structure.md new file mode 100644 index 00000000..7a0512ea --- /dev/null +++ b/docs/06_graph/06_02_graph_structure.md @@ -0,0 +1,513 @@ +## 1. 图的存储结构 + +图的结构比较复杂,我们需要表示顶点和边。一个图可能有任意多个(有限个)顶点,而且任何两个顶点之间都可能存在边。我们在实现图的存储时,重点需要关注边与顶点之间的关联关系,这是图的存储的关键。 + +图的存储可以通过「顺序存储结构」和「链式存储结构」来实现。其中顺序存储结构包括邻接矩阵和边集数组。链式存储结构包括邻接表、链式前向星、十字链表和邻接多重表。 + +接下来我们来介绍几个常用的图的存储结构。在下文中,我们约定用 $n$ 代表顶点数目,$m$ 代表边数目,$TD(v_i)$ 表示顶点 $v_i$ 的度。 + +### 1.1 邻接矩阵 + +#### 1.1.1 邻接矩阵的原理描述 + +> **邻接矩阵(Adjacency Matrix)**:使用一个二维数组 $adj\underline{\hspace{0.5em}}matrix$ 来存储顶点之间的邻接关系。 +> +> - 对于无权图来说,如果 $adj\underline{\hspace{0.5em}}matrix[i][j]$ 为 $1$,则说明顶点 $v_i$ 到 $v_j$ 存在边,如果 $adj\underline{\hspace{0.5em}}matrix[i][j]$ 为 $0$,则说明顶点 $v_i$ 到 $v_j$ 不存在边。 +> - 对于带权图来说,如果 $adj\underline{\hspace{0.5em}}matrix[i][j]$ 为 $w$,并且 $w \ne \infty$(即 `w != float('inf')`),则说明顶点 $v_i$ 到 $v_j$ 的权值为 $w$。如果 $adj\underline{\hspace{0.5em}}matrix[i][j]$ 为 $\infty$(即 `float('inf')`),则说明顶点 $v_i$ 到 $v_j$ 不存在边。 + +在下面的示意图中,左侧是一个无向图,右侧则是该无向图对应的邻接矩阵结构。 + +![](https://qcdn.itcharge.cn/images/20220317144826.png) + +邻接矩阵的特点: + +- 优点:实现简单,并且可以直接查询顶点 $v_i$ 与 $v_j$ 之间是否有边存在,还可以直接查询边的权值。 +- 缺点:初始化效率和遍历效率较低,空间开销大,空间利用率低,并且不能存储重复边,也不便于增删节点。如果当顶点数目过大(比如当 $n > 10^5$)时,使用邻接矩阵建立一个 $n \times n$ 的二维数组不太现实。 + +#### 1.1.2 邻接矩阵的算法分析 + +- **时间复杂度**: + - **初始化操作**:$O(n^2)$。 + - **查询、添加或删除边操作**:$O(1)$。 + - **获取某个点的所有边操作**:$O(n)$。 + - **图的遍历操作** :$O(n^2)$。 + +- **空间复杂度**:$O(n^2)$。 + +#### 1.1.3 邻接矩阵的代码实现 + +```python +class Graph: # 基本图类,采用邻接矩阵表示 + # 图的初始化操作,ver_count 为顶点个数 + def __init__(self, ver_count): + self.ver_count = ver_count # 顶点个数 + self.adj_matrix = [[None for _ in range(ver_count)] for _ in range(ver_count)] # 邻接矩阵 + + # 判断顶点 v 是否有效 + def __valid(self, v): + return 0 <= v <= self.ver_count + + # 图的创建操作,edges 为边信息 + def creatGraph(self, edges=[]): + for vi, vj, val in edges: + self.add_edge(vi, vj, val) + + # 向图的邻接矩阵中添加边:vi - vj,权值为 val + def add_edge(self, vi, vj, val): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + self.adj_matrix[vi][vj] = val + + # 获取 vi - vj 边的权值 + def get_edge(self, vi, vj): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + return self.adj_matrix[vi][vj] + + # 根据邻接矩阵打印图的边 + def printGraph(self): + for vi in range(self.ver_count): + for vj in range(self.ver_count): + val = self.get_edge(vi, vj) + if val: + print(str(vi) + ' - ' + str(vj) + ' : ' + str(val)) + + +graph = Graph(5) +edges = [[1, 2, 5],[2, 1, 5],[1, 3, 30],[3, 1, 30],[2, 3, 14],[3, 2, 14],[2, 4, 26], [4, 2, 26]] +graph.creatGraph(edges) +print(graph.get_edge(3, 4)) +graph.printGraph() +``` + +### 1.2 边集数组 + +#### 1.2.1 边集数组的原理描述 + +> **边集数组(Edgeset Array)**:使用一个数组来存储存储顶点之间的邻接关系。数组中每个元素都包含一条边的起点 $v_i$、终点 $v_j$ 和边的权值 $val$(如果是带权图)。 + +在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的边集数组结构。 + +![](https://qcdn.itcharge.cn/images/20220317161454.png) + +#### 1.2.2 边集数组的算法分析 + +边集数组的时间复杂度: + +- 图的初始化和创建操作:$O(m)$。 +- 查询是否存在某条边:$O(m)$。 +- 遍历某个点的所有边:$O(m)$。 +- 遍历整张图:$O(nm)$。 + +边集数组的空间复杂度: + +- 空间复杂度:$O(m)$。 + +采用边集数组计算节点的度或者查找某条边时,需要遍历整个边集数组,时间复杂度为 $O(m)$,`m` 是边的数量。除非特殊必要,很少用使用边集数组来存储图。 + +一般来说,边集数组适合那些对边依次进行处理的运算,不适合对顶点的运算和对任何一条边的运算。 + +#### 1.2.3 边集数组的代码实现 + +```python +class EdgeNode: # 边信息类 + def __init__(self, vi, vj, val): + self.vi = vi # 边的起点 + self.vj = vj # 边的终点 + self.val = val # 边的权值 + +class Graph: # 基本图类,采用边集数组表示 + def __init__(self): + self.edges = [] # 边数组 + + # 图的创建操作,edges 为边信息 + def creatGraph(self, edges=[]): + for vi, vj, val in edges: + self.add_edge(vi, vj, val) + + # 向图的边数组中添加边:vi - vj,权值为 val + def add_edge(self, vi, vj, val): + edge = EdgeNode(vi, vj, val) # 创建边节点 + self.edges.append(edge) # 将边节点添加到边数组中 + + # 获取 vi - vj 边的权值 + def get_edge(self, vi, vj): + for edge in self.edges: + if vi == edge.vi and vj == edge.vj: + val = edge.val + return val + return None + + # 根据边数组打印图 + def printGraph(self): + for edge in self.edges: + print(str(edge.vi) + ' - ' + str(edge.vj) + ' : ' + str(edge.val)) + +graph = Graph() +edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] +graph.creatGraph(edges) +print(graph.get_edge(3, 4)) +graph.printGraph() +``` + +### 1.3 邻接表 + +#### 1.3.1 邻接表的原理描述 + +> **邻接表(Adjacency List)**:使用顺序存储和链式存储相结合的存储结构来存储图的顶点和边。其数据结构包括两个部分,其中一个部分是数组,主要用来存放顶点的数据信息,另一个部分是链表,用来存放边信息。 + +在邻接表的存储方法中,对于对图中每个顶点 $v_i$ 建立一个线性链表,把所有邻接于 $v_i$ 的顶点链接到单链表上。这样对于具有 `n` 个顶点的图而言,其邻接表结构由 `n` 个线性链表组成。 + +然后我们在每个顶点前边设置一个表头节点,称之为「顶点节点」。每个顶点节点由「顶点域」和「指针域」组成。其中顶点域用于存放某个顶点的数据信息,指针域用于指出该顶点第 `1` 条边所对应的链节点。 + +为了方便随机访问任意顶点的链表,通常我们会使用一组顺序存储结构(数组)存储所有「顶点节点」部分,顺序存储结构(数组)的下标表示该顶点在图中的位置。 + +在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的邻接表结构。 + +![](https://qcdn.itcharge.cn/images/20220317154531.png) + +#### 1.3.2 邻接表的算法分析 + +邻接表的时间复杂度: + +- 图的初始化和创建操作:$O(n + m)$。 +- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(TD(v_i))$。 +- 遍历某个点的所有边:$O(TD(v_i))$。 +- 遍历整张图:$O(n + m)$。 + +邻接表的空间复杂度: + +- 空间复杂度:$O(n + m)$。 + +#### 1.3.3 邻接表的代码实现 + +```python +class EdgeNode: # 边信息类 + def __init__(self, vj, val): + self.vj = vj # 边的终点 + self.val = val # 边的权值 + self.next = None # 下一条边 + +class VertexNode: # 顶点信息类 + def __init__(self, vi): + self.vi = vi # 边的起点 + self.head = None # 下一个邻接点 + +class Graph: + def __init__(self, ver_count): + self.ver_count = ver_count + self.vertices = [] + for vi in range(ver_count): + vertex = VertexNode(vi) + self.vertices.append(vertex) + + # 判断顶点 v 是否有效 + def __valid(self, v): + return 0 <= v <= self.ver_count + + # 图的创建操作,edges 为边信息 + def creatGraph(self, edges=[]): + for vi, vj, val in edges: + self.add_edge(vi, vj, val) + + # 向图的邻接表中添加边:vi - vj,权值为 val + def add_edge(self, vi, vj, val): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + vertex = self.vertices[vi] + edge = EdgeNode(vj, val) + edge.next = vertex.head + vertex.head = edge + + # 获取 vi - vj 边的权值 + def get_edge(self, vi, vj): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + vertex = self.vertices[vi] + cur_edge = vertex.head + while cur_edge: + if cur_edge.vj == vj: + return cur_edge.val + cur_edge = cur_edge.next + return None + + # 根据邻接表打印图的边 + def printGraph(self): + for vertex in self.vertices: + cur_edge = vertex.head + while cur_edge: + print(str(vertex.vi) + ' - ' + str(cur_edge.vj) + ' : ' + str(cur_edge.val)) + cur_edge = cur_edge.next + +graph = Graph(7) +edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] +graph.creatGraph(edges) +print(graph.get_edge(3, 4)) +graph.printGraph() +``` + +### 1.4 链式前向星 + +#### 1.4.1 链式前向星的原理描述 + +> **链式前向星(Linked Forward Star)**:也叫做静态邻接表,实质上就是使用静态链表实现的邻接表。链式前向星将边集数组和邻接表相结合,可以快速访问一个节点所有的邻接点,并且使用很少的额外空间。 + +链式前向星采用了一种静态链表的存储方式,可以说是目前建图和遍历效率最高的存储方式。 + +链式前向星由两种数据结构组成: + +- **特殊的边集数组**:`edges`,其中 `edges[i]` 表示第 `i` 条边。`edges[i].vj` 表示第 `i` 条边的终止点,`edges[i].val` 表示第 `i` 条边的权值,`edges[i].next` 表示与第 `i` 条边同起始点的下一条边的存储位置。 +- **头节点数组**:`head`,其中 `head[i]` 存储以顶点 `i` 为起始点的第 `1` 条边在数组 `edges` 中的下标。 + +链式前向星其实并没有改变边集数组原来的存储数学,只是利用 `head` 数组构成静态链表,建立了顶点 $v_i$ 和顶点 $v_i$ 所连第 `1` 条边的关系。 + +在下面的示意图中,左侧是一个有向图,右侧则是该有向图对应的链式前向星结构。 + +如果需要在该图中遍历顶点 $v_1$ 的所有边,则步骤如下: + +- 找到以顶点 $v_1$ 为起始点的的 `1` 条边在数组 `edges` 中的下标,即 `index = head[1] = 1 `。则在 `edges` 数组中找到与顶点 $v_1$ 相连的第 `1` 条边为 `edges[1]`,即 $\langle v_1, v_5 \rangle$,权值为 6。 +- 查找 `index = self.edges[1].next = 0 `,则在 `edges` 数组中找到与顶点 $v_1$ 相连的第 `2` 条边 `edges[0]`,即 $\langle v_1, v_2 \rangle$,权值为 5。 +- 继续查找 `index = self.edges[0].next = -1`,则不存在其余边,查找结束。 + +![](https://qcdn.itcharge.cn/images/20220317161217.png) + +#### 1.4.2 链式前向星的算法分析 + +链式前向星的时间复杂度: + +- 图的初始化和创建操作:$O(n + m)$。 +- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(TD(v_i))$。 +- 遍历某个点的所有边:$O(TD(v_i))$。 +- 遍历整张图:$O(n + m)$。 + +链式前向星的空间复杂度: + +- 空间复杂度:$O(n + m)$。 + +#### 1.4.3 链式前向星的代码实现 + +```python +class EdgeNode: # 边信息类 + def __init__(self, vj, val): + self.vj = vj # 边的终点 + self.val = val # 边的权值 + self.next = None # 下一条边 + +class Graph: + def __init__(self, ver_count, edge_count): + self.ver_count = ver_count # 顶点个数 + self.edge_count = edge_count # 边个数 + self.head = [-1 for _ in range(ver_count)] # 头节点数组 + self.edges = [] # 边集数组 + + # 判断顶点 v 是否有效 + def __valid(self, v): + return 0 <= v <= self.ver_count + + # 图的创建操作,edges 为边信息 + def creatGraph(self, edges=[]): + for i in range(len(edges)): + vi, vj, val = edges[i] + self.add_edge(i, vi, vj, val) + + # 向图的边集数组中添加边:vi - vj,权值为 val + def add_edge(self, index, vi, vj, val): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + edge = EdgeNode(vj, val) # 构造边节点 + edge.next = self.head[vi] # 边节点的 next 指向原来首指针 + self.edges.append(edge) # 边集数组添加该边 + self.head[vi] = index # 首指针指向新加边所在边集数组的下标 + + # 获取 vi - vj 边的权值 + def get_edge(self, vi, vj): + if not self.__valid(vi) or not self.__valid(vj): + raise ValueError(str(vi) + ' or ' + str(vj) + " is not a valid vertex.") + + index = self.head[vi] # 得到顶点 vi 相连的第一条边在边集数组的下标 + while index != -1: # index == -1 时说明 vi 相连的边遍历完了 + if vj == self.edges[index].vj: # 找到了 vi - vj 边 + return self.edges[index].val # 返回 vi - vj 边的权值 + index = self.edges[index].next # 取顶点 vi 相连的下一条边在边集数组的下标 + return None # 没有找到 vi - vj 边 + + # 根据链式前向星打印图的边 + def printGraph(self): + for vi in range(self.ver_count): # 遍历顶点 vi + index = self.head[vi] # 得到顶点 vi 相连的第一条边在边集数组的下标 + while index != -1: # index == -1 时说明 vi 相连的边遍历完了 + print(str(vi) + ' - ' + str(self.edges[index].vj) + ' : ' + str(self.edges[index].val)) + index = self.edges[index].next # 取顶点 vi 相连的下一条边在边集数组的下标 + + +graph = Graph(7, 7) +edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] +graph.creatGraph(edges) +print(graph.get_edge(4, 3)) +print(graph.get_edge(4, 5)) +graph.printGraph() +``` + +### 1.5 哈希表实现邻接表 + +#### 1.5.1 哈希表实现邻接表的原理描述 + +在 Python 中,通过哈希表(字典)可以轻松的实现邻接表。哈希表实现邻接表包含两个哈希表:第一个哈希表主要用来存放顶点的数据信息,哈希表的键是顶点,值是该点所有邻接边构成的另一个哈希表。另一个哈希表用来存放顶点相连的边信息,哈希表的键是边的终点,值是边的权重。 + +#### 1.5.2 哈希表实现邻接表的算法分析 + +哈希表实现邻接表的时间复杂度: + +- 图的初始化和创建操作:$O(n + m)$。 +- 查询是否存在 $v_i$ 到 $v_j$ 的边:$O(1)$。 +- 遍历某个点的所有边:$O(TD(v_i))$。 +- 遍历整张图:$O(n + m)$。 + +哈希表实现邻接表的空间复杂度: + +- 空间复杂度:$O(n + m)$。 + +#### 1.5.3 哈希表实现邻接表的代码实现 + +```python +class VertexNode: # 顶点信息类 + def __init__(self, vi): + self.vi = vi # 顶点 + self.adj_edges = dict() # 顶点的邻接边 + +class Graph: + def __init__(self): + self.vertices = dict() # 顶点 + + # 图的创建操作,edges 为边信息 + def creatGraph(self, edges=[]): + for vi, vj, val in edges: + self.add_edge(vi, vj, val) + + # 向图中添加节点 + def add_vertex(self, vi): + vertex = VertexNode(vi) + self.vertices[vi] = vertex + + # 向图的邻接表中添加边:vi - vj,权值为 val + def add_edge(self, vi, vj, val): + if vi not in self.vertices: + self.add_vertex(vi) + if vj not in self.vertices: + self.add_vertex(vj) + + self.vertices[vi].adj_edges[vj] = val + + # 获取 vi - vj 边的权值 + def get_edge(self, vi, vj): + if vi in self.vertices and vj in self.vertices[vi].adj_edges: + return self.vertices[vi].adj_edges[vj] + return None + + # 根据邻接表打印图的边 + def printGraph(self): + for vi in self.vertices: + for vj in self.vertices[vi].adj_edges: + print(str(vi) + ' - ' + str(vj) + ' : ' + str(self.vertices[vi].adj_edges[vj])) + + +graph = Graph() +edges = [[1, 2, 5],[1, 5, 6],[2, 4, 7],[4, 3, 9],[3, 1, 2],[5, 6, 8],[6, 4, 3]] +graph.creatGraph(edges) +print(graph.get_edge(3, 4)) +graph.printGraph() +``` + +## 2. 图论问题应用 + +图论和图论算法在计算机科学中扮演着很重要的角色,它提供了对很多问题都有效的一种简单而系统的建模方式。很多实际问题都可以转化为图论问题,然后使用图论的景点算法加以解决。例如: + +- 集成电路的设计和布线。 +- 互联网和路由移动电话网的路由设计。 +- 工程项目的计划安排问题。 + +常见的图论问题应用大概可以分为以下几类:**图的遍历问题**、**图的连通性问题**、**图的生成树问题**、**图的最短路径问题**、**图的网络流问题**、**二分图问题** 等等。 + +### 2.1 图的遍历问题 + +> **图的遍历**:与树的遍历类似,图的遍历指的是从图的某一个顶点出发,按照某种搜索方式对图中的所有节点都仅访问一次。 + +图的遍历是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。 + +根据搜索方式的不同,可以将图的遍历分为「深度优先搜索」和「广度优先搜索」。 + +- **深度优先搜索**:从某一顶点出发,沿着⼀条路径⼀直搜索下去,在⽆法搜索时,回退到刚刚访问过的节点。 +- **广度优先搜索**:从某个顶点出发,⼀次性访问所有未被访问的邻接点,再依次从这些已访问过的邻接点出发,⼀层⼀层地访问。 + +### 2.2 图的连通性问题 + +我们在「2.3 连通图和非连通图」中提到过「2.3.1 连通无向图和连通分量」和「2.3.2 强连通有向图和强连通分量」。 + +在无向图中,图的连通性问题主要包括:**求无向图的连通分量**、**求点双连通分量(找割点)**、**求边双连通分量(找桥)**、**全局最小割问题** 等等。 + +在有向图中,图的连通性问题主要包括:**求有向图的强连通分量**、**最小点基**、**最小权点基**、**2-SAT 问题** 等等。 + +### 2.3 图的生成树问题 + +> **图的生成树(Spanning Tree)**:如果连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。 + +图的生成树问题主要包括:**最小生成树问题**、**次小生成树问题** 和 **有向图的最小树形图问题** 等等。 + +- **无向图的最小生成树**:如果连通图 $G$ 是一个带权无向图,则生成树的边也带权,则称该带权图中所有带权生成树中权值总和最小的生成树为最小生成树(也称为最小代价生成树)。 +- **无向图的次小生成树**:如果连通图 $G$ 是一个带权无向图,生成树 $T$ 是图 $G$ 的一个最小生成树,如果有另一棵生成树 $T_1$,$T_1 \ne T$,满足不存在树 $T^{'}$,$T^{'} \ne T$,且 $w(T^{'}) < W(T_1)$,则称 $T_1$ 是图 $G$ 的次小生成树。 +- **有向图的最小树形图**:如果连通图 $G$ 是一个带权有向图,以顶点 $v_i$ 为根节点的生成树 $T$ 中,顶点 $v_i$ 到任意非 $v_i$ 顶点的路径存在且唯一,并且生成树 $T$ 中权值总和最小,则该生成树被称为有向图 $G$ 的最小树形图。 + +### 2.4 图的最短路径问题 + +> **图的最短路径问题**:如果用带权图来表示真实的交通、物流或社交网络,则边的权重可能代表交通运输费、距离或者熟悉程度。此时我们会考虑两个不同顶点之间的最短路径有多长,这一类问题统称为最短路径。并且我们称路径上的第一个顶点为源点,最后一个顶点为终点。 + +按照源点数目的不同,可以将图的最短路径问题分为 **单源最短路径问题** 和 **多源最短路径问题**。 + +- **单源最短路径问题**:从一个顶点出发到图中其余各个顶点之间的最短路径问题。 +- **多源最短路径问题**:图中任意两点之间的最短路径问题。 + +**单源最短路径问题** 的求解还是 **差分约束系统问题** 的基础。 + +除此之外,在实际应用中,有时候除了需要知道最短路径外,还需要知道次最短路径或者第三最短路径。这样的多条最短路径问题称为 **`k` 最短路径问题**。 + +### 2.5 图的网络流问题 + +> **图的网络流**:这里的「网络」指的是:带权的连通有向图。该有向图中的每条边都有一个权值(也称为容量值),当顶点之间不存在边时,两点之间的容量为 0。并且该有向图中有两个特殊的顶点:源点 $s$ 和汇点 $t$。 +> +> 这里的「流」指的是:网络上的流。如果把网络想象成一个自来水管道网络,那么流就是其中流动的水。每条边的方向表示允许的流向,边上的权值表示这条边允许通过的最大流量,也就是说每条边上的流都不能超过它的容量。并且对于除了源点 $s$ 和汇点 $t$ 外的所有点(即中继点),流入的流量都等于流出的流量。 + +图的网络流中最常见的问题就是 **网络最大流问题**。其次还有 **网络最小费用最大流问题**、**网络最小割问题**。 + +- **网络最大流**:给定一个网络,要求计算从源点流向汇点的最大流量(可以有很多条路到达汇点)。 +- **网络最小费用最大流**:给定一个网络,并且每条边都有一个费用,代表单位流量流过这条边的开销。要求计算出最大流的同时,要求花费的费用最小。 +- **网络最小割**:割是删边的意思。给定一个网络,删掉其中 $x$ 条边,从而使原本连通的网络变得不连通,要求计算出 $x$ 条边加起来最小的流量总和是多少。 + +### 2.6 二分图问题 + +> **二分图**:设 $G = (V, E)$ 是一个无向图,如果顶点 $V$ 可以分为两个互不相交的子集 $(A, B)$,并且图中每条边 $(u, v)$ 所关联的两个顶点 $u$ 和 $v$ 分别属于这两个不同的顶点集(即 $u \in A, v \in B$),则称图 $G$ 是一个二分图。 + +二分图中的常见问题有:**二分图最大匹配问题**、**二分图最大权匹配问题**、**二分图多重匹配问题**。 + +先来介绍一下匹配的概念:在二分图中,一个匹配就是一个边的集合,其中任意两条边之间都没有公共节点。 + +- **二分图最大匹配**:在一个二分图的所有匹配中,边数最多的匹配叫做该二分图的最大匹配。 +- **二分图最大权匹配**:在一个二分图的所有匹配中,边的权值和最大的匹配叫做该二分图的最大权匹配。 +- **二分图多重匹配**:在二分图最大匹配问题中,每个点最多只能和一条匹配边相关联。但是在二分图多重匹配中,每个点可以多次匹配但是有匹配上限。 + + +## 参考资料 + +- 【书籍】ACM-ICPC 程序设计系列 - 图论及应用 \- 陈宇 吴昊 主编 +- 【书籍】数据结构教程 第 3 版 - 唐发根 著 +- 【书籍】大话数据结构 - 程杰 著 +- 【书籍】算法训练营 - 陈小玉 著 +- 【书籍】Python 数据结构与算法分析 第 2 版 - 布拉德利·米勒 戴维·拉努姆 著 +- 【博文】[图的基础知识 | 小浩算法](https://www.geekxh.com/1.99.其他补充题目/50.html) +- 【博文】[链式前向星及其简单应用 | Malash's Blog](https://malash.me/200910/linked-forward-star/) +- 【博文】[图论部分简介 - OI Wiki](https://oi-wiki.org/graph/) + diff --git a/docs/06_graph/06_03_graph_dfs.md b/docs/06_graph/06_03_graph_dfs.md new file mode 100644 index 00000000..f3f0634b --- /dev/null +++ b/docs/06_graph/06_03_graph_dfs.md @@ -0,0 +1,347 @@ +## 1. 深度优先搜索简介 + +> **深度优先搜索算法(Depth First Search)**:英文缩写为 DFS,是一种用于搜索树或图结构的算法。深度优先搜索算法采用了回溯思想,从起始节点开始,沿着一条路径尽可能深入地访问节点,直到无法继续前进时为止,然后回溯到上一个未访问的节点,继续深入搜索,直到完成整个搜索过程。 + +深度优先搜索算法中所谓的深度优先,就是说优先沿着一条路径走到底,直到无法继续深入时再回头。 + +在深度优先遍历的过程中,我们需要将当前遍历节点 $u$ 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。 + +## 2. 深度优先搜索算法步骤 + +接下来我们以一个无向图为例,介绍一下深度优先搜索的算法步骤。 + +1. 选择起始节点 $u$,并将其标记为已访问。 +2. 检查当前节点是否为目标节点(看具体题目要求)。 +3. 如果当前节点 $u$ 是目标节点,则直接返回结果。 +4. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点。 +5. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归)。 +6. 如果节点 $u$ 没有未访问的相邻节点,回溯到上一个节点,继续搜索其他路径。 +7. 重复 $2 \sim 6$ 步骤,直到遍历完整个图或找到目标节点为止。 + +::: tabs#DFS + +@tab <1> + +![深度优先搜索 1](https://qcdn.itcharge.cn/images/202309042321406.png) + +@tab <2> + +![深度优先搜索 2](https://qcdn.itcharge.cn/images/202309042323911.png) + +@tab <3> + +![深度优先搜索 3](https://qcdn.itcharge.cn/images/202309042324370.png) + +@tab <4> + +![深度优先搜索 4](https://qcdn.itcharge.cn/images/202309042325587.png) + +@tab <5> + +![深度优先搜索 5](https://qcdn.itcharge.cn/images/202309042325689.png) + +@tab <6> + +![深度优先搜索 6](https://qcdn.itcharge.cn/images/202309042325770.png) + +::: + +## 3. 基于递归实现的深度优先搜索 + +### 3.1 基于递归实现的深度优先搜索算法步骤 + +深度优先搜索算法可以通过递归来实现,以下是基于递归实现的深度优先搜索算法步骤: + +1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$u$ 为当前遍历边的开始节点。定义 `def dfs_recursive(graph, u, visited):` 为递归实现的深度优先搜索方法。 +2. 选择起始节点 $u$,并将其标记为已访问,即将节点 $u$ 放入 $visited$ 中(`visited.add(u)`)。 +3. 检查当前节点 $u$ 是否为目标节点(看具体题目要求)。 +4. 如果当前节点 $u$ 是目标节点,则直接返回结果。 +5. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点。 +6. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归),即调用 `dfs_recursive(graph, v, visited)`。 +7. 如果节点 $u$ 没有未访问的相邻节点,则回溯到最近访问的节点,继续搜索其他路径。 +8. 重复 $3 \sim 7$ 步骤,直到遍历完整个图或找到目标节点为止。 + +### 3.2 基于递归实现的深度优先搜索实现代码 + +```python +class Solution: + def dfs_recursive(self, graph, u, visited): + print(u) # 访问节点 + visited.add(u) # 节点 u 标记其已访问 + + for v in graph[u]: + if v not in visited: # 节点 v 未访问过 + # 深度优先搜索遍历节点 + self.dfs_recursive(graph, v, visited) + + +graph = { + "A": ["B", "C"], + "B": ["A", "C", "D"], + "C": ["A", "B", "D", "E"], + "D": ["B", "C", "E", "F"], + "E": ["C", "D"], + "F": ["D", "G"], + "G": [] +} + +# 基于递归实现的深度优先搜索 +visited = set() +Solution().dfs_recursive(graph, "A", visited) +``` + +## 4. 基于堆栈实现的深度优先搜索 + +### 4.1 基于堆栈实现的深度优先搜索算法步骤 + +深度优先搜索算法除了基于递归实现之外,还可以基于堆栈来实现。同时,为了防止多次遍历同一节点,在使用栈存放节点访问记录时,我们将「当前节点」以及「下一个将要访问的邻接节点下标」一同存入栈中,从而在出栈时,可以通过下标直接找到下一个邻接节点,而不用遍历所有邻接节点。 + +以下是基于堆栈实现的深度优先搜索的算法步骤: + +1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$start$ 为当前遍历边的开始节点。定义 $stack$ 用于存放节点访问记录的栈结构。 +2. 选择起始节点 $u$,检查当前节点 $u$ 是否为目标节点(看具体题目要求)。 +3. 如果当前节点 $u$ 是目标节点,则直接返回结果。 +4. 如果当前节点 $u$ 不是目标节点,则将节点 $u$ 以及节点 $u$ 下一个将要访问的邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([u, 0])`,`visited.add(u)`。 +5. 如果栈不为空,取出 $stack$ 栈顶元素节点 $u$,以及节点 $u$ 下一个将要访问的邻接节点下标 $i$。 +6. 根据节点 $u$ 和下标 $i$,取出将要遍历的未访问过的邻接节点 $v$。 +7. 将节点 $u$ 以及节点 u 的下一个邻接节点下标 $i + 1$ 放入栈中。 +8. 访问节点 $v$,并对节点进行相关操作(看具体题目要求)。 +9. 将节点 $v$ 以及节点 $v$ 下一个邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([v, 0])`,`visited.add(v)`。 +10. 重复步骤 $5 \sim 9$,直到 $stack$ 栈为空或找到目标节点为止。 + +### 4.2 基于堆栈实现的深度优先搜索实现代码 + +```python +class Solution: + def dfs_stack(self, graph, u): + print(u) # 访问节点 u + visited, stack = set(), [] # 使用 visited 标记访问过的节点, 使用栈 stack 存放临时节点 + + stack.append([u, 0]) # 将节点 u,节点 u 的下一个邻接节点下标放入栈中,下次将遍历 graph[u][0] + visited.add(u) # 将起始节点 u 标记为已访问 + + + while stack: + u, i = stack.pop() # 取出节点 u,以及节点 u 下一个将要访问的邻接节点下标 i + + if i < len(graph[u]): + v = graph[u][i] # 取出邻接节点 v + stack.append([u, i + 1]) # 下一次将遍历 graph[u][i + 1] + if v not in visited: # 节点 v 未访问过 + print(v) # 访问节点 v + stack.append([v, 0]) # 下一次将遍历 graph[v][0] + visited.add(v) # 将节点 v 标记为已访问 + + +graph = { + "A": ["B", "C"], + "B": ["A", "C", "D"], + "C": ["A", "B", "D", "E"], + "D": ["B", "C", "E", "F"], + "E": ["C", "D"], + "F": ["D", "G"], + "G": [] +} + +# 基于堆栈实现的深度优先搜索 +Solution().dfs_stack(graph, "A") +``` + +## 5. 深度优先搜索应用 + +### 5.1 岛屿数量 + +#### 5.1.1 题目链接 + +- [200. 岛屿数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-islands/) + +#### 5.1.2 题目大意 + +**描述**:给定一个由字符 `'1'`(陆地)和字符 `'0'`(水)组成的的二维网格 `grid`。 + +**要求**:计算网格中岛屿的数量。 + +**说明**: + +- 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 +- 此外,你可以假设该网格的四条边均被水包围。 +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 300$。 +- $grid[i][j]$ 的值为 `'0'` 或 `'1'`。 + +**示例**: + +- 示例 1: + +```python +输入:grid = [ + ["1","1","1","1","0"], + ["1","1","0","1","0"], + ["1","1","0","0","0"], + ["0","0","0","0","0"] +] +输出:1 +``` + +- 示例 2: + +```python +输入:grid = [ + ["1","1","0","0","0"], + ["1","1","0","0","0"], + ["0","0","1","0","0"], + ["0","0","0","1","1"] +] +输出:3 +``` + +#### 5.1.3 解题思路 + +如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。 + +使用深度优先搜索或者广度优先搜索都可以。 + +##### 思路 1:深度优先搜索 + +1. 遍历 $grid$。 +2. 对于每一个字符为 `'1'` 的元素,遍历其上下左右四个方向,并将该字符置为 `'0'`,保证下次不会被重复遍历。 +3. 如果超出边界,则返回 $0$。 +4. 对于 $(i, j)$ 位置的元素来说,递归遍历的位置就是 $(i - 1, j)$、$(i, j - 1)$、$(i + 1, j)$、$(i, j + 1)$ 四个方向。每次遍历到底,统计数记录一次。 +5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。 + +##### 思路 1:代码 + +```python +class Solution: + def dfs(self, grid, i, j): + n = len(grid) + m = len(grid[0]) + if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0': + return 0 + grid[i][j] = '0' + self.dfs(grid, i + 1, j) + self.dfs(grid, i, j + 1) + self.dfs(grid, i - 1, j) + self.dfs(grid, i, j - 1) + + def numIslands(self, grid: List[List[str]]) -> int: + count = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == '1': + self.dfs(grid, i, j) + count += 1 + return count +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(m \times n)$。 + +### 5.2 克隆图 + +#### 5.2.1 题目链接 + +- [133. 克隆图 - 力扣(LeetCode)](https://leetcode.cn/problems/clone-graph/) + +#### 5.2.2 题目大意 + +**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 + +**要求**:返回该图的深拷贝。 + +**说明**: + +- 节点数不超过 $100$。 +- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 +- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 +- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 +- 图是连通图,你可以从给定节点访问到所有节点。 + +**示例**: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) + +```python +输入:adjList = [[2,4],[1,3],[2,4],[1,3]] +输出:[[2,4],[1,3],[2,4],[1,3]] +解释: +图中有 4 个节点。 +节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 +节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 +节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 +节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 +``` + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) + +```python +输入:adjList = [[2],[1]] +输出:[[2],[1]] +``` + +#### 5.2.3 解题思路 + +所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。 + +可以用深度优先搜索或者广度优先搜索来做。 + +##### 思路 1:深度优先搜索 + +1. 使用哈希表 $visitedDict$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。 +2. 从给定节点开始,以深度优先搜索的方式遍历原图。 + 1. 如果当前节点被访问过,则返回隆图中对应节点。 + 2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。 + 3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。 +3. 递归结束,返回克隆节点。 + +##### 思路 1:代码 + +```python +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + if not node: + return node + visitedDict = dict() + + def dfs(node: 'Node') -> 'Node': + if node in visitedDict: + return visitedDict[node] + + clone_node = Node(node.val, []) + visitedDict[node] = clone_node + for neighbor in node.neighbors: + clone_node.neighbors.append(dfs(neighbor)) + return clone_node + + return dfs(node) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0200. 岛屿数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) +- [0133. 克隆图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/clone-graph.md) +- [0494. 目标和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) +- [0841. 钥匙和房间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/keys-and-rooms.md) +- [0695. 岛屿的最大面积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) +- [0130. 被围绕的区域](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/surrounded-regions.md) +- [0417. 太平洋大西洋水流问题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/pacific-atlantic-water-flow.md) +- [1020. 飞地的数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/number-of-enclaves.md) +- [1254. 统计封闭岛屿的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/number-of-closed-islands.md) + +- [深度优先搜索题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[深度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/dfs/egx6xc/) +- 【文章】[算法数据结构:深度优先搜索(DFS) - 掘金](https://juejin.cn/post/6864348493721387021) +- 【文章】[Python 图的 BFS 与 DFS - 黄蜜桃的博客 - CSDN 博客](https://blog.csdn.net/qq_37738656/article/details/83027943) +- 【文章】[图的深度优先遍历(递归、非递归;邻接表,邻接矩阵)_zjq_smile 的博客 - CSDN博客](https://blog.csdn.net/zscfa/article/details/75947816) +- 【题解】[200. 岛屿数量(DFS / BFS) - 岛屿数量 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-islands/solution/number-of-islands-shen-du-you-xian-bian-li-dfs-or-/) \ No newline at end of file diff --git a/docs/06_graph/06_04_graph_bfs.md b/docs/06_graph/06_04_graph_bfs.md new file mode 100644 index 00000000..7f1e8aff --- /dev/null +++ b/docs/06_graph/06_04_graph_bfs.md @@ -0,0 +1,286 @@ +## 1. 广度优先搜索简介 + +> **广度优先搜索算法(Breadth First Search)**:英文缩写为 BFS,又译作宽度优先搜索 / 横向优先搜索,是一种用于搜索树或图结构的算法。广度优先搜索算法从起始节点开始,逐层扩展,先访问离起始节点最近的节点,后访问离起始节点稍远的节点。以此类推,直到完成整个搜索过程。 + +因为遍历到的节点顺序符合「先进先出」的特点,所以广度优先搜索可以通过「队列」来实现。 + +## 2. 广度优先搜索算法步骤 + +接下来我们以一个无向图为例,介绍一下广度优先搜索的算法步骤。 + +1. 将起始节点 $u$ 放入队列中,并标记为已访问。 +2. 从队列中取出一个节点,访问它并将其所有的未访问邻接节点 $v$ 放入队列中。 +3. 标记已访问的节点 $v$,以避免重复访问。 +4. 重复步骤 $2 \sim 3$,直到队列为空或找到目标节点。 + +::: tabs#BFS + +@tab <1> + +![广度优先搜索 1](https://qcdn.itcharge.cn/images/20230905152316.png) + +@tab <2> + +![广度优先搜索 2](https://qcdn.itcharge.cn/images/20230905152327.png) + +@tab <3> + +![广度优先搜索 3](http://qcdn.itcharge.cn/images/20231009141628.png) + +@tab <4> + +![广度优先搜索 4](https://qcdn.itcharge.cn/images/20230905152401.png) + +@tab <5> + +![广度优先搜索 5](https://qcdn.itcharge.cn/images/20230905152420.png) + +@tab <6> + +![广度优先搜索 6](https://qcdn.itcharge.cn/images/20230905152433.png) + +@tab <7> + +![广度优先搜索 7](https://qcdn.itcharge.cn/images/20230905152445.png) + +::: + +## 3. 基于队列实现的广度优先搜索 + +### 3.1 基于队列实现的广度优先搜索算法步骤 + +1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量,$queue$ 为存放节点的队列,$u$ 为开始节点,定义 `def bfs(graph, u):` 为队列实现的广度优先搜索方法。 +2. 首先将起始节点 $u$ 标记为已访问,并将其加入队列中,即 `visited.add(u)`,`queue.append(u)`。 +3. 从队列中取出队头节点 $u$。访问节点 $u$,并对节点进行相关操作(看具体题目要求)。 +4. 遍历节点 $u$ 的所有未访问邻接节点 $v$(节点 $v$ 不在 $visited$ 中)。 +5. 将节点 $v$ 标记已访问,并加入队列中,即 `visited.add(v)`,`queue.append(v)`。 +6. 重复步骤 $3 \sim 5$,直到队列 $queue$ 为空。 + +### 3.2 基于队列实现的广度优先搜索实现代码 + +```python +import collections + +class Solution: + def bfs(self, graph, u): + visited = set() # 使用 visited 标记访问过的节点 + queue = collections.deque([]) # 使用 queue 存放临时节点 + + visited.add(u) # 将起始节点 u 标记为已访问 + queue.append(u) # 将起始节点 u 加入队列中 + + while queue: # 队列不为空 + u = queue.popleft() # 取出队头节点 u + print(u) # 访问节点 u + for v in graph[u]: # 遍历节点 u 的所有未访问邻接节点 v + if v not in visited: # 节点 v 未被访问 + visited.add(v) # 将节点 v 标记为已访问 + queue.append(v) # 将节点 v 加入队列中 + + +graph = { + "0": ["1", "2"], + "1": ["0", "2", "3"], + "2": ["0", "1", "3", "4"], + "3": ["1", "2", "4", "5"], + "4": ["2", "3"], + "5": ["3", "6"], + "6": [] +} + +# 基于队列实现的广度优先搜索 +Solution().bfs(graph, "0") +``` + +## 4. 广度优先搜索应用 + +### 4.1 克隆图 + +#### 4.1.1 题目链接 + +- [133. 克隆图 - 力扣(LeetCode)](https://leetcode.cn/problems/clone-graph/) + +#### 4.1.2 题目大意 + +**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 + +**要求**:返回该图的深拷贝。 + +**说明**: + +- 节点数不超过 $100$。 +- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 +- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 +- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 +- 图是连通图,你可以从给定节点访问到所有节点。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) + +```python +输入:adjList = [[2,4],[1,3],[2,4],[1,3]] +输出:[[2,4],[1,3],[2,4],[1,3]] +解释: +图中有 4 个节点。 +节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 +节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 +节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 +节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) + +```python +输入:adjList = [[2],[1]] +输出:[[2],[1]] +``` + +#### 4.1.3 解题思路 + +##### 思路 1:广度优先搜索 + +1. 使用哈希表 $visited$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。使用队列 $queue$ 存放节点。 +2. 根据起始节点 $node$,创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列中,即 `queue.append(node)`。 +3. 从队列中取出第一个节点 $node\underline{\hspace{0.5em}}u$。访问节点 $node\underline{\hspace{0.5em}}u$。 +4. 遍历节点 $node\underline{\hspace{0.5em}}u$ 的所有未访问邻接节点 $node\underline{\hspace{0.5em}}v$(节点 $node\underline{\hspace{0.5em}}v$ 不在 $visited$ 中)。 +5. 根据节点 $node\underline{\hspace{0.5em}}v$ 创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node_v] = Node(node_v.val, [])`。 +6. 然后将节点 $node\underline{\hspace{0.5em}}v$ 放入队列 $queue$ 中,即 `queue.append(node_v)`。 +7. 重复步骤 $3 \sim 6$,直到队列 $queue$ 为空。 +8. 广度优先搜索结束,返回起始节点的克隆节点(即 $visited[node]$)。 + +##### 思路 1:代码 + +```python +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + if not node: + return node + + visited = dict() + queue = collections.deque() + + visited[node] = Node(node.val, []) + queue.append(node) + + while queue: + node_u = queue.popleft() + for node_v in node_u.neighbors: + if node_v not in visited: + visited[node_v] = Node(node_v.val, []) + queue.append(node_v) + visited[node_u].neighbors.append(visited[node_v]) + + return visited[node] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 +- **空间复杂度**:$O(n)$。 + +### 4.2 岛屿的最大面积 + +#### 4.2.1 题目链接 + +- [695. 岛屿的最大面积 - 力扣(LeetCode)](https://leetcode.cn/problems/max-area-of-island/) + +#### 4.2.2 题目大意 + +**描述**:给定一个只包含 $0$、$1$ 元素的二维数组,$1$ 代表岛屿,$0$ 代表水。一座岛的面积就是上下左右相邻的 $1$ 所组成的连通块的数目。 + +**要求**:计算出最大的岛屿面积。 + +**说明**: + +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 50$。 +- $grid[i][j]$ 为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg) + +```python +输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] +输出:6 +解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。 +``` + +- 示例 2: + +```python +输入:grid = [[0,0,0,0,0,0,0,0]] +输出:0 +``` + +#### 4.2.3 解题思路 + +##### 思路 1:广度优先搜索 + +1. 使用 $ans$ 记录最大岛屿面积。 +2. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: + 1. 将该元素置为 $0$。并使用队列 $queue$ 存储该节点位置。使用 $temp\underline{\hspace{0.5em}}ans$ 记录当前岛屿面积。 + 2. 然后从队列 $queue$ 中取出第一个节点位置 $(i, j)$。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 $0$(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`。 + 3. 不断重复上一步骤,直到队列 $queue$ 为空。 + 4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`。 +3. 将 $ans$ 作为答案返回。 + +##### 思路 1:代码 + +```python +import collections + +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + rows, cols = len(grid), len(grid[0]) + ans = 0 + for i in range(rows): + for j in range(cols): + if grid[i][j] == 1: + grid[i][j] = 0 + temp_ans = 1 + queue = collections.deque([(i, j)]) + while queue: + i, j = queue.popleft() + for direct in directs: + new_i = i + direct[0] + new_j = j + direct[1] + if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols or grid[new_i][new_j] == 0: + continue + grid[new_i][new_j] = 0 + queue.append((new_i, new_j)) + temp_ans += 1 + + ans = max(ans, temp_ans) + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(n \times m)$。 + +## 练习题目 + +- [0463. 岛屿的周长](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/island-perimeter.md) +- [0752. 打开转盘锁](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/open-the-lock.md) +- [0279. 完全平方数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) +- [0542. 01 矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/01-matrix.md) +- [0322. 零钱兑换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) +- [LCR 130. 衣橱整理](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md) + +- [广度优先搜索题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[广度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/bfs/e69rh1/) + diff --git a/docs/06_graph/06_05_graph_topological_sorting.md b/docs/06_graph/06_05_graph_topological_sorting.md new file mode 100644 index 00000000..710ea820 --- /dev/null +++ b/docs/06_graph/06_05_graph_topological_sorting.md @@ -0,0 +1,354 @@ +## 1. 拓扑排序简介 + +> **拓扑排序(Topological Sorting)**:一种对有向无环图(DAG)的所有顶点进行线性排序的方法,使得图中任意一点 $u$ 和 $v$,如果存在有向边 $$,则 $u$ 必须在 $v$ 之前出现。对有向图进行拓扑排序产生的线性序列称为满足拓扑次序的序列,简称拓扑排序。 + +图的拓扑排序是针对有向无环图(DAG)来说的,无向图和有向有环图没有拓扑排序,或者说不存在拓扑排序。 + +![有向无环图](https://qcdn.itcharge.cn/images/202405092308713.png) + +如上图中的有向无环图(DAG)所示,$v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_5 \rightarrow v_6$ 是该图的一个拓扑序列。与此同时,$v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_6 \rightarrow v_5$ 也是该图的一个拓扑序列。也就是说,对于一个有向无环图来说,拓扑序列可能不止一个。 + +## 2. 拓扑排序的实现方法 + +拓扑排序有两种实现方法,分别是「Kahn 算法」和「DFS 深度优先搜索算法」。接下来我们依次来看下它们是如何实现的。 + +### 2.1 Kahn 算法 + +> **Kahn 算法的基本思想**: +> +> 1. 不断找寻有向图中入度为 $0$ 的顶点,将其输出。 +> 2. 然后删除入度为 $0$ 的顶点和从该顶点出发的有向边。 +> 3. 重复上述操作直到图为空,或者找不到入度为 $0$ 的节点为止。 + +#### 2.1.1 Kahn 算法的实现步骤 + +1. 使用数组 $indegrees$ 用于记录图中各个顶点的入度。 +2. 维护一个入度为 $0$ 的顶点集合 $S$(可使用栈、队列、优先队列)。 +3. 每次从集合中选择任何一个没有前驱(即入度为 $0$)的顶点 $u$,将其输出到拓扑序列 $order$ 中。 +4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将顶点 $v$ 放入集合 $S$ 中。 +5. 重复上述过程,直到集合 $S$ 为空,或者图中还有顶点未被访问(说明一定存在环路,无法形成拓扑序列)。 +6. 如果不存在环路,则 $order$ 中顶点的顺序就是拓扑排序的结果。 + +#### 2.1.2 Kahn 算法的实现代码 + +```python +import collections + +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingKahn(self, graph: dict): + indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 + for u in graph: + for v in graph[u]: + indegrees[v] += 1 # 统计所有顶点入度 + + # 将入度为 0 的顶点存入集合 S 中 + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + order = [] # order 用于存储拓扑序列 + + while S: + u = S.pop() # 从集合中选择一个没有前驱的顶点 0 + order.append(u) # 将其输出到拓扑序列 order 中 + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 + if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 + S.append(v) # 将其放入集合 S 中 + + if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 + return [] + return order # 返回拓扑序列 + + + def findOrder(self, n: int, edges): + # 构建图 + graph = dict() + for i in range(n): + graph[i] = [] + + for u, v in edges: + graph[u].append(v) + + return self.topologicalSortingKahn(graph) +``` + +### 2.2 基于 DFS 实现拓扑排序算法 + +> **基于 DFS 实现拓扑排序算法的基本思想**: +> +> 1. 对于一个顶点 $u$,深度优先遍历从该顶点出发的有向边 $$。如果从该顶点 $u$ 出发的所有相邻顶点 $v$ 都已经搜索完毕,则回溯到顶点 $u$ 时,该顶点 $u$ 应该位于其所有相邻顶点 $v$ 的前面(拓扑序列中)。 +> 2. 这样一来,当我们对每个顶点进行深度优先搜索,在回溯到该顶点时将其放入栈中,则最终从栈顶到栈底的序列就是一种拓扑排序。 + +#### 2.2.1 基于 DFS 实现拓扑排序算法实现步骤 + +1. 使用集合 $visited$ 用于记录当前顶点是否被访问过,避免重复访问。 +2. 使用集合 $onStack$ 用于记录同一次深度优先搜索时,当前顶点是否被访问过。如果当前顶点被访问过,则说明图中存在环路,无法构成拓扑序列。 +3. 使用布尔变量 $hasCycle$ 用于判断图中是否存在环。 +4. 从任意一个未被访问的顶点 $u$ 出发。 + 1. 如果顶点 $u$ 在同一次深度优先搜索时被访问过,则说明存在环。 + 2. 如果当前顶点被访问或者有环时,则无需再继续遍历,直接返回。 + +5. 将顶点 $u$ 标记为被访问过,并在本次深度优先搜索中标记为访问过。然后深度优先遍历从顶点 $u$ 出发的有向边 $$。 +6. 当顶点 $u$ 的所有相邻顶点 $v$ 都被访问后,回溯前记录当前节点 $u$(将当前节点 $u$ 输出到拓扑序列 $order$ 中)。 +7. 取消本次深度优先搜索时,顶点 $u$ 的访问标记。 +8. 对其他未被访问的顶点重复 $4 \sim 7$ 步过程,直到所有节点都遍历完,或者出现环。 +9. 如果不存在环路,则将 $order$ 逆序排序后,顶点的顺序就是拓扑排序的结果。 + +#### 2.2.2 DFS 深度优先搜索算法实现代码 + +```python +import collections + +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingDFS(self, graph: dict): + visited = set() # 记录当前顶点是否被访问过 + onStack = set() # 记录同一次深搜时,当前顶点是否被访问过 + order = [] # 用于存储拓扑序列 + hasCycle = False # 用于判断是否存在环 + + def dfs(u): + nonlocal hasCycle + if u in onStack: # 同一次深度优先搜索时,当前顶点被访问过,说明存在环 + hasCycle = True + if u in visited or hasCycle: # 当前节点被访问或者有环时直接返回 + return + + visited.add(u) # 标记节点被访问 + onStack.add(u) # 标记本次深搜时,当前顶点被访问 + + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + dfs(v) # 递归访问节点 v + + order.append(u) # 后序遍历顺序访问节点 u + onStack.remove(u) # 取消本次深搜时的 顶点访问标记 + + for u in graph: + if u not in visited: + dfs(u) # 递归遍历未访问节点 u + + if hasCycle: # 判断是否存在环 + return [] # 存在环,无法构成拓扑序列 + order.reverse() # 将后序遍历转为拓扑排序顺序 + return order # 返回拓扑序列 + + def findOrder(self, n: int, edges): + # 构建图 + graph = dict() + for i in range(n): + graph[i] = [] + for v, u in edges: + graph[u].append(v) + + return self.topologicalSortingDFS(graph) +``` + +## 3. 拓扑排序的应用 + +拓扑排序可以用来解决一些依赖关系的问题,比如项目的执行顺序,课程的选修顺序等。 + +### 3.1 课程表 II + +#### 3.1.1 题目链接 + +- [210. 课程表 II - 力扣](https://leetcode.cn/problems/course-schedule-ii/) + +#### 3.1.2 题目大意 + +**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 + +**要求**:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 + +**说明**: + +- $1 \le numCourses \le 2000$。 +- $0 \le prerequisites.length \le numCourses \times (numCourses - 1)$。 +- $prerequisites[i].length == 2$。 +- $0 \le ai, bi < numCourses$。 +- $ai \ne bi$。 +- 所有$[ai, bi]$ 互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:numCourses = 2, prerequisites = [[1,0]] +输出:[0,1] +解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]。 +``` + +- 示例 2: + +```python +输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] +输出:[0,2,1,3] +解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 +因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]。 +``` + +#### 3.1.3 解题思路 + +##### 思路 1:拓扑排序 + +这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组 $order$ 即可。 + +1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 +2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 +3. 从队列中选择一个节点 $u$,并将其加入到答案数组 $order$ 中。 +4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 +5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 +6. 最后判断总的顶点数和拓扑序列中的顶点数是否相等,如果相等,则返回答案数组 $order$,否则,返回空数组。 + +##### 思路 1:代码 + +```python +import collections + +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingKahn(self, graph: dict): + indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 + for u in graph: + for v in graph[u]: + indegrees[v] += 1 # 统计所有顶点入度 + + # 将入度为 0 的顶点存入集合 S 中 + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + order = [] # order 用于存储拓扑序列 + + while S: + u = S.pop() # 从集合中选择一个没有前驱的顶点 0 + order.append(u) # 将其输出到拓扑序列 order 中 + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 + if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 + S.append(v) # 将其放入集合 S 中 + + if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 + return [] + return order # 返回拓扑序列 + + + def findOrder(self, numCourses: int, prerequisites): + graph = dict() + for i in range(numCourses): + graph[i] = [] + + for v, u in prerequisites: + graph[u].append(v) + + return self.topologicalSortingKahn(graph) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 +- **空间复杂度**:$O(n + m)$。 + +### 3.2 找到最终的安全状态 + +#### 3.2.1 题目链接 + +- [802. 找到最终的安全状态 - 力扣](https://leetcode.cn/problems/find-eventual-safe-states/) + +#### 3.2.2 题目大意 + +**描述**:给定一个有向图 $graph$,其中 $graph[i]$ 是与节点 $i$ 相邻的节点列表,意味着从节点 $i$ 到节点 $graph[i]$ 中的每个节点都有一条有向边。 + +**要求**:找出图中所有的安全节点,将其存入数组作为答案返回,答案数组中的元素应当按升序排列。 + +**说明**: + +- **终端节点**:如果一个节点没有连出的有向边,则它是终端节点。或者说,如果没有出边,则节点为终端节点。 +- **安全节点**:如果从该节点开始的所有可能路径都通向终端节点,则该节点为安全节点。 +- $n == graph.length$。 +- $1 \le n \le 10^4$。 +- $0 \le graph[i].length \le n$。 +- $0 \le graph[i][j] \le n - 1$。 +- $graph[i]$ 按严格递增顺序排列。 +- 图中可能包含自环。 +- 图中边的数目在范围 $[1, 4 \times 10^4]$ 内。 + +**示例**: + +- 示例 1: + +![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/03/17/picture1.png) + +```python +输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]] +输出:[2,4,5,6] +解释:示意图如上。 +节点 5 和节点 6 是终端节点,因为它们都没有出边。 +从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6。 +``` + +- 示例 2: + +```python +输入:graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]] +输出:[4] +解释: +只有节点 4 是终端节点,从节点 4 开始的所有路径都通向节点 4。 +``` + +#### 3.2.3 解题思路 + +##### 思路 1:拓扑排序 + +1. 根据题意可知,安全节点所对应的终点,一定是出度为 $0$ 的节点。而安全节点一定能在有限步内到达终点,则说明安全节点一定不在「环」内。 +2. 我们可以利用拓扑排序来判断顶点是否在环中。 +3. 为了找出安全节点,可以采取逆序建图的方式,将所有边进行反向。这样出度为 $0$ 的终点就变为了入度为 $0$ 的点。 +4. 然后通过拓扑排序不断移除入度为 $0$ 的点之后,如果不在「环」中的点,最后入度一定为 $0$,这些点也就是安全节点。而在「环」中的点,最后入度一定不为 $0$。 +5. 最后将所有安全的起始节点存入数组作为答案返回。 + +##### 思路 1:代码 + +```python +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingKahn(self, graph: dict): + indegrees = {u: 0 for u in graph} # indegrees 用于记录所有节点入度 + for u in graph: + for v in graph[u]: + indegrees[v] += 1 # 统计所有节点入度 + + # 将入度为 0 的顶点存入集合 S 中 + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + + while S: + u = S.pop() # 从集合中选择一个没有前驱的顶点 0 + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 + if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 + S.append(v) # 将其放入集合 S 中 + + res = [] + for u in indegrees: + if indegrees[u] == 0: + res.append(u) + + return res + + def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]: + graph_dict = {u: [] for u in range(len(graph))} + + for u in range(len(graph)): + for v in graph[u]: + graph_dict[v].append(u) # 逆序建图 + + return self.topologicalSortingKahn(graph_dict) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 是图中节点数目,$m$ 是图中边数目。 +- **空间复杂度**:$O(n + m)$。 + +## 练习题目 + +- [0207. 课程表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule.md) +- [0210. 课程表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule-ii.md) +- [0802. 找到最终的安全状态](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/find-eventual-safe-states.md) + +- [拓扑排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%9B%BE%E7%9A%84%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_06_graph_minimum_spanning_tree.md b/docs/06_graph/06_06_graph_minimum_spanning_tree.md new file mode 100644 index 00000000..7485f2a8 --- /dev/null +++ b/docs/06_graph/06_06_graph_minimum_spanning_tree.md @@ -0,0 +1,219 @@ +## 1. 最小生成树的定义 + +在了解「最小生成树」之前,我们需要要先理解 「生成树」的概念。 + +> **生成树(Spanning Tree)**:如果无向连通图 G 的一个子图是一棵包含图 G 所有顶点的树,则称该子图为 G 的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。 + +换句话说,生成树是原图 G 的一个子图,它包含了原图 G 的所有顶点,并且通过选择图中一部分边连接这些顶点,使得子图中没有环。 + +生成树有以下特点: + +1. **包含所有顶点**:生成树中包含了原图的所有顶点。 +2. **连通性**:生成树是原图的一个连通子图,意味着任意两个顶点之间都存在一条路径。 +3. **无环图**:生成树一个无环图。 +4. **边数最少**:在包含所有顶点的情况下,生成树的边数最少,其边数为顶点数减 $1$。 + +![](https://qcdn.itcharge.cn/images/20231211100145.png) + +如上图所示,左侧图 $G$ 是包含 $v_1…v_6$ 共 $6$ 个顶点 $7$ 条边在内的有权图。右侧是图 $G$ 的两个生成树,两者都包含了 $6$ 个顶点 $5$ 条边。 + +> **最小生成树(Minimum Spanning Tree)**:无向连通图 G 的所有生成树中,边的权值之和最小的生成树,被称为最小生成树。 + +最小生成树除了包含生成树的特点之外,还具有一个特点。 + +1. **边的权值之和最小**:在包含所有顶点的情况下,最小生成树的边的权重之和是所有可能的生成树中最小的。 + +![](https://qcdn.itcharge.cn/images/20231211101937.png) + +如上图所示,左侧图 $G$ 是包含 $v_1…v_6$ 共 $6$ 个顶点 $7$ 条边在内的有权图。右侧是图 $G$ 的最小生成树,包含了 $6$ 个顶点 $5$ 条边,并且所有边的权值和最小。 + +为了找到无向图的最小生成树,常用的算法有「Prim 算法」和「Kruskal 算法」。 + +- **Prim 算法**:从一个起始顶点出发,逐步选择与已经构建的树连接的最短边,直到包含所有顶点为止。 +- **Kruskal 算法**:基于边的排序和并查集数据结构,逐步添加边,并保证所选边不会构成环路,直到构建出最小生成树。 + +这两个算法都可以帮助我们找到图中的最小生成树,以满足连接所有顶点的要求同时使得总权重最小。 + +## 2. Prim 算法 + +### 2.1 Prim 算法的算法思想 + +> **Prim 算法的算法思想**:每次选择最短边来扩展最小生成树,从而保证生成树的总权重最小。算法通过不断扩展小生成树的顶点集合 $MST$,逐步构建出最小生成树。 + +### 2.2 Prim 算法的实现步骤 + +1. 将图 $G$ 中所有的顶点 $V$ 分为两个顶点集合 $V_A$ 和 $V_B$。其中 $V_A$ 为已经加入到最小生成树的顶点集合,$V_B$ 是还未加入生成树的顶点集合。 +2. 选择起始顶点 $start$,将其加入到最小生成树的顶点集合 $V_A$ 中。 +3. 从 $V_A$ 的顶点集合中选择一个顶点 $u$,然后找到连接顶点 $u$ 与 $V_B$ 之间的边中权重最小的边。 +4. 让上一步中找到的顶点和边加入到 $MST$ 中,更新 $MST$ 的顶点集合和边集合。 +5. 重复第 $3 \sim 4$ 步,直到 $MST$ 的顶点集合中包含了图中的所有顶点为止。 + +### 2.3 Prim 算法的实现代码 + +```python +class Solution: + # graph 为图的邻接矩阵,start 为起始顶点 + def Prim(self, graph, start): + size = len(graph) + vis = set() + dist = [float('inf') for _ in range(size)] + + ans = 0 # 最小生成树的边权和 + dist[start] = 0 # 初始化起始顶点到起始顶点的边权值为 0 + + for i in range(1, size): # 初始化起始顶点到其他顶点的边权值 + dist[i] = graph[start][i] + vis.add(start) # 将 start 顶点标记为已访问 + + for _ in range(size - 1): + min_dis = float('inf') + min_dis_pos = -1 + for i in range(size): + if i not in vis and dist[i] < min_dis: + min_dis = dist[i] + min_dis_pos = i + if min_dis_pos == -1: # 没有顶点可以加入 MST,图 G 不连通 + return -1 + ans += min_dis # 将顶点加入 MST,并将边权值加入到答案中 + vis.add(min_dis_pos) + for i in range(size): + if i not in vis and dist[i] > graph[min_dis_pos][i]: + dist[i] = graph[min_dis_pos][i] + return ans + +points = [[0,0]] +graph = dict() +size = len(points) +for i in range(size): + x1, y1 = points[i] + for j in range(size): + x2, y2 = points[j] + dist = abs(x2 - x1) + abs(y2 - y1) + if i not in graph: + graph[i] = dict() + if j not in graph: + graph[j] = dict() + graph[i][j] = dist + graph[j][i] = dist + + +print(Solution().Prim(graph)) +``` + +### 2.4 Prim 算法复杂度分析 + +Prim 算法的时间复杂度主要取决于以下几个因素: + +1. **初始化阶段**: + - 初始化距离数组和访问数组的时间复杂度为 $O(V)$,其中 $V$ 是图中的顶点数。 + +2. **主循环阶段**: + - 外层循环需要执行 $V-1$ 次,用于选择 $V-1$ 条边。 + - 每次循环中需要: + - 找到未访问顶点中距离最小的顶点,时间复杂度为 $O(V)$。 + - 更新相邻顶点的距离,时间复杂度为 $O(V)$。 + +因此,Prim 算法的总体复杂度为: +- 时间复杂度:$O(V^2)$,其中 $V$ 是图中的顶点数。 +- 空间复杂度:$O(V)$,主要用于存储距离数组和访问数组。 + +## 3. Kruskal 算法 + +### 3.1 Kruskal 算法的算法思想 + +> **Kruskal 算法的算法思想**:通过依次选择权重最小的边并判断其两个端点是否连接在同一集合中,从而逐步构建最小生成树。这个过程保证了最终生成的树是无环的,并且总权重最小。 + +在实际实现中,我们通常使用并查集数据结构来管理顶点的集合信息,以便高效地判断两个顶点是否在同一个集合中,以及合并集合。 + +### 3.2 Kruskal 算法的实现步骤 + +1. 将图中所有边按照权重从小到大进行排序。 +2. 将每个顶点看做是一个单独集合,即初始时每个顶点自成一个集合。 +3. 按照排好序的边顺序,按照权重从小到大,依次遍历每一条边。 +4. 对于每条边,检查其连接的两个顶点所属的集合: + 1. 如果两个顶点属于同一个集合,则跳过这条边,以免形成环路。 + 2. 如果两个顶点不属于同一个集合,则将这条边加入到最小生成树中,同时合并这两个顶点所属的集合。 +5. 重复第 $3 \sim 4$ 步,直到最小生成树中的变数等于所有节点数减 $1$ 为止。 + +### 3.3 Kruskal 算法的实现代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + + +class Solution: + def Kruskal(self, edges, size): + union_find = UnionFind(size) + + edges.sort(key=lambda x: x[2]) + + res, cnt = 0, 1 + for x, y, dist in edges: + if union_find.is_connected(x, y): + continue + ans += dist + cnt += 1 + union_find.union(x, y) + if cnt == size - 1: + return ans + return ans + + def minCostConnectPoints(self, points: List[List[int]]) -> int: + size = len(points) + edges = [] + for i in range(size): + xi, yi = points[i] + for j in range(i + 1, size): + xj, yj = points[j] + dist = abs(xi - xj) + abs(yi - yj) + edges.append([i, j, dist]) + + ans = Solution().Kruskal(edges, size) + return ans +``` + +### 3.4 Kruskal 算法复杂度分析 + +Kruskal 算法的时间复杂度主要取决于以下几个因素: + +1. **边的排序**:对 $E$ 条边进行排序的时间复杂度为 $O(E \log E)$。 + +2. **并查集操作**: + - 查找操作(find)的时间复杂度为 $O(\alpha(n))$,其中 $\alpha(n)$ 是阿克曼函数的反函数,增长极其缓慢,可以近似认为是常数时间。 + - 合并操作(union)的时间复杂度也是 $O(\alpha(n))$。 + +3. **遍历边的过程**:需要遍历所有边,时间复杂度为 $O(E)$。 + +因此,Kruskal 算法的总体时间复杂度为: +- 时间复杂度:$O(E \log E)$,其中 $E$ 是图中的边数。 +- 空间复杂度:$O(V)$,其中 $V$ 是图中的顶点数,主要用于存储并查集数据结构。 + +## 练习题目 + +- [1584. 连接所有点的最小费用](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/min-cost-to-connect-all-points.md) +- [1631. 最小体力消耗路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) +- [0778. 水位上升的泳池中游泳](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/swim-in-rising-water.md) + +- [图的最小生成树题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%9B%BE%E7%9A%84%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_07_graph_shortest_path_01.md b/docs/06_graph/06_07_graph_shortest_path_01.md new file mode 100644 index 00000000..233593ce --- /dev/null +++ b/docs/06_graph/06_07_graph_shortest_path_01.md @@ -0,0 +1,212 @@ +## 1. 单源最短路径简介 + +> **单源最短路径(Single Source Shortest Path)**:对于一个带权图 $G = (V, E)$,其中每条边的权重是一个实数。另外,给定 $v$ 中的一个顶点,称之为源点。则源点到其他所有各个顶点之间的最短路径长度,称为单源最短路径。 + +这里的路径长度,指的是路径上各边权之和。 + +单源最短路径问题的核心是找到从源点到其他各个顶点的路径,使得路径上边的权重之和最小。这个问题在许多实际应用中都非常重要,比如网络路由、地图导航、通信网络优化等。 + +常见的解决单源最短路径问题的算法包括: + +1. **Dijkstra 算法**:一种贪心算法,用于解决无负权边的情况。它逐步扩展当前已知最短路径的范围,选择当前距离起始节点最近的节点,并更新与该节点相邻的节点的距离。 +2. **Bellman-Ford 算法**:适用于有负权边的情况。它通过多次迭代来逐步逼近最短路径,每次迭代都尝试通过更新边的权重来缩短路径。 +3. **SPFA 算法**:优化的 Bellman-Ford 算法,它在每次迭代中不遍历所有的边,而是选择性地更新与当前节点相关的边,从而提高了算法的效率。 + +这些算法根据图的特点和问题的需求有所不同,选择适合的算法可以在不同情况下有效地解决单源最短路径问题。 + +## 2. Dijkstra 算法 + +### 2.1 Dijkstra 算法的算法思想 + +> **Dijkstra 算法的算法思想**:通过逐步选择距离起始节点最近的节点,并根据这些节点的路径更新其他节点的距离,从而逐步找到最短路径。 + +Dijkstra 算法是一种用来解决单源最短路径问题的算法。这个算法适用于没有负权边的图。算法的核心思想是从源点出发,逐步找到到其他所有点的最短路径。它通过不断选择当前距离源点最近的节点,并更新与该节点相邻的节点的距离,最终得到所有节点的最短路径。 + +Dijkstra 算法使用贪心的策略。它每次选择当前未处理的节点中距离源点最近的节点,认为这个节点的最短路径已经确定。然后,它用这个节点的最短路径去更新其他相邻节点的距离。这个过程重复进行,直到所有节点的最短路径都被确定。 + +Dijkstra 算法的一个重要特点是它不能处理有负权边的图。因为负权边可能导致已经确定的最短路径被破坏。如果图中存在负权边,应该使用 Bellman-Ford 算法或 SPFA 算法。 + +### 2.2 Dijkstra 算法的实现步骤 + +1. 初始化距离数组,将源节点 $source$ 的距离设为 $0$,其他节点的距离设为无穷大。 +2. 维护一个访问数组 $visited$,记录节点是否已经被访问。 +3. 每次从未访问的节点中找到距离最小的节点,标记为已访问。 +4. 更新该节点的所有相邻节点的距离。 +5. 重复步骤 $3 \sim 4$,直到所有节点都被访问。 +6. 最后返回所有节点中最大的距离值,如果存在无法到达的节点则返回 $-1$。 + + + +### 2.3 Dijkstra 算法的实现代码 + +```python +class Solution: + def dijkstra(self, graph, n, source): + # 初始化距离数组 + dist = [float('inf') for _ in range(n + 1)] + dist[source] = 0 + # 记录已处理的节点 + visited = set() + + while len(visited) < n: + # 选择当前未处理的、距离源点最近的节点 + current_node = None + min_distance = float('inf') + for i in range(1, n + 1): + if i not in visited and dist[i] < min_distance: + min_distance = dist[i] + current_node = i + + # 如果没有可处理的节点(非连通图),提前结束 + if current_node is None: + break + + # 标记当前节点为已处理 + visited.add(current_node) + + # 更新相邻节点的距离 + for neighbor, weight in graph[current_node].items(): + new_distance = dist[current_node] + weight + if new_distance < dist[neighbor]: + dist[neighbor] = new_distance + + return dist + +# 使用示例 +# 创建一个有向图,使用邻接表表示 +graph = { + 1: {2: 2, 3: 4}, + 2: {3: 1, 4: 7}, + 3: {4: 3}, + 4: {} +} +n = 4 # 图中节点数量 +source = 1 # 源节点 + +dist = Solution().dijkstra(graph, n, source) +print("从节点", source, "到其他节点的最短距离:") +for i in range(1, n + 1): + if dist[i] == float('inf'): + print(f"到节点 {i} 的距离:不可达") + else: + print(f"到节点 {i} 的距离:{dist[i]}") +``` + +### 2.4 Dijkstra 算法复杂度分析 + +- **时间复杂度**:$O(V^2)$ + - 外层循环需要遍历所有节点,时间复杂度为 $O(V)$ + - 内层循环需要遍历所有未访问的节点来找到距离最小的节点,时间复杂度为 $O(V)$ + - 因此总时间复杂度为 $O(V^2)$ + +- **空间复杂度**:$O(V)$ + - 需要存储距离数组 `dist`,大小为 $O(V)$ + - 需要存储访问集合 `visited`,大小为 $O(V)$ + - 因此总空间复杂度为 $O(V)$ + + +## 3. 堆优化的 Dijkstra 算法 + +### 3.1 堆优化的 Dijkstra 算法思想 + +> **堆优化的 Dijkstra 算法**:通过使用优先队列(堆)来优化选择最小距离节点的过程,从而降低算法的时间复杂度。 + +在原始的 Dijkstra 算法中,每次都需要遍历所有未访问的节点来找到距离最小的节点,这个过程的时间复杂度是 $O(V)$。通过使用优先队列(堆)来维护当前已知的最短距离,我们可以将这个过程的时间复杂度优化到 $O(\log V)$。 + +堆优化的主要思想是: +1. 使用优先队列存储当前已知的最短距离 +2. 每次从队列中取出距离最小的节点进行处理 +3. 当发现更短的路径时,将新的距离加入队列 +4. 通过优先队列的特性,保证每次取出的都是当前最小的距离 + +### 3.2 堆优化的 Dijkstra 算法实现步骤 + +1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大。 +2. 创建一个优先队列,将源节点及其距离 $(0, source)$ 加入队列。 +3. 当队列不为空时: + - 取出队列中距离最小的节点 + - 如果该节点的距离大于已知的最短距离,则跳过 + - 否则,遍历该节点的所有相邻节点 + - 如果通过当前节点到达相邻节点的距离更短,则更新距离并将新的距离加入队列 +4. 重复步骤 3,直到队列为空 +5. 返回所有节点的最短距离 + +### 3.3 堆优化的 Dijkstra 算法实现代码 + +```python +import heapq + +class Solution: + def dijkstra(self, graph, n, source): + # 初始化距离数组 + dist = [float('inf') for _ in range(n + 1)] + dist[source] = 0 + + # 创建优先队列,存储 (距离, 节点) 的元组 + priority_queue = [(0, source)] + + while priority_queue: + current_distance, current_node = heapq.heappop(priority_queue) + + # 如果当前距离大于已知的最短距离,跳过 + if current_distance > dist[current_node]: + continue + + # 遍历当前节点的所有相邻节点 + for neighbor, weight in graph[current_node].items(): + distance = current_distance + weight + if distance < dist[neighbor]: + dist[neighbor] = distance + heapq.heappush(priority_queue, (distance, neighbor)) + + return dist + +# 使用示例 +# 创建一个有向图,使用邻接表表示 +graph = { + 1: {2: 2, 3: 4}, + 2: {3: 1, 4: 7}, + 3: {4: 3}, + 4: {} +} +n = 4 # 图中节点数量 +source = 1 # 源节点 + +dist = Solution().dijkstra(graph, n, source) +print("从节点", source, "到其他节点的最短距离:") +for i in range(1, n + 1): + if dist[i] == float('inf'): + print(f"到节点 {i} 的距离:不可达") + else: + print(f"到节点 {i} 的距离:{dist[i]}") +``` + +代码解释: + +1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。 +2. `n` 是图中顶点的数量。 +3. `source` 是源节点的编号。 +4. `dist` 数组存储源点到各个节点的最短距离。 +5. `priority_queue` 是一个优先队列,用来选择当前距离源点最近的节点。队列中的元素是 (距离, 节点) 的元组。 +6. 主循环中,每次从队列中取出距离最小的节点。如果该节点的距离已经被更新过,跳过。 +7. 对于当前节点的每一个邻居,计算通过当前节点到达邻居的距离。如果这个距离比已知的更短,更新距离并将邻居加入队列。 +8. 最终,`dist` 数组中存储的就是源点到所有节点的最短距离。 + +### 3.4 堆优化的 Dijkstra 算法复杂度分析 + +- **时间复杂度**:$O((V + E) \log V)$ + - 每个节点最多被加入优先队列一次,每次操作的时间复杂度为 $O(\log V)$ + - 每条边最多被处理一次,每次处理的时间复杂度为 $O(\log V)$ + - 因此总时间复杂度为 $O((V + E) \log V)$ + +- **空间复杂度**:$O(V)$ + - 需要存储距离数组,大小为 $O(V)$。 + - 优先队列在最坏情况下可能存储所有节点,大小为 $O(V)$。 + +## 练习题目 + +- [0743. 网络延迟时间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/network-delay-time.md) +- [0787. K 站中转内最便宜的航班](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/cheapest-flights-within-k-stops.md) +- [1631. 最小体力消耗路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) + +- [单源最短路径题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_08_graph_shortest_path_02.md b/docs/06_graph/06_08_graph_shortest_path_02.md new file mode 100644 index 00000000..8bb4c6df --- /dev/null +++ b/docs/06_graph/06_08_graph_shortest_path_02.md @@ -0,0 +1,169 @@ +## 1. Bellman-Ford 算法 + +### 1.1 Bellman-Ford 算法的算法思想 + +> **Bellman-Ford 算法**:一种用于计算单源最短路径的算法,可以处理图中存在负权边的情况,并且能够检测负权环。 + +Bellman-Ford 算法的核心思想是: +1. 对图中的所有边进行 $V-1$ 次松弛操作,其中 $V$ 是图中顶点的数量 +2. 每次松弛操作都会尝试通过当前边来缩短源点到目标顶点的距离 +3. 如果在 $V-1$ 次松弛后还能继续松弛,说明图中存在负权环 +4. 算法可以处理负权边,但不能处理负权环 + +### 1.2 Bellman-Ford 算法的实现步骤 + +1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大 +2. 进行 $V-1$ 次迭代,每次迭代: + - 遍历图中的所有边 + - 对每条边进行松弛操作:如果通过当前边可以缩短源点到目标顶点的距离,则更新距离 +3. 进行第 $V$ 次迭代,检查是否还能继续松弛: + - 如果还能松弛,说明图中存在负权环 + - 如果不能松弛,说明已经找到最短路径 +4. 返回最短路径距离数组 + +### 1.3 Bellman-Ford 算法的实现代码 + +```python +class Solution: + def bellmanFord(self, graph, n, source): + # 初始化距离数组 + dist = [float('inf') for _ in range(n + 1)] + dist[source] = 0 + + # 进行 V-1 次迭代 + for i in range(n - 1): + # 遍历所有边 + for vi in graph: + for vj in graph[vi]: + # 松弛操作 + if dist[vj] > graph[vi][vj] + dist[vi]: + dist[vj] = graph[vi][vj] + dist[vi] + + # 检查是否存在负权环 + for vi in graph: + for vj in graph[vi]: + if dist[vj] > dist[vi] + graph[vi][vj]: + return None # 存在负权环 + + return dist +``` + +代码解释: + +1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。 +2. `n` 是图中顶点的数量。 +3. `source` 是源节点的编号。 +4. `dist` 数组存储源点到各个节点的最短距离。 +5. 外层循环进行 $V-1$ 次迭代,确保所有可能的最短路径都被找到。 +6. 内层循环遍历所有边,进行松弛操作。 +7. 最后检查是否存在负权环,如果存在则返回 None。 + +### 1.4 Bellman-Ford 算法复杂度分析 + +- **时间复杂度**:$O(VE)$ + - 需要进行 $V-1$ 次迭代 + - 每次迭代需要遍历所有边 $E$ + - 因此总时间复杂度为 $O(VE)$ + +- **空间复杂度**:$O(V)$ + - 需要存储距离数组,大小为 $O(V)$ + - 不需要额外的空间来存储图的结构,因为使用邻接表表示 + + +## 2. SPFA 算法 + +### 2.1 SPFA 算法的算法思想 + +> **SPFA 算法(Shortest Path Faster Algorithm)**:是 Bellman-Ford 算法的一种队列优化版本,通过使用队列来维护待更新的节点,从而减少不必要的松弛操作。 + +SPFA 算法的核心思想是: +1. 使用队列来维护待更新的节点,而不是像 Bellman-Ford 算法那样遍历所有边 +2. 只有当某个节点的距离被更新时,才将其加入队列 +3. 通过这种方式,避免了大量不必要的松弛操作,提高了算法的效率 +4. 算法可以处理负权边,并且能够检测负权环 + +### 2.2 SPFA 算法的实现步骤 + +1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大 +2. 创建一个队列,将源节点加入队列 +3. 当队列不为空时: + - 取出队首节点 + - 遍历该节点的所有相邻节点 + - 如果通过当前节点可以缩短到相邻节点的距离,则更新距离 + - 如果相邻节点不在队列中,则将其加入队列 +4. 重复步骤 3,直到队列为空 +5. 返回最短路径距离数组 + +### 2.3 SPFA 算法的实现代码 + +```python +from collections import deque + +def spfa(graph, n, source): + # 初始化距离数组 + dist = [float('inf') for _ in range(n + 1)] + dist[source] = 0 + + # 初始化队列和访问数组 + queue = deque([source]) + in_queue = [False] * (n + 1) + in_queue[source] = True + + # 记录每个节点入队次数,用于检测负环 + count = [0] * (n + 1) + + while queue: + # 取出队首节点 + current = queue.popleft() + in_queue[current] = False + + # 遍历当前节点的所有相邻节点 + for neighbor, weight in graph[current].items(): + # 如果通过当前节点可以缩短距离 + if dist[neighbor] > dist[current] + weight: + dist[neighbor] = dist[current] + weight + count[neighbor] += 1 + + # 如果节点入队次数超过 n-1 次,说明存在负环 + if count[neighbor] >= n: + return None + + # 如果相邻节点不在队列中,将其加入队列 + if not in_queue[neighbor]: + queue.append(neighbor) + in_queue[neighbor] = True + + return dist +``` + +代码解释: + +1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。 +2. `n` 是图中顶点的数量。 +3. `source` 是源节点的编号。 +4. `dist` 数组存储源点到各个节点的最短距离。 +5. `queue` 是一个双端队列,用于维护待更新的节点。 +6. `in_queue` 数组用于记录节点是否在队列中,避免重复入队。 +7. `count` 数组用于记录每个节点的入队次数,用于检测负环。 +8. 主循环中,每次从队列中取出一个节点,遍历其所有相邻节点,如果发现更短的路径则更新距离并将相邻节点加入队列。 +9. 如果某个节点的入队次数超过 $n-1$ 次,说明图中存在负环,返回 None。 + +### 2.4 SPFA 算法复杂度分析 + +- **时间复杂度**: + - 平均情况下:$O(kE)$,其中 $k$ 是每个节点的平均入队次数 + - 最坏情况下:$O(VE)$,与 Bellman-Ford 算法相同 + - 实际运行中,SPFA 算法通常比 Bellman-Ford 算法快很多 + +- **空间复杂度**:$O(V)$ + - 需要存储距离数组,大小为 $O(V)$ + - 需要存储队列和访问数组,大小为 $O(V)$ + - 因此总空间复杂度为 $O(V)$ + +## 练习题目 + +- [0743. 网络延迟时间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/network-delay-time.md) +- [0787. K 站中转内最便宜的航班](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/cheapest-flights-within-k-stops.md) +- [1631. 最小体力消耗路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) + +- [单源最短路径题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_09_graph_multi_source_shortest_path.md b/docs/06_graph/06_09_graph_multi_source_shortest_path.md new file mode 100644 index 00000000..c0c7db58 --- /dev/null +++ b/docs/06_graph/06_09_graph_multi_source_shortest_path.md @@ -0,0 +1,209 @@ +## 1. 多源最短路径简介 + +> **多源最短路径(All-Pairs Shortest Paths)**:对于一个带权图 $G = (V, E)$,计算图中任意两个顶点之间的最短路径长度。 + +多源最短路径问题的核心是找到图中任意两个顶点之间的最短路径。这个问题在许多实际应用中都非常重要,比如: + +1. 网络路由中的路由表计算 +2. 地图导航系统中的距离矩阵计算 +3. 社交网络中的最短关系链分析 +4. 交通网络中的最优路径规划 + +常见的解决多源最短路径问题的算法包括: + +1. **Floyd-Warshall 算法**:一种动态规划算法,可以处理负权边,但不能处理负权环。 +2. **Johnson 算法**:结合了 Bellman-Ford 算法和 Dijkstra 算法,可以处理负权边,但不能处理负权环。 +3. **重复 Dijkstra 算法**:对每个顶点运行一次 Dijkstra 算法,适用于无负权边的图。 + +## 2. Floyd-Warshall 算法 + +### 2.1 Floyd-Warshall 算法的算法思想 + +> **Floyd-Warshall 算法**:一种动态规划算法,通过逐步考虑中间顶点来更新任意两点之间的最短路径。 + +Floyd-Warshall 算法的核心思想是: + +1. 对于图中的任意两个顶点 $i$ 和 $j$,考虑是否存在一个顶点 $k$,使得从 $i$ 到 $k$ 再到 $j$ 的路径比已知的从 $i$ 到 $j$ 的路径更短 +2. 如果存在这样的顶点 $k$,则更新从 $i$ 到 $j$ 的最短路径 +3. 通过考虑所有可能的中间顶点 $k$,最终得到任意两点之间的最短路径 + +### 2.2 Floyd-Warshall 算法的实现步骤 + +1. 初始化距离矩阵 $dist$,其中 $dist[i][j]$ 表示从顶点 $i$ 到顶点 $j$ 的最短路径长度 +2. 对于每对顶点 $(i, j)$,如果存在边 $(i, j)$,则 $dist[i][j]$ 设为边的权重,否则设为无穷大 +3. 对于每个顶点 $k$,作为中间顶点: + - 对于每对顶点 $(i, j)$,如果 $dist[i][k] + dist[k][j] < dist[i][j]$,则更新 $dist[i][j]$ +4. 重复步骤 3,直到考虑完所有可能的中间顶点 +5. 返回最终的距离矩阵 + +### 2.3 Floyd-Warshall 算法的实现代码 + +```python +def floyd_warshall(graph, n): + # 初始化距离矩阵 + dist = [[float('inf') for _ in range(n)] for _ in range(n)] + + # 设置直接相连的顶点之间的距离 + for i in range(n): + dist[i][i] = 0 + for j, weight in graph[i].items(): + dist[i][j] = weight + + # 考虑每个顶点作为中间顶点 + for k in range(n): + for i in range(n): + for j in range(n): + if dist[i][k] != float('inf') and dist[k][j] != float('inf'): + dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) + + return dist +``` + +代码解释: + +1. `graph` 是一个字典,表示图的邻接表。例如,`graph[0] = {1: 3, 2: 4}` 表示从节点 0 到节点 1 的边权重为 3,到节点 2 的边权重为 4。 +2. `n` 是图中顶点的数量。 +3. `dist` 是一个二维数组,存储任意两点之间的最短路径长度。 +4. 首先初始化距离矩阵,将对角线元素设为 0,表示顶点到自身的距离为 0。 +5. 然后设置直接相连的顶点之间的距离。 +6. 主循环中,对于每个顶点 $k$,考虑它作为中间顶点时,是否能缩短其他顶点之间的距离。 +7. 最终返回的距离矩阵中,$dist[i][j]$ 表示从顶点 $i$ 到顶点 $j$ 的最短路径长度。 + +### 2.4 Floyd-Warshall 算法复杂度分析 + +- **时间复杂度**:$O(V^3)$ + - 需要三层嵌套循环,分别遍历所有顶点 + - 因此总时间复杂度为 $O(V^3)$ + +- **空间复杂度**:$O(V^2)$ + - 需要存储距离矩阵,大小为 $O(V^2)$ + - 不需要额外的空间来存储图的结构,因为使用邻接表表示 + +Floyd-Warshall 算法的主要优势在于: + +1. 实现简单,容易理解 +2. 可以处理负权边 +3. 可以检测负权环(如果某个顶点到自身的距离变为负数,说明存在负权环) +4. 适用于稠密图 + +主要缺点: + +1. 时间复杂度较高,不适用于大规模图 +2. 空间复杂度较高,需要存储完整的距离矩阵 +3. 不能处理负权环 + +## 3. Johnson 算法 + +### 3.1 Johnson 算法的算法思想 + +> **Johnson 算法**:一种结合了 Bellman-Ford 算法和 Dijkstra 算法的多源最短路径算法,可以处理负权边,但不能处理负权环。 + +Johnson 算法的核心思想是: + +1. 通过重新赋权,将图中的负权边转换为非负权边 +2. 对每个顶点运行一次 Dijkstra 算法,计算最短路径 +3. 将结果转换回原始权重 + +### 3.2 Johnson 算法的实现步骤 + +1. 添加一个新的顶点 $s$,并添加从 $s$ 到所有其他顶点的边,权重为 0 +2. 使用 Bellman-Ford 算法计算从 $s$ 到所有顶点的最短路径 $h(v)$ +3. 重新赋权:对于每条边 $(u, v)$,新的权重为 $w(u, v) + h(u) - h(v)$ +4. 对每个顶点 $v$,使用 Dijkstra 算法计算从 $v$ 到所有其他顶点的最短路径 +5. 将结果转换回原始权重:对于从 $u$ 到 $v$ 的最短路径,原始权重为 $d(u, v) - h(u) + h(v)$ + +### 3.3 Johnson 算法的实现代码 + +```python +from collections import defaultdict +import heapq + +def johnson(graph, n): + # 添加新顶点 s + new_graph = defaultdict(dict) + for u in graph: + for v, w in graph[u].items(): + new_graph[u][v] = w + new_graph[n][u] = 0 # 从 s 到所有顶点的边权重为 0 + + # 使用 Bellman-Ford 算法计算 h(v) + h = [float('inf')] * (n + 1) + h[n] = 0 + + for _ in range(n): + for u in new_graph: + for v, w in new_graph[u].items(): + if h[v] > h[u] + w: + h[v] = h[u] + w + + # 检查是否存在负权环 + for u in new_graph: + for v, w in new_graph[u].items(): + if h[v] > h[u] + w: + return None # 存在负权环 + + # 重新赋权 + reweighted_graph = defaultdict(dict) + for u in graph: + for v, w in graph[u].items(): + reweighted_graph[u][v] = w + h[u] - h[v] + + # 对每个顶点运行 Dijkstra 算法 + dist = [[float('inf') for _ in range(n)] for _ in range(n)] + for source in range(n): + # 初始化距离数组 + d = [float('inf')] * n + d[source] = 0 + + # 使用优先队列 + pq = [(0, source)] + visited = set() + + while pq: + current_dist, u = heapq.heappop(pq) + if u in visited: + continue + visited.add(u) + + for v, w in reweighted_graph[u].items(): + if d[v] > current_dist + w: + d[v] = current_dist + w + heapq.heappush(pq, (d[v], v)) + + # 转换回原始权重 + for v in range(n): + if d[v] != float('inf'): + dist[source][v] = d[v] - h[source] + h[v] + + return dist +``` + +代码解释: + +1. `graph` 是一个字典,表示图的邻接表。 +2. `n` 是图中顶点的数量。 +3. 首先添加一个新的顶点 $s$,并添加从 $s$ 到所有其他顶点的边,权重为 0。 +4. 使用 Bellman-Ford 算法计算从 $s$ 到所有顶点的最短路径 $h(v)$。 +5. 检查是否存在负权环,如果存在则返回 None。 +6. 重新赋权,将图中的负权边转换为非负权边。 +7. 对每个顶点运行一次 Dijkstra 算法,计算最短路径。 +8. 将结果转换回原始权重,得到最终的距离矩阵。 + +### 3.4 Johnson 算法复杂度分析 + +- **时间复杂度**:$O(VE \log V)$ + - 需要运行一次 Bellman-Ford 算法,时间复杂度为 $O(VE)$ + - 需要运行 $V$ 次 Dijkstra 算法,每次时间复杂度为 $O(E \log V)$ + - 因此总时间复杂度为 $O(VE \log V)$ + +- **空间复杂度**:$O(V^2)$ + - 需要存储距离矩阵,大小为 $O(V^2)$ + - 需要存储重新赋权后的图,大小为 $O(E)$ + - 因此总空间复杂度为 $O(V^2)$ + +## 练习题目 + +- [0815. 公交路线](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/bus-routes.md) +- [1162. 地图分析](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/as-far-from-land-as-possible.md) + +- [多源最短路径题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%A4%9A%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_10_graph_the_second_shortest_path.md b/docs/06_graph/06_10_graph_the_second_shortest_path.md new file mode 100644 index 00000000..47864e21 --- /dev/null +++ b/docs/06_graph/06_10_graph_the_second_shortest_path.md @@ -0,0 +1,137 @@ +## 1.1 次短路径简介 + +> **次短路径**:给定一个带权有向图,求从起点到终点的次短路径。次短路径是指长度严格大于最短路径的所有路径中长度最小的那条路径。 + +### 1.1.1 问题特点 + +- 次短路径必须严格大于最短路径 +- 可能存在多条最短路径,但次短路径是唯一的 +- 如果不存在次短路径(如最短路径是唯一的),则返回 $-1$。 + +### 1.1.2 常见变体 + +1. 允许重复边的次短路径 +2. 不允许重复边的次短路径 +3. 带约束条件的次短路径(如必须经过某些节点) + +## 1.2 次短路径基本思路 + +求解次短路径的常用方法是使用 Dijkstra 算法的变体。基本思路如下: + +1. 使用 Dijkstra 算法找到最短路径。 +2. 在寻找最短路径的过程中,同时维护次短路径。 +3. 对于每个节点,我们需要维护两个距离值: + - $dist1[u]$:从起点到节点 u 的最短距离。 + - $dist2[u]$:从起点到节点 u 的次短距离。 + +### 1.2.1 具体实现步骤 + +1. 初始化 $dist1$ 和 $dist2$ 数组,所有值设为无穷大。 +2. 将起点加入优先队列,距离为 $0$。 +3. 每次从队列中取出距离最小的节点 $u$。 +4. 遍历 $u$ 的所有邻接节点 $v$: + - 如果找到更短的路径,更新 $dist1[v]$。 + - 如果找到次短的路径,更新 $dist2[v]$。 +5. 最终 $dist2[target]$ 即为所求的次短路径长度。 + +### 1.2.2 算法正确性证明 + +1. 对于任意节点 $u$,$dist1[u]$ 一定是最短路径长度。 +2. 对于任意节点 $u$,$dist2[u]$ 一定是次短路径长度。 +3. 算法会考虑所有可能的路径,因此不会遗漏次短路径。 + +## 1.3 次短路径代码实现 + +```python +import heapq + +def second_shortest_path(n: int, edges: List[List[int]], start: int, end: int) -> int: + """ + 计算从起点到终点的次短路径长度 + + 参数: + n: 节点数量 + edges: 边列表,每个元素为 [起点, 终点, 权重] + start: 起始节点 + end: 目标节点 + + 返回: + 次短路径的长度,如果不存在则返回 -1 + """ + # 构建邻接表 + graph = [[] for _ in range(n)] + for u, v, w in edges: + graph[u].append((v, w)) + + # 初始化距离数组 + dist1 = [float('inf')] * n # 最短距离 + dist2 = [float('inf')] * n # 次短距离 + dist1[start] = 0 + + # 优先队列,存储 (距离, 节点) 的元组 + pq = [(0, start)] + + while pq: + d, u = heapq.heappop(pq) + + # 如果当前距离大于次短距离,跳过 + if d > dist2[u]: + continue + + # 遍历所有邻接节点 + for v, w in graph[u]: + # 计算新的距离 + new_dist = d + w + + # 如果找到更短的路径 + if new_dist < dist1[v]: + dist2[v] = dist1[v] # 原来的最短路径变成次短路径 + dist1[v] = new_dist # 更新最短路径 + heapq.heappush(pq, (new_dist, v)) + # 如果找到次短的路径 + elif new_dist > dist1[v] and new_dist < dist2[v]: + dist2[v] = new_dist + heapq.heappush(pq, (new_dist, v)) + + return dist2[end] if dist2[end] != float('inf') else -1 + +# 使用示例 +n = 4 +edges = [ + [0, 1, 1], + [1, 2, 2], + [2, 3, 1], + [0, 2, 4], + [1, 3, 5] +] +start = 0 +end = 3 + +result = second_shortest_path(n, edges, start, end) +print(f"次短路径长度: {result}") +``` + +## 1.4 算法复杂度分析 + +- 时间复杂度:$O((V + E)\log V)$,其中 $V$ 是节点数,$E$ 是边数。 +- 空间复杂度:$O(V)$,用于存储距离数组和优先队列。 + +## 1.5 应用场景 + +1. 网络路由:寻找备用路径。 +2. 交通规划:寻找替代路线。 +3. 通信网络:寻找备用通信路径。 +4. 物流配送:规划备用配送路线。 + +## 1.6 注意事项 + +1. 次短路径必须严格大于最短路径。 +2. 如果不存在次短路径,返回 $-1$。 +3. 图中可能存在负权边,此时需要使用 Bellman-Ford 算法的变体。 +4. 对于无向图,需要将每条边都加入两次。 + +## 练习题目 + +- [2045. 到达目的地的第二短时间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/second-minimum-time-to-reach-destination.md) + +- [次短路径题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%AC%A1%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_11_graph_system_of_difference_constraints.md b/docs/06_graph/06_11_graph_system_of_difference_constraints.md new file mode 100644 index 00000000..8a3b184b --- /dev/null +++ b/docs/06_graph/06_11_graph_system_of_difference_constraints.md @@ -0,0 +1,127 @@ +## 1.1 差分约束系统简介 + +> **差分约束系统(System of Difference Constraints)**:一种特殊的线性规划问题,其中每个约束条件都是形如 $x_i - x_j \leq c$ 的不等式。这类问题可以通过图论中的最短路径算法来求解。 + +## 1.2 问题形式 + +给定一组形如 $x_i - x_j \leq c$ 的约束条件,其中: + +- $x_i, x_j$ 是变量。 +- $c$ 是常数。 + +我们的目标是找到一组满足所有约束条件的变量值。 + +## 1.3 图论建模 + +差分约束系统可以转化为有向图问题: + +1. 将每个变量 $x_i$ 看作图中的一个顶点。 +2. 对于约束 $x_i - x_j \leq c$,添加一条从 $j$ 到 $i$ 的边,权重为 $c$。 +3. 添加一个虚拟源点 $s$,向所有顶点连一条权重为 $0$ 的边。 + +## 1.4 求解方法 + +1. **Bellman-Ford 算法**: + - 如果图中存在负环,则无解。 + - 否则,从源点到各点的最短路径长度即为对应变量的解。 + +2. **SPFA 算法**: + - 队列优化的 Bellman-Ford 算法。 + - 适用于稀疏图。 + +## 1.5 应用场景 + +1. 任务调度问题 +2. 区间约束问题 +3. 资源分配问题 +4. 时间序列分析 + +## 1.6 代码实现 + +```python +def solve_difference_constraints(n, constraints): + # 构建图 + graph = [[] for _ in range(n + 1)] + for i, j, c in constraints: + graph[j].append((i, c)) + + # 添加虚拟源点 + for i in range(n): + graph[n].append((i, 0)) + + # Bellman-Ford 算法 + dist = [float('inf')] * (n + 1) + dist[n] = 0 + + # 松弛操作 + for _ in range(n): + for u in range(n + 1): + for v, w in graph[u]: + if dist[u] + w < dist[v]: + dist[v] = dist[u] + w + + # 检查负环 + for u in range(n + 1): + for v, w in graph[u]: + if dist[u] + w < dist[v]: + return None # 存在负环,无解 + + return dist[:n] # 返回前 n 个变量的解 +``` + +## 1.7 算法复杂度 + +- 时间复杂度: + + - **Bellman-Ford 算法**: + + - 最坏情况:$O(VE)$。 + + - 其中 $V$ 为顶点数,$E$ 为边数。 + + - 需要进行 $V-1$ 次松弛操作,每次操作遍历所有边。 + + - **SPFA 算法**: + - 平均情况:$O(kE)$,其中 $k$ 为每个点的平均入队次数。 + - 最坏情况:$O(VE)$。 + - 实际运行时间通常优于 Bellman-Ford 算法。 + +- 空间复杂度: + + - **Bellman-Ford 算法**: + + - $O(V + E)$ + + - 需要存储图结构:$O(V + E)$。 + + - 需要存储距离数组:$O(V)$。 + + - **SPFA 算法**: + + - $O(V + E)$。 + + - 需要存储图结构:$O(V + E)$。 + + - 需要存储距离数组:$O(V)$。 + + - 需要存储队列:$O(V)$。 + +### 1.8 优化建议 + +1. 对于稀疏图,优先使用 SPFA 算法。 +2. 对于稠密图,可以考虑使用 Bellman-Ford 算法。 +3. 如果问题规模较大,可以考虑使用其他优化算法或启发式方法。 + +### 1.9 注意事项 + +1. 差分约束系统可能有多个解 +2. 如果存在负环,则无解 +3. 实际应用中需要注意数值精度问题 +4. 对于大规模问题,可以考虑使用其他优化算法 + +## 练习题目 + +- [0995. K 连续位的最小翻转次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md) +- [1109. 航班预订统计](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/corporate-flight-bookings.md) + +- [差分约束系统题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%B7%AE%E5%88%86%E7%BA%A6%E6%9D%9F%E7%B3%BB%E7%BB%9F%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_12_graph_bipartite_basic.md b/docs/06_graph/06_12_graph_bipartite_basic.md new file mode 100644 index 00000000..65824e5e --- /dev/null +++ b/docs/06_graph/06_12_graph_bipartite_basic.md @@ -0,0 +1,78 @@ +## 1.1 二分图的定义 + +> **二分图(Bipartite Graph)**:一种特殊的图,其顶点集可以被划分为两个互不相交的子集,使得图中的每一条边都连接着这两个子集中的顶点。换句话说,二分图中的顶点可以被分成两组,使得同一组内的顶点之间没有边相连。 + +## 1.2 二分图的性质 + +1. **染色性质**:二分图是二色的,即可以用两种颜色对顶点进行着色,使得相邻顶点颜色不同。 +2. **无奇环**:二分图中不存在长度为奇数的环。 +3. **最大匹配**:二分图的最大匹配问题可以通过匈牙利算法或网络流算法高效求解。 + +## 1.3 二分图的判定 + +判断一个图是否为二分图的方法: + +1. 使用深度优先搜索(DFS)或广度优先搜索(BFS)进行二着色 +2. 如果在染色过程中发现相邻顶点颜色相同,则该图不是二分图 +3. 如果能够成功完成二着色,则该图是二分图 + +## 1.4 二分图的应用场景 + +1. **任务分配**:将工人和任务分别作为两个顶点集,边表示工人可以完成的任务 +2. **婚姻匹配**:将男性和女性分别作为两个顶点集,边表示可能的配对关系 +3. **网络流问题**:许多网络流问题可以转化为二分图最大匹配问题 +4. **资源分配**:将资源和需求分别作为两个顶点集,边表示资源可以满足的需求 + +## 1.5 二分图的基本算法 + +1. **匈牙利算法**:用于求解二分图的最大匹配 +2. **Hopcroft-Karp算法**:用于求解二分图的最大匹配,时间复杂度更优 +3. **网络流算法**:将二分图最大匹配问题转化为最大流问题求解 + +## 1.6 二分图的判定代码 + +```python +def is_bipartite(graph): + """ + 判断图是否为二分图 + :param graph: 邻接表表示的图 + :return: 是否为二分图 + """ + n = len(graph) + colors = [0] * n # 0表示未染色,1和-1表示两种不同的颜色 + + def dfs(node, color): + colors[node] = color + for neighbor in graph[node]: + if colors[neighbor] == color: + return False + if colors[neighbor] == 0 and not dfs(neighbor, -color): + return False + return True + + for i in range(n): + if colors[i] == 0 and not dfs(i, 1): + return False + return True +``` + +## 1.7 常见问题类型 + +1. 判断图是否为二分图 +2. 求二分图的最大匹配 +3. 求二分图的最小点覆盖 +4. 求二分图的最大独立集 +5. 求二分图的最小路径覆盖 + +## 1.8 注意事项 + +1. 在实现二分图算法时,需要注意图的表示方式(邻接表或邻接矩阵) +2. 对于大规模图,需要考虑算法的空间复杂度 +3. 在实际应用中,可能需要根据具体问题对基本算法进行优化 +4. 处理有向图时,需要先将其转换为无向图再判断是否为二分图 + +## 练习题目 + +- [0785. 判断二分图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/is-graph-bipartite.md) + +- [二分图基础题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%88%86%E5%9B%BE%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/06_13_graph_bipartite_matching.md b/docs/06_graph/06_13_graph_bipartite_matching.md new file mode 100644 index 00000000..ce29d25d --- /dev/null +++ b/docs/06_graph/06_13_graph_bipartite_matching.md @@ -0,0 +1,285 @@ +## 1. 二分图最大匹配简介 + +> **二分图最大匹配(Maximum Bipartite Matching)**:图论中的一个重要问题。在二分图中,我们需要找到最大的匹配数,即最多可以有多少对顶点之间形成匹配。 + +- **二分图**:图中的顶点可以被分成两个独立的集合,使得每条边的两个端点分别属于这两个集合。 +- **匹配**:一组边的集合,其中任意两条边都没有共同的顶点。 +- **最大匹配**:包含边数最多的匹配。 + +### 1.1 应用场景 + +二分图最大匹配在实际应用中有广泛的应用: + +1. **任务分配**:将任务分配给工人,每个工人只能完成一个任务 +2. **婚姻匹配**:将男生和女生进行配对 +3. **网络流问题**:可以转化为最大流问题求解 +4. **资源分配**:将资源分配给需求方 +5. **学生选课**:将学生与课程进行匹配 +6. **网络路由**:将数据包与可用路径进行匹配 + +### 1.2 优化方法 + +1. **使用邻接表**:对于稀疏图,使用邻接表可以显著减少空间复杂度 +2. **双向搜索**:同时从左右两侧进行搜索,可以减少搜索次数 +3. **预处理**:对图进行预处理,去除不可能形成匹配的边 +4. **贪心匹配**:先进行贪心匹配,减少后续搜索的复杂度 +5. **并行处理**:对于大规模图,可以使用并行算法提高效率 + +## 2. 匈牙利算法 + +### 2.1 匈牙利算法基本思想 + +匈牙利算法(Hungarian Algorithm)是求解二分图最大匹配的经典算法。其基本思想是: + +1. 从左侧集合中任选一个未匹配的点开始 +2. 尝试寻找增广路径 +3. 如果找到增广路径,则更新匹配 +4. 重复以上步骤直到无法找到增广路径 + +### 2.2 匈牙利算法实现代码 + +```python +def max_bipartite_matching(graph, left_size, right_size): + # 初始化匹配数组 + match_right = [-1] * right_size + result = 0 + + # 对左侧每个顶点尝试匹配 + for left in range(left_size): + # 记录右侧顶点是否被访问过 + visited = [False] * right_size + + # 如果找到增广路径,则匹配数加1 + if find_augmenting_path(graph, left, visited, match_right): + result += 1 + + return result + +def find_augmenting_path(graph, left, visited, match_right): + # 遍历右侧所有顶点 + for right in range(len(graph[left])): + # 如果存在边且右侧顶点未被访问 + if graph[left][right] and not visited[right]: + visited[right] = True + + # 如果右侧顶点未匹配,或者可以找到新的匹配 + if match_right[right] == -1 or find_augmenting_path(graph, match_right[right], visited, match_right): + match_right[right] = left + return True + + return False +``` + +### 2.3 匈牙利算法时间复杂度 + +- 匈牙利算法的时间复杂度为 O(VE),其中 V 是顶点数,E 是边数 +- 使用邻接矩阵存储图时,空间复杂度为 O(V²) +- 使用邻接表存储图时,空间复杂度为 O(V + E) + + +## 3. Hopcroft-Karp 算法 + +### 3.1 Hopcroft-Karp 算法基本思想 + +Hopcroft-Karp 算法是求解二分图最大匹配的一个更高效的算法,时间复杂度为 O(√VE)。其基本思想是: + +1. 同时寻找多条不相交的增广路径 +2. 使用 BFS 分层,然后使用 DFS 寻找增广路径 +3. 每次迭代可以找到多条增广路径 + + +### 3.2 Hopcroft-Karp 算法实现代码 + +```python +from collections import deque + +def hopcroft_karp(graph, left_size, right_size): + # 初始化匹配数组 + match_left = [-1] * left_size + match_right = [-1] * right_size + result = 0 + + while True: + # 使用 BFS 寻找增广路径 + dist = [-1] * left_size + queue = deque() + + # 将未匹配的左侧顶点加入队列 + for i in range(left_size): + if match_left[i] == -1: + dist[i] = 0 + queue.append(i) + + # BFS 分层 + while queue: + left = queue.popleft() + for right in graph[left]: + if match_right[right] == -1: + # 找到增广路径 + break + if dist[match_right[right]] == -1: + dist[match_right[right]] = dist[left] + 1 + queue.append(match_right[right]) + + # 使用 DFS 寻找增广路径 + def dfs(left): + for right in graph[left]: + if match_right[right] == -1 or \ + (dist[match_right[right]] == dist[left] + 1 and \ + dfs(match_right[right])): + match_left[left] = right + match_right[right] = left + return True + return False + + # 尝试为每个未匹配的左侧顶点寻找增广路径 + found = False + for i in range(left_size): + if match_left[i] == -1 and dfs(i): + found = True + result += 1 + + if not found: + break + + return result +``` + +### 3.3 Hopcroft-Karp 算法复杂度 + +- **时间复杂度**:O(√VE),其中 V 是顶点数,E 是边数 +- **空间复杂度**:O(V + E) +- **优点**: + 1. 比匈牙利算法更高效 + 2. 适合处理大规模图 + 3. 可以并行化实现 +- **缺点**: + 1. 实现相对复杂 + 2. 常数因子较大 + 3. 对于小规模图可能不如匈牙利算法 + +### 3.4 Hopcroft-Karp 算法优化 + +1. **双向 BFS**:同时从左右两侧进行 BFS,减少搜索空间 +2. **动态分层**:根据当前匹配状态动态调整分层策略 +3. **预处理**:使用贪心算法进行初始匹配 +4. **并行化**:利用多线程或分布式计算提高效率 + +## 4. 网络流算法 + +### 4.1 网络流算法实现步骤 + +二分图最大匹配问题可以转化为最大流问题来求解。具体步骤如下: + +1. 添加源点和汇点 +2. 将二分图转化为网络流图 +3. 使用最大流算法求解 + +### 4.2 网络流算法实现代码 + +```python +from collections import defaultdict + +def max_flow_bipartite_matching(graph, left_size, right_size): + # 构建网络流图 + flow_graph = defaultdict(dict) + source = left_size + right_size + sink = source + 1 + + # 添加源点到左侧顶点的边 + for i in range(left_size): + flow_graph[source][i] = 1 + flow_graph[i][source] = 0 + + # 添加右侧顶点到汇点的边 + for i in range(right_size): + flow_graph[left_size + i][sink] = 1 + flow_graph[sink][left_size + i] = 0 + + # 添加二分图中的边 + for i in range(left_size): + for j in graph[i]: + flow_graph[i][left_size + j] = 1 + flow_graph[left_size + j][i] = 0 + + # 使用 Ford-Fulkerson 算法求解最大流 + def bfs(): + parent = [-1] * (sink + 1) + queue = deque([source]) + parent[source] = -2 + + while queue: + u = queue.popleft() + for v, capacity in flow_graph[u].items(): + if parent[v] == -1 and capacity > 0: + parent[v] = u + if v == sink: + return parent + queue.append(v) + return None + + def ford_fulkerson(): + max_flow = 0 + while True: + parent = bfs() + if not parent: + break + + # 找到增广路径上的最小容量 + v = sink + min_capacity = float('inf') + while v != source: + u = parent[v] + min_capacity = min(min_capacity, flow_graph[u][v]) + v = u + + # 更新流量 + v = sink + while v != source: + u = parent[v] + flow_graph[u][v] -= min_capacity + flow_graph[v][u] += min_capacity + v = u + + max_flow += min_capacity + + return max_flow + + return ford_fulkerson() +``` + +### 4.3 网络流算法复杂度 + +- **时间复杂度**: + 1. Ford-Fulkerson 算法:O(VE²) + 2. Dinic 算法:O(V²E) + 3. ISAP 算法:O(V²E) +- **空间复杂度**:O(V + E) + +## 5. 算法复杂度分析 + +1. **匈牙利算法** + - 时间复杂度:O(VE) + - 优点:实现简单,容易理解 + - 缺点:对于大规模图效率较低 + - 适用场景:小规模图,需要快速实现 + +2. **Hopcroft-Karp 算法** + - 时间复杂度:O(√VE) + - 优点:效率更高,适合大规模图 + - 缺点:实现相对复杂 + - 适用场景:大规模图,需要高效算法 + +3. **网络流算法** + - 时间复杂度:O(VE²) 或 O(V²E) + - 优点:可以处理更复杂的问题,如带权匹配 + - 缺点:实现复杂,常数较大 + - 适用场景:带权匹配,复杂约束条件 + +## 练习题目 + +- [LCP 04. 覆盖](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCP/broken-board-dominoes.md) +- [1947. 最大兼容性评分和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) +- [1595. 连通两组点的最小成本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md) + +- [二分图最大匹配题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BA%8C%E5%88%86%E5%9B%BE%E6%9C%80%E5%A4%A7%E5%8C%B9%E9%85%8D%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/06_graph/index.md b/docs/06_graph/index.md new file mode 100644 index 00000000..249691cd --- /dev/null +++ b/docs/06_graph/index.md @@ -0,0 +1,15 @@ +## 本章内容 + +- [6.1 图的定义和分类](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_01_graph_basic.md) +- [6.2 图的存储结构和问题应用](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_02_graph_structure.md) +- [6.3 深度优先搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_03_graph_dfs.md) +- [6.4 广度优先搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_04_graph_bfs.md) +- [6.5 图的拓扑排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_05_graph_topological_sorting.md) +- [6.6 图的最小生成树](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_06_graph_minimum_spanning_tree.md) +- [6.7 单源最短路径(一)](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_07_graph_shortest_path_01.md) +- [6.8 单源最短路径(二)](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_08_graph_shortest_path_02.md) +- [6.9 多源最短路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_09_graph_multi_source_shortest_path.md) +- [6.10 次短路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_10_graph_the_second_shortest_path.md) +- [6.11 差分约束系统](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_11_graph_system_of_difference_constraints.md) +- [6.12 二分图基础](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_12_graph_bipartite_basic.md) +- [6.13 二分图最大匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/06_graph/06_13_graph_bipartite_matching.md) diff --git a/docs/07_algorithm/07_01_enumeration_algorithm.md b/docs/07_algorithm/07_01_enumeration_algorithm.md new file mode 100644 index 00000000..bf8b5080 --- /dev/null +++ b/docs/07_algorithm/07_01_enumeration_algorithm.md @@ -0,0 +1,291 @@ +## 1. 枚举算法简介 + +> **枚举算法(Enumeration Algorithm)**:也称为穷举算法,指的是按照问题本身的性质,一一列举出该问题所有可能的解,并在逐一列举的过程中,将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中,既不能遗漏也不能重复。 + +枚举算法的核心思想是:通过列举问题的所有状态,将它们逐一与目标状态进行比较,从而得到满足条件的解。 + +由于枚举算法要通过列举问题的所有状态来得到满足条件的解,因此,在问题规模变大时,其效率一般是比较低的。但是枚举算法也有自己特有的优点: + +1. 多数情况下容易编程实现,也容易调试。 +2. 建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。 + +所以,枚举算法通常用于求解问题规模比较小的问题,或者作为求解问题的一个子算法出现,通过枚举一些信息并进行保存,而这些消息的有无对主算法效率的高低有着较大影响。 + +## 2. 枚举算法的解题思路 + +### 2.1 枚举算法的解题思路 + +枚举算法是设计最简单、最基本的搜索算法。是我们在遇到问题时,最应该优先考虑的算法。 + +因为其实现足够简单,所以在遇到问题时,我们往往可以先通过枚举算法尝试解决问题,然后在此基础上,再去考虑其他优化方法和解题思路。 + +采用枚举算法解题的一般思路如下: + +1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 +2. 一一枚举可能的情况,并验证是否是问题的解。 +3. 考虑提高枚举算法的效率。 + +我们可以从下面几个方面考虑提高算法的效率: + +1. 抓住问题状态的本质,尽可能缩小问题状态空间的大小。 +2. 加强约束条件,缩小枚举范围。 +3. 根据某些问题特有的性质,例如对称性等,避免对本质相同的状态重复求解。 + +### 2.2 枚举算法的简单应用 + +下面举个著名的例子:「百钱买百鸡问题」。这个问题是我国古代数学家张丘在「算经」一书中提出的。该问题叙述如下: + +> **百钱买百鸡问题**:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则鸡翁、鸡母、鸡雏各几何? + +翻译一下,意思就是:公鸡一只五块钱,母鸡一只三块钱,小鸡三只一块钱。现在我们用 $100$ 块钱买了 $100$ 只鸡,问公鸡、母鸡、小鸡各买了多少只? + +下面我们根据算法的一般思路来解决一下这道题。 + +1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 + + 1. 确定枚举对象:枚举对象为公鸡、母鸡、小鸡的只数,那么我们可以用变量 $x$、$y$、$z$ 分别来代表公鸡、母鸡、小鸡的只数。 + 2. 确定枚举范围:因为总共买了 $100$ 只鸡,所以 $0 \le x, y, z \le 100$,则 $x$、$y$、$z$ 的枚举范围为 $[0, 100]$。 + 3. 确定判断条件:根据题意,我们可以列出两个方程式:$5 \times x + 3 \times y + \frac{z}{3} = 100$,$x + y + z = 100$。在枚举 $x$、$y$、$z$ 的过程中,我们可以根据这两个方程式来判断是否当前状态是否满足题意。 + +2. 一一枚举可能的情况,并验证是否是问题的解。 + + 1. 根据枚举对象、枚举范围和判断条件,我们可以顺利写出对应的代码。 + + ```python + class Solution: + def buyChicken(self): + for x in range(101): + for y in range(101): + for z in range(101): + if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100 and x + y + z == 100: + print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) + ``` + +3. 考虑提高枚举算法的效率。 + + 1. 在上面的代码中,我们枚举了 $x$、$y$、$z$,但其实根据方程式 $x + y + z = 100$,得知:$z$ 可以通过 $z = 100 - x - y$ 而得到,这样我们就不用再枚举 $z$ 了。 + 2. 在上面的代码中,对 $x$、$y$ 的枚举范围是 $[0, 100]$,但其实如果所有钱用来买公鸡,最多只能买 $20$ 只,同理,全用来买母鸡,最多只能买 $33$ 只。所以对 $x$ 的枚举范围可改为 $[0, 20]$,$y$ 的枚举范围可改为 $[0, 33]$。 + + ```python + class Solution: + def buyChicken(self): + for x in range(21): + for y in range(34): + z = 100 - x - y + if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100: + print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) + ``` + + +## 3. 枚举算法的应用 + +### 3.1 两数之和 + +#### 3.1.1 题目链接 + +- [1. 两数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum/) + +#### 3.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数目标值 $target$。 + +**要求**:在该数组中找出和为 $target$ 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。 + +**说明**: + +- $2 \le nums.length \le 10^4$。 +- $-10^9 \le nums[i] \le 10^9$。 +- $-10^9 \le target \le 10^9$。 +- 只会存在一个有效答案。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,7,11,15], target = 9 +输出:[0,1] +解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 +``` + +- 示例 2: + +```python +输入:nums = [3,2,4], target = 6 +输出:[1,2] +``` + +#### 3.1.3 解题思路 + +这里说下枚举算法的解题思路。 + +##### 思路 1:枚举算法 + +1. 使用两重循环枚举数组中每一个数 $nums[i]$、$nums[j]$,判断所有的 $nums[i] + nums[j]$ 是否等于 $target$。 +2. 如果出现 $nums[i] + nums[j] == target$,则说明数组中存在和为 $target$ 的两个整数,将两个整数的下标 $i$、$j$ 输出即可。 + +##### 思路 1:代码 + +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + for i in range(len(nums)): + for j in range(i + 1, len(nums)): + if i != j and nums[i] + nums[j] == target: + return [i, j] + return [] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $nums$ 的元素数量。 +- **空间复杂度**:$O(1)$。 + +### 3.2 计数质数 + +#### 3.2.1 题目链接 + +- [204. 计数质数 - 力扣(LeetCode)](https://leetcode.cn/problems/count-primes/) + +#### 3.2.2 题目大意 + +**描述**:给定 一个非负整数 $n$。 + +**要求**:统计小于 $n$ 的质数数量。 + +**说明**: + +- $0 \le n \le 5 \times 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入 n = 10 +输出 4 +解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:0 +``` + +#### 3.2.3 解题思路 + +这里说下枚举算法的解题思路(注意:提交会超时,只是讲解一下枚举算法的思路)。 + +##### 思路 1:枚举算法(超时) + +对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整除的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 + +这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。 + +在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 $cnt$。如果符合要求,则将计数 $cnt$ 加 $1$。最终返回该数目作为答案。 + +考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。 + +利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n})$,检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。 + +##### 思路 1:代码 + +```python +class Solution: + def isPrime(self, x): + for i in range(2, int(pow(x, 0.5)) + 1): + if x % i == 0: + return False + return True + + def countPrimes(self, n: int) -> int: + cnt = 0 + for x in range(2, n): + if self.isPrime(x): + cnt += 1 + return cnt +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \sqrt{n})$。 +- **空间复杂度**:$O(1)$。 + +### 3.3 统计平方和三元组的数目 + +#### 3.3.1 题目链接 + +- [1925. 统计平方和三元组的数目 - 力扣(LeetCode)](https://leetcode.cn/problems/count-square-sum-triples/) + +#### 3.3.2 题目大意 + +**描述**:给你一个整数 $n$。 + +**要求**:请你返回满足 $1 \le a, b, c \le n$ 的平方和三元组的数目。 + +**说明**: + +- **平方和三元组**:指的是满足 $a^2 + b^2 = c^2$ 的整数三元组 $(a, b, c)$。 +- $1 \le n \le 250$。 + +**示例**: + +- 示例 1: + +```python +输入 n = 5 +输出 2 +解释 平方和三元组为 (3,4,5) 和 (4,3,5)。 +``` + +- 示例 2: + +```python +输入:n = 10 +输出:4 +解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。 +``` + +#### 3.3.3 解题思路 + +##### 思路 1:枚举算法 + +我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n$,并且是完全平方数。 + +在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 $cnt$。如果符合要求,则将计数 $cnt$ 加 $1$。最终,我们返回该数目作为答案。 + +利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。 + +- 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 $1$,所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。 + +##### 思路 1:代码 + +```python +class Solution: + def countTriples(self, n: int) -> int: + cnt = 0 + for a in range(1, n + 1): + for b in range(1, n + 1): + c = int(sqrt(a * a + b * b + 1)) + if c <= n and a * a + b * b == c * c: + cnt += 1 + return cnt +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +## 练习题目 + +- [0001. 两数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) +- [0204. 计数质数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-primes.md) +- [1925. 统计平方和三元组的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/count-square-sum-triples.md) +- [2427. 公因子的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2400-2499/number-of-common-factors.md) +- [LCR 180. 文件组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md) +- [2249. 统计圆内格点数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md) + +- [枚举算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%9E%9A%E4%B8%BE%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/07_algorithm/07_02_recursive_algorithm.md b/docs/07_algorithm/07_02_recursive_algorithm.md new file mode 100644 index 00000000..050e9061 --- /dev/null +++ b/docs/07_algorithm/07_02_recursive_algorithm.md @@ -0,0 +1,339 @@ +## 1. 递归简介 + +> **递归(Recursion)**:指的是一种通过重复将原问题分解为同类的子问题而解决的方法。在绝大数编程语言中,可以通过在函数中再次调用函数自身的方式来实现递归。 + +举个简单的例子来了解一下递归算法。比如阶乘的计算方法在数学上的定义为: + +$fact(n) = \begin{cases} 1 & \text{n = 0} \cr n \times fact(n - 1) & \text{n > 0} \end{cases}$ + +根据阶乘计算方法的数学定义,我们可以使用调用函数自身的方式来实现阶乘函数 $fact(n)$ ,其实现代码可以写作: + +```python +def fact(n): + if n == 0: + return 1 + return n * fact(n - 1) +``` + +以 $n = 6$ 为例,上述代码中阶乘函数 $fact(6)$ 的计算过程如下: + +```python +fact(6) += 6 * fact(5) += 6 * (5 * fact(4)) += 6 * (5 * (4 * fact(3))) += 6 * (5 * (4 * (3 * fact(2)))) += 6 * (5 * (4 * (3 * (2 * fact(1))))) += 6 * (5 * (4 * (3 * (2 * (1 * fact(0)))))) += 6 * (5 * (4 * (3 * (2 * (1 * 1))))) += 6 * (5 * (4 * (3 * (2 * 1)))) += 6 * (5 * (4 * (3 * 2))) += 6 * (5 * (4 * 6)) += 6 * (5 * 24) += 6 * 120 += 720 +``` + +上面的例子也可以用语言描述为: + +1. 函数从 $fact(6)$ 开始,一层层地调用 $fact(5)$、$fact(4)$、…… 一直调用到最底层的 $fact(0)$。 +2. 当 $n == 0$ 时,$fact(0)$ 不再继续调用自身,而是直接向上一层返回结果 $1$。 +3. $fact(1)$ 通过下一层 $fact(0)$ 的计算结果得出 $fact(1) = 1 \times 1 = 1$,从而向上一层返回结果 $1$。 +4. $fact(2)$ 通过下一层 $fact(1)$ 的计算结果得出 $fact(2) = 2 \times 1 = 2 $,从而向上一层返回结果 $2$。 +5. $fact(3)$ 通过下一层 $fact(2)$ 的计算结果得出 $fact(3) = 3 \times 2 = 6 $,从而向上一层返回结果 $6$。 +6. $fact(4)$ 通过下一层 $fact(3)$ 的计算结果得出 $fact(4) = 4 \times 6 = 24$,从而向上一层返回结果 $24$。 +7. $fact(5)$ 通过下一层 $fact(4)$ 的计算结果得出 $fact(5) = 5 \times 24 = 120$,从而向上一层返回结果 $120$。 +8. $fact(6)$ 通过下一层 $fact(5)$ 的计算结果得出 $fact(6) = 6 \times 120 = 720$,从而返回函数的最终结果 $720$。 + +这就是阶乘函数的递归计算过程。 + +根据上面的描述,我们可以把阶乘函数的递归计算过程分为两个部分: + +1. 先逐层向下调用自身,直到达到结束条件(即 $n == 0$)。 +2. 然后再向上逐层返回结果,直到返回原问题的解(即返回 $fact(6) == 720$)。 + +这两个部分也可以叫做「递推过程」和「回归过程」,如下面两幅图所示: + +![递推过程](https://qcdn.itcharge.cn/images/20220407160648.png) + +![回归过程](https://qcdn.itcharge.cn/images/20220407160659.png) + +如上面所说,我们可以把「递归」分为两个部分:「递推过程」和「回归过程」。 + +- **递推过程**:指的是将原问题一层一层地分解为与原问题形式相同、规模更小的子问题,直到达到结束条件时停止,此时返回最底层子问题的解。 +- **回归过程**:指的是从最底层子问题的解开始,逆向逐一回归,最终达到递推开始时的原问题,返回原问题的解。 + +「递推过程」和「回归过程」是递归算法的精髓。从这个角度来理解递归,递归的基本思想就是: **把规模大的问题不断分解为子问题来解决。** + +同时,因为解决原问题和不同规模的小问题往往使用的是相同的方法,所以就产生了函数调用函数自身的情况,这也是递归的定义所在。 + +## 2. 递归和数学归纳法 + +递归的数学模型其实就是「数学归纳法」。这里简单复习一下数学归纳法的证明步骤: + +1. 证明当 $n = b$ ($b$ 为基本情况,通常为 $0$ 或者 $1$)时,命题成立。 +2. 证明当 $n > b$ 时,假设 $n = k$ 时命题成立,那么可以推导出 $n = k + 1$ 时命题成立。这一步不是直接证明的,而是先假设 $n = k$ 时命题成立,利用这个条件,可以推论出 $n = k + 1$ 时命题成立。 + +通过以上两步证明,就可以说:当 $n >= b$ 时,命题都成立。 + +我们可以从「数学归纳法」的角度来解释递归: + +- **递归终止条件**:数学归纳法第一步中的 $n = b$,可以直接得出结果。 +- **递推过程**:数学归纳法第二步中的假设部分(假设 $n = k$ 时命题成立),也就是假设我们当前已经知道了 $n = k$ 时的计算结果。 +- **回归过程**:数学归纳法第二步中的推论部分(根据 $n = k$ 推论出 $n = k + 1$),也就是根据下一层的结果,计算出上一层的结果。 + +事实上,数学归纳法的思考过程也正是在解决某些数列问题时,可以使用递归算法的原因。比如阶乘、数组前 $n$ 项和、斐波那契数列等等。 + +## 3. 递归三步走 + +上面我们提到,递归的基本思想就是: **把规模大的问题不断分解为子问题来解决。** 那么,在写递归的时候,我们可以按照这个思想来书写递归,具体步骤如下: + +1. **写出递推公式**:找到将原问题分解为子问题的规律,并且根据规律写出递推公式。 +2. **明确终止条件**:推敲出递归的终止条件,以及递归终止时的处理方法。 +3. **将递推公式和终止条件翻译成代码**: + 1. 定义递归函数(明确函数意义、传入参数、返回结果等)。 + 2. 书写递归主体(提取重复的逻辑,缩小问题规模)。 + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + +### 3.1 写出递推公式 + +写出递推公式的关键在于:**找到将原问题分解为子问题的规律,并将其抽象成递推公式**。 + +我们在思考递归的逻辑时,没有必要在大脑中将整个递推过程和回归过程一层层地想透彻。很可能还没有递推到栈底呢,脑子就已经先绕晕了。 + +之前讲解的阶乘例子中,一个问题只需要分解为一个子问题,我们很容易能够想清楚「递推过程」和「回归过程」的每一个步骤,所以写起来和理解起来都不难。 + +但是当我们面对的是一个问题需要分解为多个子问题的情况时,就没有那么容易想清楚「递推过程」和「回归过程」的每一个步骤了。 + +那么我们应该如何思考「递推过程」和「回归过程」呢,又该如何写出递归中的递推公式呢? + +如果一个问题 $A$,可以分解为若干个规模较小、与原问题形式相同的子问题 $B$、$C$、$D$,那么这些子问题就可以用相同的解题思路来解决。我们可以假设 $B$、$C$、$D$ 已经解决了,然后只需要考虑在这个基础上去思考如何解决问题 $A$ 即可。不需要再一层层往下思考子问题与子子问题、子子问题与子子子问题之间的关系。这样理解起来就简单多了。 + +从问题 $A$ 到分解为子问题 $B$、$C$、$D$ 的思考过程其实就是递归的「递推过程」。而从子问题 $B$、$C$、$D$ 的解回到问题 $A$ 的解的思考过程其实就是递归的「回归过程」。想清楚了「如何划分子问题」和「如何通过子问题来解决原问题」这两个过程,也就想清楚了递归的「递推过程」和「回归过程」。 + +然后,我们只需要考虑原问题与子问题之间的关系,就能够在此基础上,写出递推公式了。 + +### 3.2 明确终止条件 + +递归的终止条件也叫做递归出口。在写出了递推公式之后,就要考虑递归的终止条件是什么。如果没有递归的终止条件,函数就会无限地递归下去,程序就会失控崩溃了。通常情况下,递归的终止条件是问题的边界值。 + +在找到递归的终止条件时,我们应该直接给出该条件下的处理方法。一般地,在这种情境下,问题的解决方案是直观的、容易的。例如阶乘中 $fact(0) = 1$。斐波那契数列中 $f(1) = 1$,$f(2) = 2$。 + +### 3.3 将递推公式和终止条件翻译成代码 + +在写出递推公式和明确终止条件之后,我们就可以将其翻译成代码了。这一步也可以分为 $3$ 步来做: + +1. **定义递归函数**:明确函数意义、传入参数、返回结果等。 +2. **书写递归主体**:提取重复的逻辑,缩小问题规模。 +3. **明确递归终止条件**:给出递归终止条件,以及递归终止时的处理方法。 + +#### 3.3.1 定义递归函数 + +在定义递归函数时,一定要明确递归函数的意义,也就是要明白这个问题传入的参数是什么,最终返回的结果是要解决的什么问题。 + +比如说阶乘函数 $fact(n)$,这个函数的传入参数是问题的规模 $n$,最终返回的结果是 $n$ 的阶乘值。 + +#### 3.3.2 书写递归主体 + +在将原问题划分为子问题,并根据原问题和子问题的关系,我们就可以推论出对应的递推公式。然后根据递推公式,就可以将其转换为递归的主体代码。 + +#### 3.3.3 明确递归终止条件 + +这一步其实就是将「3.2 明确终止条件」章节中的递归终止条件和终止条件下的处理方法转换为代码中的条件语句和对应的执行语句。 + +#### 3.3.4 递归伪代码 + +根据上述递归书写的步骤,我们就可以写出递归算法的代码了。递归算法的伪代码如下: + +```python +def recursion(大规模问题): + if 递归终止条件: + 递归终止时的处理方法 + + return recursion(小规模问题) +``` + +## 4. 递归的注意点 + +### 4.1 避免栈溢出 + +在程序执行中,递归是利用堆栈来实现的。每一次递推都需要一个栈空间来保存调用记录,每当进入一次函数调用,栈空间就会加一层栈帧。每一次回归,栈空间就会减一层栈帧。由于系统中的栈空间大小不是无限的,所以,如果递归调用的次数过多,会导致栈空间溢出。 + +为了避免栈溢出,我们可以在代码中限制递归调用的最大深度来解决问题。当递归调用超过一定深度时(比如 100)之后,不再进行递归,而是直接返回报错。 + +当然这种做法并不能完全避免栈溢出,也无法完全解决问题,因为系统允许的最大递归深度跟当前剩余的占空间有关,事先无法计算。 + +如果使用递归算法实在无法解决问题,我们可以考虑将递归算法变为非递归算法(即递推算法)来解决栈溢出的问题。 + +### 4.2 避免重复运算 + +在使用递归算法时,还可能会出现重复运算的问题。 + +比如斐波那契数列的定义是: + +$f(n) = \begin{cases} 0 & n = 0 \cr 1 & n = 1 \cr f(n - 2) + f(n - 1) & n > 1 \end{cases}$ + +其对应的递归过程如下图所示: + +![斐波那契数列的递归过程](https://qcdn.itcharge.cn/images/20230307164107.png) + +从图中可以看出:想要计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$,这样 $f(3)$ 就进行了多次计算。同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,就导致了重复计算问题。 + +为了避免重复计算,我们可以使用一个缓存(哈希表、集合或数组)来保存已经求解过的 $f(k)$ 的结果,这也是动态规划算法中的做法。当递归调用用到 $f(k)$ 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。 + +## 5. 递归的应用 + +### 5.1 斐波那契数 + +#### 5.1.1 题目链接 + +- [509. 斐波那契数 - 力扣(LeetCode)](https://leetcode.cn/problems/fibonacci-number/) + +#### 5.1.2 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算第 $n$ 个斐波那契数。 + +**说明**: + +- 斐波那契数列的定义如下: + - $f(0) = 0, f(1) = 1$。 + - $f(n) = f(n - 1) + f(n - 2)$,其中 $n > 1$。 + + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:1 +解释:F(2) = F(1) + F(0) = 1 + 0 = 1 +``` + +- 示例 2: + +```python +输入:n = 3 +输出:2 +解释:F(3) = F(2) + F(1) = 1 + 1 = 2 +``` + +#### 5.1.3 解题思路 + +##### 思路 1:递归算法 + +根据我们的递推三步走策略,写出对应的递归代码。 + +1. 写出递推公式:$f(n) = f(n - 1) + f(n - 2)$。 +2. 明确终止条件:$f(0) = 0, f(1) = 1$。 +3. 翻译为递归代码: + 1. 定义递归函数:`fib(self, n)` 表示输入参数为问题的规模 $n$,返回结果为第 $n$ 个斐波那契数。 + 2. 书写递归主体:`return self.fib(n - 1) + self.fib(n - 2)`。 + 3. 明确递归终止条件: + 1. `if n == 0: return 0` + 2. `if n == 1: return 1` + +##### 思路 1:代码 + +```python +class Solution: + def fib(self, n: int) -> int: + if n == 0: + return 0 + if n == 1: + return 1 + return self.fib(n - 1) + self.fib(n - 2) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。具体证明方法参考 [递归求斐波那契数列的时间复杂度,不要被网上的答案误导了 - 知乎](https://zhuanlan.zhihu.com/p/256344121)。 +- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 + +### 5.2 二叉树的最大深度 + +#### 5.2.1 题目链接 + +- [104. 二叉树的最大深度 - 力扣(LeetCode)](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) + +#### 5.2.2 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:找出该二叉树的最大深度。 + +**说明**: + +- **二叉树的深度**:根节点到最远叶子节点的最长路径上的节点数。 +- **叶子节点**:没有子节点的节点。 + +**示例**: + +- 示例 1: + +```python +输入:[3,9,20,null,null,15,7] +对应二叉树 + 3 + / \ + 9 20 + / \ + 15 7 +输出:3 +解释:该二叉树的最大深度为 3 +``` + +#### 5.2.3 解题思路 + +##### 思路 1: 递归算法 + +根据递归三步走策略,写出对应的递归代码。 + +1. 写出递推公式:**当前二叉树的最大深度 = max(当前二叉树左子树的最大深度, 当前二叉树右子树的最大深度) + 1**。 + - 即:先得到左右子树的高度,在计算当前节点的高度。 +2. 明确终止条件:当前二叉树为空。 +3. 翻译为递归代码: + 1. 定义递归函数:`maxDepth(self, root)` 表示输入参数为二叉树的根节点 $root$,返回结果为该二叉树的最大深度。 + 2. 书写递归主体:`return max(self.maxDepth(root.left) + self.maxDepth(root.right))`。 + 3. 明确递归终止条件:`if not root: return 0` + +##### 思路 1:代码 + +```python +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + + return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + +## 练习题目 + +- [0509. 斐波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) +- [0070. 爬楼梯](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) +- [0226. 翻转二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) +- [0206. 反转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) +- [0092. 反转链表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) +- [0779. 第K个语法符号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/k-th-symbol-in-grammar.md) + +- [递归算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%80%92%E5%BD%92%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著 +- 【书籍】算法训练营 陈小玉 著 +- 【书籍】挑战程序设计竞赛 第 2 版 - 秋叶拓哉,岩田阳一,北川宜稔 著,巫泽俊,庄俊元,李津羽 译 +- 【问答】[对于递归有没有什么好的理解方法? - 知乎 - 方应杭](https://www.zhihu.com/question/31412436/answer/738989709) +- 【问答】[对于递归有没有什么好的理解方法? - 知乎 - 老刘](https://www.zhihu.com/question/31412436/answer/724915708) +- 【博文】[递归 & 分治 - OI Wiki](https://oi-wiki.org/basic/divide-and-conquer/) +- 【博文】[递归详解 - labuladong](https://github.com/labuladong/fucking-algorithm/blob/master/算法思维系列/递归详解.md) +- 【博文】[递归 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/41440) +- 【视频】[清华学长带你从宏观角度看递归](https://mp.weixin.qq.com/s/BHY7ZBxIr3UCpIvY4-IVOQ) \ No newline at end of file diff --git a/docs/07_algorithm/07_03_divide_and_conquer_algorithm.md b/docs/07_algorithm/07_03_divide_and_conquer_algorithm.md new file mode 100644 index 00000000..fb1d8b90 --- /dev/null +++ b/docs/07_algorithm/07_03_divide_and_conquer_algorithm.md @@ -0,0 +1,269 @@ +## 1. 分治算法简介 + +### 1.1 分治算法的定义 + +> **分治算法(Divide and Conquer)**:字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。 + +简单来说,分治算法的基本思想就是: **把规模大的问题不断分解为子问题,使得问题规模减小到可以直接求解为止。** + +![分治算法的基本思想](https://qcdn.itcharge.cn/images/20220413153059.png) + +### 1.2 分治算法和递归算法的异同 + +从定义上来看,分治算法的思想和递归算法的思想是一样的,都是把规模大的问题不断分解为子问题。 + +其实,分治算法和递归算法的关系是包含与被包含的关系,可以看做: $\text{递归算法} \in \text{分治算法}$。 + +分治算法从实现方式上来划分,可以分为两种:「递归算法」和「迭代算法」。 + +![分治算法的实现方式](https://qcdn.itcharge.cn/images/20240513162133.png) + +一般情况下,分治算法比较适合使用递归算法来实现。但除了递归算法之外,分治算法还可以通过迭代算法来实现。比较常见的例子有:快速傅里叶变换算法、二分查找算法、非递归实现的归并排序算法等等。 + +我们先来讲解一下分支算法的适用条件,再来讲解一下基本步骤。 + +### 1.3 分治算法的适用条件 + +分治算法能够解决的问题,一般需要满足以下 $4$ 个条件: + +1. **可分解**:原问题可以分解为若干个规模较小的相同子问题。 +2. **子问题可独立求解**:分解出来的子问题可以独立求解,即子问题之间不包含公共的子子问题。 +3. **具有分解的终止条件**:当问题的规模足够小时,能够用较简单的方法解决。 +4. **可合并**:子问题的解可以合并为原问题的解,并且合并操作的复杂度不能太高,否则就无法起到减少算法总体复杂度的效果了。 + +## 2. 分治算法的基本步骤 + +使用分治算法解决问题主要分为 $3$ 个步骤: + +1. **分解**:把要解决的问题分解为成若干个规模较小、相对独立、与原问题形式相同的子问题。 +2. **求解**:递归求解各个子问题。 +3. **合并**:按照原问题的要求,将子问题的解逐层合并构成原问题的解。 + +其中第 $1$ 步中将问题分解为若干个子问题时,最好使子问题的规模大致相同。换句话说,将一个问题分成大小相等的 $k$ 个子问题的处理方法是行之有效的。在许多问题中,可以取 $k = 2$。这种使子问题规模大致相等的做法是出自一种平衡子问题的思想,它几乎总是比子问题规模不等的做法要好。 + +其中第 $2$ 步的「递归求解各个子问题」指的是按照同样的分治策略进行求解,即通过将这些子问题分解为更小的子子问题来进行求解。就这样一直分解下去,直到分解出来的子问题简单到只用常数操作时间即可解决为止。 + +在完成第 $2$ 步之后,最小子问题的解可用常数时间求得。然后我们再按照递归算法中回归过程的顺序,由底至上地将子问题的解合并起来,逐级上推就构成了原问题的解。 + +按照分而治之的策略,在编写分治算法的代码时,也是按照上面的 $3$ 个步骤来编写的,其对应的伪代码如下: + +```python +def divide_and_conquer(problems_n): # problems_n 为问题规模 + if problems_n < d: # 当问题规模足够小时,直接解决该问题 + return solove() # 直接求解 + + problems_k = divide(problems_n) # 将问题分解为 k 个相同形式的子问题 + + res = [0 for _ in range(k)] # res 用来保存 k 个子问题的解 + for problem_k in problems_k: + res[i] = divide_and_conquer(problem_k) # 递归的求解 k 个子问题 + + ans = merge(res) # 合并 k 个子问题的解 + return ans # 返回原问题的解 +``` + +## 3. 分治算法的复杂度分析 + + 分治算法中,在不断递归后,最后的子问题将变得极为简单,可在常数操作时间内予以解决,其带来的时间复杂度在整个分治算法中的比重微乎其微,可以忽略不计。所以,分治算法的时间复杂度实际上是由「分解」和「合并」两个部分构成的。 + +一般来讲,分治算法将一个问题划分为 $a$ 个形式相同的子问题,每个子问题的规模为 $n/b$,则总的时间复杂度的递归表达式可以表示为: + +$T(n) = \begin{cases} \Theta{(1)} & n = 1 \cr a \times T(n/b) + f(n) & n > 1 \end{cases}$ + +其中,每次分解时产生的子问题个数是 $a$ ,每个子问题的规模是原问题规模的 $1 / b$,分解和合并 $a$ 个子问题的时间复杂度是 $f(n)$。 + +这样,求解一个分治算法的时间复杂度,就是求解上述递归表达式。关于递归表达式的求解有多种方法,这里我们介绍一下比较常用的「递推求解法」和「递归树法」。 + +### 3.1 递推求解法 + +根据问题的递归表达式,通过一步步递推分解推导,从而得到最终结果。 + +以「归并排序算法」为例,接下来我们通过递推求解法计算一下归并排序算法的时间复杂度。 + +我们得出归并排序算法的递归表达式如下: + +$T(n) = \begin{cases} O{(1)} & n = 1 \cr 2 \times T(n/2) + O(n) & n > 1 \end{cases}$ + +根据归并排序的递归表达式,当 $n > 1$ 时,可以递推求解: + +$$\begin{aligned} T(n) & = 2 \times T(n/2) + O(n) \cr & = 2 \times (2 \times T(n / 4) + O(n/2)) + O(n) \cr & = 4 \times T(n/4) + 2 \times O(n) \cr & = 8 \times T(n/8) + 3 \times O(n) \cr & = …… \cr & = 2^x \times T(n/2^x) + x \times O(n) \end{aligned}$$ + +递推最终规模为 $1$,令 $n = 2^x$,则 $x = \log_2n$,则: + +$$\begin{aligned} T(n) & = n \times T(1) + \log_2n \times O(n) \cr & = n + \log_2n \times O(n) \cr & = O(n \times \log_2n) \end{aligned}$$ + +则归并排序的时间复杂度为 $O(n \times \log_2n)$。 + +### 3.2 递归树法 + +递归树求解方式其实和递推求解一样,只不过递归树能够更清楚直观的显示出来,更能够形象地表达每层分解的节点和每层产生的时间成本。 + +使用递归树法计算时间复杂度的公式为: + +$\text{时间复杂度} = \text{叶子数} \times T(1) + \text{成本和} = 2^x \times T(1) + x \times O(n)$。 + +我们还是以「归并排序算法」为例,通过递归树法计算一下归并排序算法的时间复杂度。 + +归并排序算法的递归表达式如下: + +$T(n) = \begin{cases} O{(1)} & n = 1 \cr 2T(n/2) + O(n) & n > 1 \end{cases}$ + +其对应的递归树如下图所示。 + +![归并排序算法的递归树](https://qcdn.itcharge.cn/images/20220414171458.png) + +因为 $n = 2^x$,则 $x = \log_2n$,则归并排序算法的时间复杂度为:$2^x \times T(1) + x \times O(n) = n + \log_2n \times O(n) = O(n \times \log_2n)$。 + +## 4. 分治算法的应用 + +### 4.1 归并排序 + +#### 4.1.1 题目链接 + +- [912. 排序数组 - 力扣(LeetCode) ](https://leetcode.cn/problems/sort-an-array/) + +#### 4.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:对该数组升序排列。 + +**说明**: + +- $1 \le nums.length \le 5 * 10^4$。 +- $-5 * 10^4 \le nums[i] \le 5 * 10^4$。 + +**示例**: + +```python +输入 nums = [5,2,3,1] +输出 [1,2,3,5] +``` + +#### 4.1.3 解题思路 + +我们使用归并排序算法来解决这道题。 + +1. **分解**:将待排序序列中的 $n$ 个元素分解为左右两个各包含 $\frac{n}{2}$ 个元素的子序列。 +2. **求解**:递归将子序列进行分解和排序,直到所有子序列长度为 $1$。 +3. **合并**:把当前序列组中有序子序列逐层向上,进行两两合并。 + +使用归并排序算法对数组排序的过程如下图所示。 + +![归并排序算法对数组排序的过程](https://qcdn.itcharge.cn/images/20220414204405.png) + +#### 4.1.4 代码 + +```python +class Solution: + def merge(self, left_arr, right_arr): # 合并 + arr = [] + while left_arr and right_arr: # 将两个排序数组中较小元素依次插入到结果数组中 + if left_arr[0] <= right_arr[0]: + arr.append(left_arr.pop(0)) + else: + arr.append(right_arr.pop(0)) + + while left_arr: # 如果左子序列有剩余元素,则将其插入到结果数组中 + arr.append(left_arr.pop(0)) + while right_arr: # 如果右子序列有剩余元素,则将其插入到结果数组中 + arr.append(right_arr.pop(0)) + return arr # 返回排好序的结果数组 + + def mergeSort(self, arr): # 分解 + if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 + return arr + + mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 + left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分解和排序 + right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分解和排序 + return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 + + def sortArray(self, nums: List[int]) -> List[int]: + return self.mergeSort(nums) +``` + +### 4.2 二分查找 + +#### 4.2.1 题目链接 + +- [704. 二分查找 - 力扣(LeetCode)](https://leetcode.cn/problems/binary-search/) + +#### 4.2.2 题目大意 + +**描述**:给定一个含有 $n$ 个元素有序的(升序)整型数组 $nums$ 和一个目标值 $target$。 + +**要求**:返回 $target$ 在数组 $nums$ 中的位置,如果找不到,则返回 $-1$。 + +**说明**: + +- 假设 $nums$ 中的所有元素是不重复的。 +- $n$ 将在 $[1, 10000]$ 之间。 +- $-9999 \le nums[i] \le 9999$。 + +**示例**: + +```python +输入 nums = [-1,0,3,5,9,12], target = 9 +输出 4 +解释 9 出现在 nums 中并且下标为 4 +``` + +#### 4.2.3 解题思路 + +我们使用分治算法来解决这道题。与其他分治题目不一样的地方是二分查找不用进行合并过程,最小子问题的解就是原问题的解。 + +1. **分解**:将数组的 $n$ 个元素分解为左右两个各包含 $\frac{n}{2}$ 个元素的子序列。 +2. **求解**:取中间元素 $nums[mid]$ 与 $target$ 相比。 + 1. 如果相等,则找到该元素; + 2. 如果 $nums[mid] < target$,则递归在左子序列中进行二分查找。 + 3. 如果 $nums[mid] > target$,则递归在右子序列中进行二分查找。 + +二分查找的的分治算法过程如下图所示。 + +![二分查找的的分治算法过程](https://qcdn.itcharge.cn/images/20211223115032.png) + +#### 4.2.4 代码 + +二分查找问题的非递归实现的分治算法代码如下: + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + # 在区间 [left, right] 内查找 target + while left < right: + # 取区间中间节点 + mid = left + (right - left) // 2 + # nums[mid] 小于目标值,排除掉不可能区间 [left, mid],在 [mid + 1, right] 中继续搜索 + if nums[mid] < target: + left = mid + 1 + # nums[mid] 大于等于目标值,目标元素可能在 [left, mid] 中,在 [left, mid] 中继续搜索 + else: + right = mid + # 判断区间剩余元素是否为目标元素,不是则返回 -1 + return left if nums[left] == target else -1 +``` + +## 练习题目 + +- [0050. Pow(x, n)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) +- [0169. 多数元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) +- [0053. 最大子数组和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) +- [0932. 漂亮数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/beautiful-array.md) +- [0241. 为运算表达式设计优先级](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/different-ways-to-add-parentheses.md) +- [0023. 合并 K 个升序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) + +- [分治算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%88%86%E6%B2%BB%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【书籍】趣学算法 - 陈小玉 著 +- 【书籍】算法之道 - 邹恒铭 著 +- 【书籍】算法图解 - 袁国忠 译 +- 【书籍】算法训练营 陈小玉 著 +- 【博文】[从合并排序算法看“分治法” - 船长&CAP - 博客园](https://www.cnblogs.com/liuning8023/archive/2012/06/25/2562747.html) +- 【博文】[递归、迭代、分治、回溯、动态规划、贪心算法 - 力扣](https://leetcode.cn/circle/article/yXFal5/) +- 【博文】[递归 & 分治 - OI Wiki](https://oi-wiki.org/basic/divide-and-conquer/) +- 【博文】[漫画:5分钟弄懂分治算法!它和递归算法的关系!](https://mp.weixin.qq.com/s/0Z1tiqWTO410jYTJ4K0Ihg) \ No newline at end of file diff --git a/docs/07_algorithm/07_04_backtracking_algorithm.md b/docs/07_algorithm/07_04_backtracking_algorithm.md new file mode 100644 index 00000000..0950bf5e --- /dev/null +++ b/docs/07_algorithm/07_04_backtracking_algorithm.md @@ -0,0 +1,420 @@ +## 1. 回溯算法简介 + +> **回溯算法(Backtracking)**:一种能避免不必要搜索的穷举式的搜索算法。采用试错的思想,在搜索尝试过程中寻找问题的解,当探索到某一步时,发现原先的选择并不满足求解条件,或者还需要满足更多求解条件时,就退回一步(回溯)重新选择,这种走不通就退回再走的技术称为「回溯法」,而满足回溯条件的某个状态的点称为「回溯点」。 + +简单来说,回溯算法采用了一种 **「走不通就回退」** 的算法思想。 + +回溯算法通常用简单的递归方法来实现,在进行回溯过程中更可能会出现两种情况: + +1. 找到一个可能存在的正确答案; +2. 在尝试了所有可能的分布方法之后宣布该问题没有答案。 + +## 2. 从全排列问题开始理解回溯算法 + +以求解 $[1, 2, 3]$ 的全排列为例,我们来讲解一下回溯算法的过程。 + +1. 选择以 $1$ 为开头的全排列。 + 1. 选择以 $2$ 为中间数字的全排列,则最后数字只能选择 $3$。即排列为:$[1, 2, 3]$。 + 2. 撤销选择以 $3$ 为最后数字的全排列,再撤销选择以 $2$ 为中间数字的全排列。然后选择以 $3$ 为中间数字的全排列,则最后数字只能选择 $2$,即排列为:$[1, 3, 2]$。 +2. 撤销选择以 $2$ 为最后数字的全排列,再撤销选择以 $3$ 为中间数字的全排列,再撤销选择以 $1$ 为开头的全排列。然后选择以 $2$ 开头的全排列。 + 1. 选择以 $1$ 为中间数字的全排列,则最后数字只能选择 $3$。即排列为:$[2, 1, 3]$。 + 2. 撤销选择以 $3$ 为最后数字的全排列,再撤销选择以 $1$ 为中间数字的全排列。然后选择以 $3$ 为中间数字的全排列,则最后数字只能选择 $1$,即排列为:$[2, 3, 1]$。 +3. 撤销选择以 $1$ 为最后数字的全排列,再撤销选择以 $3$ 为中间数字的全排列,再撤销选择以 $2$ 为开头的全排列,选择以 $3$ 开头的全排列。 + 1. 选择以 $1$ 为中间数字的全排列,则最后数字只能选择 $2$。即排列为:$[3, 1, 2]$。 + 2. 撤销选择以 $2$ 为最后数字的全排列,再撤销选择以 $1$ 为中间数字的全排列。然后选择以 $2$ 为中间数字的全排列,则最后数字只能选择 $1$,即排列为:$[3, 2, 1]$。 + +总结一下全排列的回溯过程: + +- **按顺序枚举每一位上可能出现的数字,之前已经出现的数字在接下来要选择的数字中不能再次出现。** +- 对于每一位,进行如下几步: + 1. **选择元素**:从可选元素列表中选择一个之前没有出现过的元素。 + 2. **递归搜索**:从选择的元素出发,一层层地递归搜索剩下位数,直到遇到边界条件时,不再向下搜索。 + 3. **撤销选择**:一层层地撤销之前选择的元素,转而进行另一个分支的搜索。直到完全遍历完所有可能的路径。 + +对于上述决策过程,我们也可以用一棵决策树来表示: + +![全排列问题的决策树](https://qcdn.itcharge.cn/images/20220425102048.png) + +从全排列的决策树中我们可以看出: + +- 每一层中有一个或多个不同的节点,这些节点以及节点所连接的分支代表了「不同的选择」。 +- 每一个节点代表了求解全排列问题的一个「状态」,这些状态是通过「不同的值」来表现的。 +- 每向下递推一层就是在「可选元素列表」中选择一个「元素」加入到「当前状态」。 +- 当一个决策分支探索完成之后,会逐层向上进行回溯。 +- 每向上回溯一层,就是把所选择的「元素」从「当前状态」中移除,回退到没有选择该元素时的状态(或者说重置状态),从而进行其他分支的探索。 + +根据上文的思路和决策树,我们来写一下全排列的回溯算法代码(假设给定数组 $nums$ 中不存在重复元素)。则代码如下所示: + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + res = [] # 存放所有符合条件结果的集合 + path = [] # 存放当前符合条件的结果 + def backtracking(nums): # nums 为选择元素列表 + if len(path) == len(nums): # 说明找到了一组符合条件的结果 + res.append(path[:]) # 将当前符合条件的结果放入集合中 + return + + for i in range(len(nums)): # 枚举可选元素列表 + if nums[i] not in path: # 从当前路径中没有出现的数字中选择 + path.append(nums[i]) # 选择元素 + backtracking(nums) # 递归搜索 + path.pop() # 撤销选择 + + backtracking(nums) + return res +``` + +## 3. 回溯算法的通用模板 + +根据上文全排列的回溯算法代码,我们可以提炼出回溯算法的通用模板,回溯算法的通用模板代码如下所示: + +```python +res = [] # 存放所欲符合条件结果的集合 +path = [] # 存放当前符合条件的结果 +def backtracking(nums): # nums 为选择元素列表 + if 遇到边界条件: # 说明找到了一组符合条件的结果 + res.append(path[:]) # 将当前符合条件的结果放入集合中 + return + + for i in range(len(nums)): # 枚举可选元素列表 + path.append(nums[i]) # 选择元素 + backtracking(nums) # 递归搜索 + path.pop() # 撤销选择 + +backtracking(nums) +``` + +## 4. 回溯算法三步走 + +网络上给定的回溯算法解题步骤比较抽象,这里只做一下简单介绍。 + +1. **根据所给问题,定义问题的解空间**:要定义合适的解空间,包括解的组织形式和显约束。 + - **解的组织形式**:将解的组织形式都规范为⼀个 $n$ 元组 ${x_1, x_2 …, x_n}$。 + - **显约束**:对解分量的取值范围的限定,可以控制解空间的大小。 +2. **确定解空间的组织结构**:解空间的组织结构通常以解空间树的方式形象地表达,根据解空间树的不同,解空间分为⼦集树、排列树、$m$ 叉树等。 +3. **搜索解空间**:按照深度优先搜索策略,根据隐约束(约束函数和限界函数),在解空间中搜索问题的可⾏解或最优解。当发现当 前节点不满⾜求解条件时,就回溯,尝试其他路径。 + - 如果问题只是求可⾏解,则只需设定约束函数即可,如果要求最优解,则需要设定约束函数和限界函数。 + +这种回溯算法的解题步骤太过于抽象,不利于我们在日常做题时进行思考。其实在递归算法知识的相关章节中,我们根据递归的基本思想总结了递归三步走的书写步骤。同样,根据回溯算法的基本思想,我们也来总结一下回溯算法三步走的书写步骤。 + +回溯算法的基本思想是:**以深度优先搜索的方式,根据产生子节点的条件约束,搜索问题的解。当发现当前节点已不满足求解条件时,就「回溯」返回,尝试其他的路径。** + +那么,在写回溯算法时,我们可以按照这个思想来书写回溯算法,具体步骤如下: + +1. **明确所有选择**:画出搜索过程的决策树,根据决策树来确定搜索路径。 +2. **明确终止条件**:推敲出递归的终止条件,以及递归终止时的要执行的处理方法。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数(明确函数意义、传入参数、返回结果等)。 + 2. 书写回溯函数主体(给出约束条件、选择元素、递归搜索、撤销选择部分)。 + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + +### 4.1 明确所有选择 + +决策树是帮助我们理清搜索过程的一个很好的工具。我们可以画出搜索过程的决策树,根据决策树来帮助我们确定搜索范围和对应的搜索路径。 + +### 4.2 明确终止条件 + +回溯算法的终止条件也就是决策树的底层,即达到无法再做选择的条件。 + +回溯函数的终止条件一般为给定深度、叶子节点、非叶子节点(包括根节点)、所有节点等。并且还要给出在终止条件下的处理方法,比如输出答案,将当前符合条件的结果放入集合中等等。 + +### 4.3 将决策树和终止条件翻译成代码 + +在明确所有选择和明确终止条件之后,我们就可以将其翻译成代码了。这一步也可以分为 $3$ 步来做: + +1. 定义回溯函数(明确函数意义、传入参数、返回结果等)。 +2. 书写回溯函数主体(给出约束条件、选择元素、递归搜索、撤销选择部分)。 +3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + +#### 4.3.1 定义回溯函数 + +在定义回溯函数时,一定要明确递归函数的意义,也就是要明白这个问题的传入参数和全局变量是什么,最终返回的结果是要解决的什么问题。 + +- **传入参数和全局变量**:是由递归搜索阶段时的「当前状态」来决定的。最好是能通过传入参数和全局变量直接记录「当前状态」。 + +比如全排列中,`backtracking(nums)` 这个函数的传入参数是 $nums$(可选择的元素列表),全局变量是 $res$(存放所有符合条件结果的集合数组)和 $path$(存放当前符合条件的结果)。$nums$ 表示当前可选的元素,$path$ 用于记录递归搜索阶段的「当前状态」。$res$ 则用来保存递归搜索阶段的「所有状态」。 + +- **返回结果**:返回结果是在遇到递归终止条件时,需要向上一层函数返回的信息。 + +一般回溯函数的返回结果都是单个节点或单个数值,告诉上一层函数我们当前的搜索结果是什么即可。 + +当然,如果使用全局变量来保存「当前状态」的话,也可以不需要向上一层函数返回结果,即返回空结果。比如上文中的全排列。 + +#### 4.3.2 书写回溯函数主体 + +根据当前可选择的元素列表、给定的约束条件(例如之前已经出现的数字在接下来要选择的数字中不能再次出现)、存放当前状态的变量,我们就可以写出回溯函数的主体部分了。即: + +```python +for i in range(len(nums)): # 枚举可选元素列表 + if 满足约束条件: # 约束条件 + path.append(nums[i]) # 选择元素 + backtracking(nums) # 递归搜索 + path.pop() # 撤销选择 +``` + +#### 4.3.3 明确递归终止条件 + +这一步其实就是将「4.2 明确终止条件」章节中的递归终止条件和终止条件下的处理方法转换为代码中的条件语句和对应的执行语句。 + +## 5. 回溯算法的应用 + +### 5.1 子集 + +#### 5.1.1 题目链接 + +- [78. 子集 - 力扣(LeetCode)](https://leetcode.cn/problems/subsets/) + +#### 5.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$,数组中的元素互不相同。 + +**要求**:返回该数组所有可能的不重复子集。可以按任意顺序返回解集。 + +**说明**: + +- $1 \le nums.length \le 10$。 +- $-10 \le nums[i] \le 10$。 +- $nums$ 中的所有元素互不相同。 + +**示例**: + +- 示例 1: + +```python +输入 nums = [1,2,3] +输出 [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] +``` + +- 示例 2: + +```python +输入:nums = [0] +输出:[[],[0]] +``` + +#### 5.1.3 解题思路 + +##### 思路 1:回溯算法 + +数组的每个元素都有两个选择:选与不选。 + +我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +![子集的决策树](https://qcdn.itcharge.cn/images/20220425210640.png) + +1. **明确所有选择**:根据数组中每个位置上的元素选与不选两种选择,画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数: + - `backtracking(nums, index):` 函数的传入参数是 $nums$(可选数组列表)和 $index$(代表当前正在考虑元素是 $nums[i]$),全局变量是 $res$(存放所有符合条件结果的集合数组)和 $path$(存放当前符合条件的结果)。 + - `backtracking(nums, index):` 函数代表的含义是:在选择 $nums[index]$ 的情况下,递归选择剩下的元素。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: + - 约束条件:之前选过的元素不再重复选用。每次从 $index$ 位置开始遍历而不是从 $0$ 位置开始遍历就是为了避免重复。集合跟全排列不一样,子集中 ${1, 2}$ 和 ${2, 1}$ 是等价的。为了避免重复,我们之前考虑过的元素,就不再重复考虑了。 + - 选择元素:将其添加到当前子集数组 $path$ 中。 + - 递归搜索:在选择该元素的情况下,继续递归考虑下一个位置上的元素。 + - 撤销选择:将该元素从当前子集数组 $path$ 中移除。 + ```python + for i in range(index, len(nums)): # 枚举可选元素列表 + path.append(nums[i]) # 选择元素 + backtracking(nums, i + 1) # 递归搜索 + path.pop() # 撤销选择 + ``` + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是当正在考虑的元素位置到达数组末尾(即 $start \ge len(nums)$)时,递归停止。 + - 从决策树中也可以看出,子集需要存储的答案集合应该包含决策树上所有的节点,应该需要保存递归搜索的所有状态。所以无论是否达到终止条件,我们都应该将当前符合条件的结果放入到集合中。 + +##### 思路 1:代码 + +```python +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + res = [] # 存放所有符合条件结果的集合 + path = [] # 存放当前符合条件的结果 + def backtracking(nums, index): # 正在考虑可选元素列表中第 index 个元素 + res.append(path[:]) # 将当前符合条件的结果放入集合中 + if index >= len(nums): # 遇到终止条件(本题) + return + + for i in range(index, len(nums)): # 枚举可选元素列表 + path.append(nums[i]) # 选择元素 + backtracking(nums, i + 1) # 递归搜索 + path.pop() # 撤销选择 + + backtracking(nums, 0) + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 $nums$ 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 +- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 + +### 5.2 N 皇后 + +#### 5.2.1 题目链接 + +- [51. N 皇后 - 力扣(LeetCode)](https://leetcode.cn/problems/n-queens/) + +#### 5.2.2 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回所有不同的「$n$ 皇后问题」的解决方案。每一种解法包含一个不同的「$n$ 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 + +**说明**: + +- **n 皇后问题**:将 $n$ 个皇后放置在 $n \times n$ 的棋盘上,并且使得皇后彼此之间不能攻击。 +- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 +- $1 \le n \le 9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +解释:如下图所示,4 皇后问题存在 2 个不同的解法。 +``` + +![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) + +#### 5.2.3 解题思路 + +##### 思路 1:回溯算法 + +这道题是经典的回溯问题。我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 + +对于 $n \times n$ 的棋盘来说,每一行有 $n$ 列,也就有 $n$ 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 + +并且在放置完之后,通过回溯的方式尝试其他可能的分支。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +![](https://qcdn.itcharge.cn/images/20220426095225.png) + +1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 +3. **将决策树和终止条件翻译成代码:** + 1. 定义回溯函数: + - 首先我们先使用一个 $n \times n$ 大小的二维矩阵 $chessboard$ 来表示当前棋盘,$chessboard$ 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 + - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 $chessboard$(棋盘数组)和 $row$(代表当前正在考虑放置第 $row$ 行皇后),全局变量是 $res$(存放所有符合条件结果的集合数组)。 + - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 $row$ 行皇后的情况下,递归放置剩下行的皇后。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 枚举出当前行所有的列。对于每一列位置: + - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 + - 选择元素:选择 $row, col$ 位置放置皇后,将其棋盘对应位置设置为 `Q`。 + - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 + - 撤销选择:将棋盘上 $row, col$ 位置设置为 `.`。 + ```python + # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + ``` + + ```python + for col in range(n): # 枚举可放置皇后的列 + if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 + chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 + backtrack(row + 1, chessboard) # 递归放置 row + 1 行之后的皇后 + chessboard[row][col] = '.' # 撤销选择 row, col 位置 + ``` + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后(即 $row == n$)时,递归停止。 + - 递归停止时,将当前符合条件的棋盘转换为答案需要的形式,然后将其存入答案数组 $res$ 中即可。 + +##### 思路 1:代码 + +```python +class Solution: + res = [] + def backtrack(self, n: int, row: int, chessboard: List[List[str]]): + if row == n: + temp_res = [] + for temp in chessboard: + temp_str = ''.join(temp) + temp_res.append(temp_str) + self.res.append(temp_res) + return + for col in range(n): + if self.isValid(n, row, col, chessboard): + chessboard[row][col] = 'Q' + self.backtrack(n, row + 1, chessboard) + chessboard[row][col] = '.' + + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + + def solveNQueens(self, n: int) -> List[List[str]]: + self.res.clear() + chessboard = [['.' for _ in range(n)] for _ in range(n)] + self.backtrack(n, 0, chessboard) + return self.res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 +- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 + +## 练习题目 + +- [0046. 全排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) +- [0047. 全排列 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations-ii.md) +- [0022. 括号生成](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) +- [0017. 电话号码的字母组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md) +- [0039. 组合总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) +- [0040. 组合总和 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum-ii.md) +- [0078. 子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) +- [0090. 子集 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets-ii.md) +- [0079. 单词搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/word-search.md) + +- [回溯算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【题解】[回溯算法入门级详解 + 练习(持续更新) - 全排列 - 力扣](https://leetcode.cn/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/) +- 【题解】[「代码随想录」带你学透回溯算法!51. N-Queens - N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/solution/dai-ma-sui-xiang-lu-51-n-queenshui-su-fa-2k32/) +- 【文章】[回溯算法详解](https://mp.weixin.qq.com/s/trILKSiN9EoS58pXmvUtUQ) +- 【文章】[回溯算法详解修订版 - labuladong](https://github.com/labuladong/fucking-algorithm/blob/master/算法思维系列/回溯算法详解修订版.md) +- 【文章】[【算法】回溯法四步走 - Nemo& - 博客园](https://www.cnblogs.com/blknemo/p/12431911.html) diff --git a/docs/07_algorithm/07_05_greedy_algorithm.md b/docs/07_algorithm/07_05_greedy_algorithm.md new file mode 100644 index 00000000..9ea54759 --- /dev/null +++ b/docs/07_algorithm/07_05_greedy_algorithm.md @@ -0,0 +1,255 @@ +## 1. 贪心算法简介 + +### 1.1 贪心算法的定义 + +> **贪心算法(Greedy Algorithm)**:一种在每次决策时,总是采取在当前状态下的最好选择,从而希望导致结果是最好或最优的算法。 + +贪心算法是一种改进的「分步解决算法」,其核心思想是:将求解过程分成「若干个步骤」,然后根据题意选择一种「度量标准」,每个步骤都应用「贪心原则」,选取当前状态下「最好 / 最优选择(局部最优解)」,并以此希望最后得出的结果也是「最好 / 最优结果(全局最优解)」。 + +换句话说,贪心算法不从整体最优上加以考虑,而是一步一步进行,每一步只以当前情况为基础,根据某个优化测度做出局部最优选择,从而省去了为找到最优解要穷举所有可能所必须耗费的大量时间。 + +### 1.2 贪心算法的特征 + +对许多问题来说,可以使用贪心算法,通过局部最优解而得到整体最优解或者是整体最优解的近似解。但并不是所有问题,都可以使用贪心算法的。 + +一般来说,这些能够使用贪心算法解决的问题必须满足下面的两个特征: + +1. **贪⼼选择性质** +2. **最优子结构** + +#### 1.2.1 贪心选择性质 + +> **贪心选择性质**:指的是一个问题的全局最优解可以通过一系列局部最优解(贪心选择)来得到。 + +换句话说,当进行选择时,我们直接做出在当前问题中看来最优的选择,而不用去考虑子问题的解。在做出选择之后,才会去求解剩下的子问题,如下图所示。 + +![贪心选择性质](https://qcdn.itcharge.cn/images/20240513163300.png) + +贪心算法在进行选择时,可能会依赖之前做出的选择,但不会依赖任何将来的选择或是子问题的解。运用贪心算法解决的问题在程序的运行过程中无回溯过程。 + +#### 1.2.2 最优子结构性质 + +> **最优子结构性质**:指的是一个问题的最优解包含其子问题的最优解。 + +问题的最优子结构性质是该问题能否用贪心算法求解的关键。 + +举个例子,如下图所示,原问题 $S = \lbrace a_1, a_2, a_3, a_4 \rbrace$,在 $a_1$ 步我们通过贪心选择选出一个当前最优解之后,问题就转换为求解子问题 $S_{\text{子问题}} = \lbrace a_2, a_3, a_4 \rbrace$。如果原问题 $S$ 的最优解可以由「第 $a_1$ 步通过贪心选择的局部最优解」和「 $S_{\text{子问题}}$ 的最优解」构成,则说明该问题满足最优子结构性质。 + +也就是说,如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。 + +![最优子结构性质](https://qcdn.itcharge.cn/images/20240513163310.png) + +在做了贪心选择后,满足最优子结构性质的原问题可以分解成规模更小的类似子问题来解决,并且可以通过贪心选择和子问题的最优解推导出问题的最优解。 + +反之,如果不能利用子问题的最优解推导出整个问题的最优解,那么这种问题就不具有最优子结构。 + +### 1.3 贪心算法正确性的证明 + +贪心算法最难的部分不在于问题的求解,而在于是正确性的证明。我们常用的证明方法有「数学归纳法」和「交换论证法」。 + +> - **数学归纳法**:先计算出边界情况(例如 $n = 1$)的最优解,然后再证明对于每个 $n$,$F_{n + 1}$ 都可以由 $F_n$ 推导出。 +> +> - **交换论证法**:从最优解出发,在保证全局最优不变的前提下,如果交换方案中任意两个元素 / 相邻的两个元素后,答案不会变得更好,则可以推定目前的解是最优解。 + +判断一个问题是否通过贪心算法求解,是需要进行严格的数学证明的。但是在日常写题或者算法面试中,不太会要求大家去证明贪心算法的正确性。 + +所以,当我们想要判断一个问题是否通过贪心算法求解时,我们可以: + +1. **凭直觉**:如果感觉这道题可以通过「贪心算法」去做,就尝试找到局部最优解,再推导出全局最优解。 +2. **举反例**:尝试一下,举出反例。也就是说找出一个局部最优解推不出全局最优解的例子,或者找出一个替换当前子问题的最优解,可以得到更优解的例子。如果举不出反例,大概率这道题是可以通过贪心算法求解的。 + +## 3. 贪心算法三步走 + +1. **转换问题**:将优化问题转换为具有贪心选择性质的问题,即先做出选择,再解决剩下的一个子问题。 +2. **贪心选择性质**:根据题意选择一种度量标准,制定贪心策略,选取当前状态下「最好 / 最优选择」,从而得到局部最优解。 +3. **最优子结构性质**:根据上一步制定的贪心策略,将贪心选择的局部最优解和子问题的最优解合并起来,得到原问题的最优解。 + +## 4. 贪心算法的应用 + +### 4.1 分发饼干 + +#### 4.1.1 题目链接 + +- [455. 分发饼干 - 力扣](https://leetcode.cn/problems/assign-cookies/) + +#### 4.1.2 题目大意 + +**描述**:一位很棒的家长为孩子们分发饼干。对于每个孩子 $i$,都有一个胃口值 $g[i]$,即每个小孩希望得到饼干的最小尺寸值。对于每块饼干 $j$,都有一个尺寸值 $s[j]$。只有当 $s[j] > g[i]$ 时,我们才能将饼干 $j$ 分配给孩子 $i$。每个孩子最多只能给一块饼干。 + +现在给定代表所有孩子胃口值的数组 $g$ 和代表所有饼干尺寸的数组 $j$。 + +**要求**:尽可能满足越多数量的孩子,并求出这个最大数值。 + +**说明**: + +- $1 \le g.length \le 3 * 10^4$。 +- $0 \le s.length \le 3 * 10^4$。 +- $1 \le g[i], s[j] \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:g = [1,2,3], s = [1,1] +输出:1 +解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1, 2, 3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以应该输出 1。 +``` + +- 示例 2: + +```python +输入: g = [1,2], s = [1,2,3] +输出: 2 +解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1, 2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2。 +``` + +#### 4.1.3 解题思路 + +##### 思路 1:贪心算法 + +为了尽可能的满⾜更多的⼩孩,而且一块饼干不能掰成两半,所以我们应该尽量让胃口小的孩子吃小块饼干,这样胃口大的孩子才有大块饼干吃。 + +所以,从贪心算法的角度来考虑,我们应该按照孩子的胃口从小到大对数组 $g$ 进行排序,然后按照饼干的尺寸大小从小到大对数组 $s$ 进行排序,并且对于每个孩子,应该选择满足这个孩子的胃口且尺寸最小的饼干。 + +下面我们使用贪心算法三步走的方法解决这道题。 + +1. **转换问题**:将原问题转变为,当胃口最小的孩子选择完满足这个孩子的胃口且尺寸最小的饼干之后,再解决剩下孩子的选择问题(子问题)。 +2. **贪心选择性质**:对于当前孩子,用尺寸尽可能小的饼干满足这个孩子的胃口。 +3. **最优子结构性质**:在上面的贪心策略下,当前孩子的贪心选择 + 剩下孩子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得满足胃口的孩子数量达到最大。 + +使用贪心算法的代码解决步骤描述如下: + +1. 对数组 $g$、$s$ 进行从小到大排序,使用变量 $index\underline{\hspace{0.5em}}g$ 和 $index\underline{\hspace{0.5em}}s$ 分别指向 $g$、$s$ 初始位置,使用变量 $res$ 保存结果,初始化为 $0$。 +2. 对比每个元素 $g[index\underline{\hspace{0.5em}}g]$ 和 $s[index\underline{\hspace{0.5em}}s]$: + 1. 如果 $g[index\underline{\hspace{0.5em}}g] \le s[index\underline{\hspace{0.5em}}s]$,说明当前饼干满足当前孩子胃口,则答案数量加 $1$,并且向右移动 $index\underline{\hspace{0.5em}}g$ 和 $index\underline{\hspace{0.5em}}s$。 + 2. 如果 $g[index\underline{\hspace{0.5em}}g] > s[index\underline{\hspace{0.5em}}s]$,说明当前饼干无法满足当前孩子胃口,则向右移动 $index_s$,判断下一块饼干是否可以满足当前孩子胃口。 +3. 遍历完输出答案 $res$。 + +##### 思路 1:代码 + +```python +class Solution: + def findContentChildren(self, g: List[int], s: List[int]) -> int: + g.sort() + s.sort() + index_g, index_s = 0, 0 + res = 0 + while index_g < len(g) and index_s < len(s): + if g[index_g] <= s[index_s]: + res += 1 + index_g += 1 + index_s += 1 + else: + index_s += 1 + + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times \log m + n \times \log n)$,其中 $m$ 和 $n$ 分别是数组 $g$ 和 $s$ 的长度。 +- **空间复杂度**:$O(\log m + \log n)$。 + +### 4.2 无重叠区间 + +#### 4.2.1 题目链接 + +- [435. 无重叠区间 - 力扣](https://leetcode.cn/problems/non-overlapping-intervals/) + +#### 4.2.2 题目大意 + +**描述**:给定一个区间的集合 $intervals$,其中 $intervals[i] = [starti, endi]$。从集合中移除部分区间,使得剩下的区间互不重叠。 + +**要求**:返回需要移除区间的最小数量。 + +**说明**: + +- $1 \le intervals.length \le 10^5$。 +- $intervals[i].length == 2$。 +- $-5 * 10^4 \le starti < endi \le 5 * 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:intervals = [[1,2],[2,3],[3,4],[1,3]] +输出:1 +解释:移除 [1,3] 后,剩下的区间没有重叠。 +``` + +- 示例 2: + +```python +输入: intervals = [ [1,2], [1,2], [1,2] ] +输出: 2 +解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 +``` + +#### 4.2.3 解题思路 + +##### 思路 1:贪心算法 + +这道题我们可以转换一下思路。原题要求保证移除区间最少,使得剩下的区间互不重叠。换个角度就是:「如何使得剩下互不重叠区间的数目最多」。那么答案就变为了:「总区间个数 - 不重叠区间的最多个数」。我们的问题也变成了求所有区间中不重叠区间的最多个数。 + +从贪心算法的角度来考虑,我们应该将区间按照结束时间排序。每次选择结束时间最早的区间,然后再在剩下的时间内选出最多的区间。 + +我们用贪心三部曲来解决这道题。 + +1. **转换问题**:将原问题转变为,当选择结束时间最早的区间之后,再在剩下的时间内选出最多的区间(子问题)。 +2. **贪心选择性质**:每次选择时,选择结束时间最早的区间。这样选出来的区间一定是原问题最优解的区间之一。 +3. **最优子结构性质**:在上面的贪心策略下,贪心选择当前时间最早的区间 + 剩下的时间内选出最多区间的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使所有区间中不重叠区间的个数最多。 + +使用贪心算法的代码解决步骤描述如下: + +1. 将区间集合按照结束坐标升序排列,然后维护两个变量,一个是当前不重叠区间的结束时间 $end\underline{\hspace{0.5em}}pos$,另一个是不重叠区间的个数 $count$。初始情况下,结束坐标 $end\underline{\hspace{0.5em}}pos$ 为第一个区间的结束坐标,$count$ 为 $1$。 +2. 依次遍历每段区间。对于每段区间:$intervals[i]$: + 1. 如果 $end\underline{\hspace{0.5em}}pos \le intervals[i][0]$,即 $end\underline{\hspace{0.5em}}pos$ 小于等于区间起始位置,则说明出现了不重叠区间,令不重叠区间数 $count$ 加 $1$,$end\underline{\hspace{0.5em}}pos$ 更新为新区间的结束位置。 +3. 最终返回「总区间个数 - 不重叠区间的最多个数」即 $len(intervals) - count$ 作为答案。 + +##### 思路 1:代码 + +```python +class Solution: + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + if not intervals: + return 0 + intervals.sort(key=lambda x: x[1]) + end_pos = intervals[0][1] + count = 1 + for i in range(1, len(intervals)): + if end_pos <= intervals[i][0]: + count += 1 + end_pos = intervals[i][1] + + return len(intervals) - count +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是区间的数量。 +- **空间复杂度**:$O(\log n)$。 + +## 练习题目 + +- [0455. 分发饼干](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/assign-cookies.md) +- [0860. 柠檬水找零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/lemonade-change.md) +- [0135. 分发糖果](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/candy.md) +- [0055. 跳跃游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game.md) +- [0045. 跳跃游戏 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) +- [0881. 救生艇](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/boats-to-save-people.md) +- [0435. 无重叠区间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-overlapping-intervals.md) +- [0452. 用最少数量的箭引爆气球](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md) +- [1710. 卡车上的最大单元数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-units-on-a-truck.md) + +- [贪心算法题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[贪心 - OI Wiki](https://oi-wiki.org/basic/greedy/) +- 【博文】[贪心算法 | 算法吧](https://suanfa8.com/greedy/) +- 【博文】[贪心算法理论基础 - Carl - 代码随想录](https://github.com/youngyangyang04/leetcode-master/blob/master/problems/贪心算法理论基础.md) +- 【博文】[小白带你学 贪心算法(Greedy Algorithm) - 知乎](https://zhuanlan.zhihu.com/p/53334049) +- 【书籍】算法导论 第三版(中文版)- 殷建平等 译 +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 \ No newline at end of file diff --git a/docs/07_algorithm/07_06_bit_operation.md b/docs/07_algorithm/07_06_bit_operation.md new file mode 100644 index 00000000..52bd9791 --- /dev/null +++ b/docs/07_algorithm/07_06_bit_operation.md @@ -0,0 +1,329 @@ +## 1. 位运算简介 + +### 1.1 位运算与二进制简介 + +> **位运算(Bit Operation)**:在计算机内部,数是以「二进制(Binary)」的形式来进行存储。位运算就是直接对数的二进制进行计算操作,在程序中使用位运算进行操作,会大大提高程序的性能。 + +在学习二进制数的位运算之前,我们先来了解一下什么叫做「二进制数」。 + +![二进制数](https://qcdn.itcharge.cn/images/202405132135165.png) + +> **二进制数(Binary)**:由 $0$ 和 $1$ 两个数码来表示的数。二进制数中每一个 $0$ 或每一个 $1$ 都称为一个「位(Bit)」。 + +我们通常使用的十进制数有 $0 \sim 9$ 共 $10$ 个数字,进位规则是「满十进一」。例如: + +1. $7_{(10)} + 2_{(10)} = 9_{(10)}$:$7_{(10)}$ 加上 $2_{(10)}$ 等于 $9_{(10)}$。 +2. $9_{(10)} + 2_{(10)} = 11_{(10)}$:$9_{(10)}$ 加上 $2_{(10)}$ 之后个位大于等于 $10$,符合「满十进一」,结果等于 $11_{(10)}$。 + +而在二进制数中,我们只有 $0$ 和 $1$ 两个数码,它的进位规则是「逢二进一」。例如: + +1. $1_{(2)} + 0_{(2)} = 1_{(2)}$:$1_{(2)}$ 加上 $0_{(2)}$ 等于 $1_{(2)}$。 +2. $1_{(2)} + 1_{(2)} = 10_{(2)}$:$1_{(2)}$ 加上 $1_{(2)}$,大于等于 $2$,符合「逢二进一」,结果等于 $10_{(2)}$。 +3. $10_{(2)} + 1_{(2)} = 11_{(2)}$。 + +### 1.2 二进制数的转换 + +#### 1.2.1 二进制转十进制数 + +在十进制数中,数字 $2749_{(10)}$ 可以理解为 $2 \times 1000 + 7 \times 100 + 4 \times 10 + 9 * 1$,相当于 $2 \times 10^3 + 7 \times 10^2 + 4 \times 10^1 + 9 \times 10^0$,即 $2000 + 700 + 40 + 9 = 2749_{(10)}$。 + +同理,在二进制数中,$01101010_{(2)}$ 可以看作为 $(0 \times 2^7) + (1 \times 2^6) + (1 \times 2^5) + (0 \times 2^4) + (1 \times 2^3) + (0 \times 2^2) + (1 \times 2^1) + (0 \times 2^0)$,即 $0 + 64 + 32 + 0 + 8 + 0 + 2 + 0 = 106_{(10)}$。 + +![二进制数转十进制数](https://qcdn.itcharge.cn/images/202405132136456.png) + +我们可以通过这样的方式,将一个二进制数转为十进制数。 + +#### 1.2.2 十进制转二进制数 + +十进制数转二进制数的方法是:**除二取余,逆序排列法**。 + +我们以十进制数中的 $106_{(10)}$ 为例。 + +$\begin{aligned} 106 \div 2 = 53 & \text{(余 0)} \cr 53 \div 2 = 26 & \text{(余 1)} \cr 26 \div 2 = 13 & \text{(余 0)} \cr 13 \div 2 = 6 & \text{(余 1)} \cr 6 \div 2 = 3 & \text{(余 0)} \cr 3 \div 2 = 1 & \text{(余 1)} \cr 1 \div 2 = 0 & \text{(余 1)} \cr 0 \div 2 = 0 & \text{(余 0)} \end{aligned}$ + +我们反向遍历每次计算的余数,依次是 $0$,$1$,$1$,$0$,$1$,$0$,$1$,$0$,即 $01101010_{(2)}$。 + +## 2. 位运算基础操作 + +在二进制的基础上,我们可以对二进制数进行相应的位运算。基本的位运算共有 $6$ 种,分别是:「按位与运算」、「按位或运算」、「按位异或运算」、「取反运算」、「左移运算」、「右移运算」。 + +这里的「按位与运算」、「按位或运算」、「按位异或运算」、「左移运算」、「右移运算」是双目运算。 + +- 「按位与运算」、「按位或运算」、「按位异或运算」是将两个整数作为二进制数,对二进制数表示中的每一位(即二进位)逐一进行相应运算,即双目运算。 +- 「左移运算」、「右移运算」是将左侧整数作为二进制数,将右侧整数作为移动位数,然后对左侧二进制数的全部位进行移位运算,每次移动一位,总共移动右侧整数次位,也是双目运算。 + +而「取反运算」是单目运算,是对一个整数的二进制数进行的位运算。 + +我们先来看下这 $6$ 种位运算的规则,再来进行详细讲解。 + +| 运算符 | 描述 | 规则 | +| ------------------- | -------------- | ----------------------------------------------------------------------------------------- | +| | | 按位或运算符 | 只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。 | +| `&` | 按位与运算符 | 只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。 | +| `<<` | 左移运算符 | 将二进制数的各个二进位全部左移若干位。`<<` 右侧数字指定了移动位数,高位丢弃,低位补 $0$。 | +| `>>` | 右移运算符 | 对二进制数的各个二进位全部右移若干位。`>>` 右侧数字指定了移动位数,低位丢弃,高位补 $0$。 | +| `^` | 按位异或运算符 | 对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。 | +| `~` | 取反运算符 | 对二进制数的每个二进位取反,使数字 $1$ 变为 $0$,$0$ 变为 $1$。 | + +### 2.1 按位与运算 + +> **按位与运算(AND)**:按位与运算符为 `&`。其功能是对两个二进制数的每一个二进位进行与运算。 + +- **按位与运算规则**:只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。 + + - `1 & 1 = 1` + + - `1 & 0 = 0` + + - `0 & 1 = 0` + + - `0 & 0 = 0` + + +举个例子,对二进制数 $01111100_{(2)}$ 与 $00111110_{(2)}$ 进行按位与运算,结果为 $00111100_{(2)}$,如图所示: + +![按位与运算](https://qcdn.itcharge.cn/images/202405132137023.png) + +### 2.2 按位或运算 + +> **按位或运算(OR)**:按位或运算符为 `|`。其功能对两个二进制数的每一个二进位进行或运算。 + +- **按位或运算规则**:只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。 + - `1 | 1 = 1` + - `1 | 0 = 1` + - `0 | 1 = 1` + - `0 | 0 = 0` + + +举个例子,对二进制数 $01001010_{(2)}$ 与 $01011011_{(2)}$ 进行按位或运算,结果为 $01011011_{(2)}$,如图所示: + +![按位或运算](https://qcdn.itcharge.cn/images/202405132137593.png) + +### 2.3 按位异或运算 + +> **按位异或运算(XOR)**:按位异或运算符为 `^`。其功能是对两个二进制数的每一个二进位进行异或运算。 + +- **按位异或运算规则**:对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。 +- `0 ^ 0 = 0` + +- `1 ^ 0 = 1` + +- `0 ^ 1 = 1` + +- `1 ^ 1 = 0` + + +举个例子,对二进制数 $01001010_{(2)}$ 与 $01000101_{(2)}$ 进行按位异或运算,结果为 $00001111_{(2)}$,如图所示: + +![按位异或运算](https://qcdn.itcharge.cn/images/202405132137874.png) + +### 2.4 取反运算 + +>**取反运算(NOT)**:取反运算符为 `~`。其功能是对一个二进制数的每一个二进位进行取反运算。 + +- **取反运算规则**:使数字 $1$ 变为 $0$,$0$ 变为 $1$。 + - `~0 = 1` + - `~1 = 0` + +举个例子,对二进制数 $01101010_{(2)}$ 进行取反运算,结果如图所示: + +![取反运算](https://qcdn.itcharge.cn/images/202405132138853.png) + +### 2.5 左移运算和右移运算 + +> **左移运算(SHL)**: 左移运算符为 `<<`。其功能是对一个二进制数的各个二进位全部左移若干位(高位丢弃,低位补 $0$)。 + +举个例子,对二进制数 $01101010_{(2)}$ 进行左移 $1$ 位运算,结果为 $11010100_{(2)}$,如图所示: + +![左移运算](https://qcdn.itcharge.cn/images/202405132138841.png) + +> **右移运算(SHR)**: 右移运算符为 `>>`。其功能是对一个二进制数的各个二进位全部右移若干位(低位丢弃,高位补 $0$)。 + +举个例子,对二进制数 $01101010_{(2)}$ 进行右移 $1$ 位运算,结果为 $00110101_{(2)}$,如图所示: + +![右移运算](https://qcdn.itcharge.cn/images/202405132138348.png) + +## 3. 位运算的应用 + +### 3.1 位运算的常用操作 + +#### 3.1.1 判断整数奇偶 + +一个整数,只要是偶数,其对应二进制数的末尾一定为 $0$;只要是奇数,其对应二进制数的末尾一定为 $1$。所以,我们通过与 $1$ 进行按位与运算,即可判断某个数是奇数还是偶数。 + +1. `(x & 1) == 0` 为偶数。 +2. `(x & 1) == 1` 为奇数。 + +#### 3.1.2 二进制数选取指定位 + +如果我们想要从一个二进制数 $X$ 中取出某几位,使取出位置上的二进位保留原值,其余位置为 $0$,则可以使用另一个二进制数 $Y$,使该二进制数上对应取出位置为 $1$,其余位置为 $0$。然后令两个数进行按位与运算(`X & Y`),即可得到想要的数。 + +举个例子,比如我们要取二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$ (末尾 $4$ 位为 $1$,其余位为 $0$) 进行按位与运算,即 `01101010 & 00001111 == 00001010`。其结果 $00001010$ 就是我们想要的数(即二进制数 $01101010_{(2)}$ 的末尾 $4$ 位)。 + +#### 3.1.3 将指定位设置为 $1$ + +如果我们想要把一个二进制数 $X$ 中的某几位设置为 $1$,其余位置保留原值,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位或运算(`X | Y`),即可得到想要的数。 + +举个例子,比如我们想要将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位或运算,即 `01101010 | 00001111 = 01101111`。其结果 $01101111$ 就是我们想要的数(即将二进制数 $01101010_{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值)。 + +#### 3.1.4 反转指定位 + +如果我们想要把一个二进制数 $X$ 的某几位进行反转,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位异或运算(`X ^ Y`),即可得到想要的数。 + +举个例子,比如想要将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位进行反转,则只需将 $X = 01101010_{(2)}$ 与 $Y = 00001111_{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位异或运算,即 `01101010 ^ 00001111 = 01100101`。其结果 $01100101$ 就是我们想要的数(即将二进制数 $X = 01101010_{(2)}$ 的末尾 $4$ 位进行反转)。 + +#### 3.1.5 交换两个数 + +通过按位异或运算可以实现交换两个数的目的(只能用于交换两个整数)。 + +```python +a, b = 10, 20 +a ^= b +b ^= a +a ^= b +print(a, b) +``` + +#### 3.1.6 将二进制最右侧为 $1$ 的二进位改为 $0$ + +如果我们想要将一个二进制数 $X$ 最右侧为 $1$ 的二进制位改为 $0$,则只需通过 `X & (X - 1)` 的操作即可完成。 + +比如 $X = 01101100_{(2)}$,$X - 1 = 01101011_{(2)}$,则 `X & (X - 1) == 01101100 & 01101011 == 01101000`,结果为 $01101000_{(2)}$(即将 $X$ 最右侧为 $1$ 的二进制为改为 $0$)。 + +#### 3.1.7 计算二进制中二进位为 $1$ 的个数 + +从 3.1.6 中得知,通过 `X & (X - 1)` 我们可以将二进制 $X$ 最右侧为 $1$ 的二进制位改为 $0$,那么如果我们不断通过 `X & (X - 1)` 操作,最终将二进制 $X$ 变为 $0$,并统计执行次数,则可以得到二进制中二进位为 $1$ 的个数。 + +具体代码如下: + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + cnt = 0 + while n: + n = n & (n - 1) + cnt += 1 + return cnt +``` + +#### 3.1.8 判断某数是否为 $2$ 的幂次方 + +通过判断 `X & (X - 1) == 0` 是否成立,即可判断 $X$ 是否为 $2$ 的幂次方。 + +这是因为: + +1. 凡是 $2$ 的幂次方,其二进制数的某一高位为 $1$,并且仅此高位为 $1$,其余位都为 $0$。比如:$4_{(10)} = 00000100_{(2)}$、$8_{(10)} = 00001000_{(2)}$。 +2. 不是 $2$ 的幂次方,其二进制数存在多个值为 $1$ 的位。比如:$5_{10} = 00000101_{(2)}$、$6_{10} = 00000110_{(2)}$。 + +接下来我们使用 `X & (X - 1)` 操作,将原数对应二进制数最右侧为 $1$ 的二进位改为 $0$ 之后,得到新值: + +1. 如果原数是 $2$ 的幂次方,则通过 `X & (X - 1)` 操作之后,新值所有位都为 $0$,值为 $0$。 +2. 如果该数不是 $2$ 的幂次方,则通过 `X & (X - 1)` 操作之后,新值仍存在不为 $0$ 的位,值肯定不为 $0$。 + +所以我们可以通过是否为 $0$ 即可判断该数是否为 $2$ 的幂次方。 + +### 3.2 位运算的常用操作总结 + +| 功 能 | 位运算 | 示例 | +| ----------------------------------------- | ------------------------------------------------------- | ------------------------- | +| **从右边开始,把最后一个 $1$ 改写成 $0$** | x & (x - 1) | `100101000 -> 100100000` | +| **去掉右边起第一个 $1$ 的左边** | x & (x ^ (x - 1))x & (-x) | `100101000 -> 1000` | +| **去掉最后一位** | x >> 1 | `101101 -> 10110` | +| **取右数第 $k$ 位** | x >> (k - 1) & 1 | `1101101 -> 1, k = 4` | +| **取末尾 $3$ 位** | x & 7 | `1101101 -> 101` | +| **取末尾 $k$ 位** | x & 15 | `1101101 -> 1101, k = 4` | +| **只保留右边连续的 $1$** | (x ^ (x + 1)) >> 1 | `100101111 -> 1111` | +| **右数第 $k$ 位取反** | x ^ (1 << (k - 1)) | `101001 -> 101101, k = 3` | +| **在最后加一个 $0$** | x << 1 | `101101 -> 1011010` | +| **在最后加一个 $1$** | (x << 1) + 1 | `101101 -> 1011011` | +| **把右数第 $k$ 位变成 $0$** | x & ~(1 << (k - 1)) | `101101 -> 101001, k = 3` | +| **把右数第 $k$ 位变成 $1$** | x | (1 << (k - 1)) | `101001 -> 101101, k = 3` | +| **把右边起第一个 $0$ 变成 $1$** | x | (x + 1) | `100101111 -> 100111111` | +| **把右边连续的 $0$ 变成 $1$** | x | (x - 1) | `11011000 -> 11011111` | +| **把右边连续的 $1$ 变成 $0$** | x & (x + 1) | `100101111 -> 100100000` | +| **把最后一位变成 $0$** | x | 1 - 1 | `101101 -> 101100` | +| **把最后一位变成 $1$** | x | 1 | `101100 -> 101101` | +| **把末尾 $k$ 位变成 $1$** | x | (1 << k - 1) | `101001 -> 101111, k = 4` | +| **最后一位取反** | x ^ 1 | `101101 -> 101100` | +| **末尾 $k$ 位取反** | x ^ (1 << k - 1) | `101001 -> 100110, k = 4` | + +### 3.3 二进制枚举子集 + +除了上面的这些常见操作,我们经常常使用二进制数第 $1 \sim n$ 位上 $0$ 或 $1$ 的状态来表示一个由 $1 \sim n$ 组成的集合。也就是说通过二进制来枚举子集。 + +#### 3.3.1 二进制枚举子集简介 + +先来介绍一下「子集」的概念。 + +- **子集**:如果集合 $A$ 的任意一个元素都是集合 $S$ 的元素,则称集合 $A$ 是集合 $S$ 的子集。可以记为 $A \in S$。 + +有时候我们会遇到这样的问题:给定一个集合 $S$,枚举其所有可能的子集。 + +枚举子集的方法有很多,这里介绍一种简单有效的枚举方法:「二进制枚举子集算法」。 + +对于一个元素个数为 $n$ 的集合 $S$ 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 $1$ 来表示选取该元素,用数字 $0$ 来表示不选取该元素。 + +那么我们就可以用一个长度为 $n$ 的二进制数来表示集合 $S$ 或者表示 $S$ 的子集。其中二进制的每一个二进位都对应了集合中某一个元素的选取状态。对于集合中第 $i$ 个元素来说,二进制对应位置上的 $1$ 代表该元素被选取,$0$ 代表该元素未被选取。 + +举个例子,比如长度为 $5$ 的集合 $S = \lbrace 5, 4, 3, 2, 1 \rbrace$,我们可以用一个长度为 $5$ 的二进制数来表示该集合。 + +比如二进制数 $11111_{(2)}$ 就表示选取集合的第 $1$ 位、第 $2$ 位、第 $3$ 位、第 $4$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 4, 3, 2, 1 \rbrace$,即集合 $S$ 本身。如下表所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :--: | :--: | :--: | :--: | :--: | +| 二进位对应值 | 1 | 1 | 1 | 1 | 1 | +| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | + +再比如二进制数 $10101_{(2)}$ 就表示选取集合的第 $1$ 位、第 $3$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 3, 1 \rbrace$。如下表所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :--: | :----: | :--: | :----: | :--: | +| 二进位对应值 | 1 | 0 | 1 | 0 | 1 | +| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | + +再比如二进制数 $01001_{(2)}$ 就表示选取集合的第 $1$ 位、第 $4$ 位元素,也就是集合 $\lbrace 4, 1 \rbrace$。如下标所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :----: | :--: | :----: | :----: | :--: | +| 二进位对应值 | 0 | 1 | 0 | 0 | 1 | +| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | + +通过上面的例子我们可以得到启发:对于长度为 $5$ 的集合 $S$ 来说,我们只需要从 $00000 \sim 11111$ 枚举一次(对应十进制为 $0 \sim 2^5 - 1$)即可得到长度为 $5$ 的集合 $S$ 的所有子集。 + +我们将上面的例子拓展到长度为 $n$ 的集合 $S$。可以总结为: + +- 对于长度为 $n$ 的集合 $S$ 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到集合 $S$ 的所有子集。 + +#### 3.3.2 二进制枚举子集代码 + +```python +class Solution: + def subsets(self, S): # 返回集合 S 的所有子集 + n = len(S) # n 为集合 S 的元素个数 + sub_sets = [] # sub_sets 用于保存所有子集 + for i in range(1 << n): # 枚举 0 ~ 2^n - 1 + sub_set = [] # sub_set 用于保存当前子集 + for j in range(n): # 枚举第 i 位元素 + if i >> j & 1: # 如果第 i 为元素对应二进位删改为 1,则表示选取该元素 + sub_set.append(S[j]) # 将选取的元素加入到子集 sub_set 中 + sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 + return sub_sets # 返回所有子集 +``` + +## 练习题目 + +- [0190. 颠倒二进制位](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-bits.md) +- [0191. 位1的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/number-of-1-bits.md) +- [0201. 数字范围按位与](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md) +- [0136. 只出现一次的数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) +- [0137. 只出现一次的数字 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number-ii.md) +- [0260. 只出现一次的数字 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/single-number-iii.md) + +- [位运算题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E4%BD%8D%E8%BF%90%E7%AE%97%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【博文】[Python 中的按位运算符 |【生长吧!Python!】- 云社区 - 华为云](https://bbs.huaweicloud.com/blogs/280901) +- 【博文】[一文读懂位运算的使用 - 小黑说 Java - 掘金](https://juejin.cn/post/7011407264581943326) +- 【博文】[枚举排列和枚举子集 - CUC ACM-Wiki](https://cuccs.github.io/acm-wiki/search/enumeration/) +- 【博文】[Swift 运算符 | 菜鸟教程](https://www.runoob.com/swift/swift-operators.html) \ No newline at end of file diff --git a/docs/07_algorithm/index.md b/docs/07_algorithm/index.md new file mode 100644 index 00000000..1e2d7463 --- /dev/null +++ b/docs/07_algorithm/index.md @@ -0,0 +1,8 @@ +## 本章内容 + +- [7.1 枚举算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_01_enumeration_algorithm.md) +- [7.2 递归算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_02_recursive_algorithm.md) +- [7.3 分治算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_03_divide_and_conquer_algorithm.md) +- [7.4 回溯算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_04_backtracking_algorithm.md) +- [7.5 贪心算法](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_05_greedy_algorithm.md) +- [7.6 位运算](https://github.com/ITCharge/AlgoNote/tree/main/docs/07_algorithm/07_06_bit_operation.md) diff --git a/docs/08_dynamic_programming/08_01_dynamic_programming_basic.md b/docs/08_dynamic_programming/08_01_dynamic_programming_basic.md new file mode 100644 index 00000000..4d4e4402 --- /dev/null +++ b/docs/08_dynamic_programming/08_01_dynamic_programming_basic.md @@ -0,0 +1,405 @@ +## 1. 动态规划简介 + +### 1.1 动态规划的定义 + +> **动态规划(Dynamic Programming)**:简称 **DP**,是一种求解多阶段决策过程最优化问题的方法。在动态规划中,通过把原问题分解为相对简单的子问题,先求解子问题,再由子问题的解而得到原问题的解。 + +动态规划最早由理查德 · 贝尔曼于 1957 年在其著作「动态规划(Dynamic Programming)」一书中提出。这里的 Programming 并不是编程的意思,而是指一种「表格处理方法」,即将每一步计算的结果存储在表格中,供随后的计算查询使用。 + +### 1.2 动态规划的核心思想 + +> **动态规划的核心思想**: +> +> 1. 把「原问题」分解为「若干个重叠的子问题」,每个子问题的求解过程都构成一个 **「阶段」**。在完成一个阶段的计算之后,动态规划方法才会执行下一个阶段的计算。 +> 2. 在求解子问题的过程中,按照「自顶向下的记忆化搜索方法」或者「自底向上的递推方法」求解出「子问题的解」,把结果存储在表格中,当需要再次求解此子问题时,直接从表格中查询该子问题的解,从而避免了大量的重复计算。 + +这看起来很像是分治算法,但动态规划与分治算法的不同点在于: + +1. 适用于动态规划求解的问题,在分解之后得到的子问题往往是相互联系的,会出现若干个重叠子问题。 +2. 使用动态规划方法会将这些重叠子问题的解保存到表格里,供随后的计算查询使用,从而避免大量的重复计算。 + +### 1.3 动态规划的简单例子 + +下面我们先来通过一个简单的例子来介绍一下什么是动态规划算法,然后再来讲解动态规划中的各种术语。 + +> **斐波那契数列**:数列由 $f(0) = 1, f(1) = 2$ 开始,后面的每一项数字都是前面两项数字的和。也就是: +> +> $f(n) = \begin{cases} 0 & n = 0 \cr 1 & n = 1 \cr f(n - 2) + f(n - 1) & n > 1 \end{cases}$ + +通过公式 $f(n) = f(n - 2) + f(n - 1)$,我们可以将原问题 $f(n)$ 递归地划分为 $f(n - 2)$ 和 $f(n - 1)$ 这两个子问题。其对应的递归过程如下图所示: + +![斐波那契数列的重复计算项](https://qcdn.itcharge.cn/images/20230307164107.png) + +从图中可以看出:如果使用传统递归算法计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$,这样 $f(3)$ 就进行了多次计算。同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,从而导致了重复计算问题。 + +为了避免重复计算,我们可以使用动态规划中的「表格处理方法」来处理。 + +这里我们使用「自底向上的递推方法」求解出子问题 $f(n - 2)$ 和 $f(n - 1)$ 的解,然后把结果存储在表格中,供随后的计算查询使用。具体过程如下: + +1. 定义一个数组 $dp$,用于记录斐波那契数列中的值。 +2. 初始化 $dp[0] = 0, dp[1] = 1$。 +3. 根据斐波那契数列的递推公式 $f(n) = f(n - 1) + f(n - 2)$,从 $dp(2)$ 开始递推计算斐波那契数列的每个数,直到计算出 $dp(n)$。 +4. 最后返回 $dp(n)$ 即可得到第 $n$ 项斐波那契数。 + +具体代码如下: + +```python +class Solution: + def fib(self, n: int) -> int: + if n == 0: + return 0 + if n == 1: + return 1 + + dp = [0 for _ in range(n + 1)] + dp[0] = 0 + dp[1] = 1 + + for i in range(2, n + 1): + dp[i] = dp[i - 2] + dp[i - 1] + + return dp[n] +``` + +这种使用缓存(哈希表、集合或数组)保存计算结果,从而避免子问题重复计算的方法,就是「动态规划算法」。 + +## 2. 动态规划的特征 + +究竟什么样的问题才可以使用动态规划算法解决呢? + +首先,能够使用动态规划方法解决的问题必须满足以下三个特征: + +1. **最优子结构性质** +2. **重叠子问题性质** +3. **无后效性** + +### 2.1 最优子结构性质 + +> **最优子结构**:指的是一个问题的最优解包含其子问题的最优解。 + +举个例子,如下图所示,原问题 $S = \lbrace a_1, a_2, a_3, a_4 \rbrace$,在 $a_1$ 步我们选出一个当前最优解之后,问题就转换为求解子问题 $S_{\text{子问题}} = \lbrace a_2, a_3, a_4 \rbrace$。如果原问题 $S$ 的最优解可以由「第 $a_1$ 步得到的局部最优解」和「 $S_{\text{子问题}}$ 的最优解」构成,则说明该问题满足最优子结构性质。 + +也就是说,如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。 + +![最优子结构性质](https://qcdn.itcharge.cn/images/20240513163310.png) + +### 2.2 重叠子问题性质 + +> **重叠子问题性质**:指的是在求解子问题的过程中,有大量的子问题是重复的,一个子问题在下一阶段的决策中可能会被多次用到。如果有大量重复的子问题,那么只需要对其求解一次,然后用表格将结果存储下来,以后使用时可以直接查询,不需要再次求解。 + +![重叠子问题性质](https://qcdn.itcharge.cn/images/20230307164107.png) + +之前我们提到的「斐波那契数列」例子中,$f(0)$、$f(1)$、$f(2)$、$f(3)$ 都进行了多次重复计算。动态规划算法利用了子问题重叠的性质,在第一次计算 $f(0)$、$f(1)$、$f(2)$、$f(3)$ 时就将其结果存入表格,当再次使用时可以直接查询,无需再次求解,从而提升效率。 + +### 2.3 无后效性 + +> **无后效性**:指的是子问题的解(状态值)只与之前阶段有关,而与后面阶段无关。当前阶段的若干状态值一旦确定,就不再改变,不会再受到后续阶段决策的影响。 + +也就是说,**一旦某一个子问题的求解结果确定以后,就不会再被修改**。 + +举个例子,下图是一个有向无环带权图,我们在求解从 $A$ 点到 $F$ 点的最短路径问题时,假设当前已知从 $A$ 点到 $D$ 点的最短路径($2 + 7 = 9$)。那么无论之后的路径如何选择,都不会影响之前从 $A$ 点到 $D$ 点的最短路径长度。这就是「无后效性」。 + +而如果一个问题具有「后效性」,则可能需要先将其转化或者逆向求解来消除后效性,然后才可以使用动态规划算法。 + +![无后效性](https://qcdn.itcharge.cn/images/20240514110127.png) + +## 3. 动态规划的基本思路 + +如下图所示,我们在使用动态规划方法解决某些最优化问题时,可以将解决问题的过程按照一定顺序(时间顺序、空间顺序或其他顺序)分解为若干个相互联系的「阶段」。然后按照顺序对每一个阶段做出「决策」,这个决策既决定了本阶段的效益,也决定了下一阶段的初始状态。依次做完每个阶段的决策之后,就得到了一个整个问题的决策序列。 + +这样就将一个原问题分解为了一系列的子问题,再通过逐步求解从而获得最终结果。 + +![动态规划方法](https://qcdn.itcharge.cn/images/20240514110154.png) + +这种前后关联、具有链状结构的多阶段进行决策的问题也叫做「多阶段决策问题」。 + +通常我们使用动态规划方法来解决问题的基本思路如下: + +1. **划分阶段**:将原问题按顺序(时间顺序、空间顺序或其他顺序)分解为若干个相互联系的「阶段」。划分后的阶段⼀定是有序或可排序的,否则问题⽆法求解。 + - 这里的「阶段」指的是⼦问题的求解过程。每个⼦问题的求解过程都构成⼀个「阶段」,在完成前⼀阶段的求解后才会进⾏后⼀阶段的求解。 +2. **定义状态**:将和子问题相关的某些变量(位置、数量、体积、空间等等)作为一个「状态」表示出来。状态的选择要满⾜⽆后效性。 + - 一个「状态」对应一个或多个子问题,所谓某个「状态」下的值,指的就是这个「状态」所对应的子问题的解。 +3. **状态转移**:根据「上一阶段的状态」和「该状态下所能做出的决策」,推导出「下一阶段的状态」。或者说根据相邻两个阶段各个状态之间的关系,确定决策,然后推导出状态间的相互转移方式(即「状态转移方程」)。 +4. **初始条件和边界条件**:根据问题描述、状态定义和状态转移方程,确定初始条件和边界条件。 +5. **最终结果**:确定问题的求解目标,然后按照一定顺序求解每一个阶段的问题。最后根据状态转移方程的递推结果,确定最终结果。 + +## 4. 动态规划的应用 + +动态规划相关的问题往往灵活多变,思维难度大,没有特别明显的套路,并且经常会在各类算法竞赛和面试中出现。 + +动态规划问题的关键点在于「如何状态设计」和「推导状态转移条件」,还有各种各样的「优化方法」。这类问题一定要多练习、多总结,只有接触的题型多了,才能熟练掌握动态规划思想。 + +下面来介绍几道关于动态规划的基础题目。 + +### 4.1 斐波那契数 + +#### 4.1.1 题目链接 + +- [509. 斐波那契数 - 力扣](https://leetcode.cn/problems/fibonacci-number/) + +#### 4.1.2 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算第 $n$ 个斐波那契数。 + +**说明**: + +- 斐波那契数列的定义如下: + - $f(0) = 0, f(1) = 1$。 + - $f(n) = f(n - 1) + f(n - 2)$,其中 $n > 1$。 +- $0 \le n \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:1 +解释:F(2) = F(1) + F(0) = 1 + 0 = 1 +``` + +- 示例 2: + +```python +输入:n = 3 +输出:2 +解释:F(3) = F(2) + F(1) = 1 + 1 = 2 +``` + +#### 4.1.3 解题思路 + +###### 1. 划分阶段 + +我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:第 $i$ 个斐波那契数。 + +###### 3. 状态转移方程 + +根据题目中所给的斐波那契数列的定义 $f(n) = f(n - 1) + f(n - 2)$,则直接得出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 + +###### 4. 初始条件 + +根据题目中所给的初始条件 $f(0) = 0, f(1) = 1$ 确定动态规划的初始条件,即 $dp[0] = 0, dp[1] = 1$。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[n]$,即第 $n$ 个斐波那契数为 $dp[n]$。 + +#### 4.1.4 代码 + +```python +class Solution: + def fib(self, n: int) -> int: + if n <= 1: + return n + + dp = [0 for _ in range(n + 1)] + dp[0] = 0 + dp[1] = 1 + for i in range(2, n + 1): + dp[i] = dp[i - 2] + dp[i - 1] + + return dp[n] +``` + +#### 4.1.5 复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +### 4.2 爬楼梯 + +#### 4.2.1 题目链接 + +- [70. 爬楼梯 - 力扣](https://leetcode.cn/problems/climbing-stairs/) + +#### 4.2.2 题目大意 + +**描述**:假设你正在爬楼梯。需要 $n$ 阶你才能到达楼顶。每次你可以爬 $1$ 或 $2$ 个台阶。现在给定一个整数 $n$。 + +**要求**:计算出有多少种不同的方法可以爬到楼顶。 + +**说明**: + +- $1 \le n \le 45$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:2 +解释:有两种方法可以爬到楼顶。 +1. 1 阶 + 1 阶 +2. 2 阶 +``` + +- 示例 2: + +```python +输入:n = 3 +输出:3 +解释:有三种方法可以爬到楼顶。 +1. 1 阶 + 1 阶 + 1 阶 +2. 1 阶 + 2 阶 +3. 2 阶 + 1 阶 +``` + +#### 4.2.3 解题思路 + +###### 1. 划分阶段 + +我们按照台阶的阶层划分阶段,将其划分为 $0 \sim n$ 阶。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:爬到第 $i$ 阶台阶的方案数。 + +###### 3. 状态转移方程 + +根据题目大意,每次只能爬 $1$ 或 $2$ 个台阶。则第 $i$ 阶楼梯只能从第 $i - 1$ 阶向上爬 $1$ 阶上来,或者从第 $i - 2$ 阶向上爬 $2$ 阶上来。所以可以推出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 + +###### 4. 初始条件 + +- 第 $0$ 层台阶方案数:可以看做 $1$ 种方法(从 $0$ 阶向上爬 $0$ 阶),即 $dp[1] = 1$。 +- 第 $1$ 层台阶方案数:$1$ 种方法(从 $0$ 阶向上爬 $1$ 阶),即 $dp[1] = 1$。 +- 第 $2$ 层台阶方案数:$2$ 中方法(从 $0$ 阶向上爬 $2$ 阶,或者从 $1$ 阶向上爬 $1$ 阶)。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[n]$,即爬到第 $n$ 阶台阶(即楼顶)的方案数为 $dp[n]$。 + +虽然这道题跟上一道题的状态转移方程都是 $dp[i] = dp[i - 1] + dp[i - 2]$,但是两道题的考察方式并不相同,一定程度上也可以看出来动态规划相关题目的灵活多变。 + +#### 4.2.4 代码 + +```python +class Solution: + def climbStairs(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + dp[0] = 1 + dp[1] = 1 + for i in range(2, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + + return dp[n] +``` + +#### 4.2.5 复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 + +### 4.3 不同路径 + +#### 4.3.1 题目链接 + +- [62. 不同路径 - 力扣](https://leetcode.cn/problems/unique-paths/) + +#### 4.3.2 题目大意 + +**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 + +**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 + +**说明**: + +- $1 \le m, n \le 100$。 +- 题目数据保证答案小于等于 $2 \times 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:m = 3, n = 7 +输出:28 +``` + +- 示例 2: + +```python +输入:m = 3, n = 2 +输出:3 +解释: +从左上角开始,总共有 3 条路径可以到达右下角。 +1. 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 +3. 向下 -> 向右 -> 向下 +``` + +![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) + +#### 4.3.3 解题思路 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:从左上角到达 $(i, j)$ 位置的路径数量。 + +###### 3. 状态转移方程 + +因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 + +###### 4. 初始条件 + +- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 +- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 +- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 + +#### 4.3.4 代码 + +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp = [[0 for _ in range(n)] for _ in range(m)] + + for j in range(n): + dp[0][j] = 1 + for i in range(m): + dp[i][0] = 1 + + for i in range(1, m): + for j in range(1, n): + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + + return dp[m - 1][n - 1] +``` + +#### 4.3.5 复杂度分析 + +- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m \times n)$,所以总体时间复杂度为 $O(m \times n)$。 +- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $n$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(n)$。 + +## 题目练习 + +- [0509. 斐波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) +- [0070. 爬楼梯](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) +- [0062. 不同路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) + +- [动态规划基础题目](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E5%9F%BA%E7%A1%80%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[动态规划基础 - OI Wiki](https://oi-wiki.org/dp/basic/) +- 【文章】[动态规划 1 ——基本概念 - 知乎](https://zhuanlan.zhihu.com/p/25441186) +- 【文章】[动态规划算法 | 曹世宏的博客](https://cshihong.github.io/2018/03/30/动态规划算法/) +- 【文章】[动态规划之初识动规:有了四步解题法模板,再也不害怕动态规划! - 知乎](https://zhuanlan.zhihu.com/p/91680256) +- 【文章】[第 6 节 最优子结构、重复子问题、无后效性 | 算法吧](https://suanfa8.com/dynamic-programming/06/) +- 【书籍】算法训练营 陈小玉 著 +- 【书籍】趣学算法 陈小玉 著 +- 【书籍】算法竞赛进阶指南 - 李煜东 著 +- 【书籍】ACM-ICPC 程序设计系列 - 算法设计与实现 - 陈宇 吴昊 主编 diff --git a/docs/08_dynamic_programming/08_02_memoization_search.md b/docs/08_dynamic_programming/08_02_memoization_search.md new file mode 100644 index 00000000..b305026f --- /dev/null +++ b/docs/08_dynamic_programming/08_02_memoization_search.md @@ -0,0 +1,291 @@ +## 1. 记忆化搜索简介 + +>**记忆化搜索(Memoization Search)**:是一种通过存储已经遍历过的状态信息,从而避免对同一状态重复遍历的搜索算法。 + +记忆化搜索是动态规划的一种实现方式。在记忆化搜索中,当算法需要计算某个子问题的结果时,它首先检查是否已经计算过该问题。如果已经计算过,则直接返回已经存储的结果;否则,计算该问题,并将结果存储下来以备将来使用。 + +举个例子,比如「斐波那契数列」的定义是:$f(0) = 0, f(1) = 1, f(n) = f(n - 1) + f(n - 2)$。如果我们使用递归算法求解第 $n$ 个斐波那契数,则对应的递推过程如下: + +![记忆化搜索](https://qcdn.itcharge.cn/images/20240514110503.png) + +从图中可以看出:如果使用普通递归算法,想要计算 $f(5)$,需要先计算 $f(3)$ 和 $f(4)$,而在计算 $f(4)$ 时还需要计算 $f(3)$。这样 $f(3)$ 就进行了多次计算,同理 $f(0)$、$f(1)$、$f(2)$ 都进行了多次计算,从而导致了重复计算问题。 + +为了避免重复计算,在递归的同时,我们可以使用一个缓存(数组或哈希表)来保存已经求解过的 $f(k)$ 的结果。如上图所示,当递归调用用到 $f(k)$ 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。 + +使用「记忆化搜索」方法解决斐波那契数列的代码如下: + +```python +class Solution: + def fib(self, n: int) -> int: + # 使用数组保存已经求解过的 f(k) 的结果 + memo = [0 for _ in range(n + 1)] + return self.my_fib(n, memo) + + def my_fib(self, n: int, memo: List[int]) -> int: + if n == 0: + return 0 + if n == 1: + return 1 + + # 已经计算过结果 + if memo[n] != 0: + return memo[n] + + # 没有计算过结果 + memo[n] = self.my_fib(n - 1, memo) + self.my_fib(n - 2, memo) + return memo[n] +``` + +## 2. 记忆化搜索与递推区别 + +「记忆化搜索」与「递推」都是动态规划的实现方式,但是两者之间有一些区别。 + +> **记忆化搜索**:「自顶向下」的解决问题,采用自然的递归方式编写过程,在过程中会保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。 +> +> - 优点:代码清晰易懂,可以有效的处理一些复杂的状态转移方程。有些状态转移方程是非常复杂的,使用记忆化搜索可以将复杂的状态转移方程拆分成多个子问题,通过递归调用来解决。 +> - 缺点:可能会因为递归深度过大而导致栈溢出问题。 +> +> **递推**:「自底向上」的解决问题,采用循环的方式编写过程,在过程中通过保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。 +> +> - 优点:避免了深度过大问题,不存在栈溢出问题。计算顺序比较明确,易于实现。 +> - 缺点:无法处理一些复杂的状态转移方程。有些状态转移方程非常复杂,如果使用递推方法来计算,就会导致代码实现变得非常困难。 + +根据记忆化搜索和递推的优缺点,我们可以在不同场景下使用这两种方法。 + +适合使用「记忆化搜索」的场景: + +1. 问题的状态转移方程比较复杂,递推关系不是很明确。 +2. 问题适合转换为递归形式,并且递归深度不会太深。 + +适合使用「递推」的场景: + +1. 问题的状态转移方程比较简单,递归关系比较明确。 +2. 问题不太适合转换为递归形式,或者递归深度过大容易导致栈溢出。 + +## 3. 记忆化搜索解题步骤 + +我们在使用记忆化搜索解决问题的时候,其基本步骤如下: + +1. 写出问题的动态规划「状态」和「状态转移方程」。 +2. 定义一个缓存(数组或哈希表),用于保存子问题的解。 +3. 定义一个递归函数,用于解决问题。在递归函数中,首先检查缓存中是否已经存在需要计算的结果,如果存在则直接返回结果,否则进行计算,并将结果存储到缓存中,再返回结果。 +4. 在主函数中,调用递归函数并返回结果。 + +## 4. 记忆化搜索的应用 + +### 4.1 目标和 + +#### 4.1.1 题目链接 + +- [494. 目标和 - 力扣](https://leetcode.cn/problems/target-sum/) + +#### 4.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $target$。数组长度不超过 $20$。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 + +**要求**:返回通过上述方法构造的、运算结果等于 $target$ 的不同表达式数目。 + +**说明**: + +- $1 \le nums.length \le 20$。 +- $0 \le nums[i] \le 1000$。 +- $0 \le sum(nums[i]) \le 1000$。 +- $-1000 \le target \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1,1,1], target = 3 +输出:5 +解释:一共有 5 种方法让最终目标和为 3。 +-1 + 1 + 1 + 1 + 1 = 3 ++1 - 1 + 1 + 1 + 1 = 3 ++1 + 1 - 1 + 1 + 1 = 3 ++1 + 1 + 1 - 1 + 1 = 3 ++1 + 1 + 1 + 1 - 1 = 3 +``` + +- 示例 2: + +```python +输入:nums = [1], target = 1 +输出:1 +``` + +#### 4.1.3 解题思路 + +##### 思路 1:深度优先搜索(超时) + +使用深度优先搜索对每位数字进行 `+` 或者 `-`,具体步骤如下: + +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 到达最后一个位置 $size$: + 1. 如果和 $cur\underline{\hspace{0.5em}}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{\hspace{0.5em}}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum - nums[i]$ 的方案数。 +5. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum + nums[i]$ 的方案数。 +6. 将 4 ~ 5 两个方案数加起来就是当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 的方案数,返回该方案数。 +7. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 + +##### 思路 1:代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + size = len(nums) + + def dfs(i, cur_sum): + if i == size: + if cur_sum == target: + return 1 + else: + return 0 + ans = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) + return ans + + return dfs(0, 0) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 + +##### 思路 2:记忆化搜索 + +在思路 1 中我们单独使用深度优先搜索对每位数字进行 `+` 或者 `-` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。 + +这里我们使用哈希表 $table$ 记录遍历过的位置 $i$ 及所得到的的当前和$cur\underline{\hspace{0.5em}}sum$ 下的方案数,来避免重复搜索。具体步骤如下: + +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 遍历完所有位置: + 1. 如果和 $cur\underline{\hspace{0.5em}}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{\hspace{0.5em}}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 如果当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 之前记录过(即使用 $table$ 记录过对应方案数),则返回该方案数。 +5. 如果当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 之前没有记录过,则: + 1. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum - nums[i]$ 的方案数。 + 2. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum + nums[i]$ 的方案数。 + 3. 将上述两个方案数加起来就是当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 的方案数,将其记录到哈希表 $table$ 中,并返回该方案数。 +6. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 + +##### 思路 2:代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + size = len(nums) + table = dict() + + def dfs(i, cur_sum): + if i == size: + if cur_sum == target: + return 1 + else: + return 0 + + if (i, cur_sum) in table: + return table[(i, cur_sum)] + + cnt = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) + table[(i, cur_sum)] = cnt + return cnt + + return dfs(0, 0) +``` + +##### 思路 2:复杂度分析 + +- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 + +### 4.2 第 N 个泰波那契数 + +#### 4.2.1 题目链接 + +- [1137. 第 N 个泰波那契数 - 力扣](https://leetcode.cn/problems/n-th-tribonacci-number/) + +#### 4.2.2 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回第 $n$ 个泰波那契数。 + +**说明**: + +- **泰波那契数**:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 +- $0 \le n \le 37$。 +- 答案保证是一个 32 位整数,即 $answer \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:4 +解释: +T_3 = 0 + 1 + 1 = 2 +T_4 = 1 + 1 + 2 = 4 +``` + +- 示例 2: + +```python +输入:n = 25 +输出:1389537 +``` + +#### 4.2.3 解题思路 + +##### 思路 1:记忆化搜索 + +1. 问题的状态定义为:第 $n$ 个泰波那契数。其状态转移方程为:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 +2. 定义一个长度为 $n + 1$ 数组 $memo$ 用于保存一斤个计算过的泰波那契数。 +3. 定义递归函数 `my_tribonacci(n, memo)`。 + 1. 当 $n = 0$ 或者 $n = 1$,或者 $n = 2$ 时直接返回结果。 + 2. 当 $n > 2$ 时,首先检查是否计算过 $T(n)$,即判断 $memo[n]$ 是否等于 $0$。 + 1. 如果 $memo[n] \ne 0$,说明已经计算过 $T(n)$,直接返回 $memo[n]$。 + 2. 如果 $memo[n] = 0$,说明没有计算过 $T(n)$,则递归调用 `my_tribonacci(n - 3, memo)`、`my_tribonacci(n - 2, memo)`、`my_tribonacci(n - 1, memo)`,并将计算结果存入 $memo[n]$ 中,并返回 $memo[n]$。 + +##### 思路 1:代码 + +```python +class Solution: + def tribonacci(self, n: int) -> int: + # 使用数组保存已经求解过的 T(k) 的结果 + memo = [0 for _ in range(n + 1)] + return self.my_tribonacci(n, memo) + + def my_tribonacci(self, n: int, memo: List[int]) -> int: + if n == 0: + return 0 + if n == 1 or n == 2: + return 1 + + if memo[n] != 0: + return memo[n] + memo[n] = self.my_tribonacci(n - 3, memo) + self.my_tribonacci(n - 2, memo) + self.my_tribonacci(n - 1, memo) + return memo[n] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 题目练习 + +- [0494. 目标和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) +- [1137. 第 N 个泰波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) +- [0576. 出界的路径数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/out-of-boundary-paths.md) + + +- [记忆化搜索题目](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%E9%A2%98%E7%9B%AE) + +## 参考资料 + +1. 【文章】[记忆化搜索 - OI Wiki](https://oi-wiki.org/dp/memo/) diff --git a/docs/08_dynamic_programming/08_03_linear_dp_01.md b/docs/08_dynamic_programming/08_03_linear_dp_01.md new file mode 100644 index 00000000..3a2e1705 --- /dev/null +++ b/docs/08_dynamic_programming/08_03_linear_dp_01.md @@ -0,0 +1,757 @@ +## 1. 线性动态规划简介 + +> **线性动态规划**:具有「线性」阶段划分的动态规划方法统称为线性动态规划(简称为「线性 DP」),如下图所示。 + +![线性 DP](https://qcdn.itcharge.cn/images/20240514110630.png) + +如果状态包含多个维度,但是每个维度上都是线性划分的阶段,也属于线性 DP。比如背包问题、区间 DP、数位 DP 等都属于线性 DP。 + +线性 DP 问题的划分方法有多种方式。 + +- 如果按照「状态的维度数」进行分类,我们可以将线性 DP 问题分为:一维线性 DP 问题、二维线性 DP 问题,以及多维线性 DP 问题。 +- 如果按照「问题的输入格式」进行分类,我们可以将线性 DP 问题分为:单串线性 DP 问题、双串线性 DP 问题、矩阵线性 DP 问题,以及无串线性 DP 问题。 + +本文中,我们将按照问题的输入格式进行分类,对线性 DP 问题中各种类型问题进行一一讲解。 + +## 2. 单串线性 DP 问题 + +> **单串线性 DP** 问题:问题的输入为单个数组或单个字符串的线性 DP 问题。状态一般可定义为 $dp[i]$,表示为: +> +> 1. 「以数组中第 $i$ 个位置元素 $nums[i]$ 为结尾的子数组($nums[0]...nums[i]$)」的相关解。 +> 2. 「以数组中第 $i - 1$ 个位置元素 $nums[i - 1]$ 为结尾的子数组($nums[0]...nums[i - 1]$)」的相关解。 +> 3. 「以数组中前 $i$ 个元素为子数组($nums[0]...nums[i - 1]$)」的相关解。 + +这 $3$ 种状态的定义区别在于相差一个元素 $nums[i]$。 + +1. 第 $1$ 种状态:子数组的长度为 $i + 1$,子数组长度不可为空; +2. 第 $2$ 种状态、第 $3$ 种状态:这两种状态描述是相同的。子数组的长度为 $i$,子数组长度可为空。在 $i = 0$ 时,方便用于表示空数组(以数组中前 $0$ 个元素为子数组)。 + +### 2.1 最长递增子序列 + +单串线性 DP 问题中最经典的问题就是「最长递增子序列(Longest Increasing Subsequence,简称 LIS)」。 + +#### 2.1.1 题目链接 + +- [300. 最长递增子序列 - 力扣](https://leetcode.cn/problems/longest-increasing-subsequence/) + +#### 2.1.2 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:找到其中最长严格递增子序列的长度。 + +**说明**: + +- **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,$[3,6,2,7]$ 是数组 $[0,3,1,6,2,2,7]$ 的子序列。 +- $1 \le nums.length \le 2500$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [10,9,2,5,3,7,101,18] +输出:4 +解释:最长递增子序列是 [2,3,7,101],因此长度为 4。 +``` + +- 示例 2: + +```python +输入:nums = [0,1,0,3,2,3] +输出:4 +``` + +#### 2.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照子序列的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。 + +###### 3. 状态转移方程 + +一个较小的数后边如果出现一个较大的数,则会形成一个更长的递增子序列。 + +对于满足 $0 \le j < i$ 的数组元素 $nums[j]$ 和 $nums[i]$ 来说: + +- 如果 $nums[j] < nums[i]$,则 $nums[i]$ 可以接在 $nums[j]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[j]$ 结尾的最长递增子序列长度」的基础上加 $1$,即:$dp[i] = dp[j] + 1$。 + +- 如果 $nums[j] \ge nums[i]$,则 $nums[i]$ 不可以接在 $nums[j]$ 后面,可以直接跳过。 + +综上,我们的状态转移方程为:$dp[i] = max(dp[i], dp[j] + 1), 0 \le j < i, nums[j] < nums[i]$。 + +###### 4. 初始条件 + +默认状态下,把数组中的每个元素都作为长度为 $1$ 的递增子序列。即 $dp[i] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。那为了计算出最大的最长递增子序列长度,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 + +##### 思路 1:动态规划代码 + +```python +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + size = len(nums) + dp = [1 for _ in range(size)] + + for i in range(size): + for j in range(i): + if nums[i] > nums[j]: + dp[i] = max(dp[i], dp[j] + 1) + + return max(dp) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +### 2.2 最大子数组和 + +单串线性 DP 问题中除了子序列相关的线性 DP 问题,还有子数组相关的线性 DP 问题。 + +> **注意**: +> +> - **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。 +> - **子数组**:指的是数组中的一个连续子序列。 +> +> 「子序列」与「子数组」都可以看做是原数组的一部分,而且都不会改变原来数组中元素的相对顺序。其区别在于数组元素是否要求连续。 + +#### 2.2.1 题目链接 + +- [53. 最大子数组和 - 力扣](https://leetcode.cn/problems/maximum-subarray/) + +#### 2.2.2 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + +**说明**: + +- **子数组**:指的是数组中的一个连续部分。 +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +输出:6 +解释:连续子数组 [4,-1,2,1] 的和最大,为 6。 +``` + +- 示例 2: + +```python +输入:nums = [1] +输出:1 +``` + +#### 2.2.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照连续子数组的结束位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。 + +###### 3. 状态转移方程 + +状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则我们可以从「第 $i - 1$ 个数结尾的连续子数组的最大和」,以及「第 $i$ 个数的值」来讨论 $dp[i]$。 + +- 如果 $dp[i - 1] < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,即:$dp[i - 1] + nums[i] < nums[i]$。所以,此时 $dp[i]$ 应取「第 $i$ 个数的值」,即 $dp[i] = nums[i]$。 +- 如果 $dp[i - 1] \ge 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,即:$dp[i - 1] + nums[i] \ge nums[i]$。所以,此时 $dp[i]$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」+「 第 $i$ 个数的值」,即 $dp[i] = dp[i - 1] + nums[i]$。 + +归纳一下,状态转移方程为: + +$dp[i] = \begin{cases} nums[i], & dp[i - 1] < 0 \cr dp[i - 1] + nums[i] & dp[i - 1] \ge 0 \end{cases}$ + +###### 4. 初始条件 + +- 第 $0$ 个数结尾的连续子数组的最大和为 $nums[0]$,即 $dp[0] = nums[0]$。 + +###### 5. 最终结果 + +根据状态定义,$dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则最终结果应为所有 $dp[i]$ 的最大值,即 $max(dp)$。 + +##### 思路 1:代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + size = len(nums) + dp = [0 for _ in range(size)] + + dp[0] = nums[0] + for i in range(1, size): + if dp[i - 1] < 0: + dp[i] = nums[i] + else: + dp[i] = dp[i - 1] + nums[i] + return max(dp) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +##### 思路 2:动态规划 + 滚动优化 + +因为 $dp[i]$ 只和 $dp[i - 1]$ 和当前元素 $nums[i]$ 相关,我们也可以使用一个变量 $subMax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。然后使用 $ansMax$ 来保存全局中最大值。 + +##### 思路 2:代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + size = len(nums) + subMax = nums[0] + ansMax = nums[0] + + for i in range(1, size): + if subMax < 0: + subMax = nums[i] + else: + subMax += nums[i] + ansMax = max(ansMax, subMax) + return ansMax +``` + +##### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(1)$。 + +### 2.3 最长的斐波那契子序列的长度 + +有一些单串线性 DP 问题在定义状态时需要考虑两个结束位置,只考虑一个结束位置的无法清楚描述问题。这时候我们就需要需要增加一个结束位置维度来定义状态。 + +#### 2.3.1 题目链接 + +- [873. 最长的斐波那契子序列的长度 - 力扣](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) + +#### 2.3.2 题目大意 + +**描述**:给定一个严格递增的正整数数组 $arr$。 + +**要求**:从数组 $arr$ 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 0。 + +**说明**: + +- **斐波那契式序列**:如果序列 $X_1, X_2, ..., X_n$ 满足: + + - $n \ge 3$; + - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 + + 则称该序列为斐波那契式序列。 + +- **斐波那契式子序列**:从序列 $A$ 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:$A = [3, 4, 5, 6, 7, 8]$。则 $[3, 5, 8]$ 是 $A$ 的一个斐波那契式子序列。 + +- $3 \le arr.length \le 1000$。 + +- $1 \le arr[i] < arr[i + 1] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入: arr = [1,2,3,4,5,6,7,8] +输出: 5 +解释: 最长的斐波那契式子序列为 [1,2,3,5,8]。 +``` + +- 示例 2: + +```python +输入: arr = [1,3,7,11,12,14,18] +输出: 3 +解释: 最长的斐波那契式子序列有 [1,11,12]、[3,11,14] 以及 [7,11,18]。 +``` + +#### 2.3.3 解题思路 + +##### 思路 1: 暴力枚举(超时) + +假设 $arr[i]$、$arr[j]$、$arr[k]$ 是序列 $arr$ 中的 $3$ 个元素,且满足关系:$arr[i] + arr[j] == arr[k]$,则 $arr[i]$、$arr[j]$、$arr[k]$ 就构成了 $arr$ 的一个斐波那契式子序列。 + +通过 $arr[i]$、$arr[j]$,我们可以确定下一个斐波那契式子序列元素的值为 $arr[i] + arr[j]$。 + +因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 $arr[i]$、$arr[j]$,则可以顺着 $arr$ 序列,从第 $j + 1$ 的元素开始,查找值为 $arr[i] + arr[j]$ 的元素 。找到 $arr[i] + arr[j]$ 之后,然后再顺着查找子序列的下一个元素。 + +简单来说,就是确定了 $arr[i]$、$arr[j]$,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 $arr[i]$、$arr[j]$,统计不同的斐波那契式子序列的长度。 + +最后将这些长度进行比较,其中最长的长度就是答案。 + +##### 思路 1:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + k = j + 1 + while k < size: + if arr[temp_i] + arr[temp_j] == arr[k]: + temp_ans += 1 + temp_i = temp_j + temp_j = k + k += 1 + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(1)$。 + +##### 思路 2:哈希表 + +对于 $arr[i]$、$arr[j]$,要查找的元素 $arr[i] + arr[j]$ 是否在 $arr$ 中,我们可以预先建立一个反向的哈希表。键值对关系为 $value : idx$,这样就能在 $O(1)$ 的时间复杂度通过 $arr[i] + arr[j]$ 的值查找到对应的 $arr[k]$,而不用像原先一样线性查找 $arr[k]$ 了。 + +##### 思路 2:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + idx_map = dict() + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + while arr[temp_i] + arr[temp_j] in idx_map: + temp_ans += 1 + k = idx_map[arr[temp_i] + arr[temp_j]] + temp_i = temp_j + temp_j = k + + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +##### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +##### 思路 3:动态规划 + 哈希表 + +###### 1. 划分阶段 + +按照斐波那契式子序列相邻两项的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。 + +###### 3. 状态转移方程 + +以 $arr[j]$、$arr[k]$ 结尾的斐波那契式子序列的最大长度 = 满足 $arr[i] + arr[j] = arr[k]$ 条件下,以 $arr[i]$、$arr[j]$ 结尾的斐波那契式子序列的最大长度加 $1$。即状态转移方程为:$dp[j][k] = max_{(A[i] + A[j] = A[k], \quad i < j < k)}(dp[i][j] + 1)$。 + +###### 4. 初始条件 + +默认状态下,数组中任意相邻两项元素都可以作为长度为 $2$ 的斐波那契式子序列,即 $dp[i][j] = 2$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。那为了计算出最大的最长递增子序列长度,则需要在进行状态转移时,求出最大值 $ans$ 即为最终结果。 + +因为题目定义中,斐波那契式中 $n \ge 3$,所以只有当 $ans \ge 3$ 时,返回 $ans$。如果 $ans < 3$,则返回 $0$。 + +> **注意**:在进行状态转移的同时,我们应和「思路 2:哈希表」一样采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 + +##### 思路 3:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + + dp = [[0 for _ in range(size)] for _ in range(size)] + ans = 0 + + # 初始化 dp + for i in range(size): + for j in range(i + 1, size): + dp[i][j] = 2 + + idx_map = {} + # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + if arr[i] + arr[j] in idx_map: + # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 + k = idx_map[arr[i] + arr[j]] + + dp[j][k] = max(dp[j][k], dp[i][j] + 1) + ans = max(ans, dp[j][k]) + + if ans >= 3: + return ans + return 0 +``` + +##### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +## 3. 双串线性 DP 问题 + +> **双串线性 DP 问题**:问题的输入为两个数组或两个字符串的线性 DP 问题。状态一般可定义为 $dp[i][j]$,表示为: +> +> 1. 「以第一个数组中第 $i$ 个位置元素 $nums1[i]$ 为结尾的子数组($nums1[0]...nums1[i]$)」与「以第二个数组中第 $j$ 个位置元素 $nums2[j]$ 为结尾的子数组($nums2[0]...nums2[j]$)」的相关解。 +> 2. 「以第一个数组中第 $i - 1$ 个位置元素 $nums1[i - 1]$ 为结尾的子数组($nums1[0]...nums1[i - 1]$)」与「以第二个数组中第 $j - 1$ 个位置元素 $nums2[j - 1]$ 为结尾的子数组($nums2[0]...nums2[j - 1]$)」的相关解。 +> 3. 「以第一个数组中前 $i$ 个元素为子数组($nums1[0]...nums1[i - 1]$)」与「以第二个数组中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的相关解。 + +这 $3$ 种状态的定义区别在于相差一个元素 $nums1[i]$ 或 $nums2[j]$。 + +1. 第 $1$ 种状态:子数组的长度为 $i + 1$ 或 $j + 1$,子数组长度不可为空 +2. 第 $2$ 种状态、第 $3$ 种状态:子数组的长度为 $i$ 或 $j$,子数组长度可为空。$i = 0$ 或 $j = 0$ 时,方便用于表示空数组(以数组中前 $0$ 个元素为子数组)。 + +### 3.1 最长公共子序列 + +双串线性 DP 问题中最经典的问题就是「最长公共子序列(Longest Common Subsequence,简称 LCS)」。 + +#### 3.1.1 题目链接 + +- [1143. 最长公共子序列 - 力扣](https://leetcode.cn/problems/longest-common-subsequence/) + +#### 3.1.2 题目大意 + +**描述**:给定两个字符串 $text1$ 和 $text2$。 + +**要求**:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 $0$。 + +**说明**: + +- **子序列**:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 +- **公共子序列**:两个字符串所共同拥有的子序列。 +- $1 \le text1.length, text2.length \le 1000$。 +- $text1$ 和 $text2$ 仅由小写英文字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:text1 = "abcde", text2 = "ace" +输出:3 +解释:最长公共子序列是 "ace",它的长度为 3。 +``` + +- 示例 2: + +```python +输入:text1 = "abc", text2 = "abc" +输出:3 +解释:最长公共子序列是 "abc",它的长度为 3。 +``` + +#### 3.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度为 $dp[i][j]$。 + +###### 3. 状态转移方程 + +双重循环遍历字符串 $text1$ 和 $text2$,则状态转移方程为: + +1. 如果 $text1[i - 1] = text2[j - 1]$,说明两个子字符串的最后一位是相同的,所以最长公共子序列长度加 $1$。即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 +2. 如果 $text1[i - 1] \ne text2[j - 1]$,说明两个子字符串的最后一位是不同的,则 $dp[i][j]$ 需要考虑以下两种情况,取两种情况中最大的那种:$dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$。 + 1. 「以 $text1$ 中前 $i - 1$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i - 1][j]$。 + 2. 「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j - 1$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i][j - 1]$。 + +###### 4. 初始条件 + +1. 当 $i = 0$ 时,$str1$ 表示的是空串,空串与 $str2$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 +2. 当 $j = 0$ 时,$str2$ 表示的是空串,$str1$ 与 空串的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[sise1][size2]$(即 $text1$ 与 $text2$ 的最长公共子序列长度)即可,其中 $size1$、$size2$ 分别为 $text1$、$text2$ 的字符串长度。 + +##### 思路 1:代码 + +```python +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + size1 = len(text1) + size2 = len(text2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[size1][size2] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $text1$、$text2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 +- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 + +### 3.2 最长重复子数组 + +#### 3.2.1 题目链接 + +- [718. 最长重复子数组 - 力扣](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) + +#### 3.2.2 题目大意 + +**描述**:给定两个整数数组 $nums1$、$nums2$。 + +**要求**:计算两个数组中公共的、长度最长的子数组长度。 + +**说明**: + +- $1 \le nums1.length, nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] +输出:3 +解释:长度最长的公共子数组是 [3,2,1] 。 +``` + +- 示例 2: + +```python +输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] +输出:5 +``` + +#### 3.2.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照子数组结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:「在 $nums1$ 中以第 $i - 1$ 个元素结尾的子数组 」和「在 $nums2$ 中以第 $j - 1$ 个元素结尾的子数组 」的最长公共子数组长度。 + +###### 3. 状态转移方程 + +1. 如果 $nums1[i - 1] = nums2[j - 1]$,则当前元素可以构成公共子数组,此时 $dp[i][j] = dp[i - 1][j - 1] + 1$。 +2. 如果 $nums1[i - 1] \ne nums2[j - 1]$,则当前元素不能构成公共子数组,此时 $dp[i][j] = 0$。 + +###### 4. 初始条件 + +- 当 $i = 0$ 时,$nums1[0]...nums1[i - 1]$ 表示的是空数组,空数组与 $nums2$ 中任意子数组的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 +- 当 $j = 0$ 时,$nums2[0]...nums2[j - 1]$ 表示的是空数组,空数组与 $nums1$ 中任意子数组的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 + +###### 5. 最终结果 + +- 根据状态定义, $dp[i][j]$ 为:「在 $nums1$ 中以第 $i - 1$ 个元素结尾的子数组 」和「在 $nums2$ 中以第 $j - 1$ 个元素结尾的子数组 」的最长公共子数组长度。在遍历过程中,我们可以使用 $res$ 记录下所有 $dp[i][j]$ 中最大值即为答案。 + +##### 思路 1:代码 + +```python +class Solution: + def findLength(self, nums1: List[int], nums2: List[int]) -> int: + size1 = len(nums1) + size2 = len(nums2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + res = 0 + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if nums1[i - 1] == nums2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + if dp[i][j] > res: + res = dp[i][j] + + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 +- **空间复杂度**:$O(n \times m)$。 + +### 3.3 编辑距离 + +双串线性 DP 问题中除了经典的最长公共子序列问题之外,还包括字符串的模糊匹配问题。 + +#### 3.3.1 题目链接 + +- [72. 编辑距离 - 力扣](https://leetcode.cn/problems/edit-distance/) + +#### 3.3.2 题目大意 + +**描述**:给定两个单词 $word1$、$word2$。 + +对一个单词可以进行以下三种操作: + +- 插入一个字符 +- 删除一个字符 +- 替换一个字符 + +**要求**:计算出将 $word1$ 转换为 $word2$ 所使用的最少操作数。 + +**说明**: + +- $0 \le word1.length, word2.length \le 500$。 +- $word1$ 和 $word2$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:word1 = "horse", word2 = "ros" +输出:3 +解释: +horse -> rorse (将 'h' 替换为 'r') +rorse -> rose (删除 'r') +rose -> ros (删除 'e') +``` + +- 示例 2: + +```python +输入:word1 = "intention", word2 = "execution" +输出:5 +解释: +intention -> inention (删除 't') +inention -> enention (将 'i' 替换为 'e') +enention -> exention (将 'n' 替换为 'x') +exention -> exection (将 'n' 替换为 'c') +exection -> execution (插入 'u') +``` + +#### 3.3.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,所需要的最少操作次数。 + +###### 3. 状态转移方程 + +1. 如果当前字符相同($word1[i - 1] = word2[j - 1]$),无需插入、删除、替换。$dp[i][j] = dp[i - 1][j - 1]$。 +2. 如果当前字符不同($word1[i - 1] \ne word2[j - 1]$),$dp[i][j]$ 取源于以下三种情况中的最小情况: + 1. 替换($word1[i - 1]$ 替换为 $word2[j - 1]$):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上替换的操作数 $1$,即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 + 2. 插入($word1$ 在第 $i - 1$ 位置上插入元素):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」 变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,再加上插入需要的操作数 $1$,即:$dp[i][j] = dp[i - 1][j] + 1$。 + 3. 删除($word1$ 删除第 $i - 1$ 位置元素):最少操作次数依赖于「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上删除需要的操作数 $1$,即:$dp[i][j] = dp[i][j - 1] + 1$。 + +综合上述情况,状态转移方程为: + +$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & word1[i - 1] = word2[j - 1] \cr min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 & word1[i - 1] \ne word2[j - 1] \end{cases}$ + +###### 4. 初始条件 + +- 当 $i = 0$,「以 $word1$ 中前 $0$ 个字符组成的子字符串 $str1$」为空字符串,「$str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」时,至少需要插入 $j$ 次,即:$dp[0][j] = j$。 +- 当 $j = 0$,「以 $word2$ 中前 $0$ 个字符组成的子字符串 $str2$」为空字符串,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「$str2$」时,至少需要删除 $i$ 次,即:$dp[i][0] = i$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[sise1][size2]$(即 $word1$ 变为 $word2$ 所使用的最少操作数)即可。其中 $size1$、$size2$ 分别为 $word1$、$word2$ 的字符串长度。 + +##### 思路 1:代码 + +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + size1 = len(word1) + size2 = len(word2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + + for i in range(size1 + 1): + dp[i][0] = i + for j in range(size2 + 1): + dp[0][j] = j + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if word1[i - 1] == word2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + return dp[size1][size2] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $word1$、$word2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 +- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 + +## 题目练习 + +- [0300. 最长递增子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) +- [0053. 最大子数组和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) +- [0198. 打家劫舍](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) +- [0213. 打家劫舍 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/house-robber-ii.md) +- [0873. 最长的斐波那契子序列的长度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md) +- [1143. 最长公共子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) + +- [单串线性 DP 问题题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8D%95%E4%B8%B2%E7%BA%BF%E6%80%A7-dp-%E9%97%AE%E9%A2%98) +- [双串线性 DP 问题题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8F%8C%E4%B8%B2%E7%BA%BF%E6%80%A7-dp-%E9%97%AE%E9%A2%98) + +## 参考资料 + +- 【书籍】算法竞赛进阶指南 +- 【文章】[动态规划概念和基础线性DP | 潮汐朝夕](https://chengzhaoxi.xyz/1a4a2483.html) diff --git a/docs/08_dynamic_programming/08_04_linear_dp_02.md b/docs/08_dynamic_programming/08_04_linear_dp_02.md new file mode 100644 index 00000000..ee77a993 --- /dev/null +++ b/docs/08_dynamic_programming/08_04_linear_dp_02.md @@ -0,0 +1,388 @@ +## 4. 矩阵线性 DP问题 + +> **矩阵线性 DP 问题**:问题的输入为二维矩阵的线性 DP 问题。状态一般可定义为 $dp[i][j]$,表示为:从「位置 $(0, 0)$」到达「位置 $(i, j)$」的相关解。 + +### 4.1 最小路径和 + +#### 4.1.1 题目链接 + +- [64. 最小路径和 - 力扣](https://leetcode.cn/problems/minimum-path-sum/) + +#### 4.1.2 题目大意 + +**描述**:给定一个包含非负整数的 $m \times n$ 大小的网格 $grid$。 + +**要求**:找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + +**说明**: + +- 每次只能向下或者向右移动一步。 +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 200$。 +- $0 \le grid[i][j] \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/05/minpath.jpg) + +```python +输入:grid = [[1,3,1],[1,5,1],[4,2,1]] +输出:7 +解释:因为路径 1→3→1→1→1 的总和最小。 +``` + +- 示例 2: + +```python +输入:grid = [[1,2,3],[4,5,6]] +输出:12 +``` + +#### 4.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:从位置 $(0, 0)$ 到达位置 $(i, j)$ 的最小路径和。 + +###### 3. 状态转移方程 + +当前位置 $(i, j)$ 只能从左侧位置 $(i, j - 1)$ 或者上方位置 $(i - 1, j)$ 到达。为了使得从左上角到达 $(i, j)$ 位置的最小路径和最小,应从 $(i, j - 1)$ 位置和 $(i - 1, j)$ 位置选择路径和最小的位置达到 $(i, j)$。 + +即状态转移方程为:$dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]$。 + +###### 4. 初始条件 + +- 当左侧和上方是矩阵边界时(即 $i = 0, j = 0$),$dp[i][j] = grid[i][j]$。 +- 当只有左侧是矩阵边界时(即 $i \ne 0, j = 0$),只能从上方到达,$dp[i][j] = dp[i - 1][j] + grid[i][j]$。 +- 当只有上方是矩阵边界时(即 $i = 0, j \ne 0$),只能从左侧到达,$dp[i][j] = dp[i][j - 1] + grid[i][j]$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[rows - 1][cols - 1]$(即从左上角到达 $(rows - 1, cols - 1)$ 位置的最小路径和)即可。其中 $rows$、$cols$ 分别为 $grid$ 的行数、列数。 + +##### 思路 1:代码 + +```python +class Solution: + def minPathSum(self, grid: List[List[int]]) -> int: + rows, cols = len(grid), len(grid[0]) + dp = [[0 for _ in range(cols)] for _ in range(rows)] + + dp[0][0] = grid[0][0] + + for i in range(1, rows): + dp[i][0] = dp[i - 1][0] + grid[i][0] + + for j in range(1, cols): + dp[0][j] = dp[0][j - 1] + grid[0][j] + + for i in range(1, rows): + for j in range(1, cols): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + + return dp[rows - 1][cols - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m * n)$,其中 $m$、$n$ 分别为 $grid$ 的行数和列数。 +- **空间复杂度**:$O(m * n)$。 + +### 4.2 最大正方形 + +#### 4.2.1 题目链接 + +- [221. 最大正方形 - 力扣](https://leetcode.cn/problems/maximal-square/) + +#### 4.2.2 题目大意 + +**描述**:给定一个由 `'0'` 和 `'1'` 组成的二维矩阵 $matrix$。 + +**要求**:找到只包含 `'1'` 的最大正方形,并返回其面积。 + +**说明**: + +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 300$。 +- $matrix[i][j]$ 为 `'0'` 或 `'1'`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/26/max1grid.jpg) + +```python +输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] +输出:4 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/26/max2grid.jpg) + +```python +输入:matrix = [["0","1"],["1","0"]] +输出:1 +``` + +#### 4.2.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照正方形的右下角坐标进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。 + +###### 3. 状态转移方程 + +只有当矩阵位置 $(i, j)$ 值为 $1$ 时,才有可能存在正方形。 + +- 如果矩阵位置 $(i, j)$ 上值为 $0$,则 $dp[i][j] = 0$。 +- 如果矩阵位置 $(i, j)$ 上值为 $1$,则 $dp[i][j]$ 的值由该位置上方、左侧、左上方三者共同约束的,为三者中最小值加 $1$。即:$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1$。 + +###### 4. 初始条件 + +- 默认所有以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长都为 $0$,即 $dp[i][j] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。则最终结果为所有 $dp[i][j]$ 中的最大值。 + +##### 思路 1:代码 + +```python +class Solution: + def maximalSquare(self, matrix: List[List[str]]) -> int: + rows, cols = len(matrix), len(matrix[0]) + max_size = 0 + dp = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] + for i in range(rows): + for j in range(cols): + if matrix[i][j] == '1': + if i == 0 or j == 0: + dp[i][j] = 1 + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + max_size = max(max_size, dp[i][j]) + return max_size * max_size +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为二维矩阵 $matrix$ 的行数和列数。 +- **空间复杂度**:$O(m \times n)$。 + +## 5. 无串线性 DP 问题 + +> **无串线性 DP 问题**:问题的输入不是显式的数组或字符串,但依然可分解为若干子问题的线性 DP 问题。 + +### 5.1 整数拆分 + +#### 5.1.1 题目链接 + +- [343. 整数拆分 - 力扣](https://leetcode.cn/problems/integer-break/) + +#### 5.1.2 题目大意 + +**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 + +**要求**:返回可以获得的最大乘积。 + +**说明**: + +- $2 \le n \le 58$。 + +**示例**: + +- 示例 1: + +```python +输入: n = 2 +输出: 1 +解释: 2 = 1 + 1, 1 × 1 = 1。 +``` + +- 示例 2: + +```python +输入: n = 10 +输出: 36 +解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 +``` + +#### 5.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照正整数进行划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 + +###### 3. 状态转移方程 + +当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: + +1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 +2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 + +则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 + +由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: + +$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 + +###### 4. 初始条件 + +- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 + +##### 思路 1:代码 + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + for i in range(2, n + 1): + for j in range(i): + dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) + return dp[n] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + +### 5.2 只有两个键的键盘 + +#### 5.2.1 题目链接 + +- [650. 只有两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) + +#### 5.2.2 题目大意 + +**描述**:最初记事本上只有一个字符 `'A'`。你每次可以对这个记事本进行两种操作: + +- **Copy All(复制全部)**:复制这个记事本中的所有字符(不允许仅复制部分字符)。 +- **Paste(粘贴)**:粘贴上一次复制的字符。 + +现在,给定一个数字 $n$,需要使用最少的操作次数,在记事本上输出恰好 $n$ 个 `'A'` 。 + +**要求**:返回能够打印出 $n$ 个 `'A'` 的最少操作次数。 + +**说明**: + +- $1 \le n \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:3 +输出:3 +解释 +最初, 只有一个字符 'A'。 +第 1 步, 使用 Copy All 操作。 +第 2 步, 使用 Paste 操作来获得 'AA'。 +第 3 步, 使用 Paste 操作来获得 'AAA'。 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:0 +``` + +#### 5.2.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照字符 `'A'` 的个数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 + +###### 3. 状态转移方程 + +1. 对于 $i$ 个字符 `'A'`,如果 $i$ 可以被一个小于 $i$ 的整数 $j$ 除尽($j$ 是 $i$ 的因子),则说明 $j$ 个字符 `'A'` 可以通过「复制」+「粘贴」总共 $\frac{i}{j}$ 次得到 $i$ 个字符 `'A'`。 +2. 而得到 $j$ 个字符 `'A'`,最少需要的操作数可以通过 $dp[j]$ 获取。 + +则我们可以枚举 $i$ 的因子,从中找到在满足 $j$ 能够整除 $i$ 的条件下,最小的 $dp[j] + \frac{i}{j}$,即为 $dp[i]$,即 $dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j})$。 + +由于 $j$ 能够整除 $i$,则 $j$ 与 $\frac{i}{j}$ 都是 $i$ 的因子,两者中必有一个因子是小于等于 $\sqrt{i}$ 的,所以在枚举 $i$ 的因子时,我们只需要枚举区间 $[1, \sqrt{i}]$ 即可。 + +综上所述,状态转移方程为:$dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j}, dp[\frac{i}{j}] + j)$。 + +###### 4. 初始条件 + +- 当 $i = 1$ 时,最少需要的操作数为 $0$。所以 $dp[1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 所以最终结果为 $dp[n]$。 + +##### 思路 1:动态规划代码 + +```python +import math + +class Solution: + def minSteps(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + for i in range(2, n + 1): + dp[i] = float('inf') + for j in range(1, int(math.sqrt(n)) + 1): + if i % j == 0: + dp[i] = min(dp[i], dp[j] + i // j, dp[i // j] + j) + + return dp[n] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \sqrt{n})$。外层循环遍历的时间复杂度是 $O(n)$,内层循环遍历的时间复杂度是 $O(\sqrt{n})$,所以总体时间复杂度为 $O(n \sqrt{n})$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +## 题目练习 + +- [0718. 最长重复子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) +- [0072. 编辑距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) +- [0064. 最小路径和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) +- [0221. 最大正方形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) +- [0343. 整数拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) +- [0650. 两个键的键盘](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/2-keys-keyboard.md) + +- [矩阵线性 DP 问题题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E7%9F%A9%E9%98%B5%E7%BA%BF%E6%80%A7-dp-%E9%97%AE%E9%A2%98) +- [矩阵线性 DP 问题题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%97%A0%E4%B8%B2%E7%BA%BF%E6%80%A7-dp-%E9%97%AE%E9%A2%98) + +## 参考资料 + +- 【书籍】算法竞赛进阶指南 +- 【文章】[动态规划概念和基础线性DP | 潮汐朝夕](https://chengzhaoxi.xyz/1a4a2483.html) diff --git a/docs/08_dynamic_programming/08_05_knapsack_problem_01.md b/docs/08_dynamic_programming/08_05_knapsack_problem_01.md new file mode 100644 index 00000000..bc825b04 --- /dev/null +++ b/docs/08_dynamic_programming/08_05_knapsack_problem_01.md @@ -0,0 +1,279 @@ +## 1. 背包问题简介 + +### 1.1 背包问题的定义 + +> **背包问题**:背包问题是线性 DP 问题中一类经典而又特殊的模型。背包问题可以描述为:给定一组物品,每种物品都有自己的重量、价格以及数量。再给定一个最多能装重量为 $W$ 的背包。现在选择将一些物品放入背包中,请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值总和是多少? + +![背包问题](https://qcdn.itcharge.cn/images/20240514111553.png) + +根据物品限制条件的不同,背包问题可分为:0-1 背包问题、完全背包问题、多重背包问题、分组背包问题,以及混合背包问题等。 + +### 1.2 背包问题的暴力解题思路 + +背包问题的暴力解题思路比较简单。假设有 $n$ 件物品。我们先枚举出这 $n$ 件物品所有可能的组合。然后再判断这些组合中的物品是否能放入背包,以及是否能得到最大价值。这种做法的时间复杂度是 $O(2^n)$。 + +背包问题暴力解法的时间复杂度是指数级别的,我们可以利用动态规划算法减少一下时间复杂度。 + +下面我们来讲解一下如何使用动态规划方法解决各种类型的背包问题。 + +## 2. 0-1 背包问题 + +> **0-1 背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? + +![0-1 背包问题](https://qcdn.itcharge.cn/images/20240514111617.png) + +### 2.1 0-1 背包问题基本思路 + +> **0-1 背包问题的特点**:每种物品有且仅有 $1$ 件,可以选择不放入背包,也可以选择放入背包。 + +#### 思路 1:动态规划 + 二维基本思路 + +###### 1. 划分阶段 + +按照物品的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 + +###### 3. 状态转移方程 + +对于「将前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值 」这个子问题,如果我们只考虑第 $i - 1$ 件物品(前 $i$ 件物品中最后一件物品)的放入策略(放入背包和不放入背包两种策略)。则问题可以转换为一个只跟前 $i - 1$ 件物品相关的问题。 + +1. **第 $i - 1$ 件物品不放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w$ 的背包中 ,可以获得的最大价值」为 $dp[i - 1][w]$。 +2. **第 $i - 1$ 件物品放入背包**:问题转换为「前 $i - 1$ 件物品放入一个最多能装重量为 $w - weight[i - 1]$ 的背包中,可以获得的最大价值」为 $dp[i - 1][w - weight[i - 1]]$,再加上「放入的第 $i - 1$ 件物品的价值」为 $value[i - 1]$,则此时可以获得的最大价值为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$。 + +接下来我们再来考虑一下第 $i - 1$ 件物品满足什么条件时才能考虑是否放入背包,并且在什么条件下一定不能放入背包。 + +1. 如果当前背包的载重不足时(即 $w < weight[i - 1]$):第 $i - 1$ 件物品一定不能放入背包,此时背包的价值 $dp[i][w]$ 仍为 $dp[i - 1][w]$ 时的价值,即 $dp[i][w] = dp[i - 1][w]$。 +2. 如果当前背包的载重足够时(即 $w \ge weight[i - 1]$):第 $i - 1$ 件物品可以考虑放入背包,或者不放入背包,此时背包的价值取两种情况下的最大值,即 $dp[i][w] = max \lbrace dp[i - 1][w], dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace$。 + +则状态转移方程为: + +$dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 +- 无论背包载重上限是多少,前 $0$ 件物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的件数,$W$ 为背包的载重上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 思路 1:动态规划 + 二维基本思路 + def zeroOnePackMethod1(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1]: + # dp[i][w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」 + dp[i][w] = dp[i - 1][w] + else: + # dp[i][w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i - 1]] + value[i - 1]) + + return dp[size][W] +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(n \times W)$。 + +### 2.2 0-1 背包问题滚动数组优化 + +根据之前的求解过程可以看出:当我们依次处理前 $1 \sim n$ 件物品时,「前 $i$ 件物品的处理结果」只跟「前 $i - 1$ 件物品的处理结果」,而跟之前更早的处理结果没有太大关系。 + +也就是说在状态转移的过程中,我们只用到了当前行(第 $i$ 行)的 $dp[i][w]$ 以及上一行(第 $i - 1$ 行)的 $dp[i - 1][w]$、$dp[i - 1][w - weight[i - 1]]$。 + +所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。即:用 $dp[0][w]$ 保存原先 $dp[i - 1][w]$ 的状态,用 $dp[1][w]$ 保存当前 $dp[i][w]$ 的状态。 + +其实我们还可以进一步进行优化,我们只需要使用一个一维数组 $dp[w]$ 保存上一阶段的所有状态,采用使用「滚动数组」的方式对空间进行优化(去掉动态规划状态的第一维)。 + +#### 思路 2:动态规划 + 滚动数组优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = \begin{cases} dp[w] & w < weight[i - 1] \cr max \lbrace dp[w], dp[w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +在第 $i$ 轮计算之前,$dp[w]$ 中保存的是「第 $i - 1$ 阶段的所有状态值」。在第 $i$ 轮计算之后,$d[w]$ 中保存的是「第 $i$ 阶段的所有状态值」。 + +为了保证第 $i$ 轮计算过程中,$dp[w]$ 是由第 $i - 1$ 轮中 $dp[w]$ 和 $dp[w - weight[i - 1]]$ 两个状态递推而来的值,我们需要按照「从 $W \sim 0$ 逆序的方式」倒推 $dp[w]$。 + +这是因为如果我们采用「从 $0 \sim W$ 正序递推的方式」递推 $dp[w]$,如果当前状态 $dp[w - weight[i]]$ 已经更新为当前第 $i$ 阶段的状态值。那么在向右遍历到 $dp[w]$ 时,我们需要的是第 $i - 1$ 阶段的状态值(即上一阶段的 $dp[w - weight[i - 1]]$),而此时 $dp[w - weight[i - 1]]$ 已经更新了,会破坏当前阶段的状态值,从而无法推出正确结果。 + +而如果按照「从 $W \sim 0$ 逆序的方式」倒推 $dp[w]$ 则不会出现该问题。 + +因为 $w < weight[i - 1]$ 时,$dp[w]$ 只能取上一阶段的 $dp[w]$,其值相当于没有变化,这部分可以不做处理。所以我们在逆序倒推 $dp[w]$ 时,只需遍历到 $weight[i - 1]$ 时即可。 + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) + + return dp[W] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(W)$。 + +### 2.3 0-1 背包问题的应用 + +#### 2.3.1 题目链接 + +- [416. 分割等和子集 - 力扣](https://leetcode.cn/problems/partition-equal-subset-sum/) + +#### 2.3.2 题目大意 + +**描述**:给定一个只包含正整数的非空数组 $nums$。 + +**要求**:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 + +**说明**: + +- $1 \le nums.length \le 200$。 +- $1 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,5,11,5] +输出:true +解释:数组可以分割成 [1, 5, 5] 和 [11]。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3,5] +输出:false +解释:数组不能分割成两个元素和相等的子集。 +``` + +#### 2.3.3 解题思路 + +##### 思路 1:动态规划 + +这道题换一种说法就是:从数组中选择一些元素组成一个子集,使子集的元素和恰好等于整个数组元素和的一半。 + +这样的话,这道题就可以转变为「0-1 背包问题」。 + +1. 把整个数组中的元素和记为 $sum$,把元素和的一半 $target = \frac{sum}{2}$ 看做是「0-1 背包问题」中的背包容量。 +2. 把数组中的元素 $nums[i]$ 看做是「0-1 背包问题」中的物品。 +3. 第 $i$ 件物品的重量为 $nums[i]$,价值也为 $nums[i]$。 +4. 因为物品的重量和价值相等,如果能装满载重上限为 $target$ 的背包,那么得到的最大价值也应该是 $target$。 + +这样问题就转变为:给定一个数组 $nums$ 代表物品,数组元素和的一半 $target = \frac{sum}{2}$ 代表背包的载重上限。其中第 $i$ 件物品的重量为 $nums[i]$,价值为 $nums[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,能否将背包装满从而得到最大价值? + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $w$ 的背包中,得到的元素和最大为多少。 + +###### 3. 状态转移方程 + +$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], \quad dp[w - nums[i - 1]] + nums[i - 1] \rbrace & w \ge nums[i - 1] \end{cases}$ + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[target]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $target = \frac{sum}{2}$ 的背包中,得到的元素和最大值。 + +所以最后判断一下 $dp[target]$ 是否等于 $target$。如果 $dp[target] == target$,则说明集合中的子集刚好能够凑成总和 $target$,此时返回 `True`;否则返回 `False`。 + +##### 思路 1:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) + + return dp[W] + + def canPartition(self, nums: List[int]) -> bool: + sum_nums = sum(nums) + if sum_nums & 1: + return False + + target = sum_nums // 2 + return self.zeroOnePackMethod2(nums, nums, target) == target +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 是整个数组元素和的一半。 +- **空间复杂度**:$O(target)$。 + +## 练习题目 + +- [0416. 分割等和子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/partition-equal-subset-sum.md) +- [0494. 目标和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) +- [1049. 最后一块石头的重量 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/last-stone-weight-ii.md) + +- [0-1 背包问题题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#0-1-%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E9%A2%98%E7%9B%AE) + + +## 参考资料 + +- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) +- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) +- 【文章】[背包问题 第四讲 - 宫水三叶的刷题日记](https://juejin.cn/post/7003243733604892685) +- 【文章】[Massive Algorithms: 讲透完全背包算法](https://massivealgorithms.blogspot.com/2015/06/unbounded-knapsack-problem.html) diff --git a/docs/08_dynamic_programming/08_06_knapsack_problem_02.md b/docs/08_dynamic_programming/08_06_knapsack_problem_02.md new file mode 100644 index 00000000..c6e11f91 --- /dev/null +++ b/docs/08_dynamic_programming/08_06_knapsack_problem_02.md @@ -0,0 +1,254 @@ +## 3. 完全背包问题 + +> **完全背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每种物品数量没有限制。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? + +![完全背包问题](https://qcdn.itcharge.cn/images/20240514111640.png) + +### 3.1 完全背包问题基本思路 + +> **完全背包问题的特点**:每种物品有无限件。 + +我们可以参考「0-1 背包问题」的状态定义和基本思路,对于容量为 $w$ 的背包,最多可以装 $\frac{w}{weight[i - 1]}$ 件第 $i - 1$ 件物品。那么我们可以多加一层循环,枚举第 $i - 1$ 件物品可以选择的件数($0 \sim \frac{w}{weight[i - 1]}$),从而将「完全背包问题」转换为「0-1 背包问题」。 + +#### 思路 1:动态规划 + 二维基本思路 + +###### 1. 划分阶段 + +按照物品种类的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 + +###### 3. 状态转移方程 + +由于每种物品可选的数量没有限制,因此状态 $dp[i][w]$ 可能从以下方案中选择最大值: + +1. 选择 $0$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w]$ +2. 选择 $1$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$。 +3. 选择 $2$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - 2 \times weight[i - 1]] + 2 \times value[i - 1]$。 +4. …… +5. 选择 $k$ 件第 $i - 1$ 件物品:可以获得的最大价值为 $dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1]$。 + +> 注意:选择 $k$ 件第 $i - 1$ 件物品的条件是 $0 \le k \times weight[i - 1] \le w$。 + +则状态转移方程为: + +$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \times weight[i - 1] \le w$。 + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 +- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 思路 1:动态规划 + 二维基本思路 + def completePackMethod1(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 枚举第 i - 1 种物品能取个数 + for k in range(w // weight[i - 1] + 1): + # dp[i][w] 取所有 dp[i - 1][w - k * weight[i - 1] + k * value[i - 1] 中最大值 + dp[i][w] = max(dp[i][w], dp[i - 1][w - k * weight[i - 1]] + k * value[i - 1]) + + return dp[size][W] +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W \times \sum\frac{W}{weight[i]})$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$weight[i]$ 是第 $i$ 种物品的重量。 +- **空间复杂度**:$O(n \times W)$。 + +### 3.2 完全背包问题状态转移方程优化 + +上之前的思路中,对于每种物品而言,每次我们都需要枚举所有可行的物品数目 $k$,这就大大增加了时间复杂度。 + +实际上,我们可以对之前的状态转移方程进行一些优化,从而减少一下算法的时间复杂度。 + +我们将之前的状态转移方程 + +$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \times weight[i - 1] \le w$ + +进行展开: + +$(1) \quad dp[i][w] = max \begin{cases} dp[i - 1][w] \cr dp[i - 1][w - weight[i - 1]] + value[i - 1] \cr dp[i - 1][w - 2 \times weight[i - 1]] + 2 \times value[i - 1] \cr …… \cr \cr dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \end{cases}, \quad 0 \le k \times weight[i - 1] \le w$ + +而对于 $dp[i][w - weight[i - 1]]$ 我们有: + +$(2) \quad dp[i][w - weight[i - 1]] = max \begin{cases} dp[i - 1][w - weight[i - 1]] \cr dp[i - 1][w - 2 \times weight[i - 1]] + value[i - 1] \cr dp[i - 1][w - 3 \times weight[i - 1]] + 2 \times value[i - 1] \cr …… \cr dp[i - 1][w - k \times weight[i - 1]] + (k - 1) \times value[i - 1] \end{cases}, \quad weight[i - 1] \le k \times weight[i - 1] \le w$ + +通过观察可以发现: + +1. $(1)$ 式中共有 $k + 1$ 项,$(2)$ 式中共有 $k$ 项; +2. $(2)$ 式整个式子与 $(1)$ 式第 $1 \sim k + 1$ 项刚好相差一个 $value[i - 1]$。 + +则我们将 $(2)$ 式加上 $value[i - 1]$,再代入 $(1)$ 式中,可得到简化后的「状态转移方程」为: + +$(3) \quad dp[i][w] = max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace, \quad 0 \le weight[i - 1] \le w$。 + +简化后的「状态转移方程」去除了对物品件数的依赖,也就不需要遍历 $k$ 了,三层循环降为了两层循环。 + +> 注意:式 $(3)$ 的满足条件为 $0 \le weight[i - 1] \le w$。当 $w < weight[i - 1]$ 时,$dp[i][w] = dp[i - 1][w]$。 + +则状态转移方程为: + +$\quad dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +从上述状态转移方程我们可以看出:该式子与 0-1 背包问题中「思路 1」的状态转移式极其相似。 + +> 唯一区别点在于: +> +> 1. 0-1 背包问题中状态为 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$,这是第 $i - 1$ 阶段上的状态值。 +> 2. 完全背包问题中状态为 $dp[i][w - weight[i - 1]] + value[i - 1]$,这是第 $i$ 阶段上的状态值。 + +#### 思路 2:动态规划 + 状态转移方程优化 + +###### 1. 划分阶段 + +按照物品种类的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 + +###### 3. 状态转移方程 + +$\quad dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 +- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 思路 2:动态规划 + 状态转移方程优化 + def completePackMethod2(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1]: + # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 + dp[i][w] = dp[i - 1][w] + else: + # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 + dp[i][w] = max(dp[i - 1][w], dp[i][w - weight[i - 1]] + value[i - 1]) + + return dp[size][W] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(n \times W)$。 + +### 3.3 完全背包问题滚动数组优化 + +通过观察「思路 2」中的状态转移方程 + +$dp[i][w] = \begin{cases} dp[i - 1][w] & w < weight[i - 1] \cr max \lbrace dp[i - 1][w], \quad dp[i][w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +可以看出:我们只用到了当前行(第 $i$ 行)的 $dp[i][w]$、$dp[i][w - weight[i - 1]]$,以及上一行(第 $i - 1$ 行)的 $dp[i - 1][w]$。 + +所以我们没必要保存所有阶段的状态,只需要使用一个一维数组 $dp[w]$ 保存上一阶段的所有状态,采用使用「滚动数组」的方式对空间进行优化(去掉动态规划状态的第一维)。 + +#### 思路 3:动态规划 + 滚动数组优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = \begin{cases} dp[w] & w < weight[i - 1] \cr max \lbrace dp[w], \quad dp[w - weight[i - 1]] + value[i - 1] \rbrace & w \ge weight[i - 1] \end{cases}$ + +> 注意:这里的 $dp[w - weight[i - 1]]$ 是第 $i$ 轮计算之后的「第 $i$ 阶段的状态值」。 + +因为在计算 $dp[w]$ 时,我们需要用到第 $i$ 轮计算之后的 $dp[w - weight[i - 1]]$,所以我们需要按照「从 $0 \sim W$ 正序递推的方式」递推 $dp[w]$,这样才能得到正确的结果。 + +因为 $w < weight[i - 1]$ 时,$dp[w]$ 只能取上一阶段的 $dp[w]$,其值相当于没有变化,这部分可以不做处理。所以我们在正序递推 $dp[w]$ 时,只需从 $weight[i - 1]$ 开始遍历即可。 + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 3:代码 + +```python +class Solution: + # 思路 3:动态规划 + 滚动数组优化 + def completePackMethod3(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 正序枚举背包装载重量 + for w in range(weight[i - 1], W + 1): + # dp[w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) + + return dp[W] +``` + +> 通过观察「0-1 背包问题滚动数组优化的代码」和「完全背包问题滚动数组优化的代码」可以看出,两者的唯一区别在于: +> +> 1. 0-1 背包问题滚动数组优化的代码采用了「从 $W \sim weight[i - 1]$ 逆序递推的方式」。 +> 2. 完全背包问题滚动数组优化的代码采用了「从 $weight[i - 1] \sim W$ 正序递推的方式」。 + +#### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(W)$。 + +## 练习题目 + +- [0279. 完全平方数]() +- [0322. 零钱兑换]() +- [0518. 零钱兑换 II]() +- [0377. 组合总和 IV]() + +- [完全背包问题题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) +- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) +- 【文章】[背包问题 第四讲 - 宫水三叶的刷题日记](https://juejin.cn/post/7003243733604892685) +- 【题解】[『 套用完全背包模板 』详解完全背包(含数学推导) - 完全平方数 - 力扣](https://leetcode.cn/problems/perfect-squares/solution/by-flix-sve5/) +- 【题解】[『 一文搞懂完全背包问题 』从0-1背包到完全背包,逐层深入+推导 - 零钱兑换 - 力扣](https://leetcode.cn/problems/coin-change/solution/by-flix-su7s/) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_07_knapsack_problem_03.md b/docs/08_dynamic_programming/08_07_knapsack_problem_03.md new file mode 100644 index 00000000..51a3231f --- /dev/null +++ b/docs/08_dynamic_programming/08_07_knapsack_problem_03.md @@ -0,0 +1,215 @@ +## 4. 多重背包问题 + +> **多重背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,件数为 $count[i]$。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? + +![多重背包问题](https://qcdn.itcharge.cn/images/20240514111701.png) + +### 4.1 多重背包问题基本思路 + +我们可以参考「0-1 背包问题」的状态定义和基本思路,对于容量为 $w$ 的背包,最多可以装 $min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$ 件第 $i - 1$ 件物品。那么我们可以多加一层循环,枚举第 $i - 1$ 件物品可以选择的件数($0 \sim min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$),从而将「完全背包问题」转换为「0-1 背包问题」。 + +#### 思路 1:动态规划 + 二维基本思路 + +###### 1. 划分阶段 + +按照物品种类的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品种类」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 + +###### 3. 状态转移方程 + +$dp[i][w] = max \lbrace dp[i - 1][w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \le min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$。 + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 +- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 思路 1:动态规划 + 二维基本思路 + def multiplePackMethod1(self, weight: [int], value: [int], count: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 枚举第 i - 1 种物品能取个数 + for k in range(min(count[i - 1], w // weight[i - 1]) + 1): + # dp[i][w] 取所有 dp[i - 1][w - k * weight[i - 1] + k * value[i - 1] 中最大值 + dp[i][w] = max(dp[i][w], dp[i - 1][w - k * weight[i - 1]] + k * value[i - 1]) + + return dp[size][W] +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$C$ 是物品的数量数组长度。因为 $n \times C = \sum count[i]$,所以时间复杂度也可以写成 $O(W \times \sum count[i])$。 +- **空间复杂度**:$O(n \times W)$。 + +### 4.2 多重背包问题滚动数组优化 + +在「完全背包问题」中,我们通过优化「状态转移方程」的方式,成功去除了对物品件数 $k$ 的依赖,从而将时间复杂度下降了一个维度。 + +而在「多重背包问题」中,我们在递推 $dp[i][w]$ 时,是无法从 $dp[i][w - weight[i - 1]]$ 状态得知目前究竟已经使用了多个件第 $i - 1$ 种物品,也就无法判断第 $i - 1$ 种物品是否还有剩余数量可选。这就导致了我们无法通过优化「状态转移方程」的方式将「多重背包问题」的时间复杂度降低。 + +但是我们可以参考「完全背包问题」+「滚动数组优化」的方式,将算法的空间复杂度下降一个维度。 + +#### 思路 2:动态规划 + 滚动数组优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = max \lbrace dp[w - k \times weight[i - 1]] + k \times value[i - 1] \rbrace, \quad 0 \le k \le min \lbrace count[i - 1], \frac{w}{weight[i - 1]} \rbrace$ + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def multiplePackMethod2(self, weight: [int], value: [int], count: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight[i - 1] - 1, -1): + # 枚举第 i - 1 种物品能取个数 + for k in range(min(count[i - 1], w // weight[i - 1]) + 1): + # dp[w] 取所有 dp[w - k * weight[i - 1]] + k * value[i - 1] 中最大值 + dp[w] = max(dp[w], dp[w - k * weight[i - 1]] + k * value[i - 1]) + + return dp[W] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限,$C$ 是物品的数量数组长度。因为 $n \times C = \sum count[i]$,所以时间复杂度也可以写成 $O(W \times \sum count[i])$。 +- **空间复杂度**:$O(W)$。 + +### 4.3 多重背包问题二进制优化 + +在「思路 2」中,我们通过「滚动数组优化」的方式,降低了算法的空间复杂度。同时也提到了无法通过优化「状态转移方程」的方式将「多重背包问题」的时间复杂度降低。 + +但我们还是可以从物品数量入手,通过「二进制优化」的方式,将算法的时间复杂度降低。 + +> **二进制优化**:简单来说,就是把物品的数量 $count[i]$ 拆分成「由 $1, 2, 4, …, 2^m$ 件单个物品组成的大物品」,以及「剩余不足 $2$ 的整数次幂数量的物品,由 $count[i] -2^{\lfloor \log_2(count[i] + 1) \rfloor - 1}$ 件单个物品组成大物品」。 + +举个例子,第 $i$ 件物品的数量为 $31$,采用「二进制优化」的方式,可以拆分成 $31 = 1 + 2 + 4 + 8 + 16$ 一共 $5$ 件物品。也将是将 $31$ 件物品分成了 $5$ 件大物品: + +1. 第 $1$ 件大物品有 $1$ 件第 $i$ 种物品组成; +2. 第 $2$ 件大物品有 $2$ 件第 $i$ 种物品组成; +3. 第 $3$ 件大物品有 $4$ 件第 $i$ 种物品组成; +4. 第 $4$ 件大物品有 $8$ 件第 $i$ 种物品组成; +5. 第 $5$ 件大物品有 $16$ 件第 $i$ 种物品组成。 + +这 $5$ 件大物品通过不同的组合,可表达出第 $i$ 种物品的数量范围刚好是 $0 \sim 31$。 + +这样本来第 $i$ 件物品数量需要枚举共计 $32$ 次($0 \sim 31$),而现在只需要枚举 $5$ 次即可。 + +再举几个例子: + +1. 第 $i$ 件物品的数量为 $6$,可以拆分为 $6 = 1 + 2 + 3$ 一共 $3$ 件物品。 +2. 第 $i$ 件物品的数量为 $8$,可以拆分为 $8 = 1 + 2 + 4 + 1$ 一共 $4$ 件物品。 +3. 第 $i$ 件物品的数量为 $18$,可以拆分为 $18 = 1 + 2 + 4 + 8 + 3$ 一共 $5$ 件物品。 + +经过「二进制优化」之后,算法的时间复杂度从 $O(W \times \sum count[i])$ 降到了 $O(W \times \sum \log_2{count[i]})$。 + +#### 思路 3:动态规划 + 二进制优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = max \lbrace dp[w - weight \underline{ } new[i - 1]] + value \underline{ } new[i - 1] \rbrace$ + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 3:代码 + +```python +class Solution: + # 思路 3:动态规划 + 二进制优化 + def multiplePackMethod3(self, weight: [int], value: [int], count: [int], W: int): + weight_new, value_new = [], [] + + # 二进制优化 + for i in range(len(weight)): + cnt = count[i] + k = 1 + while k <= cnt: + cnt -= k + weight_new.append(weight[i] * k) + value_new.append(value[i] * k) + k *= 2 + if cnt > 0: + weight_new.append(weight[i] * cnt) + value_new.append(value[i] * cnt) + + dp = [0 for _ in range(W + 1)] + size = len(weight_new) + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight_new[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight_new[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) + + return dp[W] +``` + +#### 思路 3:复杂度分析 + +- **时间复杂度**:$O(W \times \sum \log_2{count[i]})$,其中 $W$ 为背包的载重上限,$count[i]$ 是第 $i$ 种物品的数量。 +- **空间复杂度**:$O(W)$。 + +## 练习题目 + +- [多重背包问题题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%A4%9A%E9%87%8D%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) +- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) +- 【文章】[【动态规划/背包问题】多重背包の二进制优化](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486796&idx=1&sn=a382b38f8aed295410550bb1767437bd&chksm=fd9ca653caeb2f456262bbf70ffe1eeda8758b426a901a6ac15be184e7017870020e456c6fa2&scene=178&cur_album_id=1869157771795841024#rd) diff --git a/docs/08_dynamic_programming/08_08_knapsack_problem_04.md b/docs/08_dynamic_programming/08_08_knapsack_problem_04.md new file mode 100644 index 00000000..af9fee9f --- /dev/null +++ b/docs/08_dynamic_programming/08_08_knapsack_problem_04.md @@ -0,0 +1,338 @@ +## 5. 混合背包问题 + +> **混合背包问题**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,件数为 $count[i]$。其中: +> +> 1. 当 $count[i] = -1$ 时,代表该物品只有 $1$ 件。 +> 2. 当 $count[i] = 0$ 时,代表该物品有无限件。 +> 3. 当 $count[i] > 0$ 时,代表该物品有 $count[i]$ 件。 +> +> 请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? + +![混合背包问题](https://qcdn.itcharge.cn/images/20240514111727.png) + +#### 思路 1:动态规划 + +混合背包问题其实就是将「0-1 背包问题」、「完全背包问题」和「多重背包问题」这 $3$ 种背包问题综合起来,有的是能取 $1$ 件,有的能取无数件,有的只能取 $count[i]$ 件。 + +其实只要理解了之前讲解的这 $3$ 种背包问题的核心思想,只要将其合并在一起就可以了。 + +并且在「多重背包问题」中,我们曾经使用「二进制优化」的方式,将「多重背包问题」转换为「0-1 背包问题」,那么在解决「混合背包问题」时,我们也可以先将「多重背包问题」转换为「0-1 背包问题」,然后直接再区分是「0-1 背包问题」还是「完全背包问题」就可以了。 + +#### 思路 1:代码 + +```python +class Solution: + def mixedPackMethod1(self, weight: [int], value: [int], count: [int], W: int): + weight_new, value_new, count_new = [], [], [] + + # 二进制优化 + for i in range(len(weight)): + cnt = count[i] + # 多重背包问题,转为 0-1 背包问题 + if cnt > 0: + k = 1 + while k <= cnt: + cnt -= k + weight_new.append(weight[i] * k) + value_new.append(value[i] * k) + count_new.append(1) + k *= 2 + if cnt > 0: + weight_new.append(weight[i] * cnt) + value_new.append(value[i] * cnt) + count_new.append(1) + # 0-1 背包问题,直接添加 + elif cnt == -1: + weight_new.append(weight[i]) + value_new.append(value[i]) + count_new.append(1) + # 完全背包问题,标记并添加 + else: + weight_new.append(weight[i]) + value_new.append(value[i]) + count_new.append(0) + + dp = [0 for _ in range(W + 1)] + size = len(weight_new) + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 0-1 背包问题 + if count_new[i - 1] == 1: + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight_new[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight_new[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) + # 完全背包问题 + else: + # 正序枚举背包装载重量 + for w in range(weight_new[i - 1], W + 1): + # dp[w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」与「前 i 种物品装入载重为 w - weight[i - 1] 的背包中,再装入 1 件第 i - 1 种物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1]) + + return dp[W] +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(W \times \sum \log_2{count[i]})$,其中 $W$ 为背包的载重上限,$count[i]$ 是第 $i$ 种物品的数量。 +- **空间复杂度**:$O(W)$。 + +## 6. 分组背包问题 + +> **分组背包问题**:有 $n$ 组物品和一个最多能装重量为 $W$ 的背包,第 $i$ 组物品的件数为 $group\underline{\hspace{0.5em}}count[i]$,第 $i$ 组的第 $j$ 个物品重量为 $weight[i][j]$,价值为 $value[i][j]$。每组物品中最多只能选择 $1$ 件物品装入背包。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? + +![分组背包问题](https://qcdn.itcharge.cn/images/20240514111745.png) + +### 6.1 分组背包问题基本思路 + +#### 思路 1:动态规划 + 二维基本思路 + +###### 1. 划分阶段 + +按照物品种类的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品组数」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 + +###### 3. 状态转移方程 + +由于我们可以不选择 $i - 1$ 组物品中的任何物品,也可以从第 $i - 1$ 组物品的第 $0 \sim group\underline{\hspace{0.5em}}count[i - 1] - 1$ 件物品中随意选择 $1$ 件物品,所以状态 $dp[i][w]$ 可能从以下方案中选择最大值: + +1. 不选择第 $i - 1$ 组中的任何物品:可以获得的最大价值为 $dp[i - 1][w]$。 +2. 选择第 $i - 1$ 组物品中第 $0$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][0]] + value[i - 1][0]$。 +3. 选择第 $i - 1$ 组物品中第 $1$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][1]] + value[i - 1][1]$。 +4. …… +5. 选择第 $i - 1$ 组物品中最后 $1$ 件:假设 $k = group\underline{\hspace{0.5em}}count[i - 1] - 1$,则可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]$。 + +则状态转移方程为: + +$dp[i][w] = max \lbrace dp[i - 1][w], dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] \rbrace , \quad 0 \le k \le group\underline{\hspace{0.5em}}count[i - 1]$ + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即 $dp[i][0] = 0, 0 \le i \le size$。 +- 无论背包载重上限是多少,前 $0$ 组物品所能获得的最大价值一定为 $0$,即 $dp[0][w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 思路 1:动态规划 + 二维基本思路 + def groupPackMethod1(self, group_count: [int], weight: [[int]], value: [[int]], W: int): + size = len(group_count) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 组物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 枚举第 i - 1 组物品能取个数 + dp[i][w] = dp[i - 1][w] + for k in range(group_count[i - 1]): + if w >= weight[i - 1][k]: + # dp[i][w] 取所有 dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] 中最大值 + dp[i][w] = max(dp[i][w], dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]) +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{\hspace{0.5em}}count[i]$,所以时间复杂度也可以写成 $O(W \times \sum group\underline{\hspace{0.5em}}count[i])$。 +- **空间复杂度**:$O(n \times W)$。 + +### 6.2 分组背包问题滚动数组优化 + +#### 思路 2:动态规划 + 滚动数组优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = max \lbrace dp[w], \quad dp[w - weight[i - 1][k]] + value[i - 1][k] \rbrace , \quad 0 \le k \le group\underline{\hspace{0.5em}}count[i - 1]$ + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def groupPackMethod2(self, group_count: [int], weight: [[int]], value: [[int]], W: int): + size = len(group_count) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 组物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量 + for w in range(W, -1, -1): + # 枚举第 i - 1 组物品能取个数 + for k in range(group_count[i - 1]): + if w >= weight[i - 1][k]: + # dp[w] 取所有 dp[w - weight[i - 1][k]] + value[i - 1][k] 中最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1][k]] + value[i - 1][k]) + + return dp[W] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W \times C)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{\hspace{0.5em}}count[i]$,所以时间复杂度也可以写成 $O(W \times \sum group\underline{\hspace{0.5em}}count[i])$。 +- **空间复杂度**:$O(W)$。 + +## 7. 二维费用背包问题 + +> **二维费用背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$、容量为 $V$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,体积为 $volume[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限、容量上限的情况下,能装入背包的最大价值是多少? + +![二维费用背包问题](https://qcdn.itcharge.cn/images/20240514111802.png) + +### 7.1 二维费用背包问题基本思路 + +我们可以参考「0-1 背包问题」的状态定义和基本思路,在「0-1 背包问题」基本思路的基础上,增加一个维度用于表示物品的容量。 + +#### 思路 1:动态规划 + 三维基本思路 + +###### 1. 划分阶段 + +按照物品种类的序号、当前背包的载重上限、容量上限进行阶段划分 + +###### 2. 定义状态 + +定义状态 $dp[i][w][v]$ 为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]), \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ + +> 注意:采用这种「状态定义」和「状态转移方程」,往往会导致内存超出要求限制,所以一般我们会采用「滚动数组」对算法的空间复杂度进行优化。 + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$ 或者容量上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即: + - $dp[i][w][0] = 0, 0 \le i \le size, 0 \le w \le W$ + - $dp[i][0][v] = 0, 0 \le i \le size, 0 \le v \le V$ + +- 无论背包载重上限是多少,前 $0$ 种物品所能获得的最大价值一定为 $0$,即: + - $dp[0][w][v] = 0, 0 \le w \le W, 0 \le v \le V$ + + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[i][w][v]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W][V]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 思路 1:动态规划 + 三维基本思路 + def twoDCostPackMethod1(self, weight: [int], volume: [int], value: [int], W: int, V: int): + size = len(weight) + dp = [[[0 for _ in range(V + 1)] for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 组物品 + for i in range(1, N + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 枚举背包装载容量 + for v in range(V + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1] or v < volume[i - 1]: + # dp[i][w][v] 取「前 i - 1 件物品装入装载重量为 w、装载容量为 v 的背包中的最大价值」 + dp[i][w][v] = dp[i - 1][w][v] + else: + # dp[i][w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 + dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) + + return dp[size][W][V] +``` + +#### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W \times V)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 +- **空间复杂度**:$O(n \times W \times V)$。 + +### 7.2 二维费用背包问题滚动数组优化 + +#### 思路 2:动态规划 + 滚动数组优化 + +###### 1. 划分阶段 + +按照当前背包的载重上限、容量上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w][v] = max \lbrace dp[w][v], \quad dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] \rbrace , \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ + +###### 4. 初始条件 + +- 如果背包载重上限为 $0$ 或者容量上限为 $0$,则无论选取什么物品,可以获得的最大价值一定是 $0$,即: + - $dp[w][0] = 0, 0 \le w \le W$ + - $dp[0][v] = 0, 0 \le v \le V$ + + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W][V]$,其中 $W$ 为背包的载重上限,$V$ 为背包的容量上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def twoDCostPackMethod2(self, weight: [int], volume: [int], value: [int], W: int, V: int): + size = len(weight) + dp = [[0 for _ in range(V + 1)] for _ in range(W + 1)] + + # 枚举前 i 组物品 + for i in range(1, N + 1): + # 逆序枚举背包装载重量 + for w in range(W, weight[i - 1] - 1, -1): + # 逆序枚举背包装载容量 + for v in range(V, volume[i - 1] - 1, -1): + # dp[w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 + dp[w][v] = max(dp[w][v], dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) + + return dp[W][V] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W \times V)$,其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 +- **空间复杂度**:$O(W \times V)$。 + +## 练习题目 + +- [1155. 掷骰子等于目标和的方法数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md) +- [0474. 一和零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/ones-and-zeroes.md) + +- [分组背包问题题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%88%86%E7%BB%84%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E9%A2%98%E7%9B%AE) +- [多维背包问题题目列表](https://github.com/itcharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%A4%9A%E7%BB%B4%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) +- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) +- 【文章】[【动态规划/背包问题】分组背包问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487504&idx=1&sn=9ac523ec0ac14c8634a229f8c3f919d7&chksm=fd9cbb0fcaeb32196b80a40e4408f6a7e2651167e0b9e31aa6d7c6109fbc2117340a59db12a1&token=1936267333&lang=zh_CN&scene=21#wechat_redirect) +- 【文章】[【动态规划/背包问题】背包问题第一阶段最终章:混合背包问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487034&idx=1&sn=eaa05b76387d34aa77f7f14f35fa78a4&chksm=fd9ca525caeb2c33095d285222dcee0dd072465bf7288bda0aab39e90a04bb7b1af018b89fd4&token=1872331648&lang=zh_CN&scene=21#wechat_redirect) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_09_knapsack_problem_05.md b/docs/08_dynamic_programming/08_09_knapsack_problem_05.md new file mode 100644 index 00000000..af0d35ba --- /dev/null +++ b/docs/08_dynamic_programming/08_09_knapsack_problem_05.md @@ -0,0 +1,328 @@ +## 8. 背包问题变种 + +### 8.1 求恰好装满背包的最大价值 + +> **背包问题求恰好装满背包的最大价值**:在给定背包重量 $W$,每件物品重量 $weight[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在恰好装满背包的情况下,能装入背包的最大价值总和是多少? + +在背包问题中,有的题目不要求把背包装满,而有的题目要求恰好装满背包。 + +如果题目要求「恰好装满背包」,则我们可在原有状态定义、状态转移方程的基础上,在初始化时,令 $dp[0] = 0$,以及 $d[w] = -\infty, 1 \le w \le W$。 这样就可以保证最终得到的 $dp[W]$ 为恰好装满背包的最大价值总和。 + +这是因为:初始化的 $dp$ 数组实际上就是在没有任何物品可以放入背包时的「合法状态」。 + +如果不要求恰好装满背包,那么: + +1. 任何载重上限下的背包,在不放入任何物品时,都有一个合法解,此时背包所含物品的最大价值为 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +而如果要求恰好装满背包,那么: + +1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为 $0$,即 $dp[0] = 0$。 +2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值应为 $-\infty$,即 $dp[w] = 0, 0 \le w \le W$。 + +这样在进行状态转移时,我们可以通过判断 $dp[w]$ 与 $-\infty$ 的关系,来判断是否能恰好装满背包。 + +下面我们以「0-1 背包问题」求恰好装满背包的最大价值为例。 + +> **0-1 背包问题求恰好装满背包的最大价值**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在恰好装满背包的情况下,能装入背包的最大价值总和是多少? + +#### 思路 1:动态规划 + 一维状态 + +1. **划分阶段**:按照当前背包的载重上限进行阶段划分。 +2. **定义状态**:定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 +3. **状态转移方程**:$dp[w] = dp[w] + dp[w - weight[i - 1]]$ +4. **初始条件**: + 1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为 $0$,即 $dp[0] = 0$。 + 2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值应为 $-\infty$,即 $dp[w] = 0, 0 \le w \le W$。 +5. **最终结果**:根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 1:代码 + +```python +class Solution: + # 0-1 背包问题 求恰好装满背包的最大价值 + def zeroOnePackJustFillUp(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [float('-inf') for _ in range(W + 1)] + dp[0] = 0 + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) + + if dp[W] == float('-inf'): + return -1 + return dp[W] +``` + +#### 思路 1:算法复杂度 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(W)$。 + +### 8.2 求方案总数 + +> **背包问题求方案数**:在给定背包重量 $W$,每件物品重量 $weight[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在总重量不超过背包载重上限的情况下,或者在总重量不超过某一指定重量的情况下,一共有多少种方案? + +这种问题就是将原有状态转移方程中的「求最大值」变为「求和」即可。 + +下面我们以「0-1 背包问题」求方案总数为例。 + +> **0-1 背包问题求方案数**:有 $n$ 件物品和有一个最多能装重量为 $W$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。 +> +> 请问在总重量不超过背包载重上限的情况下,一共有多少种方案? + +- 如果使用二维状态定义,可定义状态 $dp[i][w]$ 为:前 $i$ 件物品放入一个最多能装重量为 $w$ 的背包中的方案总数。则状态转移方程为:$dp[i][w] = dp[i - 1][w] + dp[i][w - weight[i - 1]]$。 +- 如果使用一维状态定义,可定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中的方案总数。则状态转移方程为:$dp[w] = dp[w] + dp[w - weight[i - 1]]$。 + +下面我们使用一维状态定义方式解决「0-1 背包问题求解方案数」问题。 + +#### 思路 2:动态规划 + 一维状态 + +1. **划分阶段**:按照物品种类的序号、当前背包的载重上限进行阶段划分。 +2. **定义状态**:定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中的方案总数。 +3. **状态转移方程**:$dp[w] = dp[w] + dp[w - weight[i - 1]]$ +4. **初始条件**:如果背包载重上限为 $0$,则一共有 $1$ 种方案(什么也不装),即 $dp[0] = 1$。 +5. **最终结果**:根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。则最终结果为 $dp[W]$,其中 $W$ 为背包的载重上限。 + +#### 思路 2:代码 + +```python +class Solution: + # 0-1 背包问题求方案总数 + def zeroOnePackNumbers(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + dp[0] = 1 + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量 + for w in range(W, weight[i - 1] - 1, -1): + # dp[w] = 前 i - 1 件物品装入载重为 w 的背包中的方案数 + 前 i 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 件物品的方案数 + dp[w] = dp[w] + dp[w - weight[i - 1]] + + return dp[W] +``` + +#### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(W)$。 + +### 8.3 求最优方案数 + +> **背包问题求最优方案数**:在给定背包重量 $W$,每件物品重量 $weight[i]$、物品价值 $value[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问在总重量不超过背包载重上限的情况下,使背包总价值最大的方案数是多少? + +通过结合「求背包最大可得价值」和「求方案数」两个问题的思路,我们可以分别定义两个状态: + +1. 定义 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可获得的最大价值。 +2. 定义 $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。 + +下面我们以「0-1 背包问题」求最优方案数为例。 + +> **0-1 背包问题求最优方案数**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限的情况下,使背包总价值最大的方案数是多少? + +#### 思路 3:动态规划 + +1. **划分阶段**:按照物品种类的序号、当前背包的载重上限进行阶段划分。 +2. **定义状态**: + 1. 定义 $dp[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,可获得的最大价值。 + 2. 定义 $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。 +3. **状态转移方程**: + 1. 如果 $dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明选择第 $i - 1$ 件物品获得价值更高,此时方案数 $op[i][w]$ 是在 $op[i - 1][w - weight[i - 1]]$ 基础上添加了第 $i - 1$ 件物品,因此方案数不变,即:$op[i][w] = op[i - 1][w - weight[i - 1]]$。 + 2. 如果 $dp[i - 1][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明选择与不选择第 $i - 1$ 件物品获得价格相等,此时方案数应为两者之和,即:$op[i][w] = op[i - 1][w] + op[i - 1][w - weight[i - 1]]$。 + 3. 如果 $dp[i - 1][w] > dp[i - 1][w - weight[i - 1]] + value[i - 1]$,则说明不选择第 $i - 1$ 件物品获得价值更高,此时方案数等于之前方案数,即:$op[i][w] = op[i - 1][w]$。 +4. **初始条件**:如果背包载重上限为 $0$,则一共有 $1$ 种方案(什么也不装),即 $dp[0] = 1$。 +5. **最终结果**:根据我们之前定义的状态, $op[i][w]$ 表示为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,使背包总价值最大的方案数。则最终结果为 $op[size][W]$,其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 + +#### 思路 3:代码 + +```python +class Solution: + # 0-1 背包问题求最优方案数 思路 1 + def zeroOnePackMaxProfitNumbers1(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + op = [[1 for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1]: + # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 + dp[i][w] = dp[i - 1][w] + op[i][w] = op[i - 1][w] + else: + # 选择第 i - 1 件物品获得价值更高 + if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] + # 在之前方案基础上添加了第 i - 1 件物品,因此方案数量不变 + op[i][w] = op[i - 1][w - weight[i - 1]] + # 两种方式获得价格相等 + elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w] + # 方案数 = 不使用第 i - 1 件物品的方案数 + 使用第 i - 1 件物品的方案数 + op[i][w] = op[i - 1][w] + op[i - 1][w - weight[i - 1]] + # 不选择第 i - 1 件物品获得价值最高 + else: + dp[i][w] = dp[i - 1][w] + # 不选择第 i - 1 件物品,与之前方案数相等 + op[i][w] = op[i - 1][w] + + return op[size][W] +``` + +#### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(n \times W)$。 + +### 8.4 求具体方案 + +> **背包问题求具体方案**:在给定背包重量 $W$,每件物品重量 $weight[i]$、物品价值 $value[i]$,物品间相互关系(分组、依赖等)的背包问题中,请问将哪些物品装入背包,可使这些物品的总重量不超过背包载重上限,且价值总和最大? + +一般背包问题都是求解一个最优值,但是如果要输出该最优值的具体方案,除了 $dp[i][w]$,我们可以再定义一个数组 $path[i][w]$ 用于记录状态转移时,所取的状态是状态转移方程中的哪一项,从而确定选择的具体物品。 + +下面我们以「0-1 背包问题」求具体方案为例。 + +> **0-1 背包问题求具体方案**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问将哪些物品装入背包,可使这些物品的总重量不超过背包载重上限,且价值总和最大? + +#### 思路 4:动态规划 + 路径记录 + +0-1 背包问题的状态转移方程为:$dp[i][w] = max \lbrace dp[i - 1][w], \quad dp[i - 1][w - weight[i - 1]] + value[i - 1] \rbrace$ + +则我们可以再定义一个 $path[i][w]$ 用于记录状态转移时,所取的状态是状态转移方程中的哪一项。 + +1. 如果 $path[i][w] = False$,说明:转移到 $dp[i][w]$ 时,选择了前一项 $dp[i - 1][w]$,并且具体方案中不包括第 $i - 1$ 件物品。 +2. 如果 $paht[i][w] = True$,说明:转移到 $dp[i][w]$ 时,选择了后一项 $dp[i - 1][w - weight[i - 1]] + value[i - 1]$,并且具体方案中包括第 $i - 1$ 件物品。 + +#### 思路 4:代码 + +```python +class Solution: + # 0-1 背包问题求具体方案 + def zeroOnePackPrintPath(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + path = [[False for _ in range(W + 1)] for _ in range(size + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1]: + # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 + dp[i][w] = dp[i - 1][w] + path[i][w] = False + else: + # 选择第 i - 1 件物品获得价值更高 + if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] + # 取状态转移式第二项:在之前方案基础上添加了第 i - 1 件物品 + path[i][w] = True + # 两种方式获得价格相等 + elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w] + # 取状态转移式第二项:尽量使用第 i - 1 件物品 + path[i][w] = True + # 不选择第 i - 1 件物品获得价值最高 + else: + dp[i][w] = dp[i - 1][w] + # 取状态转移式第一项:不选择第 i - 1 件物品 + path[i][w] = False + + res = [] + i, w = size, W + while i >= 1 and w >= 0: + if path[i][w]: + res.append(str(i - 1)) + w -= weight[i - 1] + i -= 1 + + return " ".join(res[::-1]) +``` + +#### 思路 4:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(n \times W)$。 + +### 8.5 求字典序最小的具体方案 + +这里的「字典序最小」指的是序号为 $0 \sim size - 1$ 的物品选择方案排列出来之后的字典序最小。 + +我们仍以「0-1 背包问题」求字典序最小的具体方案为例。 + +为了使「字典序最小」。我们可以先将物品的序号进行反转,从 $0 \sim size - 1$ 变为 $size - 1 \sim 0$,然后在返回具体方案时,再根据 $i = size - 1$ 将序号变回来。 + +这是为了在选择物品时,尽可能的向后选择反转后序号大的物品(即原序号小的物品),从而保证原序号为 $0 \sim size - 1$ 的物品选择方案排列出来之后的字典序最小。 + +#### 思路 5:代码 + +```python +class Solution: + # 0-1 背包问题求具体方案,要求最小序输出 + def zeroOnePackPrintPathMinOrder(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] + path = [[False for _ in range(W + 1)] for _ in range(size + 1)] + + weight.reverse() + value.reverse() + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(W + 1): + # 第 i - 1 件物品装不下 + if w < weight[i - 1]: + # dp[i][w] 取「前 i - 1 种物品装入载重为 w 的背包中的最大价值」 + dp[i][w] = dp[i - 1][w] + path[i][w] = False + else: + # 选择第 i - 1 件物品获得价值更高 + if dp[i - 1][w] < dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w - weight[i - 1]] + value[i - 1] + # 取状态转移式第二项:在之前方案基础上添加了第 i - 1 件物品 + path[i][w] = True + # 两种方式获得价格相等 + elif dp[i - 1][w] == dp[i - 1][w - weight[i - 1]] + value[i - 1]: + dp[i][w] = dp[i - 1][w] + # 取状态转移式第二项:尽量使用第 i - 1 件物品 + path[i][w] = True + # 不选择第 i - 1 件物品获得价值最高 + else: + dp[i][w] = dp[i - 1][w] + # 取状态转移式第一项:不选择第 i - 1 件物品 + path[i][w] = False + + res = [] + i, w = size, W + while i >= 1 and w >= 0: + if path[i][w]: + res.append(str(size - i)) + w -= weight[i - 1] + i -= 1 + + return " ".join(res) +``` + +#### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 +- **空间复杂度**:$O(n \times W)$。 + + +## 参考资料 + +- 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) +- 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/) +- 【文章】[背包问题——“01背包”最优方案总数分析及实现 - wumuzi 的博客](https://blog.csdn.net/wumuzi520/article/details/7019131) +- 【文章】[背包问题——“完全背包”最优方案总数分析及实现 - wumuzi的博客](https://blog.csdn.net/wumuzi520/article/details/7019661) +- 【文章】[背包问题——“01背包”及“完全背包”装满背包的方案总数分析及实现 - wumuzi的博客](https://blog.csdn.net/wumuzi520/article/details/7021210) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_10_interval_dp.md b/docs/08_dynamic_programming/08_10_interval_dp.md new file mode 100644 index 00000000..fa594b83 --- /dev/null +++ b/docs/08_dynamic_programming/08_10_interval_dp.md @@ -0,0 +1,391 @@ +## 1. 区间动态规划简介 + +### 1.1 区间动态规划定义 + +> **区间动态规划**:线性 DP 的一种,简称为「区间 DP」。以「区间长度」划分阶段,以两个坐标(区间的左、右端点)作为状态的维度。一个状态通常由被它包含且比它更小的区间状态转移而来。 + +区间 DP 的主要思想就是:先在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。 + +根据小区间向大区间转移情况的不同,常见的区间 DP 问题可以分为两种: + +1. 单个区间从中间向两侧更大区间转移的区间 DP 问题。比如从区间 $[i + 1, j - 1]$ 转移到更大区间 $[i, j]$。 +2. 多个(大于等于 $2$ 个)小区间转移到大区间的区间 DP 问题。比如从区间 $[i, k]$ 和区间 $[k, j]$ 转移到区间 $[i, j]$。 + +下面我们讲解一下这两种区间 DP 问题的基本解题思路。 + +### 1.2 区间 DP 问题的基本思路 + +#### 1.2.1 第 1 种区间 DP 问题基本思路 + +从中间向两侧转移的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max \lbrace dp[i + 1][j - 1], \quad dp[i + 1][j], \quad dp[i][j - 1] \rbrace + cost[i][j], \quad i \le j$。 + +1. 其中 $dp[i][j]$ 表示为:区间 $[i, j]$(即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 +2. $cost$ 表示为:从小区间转移到区间 $[i, j]$ 的代价。 +3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 + +从中间向两侧转移的区间 DP 问题的基本解题思路如下: + +1. 枚举区间的起点; +2. 枚举区间的终点; +3. 根据状态转移方程计算从小区间转移到更大区间后的最优值。 + +对应代码如下: + +```python +for i in range(size - 1, -1, -1): # 枚举区间起点 + for j in range(i + 1, size): # 枚举区间终点 + # 状态转移方程,计算转移到更大区间后的最优值 + dp[i][j] = max(dp[i + 1][j - 1], dp[i + 1][j], dp[i][j - 1]) + cost[i][j] +``` + +#### 1.2.3 第 2 种区间 DP 问题基本思路 + +多个(大于等于 $2$ 个)小区间转移到大区间的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost[i][j] \rbrace, \quad i < k \le j$。 + +1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 +2. $cost[i][j]$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中的元素合并为区间 $[i, j]$ 中的元素的代价。 +3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 + +多个小区间转移到大区间的区间 DP 问题的基本解题思路如下: + +1. 枚举区间长度; +2. 枚举区间的起点,根据区间起点和区间长度得出区间终点; +3. 枚举区间的分割点,根据状态转移方程计算合并区间后的最优值。 + +对应代码如下: + +```python +for l in range(1, n): # 枚举区间长度 + for i in range(n): # 枚举区间起点 + j = i + l - 1 # 根据起点和长度得到终点 + if j >= n: + break + dp[i][j] = float('-inf') # 初始化 dp[i][j] + for k in range(i, j + 1): # 枚举区间分割点 + # 状态转移方程,计算合并区间后的最优值 + dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + cost[i][j]) +``` + +## 2. 区间 DP 问题的应用 + +下面我们根据几个例子来讲解一下区间 DP 问题的具体解题思路。 + +### 2.1 最长回文子序列 + +#### 2.1.1 题目链接 + +- [516. 最长回文子序列 - 力扣](https://leetcode.cn/problems/longest-palindromic-subsequence/) + +#### 2.1.2 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中最长的回文子序列,并返回该序列的长度。 + +**说明**: + +- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 +- $1 \le s.length \le 1000$。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "bbbab" +输出:4 +解释:一个可能的最长回文子序列为 "bbbb"。 +``` + +- 示例 2: + +```python +输入:s = "cbbd" +输出:2 +解释:一个可能的最长回文子序列为 "bb"。 +``` + +#### 2.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 + +###### 3. 状态转移方程 + +我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: + +1. 如果 $s[i] = s[j]$,则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + $2$,即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 +2. 如果 $s[i] \ne s[j]$,则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: + 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 + 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 + +则状态转移方程为: + +$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ + +###### 4. 初始条件 + +- 单个字符的最长回文序列是 $1$,即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1]$,所以我们应该按照从下到上、从左到右的顺序进行遍历。 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 + +##### 思路 1:代码 + +```python +class Solution: + def longestPalindromeSubseq(self, s: str) -> int: + size = len(s) + dp = [[0 for _ in range(size)] for _ in range(size)] + for i in range(size): + dp[i][i] = 1 + + for i in range(size - 1, -1, -1): + for j in range(i + 1, size): + if s[i] == s[j]: + dp[i][j] = dp[i + 1][j - 1] + 2 + else: + dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) + + return dp[0][size - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + +### 2.2 戳气球 + +#### 2.2.1 题目链接 + +- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/) + +#### 2.2.2 题目大意 + +**描述**:有 $n$ 个气球,编号为 $0 \sim n - 1$,每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。 + +**要求**:求出能获得硬币的最大数量。 + +**说明**: + +- $n == nums.length$。 +- $1 \le n \le 300$。 +- $0 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,1,5,8] +输出:167 +解释: +nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] +coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 +``` + +- 示例 2: + +```python +输入:nums = [1,5] +输出:10 +解释: +nums = [1,5] --> [5] --> [] +coins = 1*1*5 + 1*5*1 = 10 +``` + +#### 2.2.3 解题思路 + +##### 思路 1:动态规划 + +根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 $1$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 $0 \sim n + 1$。 + +对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 $1$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 $0 \sim n + 1$ 之间的所有气球(不包括编号 $0$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 + +###### 3. 状态转移方程 + +假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 + +$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: + +$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 $0$,即 $dp[i][j] = 0, \quad i \ge j - 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。所以最终结果为 $dp[0][n + 1]$。 + +##### 思路 1:代码 + +```python +class Solution: + def maxCoins(self, nums: List[int]) -> int: + size = len(nums) + arr = [0 for _ in range(size + 2)] + arr[0] = arr[size + 1] = 1 + for i in range(1, size + 1): + arr[i] = nums[i - 1] + + dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] + + for l in range(3, size + 3): + for i in range(0, size + 2): + j = i + l - 1 + if j >= size + 2: + break + for k in range(i + 1, j): + dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) + + return dp[0][size + 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为气球数量。 +- **空间复杂度**:$O(n^2)$。 + +### 2.3 切棍子的最小成本 + +#### 2.3.1 题目链接 + +- [1547. 切棍子的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) + +#### 2.3.2 题目大意 + +**描述**:给定一个整数 $n$,代表一根长度为 $n$ 个单位的木根,木棍从 $0 \sim n$ 标记了若干位置。例如,长度为 $6$ 的棍子可以标记如下: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg) + +再给定一个整数数组 $cuts$,其中 $cuts[i]$ 表示需要将棍子切开的位置。 + +我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 + +每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 + +**要求**:返回切棍子的最小总成本。 + +**说明**: + +- $2 \le n \le 10^6$。 +- $1 \le cuts.length \le min(n - 1, 100)$。 +- $1 \le cuts[i] \le n - 1$。 +- $cuts$ 数组中的所有整数都互不相同。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e1.jpg) + +```python +输入:n = 7, cuts = [1,3,4,5] +输出:16 +解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 +第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。 +``` + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e11.jpg) + +- 示例 2: + +```python +输入:n = 9, cuts = [5,6,1,4,2] +输出:22 +解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 +``` + +#### 2.3.3 解题思路 + +##### 思路 1:动态规划 + +我们可以预先在数组 $cuts$ 种添加位置 $0$ 和位置 $n$,然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 + +###### 3. 状态转移方程 + +假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k$,则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 + +而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 + +则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- 相邻位置之间没有切割点,不需要切割,最小成本为 $0$,即 $dp[i - 1][i] = 0$。 +- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 + +##### 思路 1:代码 + +```python +class Solution: + def minCost(self, n: int, cuts: List[int]) -> int: + cuts.append(0) + cuts.append(n) + cuts.sort() + + size = len(cuts) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for i in range(1, size): + dp[i - 1][i] = 0 + + for l in range(3, size + 1): # 枚举区间长度 + for i in range(size): # 枚举区间起点 + j = i + l - 1 # 根据起点和长度得到终点 + if j >= size: + continue + dp[i][j] = float('inf') + for k in range(i + 1, j): # 枚举区间分割点 + # 状态转移方程,计算合并区间后的最优值 + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) + return dp[0][size - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m^3)$,其中 $m$ 为数组 $cuts$ 的元素个数。 +- **空间复杂度**:$O(m^2)$。 + +## 练习题目 + +- [0005. 最长回文子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) +- [0516. 最长回文子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/longest-palindromic-subsequence.md) +- [0312. 戳气球](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/burst-balloons.md) +- [0486. 预测赢家](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/predict-the-winner.md) +- [1547. 切棍子的最小成本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md) +- [0664. 奇怪的打印机](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/strange-printer.md) + +- [区间 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E5%8C%BA%E9%97%B4-dp-%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_11_tree_dp.md b/docs/08_dynamic_programming/08_11_tree_dp.md new file mode 100644 index 00000000..8229820b --- /dev/null +++ b/docs/08_dynamic_programming/08_11_tree_dp.md @@ -0,0 +1,407 @@ +## 1. 树形动态规划简介 + +> **树形动态规划**:简称为「树形 DP」,是一种在树形结构上进行推导的动态规划方法。如下图所示,树形 DP 的求解过程一般以节点从深到浅(子树从小到大)的顺序作为动态规划的「阶段」。在树形 DP 中,第 $1$ 维通常是节点编号,代表以该节点为根的子树。 + +![树形 DP](https://qcdn.itcharge.cn/images/20240514113355.png) + +树形 DP 问题的划分方法有多种方式。 + +如果按照「阶段转移的方向」进行划分,可以划分为以下两种: + +1. **自底向上**:通过递归的方式求解每棵子树,然后在回溯时,自底向上地从子节点向上进行状态转移。只有在当前节点的所有子树求解完毕之后,才可以求解当前节点,以及继续向上进行求解。 +2. **自顶向下**:从根节点开始向下递归,逐层计算子节点的状态。这种方法常常使用记忆化搜索来避免重复计算,提高效率。 + +自顶向下的树形 DP 问题比较少见,大部分树形 DP 都是采用「自底向上」的方向进行推导。 + +如果按照「是否有固定根」进行划分,可以划分为以下两种: + +1. **固定根的树形 DP**:事先指定根节点的树形 DP 问题,通常只需要从给定的根节点开始,使用 $1$ 次深度优先搜索。 +2. **不定根的树形 DP**:事先没有指定根节点的树形 DP 问题,并且根节点的变化会对一些值,例如子节点深度和、点权和等产生影响。通常需要使用 $2$ 次深度优先搜索,第 $1$ 次预处理诸如深度,点权和之类的信息,第 $2$ 次开始运行换根动态规划。 + +本文中,我们将按照「是否有固定根」进行分类,对树形 DP 问题中这两种类型问题进行一一讲解。 + +## 2. 固定根的树形 DP + +### 2.1 固定根的树形 DP 基本思路 + +固定根的树形 DP 问题,如果是二叉树,树通常是以根节点的形式给出。我们可以直接从指定根节点出发进行深度优先搜索。如果是多叉树,树是以一张 $n$ 个节点、$n - 1$ 条边的无向图形式给出的,并且事先给出指定根节点的编号。这种情况下,我们要先用邻接表存储下这 $n$ 个点和 $n - 1$ 条边,然后从指定根节点出发进行深度优先搜索,并注意标记节点是否已经被访问过,以避免在遍历中沿着反向边回到父节点。 + +下面以这两道题为例,介绍一下树形 DP 的一般解题思路。 + +### 2.2 二叉树中的最大路径和 + +#### 2.2.1 题目链接 + +- [124. 二叉树中的最大路径和 - 力扣](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) + +#### 2.2.2 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:返回其最大路径和。 + +**说明**: + +- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 +- **路径和**:路径中各节点值的总和。 +- 树中节点数目范围是 $[1, 3 * 10^4]$。 +- $-1000 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/13/exx1.jpg) + +```python +输入:root = [1,2,3] +输出:6 +解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/10/13/exx2.jpg) + +```python +输入:root = [-10,9,20,null,null,15,7] +输出:42 +解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42 +``` + +#### 2.2.3 解题思路 + +##### 思路 1:树形 DP + 深度优先搜索 + +根据最大路径和中对应路径是否穿过根节点,我们可以将二叉树分为两种: + +1. 最大路径和中对应路径穿过根节点。 +2. 最大路径和中对应路径不穿过根节点。 + +如果最大路径和中对应路径穿过根节点,则:**该二叉树的最大路径和 = 左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值**。 + +而如果最大路径和中对应路径不穿过根节点,则:**该二叉树的最大路径和 = 所有子树中最大路径和**。 + +即:**该二叉树的最大路径和 = max(左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值,所有子树中最大路径和)**。 + +对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大路径和变量 $ans$。 + +然后定义函数 ` def dfs(self, node):` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 + +计算的结果可能的情况有 $2$ 种: + +1. 经过空节点的最大贡献值等于 $0$。 +2. 经过非空节点的最大贡献值等于 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。如果该贡献值为负数,可以考虑舍弃,即最大贡献值为 $0$。 + +在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 $ans$ 即为答案。具体步骤如下: + +1. 如果根节点 $root$ 为空,则返回 $0$。 +2. 递归计算左子树的最大贡献值为 $left\underline{\hspace{0.5em}}max$。 +3. 递归计算右子树的最大贡献值为 $right\underline{\hspace{0.5em}}max$。 +4. 更新维护最大路径和变量,即 $self.ans = max \lbrace self.ans, \quad left\underline{\hspace{0.5em}}max + right\underline{\hspace{0.5em}}max + node.val \rbrace$。 +5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。 +6. 最终 $self.ans$ 即为答案。 + +##### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def __init__(self): + self.ans = float('-inf') + + def dfs(self, node): + if not node: + return 0 + left_max = max(self.dfs(node.left), 0) # 左子树提供的最大贡献值 + right_max = max(self.dfs(node.right), 0) # 右子树提供的最大贡献值 + + cur_max = left_max + right_max + node.val # 包含当前节点和左右子树的最大路径和 + self.ans = max(self.ans, cur_max) # 更新所有路径中的最大路径和 + + return max(left_max, right_max) + node.val # 返回包含当前节点的子树的最大贡献值 + + def maxPathSum(self, root: Optional[TreeNode]) -> int: + self.dfs(root) + return self.ans +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + +### 2.3 相邻字符不同的最长路径 + +#### 2.3.1 题目链接 + +- [2246. 相邻字符不同的最长路径 - 力扣](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) + +#### 2.3.2 题目大意 + +**描述**:给定一个长度为 $n$ 的数组 $parent$ 来表示一棵树(即一个连通、无向、无环图)。该树的节点编号为 $0 \sim n - 1$,共 $n$ 个节点,其中根节点的编号为 $0$。其中 $parent[i]$ 表示节点 $i$ 的父节点,由于节点 $0$ 是根节点,所以 $parent[0] == -1$。再给定一个长度为 $n$ 的字符串,其中 $s[i]$ 表示分配给节点 $i$ 的字符。 + +**要求**:找出路径上任意一对相邻节点都没有分配到相同字符的最长路径,并返回该路径的长度。 + +**说明**: + +- $n == parent.length == s.length$。 +- $1 \le n \le 10^5$。 +- 对所有 $i \ge 1$ ,$0 \le parent[i] \le n - 1$ 均成立。 +- $parent[0] == -1$。 +- $parent$ 表示一棵有效的树。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2022/03/25/testingdrawio.png) + +```python +输入:parent = [-1,0,0,1,1,2], s = "abacbe" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3。 +可以证明不存在满足上述条件且比 3 更长的路径。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2022/03/25/graph2drawio.png) + +```python +输入:parent = [-1,0,0,0], s = "aabc" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3。 +``` + +#### 2.3.3 解题思路 + +##### 思路 1:树形 DP + 深度优先搜索 + +因为题目给定的是表示父子节点的 $parent$ 数组,为了方便递归遍历相邻节点,我们可以根据 $partent$ 数组,建立一个由父节点指向子节点的有向图 $graph$。 + +如果不考虑相邻节点是否为相同字符这一条件,那么这道题就是在求树的直径(树的最长路径长度)中的节点个数。 + +对于根节点为 $u$ 的树来说: + +1. 如果其最长路径经过根节点 $u$,则:**最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 +2. 如果其最长路径不经过根节点 $u$,则:**最长路径长度 = 某个子树中的最长路径长度**。 + +即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 + +对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{\hspace{0.5em}}len$。 + +1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{\hspace{0.5em}}len$。 +2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{\hspace{0.5em}}len + v\underline{\hspace{0.5em}}len + 1)$。 +3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{\hspace{0.5em}}len = max(u\underline{\hspace{0.5em}}len, \quad v\underline{\hspace{0.5em}}len + 1)$。 + +因为题目限定了「相邻节点字符不同」,所以在更新全局最长路径长度和当前节点 $u$ 的最长路径长度时,我们需要判断一下节点 $u$ 与相邻节点 $v$ 的字符是否相同,只有在字符不同的条件下,才能够更新维护。 + +最后,因为题目要求的是树的直径(树的最长路径长度)中的节点个数,而:**路径的节点 = 路径长度 + 1**,所以最后我们返回 $self.ans + 1$ 作为答案。 + +##### 思路 1:代码 + +```python +class Solution: + def longestPath(self, parent: List[int], s: str) -> int: + size = len(parent) + + # 根据 parent 数组,建立有向图 + graph = [[] for _ in range(size)] + for i in range(1, size): + graph[parent[i]].append(i) + + ans = 0 + def dfs(u): + nonlocal ans + u_len = 0 # u 节点的最大路径长度 + for v in graph[u]: # 遍历 u 节点的相邻节点 + v_len = dfs(v) # 相邻节点的最大路径长度 + if s[u] != s[v]: # 相邻节点字符不同 + ans = max(ans, u_len + v_len + 1) # 维护最大路径长度 + u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 + return u_len # 返回 u 节点的最大路径长度 + + dfs(0) + return ans + 1 +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是树的节点数目。 +- **空间复杂度**:$O(n)$。 + +## 3. 不定根的树形 DP + +### 3.1 不定根的树形 DP 基本思路 + +不定根的树形 DP 问题,如果是二叉树,树通常是以一张 $n$ 个节点、$n - 1$ 条边的无向图形式给出的,并且事先没有指定根节点。通常需要以「每个节点为根节点」进行一系列统计。 + +这种情况下,我们一般通过「两次扫描与换根法」的方法求解这类题目: + +1. 第一次扫描时,任选一个节点为根,在「有固定根的树」上执行一次树形 DP,预处理树的一些相关信息。 +2. 第二次扫描时,从刚才的根节点出发,对整棵树再执行一次深度优先搜索,同时携带根节点的一些信息提供给子节点进行推导,计算出「换根」之后的解。 + +### 3.2 最小高度树 + +#### 3.2.1 题目链接 + +- [310. 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/) + +#### 3.2.2 题目大意 + +**描述**:有一棵包含 $n$ 个节点的树,节点编号为 $0 \sim n - 1$。给定一个数字 $n$ 和一个有 $n - 1$ 条无向边的 $edges$ 列表来表示这棵树。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间存在一条无向边。 + +可以选择树中的任何一个节点作为根,当选择节点 $x$ 作为根节点时,设结果树的高度为 $h$。在所有可能的树种,具有最小高度的树(即 $min(h)$)被成为最小高度树。 + +**要求**:找到所有的最小高度树并按照任意顺序返回他们的根节点编号列表。 + +**说明**: + +- **树的高度**:指根节点和叶子节点之间最长向下路径上边的数量。 +- $1 \le n \le 2 \times 10^4$。 +- $edges.length == n - 1$。 +- $0 \le ai, bi < n$。 +- $ai \ne bi$。 +- 所有 $(ai, bi)$ 互不相同。 +- 给定的输入保证是一棵树,并且不会有重复的边。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/09/01/e1.jpg) + +```python +输入:n = 4, edges = [[1,0],[1,2],[1,3]] +输出:[1] +解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/09/01/e2.jpg) + +```python +输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] +输出:[3,4] +``` + +#### 3.2.3 解题思路 + +##### 思路 1:树形 DP + 二次遍历换根法 + +最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点,然后进行深度优先搜索,求出每棵树的高度。最后求出所有树中的最小高度即为答案。但这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 2 \times 10^4]$,这样做会导致超时,因此需要进行优化。 + +在上面的算法中,在一轮深度优先搜索中,除了可以得到整棵树的高度之外,在搜索过程中,其实还能得到以每个子节点为根节点的树的高度。如果我们能够利用这些子树的高度信息,快速得到以其他节点为根节点的树的高度,那么我们就能改进算法,以更小的时间复杂度解决这道题。这就是二次遍历与换根法的思想。 + +1. 第一次遍历:自底向上的计算出每个节点 $u$ 向下走(即由父节点 $u$ 向子节点 $v$ 走)的最长路径 $down1[u]$、次长路径 $down2[i]$,并记录向下走最长路径所经过的子节点 $p[u]$,方便第二次遍历时计算。 +2. 第二次遍历:自顶向下的计算出每个节点 $v$ 向上走(即由子节点 $v$ 向父节点 $u$ 走)的最长路径 $up[v]$。需要注意判断 $u$ 向下走的最长路径是否经过了节点 $v$。 + 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$。 + 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$。 +3. 接下来,我们通过枚举 $n$ 个节点向上走的最长路径与向下走的最长路径,从而找出所有树中的最小高度,并将所有最小高度树的根节点放入答案数组中并返回。 + +整个算法具体步骤如下: + +1. 使用邻接表的形式存储树。 +3. 定义第一个递归函数 `dfs(u, fa)` 用于计算每个节点向下走的最长路径 $down1[u]$、次长路径 $down2[u]$,并记录向下走的最长路径所经过的子节点 $p[u]$。 + 1. 对当前节点的相邻节点进行遍历。 + 2. 如果相邻节点是父节点,则跳过。 + 3. 递归调用 `dfs(v, u)` 函数计算邻居节点的信息。 + 4. 根据邻居节点的信息计算当前节点的高度,并更新当前节点向下走的最长路径 $down1[u]$、当前节点向下走的次长路径 $down2$、取得最长路径的子节点 $p[u]$。 +4. 定义第二个递归函数 `reroot(u, fa)` 用于计算每个节点作为新的根节点时向上走的最长路径 $up[v]$。 + 1. 对当前节点的相邻节点进行遍历。 + 2. 如果相邻节点是父节点,则跳过。 + 3. 根据当前节点 $u$ 的高度和相邻节点 $v$ 的信息更新 $up[v]$。同时需要判断节点 $u$ 向下走的最长路径是否经过了节点 $v$。 + 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down2[u]) + 1$。 + 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down1[u]) + 1$。 + 4. 递归调用 `reroot(v, u)` 函数计算邻居节点的信息。 +5. 调用 `dfs(0, -1)` 函数计算每个节点的最长路径。 +6. 调用 `reroot(0, -1)` 函数计算每个节点作为新的根节点时的最长路径。 +7. 找到所有树中的最小高度。 +8. 将所有最小高度的节点放入答案数组中并返回。 + +##### 思路 1:代码 + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + graph = [[] for _ in range(n)] + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + # down1 用于记录向下走的最长路径 + down1 = [0 for _ in range(n)] + # down2 用于记录向下走的最长路径 + down2 = [0 for _ in range(n)] + p = [0 for _ in range(n)] + # 自底向上记录最长路径、次长路径 + def dfs(u, fa): + for v in graph[u]: + if v == fa: + continue + # 自底向上统计信息 + dfs(v, u) + height = down1[v] + 1 + if height >= down1[u]: + down2[u] = down1[u] + down1[u] = height + p[u] = v + elif height > down2[u]: + down2[u] = height + + # 进行换根动态规划,自顶向下统计向上走的最长路径 + up = [0 for _ in range(n)] + def reroot(u, fa): + for v in graph[u]: + if v == fa: + continue + if p[u] == v: + up[v] = max(up[u], down2[u]) + 1 + else: + up[v] = max(up[u], down1[u]) + 1 + # 自顶向下统计信息 + reroot(v, u) + + dfs(0, -1) + reroot(0, -1) + + # 找到所有树中的最小高度 + min_h = 1e9 + for i in range(n): + min_h = min(min_h, max(down1[i], up[i])) + + # 将所有最小高度的节点放入答案数组中并返回 + res = [] + for i in range(n): + if max(down1[i], up[i]) == min_h: + res.append(i) + + return res +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0687. 最长同值路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-univalue-path.md) +- [1617. 统计子树中城市之间最大距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md) +- [0834. 树中距离之和]https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/sum-of-distances-in-tree.md) + +- [树形 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E7%8A%B6%E6%80%81%E5%8E%8B%E7%BC%A9-dp-%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【题解】[C++ 容易理解的换根动态规划解法 - 最小高度树](https://leetcode.cn/problems/minimum-height-trees/solution/c-huan-gen-by-vclip-sa84/) +- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) +- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) diff --git a/docs/08_dynamic_programming/08_12_state_compression_dp.md b/docs/08_dynamic_programming/08_12_state_compression_dp.md new file mode 100644 index 00000000..2f54b02c --- /dev/null +++ b/docs/08_dynamic_programming/08_12_state_compression_dp.md @@ -0,0 +1,331 @@ +## 1. 状态压缩 DP 简介 + +> **状态压缩 DP**:简称为「状压 DP」,是一种应用在「小规模数据」的数组 / 字符串上,结合「二进制」的性质来进行状态定义与状态转移的动态规划方法。 + +我们曾在「位运算知识」章节中,学习过「二进制枚举子集算法」。这里先来回顾一下如何通过二进制枚举子集。 + +### 1.1 二进制枚举子集 + +对于一个元素个数为 $n$ 的集合 $S$ 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 $1$ 来表示选取该元素,用数字 $0$ 来表示不选取该元素。 + +那么我们就可以用一个长度为 $n$ 的二进制数来表示集合 $S$ 或者表示 $S$ 的子集。其中二进制的每一个二进位都对应了集合中某一个元素的选取状态。对于集合中第 $i$ 个元素来说,二进制对应位置上的 $1$ 代表该元素被选取,$0$ 代表该元素未被选取。 + +举个例子,比如长度为 $5$ 的集合 $S = \lbrace 5, 4, 3, 2, 1 \rbrace$,我们可以用一个长度为 $5$ 的二进制数来表示该集合。 + +比如二进制数 $11111_{(2)}$ 就表示选取集合的第 $1$ 位、第 $2$ 位、第 $3$ 位、第 $4$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 4, 3, 2, 1 \rbrace$,即集合 $S$ 本身。如下表所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :--: | :--: | :--: | :--: | :--: | +| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | +| 二进位对应值 | 1 | 1 | 1 | 1 | 1 | + +再比如二进制数 $10101_{(2)}$ 就表示选取集合的第 $1$ 位、第 $3$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 3, 1 \rbrace$。如下表所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :--: | :----: | :--: | :----: | :--: | +| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | +| 二进位对应值 | 1 | 0 | 1 | 0 | 1 | + +再比如二进制数 $01001_{(2)}$ 就表示选取集合的第 $1$ 位、第 $4$ 位元素,也就是集合 $\lbrace 4, 1 \rbrace$。如下标所示: + +| 集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 | +| :---------------- | :----: | :--: | :----: | :----: | :--: | +| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | +| 二进位对应值 | 0 | 1 | 0 | 0 | 1 | + +通过上面的例子我们可以得到启发:对于长度为 $5$ 的集合 $S$ 来说,我们只需要从 $00000 \sim 11111$ 枚举一次(对应十进制为 $0 \sim 2^5 - 1$)即可得到长度为 $5$ 的集合 $S$ 的所有子集。 + +我们将上面的例子拓展到长度为 $n$ 的集合 $S$。可以总结为: + +- 对于长度为 $n$ 的集合 $S$ 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到集合 $S$ 的所有子集。 + +### 1.2 状态定义与状态转移 + +#### 1.2.1 状态定义 + +在状压 DP 中,我们通常采用二进制数的形式来表示一维状态,即集合中每个元素的选取情况。 + +和「二进制枚举子集算法」一样,我们通过一个「 $n$ 位长度的二进制数」来表示「由 $n$ 个物品所组成的集合中所有物品的选择状态」。 + +二进制数的每一个二进位都对应了集合中某一个元素的选取状态。如果该二进制数的第 $i$ 位为 $1$,说明集合中第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明集合中第 $i$ 个元素在该状态中没有被选取。 + +#### 1.2.1 状态转移 + +一般来说,状压 DP 的状态转移方式有两种: + +1. 枚举子集:对于一个状态,枚举它的所有子集,或者枚举所有元素位置,找到比当前状态少选一个元素的子集。然后根据子集的值和状态之间的关系,更新当前状态的值。 +2. 枚举超集:对于一个状态,枚举它的所有超集。然后根据超集的值和状态之间的关系,更新当前状态的值。 + +其中,最常用的是「枚举子集」的方式。 + +### 1.3 状压 DP 的使用条件 + +对于元素个数不超过 $n$ 的集合来说,一共会出现 $2^n$ 个状态数量。因为在 $n$ 变大时会呈现指数级增长,所以状态压缩 DP 只适用于求解小数据规模问题(通常 $n \le 20$)。当 $n$ 过大时,使用状态压缩 DP 可能会超时。 + +## 2. 状态压缩 DP 中常用的位运算 + +在状压 DP 中,一维状态是集合,对状态进行操作或者状态之间进行转移,也就是要对集合进行操作。 + +因为我们使用二进制数来定义集合状态,所以对集合进行操作,就是对二进制数进行位运算操作。 + +如下所示,其中 $n$ 为集合中的元素个数,$A$、$B$ 为两个集合对应的二进制数,$i$ 表示某个元素位置。 + +- 总状态数量:`1 << n` +- 在集合 $A$ 中加入第 $i$ 位元素(将二进制数第 $i$ 位赋值为 $1$):`A = A | (1 << i)` +- 在集合 $A$ 中删除第 $i$ 位元素(将二进制数第 $i$ 位赋值为 $0$):`A = A & ~(1 << i)` +- 判断集合 $A$ 是否选取了第 $i$ 位元素(判断二进制数第 $i$ 位是否为 $1$) :`if A & (1 << i):` 或者 `if (A >> i) & 1:` +- 将集合 $A$ 设置为空集:`A = 0` +- 将集合 $A$ 设置为全集:`A = 1 << n - 1` +- 求集合 $A$ 的补集:`A = A ^ ((1 << n) - 1)` +- 求集合 $A$ 与集合 $B$ 的并集:`A | B` +- 求集合 $A$ 与集合 $B$ 的交集:`A & B` +- 枚举集合 $A$ 的子集(包含 $A$): + + ```python + subA = A # 从集合 A 开始 + while subA > 0: + ... + subA = (subB - 1) & A # 获取下一个子集 + ``` + +- 枚举全集的所有子集: + + ```python + for state in range(1 << n): # state 为子集 + for i in range(n): # 枚举第 i 位元素 + if (state >> i) & i: # 如果第 i 位元素对应二进制位 1,则表示集合中选取了该元素 + ... + ``` + +## 3. 状态压缩 DP 的应用 + +### 3.1 两个数组最小的异或值之和 + +#### 3.1.1 题目链接 + +- [1879. 两个数组最小的异或值之和 - 力扣](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) + +#### 3.1.2 题目大意 + +**描述**:给定两个整数数组 $nums1$ 和 $nums2$,两个数组长度都为 $n$。 + +**要求**:将 $nums2$ 中的元素重新排列,使得两个数组的异或值之和最小。并返回重新排列之后的异或值之和。 + +**说明**: + +- **两个数组的异或值之和**:$(nums1[0] \oplus nums2[0]) + (nums1[1] \oplus nums2[1]) + ... + (nums1[n - 1] \oplus nums2[n - 1])$(下标从 $0$ 开始)。 +- 举个例子,$[1, 2, 3]$ 和 $[3,2,1]$ 的异或值之和 等于 $(1 \oplus 3) + (2 \oplus 2) + (3 \oplus 1) + (3 \oplus 1) = 2 + 0 + 2 = 4$。 +- $n == nums1.length$。 +- $n == nums2.length$。 +- $1 \le n \le 14$。 +- $0 \le nums1[i], nums2[i] \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2], nums2 = [2,3] +输出:2 +解释:将 nums2 重新排列得到 [3,2] 。 +异或值之和为 (1 XOR 3) + (2 XOR 2) = 2 + 0 = 2。 +``` + +- 示例 2: + +```python +输入:nums1 = [1,0,3], nums2 = [5,3,4] +输出:8 +解释:将 nums2 重新排列得到 [5,4,3] 。 +异或值之和为 (1 XOR 5) + (0 XOR 4) + (3 XOR 3) = 4 + 4 + 0 = 8。 +``` + +#### 3.1.3 解题思路 + +##### 思路 1:状态压缩 DP + +由于数组 $nums2$ 可以重新排列,所以我们可以将数组 $nums1$ 中的元素顺序固定,然后将数组 $nums1$ 中第 $i$ 个元素与数组 $nums2$ 中所有还没被选择的元素进行组合,找到异或值之和最小的组合。 + +同时因为两个数组长度 $n$ 的大小范围只有 $[1, 14]$,所以我们可以采用「状态压缩」的方式来表示 $nums2$ 中当前元素的选择情况。 + +「状态压缩」指的是使用一个 $n$ 位的二进制数 $state$ 来表示排列中数的选取情况。 + +如果二进制数 $state$ 的第 $i$ 位为 $1$,说明数组 $nums2$ 第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明数组 $nums2$ 中第 $i$ 个元素在该状态中没有被选取。 + +举个例子: + +1. $nums2 = \lbrace 1, 2, 3, 4 \rbrace, state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。 +2. $nums2 = \lbrace 1, 2, 3, 4, 5, 6 \rbrace, state = (011010)_2$,表示选择了第 $2$ 个元素、第 $4$ 个元素、第 $5$ 个元素,也就是 $2$、$4$、$5$。 + +这样,我们就可以通过动态规划的方式来解决这道题。 + +###### 1. 划分阶段 + +按照数组 $nums$ 中元素选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义当前数组 $nums2$ 中元素选择状态为 $state$,$state$ 对应选择的元素个数为 $count(state)$。 + +则可以定义状态 $dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 + +###### 3. 状态转移方程 + +对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以组成的异或值之和最小值,赋值给 $dp[state]$。 + +举个例子 $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。那么 $state$ 只能从 $(1000)_2$ 和 $(0001)_2$ 这两个状态转移而来,我们只需要枚举这两种状态,并求出转移过来的异或值之和最小值。 + +即状态转移方程为:$dp[state] = min(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + (nums1[i] \oplus nums2[one\underline{\hspace{0.5em}}cnt - 1]))$,其中 $state$ 第 $i$ 位一定为 $1$,$one\underline{\hspace{0.5em}}cnt$ 为 $state$ 中 $1$ 的个数。 + +###### 4. 初始条件 + +- 既然是求最小值,不妨将所有状态初始为最大值。 +- 未选择任何数时,异或值之和为 $0$,所以初始化 $dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } n$。 + +##### 思路 1:代码 + +```python +class Solution: + def minimumXORSum(self, nums1: List[int], nums2: List[int]) -> int: + ans = float('inf') + size = len(nums1) + states = 1 << size + + dp = [float('inf') for _ in range(states)] + dp[0] = 0 + for state in range(states): + one_cnt = bin(state).count('1') + for i in range(size): + if (state >> i) & 1: + dp[state] = min(dp[state], dp[state ^ (1 << i)] + (nums1[i] ^ nums2[one_cnt - 1])) + + return dp[states - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 $nums1$、$nums2$ 的长度。 +- **空间复杂度**:$O(2^n)$。 + +### 3.2 数组的最大与和 + +#### 3.2.1 题目链接 + +- [2172. 数组的最大与和 - 力扣](https://leetcode.cn/problems/maximum-and-sum-of-array/) + +#### 3.2.2 题目大意 + +**描述**:给定一个长度为 $n$ 的整数数组 $nums$ 和一个整数 $numSlots$ 满足 $2 \times numSlots \ge n$。一共有 $numSlots$ 个篮子,编号为 $1 \sim numSlots$。 + +现在需要将所有 $n$ 个整数分到这些篮子中,且每个篮子最多有 $2$ 个整数。 + +**要求**:返回将 $nums$ 中所有数放入 $numSlots$ 个篮子中的最大与和。 + +**说明**: + +- **与和**:当前方案中,每个数与它所在篮子编号的按位与运算结果之和。 + - 比如,将数字 $[1, 3]$ 放入篮子 $1$ 中,$[4, 6]$ 放入篮子 $2$ 中,这个方案的与和为 $(1 \text{ AND } 1) + (3 \text{ AND } 1) + (4 \text{ AND } 2) + (6 \text{ AND } 2) = 1 + 1 + 0 + 2 = 4$。 +- $n == nums.length$。 +- $1 \le numSlots \le 9$。 +- $1 \le n \le 2 \times numSlots$。 +- $1 \le nums[i] \le 15$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4,5,6], numSlots = 3 +输出:9 +解释:一个可行的方案是 [1, 4] 放入篮子 1 中,[2, 6] 放入篮子 2 中,[3, 5] 放入篮子 3 中。 +最大与和为 (1 AND 1) + (4 AND 1) + (2 AND 2) + (6 AND 2) + (3 AND 3) + (5 AND 3) = 1 + 0 + 2 + 2 + 3 + 1 = 9。 +``` + +- 示例 2: + +```python +输入:nums = [1,3,10,4,7,1], numSlots = 9 +输出:24 +解释:一个可行的方案是 [1, 1] 放入篮子 1 中,[3] 放入篮子 3 中,[4] 放入篮子 4 中,[7] 放入篮子 7 中,[10] 放入篮子 9 中。 +最大与和为 (1 AND 1) + (1 AND 1) + (3 AND 3) + (4 AND 4) + (7 AND 7) + (10 AND 9) = 1 + 1 + 3 + 4 + 7 + 8 = 24 。 +注意,篮子 2 ,5 ,6 和 8 是空的,这是允许的。 +``` + +#### 3.2.3 解题思路 + +##### 思路 1:状压 DP + +每个篮子最多可分 $2$ 个整数,则我们可以将 $1$ 个篮子分成两个篮子,这样总共有 $2 \times numSlots$ 个篮子,每个篮子中最多可以装 $1$ 个整数。 + +同时因为 $numSlots$ 的范围为 $[1, 9]$,$2 \times numSlots$ 的范围为 $[2, 19]$,范围不是很大,所以我们可以用「状态压缩」的方式来表示每个篮子中的整数放取情况。 + +即使用一个 $n \times numSlots$ 位的二进制数 $state$ 来表示每个篮子中的整数放取情况。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 个篮子里边放了整数,如果 $state$ 的第 $i$ 位为 $0$,表示第 $i$ 个篮子为空。 + +这样,我们就可以通过动态规划的方式来解决这道题。 + +###### 1. 划分阶段 + +按照 $2 \times numSlots$ 个篮子中的整数放取情况进行阶段划分。 + +###### 2. 定义状态 + +定义当前每个篮子中的整数放取情况为 $state$,$state$ 对应选择的整数个数为 $count(state)$。 + +则可以定义状态 $dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。 + +###### 3. 状态转移方程 + +对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以获得的最大与和,赋值给 $dp[state]$。 + +即状态转移方程为:$dp[state] = min(dp[state], dp[state \oplus (1 \text{ <}\text{< } i)] + (i // 2 + 1) \text{ \& } nums[one\underline{\hspace{0.5em}}cnt - 1])$,其中: + +1. $state$ 第 $i$ 位一定为 $1$。 +2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 +3. $i // 2 + 1$ 为篮子对应编号 +4. $nums[one\underline{\hspace{0.5em}}cnt - 1]$ 为当前正在考虑的数组元素。 + +###### 4. 初始条件 + +- 初始每个篮子中都没有放整数的情况下,可以获得的最大与和为 $0$,即 $dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。所以最终结果为 $max(dp)$。 + +> 注意:当 $one\underline{\hspace{0.5em}}cnt > len(nums)$ 时,无法通过递推得到 $dp[state]$,需要跳过。 + +##### 思路 1:代码 + +```python +class Solution: + def maximumANDSum(self, nums: List[int], numSlots: int) -> int: + states = 1 << (numSlots * 2) + dp = [0 for _ in range(states)] + + for state in range(states): + one_cnt = bin(state).count('1') + if one_cnt > len(nums): + continue + for i in range(numSlots * 2): + if (state >> i) & 1: + dp[state] = max(dp[state], dp[state ^ (1 << i)] + ((i // 2 + 1) & nums[one_cnt - 1])) + + return max(dp) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^m \times m)$,其中 $m = 2 \times numSlots$。 +- **空间复杂度**:$O(2^m)$。 + +## 练习题目 + +- [1879. 两个数组最小的异或值之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md) +- [1947. 最大兼容性评分和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) +- [0526. 优美的排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/beautiful-arrangement.md) + +- [状态压缩 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E7%8A%B6%E6%80%81%E5%8E%8B%E7%BC%A9-dp-%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_13_counting_dp.md b/docs/08_dynamic_programming/08_13_counting_dp.md new file mode 100644 index 00000000..0c1f9997 --- /dev/null +++ b/docs/08_dynamic_programming/08_13_counting_dp.md @@ -0,0 +1,212 @@ +## 1. 计数类 DP 简介 + +### 1.1 计数问题简介 + +> **计数问题**:计算满足特定条件下的可行方案数目的问题。 + +这里的「可行方案数目」指的是某个问题一共有多少种方法。 + +「计数问题」本身是组合数学中的重要内容,这类问题通常有两种经典的求解方法: + +1. 找到递归关系,然后以动态规划的方式,列出递推式,然后求解出方案数。 +2. 转为数学问题,计算出对应的组合数,如:卡特兰数、快速幂、排列数、组合数等。 + +在解决具体问题时,还需要根据题目的具体情况进行分析。一般情况下,我们使用第 $1$ 种方法来解决。这是因为: + +1. 采用动态规划的方式能够高效的处理大规模计数问题,并且使用较少时间和空间复杂度。 +2. 即使找到了组合数,还要面临计算组合数的困难(高阶组合数计算困难、计算效率较低)。 + +所以我们通常使用「动态规划」的方法来解决计数问题。 + +### 1.2 计数类 DP 简介 + +> **计数类 DP**:一类使用动态规划方法来统计可行方案数目的问题。区别于求解最优解,计数类 DP 需要统计所有满足条件的可行解数量,同时需要满足不重复、不遗漏的条件。 + +计数类 DP 的核心思想就是:通过动态规划的算法思想,去计算出解决这个问题有多少种方法。一般来说,计数类 DP 只关注方案数目,不关注具体方案情况。 + +比如,从一个矩阵的左上角走到右下角,每次只能向右走或者向下走,一共有多少条不同路径。**注意**:这里求解的是有多少条,而不是具体路线的走法。 + +## 2. 计数类 DP 的应用 + +### 2.1 不同路径 + +#### 2.1.1 题目链接 + +- [62. 不同路径 - 力扣](https://leetcode.cn/problems/unique-paths/) + +#### 2.1.2 题目大意 + +**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 + +**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 + +**说明**: + +- $1 \le m, n \le 100$。 +- 题目数据保证答案小于等于 $2 \times 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:m = 3, n = 7 +输出:28 +``` + +![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) + +- 示例 2: + +```python +输入:m = 3, n = 2 +输出:3 +解释: +从左上角开始,总共有 3 条路径可以到达右下角。 +1. 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 +3. 向下 -> 向右 -> 向下 +``` + +#### 2.1.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:从左上角到达位置 $(i, j)$ 的路径数量。 + +###### 3. 状态转移方程 + +因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 + +###### 4. 初始条件 + +- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 +- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 +- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 + +##### 思路 1:动态规划代码 + +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp = [[0 for _ in range(n)] for _ in range(m)] + + for j in range(n): + dp[0][j] = 1 + for i in range(m): + dp[i][0] = 1 + + for i in range(1, m): + for j in range(1, n): + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + + return dp[m - 1][n - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m * n)$,所以总体时间复杂度为 $O(m \times n)$。 +- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $m$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(m)$。 + +### 2.2 整数拆分 + +#### 2.2.1 题目链接 + +- [343. 整数拆分 - 力扣](https://leetcode.cn/problems/integer-break/) + +#### 2.2.2 题目大意 + +**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 + +**要求**:返回可以获得的最大乘积。 + +**说明**: + +- $2 \le n \le 58$。 + +**示例**: + +- 示例 1: + +```python +输入: n = 2 +输出: 1 +解释: 2 = 1 + 1, 1 × 1 = 1。 +``` + +- 示例 2: + +```python +输入: n = 10 +输出: 36 +解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 +``` + +#### 2.2.3 解题思路 + +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照正整数进行划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 + +###### 3. 状态转移方程 + +当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: + +1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 +2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 + +则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 + +由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: + +$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 + +###### 4. 初始条件 + +- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 + +##### 思路 1:代码 + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + for i in range(2, n + 1): + for j in range(i): + dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) + return dp[n] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + +## 练习题目 + +- [0063. 不同路径 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths-ii.md) +- [0343. 整数拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) +- [1137. 第 N 个泰波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) + + +- [计数 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%95%B0%E4%BD%8D-dp-%E9%A2%98%E7%9B%AE) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_14_digit_dp.md b/docs/08_dynamic_programming/08_14_digit_dp.md new file mode 100644 index 00000000..386283f4 --- /dev/null +++ b/docs/08_dynamic_programming/08_14_digit_dp.md @@ -0,0 +1,380 @@ +## 1. 数位 DP 简介 + +### 1.1 数位 DP 简介 + +> **数位动态规划**:简称为「数位 DP」,是一种与数位相关的一类计数类动态规划问题,即在数位上进行动态规划。这里的数位指的是个位、十位、百位、千位等。 + +数位 DP 一般用于求解给定区间 $[left, right]$ 中,满足特定条件的数值个数,或者用于求解满足特定条件的第 $k$ 小数。 + +数位 DP 通常有以下几个特征: + +1. 题目会提供一个查询区间(有时也会只提供区间上界)来作为统计限制。 +2. 题目中给定区间往往很大(比如 $10^9$),无法采用朴素的方法求解。 +3. 题目中给定的给定的限定条件往往与数位有关。 +4. 要求统计满足特定条件的数值个数,或者用于求解满足特定条件的第 $k$ 小数。 + +题目要求一段区间 $[left, right]$ 内满足特定条件的数值个数,如果能找到方法计算出前缀区间 $[0, n]$ 内满足特定条件的数值个数,那么我们就可以利用「前缀和思想」,分别计算出区间 $[0, left - 1]$ 与区间 $[0, right]$ 内满足特定条件的数值个数,然后将两者相减即为所求答案。即:$res[left, right] = res[0, right] - res[0, left - 1]$。 + +在使用「前缀和思想」思想后,问题转换为计算区间 $[0, n]$ 内满足特定条件的数值个数。 + +接下来就要用到数位 DP 的基本思想。 + +> **数位 DP 的基本思想**:将区间数字拆分为数位,然后逐位进行确定。 + +我们通过将区间上的数字按照数位进行拆分,然后逐位确定每一个数位上的可行方案,从而计算出区间内的可行方案个数。 + +数位 DP 可以通过「记忆化搜索」的方式实现,也可以通过「迭代递推」的方式实现。因为数位 DP 中需要考虑的参数很多,使用「记忆化搜索」的方式更加方便传入参数,所以这里我们采用「记忆化搜索」的方式来实现。 + +在使用「记忆化搜索」的时候,需要考虑的参数有: + +1. 当前枚举的数位位置($pos$)。 +2. 前一位数位(或前几位数位)的情况,比如前几位的总和($total$)、某个数字出现次数($cnt$)、前几位所选数字集合(通常使用「状态压缩」的方式,即用一个二进制整数 $state$ 来表示)等等。 +3. 前一位数位(或前几位数位)是否等于上界的前几位数字($isLimit$),用于限制本次搜索的数位范围。 +4. 前一位数位是否填了数字($isNum$),如果前一位数位填了数字,则当前位可以从 $0$ 开始填写数字;如果前一位没有填写数字,则当前位可以跳过,或者从 $1$ 开始填写数字。 +5. 当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 + +对应代码如下: + +```python +class Solution: + def digitDP(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for x in range(minX, maxX + 1): + # x 不在选择的数字集合中,即之前没有选择过 x + if (state >> x) & 1 == 0: + ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True) + return ans + + return dfs(0, 0, True, False) +``` + +接下来,我们通过一道简单的例题来具体了解一下数位 DP 以及解题思路。 + +### 1.2 统计特殊整数 + +#### 1.2.1 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:求区间 $[1, n]$ 内的所有整数中,特殊整数的数目。 + +**说明**: + +- **特殊整数**:如果一个正整数的每一个数位都是互不相同的,则称它是特殊整数。 +- $1 \le n \le 2 \times 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 20 +输出:19 +解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。 +``` + +- 示例 2: + +```python +输入:n = 5 +输出:5 +解释:1 到 5 所有整数都是特殊整数。 +``` + +#### 1.2.2 解题思路 + +##### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 + 4. 开始时没有填写数字。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:$ans = 0$。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), + 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $x$。 + 3. 如果之前没有选择 $x$,即 $x$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $x$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True)`。 + 1. `state | (1 << x)` 表示之前选择的数字集合 $state$ 加上 $x$。 + 2. `isLimit and x == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 + 3. $isNum == True$ 表示 $pos$ 位选择了数字。 + +##### 思路 1:代码 + +```python +class Solution: + def countSpecialNumbers(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for x in range(minX, maxX + 1): + # x 不在选择的数字集合中,即之前没有选择过 x + if (state >> x) & 1 == 0: + ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True) + return ans + + return dfs(0, 0, True, False) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(\log n \times 2^{10})$。 + +## 2. 数位 DP 的应用 + +### 2.1 至少有 1 位重复的数字 + +#### 2.1.1 题目链接 + +- [1012. 至少有 1 位重复的数字 - 力扣](https://leetcode.cn/problems/numbers-with-repeated-digits/) + +#### 2.1.2 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:返回在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数。 + +**说明**: + +- $1 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 20 +输出:1 +解释:具有至少 1 位重复数字的正数(<= 20)只有 11。 +``` + +- 示例 2: + +```python +输入:n = 100 +输出:10 +解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100。 +``` + +#### 2.1.3 解题思路 + +##### 思路 1:动态规划 + 数位 DP + +正向求解在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数不太容易,我们可以反向思考,先求解出在 $[1, n]$ 范围内各位数字都不重复的正整数的个数 $ans$,然后 $n - ans$ 就是题目答案。 + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 + 4. 开始时没有填写数字。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:$ans = 0$。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), + 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 + 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 + 3. $isNum == True$ 表示 $pos$ 位选择了数字。 +6. 最后的方案数为 `n - dfs(0, 0, True, False)`,将其返回即可。 + +##### 思路 1:代码 + +```python +class Solution: + def numDupDigitsAtMostN(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + # d 不在选择的数字集合中,即之前没有选择过 d + if (state >> d) & 1 == 0: + ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) + return ans + + return n - dfs(0, 0, True, False) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$。 +- **空间复杂度**:$O(\log n \times 2^{10})$。 + +### 2.2 数字 1 的个数 + +#### 2.2.1 题目链接 + +- [233. 数字 1 的个数 - 力扣](https://leetcode.cn/problems/number-of-digit-one/) + +#### 2.2.2 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算所有小于等于 $n$ 的非负整数中数字 $1$ 出现的个数。 + +**说明**: + +- $0 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 13 +输出:6 +``` + +- 示例 2: + +```python +输入:n = 0 +输出:0 +``` + +#### 2.2.3 解题思路 + +##### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $1$ 出现的个数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始数字 $1$ 出现的个数为 $0$。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $1$ 出现的个数 $cnt$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:$ans = 0$。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 + 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 + 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX)`。 + 1. `cnt + (d == 1)` 表示之前数字 $1$ 出现的个数加上当前位为数字 $1$ 的个数。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 +6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 + +##### 思路 1:代码 + +```python +class Solution: + def countDigitOne(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # cnt: 之前数字 1 出现的个数。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + def dfs(pos, cnt, isLimit): + if pos == len(s): + return cnt + + ans = 0 + # 不需要考虑前导 0,则最小可选择数字为 0 + minX = 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX) + return ans + + return dfs(0, 0, True) +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 + +## 练习题目 + +- [0357. 统计各位数字都不同的数字个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-numbers-with-unique-digits.md) +- [0788. 旋转数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotated-digits.md) +- [2719. 统计整数数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2700-2799/count-of-integers.md) + +- [数位 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%95%B0%E4%BD%8D-dp-%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[AcWing 1081. 度的数量【数位DP基本概念+数位DP记忆化搜索】](https://www.acwing.com/solution/content/66855/) +- 【视频】[数位 DP 通用模板【力扣周赛 306】LeetCode - 灵茶山艾府](https://www.bilibili.com/video/BV1rS4y1s721/) \ No newline at end of file diff --git a/docs/08_dynamic_programming/08_15_probability_dp.md b/docs/08_dynamic_programming/08_15_probability_dp.md new file mode 100644 index 00000000..bc83084d --- /dev/null +++ b/docs/08_dynamic_programming/08_15_probability_dp.md @@ -0,0 +1,78 @@ +## 1. 概率 DP 简介 + +> **概率 DP**:一类使用动态规划方法来求解概率与期望的问题,也可以分别叫做「概率 DP」、「期望 DP」。由于概率和期望具有线性性质,使得可以在概率和期望之间建立一定的递推关系,从而通过动态规划的方式来解决一些概率问题。 + +概率 DP 和期望 DP 的难点主要有两点: + +1. 状态转移方程的推导。 +2. 概率论知识。 + +其中第 $1$ 点和一般动态规划并无太大差别,而第 $2$ 点涉及到对概率论基础知识的掌握。其中,「概率 PD」对应了概率论知识中的「全概率公式」,「期望 DP」则对应了「全期望公式」。 + +我们来看看「概率 DP」「期望 DP」中所涉及到的概率论基础知识。 + +## 2. 概率论知识 + +### 2.1 样本空间、事件和概率 + +> **基本事件**:在概率论中,我们将一次随机实验中的某个可能结果称为「样本点」或者「基本事件」。 +> +> **样本空间**:所有可能的结果组成的集合,称为「样本空间」,标记为 $S$。 +> +> **随机事件**:样本空间 $S$ 的一个子集 $A$($A \subseteq S$),称为「随机事件」。 + +> **概率**:对于样本空间 $S$ 中的每一个随机事件 $A$,如果都存在一种时间到实数的映射函数 $P(A)$,满足: +> +> 1. $P(S) = 1$。 +>2. $0 \le P(A) \le 1$。 +> 3. 对于两个互斥事件,$P(A \cup B) = P(A) + P(B)$。 +> +> 则称 $P(A)$ 为随机事件 $A$ 的概率。 + +### 2.2 随机变量、数学期望 + +> **随机变量**:对于样本空间 $S$ 中的任意事件 $i$,都有唯一的实数 $X_i$ 与之对应,则称 $X = X_i$ 为样本空间 $S$ 上的随机变量。 + +常见的随机变量主要有「离散型随机变量」与「连续型随机变量」。「概率 DP」主要涉及到离散型随机变量。 + +> **数学期望**:如果随机变量 $X = X_i$ 的概率为 $P(X = X_i) = p_i$,则称 $E(x) = \sum p_ix_i$ 为随机变量 $X$ 的数学期望。 + +数学期望的一些性质: + +1. 线性函数性质,满足:$E(aX + bY) = a \times E(X) + b \times E(Y)$。 +2. 如果随机变量 $X$、$Y$ 相互独立,那么:$E(XY) = E(X)E(Y)$。 + +其中数学期望的线性函数性质是我们能够对数学期望进行地推求解的基本依据。 + +### 2.3 全概率公式、全期望公式 + +概率 DP 的理论基础主要是「全概率公式」。 + +> **全概率公式**:设 $B_1, B_2, B_3, …, B_n$ 是样本空间 $S$ 中互不相交的一系列事件,并且满足 $S = \cup_{j = 1}^n B_j$,那么对于任意事件 $A$,有: $P(A) = \sum_{j = 1}^{n} P(A \text{ | } B_j)P(B_j)$ + +「概率 DP」中一般常见的状态转移方程式为:$dp[i] = \sum_{j = 1}^{n} p[i][j] \times dp[j]$。 + +- 其中 $dp[i]$ 对应全概率公式中的 $P(A)$,$p[i][j]$ 对应了 $P(B_j)$,$dp[j]$ 则对应了 $P(A \text{ | } B_j)$。 + +与概率 DP 类似,期望 DP 的理论基础主要是「全期望公式」。 + +> **全期望公式**:设 $X$、$Y$ 为随机变量,$E(Y) = E(E(Y \text{ | } X)) = \sum_{j = 1}^n P(x_j) E(Y \text{ | } x_j)$。 + +「期望 DP」中一般常见的状态转移方程式为:$dp[i] = \sum_{j = 1}^{n} p[i][j] \times dp[j]$。 + +- 其中 $dp[i]$ 对应全概率公式中的 $E(Y)$,$p[i][j]$ 对应了 $P(x_j)$,$dp[j]$ 则对应了 $E(Y \text{ | } x_j)$。 + +## 练习题目 + +- [0688. 骑士在棋盘上的概率](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/knight-probability-in-chessboard.md) +- [1227. 飞机座位分配概率](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/airplane-seat-assignment-probability.md) + +- [概率 DP 题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E6%A6%82%E7%8E%87-dp-%E9%A2%98%E7%9B%AE) + +## 参考资料 + +- 【文章】[概率动态规划简介 - 动态规划图文学: 树形、图上、概率 & 博弈动态 - LeetBook](https://leetcode.cn/leetbook/read/dynamic-programming-3-plus/nmnp61/) +- 【文章】[期望动态规划简介 - 动态规划图文学: 树形、图上、概率 & 博弈动态 - LeetBook](https://leetcode.cn/leetbook/read/dynamic-programming-3-plus/nmwjt6/) +- 【文章】浅析竞赛中一类数学期望问题的解决方法 - 汤可因 +- 【文章】有关概率和期望问题的研究 - 鬲融 +- 【文章】信息学竞赛中概率问题求解初探 - 梅诗珂 \ No newline at end of file diff --git a/docs/08_dynamic_programming/index.md b/docs/08_dynamic_programming/index.md new file mode 100644 index 00000000..a029dc20 --- /dev/null +++ b/docs/08_dynamic_programming/index.md @@ -0,0 +1,17 @@ +## 本章内容 + +- [8.1 动态规划基础](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/01.Dynamic-Programming-Basic/01.Dynamic-Programming-Basic.md) +- [8.2 记忆化搜索](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/02.Memoization/01.Memoization.md) +- [8.3 线性 DP(一)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/03.Linear-DP/01.Linear-DP-01.md) +- [8.4 线性 DP(二)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-02.md) +- [8.5 背包问题知识(一)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/01.Knapsack-Problem-01.md) +- [8.6 背包问题知识(二)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md) +- [8.7 背包问题知识(三)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/03.Knapsack-Problem-03.md) +- [8.8 背包问题知识(四)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md) +- [8.9 背包问题知识(五)](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md) +- [8.10 区间 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md) +- [8.11 树形 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/06.Tree-DP/01.Tree-DP.md) +- [8.12 状态压缩 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/07.State-DP/01.State-DP.md) +- [8.13 计数 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/08.Counting-DP/01.Counting-DP.md) +- [8.14 数位 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/09.Digit-DP/01.Digit-DP.md) +- [8.15 概率 DP](https://github.com/ITCharge/AlgoNote/tree/main/Contents/10.Dynamic-Programming/10.Probability-DP/01.Probability-DP.md) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..752473e7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,67 @@ +## 1. 本书简介 + +本书不仅仅只是一本算法题解书,更是一本算法与数据结构基础知识的讲解书。 + +- 超详细的 **「算法与数据结构」** 基础讲解教程,**「LeetCode 800+ 道」** 经典题目详细解析。 +- 本项目易于理解,没有大跨度的思维跳跃,项目中使用大量图示、例子来帮助理解。 +- 本项目先从基础的数据结构和算法开始讲解,再针对不同分类的数据结构和算法,进行具体题目的讲解分析。让读者可以通过「算法基础理论学习」和「编程实战学习」相结合的方式,彻底的掌握算法知识。 +- 本项目从各大知名互联网公司面试算法题中整理汇总了 **「LeetCode 200 道高频面试题」**,帮助面试者更有针对性的准备面试。 + +### 1.1 源码地址 + +本书内容及代码都放在 [Github repo](https://github.com/itcharge/AlgoNote) 中,欢迎在下方项目中 **「Star ⭐️ 」** 和 **「Fork」**,这是对我最大的鼓励和支持。 + +- Github 地址:[https://github.com/itcharge/AlgoNote](https://github.com/itcharge/AlgoNote) + +### 1.2 目标读者 + +- 拥有 Python 编程基础或其他编程语言基础的编程爱好者 +- 对 LeetCode 刷题感兴趣或准备算法面试的面试人员 +- 对算法感兴趣的计算机专业学生或程序员 +- 想要提升编程思维和问题解决能力的开发者 + +### 1.3 内容结构 + +本书采用算法与数据结构相结合的方法,把内容分为如下几个主要部分: + +- **0. 序言**:介绍数据结构与算法的基础知识、算法复杂度、LeetCode 的入门和攻略,为后面的学习打好基础。 +- **1. 数组**:讲解数组的基本概念、数组的基本操作。 +- **2. 链表**:讲解链表的基本概念、操作和应用,包括单链表、双向链表、循环链表等。 +- **3. 栈、队列、哈希表**:详细介绍栈、队列、哈希表这三种数据结构,包括它们的基本概念、实现方式、应用场景以及相关的经典算法题。 +- **4. 字符串**:讲解字符串的基本操作、单字符串匹配算法、多字符串匹配算法,以及字符串相关的经典算法题。 +- **5. 树结构**:介绍树的基本概念、二叉树、二叉搜索树、线段树、树状数组、并查集等数据结构。 +- **6. 图论**:讲解图的基本概念、表示方法、遍历算法和经典应用。 +- **7. 基础算法**:介绍基本的算法思想。包括枚举、递归、分治、回溯、贪心以及位运算。 +- **8. 动态规划**:介绍动态规划的基础知识、各种动态规划题型的解法。 +- **9. 附加内容**:作为全书的扩展模块。 +- **10. 题目解析**:讲解 LeetCode 上刷过的所有题目,可按照对应题号进行检索和学习。 + +### 1.4 使用说明 + +- 本电子书的左侧为所有章节目录导航,可直接点击对应章节跳转阅读。 +- 本电子书左上角有搜索栏,可以帮你迅速找到想看的章节和题解文章。 +- 本电子书每页都接入了 giscus 评论系统,可在每页下方的评论框进行评论(需使用 GitHub 账号登录)。 +- 建议按照章节顺序学习,循序渐进地掌握各个知识点。 +- 每章末尾都配有练习题,建议及时完成以巩固所学知识。 + +## 2. 相关说明 + +### 2.1 关于作者 + +我是一名 iOS / macOS 的开发程序员,研究生毕业于北航软件学院。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 + +我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 800+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 + +### 2.2 互助与勘误 + +限于本人的水平和经验,书中一定不乏纰漏和谬误之处。恳切希望读者给予批评指正。这将有利于我改进和提高,以帮助更多的读者。如果您对本书有任何评论和建议,或者遇到问题需要帮助,可在每页评论区留言,或者致信作者邮箱 [i@itcharge.cn](mailto:i@itcharge.cn),我将不胜感激。 + +### 2.3 版权说明 + +- 本书采用 [知识署名—非商业性使用—禁止演绎(BY-NC-ND)4.0 协议国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.zh-Hans) 进行许可。 +- 本书题解中的所有题目版权均归 [LeetCode](https://leetcode.com/) 和 [力扣中国](https://leetcode.cn/) 所有。 + +### 2.4 致谢 + +在本书构思与写作阶段,很多朋友给我提出了有益的意见和建议。这些意见和建议令我受益匪浅。感谢在本书著作准备过程中,帮助过我的朋友,以及一起陪我刷题打卡的朋友,还有提供宝贵意见的读者。谢谢诸位。 + diff --git a/docs/others/update_time.md b/docs/others/update_time.md new file mode 100644 index 00000000..16ec7759 --- /dev/null +++ b/docs/others/update_time.md @@ -0,0 +1,145 @@ +## 2023-12 + +- 2023-12-26 完成「全部题目解析」的题目链接优化 + +## 2023-09 + +- 2023-09-07 完成「滑动窗口」内容优化 +- 2023-09-06 完成「数组双指针」内容优化 +- 2023-09-05 完成「广度优先搜索」内容优化 +- 2023-09-05 完成「深度优先搜索」内容优化 + +## 2023-08 + +- 2023-08-31 完成「堆排序」内容优化 +- 2023-08-24 完成「数组基础知识」内容优化 +- 2023-08-22 完成「基数排序」内容优化 +- 2023-08-22 完成「桶排序」内容优化 +- 2023-08-22 完成「计数排序」内容优化 +- 2023-08-18 完成「快速排序」内容优化 +- 2023-08-17 完成「归并排序」内容优化 +- 2023-08-16 完成「希尔排序」内容优化 +- 2023-08-16 完成「插入排序」内容优化 +- 2023-08-16 完成「选择排序」内容优化 +- 2023-08-16 完成「冒泡排序」内容优化 +- 2023-08-15 完成「序言」内容优化 + +## 2023-07 + +- 2023-07-17 完成「树形 DP」相关内容 + +## 2023-06 + +- 2023-06-29 完成「数位 DP」相关内容 +- 2023-06-02 完成「状态压缩 DP」相关内容 + +## 2023-05 + +- 2023-05-31 完成「计数类 DP」相关内容 +- 2023-05-08 完成「图的拓扑排序知识」相关内容 + +## 2023-04 + +- 2023-04-07 完成「区间 DP」相关内容 + +## 2023-03 + +- 2023-03-29 完成「背包问题知识(五)」相关内容 +- 2023-03-29 完成「背包问题知识(四)」相关内容 +- 2023-03-22 完成「背包问题知识(三)」相关内容 +- 2023-03-21 完成「背包问题知识(二)」相关内容 +- 2023-03-21 完成「背包问题知识(一)」相关内容 +- 2023-03-14 完成「线性 DP 知识(二)」相关内容 +- 2023-03-14 完成「线性 DP 知识(一)」相关内容 +- 2023-03-08 完成「位运算知识」相关内容 +- 2023-03-08 完成「动态规划基础知识(重写版)」相关内容 +- 2023-03-06 完成「记忆化搜索」相关内容 + +## 2022-07 + +- 2022-07-21 完成「动态规划基础知识」相关内容 + +## 2022-06 + +- 2022-06-13 完成「LeetCode 面试最常考 200 题」相关内容 +- 2022-06-10 完成「LeetCode 面试最常考 100 题」相关内容 + +## 2022-05 + +- 2022-05-11 完成「贪心算法知识」相关内容 +- 2022-05-02 完成「并查集知识」相关内容 + +## 2022-04 + +- 2022-04-26 完成「回溯算法知识」相关内容 +- 2022-04-14 完成「分治算法知识」相关内容 +- 2022-04-08 完成「递归算法知识」相关内容 + +## 2022-03 + +- 2022-03-29 完成「枚举算法知识」相关内容 +- 2022-03-18 完成「图的存储结构和问题应用」相关内容 +- 2022-03-13 完成「图的定义和分类」相关内容 +- 2022-03-03 完成「线段树知识」相关内容 + +## 2022-02 + +- 2022-02-28 完成「二叉搜索树」相关内容 +- 2022-02-23 完成「二叉树的还原」相关内容 +- 2022-02-22 完成「二叉树的遍历」相关内容 +- 2022-02-21 完成「树与二叉树基础知识」相关内容 +- 2022-02-05 完成「KMP 算法」相关内容 + +## 2022-01 + +- 2022-01-28 完成「Sunday 算法」相关内容 +- 2022-01-28 完成「Horspool 算法」相关内容 +- 2022-01-27 完成「BM 算法」相关内容 +- 2022-01-21 完成「RK 算法」相关内容 +- 2022-01-20 完成「BK 算法」相关内容 +- 2022-01-19 完成「字符串基础知识」相关内容 +- 2022-01-15 完成「哈希表基础知识」相关内容 +- 2022-01-09 完成「优先队列」相关内容 +- 2022-01-06 完成「单调栈」相关内容 + +## 2022-12 + +- 2022-12-21 完成「广度优先搜索」相关内容 +- 2022-12-14 完成「深度优先搜索」相关内容 +- 2022-12-12 完成「链表双指针」相关内容 +- 2022-12-10 完成「链表排序」相关内容 +- 2022-12-04 完成「队列基础知识」相关内容 +- 2022-12-04 完成「栈基础知识」相关内容 + +## 2021-11 + +- 2021-11-08 完成「滑动窗口」相关内容 +- 2021-11-08 完成「双指针」相关内容 +- 2021-11-04 完成「二分查找」相关内容 + +## 2021-10 + +- 2021-10-20 完成「基数排序」相关内容 +- 2021-10-20 完成「桶排序」相关内容 +- 2021-10-20 完成「计数排序」相关内容 +- 2021-10-19 完成「堆排序」相关内容 +- 2021-10-19 完成「快速排序」相关内容 +- 2021-10-19 完成「归并排序」相关内容 +- 2021-10-19 完成「希尔排序」相关内容 +- 2021-10-19 完成「插入排序」相关内容 +- 2021-10-19 完成「选择排序」相关内容 +- 2021-10-19 完成「冒牌排序」相关内容 + +## 2021-09 + +- 2021-09-17 完成「数组基础知识」相关内容 +- 2021-09-09 完成「算法复杂度」相关内容 + +## 2021-07 + +- 2021-07-05 完成「数据结构与算法」相关内容 + +## 2021-01 + +- 2021-01-26 完成「LeetCode 刷题顺序和技巧」相关内容 +- 2021-01-22 开始建立仓库 \ No newline at end of file diff --git a/docs/solutions/0001-0099/3sum-closest.md b/docs/solutions/0001-0099/3sum-closest.md new file mode 100644 index 00000000..02665284 --- /dev/null +++ b/docs/solutions/0001-0099/3sum-closest.md @@ -0,0 +1,82 @@ +# [0016. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0016. 最接近的三数之和 - 力扣](https://leetcode.cn/problems/3sum-closest/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和 一个目标值 $target$。 + +**要求**:从 $nums$ 中选出三个整数,使它们的和与 $target$ 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。 + +**说明**: + +- $3 \le nums.length \le 1000$。 +- $-1000 \le nums[i] \le 1000$。 +- $-10^4 \le target \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [-1,2,1,-4], target = 1 +输出:2 +解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)。 +``` + +- 示例 2: + +```python +输入:nums = [0,0,0], target = 1 +输出:0 +``` + +## 解题思路 + +### 思路 1:对撞指针 + +直接暴力枚举三个数的时间复杂度是 $O(n^3)$。很明显的容易超时。考虑使用双指针减少循环内的时间复杂度。具体做法如下: + +- 先对数组进行从小到大排序,使用 $ans$ 记录最接近的三数之和。 +- 遍历数组,对于数组元素 $nums[i]$,使用两个指针 $left$、$right$。$left$ 指向第 $0$ 个元素位置,$right$ 指向第 $i - 1$ 个元素位置。 +- 计算 $nums[i]$、$nums[left]$、$nums[right]$ 的和与 $target$ 的差值,将其与 $ans$ 与 $target$ 的差值作比较。如果差值小,则更新 $ans$。 + - 如果 $nums[i] + nums[left] + nums[right] < target$,则说明 $left$ 小了,应该将 $left$ 右移,继续查找。 + - 如果 $nums[i] + nums[left] + nums[right] \ge target$,则说明 $right$ 太大了,应该将 $right$ 左移,然后继续判断。 +- 当 $left == right$ 时,区间搜索完毕,继续遍历 $nums[i + 1]$。 +- 最后输出 $ans$。 + +这种思路使用了两重循环,其中内层循环当 $left == right$ 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$。 + +### 思路 1:代码 + +```python +class Solution: + def threeSumClosest(self, nums: List[int], target: int) -> int: + nums.sort() + res = float('inf') + size = len(nums) + for i in range(2, size): + left = 0 + right = i - 1 + while left < right: + total = nums[left] + nums[right] + nums[i] + if abs(total - target) < abs(res - target): + res = total + if total < target: + left += 1 + else: + right -= 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组中元素的个数。 +- **空间复杂度**:$O(\log n)$,排序需要 $\log n$ 的栈空间。 + diff --git a/docs/solutions/0001-0099/3sum.md b/docs/solutions/0001-0099/3sum.md new file mode 100644 index 00000000..b132c338 --- /dev/null +++ b/docs/solutions/0001-0099/3sum.md @@ -0,0 +1,85 @@ +# [0015. 三数之和](https://leetcode.cn/problems/3sum/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0015. 三数之和 - 力扣](https://leetcode.cn/problems/3sum/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:判断 $nums$ 中是否存在三个元素 $a$、$b$、$c$,满足 $a + b + c == 0$。要求找出所有满足要求的不重复的三元组。 + +**说明**: + +- $3 \le nums.length \le 3000$。 +- $-10^5 \le nums[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [-1,0,1,2,-1,-4] +输出:[[-1,-1,2],[-1,0,1]] +``` + +- 示例 2: + +```python +输入:nums = [0,1,1] +输出:[] +``` + +## 解题思路 + +### 思路 1:对撞指针 + +直接三重遍历查找 $a$、$b$、$c$ 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 + +先将数组进行排序,以保证按顺序查找 $a$、$b$、$c$ 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(n \times \log n)$。 + +第一重循环遍历 $a$,对于每个 $a$ 元素,从 $a$ 元素的下一个位置开始,使用对撞指针 $left$,$right$。$left$ 指向 $a$ 元素的下一个位置,$right$ 指向末尾位置。先将 $left$ 右移、$right$ 左移去除重复元素,再进行下边的判断。 + +1. 如果 $nums[a] + nums[left] + nums[right] == 0$,则得到一个解,将其加入答案数组中,并继续将 $left$ 右移,$right$ 左移; +2. 如果 $nums[a] + nums[left] + nums[right] > 0$,说明 $nums[right]$ 值太大,将 $right$ 向左移; +3. 如果 $nums[a] + nums[left] + nums[right] < 0$,说明 $nums[left]$ 值太小,将 $left$ 右移。 + +### 思路 1:代码 + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + n = len(nums) + nums.sort() + ans = [] + + for i in range(n): + if i > 0 and nums[i] == nums[i - 1]: + continue + left = i + 1 + right = n - 1 + while left < right: + while left < right and left > i + 1 and nums[left] == nums[left - 1]: + left += 1 + while left < right and right < n - 1 and nums[right + 1] == nums[right]: + right -= 1 + if left < right and nums[i] + nums[left] + nums[right] == 0: + ans.append([nums[i], nums[left], nums[right]]) + left += 1 + right -= 1 + elif nums[i] + nums[left] + nums[right] > 0: + right -= 1 + else: + left += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0001-0099/4sum.md b/docs/solutions/0001-0099/4sum.md new file mode 100644 index 00000000..cd1eb79c --- /dev/null +++ b/docs/solutions/0001-0099/4sum.md @@ -0,0 +1,93 @@ +# [0018. 四数之和](https://leetcode.cn/problems/4sum/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0018. 四数之和 - 力扣](https://leetcode.cn/problems/4sum/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个目标值 $target$。 + +**要求**:找出所有满足以下条件切不重复的四元组。 + +1. $0 \le a, b, c, d < n$。 +2. $a$、$b$、$c$ 和 $d$ 互不相同。 +3. $nums[a] + nums[b] + nums[c] + nums[d] == target$。 + +**说明**: + +- $1 \le nums.length \le 200$。 +- $-10^9 \le nums[i] \le 10^9$。 +- $-10^9 \le target \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,0,-1,0,-2,2], target = 0 +输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] +``` + +- 示例 2: + +```python +输入:nums = [2,2,2,2,2], target = 8 +输出:[[2,2,2,2]] +``` + +## 解题思路 + +### 思路 1:排序 + 双指针 + +和 [0015. 三数之和](https://leetcode.cn/problems/3sum/) 解法类似。 + +直接三重遍历查找 $a$、$b$、$c$、$d$ 的时间复杂度是:$O(n^4)$。我们可以通过一些操作来降低复杂度。 + +1. 先将数组进行排序,以保证按顺序查找 $a$、$b$、$c$、$d$ 时,元素值为升序,从而保证所找到的四个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。这一步的时间复杂度为:$O(n \times \log n)$。 +2. 两重循环遍历元素 $a$、$b$,对于每个 $a$ 元素,从 $a$ 元素的下一个位置开始遍历元素 $b$。对于元素 $a$、$b$,使用双指针 $left$,$right$ 来查找 $c$、$d$。$left$ 指向 $b$ 元素的下一个位置,$right$ 指向末尾位置。先将 $left$ 右移、$right$ 左移去除重复元素,再进行下边的判断。 + 1. 如果 $nums[a] + nums[b] + nums[left] + nums[right] == target$,则得到一个解,将其加入答案数组中,并继续将 $left$ 右移,$right$ 左移; + 2. 如果 $nums[a] + nums[b] + nums[left] + nums[right] > target$,说明 $nums[right]$ 值太大,将 $right$ 向左移; + 3. 如果 $nums[a] + nums[b] + nums[left] + nums[right] < target$,说明 $nums[left]$ 值太小,将 $left$ 右移。 + +### 思路 1:代码 + +```python +class Solution: + def fourSum(self, nums: List[int], target: int) -> List[List[int]]: + n = len(nums) + nums.sort() + ans = [] + + for i in range(n): + if i > 0 and nums[i] == nums[i-1]: + continue + for j in range(i+1, n): + if j > i+1 and nums[j] == nums[j-1]: + continue + left = j + 1 + right = n - 1 + while left < right: + while left < right and left > j + 1 and nums[left] == nums[left - 1]: + left += 1 + while left < right and right < n - 1 and nums[right + 1] == nums[right]: + right -= 1 + if left < right and nums[i] + nums[j] + nums[left] + nums[right] == target: + ans.append([nums[i], nums[j], nums[left], nums[right]]) + left += 1 + right -= 1 + elif nums[i] + nums[j] + nums[left] + nums[right] > target: + right -= 1 + else: + left += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为数组中元素个数。 +- **空间复杂度**:$O(\log n)$,排序额外使用空间为 $\log n$。 + diff --git a/docs/solutions/0001-0099/add-binary.md b/docs/solutions/0001-0099/add-binary.md new file mode 100644 index 00000000..29a80de6 --- /dev/null +++ b/docs/solutions/0001-0099/add-binary.md @@ -0,0 +1,48 @@ +# [0067. 二进制求和](https://leetcode.cn/problems/add-binary/) + +- 标签:位运算、数学、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [0067. 二进制求和 - 力扣](https://leetcode.cn/problems/add-binary/) + +## 题目大意 + +给定两个二进制数的字符串 a、b。计算 a 和 b 的和,返回结果也用二进制表示。 + +## 解题思路 + +这道题可以直接将 a、b 转换为十进制数,相加后再转换为二进制数。 + +也可以利用位运算的一些知识,直接求和。 + +因为 a、b 为二进制的字符串,先将其转换为二进制数。 + +本题用到的位运算知识: + +- 异或运算 x ^ y :可以获得 x + y 无进位的加法结果。 +- 与运算 x & y:对应位置为 1,说明 x、y 该位置上原来都为 1,则需要进位。 +- 座椅运算 x << 1:将 a 对应二进制数左移 1 位。 + +这样,通过 x ^ y 运算,我们可以得到相加后无进位结果,再根据 (x & y) << 1,计算进位后结果。 + +进行 x ^ y 和 (x & y) << 1操作之后判断进位是否为 0,若不为 0,则继续上一步操作,直到进位为 0。 + +最后将其结果转为 2 进制返回。 + +## 代码 + +```python +class Solution: + def addBinary(self, a: str, b: str) -> str: + x = int(a, 2) + y = int(b, 2) + ans = 0 + while y: + carry = ((x & y) << 1) + x ^= y + y = carry + return bin(x)[2:] +``` + diff --git a/docs/solutions/0001-0099/add-two-numbers.md b/docs/solutions/0001-0099/add-two-numbers.md new file mode 100644 index 00000000..81ba2e09 --- /dev/null +++ b/docs/solutions/0001-0099/add-two-numbers.md @@ -0,0 +1,78 @@ +# [0002. 两数相加](https://leetcode.cn/problems/add-two-numbers/) + +- 标签:递归、链表、数学 +- 难度:中等 + +## 题目链接 + +- [0002. 两数相加 - 力扣](https://leetcode.cn/problems/add-two-numbers/) + +## 题目大意 + +**描述**:给定两个非空的链表 `l1` 和 `l2`。分别用来表示两个非负整数,每位数字都是按照逆序的方式存储的,每个节点存储一位数字。 + +**要求**:计算两个非负整数的和,并逆序返回表示和的链表。 + +**说明**: + +- 每个链表中的节点数在范围 $[1, 100]$ 内。 +- $0 \le Node.val \le 9$。 +- 题目数据保证列表表示的数字不含前导零。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/01/02/addtwonumber1.jpg) + +```python +输入:l1 = [2,4,3], l2 = [5,6,4] +输出:[7,0,8] +解释:342 + 465 = 807. +``` + +- 示例 2: + +```python +输入:l1 = [0], l2 = [0] +输出:[0] +``` + +## 解题思路 + +### 思路 1:模拟 + +模拟大数加法,按位相加,将结果添加到新链表上。需要注意进位和对 $10$ 取余。 + +### 思路 1:代码 + +```python +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + head = curr = ListNode(0) + carry = 0 + while l1 or l2 or carry: + if l1: + num1 = l1.val + l1 = l1.next + else: + num1 = 0 + if l2: + num2 = l2.val + l2 = l2.next + else: + num2 = 0 + + sum = num1 + num2 + carry + carry = sum // 10 + + curr.next = ListNode(sum % 10) + curr = curr.next + + return head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(max(m, n))$。其中,$m$ 和 $n$ 分别是链表 `l1` 和 `l2` 的长度。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/binary-tree-inorder-traversal.md b/docs/solutions/0001-0099/binary-tree-inorder-traversal.md new file mode 100644 index 00000000..936ef32f --- /dev/null +++ b/docs/solutions/0001-0099/binary-tree-inorder-traversal.md @@ -0,0 +1,115 @@ +# [0094. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) + +- 标签:栈、树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0094. 二叉树的中序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-inorder-traversal/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:返回该二叉树的中序遍历结果。 + +**说明**: + +- 树中节点数目在范围 $[0, 100]$ 内。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) + +```python +输入:root = [1,null,2,3] +输出:[1,3,2] +``` + +- 示例 2: + +```python +输入:root = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +二叉树的中序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先递归遍历左子树。 +3. 然后访问根节点。 +4. 最后递归遍历右子树。 + +### 思路 1:代码 + +```python +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def inorder(root): + if not root: + return + inorder(root.left) + res.append(root.val) + inorder(root.right) + + inorder(root) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:模拟栈迭代遍历 + +我们可以使用一个显式栈 $stack$ 来模拟二叉树的中序遍历递归的过程。 + +与前序遍历不同,访问根节点要放在左子树遍历完之后。因此我们需要保证:**在左子树访问之前,当前节点不能提前出栈**。 + +我们应该从根节点开始,循环遍历左子树,不断将当前子树的根节点放入栈中,直到当前节点无左子树时,从栈中弹出该节点并进行处理。 + +然后再访问该元素的右子树,并进行上述循环遍历左子树的操作。这样可以保证最终遍历顺序为中序遍历顺序。 + +二叉树的中序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个空栈。 +3. 当根节点或者栈不为空时: + 1. 如果当前节点不为空,则循环遍历左子树,并不断将当前子树的根节点入栈。 + 1. 如果当前节点为空,说明当前节点无左子树,则弹出栈顶元素 $node$,并访问该元素,然后尝试访问该节点的右子树。 + +### 思路 2:代码 + +```python +class Solution: + def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + if not root: # 二叉树为空直接返回 + return [] + + res = [] + stack = [] + + while root or stack: # 根节点或栈不为空 + while root: + stack.append(root) # 将当前树的根节点入栈 + root = root.left # 找到最左侧节点 + + node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 + res.append(node.val) # 访问该节点 + root = node.right # 尝试访问该节点的右子树 + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/climbing-stairs.md b/docs/solutions/0001-0099/climbing-stairs.md new file mode 100644 index 00000000..4084516d --- /dev/null +++ b/docs/solutions/0001-0099/climbing-stairs.md @@ -0,0 +1,117 @@ +# [0070. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) + +- 标签:记忆化搜索、数学、动态规划 +- 难度:简单 + +## 题目链接 + +- [0070. 爬楼梯 - 力扣](https://leetcode.cn/problems/climbing-stairs/) + +## 题目大意 + +**描述**:假设你正在爬楼梯。需要 $n$ 阶你才能到达楼顶。每次你可以爬 $1$ 或 $2$ 个台阶。现在给定一个整数 $n$。 + +**要求**:计算出有多少种不同的方法可以爬到楼顶。 + +**说明**: + +- $1 \le n \le 45$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:2 +解释:有两种方法可以爬到楼顶。 +1. 1 阶 + 1 阶 +2. 2 阶 +``` + +- 示例 2: + +```python +输入:n = 3 +输出:3 +解释:有三种方法可以爬到楼顶。 +1. 1 阶 + 1 阶 + 1 阶 +2. 1 阶 + 2 阶 +3. 2 阶 + 1 阶 +``` + +## 解题思路 + +### 思路 1:递归(超时) + +根据我们的递推三步走策略,写出对应的递归代码。 + +1. 写出递推公式:$f(n) = f(n - 1) + f(n - 2)$。 +2. 明确终止条件:$f(0) = 0, f(1) = 1$。 +3. 翻译为递归代码: + 1. 定义递归函数:`climbStairs(self, n)` 表示输入参数为问题的规模 $n$,返回结果为爬 $n$ 阶台阶到达楼顶的方案数。 + 2. 书写递归主体:`return self.climbStairs(n - 1) + self.climbStairs(n - 2)`。 + 3. 明确递归终止条件: + 1. `if n == 0: return 0` + 2. `if n == 1: return 1` + +### 思路 1:代码 + +```python +class Solution: + def climbStairs(self, n: int) -> int: + if n == 1: + return 1 + if n == 2: + return 2 + return self.climbStairs(n - 1) + self.climbStairs(n - 2) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。 +- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 + +### 思路 2:动态规划 + +###### 1. 划分阶段 + +按照台阶的层数进行划分为 $0 \sim n$。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:爬到第 $i$ 阶台阶的方案数。 + +###### 3. 状态转移方程 + +根据题目大意,每次只能爬 $1$ 或 $2$ 个台阶。则第 $i$ 阶楼梯只能从第 $i - 1$ 阶向上爬 $1$ 阶上来,或者从第 $i - 2$ 阶向上爬 $2$ 阶上来。所以可以推出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 + +###### 4. 初始条件 + +- 第 $0$ 层台阶方案数:可以看做 $1$ 种方法(从 $0$ 阶向上爬 $0$ 阶),即 $dp[0] = 1$。 +- 第 $1$ 层台阶方案数:$1$ 种方法(从 $0$ 阶向上爬 $1$ 阶),即 $dp[1] = 1$。 +- 第 $2$ 层台阶方案数:$2$ 种方法(从 $0$ 阶向上爬 $2$ 阶,或者从 $1$ 阶向上爬 $1$ 阶)。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[n]$,即爬到第 $n$ 阶台阶(即楼顶)的方案数为 $dp[n]$。 + +### 思路 2:代码 + +```python +class Solution: + def climbStairs(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + dp[0] = 1 + dp[1] = 1 + for i in range(2, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + + return dp[n] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 + diff --git a/docs/solutions/0001-0099/combination-sum-ii.md b/docs/solutions/0001-0099/combination-sum-ii.md new file mode 100644 index 00000000..53a35027 --- /dev/null +++ b/docs/solutions/0001-0099/combination-sum-ii.md @@ -0,0 +1,92 @@ +# [0040. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0040. 组合总和 II - 力扣](https://leetcode.cn/problems/combination-sum-ii/) + +## 题目大意 + +**描述**:给定一个数组 `candidates` 和一个目标数 `target`。 + +**要求**:找出 `candidates` 中所有可以使数字和为目标数 `target` 的组合。 + +**说明**: + +- 数组 `candidates` 中的数字在每个组合中只能使用一次。 +- $1 \le candidates.length \le 100$。 +- $1 \le candidates[i] \le 50$。 + +**示例**: + +- 示例 1: + +```python +输入: candidates = [10,1,2,7,6,1,5], target = 8, +输出: +[ +[1,1,6], +[1,2,5], +[1,7], +[2,6] +] +``` + +- 示例 2: + +```python +输入: candidates = [2,5,2,1,2], target = 5, +输出: +[ +[1,2,2], +[5] +] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +跟「[0039. 组合总和](https://leetcode.cn/problems/combination-sum/)」不一样的地方在于本题不能有重复组合,所以关键步骤在于去重。 + +在回溯遍历的时候,下一层递归的 `start_index` 要从当前节点的后一位开始遍历,即 `i + 1` 位开始。而且统一递归层不能使用相同的元素,即需要增加一句判断 `if i > start_index and candidates[i] == candidates[i - 1]: continue`。 + +### 思路 1:代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): + if sum > target: + return + if sum == target: + self.res.append(self.path[:]) + return + + for i in range(start_index, len(candidates)): + if sum + candidates[i] > target: + break + if i > start_index and candidates[i] == candidates[i - 1]: + continue + sum += candidates[i] + self.path.append(candidates[i]) + self.backtrack(candidates, target, sum, i + 1) + sum -= candidates[i] + self.path.pop() + + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + self.res.clear() + self.path.clear() + candidates.sort() + self.backtrack(candidates, target, 0, 0) + return self.res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 `candidates` 的元素个数,$2^n$ 指的是所有状态数。 +- **空间复杂度**:$O(target)$,递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $O(target)$,所以空间复杂度为 $O(target)$。 + diff --git a/docs/solutions/0001-0099/combination-sum.md b/docs/solutions/0001-0099/combination-sum.md new file mode 100644 index 00000000..fe3ae34f --- /dev/null +++ b/docs/solutions/0001-0099/combination-sum.md @@ -0,0 +1,130 @@ +# [0039. 组合总和](https://leetcode.cn/problems/combination-sum/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0039. 组合总和 - 力扣](https://leetcode.cn/problems/combination-sum/) + +## 题目大意 + +**描述**:给定一个无重复元素的正整数数组 `candidates` 和一个正整数 `target`。 + +**要求**:找出 `candidates` 中所有可以使数字和为目标数 `target` 的所有不同组合,并以列表形式返回。可以按照任意顺序返回这些组合。 + +**说明**: + +- 数组 `candidates` 中的数字可以无限重复选取。 +- 如果至少一个数字的被选数量不同,则两种组合是不同的。 +- $1 \le candidates.length \le 30$。 +- $2 \le candidates[i] \le 40$。 +- `candidates` 的所有元素互不相同。 +- $1 \le target \le 40$。 + +**示例**: + +- 示例 1: + +```python +输入:candidates = [2,3,6,7], target = 7 +输出:[[2,2,3],[7]] +解释: +2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 +7 也是一个候选, 7 = 7 。 +仅有这两种组合。 +``` + +- 示例 2: + +```python +输入: candidates = [2,3,5], target = 8 +输出: [[2,2,2,2],[2,3,3],[3,5]] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +定义回溯方法,start_index = 1 开始进行回溯。 + +- 如果 `sum > target`,则直接返回。 +- 如果 `sum == target`,则将 path 中的元素加入到 res 数组中。 +- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 + - 如果 `sum + candidates[i] > target`,可以直接跳出循环。 + - 将和累积,即 `sum += candidates[i]`,然后将当前元素 i 加入 path 数组。 + - 递归遍历 `[start_index, n]` 上的数。 + - 加之前的和回退,即 `sum -= candidates[i]`,然后将遍历的 i 元素进行回退。 +- 最终返回 res 数组。 + +根据回溯算法三步走,写出对应的回溯算法。 + +1. **明确所有选择**:一个组合每个位置上的元素都可以从剩余可选元素中选出。 + +2. **明确终止条件**: + + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 + +3. **将决策树和终止条件翻译成代码:** + + 1. 定义回溯函数: + + - `backtrack(total, start_index):` 函数的传入参数是 `total`(当前和)、`start_index`(剩余可选元素开始位置),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 + - `backtrack(total, start_index):` 函数代表的含义是:当前组合和为 `total`,递归从 `candidates` 的 `start_index` 位置开始,选择剩下的元素。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: + - 约束条件:之前已经选择的元素不再重复选用,只能从剩余元素中选择。 + - 选择元素:将其添加到当前数组 `path` 中。 + - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 + - 撤销选择:将该元素从当前结果数组 `path` 中移除。 + + ```python + for i in range(start_index, len(candidates)): + if total + candidates[i] > target: + break + + total += candidates[i] + path.append(candidates[i]) + backtrack(total, i) + total -= candidates[i] + path.pop() + ``` + + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当不可能再出现解(`total > target`),或者遍历到决策树的叶子节点时(`total == target`)时,就终止了。 + - 当遍历到决策树的叶子节点时(`total == target`)时,将当前结果的数组 `path` 放入答案数组 `res` 中,递归停止。 + +### 思路 1:代码 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + path = [] + def backtrack(total, start_index): + if total > target: + return + + if total == target: + res.append(path[:]) + return + + for i in range(start_index, len(candidates)): + if total + candidates[i] > target: + break + + total += candidates[i] + path.append(candidates[i]) + backtrack(total, i) + total -= candidates[i] + path.pop() + candidates.sort() + backtrack(0, 0) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 `candidates` 的元素个数,$2^n$ 指的是所有状态数。 +- **空间复杂度**:$O(target)$,递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $O(target)$,所以空间复杂度为 $O(target)$。 + diff --git a/docs/solutions/0001-0099/combinations.md b/docs/solutions/0001-0099/combinations.md new file mode 100644 index 00000000..7fc54b4d --- /dev/null +++ b/docs/solutions/0001-0099/combinations.md @@ -0,0 +1,48 @@ +# [0077. 组合](https://leetcode.cn/problems/combinations/) + +- 标签:回溯 +- 难度:中等 + +## 题目链接 + +- [0077. 组合 - 力扣](https://leetcode.cn/problems/combinations/) + +## 题目大意 + +给定两个整数 `n` 和 `k`,返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。可以按任何顺序返回答案。 + +## 解题思路 + +组合问题通常可以用回溯算法来解决。定义两个数组 res、path。res 用来存放最终答案,path 用来存放当前符合条件的一个结果。再使用一个变量 start_index 来表示从哪一个数开始遍历。 + +定义回溯方法,start_index = 1 开始进行回溯。 + +- 如果 path 数组的长度等于 k,则将 path 中的元素加入到 res 数组中。 +- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 + - 将当前元素 i 加入 path 数组。 + - 递归遍历 `[start_index, n]` 上的数。 + - 将遍历的 i 元素进行回退。 +- 最终返回 res 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, n: int, k: int, start_index: int): + if len(self.path) == k: + self.res.append(self.path[:]) + return + for i in range(start_index, n - (k - len(self.path)) + 2): + self.path.append(i) + self.backtrack(n, k, i + 1) + self.path.pop() + + def combine(self, n: int, k: int) -> List[List[int]]: + self.res.clear() + self.path.clear() + self.backtrack(n, k, 1) + return self.res +``` + diff --git a/docs/solutions/0001-0099/container-with-most-water.md b/docs/solutions/0001-0099/container-with-most-water.md new file mode 100644 index 00000000..53399d80 --- /dev/null +++ b/docs/solutions/0001-0099/container-with-most-water.md @@ -0,0 +1,71 @@ +# [0011. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) + +- 标签:贪心、数组、双指针 +- 难度:中等 + +## 题目链接 + +- [0011. 盛最多水的容器 - 力扣](https://leetcode.cn/problems/container-with-most-water/) + +## 题目大意 + +**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n$,每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 + +**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 + +**说明**: + +- $n == height.length$。 +- $2 \le n \le 10^5$。 +- $0 \le height[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) + +```python +输入:[1,8,6,2,5,4,8,3,7] +输出:49 +解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +``` + +## 解题思路 + +### 思路 1:对撞指针 + +从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 `左右两端直线中较低直线的高度 * 两端直线之间的距离 ` 所决定的。所以我们应该使得 **较低直线的高度尽可能的高**,这样才能使盛水面积尽可能的大。 + +可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: + +1. 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 +2. 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 +3. 判断 `left` 和 `right` 的高度值大小。 + 1. 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 + 2. 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 +4. 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 + +### 思路 1:代码 + +```python +class Solution: + def maxArea(self, height: List[int]) -> int: + left = 0 + right = len(height) - 1 + ans = 0 + while left < right: + area = min(height[left], height[right]) * (right-left) + ans = max(ans, area) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/count-and-say.md b/docs/solutions/0001-0099/count-and-say.md new file mode 100644 index 00000000..cc358b93 --- /dev/null +++ b/docs/solutions/0001-0099/count-and-say.md @@ -0,0 +1,50 @@ +# [0038. 外观数列](https://leetcode.cn/problems/count-and-say/) + +- 标签:字符串 +- 难度:中等 + +## 题目链接 + +- [0038. 外观数列 - 力扣](https://leetcode.cn/problems/count-and-say/) + +## 题目大意 + +给定一个正整数 n,$(1 \le n \le 30)$,要求输出外观数列的第 n 项。 + +外观数列:整数序列,数字由 1 开始,每一项都是对前一项的描述 + +例如: + +| 1. | 1 | 由 1 开始 | +| --- | ---: | ------------------- | +| 2. | 11 | 表示 1 个 1 | +| 3. | 21 | 表示 2 个 1 | +| 4. | 1211 | 表示 1 个 1,1 个 2 | + + + +## 解题思路 + +模拟题目遍历求解。 + +将 ans 设为 "1",每次遍历判断相邻且相同的数字有多少个,再将 ans 拼接上「数字个数 + 数字」。 + +## 代码 + +```python +class Solution: + def countAndSay(self, n: int) -> str: + ans = "1" + + for _ in range(1, n): + s = "" + start = 0 + for i in range(len(ans)): + if ans[i] != ans[start]: + s += str(i-start) + ans[start] + start = i + s += str(len(ans)-start) + ans[start] + ans = s + return ans +``` + diff --git a/docs/solutions/0001-0099/decode-ways.md b/docs/solutions/0001-0099/decode-ways.md new file mode 100644 index 00000000..405aed53 --- /dev/null +++ b/docs/solutions/0001-0099/decode-ways.md @@ -0,0 +1,96 @@ +# [0091. 解码方法](https://leetcode.cn/problems/decode-ways/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0091. 解码方法 - 力扣](https://leetcode.cn/problems/decode-ways/) + +## 题目大意 + +**描述**:给定一个数字字符串 $s$。该字符串已经按照下面的映射关系进行了编码: + +- `A` 映射为 $1$。 +- `B` 映射为 $2$。 +- ... +- `Z` 映射为 $26$。 + +基于上述映射的方法,现在对字符串 $s$ 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: + +- `"AAJF"`,将消息分组为 $(1 1 10 6)$。 +- `"KJF"`,将消息分组为 $(11 10 6)$。 + +**要求**:计算出共有多少种可能的解码方案。 + +**说明**: + +- $1 \le s.length \le 100$。 +- $s$ 只包含数字,并且可能包含前导零。 +- 题目数据保证答案肯定是一个 $32$ 位的整数。 + +**示例**: + +- 示例 1: + +```python +输入:s = "226" +输出:3 +解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。 + +###### 3. 状态转移方程 + +$dp[i]$ 的来源有两种情况: + +1. 使用了一个字符,对 $s[i]$ 进行翻译。只要 $s[i] != 0$,就可以被翻译为 `A` ~ `I` 的某个字母,此时方案数为 $dp[i] = dp[i - 1]$。 +2. 使用了两个字符,对 $s[i - 1]$ 和 $s[i]$ 进行翻译,只有 $s[i - 1] != 0$,且 $s[i - 1]$ 和 $s[i]$ 组成的整数必须小于等于 $26$ 才能翻译,可以翻译为 `J` ~ `Z` 中的某字母,此时方案数为 $dp[i] = dp[i - 2]$。 + +这两种情况有可能是同时存在的,也有可能都不存在。在进行转移的时候,将符合要求的方案数累加起来即可。 + +状态转移方程可以写为: + +$dp[i] += \begin{cases} dp[i-1] & \quad s[i] \ne 0 \cr dp[i-2] & \quad s[i-1] \ne 0, s[i-1:i] \le 26 \end{cases}$ + +###### 4. 初始条件 + +- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 $dp[0] = 1$。 +- 字符串只有一个字符时,需要考虑该字符是否为 $0$,不为 $0$ 的话,$dp[1] = 1$,为 $0$ 的话,$dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。则最终结果为 $dp[size]$,$size$ 为字符串长度。 + + +### 思路 1:动态规划代码 + +```python +class Solution: + def numDecodings(self, s: str) -> int: + size = len(s) + dp = [0 for _ in range(size + 1)] + dp[0] = 1 + for i in range(1, size + 1): + if s[i - 1] != '0': + dp[i] += dp[i - 1] + if i > 1 and s[i - 2] != '0' and int(s[i - 2: i]) <= 26: + dp[i] += dp[i - 2] + return dp[size] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 diff --git a/docs/solutions/0001-0099/divide-two-integers.md b/docs/solutions/0001-0099/divide-two-integers.md new file mode 100644 index 00000000..d855deca --- /dev/null +++ b/docs/solutions/0001-0099/divide-two-integers.md @@ -0,0 +1,62 @@ +# [0029. 两数相除](https://leetcode.cn/problems/divide-two-integers/) + +- 标签:位运算、数学 +- 难度:中等 + +## 题目链接 + +- [0029. 两数相除 - 力扣](https://leetcode.cn/problems/divide-two-integers/) + +## 题目大意 + +给定两个整数,被除数 dividend 和除数 divisor。要求返回两数相除的商,并且不能使用乘法,除法和取余运算。取值范围在 $[-2^{31}, 2^{31}-1]$。如果结果溢出,则返回 $2^{31} - 1$。 + +## 解题思路 + +题目要求不能使用乘法,除法和取余运算。 + +可以把被除数和除数当做二进制,这样进行运算的时候,就可以通过移位运算来实现二进制的乘除。 + +- 先将除数不断左移,移位到位数大于或等于被除数。记录其移位次数 count。 + +- 然后再将除数右移 count 次,模拟二进制除法运算。 + - 如果当前被除数大于等于除数,则将 1 左移 count 位,即为当前位的商,并将其累加答案上。再用除数减去被除数,进行下一次运算。 + + + +## 代码 + +```python +class Solution: + def divide(self, dividend: int, divisor: int) -> int: + MIN_INT, MAX_INT = -2147483648, 2147483647 + # 标记被除数和除数是否异号 + symbol = True if (dividend ^ divisor) < 0 else False + # 将被除数和除数转换为正数处理 + if dividend < 0: + dividend = -dividend + if divisor < 0: + divisor = -divisor + + # 除数不断左移,移位到位数大于或等于被除数 + count = 0 + while dividend >= divisor: + count += 1 + divisor <<= 1 + + # 向右移位,不断模拟二进制除法运算 + res = 0 + while count > 0: + count -= 1 + divisor >>= 1 + if dividend >= divisor: + res += (1 << count) + dividend -= divisor + if symbol: + res = -res + if MIN_INT <= res <= MAX_INT: + return res + else: + return MAX_INT +``` + diff --git a/docs/solutions/0001-0099/edit-distance.md b/docs/solutions/0001-0099/edit-distance.md new file mode 100644 index 00000000..4e90d4a2 --- /dev/null +++ b/docs/solutions/0001-0099/edit-distance.md @@ -0,0 +1,111 @@ +# [0072. 编辑距离](https://leetcode.cn/problems/edit-distance/) + +- 标签:字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0072. 编辑距离 - 力扣](https://leetcode.cn/problems/edit-distance/) + +## 题目大意 + +**描述**:给定两个单词 $word1$、$word2$。 + +对一个单词可以进行以下三种操作: + +- 插入一个字符 +- 删除一个字符 +- 替换一个字符 + +**要求**:计算出将 $word1$ 转换为 $word2$ 所使用的最少操作数。 + +**说明**: + +- $0 \le word1.length, word2.length \le 500$。 +- $word1$ 和 $word2$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:word1 = "horse", word2 = "ros" +输出:3 +解释: +horse -> rorse (将 'h' 替换为 'r') +rorse -> rose (删除 'r') +rose -> ros (删除 'e') +``` + +- 示例 2: + +```python +输入:word1 = "intention", word2 = "execution" +输出:5 +解释: +intention -> inention (删除 't') +inention -> enention (将 'i' 替换为 'e') +enention -> exention (将 'n' 替换为 'x') +exention -> exection (将 'n' 替换为 'c') +exection -> execution (插入 'u') +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,所需要的最少操作次数。 + +###### 3. 状态转移方程 + +1. 如果当前字符相同($word1[i - 1] = word2[j - 1]$),无需插入、删除、替换。$dp[i][j] = dp[i - 1][j - 1]$。 +2. 如果当前字符不同($word1[i - 1] \ne word2[j - 1]$),$dp[i][j]$ 取源于以下三种情况中的最小情况: + 1. 替换($word1[i - 1]$ 替换为 $word2[j - 1]$):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上替换的操作数 $1$,即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 + 2. 插入($word1$ 在第 $i - 1$ 位置上插入元素):最少操作次数依赖于「以 $word1$ 中前 $i - 1$ 个字符组成的子字符串 $str1$」 变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」,再加上插入需要的操作数 $1$,即:$dp[i][j] = dp[i - 1][j] + 1$。 + 3. 删除($word1$ 删除第 $i - 1$ 位置元素):最少操作次数依赖于「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「以 $word2$ 中前 $j - 1$ 个字符组成的子字符串 $str2$」,再加上删除需要的操作数 $1$,即:$dp[i][j] = dp[i][j - 1] + 1$。 + +综合上述情况,状态转移方程为: + +$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & word1[i - 1] = word2[j - 1] \cr min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 & word1[i - 1] \ne word2[j - 1] \end{cases}$ + +###### 4. 初始条件 + +- 当 $i = 0$,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」为空字符串,「$str1$」变为「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」时,至少需要插入 $j$ 次,即:$dp[0][j] = j$。 +- 当 $j = 0$,「以 $word2$ 中前 $j$ 个字符组成的子字符串 $str2$」为空字符串,「以 $word1$ 中前 $i$ 个字符组成的子字符串 $str1$」变为「$str2$」时,至少需要删除 $i$ 次,即:$dp[i][0] = i$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[sise1][size2]$(即 $word1$ 变为 $word2$ 所使用的最少操作数)即可。其中 $size1$、$size2$ 分别为 $word1$、$word2$ 的字符串长度。 + +### 思路 1:代码 + +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + size1 = len(word1) + size2 = len(word2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + + for i in range(size1 + 1): + dp[i][0] = i + for j in range(size2 + 1): + dp[0][j] = j + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if word1[i - 1] == word2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + return dp[size1][size2] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $word1$、$word2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 +- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 diff --git a/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md b/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md new file mode 100644 index 00000000..fcab0e20 --- /dev/null +++ b/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md @@ -0,0 +1,86 @@ +# [0034. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0034. 在排序数组中查找元素的第一个和最后一个位置 - 力扣](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) + +## 题目大意 + +**描述**:给你一个按照非递减顺序排列的整数数组 `nums`,和一个目标值 `target`。 + +**要求**:找出给定目标值在数组中的开始位置和结束位置。 + +**说明**: + +- 要求使用时间复杂度为 $O(\log n)$ 的算法解决问题。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [5,7,7,8,8,10], target = 8 +输出:[3,4] +``` + +- 示例 2: + +```python +输入:nums = [5,7,7,8,8,10], target = 6 +输出:[-1,-1] +``` + +## 解题思路 + +### 思路 1:二分查找 + +要求使用时间复杂度为 $O(\log n)$ 的算法解决问题,那么就需要使用「二分查找算法」了。 + +- 进行两次二分查找,第一次尽量向左搜索。第二次尽量向右搜索。 + +### 思路 1:代码 + +```python +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + ans = [-1, -1] + n = len(nums) + if n == 0: + return ans + + left = 0 + right = n - 1 + while left < right: + mid = left + (right - left) // 2 + if nums[mid] < target: + left = mid + 1 + else: + right = mid + + if nums[left] != target: + return ans + + ans[0] = left + + left = 0 + right = n - 1 + while left < right: + mid = left + (right - left + 1) // 2 + if nums[mid] > target: + right = mid - 1 + else: + left = mid + + if nums[left] == target: + ans[1] = left + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log_2 n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md b/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md new file mode 100644 index 00000000..0cb99923 --- /dev/null +++ b/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md @@ -0,0 +1,384 @@ +# [0028. 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) + +- 标签:双指针、字符串、字符串匹配 +- 难度:中等 + +## 题目链接 + +- [0028. 找出字符串中第一个匹配项的下标 - 力扣](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) + +## 题目大意 + +**描述**:给定两个字符串 `haystack` 和 `needle`。 + +**要求**:在 `haystack` 字符串中找出 `needle` 字符串出现的第一个位置(从 `0` 开始)。如果不存在,则返回 `-1`。 + +**说明**: + +- 当 `needle` 为空字符串时,返回 `0`。 +- $1 \le haystack.length, needle.length \le 10^4$。 +- `haystack` 和 `needle` 仅由小写英文字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:haystack = "hello", needle = "ll" +输出:2 +解释:"sad" 在下标 0 和 6 处匹配。第一个匹配项的下标是 0 ,所以返回 0 。 +``` + +- 示例 2: + +```python +输入:haystack = "leetcode", needle = "leeto" +输出:-1 +解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。 +``` + +## 解题思路 + +字符串匹配的经典题目。常见的字符串匹配算法有:BF(Brute Force)算法、RK(Robin-Karp)算法、KMP(Knuth Morris Pratt)算法、BM(Boyer Moore)算法、Horspool 算法、Sunday 算法等。 + +### 思路 1:BF(Brute Force)算法 + +**BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 + +BF 算法具体步骤如下: + +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 + 1. 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 + 2. 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 +3. 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 + +### 思路 1:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + i = 0 + j = 0 + len1 = len(haystack) + len2 = len(needle) + + while i < len1 and j < len2: + if haystack[i] == needle[j]: + i += 1 + j += 1 + else: + i = i - (j - 1) + j = 0 + + if j == len2: + return i - j + else: + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:平均时间复杂度为 $O(n + m)$,最坏时间复杂度为 $O(m \times n)$。其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:RK(Robin Karp)算法 + +**RK 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 + +RK 算法具体步骤如下: + +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 +3. 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 +4. 然后逐个与模式串的哈希值比较大小。 + 1. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 + 2. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 + 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 + 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 +5. 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 + +### 思路 2:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def rabinKarp(T: str, p: str) -> int: + len1, len2 = len(T), len(p) + + hash_p = hash(p) + for i in range(len1 - len2 + 1): + hash_T = hash(T[i: i + len2]) + if hash_p != hash_T: + continue + k = 0 + for j in range(len2): + if T[i + j] != p[j]: + break + k += 1 + if k == len2: + return i + return -1 + return rabinKarp(haystack, needle) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + +### 思路 3:KMP(Knuth Morris Pratt)算法 + +**KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 + +KMP 算法具体步骤如下: + +1. 根据 `next` 数组的构造步骤生成「前缀表」`next`。 +2. 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 +3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 +4. 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 +5. 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 +6. 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 +7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 + +### 思路 3:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + # KMP 匹配算法,T 为文本串,p 为模式串 + def kmp(T: str, p: str) -> int: + n, m = len(T), len(p) + + next = generateNext(p) # 生成 next 数组 + + i, j = 0, 0 + while i < n and j < m: + if j == -1 or T[i] == p[j]: + i += 1 + j += 1 + else: + j = next[j] + if j == m: + return i - j + + return -1 + + # 生成 next 数组 + # next[i] 表示坏字符在模式串中最后一次出现的位置 + def generateNext(p: str): + m = len(p) + + next = [-1 for _ in range(m)] # 初始化数组元素全部为 -1 + i, k = 0, -1 + while i < m - 1: # 生成下一个 next 元素 + if k == -1 or p[i] == p[k]: + i += 1 + k += 1 + if p[i] == p[k]: + next[i] = next[k] # 设置 next 元素 + else: + next[i] = k # 退到更短相同前缀 + else: + k = next[k] + return next + + return kmp(haystack, needle) +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + +### 思路 4:BM(Boyer Moore)算法 + +**BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +BM 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 + 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: + 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 + 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 + 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 4:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def boyerMoore(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) # 生成坏字符位置表 + gs_list = generageGoodSuffixList(p) # 生成好后缀规则后移位数表 + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: + j -= 1 + if j < 0: + return i + bad_move = j - bc_table.get(T[i + j], -1) + good_move = gs_list[j] + i += max(bad_move, good_move) + return -1 + + # 生成坏字符位置表 + def generateBadCharTable(p: str): + bc_table = dict() + + for i in range(len(p)): + bc_table[p[i]] = i # 坏字符在模式串中最后一次出现的位置 + return bc_table + + # 生成好后缀规则后移位数表 + def generageGoodSuffixList(p: str): + m = len(p) + gs_list = [m for _ in range(m)] + suffix = generageSuffixArray(p) + j = 0 + for i in range(m - 1, -1, -1): + if suffix[i] == i + 1: + while j < m - 1 - i: + if gs_list[j] == m: + gs_list[j] = m - 1 - i + j += 1 + + for i in range(m - 1): + gs_list[m - 1 - suffix[i]] = m - 1 - i + + return gs_list + + def generageSuffixArray(p: str): + m = len(p) + suffix = [m for _ in range(m)] + for i in range(m - 2, -1, -1): + start = i + while start >= 0 and p[start] == p[m - 1 - i + start]: + start -= 1 + suffix[i] = i - start + return suffix + + return boyerMoore(haystack, needle) +``` + +### 思路 4:复杂度分析 + +- **时间复杂度**:$O(n + \sigma)$,其中文本串 $T$ 的长度为 $n$,字符集的大小是 $\sigma$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 + +### 思路 5:Horspool 算法 + +**Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +Horspool 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: + 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 5:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def horspool(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: + j -= 1 + if j < 0: + return i + i += bc_table.get(T[i + m - 1], m) + return -1 + + # 生成后移位置表 + # bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 + def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m - 1): + bc_table[p[i]] = m - i - 1 # 更新坏字符在模式串中最后一次出现的位置 + return bc_table + + return horspool(haystack, needle) +``` + +### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 + +### 思路 6:Sunday 算法 + +**Sunday 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +Sunday 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: + 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 6:代码 + +```python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + # sunday 算法,T 为文本串,p 为模式串 + def sunday(T: str, p: str) -> int: + n, m = len(T), len(p) + if m == 0: + return 0 + + bc_table = generateBadCharTable(p) # 生成后移位数表 + + i = 0 + while i <= n - m: + if T[i: i + m] == p: + return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 + if i + m >= n: + return -1 + i += bc_table.get(T[i + m], m + 1) # 通过后移位数表,向右进行进行快速移动 + return -1 # 匹配失败 + + # 生成后移位数表 + # bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 + def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m): + bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 + return bc_table + + return sunday(haystack, needle) +``` + +### 思路 6:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/first-missing-positive.md b/docs/solutions/0001-0099/first-missing-positive.md new file mode 100644 index 00000000..f03e38aa --- /dev/null +++ b/docs/solutions/0001-0099/first-missing-positive.md @@ -0,0 +1,73 @@ +# [0041. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) + +- 标签:数组、哈希表 +- 难度:困难 + +## 题目链接 + +- [0041. 缺失的第一个正数 - 力扣](https://leetcode.cn/problems/first-missing-positive/) + +## 题目大意 + +**描述**:给定一个未排序的整数数组 `nums`。 + +**要求**:找出其中没有出现的最小的正整数。 + +**说明**: + +- $1 \le nums.length \le 5 * 10^5$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- 要求实现时间复杂度为 `O(n)` 并且只使用常数级别额外空间的解决方案。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,0] +输出:3 +``` + +- 示例 2: + +```python +输入:nums = [3,4,-1,1] +输出:2 +``` + +## 解题思路 + +### 思路 1:哈希表、原地哈希 + +如果使用普通的哈希表,我们只需要遍历一遍数组,将对应整数存入到哈希表中,再从 `1` 开始,依次判断对应正数是否在哈希表中即可。但是这种做法的空间复杂度为 $O(n)$,不满足常数级别的额外空间要求。 + +我们可以将当前数组视为哈希表。一个长度为 `n` 的数组,对应存储的元素值应该为 `[1, n + 1]` 之间,其中还包含一个缺失的元素。 + +1. 我们可以遍历一遍数组,将当前元素放到其对应位置上(比如元素值为 `1` 的元素放到数组第 `0` 个位置上、元素值为 `2` 的元素放到数组第 `1` 个位置上,等等)。 +2. 然后再次遍历一遍数组。遇到第一个元素值不等于下标 + 1 的元素,就是答案要求的缺失的第一个正数。 +3. 如果遍历完没有在数组中找到缺失的第一个正数,则缺失的第一个正数是 `n + 1`。 +4. 最后返回我们找到的缺失的第一个正数。 + +### 思路 1:代码 + +```python +class Solution: + def firstMissingPositive(self, nums: List[int]) -> int: + size = len(nums) + + for i in range(size): + while 1 <= nums[i] <= size and nums[i] != nums[nums[i] - 1]: + index1 = i + index2 = nums[i] - 1 + nums[index1], nums[index2] = nums[index2], nums[index1] + + for i in range(size): + if nums[i] != i + 1: + return i + 1 + return size + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 `nums` 的元素个数。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0001-0099/generate-parentheses.md b/docs/solutions/0001-0099/generate-parentheses.md new file mode 100644 index 00000000..c3357610 --- /dev/null +++ b/docs/solutions/0001-0099/generate-parentheses.md @@ -0,0 +1,115 @@ +# [0022. 括号生成](https://leetcode.cn/problems/generate-parentheses/) + +- 标签:字符串、回溯算法 +- 难度:中等 + +## 题目链接 + +- [0022. 括号生成 - 力扣](https://leetcode.cn/problems/generate-parentheses/) + +## 题目大意 + +**描述**:给定一个整数 $n$,代表生成括号的对数。 + +**要求**:生成所有有可能且有效的括号组合。 + +**说明**: + +- $1 \le n \le 8$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 3 +输出:["((()))","(()())","(())()","()(())","()()()"] +``` + +- 示例 2: + +```python +输入:n = 1 +输出:["()"] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 + +如果在当前组合中增加一个 `(`,则令 `symbol` 加 `1`,如果增加一个 `)`,则令 `symbol` 减 `1`。 + +显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 + +如果最终生成 $2 \times n$ 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +1. **明确所有选择**:$2 \times n$ 的括号组合中的每个位置,都可以从 `(` 或者 `)` 中选出。并且,只有在 `symbol < n` 的时候,才能选择 `(`,在 `symbol > 0` 的时候,才能选择 `)`。 + +2. **明确终止条件**: + + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 + +3. **将决策树和终止条件翻译成代码:** + + 1. 定义回溯函数: + + - `backtracking(symbol, index):` 函数的传入参数是 `symbol`(用于表示是否当前组合是否成对匹配),`index`(当前元素下标),全局变量是 `parentheses`(用于保存所有有效的括号组合),`parenthesis`(当前括号组合),。 + - `backtracking(symbol, index)` 函数代表的含义是:递归根据 `symbol`,在 `(` 和 `)` 中选择第 `index` 个元素。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑元素,到第 $2 \times n$ 个元素为止,枚举出所有可选的元素。对于每一个可选元素: + - 约束条件:`symbol < n` 或者 `symbol > 0`。 + - 选择元素:将其添加到当前括号组合 `parenthesis` 中。 + - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 + - 撤销选择:将该元素从当前括号组合 `parenthesis` 中移除。 + + ```python + if symbol < n: + parenthesis.append('(') + backtrack(symbol + 1, index + 1) + parenthesis.pop() + if symbol > 0: + parenthesis.append(')') + backtrack(symbol - 1, index + 1) + parenthesis.pop() + ``` + + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是当 `index == 2 * n` 时,递归停止。 + - 并且在 `symbol == 0` 时,当前组合才是有效的,此时将其加入到最终答案数组中。 + +### 思路 1:代码 + +```python +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + parentheses = [] # 存放所有括号组合 + parenthesis = [] # 存放当前括号组合 + def backtrack(symbol, index): + if n * 2 == index: + if symbol == 0: + parentheses.append("".join(parenthesis)) + else: + if symbol < n: + parenthesis.append('(') + backtrack(symbol + 1, index + 1) + parenthesis.pop() + if symbol > 0: + parenthesis.append(')') + backtrack(symbol - 1, index + 1) + parenthesis.pop() + backtrack(0, 0) + return parentheses +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\frac{2^{2 \times n}}{\sqrt{n}})$,其中 $n$ 为生成括号的对数。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[22. 括号生成 - 力扣(Leetcode)](https://leetcode.cn/problems/generate-parentheses/solutions/192912/gua-hao-sheng-cheng-by-leetcode-solution/) \ No newline at end of file diff --git a/docs/solutions/0001-0099/gray-code.md b/docs/solutions/0001-0099/gray-code.md new file mode 100644 index 00000000..52cbf41b --- /dev/null +++ b/docs/solutions/0001-0099/gray-code.md @@ -0,0 +1,79 @@ +# [0089. 格雷编码](https://leetcode.cn/problems/gray-code/) + +- 标签:位运算、数学、回溯 +- 难度:中等 + +## 题目链接 + +- [0089. 格雷编码 - 力扣](https://leetcode.cn/problems/gray-code/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回任一有效的 $n$ 位格雷码序列。 + +**说明**: + +- **n 位格雷码序列**:是一个由 $2^n$ 个整数组成的序列,其中: + - 每个整数都在范围 $[0, 2^n - 1]$ 内(含 $0$ 和 $2^n - 1$)。 + - 第一个整数是 $0$。 + - 一个整数在序列中出现不超过一次。 + - 每对相邻整数的二进制表示恰好一位不同 ,且第一个和最后一个整数的二进制表示恰好一位不同。 + +- $1 \le n \le 16$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:[0,1,3,2] +解释: +[0,1,3,2] 的二进制表示是 [00,01,11,10] 。 +- 00 和 01 有一位不同 +- 01 和 11 有一位不同 +- 11 和 10 有一位不同 +- 10 和 00 有一位不同 +[0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。 +- 00 和 10 有一位不同 +- 10 和 11 有一位不同 +- 11 和 01 有一位不同 +- 01 和 00 有一位不同 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:[0,1] +``` + +## 解题思路 + +### 思路 1:位运算 + 公式法 + +- 格雷编码生成规则:以二进制值为 $0$ 的格雷编码作为第 $0$ 项,第一次改变最右边的数位,第二次改变从右边数第一个为 $1$ 的数位左边的数位,第三次跟第一次一样,改变最右边的数位,第四次跟第二次一样,改变从右边数第一个为 $1$ 的数位左边的数位。此后,第五、六次,第七、八次 ... 都跟第一二次一样反复进行,直到生成 $2^n$​ 个格雷编码。 + +- 也可以直接利用二进制转换为格雷编码公式: + + ![image.png](https://pic.leetcode-cn.com/1013850d7f6c8cf1d99dc0ac3292264b74f6a52d84e0215f540c80952e184f41-image.png) + +### 思路 1:代码 + +```python +class Solution: + def grayCode(self, n: int) -> List[int]: + gray = [] + binary = 0 + while binary < (1 << n): + gray.append(binary ^ binary >> 1) + binary += 1 + return gray +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0001-0099/group-anagrams.md b/docs/solutions/0001-0099/group-anagrams.md new file mode 100644 index 00000000..9d422b1b --- /dev/null +++ b/docs/solutions/0001-0099/group-anagrams.md @@ -0,0 +1,38 @@ +# [0049. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) + +- 标签:数组、哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [0049. 字母异位词分组 - 力扣](https://leetcode.cn/problems/group-anagrams/) + +## 题目大意 + +给定一个字符串数组,将包含字母相同的字符串组合在一起,不需要考虑输出顺序。 + +## 解题思路 + +使用哈希表记录字母相同的字符串。对每一个字符串进行排序,按照 排序字符串:字母相同的字符串数组 的键值顺序进行存储。 + +最终将哈希表的值转换为对应数组返回结果。 + +## 代码 + +```python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + str_dict = dict() + res = [] + for s in strs: + sort_s = str(sorted(s)) + if sort_s in str_dict: + str_dict[sort_s] += [s] + else: + str_dict[sort_s] = [s] + + for sort_s in str_dict: + res += [str_dict[sort_s]] + return res +``` + diff --git a/docs/solutions/0001-0099/index.md b/docs/solutions/0001-0099/index.md new file mode 100644 index 00000000..b5de5604 --- /dev/null +++ b/docs/solutions/0001-0099/index.md @@ -0,0 +1,88 @@ +## 本章内容 + +- [0001. 两数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/two-sum.md) +- [0002. 两数相加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-two-numbers.md) +- [0003. 无重复字符的最长子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md) +- [0004. 寻找两个正序数组的中位数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/median-of-two-sorted-arrays.md) +- [0005. 最长回文子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-palindromic-substring.md) +- [0007. 整数反转](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-integer.md) +- [0008. 字符串转换整数 (atoi)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/string-to-integer-atoi.md) +- [0009. 回文数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/palindrome-number.md) +- [0010. 正则表达式匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/regular-expression-matching.md) +- [0011. 盛最多水的容器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/container-with-most-water.md) +- [0012. 整数转罗马数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/integer-to-roman.md) +- [0013. 罗马数字转整数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/roman-to-integer.md) +- [0014. 最长公共前缀](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-common-prefix.md) +- [0015. 三数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum.md) +- [0016. 最接近的三数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/3sum-closest.md) +- [0017. 电话号码的字母组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md) +- [0018. 四数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/4sum.md) +- [0019. 删除链表的倒数第 N 个结点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md) +- [0020. 有效的括号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-parentheses.md) +- [0021. 合并两个有序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-two-sorted-lists.md) +- [0022. 括号生成](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/generate-parentheses.md) +- [0023. 合并 K 个升序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-k-sorted-lists.md) +- [0024. 两两交换链表中的节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/swap-nodes-in-pairs.md) +- [0025. K 个一组翻转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-nodes-in-k-group.md) +- [0026. 删除有序数组中的重复项](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md) +- [0027. 移除元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-element.md) +- [0028. 找出字符串中第一个匹配项的下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-the-index-of-the-first-occurrence-in-a-string.md) +- [0029. 两数相除](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/divide-two-integers.md) +- [0032. 最长有效括号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/longest-valid-parentheses.md) +- [0033. 搜索旋转排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array.md) +- [0034. 在排序数组中查找元素的第一个和最后一个位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/find-first-and-last-position-of-element-in-sorted-array.md) +- [0035. 搜索插入位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-insert-position.md) +- [0036. 有效的数独](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/valid-sudoku.md) +- [0037. 解数独](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sudoku-solver.md) +- [0038. 外观数列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/count-and-say.md) +- [0039. 组合总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum.md) +- [0040. 组合总和 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combination-sum-ii.md) +- [0041. 缺失的第一个正数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/first-missing-positive.md) +- [0042. 接雨水](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/trapping-rain-water.md) +- [0043. 字符串相乘](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/multiply-strings.md) +- [0044. 通配符匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/wildcard-matching.md) +- [0045. 跳跃游戏 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game-ii.md) +- [0046. 全排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations.md) +- [0047. 全排列 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/permutations-ii.md) +- [0048. 旋转图像](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-image.md) +- [0049. 字母异位词分组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/group-anagrams.md) +- [0050. Pow(x, n)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/powx-n.md) +- [0051. N 皇后](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/n-queens.md) +- [0052. N 皇后 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/n-queens-ii.md) +- [0053. 最大子数组和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/maximum-subarray.md) +- [0054. 螺旋矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix.md) +- [0055. 跳跃游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/jump-game.md) +- [0056. 合并区间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-intervals.md) +- [0058. 最后一个单词的长度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/length-of-last-word.md) +- [0059. 螺旋矩阵 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/spiral-matrix-ii.md) +- [0061. 旋转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/rotate-list.md) +- [0062. 不同路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths.md) +- [0063. 不同路径 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-paths-ii.md) +- [0064. 最小路径和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-path-sum.md) +- [0066. 加一](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/plus-one.md) +- [0067. 二进制求和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/add-binary.md) +- [0069. x 的平方根](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sqrtx.md) +- [0070. 爬楼梯](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/climbing-stairs.md) +- [0072. 编辑距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/edit-distance.md) +- [0073. 矩阵置零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/set-matrix-zeroes.md) +- [0074. 搜索二维矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-a-2d-matrix.md) +- [0075. 颜色分类](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/sort-colors.md) +- [0076. 最小覆盖子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/minimum-window-substring.md) +- [0077. 组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/combinations.md) +- [0078. 子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets.md) +- [0079. 单词搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/word-search.md) +- [0080. 删除有序数组中的重复项 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md) +- [0081. 搜索旋转排序数组 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md) +- [0082. 删除排序链表中的重复元素 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md) +- [0083. 删除排序链表中的重复元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md) +- [0084. 柱状图中最大的矩形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/largest-rectangle-in-histogram.md) +- [0088. 合并两个有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/merge-sorted-array.md) +- [0089. 格雷编码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/gray-code.md) +- [0090. 子集 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/subsets-ii.md) +- [0091. 解码方法](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/decode-ways.md) +- [0092. 反转链表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/reverse-linked-list-ii.md) +- [0093. 复原 IP 地址](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/restore-ip-addresses.md) +- [0094. 二叉树的中序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/binary-tree-inorder-traversal.md) +- [0095. 不同的二叉搜索树 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees-ii.md) +- [0096. 不同的二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/unique-binary-search-trees.md) +- [0098. 验证二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/validate-binary-search-tree.md) diff --git a/docs/solutions/0001-0099/integer-to-roman.md b/docs/solutions/0001-0099/integer-to-roman.md new file mode 100644 index 00000000..1835edee --- /dev/null +++ b/docs/solutions/0001-0099/integer-to-roman.md @@ -0,0 +1,46 @@ +# [0012. 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman/) + +- 标签:哈希表、数学、字符串 +- 难度:中等 + +## 题目链接 + +- [0012. 整数转罗马数字 - 力扣](https://leetcode.cn/problems/integer-to-roman/) + +## 题目大意 + +给定一个整数,将其转换为罗马数字。 + +罗马数字规则: + +- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; +- 一般罗马数字较大数字在左边,较小数字在右边,此时值为两者之和,比如 XI = X + I = 10 + 1 = 11。 +- 例外情况下,较小数字在左边,较大数字在右边,此时值为后者减前者之差,比如 IX = X - I = 10 - 1 = 9。 + +## 解题思路 + +根据规则,可以得出: + +- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; +- CM 代表 900,CD 代表 400,XC 代表 90,XL 代表 40,IX 代表 9,IV 代表 4。 + +依次排序可得: + +- 1000 : M、900 : CM、D : 500、400 : CD、100 : C、90 : XC、50 : L、40 : XL、10 : X、9 : IX、5 : V、4 : IV、1 : I。 + +使用贪心算法。每次尽量用最大的数对应的罗马字符来表示。先选择 1000,再选择 900,然后 500,等等。 + +## 代码 + +```python +class Solution: + def intToRoman(self, num: int) -> str: + roman_dict = {1000:'M', 900:'CM', 500:'D', 400:'CD', 100:'C', 90:'XC', 50:'L', 40:'XL', 10:'X', 9:'IX', 5:'V', 4:'IV', 1:'I'} + res = "" + for key in roman_dict: + if num // key != 0: + res += roman_dict[key] * (num // key) + num %= key + return res +``` + diff --git a/docs/solutions/0001-0099/jump-game-ii.md b/docs/solutions/0001-0099/jump-game-ii.md new file mode 100644 index 00000000..a9fc3226 --- /dev/null +++ b/docs/solutions/0001-0099/jump-game-ii.md @@ -0,0 +1,157 @@ +# [0045. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0045. 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/) + +## 题目大意 + +**描述**:给定一个非负整数数组 `nums`,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置为数组的第一个下标处。 + +**要求**:计算出到达最后一个下标处的最少的跳跃次数。假设你总能到达数组的最后一个下标处。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $0 \le nums[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,3,1,1,4] +输出:2 +解释:跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 +``` + +## 解题思路 + +### 思路 1:动态规划(超时) + +###### 1. 划分阶段 + +按照位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。 + +###### 3. 状态转移方程 + +对于当前位置 `i`,如果之前的位置 `j`($o \le j < i$) 能够跳到位置 `i` 需要满足:位置 `j`($o \le j < i$)加上位置 `j` 所能跳到的最远长度要大于等于 `i`,即 `j + nums[j] >= i` 。 + +而跳到下标 `i` 所需要的最小跳跃次数则等于满足上述要求的位置 `j` 中最小跳跃次数加 `1`,即 `dp[i] = min(dp[i], dp[j] + 1)`。 + +###### 4. 初始条件 + +初始状态下,跳到下标 `0` 需要的最小跳跃次数为 `0`,即 `dp[0] = 0`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。则最终结果为 `dp[size - 1]`。 + +### 思路 1:动态规划(超时)代码 + +```python +class Solution: + def jump(self, nums: List[int]) -> int: + size = len(nums) + dp = [float("inf") for _ in range(size)] + dp[0] = 0 + + for i in range(1, size): + for j in range(i): + if j + nums[j] >= i: + dp[i] = min(dp[i], dp[j] + 1) + + return dp[size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总体时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +### 思路 2:动态规划 + 贪心 + +因为本题的数据规模为 $10^4$,而思路 1 的时间复杂度是 $O(n^2)$,所以就超时了。那么我们有什么方法可以优化一下,减少一下时间复杂度吗? + +上文提到,在满足 `j + nums[j] >= i` 的情况下,`dp[i] = min(dp[i], dp[j] + 1)`。 + +通过观察可以发现,`dp[i]` 是单调递增的,也就是说 `dp[i - 1] <= dp[i] <= dp[i + 1]`。 + +举个例子,比如跳到下标 `i` 最少需要 `5` 步,即 `dp[i] = 5`,那么必然不可能出现少于 `5` 步就能跳到下标 `i + 1` 的情况,跳到下标 `i + 1` 至少需要 `5` 步或者更多步。 + +既然 `dp[i]` 是单调递增的,那么在更新 `dp[i]` 时,我们找到最早可以跳到 `i` 的点 `j`,从该点更新 `dp[i]`。即找到满足 `j + nums[j] >= i` 的第一个 `j`,使得 `dp[i] = dp[j] + 1`。 + +而查找第一个 `j` 的过程可以通过使用一个指针变量 `j` 从前向后迭代查找。 + +最后,将最终结果 `dp[size - 1]` 返回即可。 + +### 思路 2:动态规划 + 贪心代码 + +```python +class Solution: + def jump(self, nums: List[int]) -> int: + size = len(nums) + dp = [float("inf") for _ in range(size)] + dp[0] = 0 + + j = 0 + for i in range(1, size): + while j + nums[j] < i: + j += 1 + dp[i] = dp[j] + 1 + + return dp[size - 1] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。最外层循环遍历的时间复杂度是 $O(n)$,看似和内层循环结合遍历的时间复杂度是 $O(n^2)$,实际上内层循环只遍历了一遍,与外层循环遍历次数是相加关系,两者的时间复杂度和是 $O(2n)$,$O(2n) = O(n)$,所以总体时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +### 思路 2:贪心算法 + +如果第 `i` 个位置所能跳到的位置为 `[i + 1, i + nums[i]]`,则: + +- 第 `0` 个位置所能跳到的位置就是 `[0 + 1, 0 + nums[0]]`,即 `[1, nums[0]]`。 +- 第 `1` 个位置所能跳到的位置就是 `[1 + 1, 1 + nums[1]]`,即 `[2, 1 + nums[1]]`。 +- …… + +对于每一个位置 `i` 来说,所能跳到的所有位置都可以作为下一个起跳点,为了尽可能使用最少的跳跃次数,所以我们应该使得下一次起跳所能达到的位置尽可能的远。简单来说,就是每次在「可跳范围」内选择可以使下一次跳的更远的位置。这样才能获得最少跳跃次数。具体做法如下: + +1. 维护几个变量:当前所能达到的最远位置 `end`,下一步所能跳到的最远位置 `max_pos`,最少跳跃次数 `setps`。 +2. 遍历数组 `nums` 的前 `len(nums) - 1` 个元素: + 1. 每次更新第 `i` 位置下一步所能跳到的最远位置 `max_pos`。 + 2. 如果索引 `i` 到达了 `end` 边界,则:更新 `end` 为新的当前位置 `max_pos`,并令步数 `setps` 加 `1`。 +3. 最终返回跳跃次数 `steps`。 + +### 思路 2:贪心算法代码 + +```python +class Solution: + def jump(self, nums: List[int]) -> int: + end, max_pos = 0, 0 + steps = 0 + for i in range(len(nums) - 1): + max_pos = max(max_pos, nums[i] + i) + if i == end: + end = max_pos + steps += 1 + return steps +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(1)$。只用到了常数项的变量,所以总体空间复杂度为 $O(1)$。 + +## 参考资料 + +- 【题解】[【宫水三叶の相信科学系列】详解「DP + 贪心 + 双指针」解法,以及该如何猜 DP 的状态定义 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/xiang-jie-dp-tan-xin-shuang-zhi-zhen-jie-roh4/) +- 【题解】[动态规划+贪心,易懂。 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/dong-tai-gui-hua-tan-xin-yi-dong-by-optimjie/) diff --git a/docs/solutions/0001-0099/jump-game.md b/docs/solutions/0001-0099/jump-game.md new file mode 100644 index 00000000..300d6033 --- /dev/null +++ b/docs/solutions/0001-0099/jump-game.md @@ -0,0 +1,114 @@ +# [0055. 跳跃游戏](https://leetcode.cn/problems/jump-game/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0055. 跳跃游戏 - 力扣](https://leetcode.cn/problems/jump-game/) + +## 题目大意 + +**描述**:给定一个非负整数数组 `nums`,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置位于数组的第一个下标处。 + +**要求**:判断是否能够到达最后一个下标。 + +**说明**: + +- $1 \le nums.length \le 3 \times 10^4$。 +- $0 \le nums[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,3,1,1,4] +输出:true +解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 +``` + +- 示例 2: + +```python +输入:nums = [3,2,1,0,4] +输出:false +解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +如果我们能通过前面的某个位置 $j$,到达后面的某个位置 $i$,则我们一定能到达区间 $[j, i]$ 中所有的点($j \le i$)。 + +而前面的位置 $j$ 肯定也是通过 $j$ 前面的点到达的。所以我们可以通过贪心算法来计算出所能到达的最远位置。具体步骤如下: + +1. 初始化能到达的最远位置 $max_i$ 为 $0$。 +2. 遍历数组 `nums`。 +3. 如果能到达当前位置,即 $max_i \le i$,并且当前位置 + 当前位置最大跳跃长度 > 能到达的最远位置,即 $i + nums[i] > max_i$,则更新能到达的最远位置 $max_i$。 +4. 遍历完数组,最后比较能到达的最远位置 $max_i$ 和数组最远距离 `size - 1` 的关系。如果 $max_i >= len(nums)$,则返回 `True`,否则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def canJump(self, nums: List[int]) -> bool: + size = len(nums) + max_i = 0 + for i in range(size): + if max_i >= i and i + nums[i] > max_i: + max_i = i + nums[i] + + return max_i >= size - 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `nums` 的长度。 +- **空间复杂度**: + +### 思路 2:动态规划 + +###### 1. 划分阶段 + +按照位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i]` 表示为:从位置 $0$ 出发,经过 $j \le i$,可以跳出的最远距离。 + +###### 3. 状态转移方程 + +- 如果能通过 $0 \sim i - 1$ 个位置到达 $i$,即 $dp[i-1] \le i$,则 $dp[i] = max(dp[i-1], i + nums[i])$。 +- 如果不能通过 $0 \sim i - 1$ 个位置到达 $i$,即 $dp[i - 1] < i$,则 $dp[i] = dp[i - 1]$。 + +###### 4. 初始条件 + +初始状态下,从 $0$ 出发,经过 $0$,可以跳出的最远距离为 `nums[0]`,即 `dp[0] = nums[0]`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i]` 表示为:从位置 $0$ 出发,经过 $j \le i$,可以跳出的最远距离。则我们需要判断 `dp[size - 1]` 与数组最远距离 `size - 1` 的关系。 + +### 思路 2:代码 + +```python +class Solution: + def canJump(self, nums: List[int]) -> bool: + size = len(nums) + dp = [0 for _ in range(size)] + dp[0] = nums[0] + for i in range(1, size): + if i <= dp[i - 1]: + dp[i] = max(dp[i - 1], i + nums[i]) + else: + dp[i] = dp[i - 1] + return dp[size - 1] >= size - 1 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `nums` 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0001-0099/largest-rectangle-in-histogram.md b/docs/solutions/0001-0099/largest-rectangle-in-histogram.md new file mode 100644 index 00000000..555981a4 --- /dev/null +++ b/docs/solutions/0001-0099/largest-rectangle-in-histogram.md @@ -0,0 +1,49 @@ +# [0084. 柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) + +- 标签:栈、数组、单调栈 +- 难度:困难 + +## 题目链接 + +- [0084. 柱状图中最大的矩形 - 力扣](https://leetcode.cn/problems/largest-rectangle-in-histogram/) + +## 题目大意 + +给定一个非负整数数组 `heights` ,`heights[i]` 用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 + +要求:计算出在该柱状图中,能够勾勒出来的矩形的最大面积。 + +## 解题思路 + +思路一:枚举「宽度」。一重循环枚举所有柱子,第二重循环遍历柱子右侧的柱子,所得的宽度就是两根柱子形成区间的宽度,高度就是这段区间中的最小高度。然后计算出对应面积,记录并更新最大面积。这样下来,时间复杂度为 $O(n^2)$。 + +思路二:枚举「高度」。一重循环枚举所有柱子,以柱子高度为当前矩形高度,然后向两侧延伸,遇到小于当前矩形高度的情况就停止。然后计算当前矩形面积,记录并更新最大面积。这样下来,时间复杂度也是 $O(n^2)$。 + +思路三:利用「单调栈」减少两侧延伸的复杂度。 + +- 枚举所有柱子。 +- 如果当前柱子高度较大,大于等于栈顶柱体的高度,则直接将当前柱体入栈。 +- 如果当前柱体高度较小,小于栈顶柱体的高度,则一直出栈,直到当前柱体大于等于栈顶柱体高度。 + - 出栈后,说明当前柱体是出栈柱体向右找到的第一个小于当前柱体高度的柱体,那么就可以向右将宽度扩展到当前柱体。 + - 出栈后,说明新的栈顶柱体是出栈柱体向左找到的第一个小于新的栈顶柱体高度的柱体,那么就可以向左将宽度扩展到新的栈顶柱体。 + - 以新的栈顶柱体为左边界,当前柱体为右边界,以出栈柱体为高度。计算矩形面积,然后记录并更新最大面积。 + +## 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + heights.append(0) + ans = 0 + stack = [] + for i in range(len(heights)): + while stack and heights[stack[-1]] >= heights[i]: + cur = stack.pop(-1) + left = stack[-1] + 1 if stack else 0 + right = i - 1 + ans = max(ans, (right - left + 1) * heights[cur]) + stack.append(i) + + return ans +``` + diff --git a/docs/solutions/0001-0099/length-of-last-word.md b/docs/solutions/0001-0099/length-of-last-word.md new file mode 100644 index 00000000..2c608249 --- /dev/null +++ b/docs/solutions/0001-0099/length-of-last-word.md @@ -0,0 +1,36 @@ +# [0058. 最后一个单词的长度](https://leetcode.cn/problems/length-of-last-word/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [0058. 最后一个单词的长度 - 力扣](https://leetcode.cn/problems/length-of-last-word/) + +## 题目大意 + +给定一个字符串 s,返回字符串中最后一个单词长度。 + +- 「单词」:指仅由字母组成、不包含任何空格字符的最大子字符串。 + +## 解题思路 + +从字符串末尾开始逆序遍历,先过滤掉末尾空白字符,然后统计字符数量,直到遇到空格或到达字符串开始位置。 + +## 代码 + +```python +class Solution: + def lengthOfLastWord(self, s: str) -> int: + ans = 0 + for i in range(len(s)-1, -1, -1): + if s[i] == " ": + if ans == 0: + continue + else: + return ans + else: + ans += 1 + return ans +``` + diff --git a/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md b/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md new file mode 100644 index 00000000..97f98ba1 --- /dev/null +++ b/docs/solutions/0001-0099/letter-combinations-of-a-phone-number.md @@ -0,0 +1,83 @@ +# [0017. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) + +- 标签:哈希表、字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [0017. 电话号码的字母组合 - 力扣](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) + +## 题目大意 + +**描述**:给定一个只包含数字 2~9 的字符串 `digits`。给出数字到字母的映射如下(与电话按键相同)。注意 $1$ 不对应任何字母。 + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/original_images/17_telephone_keypad.png) + +**要求**:返回字符串 `digits` 在九宫格键盘上所能表示的所有字母组合。答案可以按 「任意顺序」返回。 + +**说明**: + +- $0 \le digits.length \le 4$。 +- `digits[i]` 是范围 $2 \sim 9$ 的一个数字。 + +**示例**: + +- 示例 1: + +```python +输入:digits = "23" +输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] +``` + +- 示例 2: + +```python +输入:digits = "2" +输出:["a","b","c"] +``` + +## 解题思路 + +### 思路 1:回溯算法 + 哈希表 + +用哈希表保存每个数字键位对应的所有可能的字母,然后进行回溯操作。 + +回溯过程中,维护一个字符串 combination,表示当前的字母排列组合。初始字符串为空,每次取电话号码的一位数字,从哈希表中取出该数字所对应的所有字母,并将其中一个插入到 combination 后面,然后继续处理下一个数字,知道处理完所有数字,得到一个完整的字母排列。开始进行回退操作,遍历其余的字母排列。 + +### 思路 1:代码 + +```python +class Solution: + def letterCombinations(self, digits: str) -> List[str]: + if not digits: + return [] + + phone_dict = { + "2": "abc", + "3": "def", + "4": "ghi", + "5": "jkl", + "6": "mno", + "7": "pqrs", + "8": "tuv", + "9": "wxyz" + } + + def backtrack(combination, index): + if index == len(digits): + combinations.append(combination) + else: + digit = digits[index] + for letter in phone_dict[digit]: + backtrack(combination + letter, index + 1) + + combinations = list() + backtrack('', 0) + return combinations +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(3^m \times 4^n)$,其中 $m$ 是 `digits` 中对应 $3$ 个字母的数字个数,$m$ 是 `digits` 中对应 $4$ 个字母的数字个数。 +- **空间复杂度**:$O(m + n)$。 + diff --git a/docs/solutions/0001-0099/longest-common-prefix.md b/docs/solutions/0001-0099/longest-common-prefix.md new file mode 100644 index 00000000..d02b65dd --- /dev/null +++ b/docs/solutions/0001-0099/longest-common-prefix.md @@ -0,0 +1,69 @@ +# [0014. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) + +- 标签:字典树、字符串 +- 难度:简单 + +## 题目链接 + +- [0014. 最长公共前缀 - 力扣](https://leetcode.cn/problems/longest-common-prefix/) + +## 题目大意 + +**描述**:给定一个字符串数组 `strs`。 + +**要求**:返回字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 `""`。 + +**说明**: + +- $1 \le strs.length \le 200$。 +- $0 \le strs[i].length \le 200$。 +- `strs[i]` 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:strs = ["flower","flow","flight"] +输出:"fl" +``` + +- 示例 2: + +```python +输入:strs = ["dog","racecar","car"] +输出:"" +解释:输入不存在公共前缀。 +``` + +## 解题思路 + +### 思路 1:纵向遍历 + +1. 依次遍历所有字符串的每一列,比较相同位置上的字符是否相同。 + 1. 如果相同,则继续对下一列进行比较。 + 2. 如果不相同,则当前列字母不再属于公共前缀,直接返回当前列之前的部分。 +2. 如果遍历结束,说明字符串数组中的所有字符串都相等,则可将字符串数组中的第一个字符串作为公共前缀进行返回。 + +### 思路 1:代码 + +```python +class Solution: + def longestCommonPrefix(self, strs: List[str]) -> str: + if not strs: + return "" + + length = len(strs[0]) + count = len(strs) + for i in range(length): + c = strs[0][i] + for j in range(1, count): + if len(strs[j]) == i or strs[j][i] != c: + return strs[0][:i] + return strs[0] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$ 是字符串数组中的字符串的平均长度,$n$ 是字符串的数量。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/longest-palindromic-substring.md b/docs/solutions/0001-0099/longest-palindromic-substring.md new file mode 100644 index 00000000..ecf59ea1 --- /dev/null +++ b/docs/solutions/0001-0099/longest-palindromic-substring.md @@ -0,0 +1,94 @@ +# [0005. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0005. 最长回文子串 - 力扣](https://leetcode.cn/problems/longest-palindromic-substring/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:找到 $s$ 中最长的回文子串。 + +**说明**: + +- **回文串**:如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 +- $1 \le s.length \le 1000$。 +- $s$ 仅由数字和英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "babad" +输出:"bab" +解释:"aba" 同样是符合题意的答案。 +``` + +- 示例 2: + +```python +输入:s = "cbbd" +输出:"bb" +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内是否是一个回文串。 + +###### 3. 状态转移方程 + +- 当子串只有 $1$ 位或 $2$ 位的时候,如果 $s[i] == s[j]$,该子串为回文子串,即:`dp[i][j] = (s[i] == s[j])`。 +- 如果子串大于 $2$ 位,则如果 $s[i + 1...j - 1]$ 是回文串,且 $s[i] == s[j]$,则 $s[i...j]$ 也是回文串,即:`dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]`。 + +###### 4. 初始条件 + +- 初始状态下,默认字符串 $s$ 的所有子串都不是回文串。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内是否是一个回文串。当判断完 $s[i: j]$ 是否为回文串时,同时判断并更新最长回文子串的起始位置 $max\underline{\hspace{0.5em}}start$ 和最大长度 $max\underline{\hspace{0.5em}}len$。则最终结果为 $s[max\underline{\hspace{0.5em}}start, max\underline{\hspace{0.5em}}start + max\underline{\hspace{0.5em}}len]$。 + +### 思路 1:代码 + +```python +class Solution: + def longestPalindrome(self, s: str) -> str: + n = len(s) + if n <= 1: + return s + + dp = [[False for _ in range(n)] for _ in range(n)] + max_start = 0 + max_len = 1 + + for j in range(1, n): + for i in range(j): + if s[i] == s[j]: + if j - i <= 2: + dp[i][j] = True + else: + dp[i][j] = dp[i + 1][j - 1] + if dp[i][j] and (j - i + 1) > max_len: + max_len = j - i + 1 + max_start = i + return s[max_start: max_start + max_len] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 是字符串的长度。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md b/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md new file mode 100644 index 00000000..888ebc90 --- /dev/null +++ b/docs/solutions/0001-0099/longest-substring-without-repeating-characters.md @@ -0,0 +1,81 @@ +# [0003. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0003. 无重复字符的最长子串 - 力扣](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中不含有重复字符的最长子串的长度。 + +**说明**: + +- $0 \le s.length \le 5 * 10^4$。 +- $s$ 由英文字母、数字、符号和空格组成。 + +**示例**: + +- 示例 1: + +```python +输入: s = "abcabcbb" +输出: 3 +解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 +``` + +- 示例 2: + +```python +输入: s = "bbbbb" +输出: 1 +解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 +``` + +## 解题思路 + +### 思路 1:滑动窗口(不定长度) + +用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。 + +1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 +2. 一开始,$left$、$right$ 都指向 $0$。 +3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。 +4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$。 +5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +6. 输出无重复字符的最长子串长度。 + +### 思路 1:代码 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + left = 0 + right = 0 + window = dict() + ans = 0 + + while right < len(s): + if s[right] not in window: + window[s[right]] = 1 + else: + window[s[right]] += 1 + + while window[s[right]] > 1: + window[s[left]] -= 1 + left += 1 + + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 diff --git a/docs/solutions/0001-0099/longest-valid-parentheses.md b/docs/solutions/0001-0099/longest-valid-parentheses.md new file mode 100644 index 00000000..528054d4 --- /dev/null +++ b/docs/solutions/0001-0099/longest-valid-parentheses.md @@ -0,0 +1,146 @@ +# [0032. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) + +- 标签:栈、字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0032. 最长有效括号 - 力扣](https://leetcode.cn/problems/longest-valid-parentheses/) + +## 题目大意 + +**描述**:给定一个只包含 `'('` 和 `')'` 的字符串。 + +**要求**:找出最长有效(格式正确且连续)括号子串的长度。 + +**说明**: + +- $0 \le s.length \le 3 * 10^4$。 +- `s[i]` 为 `'('` 或 `')'`。 + +**示例**: + +- 示例 1: + +```python +输入:s = "(()" +输出:2 +解释:最长有效括号子串是 "()" +``` + +- 示例 2: + +```python +输入:s = ")()())" +输出:4 +解释:最长有效括号子串是 "()()" +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照最长有效括号子串的结束位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i]` 表示为:以字符 `s[i]` 为结尾的最长有效括号的长度。 + +###### 3. 状态转移方程 + +- 如果 `s[i] == '('`,此时以 `s[i]` 结尾的子串不可能构成有效括号对,则 `dp[i] = 0`。 +- 如果 `s[i] == ')'`,我们需要考虑 `s[i - 1]` 来判断是否能够构成有效括号对。 + - 如果 `s[i - 1] == '('`,字符串形如 `......()`,此时 `s[i - 1]` 与 `s[i]` 为 `()`,则: + - `dp[i]` 取决于「以字符 `s[i - 2]` 为结尾的最长有效括号长度」 + 「`s[i - 1]` 与 `s[i]` 构成的有效括号对长度(`2`)」,即 `dp[i] = dp[i - 2] + 2`。 + - 特别地,如果 `s[i - 2]` 不存在,即 `i - 2 < 0`,则 `dp[i]` 直接取决于 「`s[i - 1]` 与 `s[i]` 构成的有效括号对长度(`2`)」,即 `dp[i] = 2`。 + - 如果 `s[i - 1] == ')'`,字符串形如 `......))`,此时 `s[i - 1]` 与 `s[i]` 为 `))`。那么以 `s[i - 1]` 为结尾的最长有效长度为 `dp[i - 1]`,则我们需要看 `i - 1 - dp[i - 1]` 位置上的字符 `s[i - 1 - dp[i - 1]]`是否与 `s[i]` 匹配。 + - 如果 `s[i - 1 - dp[i - 1]] == '('`,则说明 `s[i - 1 - dp[i - 1]]`与 `s[i]` 相匹配,此时我们需要看以 `s[i - 1 - dp[i - 1]]` 的前一个字符 `s[i - 1 - dp[i - 2]]` 为结尾的最长括号长度是多少,将其加上 ``s[i - 1 - dp[i - 1]]`与 `s[i]`,从而构成更长的有效括号对: + - `dp[i]` 取决于「以字符 `s[i - 1]` 为结尾的最长括号长度」 + 「以字符 `s[i - 1 - dp[i - 2]]` 为结尾的最长括号长度」+ 「`s[i - 1 - dp[i - 1]]` 与 `s[i]` 的长度(`2`)」,即 `dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2`。 + - 特别地,如果 `s[i - dp[i - 1] - 2]` 不存在,即 `i - dp[i - 1] - 2 < 0`,则 `dp[i]` 直接取决于「以字符 `s[i - 1]` 为结尾的最长括号长度」+「`s[i - 1 - dp[i - 1]]` 与 `s[i]` 的长度(`2`)」,即 `dp[i] = dp[i - 1] + 2`。 + +###### 4. 初始条件 + +- 默认所有以字符 `s[i]` 为结尾的最长有效括号的长度为 `0`,即 `dp[i] = 0`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i]` 表示为:以字符 `s[i]` 为结尾的最长有效括号的长度。则最终结果为 `max(dp[i])`。 + +### 思路 1:代码 + +```python +class Solution: + def longestValidParentheses(self, s: str) -> int: + dp = [0 for _ in range(len(s))] + ans = 0 + for i in range(1, len(s)): + if s[i] == '(': + continue + if s[i - 1] == '(': + if i >= 2: + dp[i] = dp[i - 2] + 2 + else: + dp[i] = 2 + elif i - dp[i - 1] > 0 and s[i - dp[i - 1] - 1] == '(': + if i - dp[i - 1] >= 2: + dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2 + else: + dp[i] = dp[i - 1] + 2 + ans = max(ans, dp[i]) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串长度。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:栈 + +1. 定义一个变量 `ans` 用于维护最长有效括号的长度,初始时,`ans = 0`。 +2. 定义一个栈用于判定括号对是否匹配(栈中存储的是括号的下标),栈底元素始终保持「最长有效括号子串的开始元素的前一个元素下标」。 +3. 初始时,我们在栈中存储 `-1` 作为哨兵节点,表示「最长有效括号子串的开始元素的前一个元素下标为 `-1`」,即 `stack = [-1]`, +4. 然后从左至右遍历字符串。 + 1. 如果遇到左括号,即 `s[i] == '('`,则将其下标 `i` 压入栈,用于后续匹配右括号。 + 2. 如果遇到右括号,即 `s[i] == ')'`,则将其与最近的左括号进行匹配(即栈顶元素),弹出栈顶元素,与当前右括号进行匹配。弹出之后: + 1. 如果栈为空,则说明: + 1. 之前弹出的栈顶元素实际上是「最长有效括号子串的开始元素的前一个元素下标」,而不是左括号`(`,此时无法完成合法匹配。 + 2. 将当前右括号的坐标 `i` 压入栈中,充当「下一个有效括号子串的开始元素前一个下标」。 + 2. 如果栈不为空,则说明: + 1. 之前弹出的栈顶元素为左括号 `(`,此时可完成合法匹配。 + 2. 当前合法匹配的长度为「当前右括号的下标 `i`」 - 「最长有效括号子串的开始元素的前一个元素下标」。即 `i - stack[-1]`。 + 3. 更新最长匹配长度 `ans` 为 `max(ans, i - stack[-1])`。 +5. 遍历完输出答案 `ans`。 + +### 思路 2:代码 + +```python +class Solution: + def longestValidParentheses(self, s: str) -> int: + stack = [-1] + ans = 0 + for i in range(len(s)): + if s[i] == '(': + stack.append(i) + else: + stack.pop() + if stack: + ans = max(ans, i - stack[-1]) + else: + stack.append(i) + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串长度。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[动态规划思路详解(C++)——32.最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/solutions/206995/dong-tai-gui-hua-si-lu-xiang-jie-c-by-zhanganan042/) +- 【题解】[32. 最长有效括号 - 力扣(Leetcode)](https://leetcode.cn/problems/longest-valid-parentheses/solutions/314683/zui-chang-you-xiao-gua-hao-by-leetcode-solution/) +- 【题解】[【Nick~Hot一百题系列】超简单思路栈!](https://leetcode.cn/problems/longest-valid-parentheses/solutions/1258643/nickhotyi-bai-ti-xi-lie-chao-jian-dan-si-ggi4/) \ No newline at end of file diff --git a/docs/solutions/0001-0099/maximum-subarray.md b/docs/solutions/0001-0099/maximum-subarray.md new file mode 100644 index 00000000..c4b31fe3 --- /dev/null +++ b/docs/solutions/0001-0099/maximum-subarray.md @@ -0,0 +1,169 @@ +# [0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) + +- 标签:数组、分治、动态规划 +- 难度:中等 + +## 题目链接 + +- [0053. 最大子数组和 - 力扣](https://leetcode.cn/problems/maximum-subarray/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + +**说明**: + +- **子数组**:指的是数组中的一个连续部分。 +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +输出:6 +解释:连续子数组 [4,-1,2,1] 的和最大,为 6。 +``` + +- 示例 2: + +```python +输入:nums = [1] +输出:1 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照连续子数组的结束位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。 + +###### 3. 状态转移方程 + +状态 $dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则我们可以从「第 $i - 1$ 个数结尾的连续子数组的最大和」,以及「第 $i$ 个数的值」来讨论 $dp[i]$。 + +- 如果 $dp[i - 1] < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,即:$dp[i - 1] + nums[i] < nums[i]$。所以,此时 $dp[i]$ 应取「第 $i$ 个数的值」,即 $dp[i] = nums[i]$。 +- 如果 $dp[i - 1] \ge 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,即:$dp[i - 1] + nums[i] \ge nums[i]$。所以,此时 $dp[i]$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」+「 第 $i$ 个数的值」,即 $dp[i] = dp[i - 1] + nums[i]$。 + +归纳一下,状态转移方程为: + +$dp[i] = \begin{cases} nums[i], & dp[i - 1] < 0 \cr dp[i - 1] + nums[i] & dp[i - 1] \ge 0 \end{cases}$ + +###### 4. 初始条件 + +- 第 $0$ 个数结尾的连续子数组的最大和为 $nums[0]$,即 $dp[0] = nums[0]$。 + +###### 5. 最终结果 + +根据状态定义,$dp[i]$ 为:以第 $i$ 个数结尾的连续子数组的最大和。则最终结果应为所有 $dp[i]$ 的最大值,即 $max(dp)$。 + +### 思路 1:代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + size = len(nums) + dp = [0 for _ in range(size)] + + dp[0] = nums[0] + for i in range(1, size): + if dp[i - 1] < 0: + dp[i] = nums[i] + else: + dp[i] = dp[i - 1] + nums[i] + return max(dp) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:动态规划 + 滚动优化 + +因为 $dp[i]$ 只和 $dp[i - 1]$ 和当前元素 $nums[i]$ 相关,我们也可以使用一个变量 $subMax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。然后使用 $ansMax$ 来保存全局中最大值。 + +### 思路 2:代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + size = len(nums) + subMax = nums[0] + ansMax = nums[0] + + for i in range(1, size): + if subMax < 0: + subMax = nums[i] + else: + subMax += nums[i] + ansMax = max(ansMax, subMax) + return ansMax +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(1)$。 + +### 思路 3:分治算法 + +我们将数组 $nums$ 根据中心位置分为左右两个子数组。则具有最大和的连续子数组可能存在以下 $3$ 种情况: + +1. 具有最大和的连续子数组在左子数组中。 +2. 具有最大和的连续子数组在右子数组中。 +3. 具有最大和的连续子数组跨过中心位置,一部分在左子数组中,另一部分在右子树组中。 + +那么我们要求出具有最大和的连续子数组的最大和,则分别对上面 $3$ 种情况求解即可。具体步骤如下: + +1. 将数组 $nums$ 根据中心位置递归分为左右两个子数组,直到所有子数组长度为 $1$。 +2. 长度为 $1$ 的子数组最大和肯定是数组中唯一的数,将其返回即可。 +3. 求出左子数组的最大和 $leftMax$。 +4. 求出右子树组的最大和 $rightMax$。 +5. 求出跨过中心位置,一部分在左子数组中,另一部分在右子树组的子数组最大和 $leftTotal + rightTotal$。 +6. 求出 $3$、$4$、$5$ 中的最大值,即为当前数组的最大和,将其返回即可。 + +### 思路 3:代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + def max_sub_array(low, high): + if low == high: + return nums[low] + + mid = low + (high - low) // 2 + leftMax = max_sub_array(low, mid) + rightMax = max_sub_array(mid + 1, high) + + total = 0 + leftTotal = -inf + for i in range(mid, low - 1, -1): + total += nums[i] + leftTotal = max(leftTotal, total) + + total = 0 + rightTotal = -inf + for i in range(mid + 1, high + 1): + total += nums[i] + rightTotal = max(rightTotal, total) + + return max(leftMax, rightMax, leftTotal + rightTotal) + + return max_sub_array(0, len(nums) - 1) +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(\log n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/median-of-two-sorted-arrays.md b/docs/solutions/0001-0099/median-of-two-sorted-arrays.md new file mode 100644 index 00000000..7ce95757 --- /dev/null +++ b/docs/solutions/0001-0099/median-of-two-sorted-arrays.md @@ -0,0 +1,128 @@ +# [0004. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) + +- 标签:数组、二分查找、分治 +- 难度:困难 + +## 题目链接 + +- [0004. 寻找两个正序数组的中位数 - 力扣](https://leetcode.cn/problems/median-of-two-sorted-arrays/) + +## 题目大意 + +**描述**:给定两个正序(从小到大排序)数组 $nums1$、$nums2$。 + +**要求**:找出并返回这两个正序数组的中位数。 + +**说明**: + +- 算法的时间复杂度应该为 $O(\log (m + n))$ 。 +- $nums1.length == m$。 +- $nums2.length == n$。 +- $0 \le m \le 1000$。 +- $0 \le n \le 1000$。 +- $1 \le m + n \le 2000$。 +- $-10^6 \le nums1[i], nums2[i] \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2], nums2 = [3,4] +输出:2.50000 +解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5 +``` + +- 示例 2: + +```python +输入:nums1 = [1,2], nums2 = [3,4] +输出:2.50000 +解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5 +``` + +## 解题思路 + +### 思路 1:二分查找 + +单个有序数组的中位数是中间元素位置的元素。如果中间元素位置有两个元素,则为两个元素的平均数。如果是两个有序数组,则可以使用归并排序的方式将两个数组拼接为一个大的有序数组。合并后有序数组中间位置的元素,即为中位数。 + +当然不合并的话,我们只需找到中位数的位置即可。我们用 $n1$、$n2$ 来表示数组 $nums1$、$nums2$ 的长度,则合并后的大的有序数组长度为 $(n1 + n2)$。 + +我们可以发现:**中位数把数组分割成了左右两部分,并且左右两部分元素个数相等。** + +- 如果 $(n1 + n2)$ 是奇数时,中位数是大的有序数组中第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 个(包含中位数)。 +- 如果 $(n1 + n2)$ 是偶数时,中位数是第 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 的元素和第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素的平均值,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 个。 + +因为是向下取整,上面两种情况综合可以写为:单侧元素个数为:$\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 个。 + +我们用 $k$ 来表示 $\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 。现在的问题就变为了:**如何在两个有序数组中找到前 k 小的元素位置?** + +如果我们从 $nums1$ 数组中取出前 $m1(m1 \le k)$ 个元素,那么从 $nums2$ 就需要取出前 $m2 = k - m1$ 个元素。 + +并且如果我们在 $nums1$ 数组中找到了合适的 $m1$ 位置,则 $m2$ 的位置也就确定了。 + +问题就可以进一步转换为:**如何从 $nums1$ 数组中取出前 $m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或者 $nums2$ 第 $m2 = k - m1$ 个元素为中位线位置**。 + +我们可以通过「二分查找」的方法,在数组 $nums1$ 中找到合适的 $m1$ 位置,具体做法如下: + +1. 让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 +2. 每次取中间位置作为 $m1$,则 $m2 = k - m1$。然后判断 $nums1$ 第 $m1$ 位置上元素和 $nums2$ 第 $m2 - 1$ 位置上元素之间的关系,即 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系。 + 1. 如果 $nums1[m1] < nums2[m2 - 1]$,则 $nums1$ 的前 $m1$ 个元素都不可能是第 $k$ 个元素。说明 $m1$ 取值有点小了,应该将 $m1$ 进行右移操作,即 $left = m1 + 1$。 + 2. 如果 $nums1[m1] \ge nums2[m2 - 1]$,则说明 $m1$ 取值可能有点大了,应该将 $m1$ 进行左移。根据二分查找排除法的思路(排除一定不存在的区间,在剩下区间中继续查找),这里应取 $right = m1$。 +3. 找到 $m1$ 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 + +--- + +上面之所以要判断 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系是因为: + +> 如果 $nums1[m1] < nums2[m2 - 1]$,则说明: +> +> - 最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小,所以 $nums1[m1]$ 左侧的 $m1$ 个元素都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 + +推理过程: + +如果 $nums1[m1] < nums2[m2 - 1]$,则: + +1. $nums1[m1]$ 左侧比 $nums1[m1]$ 小的一共有 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$ 共 $m1$ 个)。 +2. $nums2$ 数组最多有 $m2 - 1$ 个元素比 $nums1[m1]$ 小(即便是 $nums2[m2 - 1]$ 左侧所有元素都比 $nums1[m1]$ 小,也只有 $m2 - 1$ 个)。 +3. 综上所述,$nums1$、$nums2$ 数组中最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小。 +4. 所以 $nums1[m1]$ 左侧的 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$)都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 + +### 思路 1:代码 + +```python +class Solution: + def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: + n1 = len(nums1) + n2 = len(nums2) + if n1 > n2: + return self.findMedianSortedArrays(nums2, nums1) + + k = (n1 + n2 + 1) // 2 + left = 0 + right = n1 + while left < right: + m1 = left + (right - left) // 2 # 在 nums1 中取前 m1 个元素 + m2 = k - m1 # 在 nums2 中取前 m2 个元素 + if nums1[m1] < nums2[m2 - 1]: # 说明 nums1 中所元素不够多, + left = m1 + 1 + else: + right = m1 + + m1 = left + m2 = k - m1 + + c1 = max(float('-inf') if m1 <= 0 else nums1[m1 - 1], float('-inf') if m2 <= 0 else nums2[m2 - 1]) + if (n1 + n2) % 2 == 1: + return c1 + + c2 = min(float('inf') if m1 >= n1 else nums1[m1], float('inf') if m2 >= n2 else nums2[m2]) + + return (c1 + c2) / 2 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log (m + n))$ 。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/merge-intervals.md b/docs/solutions/0001-0099/merge-intervals.md new file mode 100644 index 00000000..e1cd7b5b --- /dev/null +++ b/docs/solutions/0001-0099/merge-intervals.md @@ -0,0 +1,72 @@ +# [0056. 合并区间](https://leetcode.cn/problems/merge-intervals/) + +- 标签:数组、排序 +- 难度:中等 + +## 题目链接 + +- [0056. 合并区间 - 力扣](https://leetcode.cn/problems/merge-intervals/) + +## 题目大意 + +**描述**:给定数组 `intervals` 表示若干个区间的集合,其中单个区间为 `intervals[i] = [starti, endi]` 。 + +**要求**:合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。 + +**说明**: + +- $1 \le intervals.length \le 10^4$。 +- $intervals[i].length == 2$。 +- $0 \le starti \le endi \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:intervals = [[1,3],[2,6],[8,10],[15,18]] +输出:[[1,6],[8,10],[15,18]] +解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +``` + +- 示例 2: + +```python +输入:intervals = [[1,4],[4,5]] +输出:[[1,5]] +解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 +``` + +## 解题思路 + +### 思路 1:排序 + +1. 设定一个数组 `ans` 用于表示最终不重叠的区间数组,然后对原始区间先按照区间左端点大小从小到大进行排序。 +2. 遍历所有区间。 +3. 先将第一个区间加入 `ans` 数组中。 +4. 然后依次考虑后边的区间: + 1. 如果第 `i` 个区间左端点在前一个区间右端点右侧,则这两个区间不会重合,直接将该区间加入 `ans` 数组中。 + 2. 否则的话,这两个区间重合,判断一下两个区间的右区间值,更新前一个区间的右区间值为较大值,然后继续考虑下一个区间,以此类推。 +5. 最后返回数组 `ans`。 + +### 思路 1:代码 + +```python +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + intervals.sort(key=lambda x: x[0]) + + ans = [] + for interval in intervals: + if not ans or ans[-1][1] < interval[0]: + ans.append(interval) + else: + ans[-1][1] = max(ans[-1][1], interval[1]) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2 n)$。其中 $n$ 为区间数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0001-0099/merge-k-sorted-lists.md b/docs/solutions/0001-0099/merge-k-sorted-lists.md new file mode 100644 index 00000000..d2a07991 --- /dev/null +++ b/docs/solutions/0001-0099/merge-k-sorted-lists.md @@ -0,0 +1,95 @@ +# [0023. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) + +- 标签:链表、分治、堆(优先队列)、归并排序 +- 难度:困难 + +## 题目链接 + +- [0023. 合并 K 个升序链表 - 力扣](https://leetcode.cn/problems/merge-k-sorted-lists/) + +## 题目大意 + +**描述**:给定一个链表数组,每个链表都已经按照升序排列。 + +**要求**:将所有链表合并到一个升序链表中,返回合并后的链表。 + +**说明**: + +- $k == lists.length$。 +- $0 \le k \le 10^4$。 +- $0 \le lists[i].length \le 500$。 +- $-10^4 \le lists[i][j] \le 10^4$。 +- $lists[i]$ 按升序排列。 +- $lists[i].length$ 的总和不超过 $10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:lists = [[1,4,5],[1,3,4],[2,6]] +输出:[1,1,2,3,4,4,5,6] +解释:链表数组如下: +[ + 1->4->5, + 1->3->4, + 2->6 +] +将它们合并到一个有序链表中得到。 +1->1->2->3->4->4->5->6 +``` + +- 示例 2: + +```python +输入:lists = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:分治算法 + +分而治之的思想。将链表数组不断二分,转为规模为二分之一的子问题,然后再进行归并排序。 + +### 思路 1:代码 + +```python +class Solution: + def merge_sort(self, lists: List[ListNode], left: int, right: int) -> ListNode: + if left == right: + return lists[left] + mid = left + (right - left) // 2 + node_left = self.merge_sort(lists, left, mid) + node_right = self.merge_sort(lists, mid + 1, right) + return self.merge(node_left, node_right) + + def merge(self, a: ListNode, b: ListNode) -> ListNode: + root = ListNode(-1) + cur = root + while a and b: + if a.val < b.val: + cur.next = a + a = a.next + else: + cur.next = b + b = b.next + cur = cur.next + if a: + cur.next = a + if b: + cur.next = b + return root.next + + def mergeKLists(self, lists: List[ListNode]) -> ListNode: + if not lists: + return None + size = len(lists) + return self.merge_sort(lists, 0, size - 1) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(k \times n \times \log_2k)$。 +- **空间复杂度**:$O(\log_2k)$。 + diff --git a/docs/solutions/0001-0099/merge-sorted-array.md b/docs/solutions/0001-0099/merge-sorted-array.md new file mode 100644 index 00000000..c3149100 --- /dev/null +++ b/docs/solutions/0001-0099/merge-sorted-array.md @@ -0,0 +1,76 @@ +# [0088. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) + +- 标签:数组、双指针、排序 +- 难度:简单 + +## 题目链接 + +- [0088. 合并两个有序数组 - 力扣](https://leetcode.cn/problems/merge-sorted-array/) + +## 题目大意 + +**描述**:给定两个有序数组 $nums1$、$nums2$。 + +**要求**:将 $nums2$ 合并到 $nums1$ 中,使 $nums1$ 成为一个有序数组。 + +**说明**: + +- 给定数组 $nums1$ 空间大小为$ m + n$ 个,其中前 $m$ 个为 $nums1$ 的元素。$nums2$ 空间大小为 $n$。这样可以用 $nums1$ 的空间来存储最终的有序数组。 +- $nums1.length == m + n$。 +- $nums2.length == n$。 +- $0 \le m, n \le 200$。 +- $1 \le m + n \le 200$。 +- $-10^9 \le nums1[i], nums2[j] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 +输出:[1,2,2,3,5,6] +解释:需要合并 [1,2,3] 和 [2,5,6] 。 +合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 +``` + +- 示例 2: + +```python +输入:nums1 = [1], m = 1, nums2 = [], n = 0 +输出:[1] +解释:需要合并 [1] 和 [] 。 +合并结果是 [1] 。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +1. 将两个指针 $index1$、$index2$ 分别指向 $nums1$、$nums2$ 数组的尾部,再用一个指针 $index$ 指向数组 $nums1$ 的尾部。 +2. 从后向前判断当前指针下 $nums1[index1]$ 和 $nums[index2]$ 的值大小,将较大值存入 $num1[index]$ 中,然后继续向前遍历。 +3. 最后再将 $nums2$ 中剩余元素赋值到 $num1$ 前面对应位置上。 + +### 思路 1:代码 + +```python +class Solution: + def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: + index1 = m - 1 + index2 = n - 1 + index = m + n - 1 + while index1 >= 0 and index2 >= 0: + if nums1[index1] < nums2[index2]: + nums1[index] = nums2[index2] + index2 -= 1 + else: + nums1[index] = nums1[index1] + index1 -= 1 + index -= 1 + + nums1[:index2+1] = nums2[:index2+1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$。 +- **空间复杂度**:$O(m + n)$。 diff --git a/docs/solutions/0001-0099/merge-two-sorted-lists.md b/docs/solutions/0001-0099/merge-two-sorted-lists.md new file mode 100644 index 00000000..af3f5721 --- /dev/null +++ b/docs/solutions/0001-0099/merge-two-sorted-lists.md @@ -0,0 +1,78 @@ +# [0021. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [0021. 合并两个有序链表 - 力扣](https://leetcode.cn/problems/merge-two-sorted-lists/) + +## 题目大意 + +**描述**:给定两个升序链表的头节点 `list1` 和 `list2`。 + +**要求**:将其合并为一个升序链表。 + +**说明**: + +- 两个链表的节点数目范围是 $[0, 50]$。 +- $-100 \le Node.val \le 100$。 +- `list1` 和 `list2` 均按 **非递减顺序** 排列 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) + +```python +输入:list1 = [1,2,4], list2 = [1,3,4] +输出:[1,1,2,3,4,4] +``` + +- 示例 2: + +```python +输入:list1 = [], list2 = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:归并排序 + +利用归并排序的思想,具体步骤如下: + +1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `curr` 指向 `dummy_head` 用于遍历。 +2. 然后判断 `list1` 和 `list2` 头节点的值,将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 +3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 +4. 将剩余链表链接到合并后的链表中。 +5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后有序链表的头节点返回。 + +### 思路 1:代码 + +```python +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + dummy_head = ListNode(-1) + + curr = dummy_head + while list1 and list2: + if list1.val <= list2.val: + curr.next = list1 + list1 = list1.next + else: + curr.next = list2 + list2 = list2.next + curr = curr.next + + curr.next = list1 if list1 is not None else list2 + + return dummy_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/minimum-path-sum.md b/docs/solutions/0001-0099/minimum-path-sum.md new file mode 100644 index 00000000..31a6e218 --- /dev/null +++ b/docs/solutions/0001-0099/minimum-path-sum.md @@ -0,0 +1,97 @@ +# [0064. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) + +- 标签:数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [0064. 最小路径和 - 力扣](https://leetcode.cn/problems/minimum-path-sum/) + +## 题目大意 + +**描述**:给定一个包含非负整数的 $m \times n$ 大小的网格 $grid$。 + +**要求**:找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + +**说明**: + +- 每次只能向下或者向右移动一步。 +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 200$。 +- $0 \le grid[i][j] \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/05/minpath.jpg) + +```python +输入:grid = [[1,3,1],[1,5,1],[4,2,1]] +输出:7 +解释:因为路径 1→3→1→1→1 的总和最小。 +``` + +- 示例 2: + +```python +输入:grid = [[1,2,3],[4,5,6]] +输出:12 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:从左上角到达 $(i, j)$ 位置的最小路径和。 + +###### 3. 状态转移方程 + +当前位置 $(i, j)$ 只能从左侧位置 $(i, j - 1)$ 或者上方位置 $(i - 1, j)$ 到达。为了使得从左上角到达 $(i, j)$ 位置的最小路径和最小,应从 $(i, j - 1)$ 位置和 $(i - 1, j)$ 位置选择路径和最小的位置达到 $(i, j)$。 + +即状态转移方程为:$dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]$。 + +###### 4. 初始条件 + +- 当左侧和上方是矩阵边界时(即 $i = 0, j = 0$),$dp[i][j] = grid[i][j]$。 +- 当只有左侧是矩阵边界时(即 $i \ne 0, j = 0$),只能从上方到达,$dp[i][j] = dp[i - 1][j] + grid[i][j]$。 +- 当只有上方是矩阵边界时(即 $i = 0, j \ne 0$),只能从左侧到达,$dp[i][j] = dp[i][j - 1] + grid[i][j]$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[rows - 1][cols - 1]$(即从左上角到达 $(rows - 1, cols - 1)$ 位置的最小路径和)即可。其中 $rows$、$cols$ 分别为 $grid$ 的行数、列数。 + +### 思路 1:代码 + +```python +class Solution: + def minPathSum(self, grid: List[List[int]]) -> int: + rows, cols = len(grid), len(grid[0]) + dp = [[0 for _ in range(cols)] for _ in range(rows)] + + dp[0][0] = grid[0][0] + + for i in range(1, rows): + dp[i][0] = dp[i - 1][0] + grid[i][0] + + for j in range(1, cols): + dp[0][j] = dp[0][j - 1] + grid[0][j] + + for i in range(1, rows): + for j in range(1, cols): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + + return dp[rows - 1][cols - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m * n)$,其中 $m$、$n$ 分别为 $grid$ 的行数和列数。 +- **空间复杂度**:$O(m * n)$。 diff --git a/docs/solutions/0001-0099/minimum-window-substring.md b/docs/solutions/0001-0099/minimum-window-substring.md new file mode 100644 index 00000000..6fae3269 --- /dev/null +++ b/docs/solutions/0001-0099/minimum-window-substring.md @@ -0,0 +1,89 @@ +# [0076. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [0076. 最小覆盖子串 - 力扣](https://leetcode.cn/problems/minimum-window-substring/) + +## 题目大意 + +**描述**:给定一个字符串 `s`、一个字符串 `t`。 + +**要求**:返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""`。 + +**说明**: + +- $1 \le s.length, t.length \le 10^5$。 +- `s` 和 `t` 由英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "ADOBECODEBANC", t = "ABC" +输出:"BANC" +``` + +- 示例 2: + +```python +输入:s = "a", t = "a" +输出:"a" +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +1. `left`、`right` 表示窗口的边界,一开始都位于下标 `0` 处。`need` 用于记录短字符串需要的字符数。`window` 记录当前窗口内的字符数。 +2. 将 `right` 右移,直到出现了 `t` 中全部字符,开始右移 `left`,减少滑动窗口的大小,并记录下最小覆盖子串的长度和起始位置。 +3. 最后输出结果。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def minWindow(self, s: str, t: str) -> str: + need = collections.defaultdict(int) + window = collections.defaultdict(int) + for ch in t: + need[ch] += 1 + + left, right = 0, 0 + valid = 0 + start = 0 + size = len(s) + 1 + + while right < len(s): + insert_ch = s[right] + right += 1 + + if insert_ch in need: + window[insert_ch] += 1 + if window[insert_ch] == need[insert_ch]: + valid += 1 + + while valid == len(need): + if right - left < size: + start = left + size = right - left + remove_ch = s[left] + left += 1 + if remove_ch in need: + if window[remove_ch] == need[remove_ch]: + valid -= 1 + window[remove_ch] -= 1 + if size == len(s) + 1: + return '' + return s[start:start+size] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是字符串 $s$ 的长度。 +- **空间复杂度**:$O(| \sum |)$。$| \sum |$ 是 $s$ 和 $t$ 的字符集大小。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/multiply-strings.md b/docs/solutions/0001-0099/multiply-strings.md new file mode 100644 index 00000000..18c3949e --- /dev/null +++ b/docs/solutions/0001-0099/multiply-strings.md @@ -0,0 +1,84 @@ +# [0043. 字符串相乘](https://leetcode.cn/problems/multiply-strings/) + +- 标签:数学、字符串、模拟 +- 难度:中等 + +## 题目链接 + +- [0043. 字符串相乘 - 力扣](https://leetcode.cn/problems/multiply-strings/) + +## 题目大意 + +**描述**:给定两个以字符串形式表示的非负整数 `num1` 和 `num2`。 + +**要求**:返回 `num1` 和 `num2` 的乘积,它们的乘积也表示为字符串形式。 + +**说明**: + +- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 +- $1 \le num1.length, num2.length \le 200$。 +- `num1` 和 `num2` 只能由数字组成。 +- `num1` 和 `num2` 都不包含任何前导零,除了数字0本身。 + +**示例**: + +- 示例 1: + +```python +输入: num1 = "2", num2 = "3" +输出: "6" +``` + +- 示例 2: + +```python +输入: num1 = "123", num2 = "456" +输出: "56088" +``` + +## 解题思路 + +### 思路 1:模拟 + +我们可以使用数组来模拟大数乘法。长度为 `len(num1)` 的整数 `num1` 与长度为 `len(num2)` 的整数 `num2` 相乘的结果长度为 `len(num1) + len(num2) - 1` 或 `len(num1) + len(num2)`。所以我们可以使用长度为 `len(num1) + len(num2)` 的整数数组 `nums` 来存储两个整数相乘之后的结果。 + +整个计算流程的步骤如下: + +1. 从个位数字由低位到高位开始遍历 `num1`,取得每一位数字 `digit1`。从个位数字由低位到高位开始遍历 `num2`,取得每一位数字 `digit2`。 +2. 将 `digit1 * digit2` 的结果累积存储到 `nums` 对应位置 `i + j + 1` 上。 +3. 计算完毕之后从 `len(num1) + len(num2) - 1` 的位置由低位到高位遍历数组 `nums`。将每个数位上大于等于 `10` 的数字进行进位操作,然后对该位置上的数字进行取余操作。 +4. 最后判断首位是否有进位。如果首位为 `0`,则从第 `1` 个位置开始将答案数组拼接成字符串。如果首位不为 `0`,则从第 `0` 个位置开始将答案数组拼接成字符串。并返回答案字符串。 + +### 思路 1:代码 + +```python +class Solution: + def multiply(self, num1: str, num2: str) -> str: + if num1 == "0" or num2 == "0": + return "0" + + len1, len2 = len(num1), len(num2) + nums = [0 for _ in range(len1 + len2)] + + for i in range(len1 - 1, -1, -1): + digit1 = int(num1[i]) + for j in range(len2 - 1, -1, -1): + digit2 = int(num2[j]) + nums[i + j + 1] += digit1 * digit2 + + for i in range(len1 + len2 - 1, 0, -1): + nums[i - 1] += nums[i] // 10 + nums[i] %= 10 + + if nums[0] == 0: + ans = "".join(str(digit) for digit in nums[1:]) + else: + ans = "".join(str(digit) for digit in nums[:]) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$ 和 $n$ 分别为 `nums1` 和 `nums2` 的长度。 +- **空间复杂度**:$O(m + n)$。 + diff --git a/docs/solutions/0001-0099/n-queens-ii.md b/docs/solutions/0001-0099/n-queens-ii.md new file mode 100644 index 00000000..eff2a297 --- /dev/null +++ b/docs/solutions/0001-0099/n-queens-ii.md @@ -0,0 +1,114 @@ +# [0052. N 皇后 II](https://leetcode.cn/problems/n-queens-ii/) + +- 标签:回溯 +- 难度:困难 + +## 题目链接 + +- [0052. N 皇后 II - 力扣](https://leetcode.cn/problems/n-queens-ii/) + +## 题目大意 + +**描述**:给定一个整数 `n`。 + +**要求**:返回「`n` 皇后问题」不同解决方案的数量。 + +**说明**: + +- **n 皇后问题**:将 `n` 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 +- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 +- $1 \le n \le 9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:2 +解释:如下图所示,4 皇后问题存在两个不同的解法。 +``` + +![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) + +## 解题思路 + +### 思路 1:回溯算法 + +和「[51. N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/)」做法一致。区别在于「[51. N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/)」需要返回所有解决方案,而这道题只需要得到所有解决方案的数量即可。下面来说一下这道题的解题思路。 + +我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 + +对于 `n * n` 的棋盘来说,每一行有 `n` 列,也就有 `n` 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 + +并且在放置完之后,通过回溯的方式尝试其他可能的分支。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +![](https://qcdn.itcharge.cn/images/20220426095225.png) + +1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数: + - 首先我们先使用一个 $n \times n$ 大小的二维矩阵 `chessboard` 来表示当前棋盘,`chessboard` 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 + - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 `chessboard`(棋盘数组)和 `row`(代表当前正在考虑放置第 `row` 行皇后),全局变量是 `ans`(所有可行方案的数量)。 + - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 `row` 行皇后的情况下,递归放置剩下行的皇后。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 枚举出当前行所有的列。对于每一列位置: + - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 + - 选择元素:选择 `row, col` 位置放置皇后,将其棋盘对应位置设置为 `Q`。 + - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 + - 撤销选择:将棋盘上 `row, col` 位置设置为 `.`。 + +### 思路 1:代码 + +```python +class Solution: + # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + + def totalNQueens(self, n: int) -> int: + chessboard = [['.' for _ in range(n)] for _ in range(n)] # 棋盘初始化 + + ans = 0 + def backtrack(chessboard: List[List[str]], row: int): # 正在考虑放置第 row 行的皇后 + if row == n: # 遇到终止条件 + nonlocal ans + ans += 1 + return + + for col in range(n): # 枚举可放置皇后的列 + if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 + chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 + backtrack(chessboard, row + 1) # 递归放置 row + 1 行之后的皇后 + chessboard[row][col] = '.' # 撤销选择 row, col 位置 + + backtrack(chessboard, 0) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 +- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 diff --git a/docs/solutions/0001-0099/n-queens.md b/docs/solutions/0001-0099/n-queens.md new file mode 100644 index 00000000..72aae45c --- /dev/null +++ b/docs/solutions/0001-0099/n-queens.md @@ -0,0 +1,147 @@ +# [0051. N 皇后](https://leetcode.cn/problems/n-queens/) + +- 标签:数组、回溯 +- 难度:困难 + +## 题目链接 + +- [0051. N 皇后 - 力扣](https://leetcode.cn/problems/n-queens/) + +## 题目大意 + +**描述**:给定一个整数 `n`。 + +**要求**:返回所有不同的「`n` 皇后问题」的解决方案。每一种解法包含一个不同的「`n` 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 + +**说明**: + +- **n 皇后问题**:将 `n` 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 +- **皇后彼此不能相互攻击**:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 +- $1 \le n \le 9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +解释:如下图所示,4 皇后问题存在 2 个不同的解法。 +``` + +![](https://assets.leetcode.com/uploads/2020/11/13/queens.jpg) + +## 解题思路 + +### 思路 1:回溯算法 + +这道题是经典的回溯问题。我们可以按照行序来放置皇后,也就是先放第一行,再放第二行 …… 一直放到最后一行。 + +对于 `n * n` 的棋盘来说,每一行有 `n` 列,也就有 `n` 种放法可供选择。我们可以尝试选择其中一列,查看是否与之前放置的皇后有冲突,如果没有冲突,则继续在下一行放置皇后。依次类推,直到放置完所有皇后,并且都不发生冲突时,就得到了一个合理的解。 + +并且在放置完之后,通过回溯的方式尝试其他可能的分支。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +![](https://qcdn.itcharge.cn/images/20220426095225.png) + +1. **明确所有选择**:根据棋盘中当前行的所有列位置上是否选择放置皇后,画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后时,递归终止。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数: + - 首先我们先使用一个 $n \times n$ 大小的二维矩阵 `chessboard` 来表示当前棋盘,`chessboard` 中的字符 `Q` 代表皇后,`.` 代表空位,初始都为 `.`。 + - 然后定义回溯函数 `backtrack(chessboard, row): ` 函数的传入参数是 `chessboard`(棋盘数组)和 `row`(代表当前正在考虑放置第 `row` 行皇后),全局变量是 `res`(存放所有符合条件结果的集合数组)。 + - `backtrack(chessboard, row):` 函数代表的含义是:在放置好第 `row` 行皇后的情况下,递归放置剩下行的皇后。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 枚举出当前行所有的列。对于每一列位置: + - 约束条件:定义一个判断方法,先判断一下当前位置是否与之前棋盘上放置的皇后发生冲突,如果不发生冲突则继续放置,否则则继续向后遍历判断。 + - 选择元素:选择 `row, col` 位置放置皇后,将其棋盘对应位置设置为 `Q`。 + - 递归搜索:在该位置放置皇后的情况下,继续递归考虑下一行。 + - 撤销选择:将棋盘上 `row, col` 位置设置为 `.`。 + + ```python + # 判断当前位置 row, col 是否与之前放置的皇后发生冲突 + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + ``` + + ```python + for col in range(n): # 枚举可放置皇后的列 + if self.isValid(n, row, col, chessboard): # 如果该位置与之前放置的皇后不发生冲突 + chessboard[row][col] = 'Q' # 选择 row, col 位置放置皇后 + backtrack(row + 1, chessboard) # 递归放置 row + 1 行之后的皇后 + chessboard[row][col] = '.' # 撤销选择 row, col 位置 + ``` + + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是在最后一行放置完皇后(即 `row == n`)时,递归停止。 + - 递归停止时,将当前符合条件的棋盘转换为答案需要的形式,然后将其存入答案数组 `res` 中即可。 + +### 思路 1:代码 + +```python +class Solution: + res = [] + def backtrack(self, n: int, row: int, chessboard: List[List[str]]): + if row == n: + temp_res = [] + for temp in chessboard: + temp_str = ''.join(temp) + temp_res.append(temp_str) + self.res.append(temp_res) + return + for col in range(n): + if self.isValid(n, row, col, chessboard): + chessboard[row][col] = 'Q' + self.backtrack(n, row + 1, chessboard) + chessboard[row][col] = '.' + + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + + def solveNQueens(self, n: int) -> List[List[str]]: + self.res.clear() + chessboard = [['.' for _ in range(n)] for _ in range(n)] + self.backtrack(n, 0, chessboard) + return self.res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n!)$,其中 $n$ 是皇后数量。 +- **空间复杂度**:$O(n^2)$,其中 $n$ 是皇后数量。递归调用层数不会超过 $n$,每个棋盘的空间复杂度为 $O(n^2)$,所以空间复杂度为 $O(n^2)$。 diff --git a/docs/solutions/0001-0099/palindrome-number.md b/docs/solutions/0001-0099/palindrome-number.md new file mode 100644 index 00000000..ad55c7c9 --- /dev/null +++ b/docs/solutions/0001-0099/palindrome-number.md @@ -0,0 +1,36 @@ +# [0009. 回文数](https://leetcode.cn/problems/palindrome-number/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [0009. 回文数 - 力扣](https://leetcode.cn/problems/palindrome-number/) + +## 题目大意 + +给定整数 x,判断 x 是否是回文数。要求不能用整数转为字符串的方式来解决这个问题。 + +回文数指的是正序(从左向右)和倒序(从右向左)读都是一样的整数。比如 12321。 + +## 解题思路 + +- 首先,负数,10 的倍数都不是回文数,可以直接排除。 +- 然后将原数进行按位取余,并按位反转,若与原数完全相等,则原数为回文数。 +- 其实,第二步在反转到一半的时候,就可以进行判断了。因为原数是回文数,那么在反转到中间的时候,留下的前半部分,应该与转换好的后半部分倒转过来相等。比如:1221,转换到一半,原数变为 12,转换好的数变为 12,则说明原数就是回文数。如果原数为奇数,比如:12321,转换到一半,原数变为 12,转换好的数变为 123,则应该将原数与 转换好的数对 10 取余的部分进行比较。 + +## 代码 + +```python +class Solution: + def isPalindrome(self, x: int) -> bool: + if x < 0 or (x % 10 == 0 and x != 0): + return False + + res = 0 + while x > res: + res = res * 10 + x % 10 + x = x // 10 + return x == res or x == res // 10 +``` + diff --git a/docs/solutions/0001-0099/permutations-ii.md b/docs/solutions/0001-0099/permutations-ii.md new file mode 100644 index 00000000..c0978fa2 --- /dev/null +++ b/docs/solutions/0001-0099/permutations-ii.md @@ -0,0 +1,85 @@ +# [0047. 全排列 II](https://leetcode.cn/problems/permutations-ii/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0047. 全排列 II - 力扣](https://leetcode.cn/problems/permutations-ii/) + +## 题目大意 + +**描述**:给定一个可包含重复数字的序列 `nums`。 + +**要求**:按任意顺序返回所有不重复的全排列。 + +**说明**: + +- $1 \le nums.length \le 8$。 +- $-10 \le nums[i] \le 10$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,2] +输出:[[1,1,2],[1,2,1],[2,1,1]] +``` + +- 示例 2: + +```python +输入:nums = [1,2,3] +输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +这道题跟「[0046. 全排列](https://leetcode.cn/problems/permutations/)」不一样的地方在于增加了序列中的元素可重复这一条件。这就涉及到了如何去重。 + +我们可以先对数组 `nums` 进行排序,然后使用一个数组 `visited` 标记该元素在当前排列中是否被访问过。 + +如果未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 + +然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 + +然后再进行回溯遍历。 + +### 思路 1:代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, nums: List[int], visited: List[bool]): + if len(self.path) == len(nums): + self.res.append(self.path[:]) + return + for i in range(len(nums)): + if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: + continue + + if not visited[i]: + visited[i] = True + self.path.append(nums[i]) + self.backtrack(nums, visited) + self.path.pop() + visited[i] = False + + def permuteUnique(self, nums: List[int]) -> List[List[int]]: + self.res.clear() + self.path.clear() + nums.sort() + visited = [False for _ in range(len(nums))] + self.backtrack(nums, visited) + return self.res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times n!)$,其中 $n$ 为数组 `nums` 的元素个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0001-0099/permutations.md b/docs/solutions/0001-0099/permutations.md new file mode 100644 index 00000000..f76d625e --- /dev/null +++ b/docs/solutions/0001-0099/permutations.md @@ -0,0 +1,94 @@ +# [0046. 全排列](https://leetcode.cn/problems/permutations/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0046. 全排列 - 力扣](https://leetcode.cn/problems/permutations/) + +## 题目大意 + +**描述**:给定一个不含重复数字的数组 `nums`。 + +**要求**:返回其有可能的全排列。 + +**说明**: + +- $1 \le nums.length \le 6$ +- $-10 \le nums[i] \le 10$。 +- `nums` 中的所有整数互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3] +输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +``` + +- 示例 2: + +```python +输入:nums = [0,1] +输出:[[0,1],[1,0]] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +根据回溯算法三步走,写出对应的回溯算法。 + +![](https://qcdn.itcharge.cn/images/20220425102048.png) + +1. **明确所有选择**:全排列中每个位置上的元素都可以从剩余可选元素中选出,对此画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 +3. **将决策树和终止条件翻译成代码:** + 1. 定义回溯函数: + - `backtracking(nums):` 函数的传入参数是 `nums`(可选数组列表),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 + - `backtracking(nums):` 函数代表的含义是:递归在 `nums` 中选择剩下的元素。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: + - 约束条件:之前已经选择的元素不再重复选用,只能从剩余元素中选择。 + - 选择元素:将其添加到当前子集数组 `path` 中。 + - 递归搜索:在选择该元素的情况下,继续递归选择剩下元素。 + - 撤销选择:将该元素从当前结果数组 `path` 中移除。 + ```python + for i in range(len(nums)): # 枚举可选元素列表 + if nums[i] not in path: # 从当前路径中没有出现的数字中选择 + path.append(nums[i]) # 选择元素 + backtracking(nums) # 递归搜索 + path.pop() # 撤销选择 + ``` + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是存放当前结果的数组 `path` 的长度等于给定数组 `nums` 的长度(即 `len(path) == len(nums)`)时,递归停止。 + +### 思路 1:代码 + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + res = [] # 存放所有符合条件结果的集合 + path = [] # 存放当前符合条件的结果 + def backtracking(nums): # nums 为选择元素列表 + if len(path) == len(nums): # 说明找到了一组符合条件的结果 + res.append(path[:]) # 将当前符合条件的结果放入集合中 + return + + for i in range(len(nums)): # 枚举可选元素列表 + if nums[i] not in path: # 从当前路径中没有出现的数字中选择 + path.append(nums[i]) # 选择元素 + backtracking(nums) # 递归搜索 + path.pop() # 撤销选择 + + backtracking(nums) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times n!)$,其中 $n$ 为数组 `nums` 的元素个数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0001-0099/plus-one.md b/docs/solutions/0001-0099/plus-one.md new file mode 100644 index 00000000..ad249805 --- /dev/null +++ b/docs/solutions/0001-0099/plus-one.md @@ -0,0 +1,77 @@ +# [0066. 加一](https://leetcode.cn/problems/plus-one/) + +- 标签:数组、数学 +- 难度:简单 + +## 题目链接 + +- [0066. 加一 - 力扣](https://leetcode.cn/problems/plus-one/) + +## 题目大意 + +**描述**:给定一个非负整数数组,数组每一位对应整数的一位数字。 + +**要求**:计算整数加 $1$ 后的结果。 + +**说明**: + +- $1 \le digits.length \le 100$。 +- $0 \le digits[i] \le 9$。 + +**示例**: + +- 示例 1: + +```python +输入:digits = [1,2,3] +输出:[1,2,4] +解释:输入数组表示数字 123,加 1 之后为 124。 +``` + +- 示例 2: + +```python +输入:digits = [4,3,2,1] +输出:[4,3,2,2] +解释:输入数组表示数字 4321。 +``` + +## 解题思路 + +### 思路 1:模拟 + +这道题把整个数组看成了一个整数,然后个位数加 $1$。问题的实质是利用数组模拟加法运算。 + +如果个位数不为 $9$ 的话,直接把个位数加 $1$ 就好。如果个位数为 $9$ 的话,还要考虑进位。 + +具体步骤: + +1. 数组前补 $0$ 位。 +2. 将个位数字进行加 $1$ 计算。 +3. 遍历数组 + 1. 如果该位数字大于等于 $10$,则向下一位进 $1$,继续下一位判断进位。 + 2. 如果该位数字小于 $10$,则跳出循环。 + +### 思路 1:代码 + +```python +def plusOne(self, digits: List[int]) -> List[int]: + digits = [0] + digits + digits[len(digits) - 1] += 1 + for i in range(len(digits)-1, 0, -1): + if digits[i] != 10: + break + else: + digits[i] = 0 + digits[i - 1] += 1 + + if digits[0] == 0: + return digits[1:] + else: + return digits +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$ 。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/powx-n.md b/docs/solutions/0001-0099/powx-n.md new file mode 100644 index 00000000..140e6c9b --- /dev/null +++ b/docs/solutions/0001-0099/powx-n.md @@ -0,0 +1,84 @@ +# [0050. Pow(x, n)](https://leetcode.cn/problems/powx-n/) + +- 标签:递归、数学 +- 难度:中等 + +## 题目链接 + +- [0050. Pow(x, n) - 力扣](https://leetcode.cn/problems/powx-n/) + +## 题目大意 + +**描述**:给定浮点数 $x$ 和整数 $n$。 + +**要求**:计算 $x$ 的 $n$ 次方(即 $x^n$)。 + +**说明**: + +- $-100.0 < x < 100.0$。 +- $-2^{31} \le n \le 2^{31} - 1$。 +- $n$ 是一个整数。 +- $-10^4 \le x^n \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:x = 2.00000, n = 10 +输出:1024.00000 +``` + +- 示例 2: + +```python +输入:x = 2.00000, n = -2 +输出:0.25000 +解释:2-2 = 1/22 = 1/4 = 0.25 +``` + +## 解题思路 + +### 思路 1:分治算法 + +常规方法是直接将 $x$ 累乘 $n$ 次得出结果,时间复杂度为 $O(n)$。 + +我们可以利用分治算法来减少时间复杂度。 + +根据 $n$ 的奇偶性,我们可以得到以下结论: + +1. 如果 $n$ 为偶数,$x^n = x^{n / 2} \times x^{n / 2}$。 +2. 如果 $n$ 为奇数,$x^n = x \times x^{(n - 1) / 2} \times x^{(n - 1) / 2}$。 + +$x^{(n / 2)}$ 或 $x^{(n - 1) / 2}$ 又可以继续向下递归划分。 + +则我们可以利用低纬度的幂计算结果,来得到高纬度的幂计算结果。 + +这样递归求解,时间复杂度为 $O(\log n)$,并且递归也可以转为递推来做。 + +需要注意如果 $n$ 为负数,可以转换为 $\frac{1}{x} ^{(-n)}$。 + +### 思路 1:代码 + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if x == 0.0: + return 0.0 + res = 1 + if n < 0: + x = 1/x + n = -n + while n: + if n & 1: + res *= x + x *= x + n >>= 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/regular-expression-matching.md b/docs/solutions/0001-0099/regular-expression-matching.md new file mode 100644 index 00000000..fa4cb589 --- /dev/null +++ b/docs/solutions/0001-0099/regular-expression-matching.md @@ -0,0 +1,106 @@ +# [0010. 正则表达式匹配](https://leetcode.cn/problems/regular-expression-matching/) + +- 标签:递归、字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0010. 正则表达式匹配 - 力扣](https://leetcode.cn/problems/regular-expression-matching/) + +## 题目大意 + +**描述**:给定一个字符串 `s` 和一个字符模式串 `p`。 + +**要求**:实现一个支持 `'.'` 和 `'*'` 的正则表达式匹配。两个字符串完全匹配才算匹配成功。如果匹配成功,则返回 `True`,否则返回 `False`。 + +- `'.'` 匹配任意单个字符。 +- `'*'` 匹配零个或多个前面的那一个元素。 + +**说明**: + +- $1 \le s.length \le 20$。 +- $1 \le p.length \le 30$。 +- `s` 只包含从 `a` ~ `z` 的小写字母。 +- `p` 只包含从 `a` ~ `z` 的小写字母,以及字符 `.` 和 `*`。 +- 保证每次出现字符 `*` 时,前面都匹配到有效的字符。 + +**示例**: + +- 示例 1: + +```python +输入:s = "aa", p = "a*" +输出:True +解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。 +``` + +- 示例 2: + +```python +输入:s = "aa", p = "a" +输出:False +解释:"a" 无法匹配 "aa" 整个字符串。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。 + +###### 3. 状态转移方程 + +- 如果 `s[i - 1] == p[j - 1]`,则字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的。此时「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j - 1] `。 +- 如果 `p[j - 1] == '.'`,则字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的(同上)。此时 `dp[i][j] = dp[i - 1][j - 1] `。 +- 如果 `p[j - 1] == '*'`,则我们可以对字符 `p[j - 2]` 进行 `0` ~ 若干次数的匹配。 + - 如果 `s[i - 1] != p[j - 2]` 并且 `p[j - 2] != '.'`,则说明当前星号匹配不上,只能匹配 `0` 次(即匹配空字符串),则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」,即 `dp[i][j] = dp[i][j - 2] `。 + - 如果 `s[i - 1] == p[j - 2]` 或者 `p[j - 2] == '.'`,则说明当前星号前面的字符 `p[j - 2]` 可以匹配 `s[i - 1]`。 + - 如果匹配 `0` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 2]`。 + - 如果匹配 `1` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 1] `。 + - 如果匹配多个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j]`。 + +###### 4. 初始条件 + +- 默认状态下,两个空字符串是匹配的,即 `dp[0][0] = True`。 +- 当字符串 `s` 为空,字符串 `p` 右端有 `*` 时,想要匹配,则如果「空字符串」与「去掉字符串 `p` 右端的 `*` 和 `*` 之前的字符之后的字符串」匹配的话,则空字符串与字符串 `p` 匹配。也就是说如果 `p[j - 1] == '*'`,则 `dp[0][j] = dp[0][j - 2]`。 + +###### 5. 最终结果 + +根据我们之前定义的状态, `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。则最终结果为 `dp[size_s][size_p]`,其实 `size_s` 是字符串 `s` 的长度,`size_p` 是字符串 `p` 的长度。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def isMatch(self, s: str, p: str) -> bool: + size_s, size_p = len(s), len(p) + dp = [[False for _ in range(size_p + 1)] for _ in range(size_s + 1)] + + dp[0][0] = True + for j in range(1, size_p + 1): + if p[j - 1] == '*': + dp[0][j] = dp[0][j - 2] + + for i in range(1, size_s + 1): + for j in range(1, size_p + 1): + if s[i - 1] == p[j - 1] or p[j - 1] == '.': + dp[i][j] = dp[i - 1][j - 1] + elif p[j - 1] == '*': + if s[i - 1] != p[j - 2] and p[j - 2] != '.': + dp[i][j] = dp[i][j - 2] + else: + dp[i][j] = dp[i][j - 1] or dp[i][j - 2] or dp[i - 1][j] + + return dp[size_s][size_p] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了两重循环,外层循环遍历的时间复杂度是 $O(m)$,内层循环遍历的时间复杂度是 $O(n)$,所以总体的时间复杂度为 $O(m n)$。 +- **空间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了二维数组保存状态,且第一维的空间复杂度为 $O(m)$,第二位的空间复杂度为 $O(n)$,所以总体的空间复杂度为 $O(m n)$。 diff --git a/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md b/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md new file mode 100644 index 00000000..e5e04b6a --- /dev/null +++ b/docs/solutions/0001-0099/remove-duplicates-from-sorted-array-ii.md @@ -0,0 +1,73 @@ +# [0080. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) + +- 标签:数组、双指针 +- 难度:中等 + +## 题目链接 + +- [0080. 删除有序数组中的重复项 II - 力扣](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) + +## 题目大意 + +**描述**:给定一个有序数组 $nums$。 + +**要求**:在原数组空间基础上删除重复出现 $2$ 次以上的元素,并返回删除后数组的新长度。 + +**说明**: + +- $1 \le nums.length \le 3 * 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $nums$ 已按升序排列。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1,2,2,3] +输出:5, nums = [1,1,2,2,3] +解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。 +``` + +- 示例 2: + +```python +输入:nums = [0,0,1,1,1,1,2,3,3] +输出:7, nums = [0,0,1,1,2,3,3] +解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 不需要考虑数组中超出新长度后面的元素。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +因为数组是有序的,所以重复元素必定是连续的。可以使用快慢指针来解决。具体做法如下: + +1. 使用两个指针 $slow$,$fast$。$slow$ 指针指向即将放置元素的位置,$fast$ 指针指向当前待处理元素。 +2. 本题要求相同元素最多出现 $2$ 次,并且 $slow - 2$ 是上上次放置了元素的位置。则应该检查 $nums[slow - 2]$ 和当前待处理元素 $nums[fast]$ 是否相同。 + 1. 如果 $nums[slow - 2] == nums[fast]$ 时,此时必有 $nums[slow - 2] == nums[slow - 1] == nums[fast]$,则当前 $nums[fast]$ 不保留,直接向右移动快指针 $fast$。 + 2. 如果 $nums[slow - 2] \ne nums[fast]$ 时,则保留 $nums[fast]$。将 $nums[fast]$ 赋值给 $nums[slow]$ ,同时将 $slow$ 右移。然后再向右移动快指针 $fast$。 +3. 这样 $slow$ 指针左边均为处理好的数组元素,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边都为舍弃的重复元素。 +4. 遍历结束之后,此时 $slow$ 就是新数组的长度。 + +### 思路 1:代码 + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + size = len(nums) + if size <= 2: + return size + slow, fast = 2, 2 + while (fast < size): + if nums[slow - 2] != nums[fast]: + nums[slow] = nums[fast] + slow += 1 + fast += 1 + return slow +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md b/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md new file mode 100644 index 00000000..b1709b79 --- /dev/null +++ b/docs/solutions/0001-0099/remove-duplicates-from-sorted-array.md @@ -0,0 +1,77 @@ +# [0026. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) + +- 标签:数组、双指针 +- 难度:简单 + +## 题目链接 + +- [0026. 删除有序数组中的重复项 - 力扣](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) + +## 题目大意 + +**描述**:给定一个有序数组 `nums`。 + +**要求**:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 + +**说明**: + +- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,2] +输出:2, nums = [1,2,_] +解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 +``` + +- 示例 2: + +```python +输入:nums = [0,0,1,1,1,2,2,3,3,4] +输出:5, nums = [0,1,2,3,4] +解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +因为数组是有序的,那么重复的元素一定会相邻。 + +删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: + +1. 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向去除重复元素后的数组的末尾位置。`fast` 指向当前元素。 +2. 令 `slow` 在后, `fast` 在前。令 `slow = 0`,`fast = 1`。 +3. 比较 `slow` 位置上元素值和 `fast` 位置上元素值是否相等。 + - 如果不相等,则将 `slow` 后移一位,将 `fast` 指向位置的元素复制到 `slow` 位置上。 +4. 将 `fast` 右移 `1` 位。 +5. 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 +6. 返回 `slow + 1` 即为新数组长度。 + +### 思路 1:代码 + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + if len(nums) <= 1: + return len(nums) + + slow, fast = 0, 1 + + while (fast < len(nums)): + if nums[slow] != nums[fast]: + slow += 1 + nums[slow] = nums[fast] + fast += 1 + + return slow + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md b/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md new file mode 100644 index 00000000..a0e2b2d1 --- /dev/null +++ b/docs/solutions/0001-0099/remove-duplicates-from-sorted-list-ii.md @@ -0,0 +1,72 @@ +# [0082. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) + +- 标签:链表、双指针 +- 难度:中等 + +## 题目链接 + +- [0082. 删除排序链表中的重复元素 II - 力扣](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) + +## 题目大意 + +**描述**:给定一个已排序的链表的头 $head$。 + +**要求**:删除原始链表中所有重复数字的节点,只留下不同的数字。返回已排序的链表。 + +**说明**: + +- 链表中节点数目在范围 $[0, 300]$ 内。 +- $-100 \le Node.val \le 100$。 +- 题目数据保证链表已经按升序排列。 + +**示例**: + +- 示例 1: + +```python +输入:head = [1,2,3,3,4,4,5] +输出:[1,2,5] +``` + +## 解题思路 + +### 思路 1:遍历 + +这道题的题意是需要保留所有不同数字,而重复出现的所有数字都要删除。因为给定的链表是升序排列的,所以我们要删除的重复元素在链表中的位置是连续的。所以我们可以对链表进行一次遍历,然后将连续的重复元素从链表中删除即可。具体步骤如下: + +- 先使用哑节点 $dummy\underline{\hspace{0.5em}}head$ 构造一个指向 $head$ 的指针,使得可以防止从 $head$ 开始就是重复元素。 +- 然后使用指针 $cur$ 表示链表中当前元素,从 $head$ 开始遍历。 +- 当指针 $cur$ 的下一个元素和下下一个元素存在时: + - 如果下一个元素值和下下一个元素值相同,则我们使用指针 $temp$ 保存下一个元素,并使用 $temp$ 向后遍历,跳过所有重复元素,然后令 $cur$ 的下一个元素指向 $temp$ 的下一个元素,继续向后遍历。 + - 如果下一个元素值和下下一个元素值不同,则令 $cur$ 向右移动一位,继续向后遍历。 +- 当指针 $cur$ 的下一个元素或者下下一个元素不存在时,说明已经遍历完,则返回哑节点 $dummy\underline{\hspace{0.5em}}head$ 的下一个节点作为头节点。 + +### 思路 1:代码 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def deleteDuplicates(self, head: ListNode) -> ListNode: + dummy_head = ListNode(-1) + dummy_head.next = head + + cur = dummy_head + while cur.next and cur.next.next: + if cur.next.val == cur.next.next.val: + temp = cur.next + while temp and temp.next and temp.val == temp.next.val: + temp = temp.next + cur.next = temp.next + else: + cur = cur.next + return dummy_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为链表长度。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md b/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md new file mode 100644 index 00000000..f4f0f90a --- /dev/null +++ b/docs/solutions/0001-0099/remove-duplicates-from-sorted-list.md @@ -0,0 +1,61 @@ +# [0083. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) + +- 标签:链表 +- 难度:简单 + +## 题目链接 + +- [0083. 删除排序链表中的重复元素 - 力扣](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) + +## 题目大意 + +**描述**:给定一个已排序的链表的头 $head$。 + +**要求**:删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。 + +**说明**: + +- 链表中节点数目在范围 $[0, 300]$ 内。 +- $-100 \le Node.val \le 100$。 +- 题目数据保证链表已经按升序排列。 + +**示例**: + +- 示例 1: + +```python +输入:head = [1,1,2,3,3] +输出:[1,2,3] +``` + +## 解题思路 + +### 思路 1:遍历 + +- 使用指针 $curr$ 遍历链表,先将 $head$ 保存到 $curr$ 指针。 +- 判断当前元素的值和当前元素下一个节点元素值是否相等。 +- 如果相等,则让当前指针指向当前指针下两个节点。 +- 否则,让 $curr$ 继续向后遍历。 +- 遍历完之后返回头节点 $head$。 + +### 思路 1:遍历代码 + +```python +class Solution: + def deleteDuplicates(self, head: ListNode) -> ListNode: + if head == None: + return head + + curr = head + while curr.next: + if curr.val == curr.next.val: + curr.next = curr.next.next + else: + curr = curr.next + return head +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为链表长度。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/remove-element.md b/docs/solutions/0001-0099/remove-element.md new file mode 100644 index 00000000..b5cfa25a --- /dev/null +++ b/docs/solutions/0001-0099/remove-element.md @@ -0,0 +1,68 @@ +# [0027. 移除元素](https://leetcode.cn/problems/remove-element/) + +- 标签:数组、双指针 +- 难度:简单 + +## 题目链接 + +- [0027. 移除元素 - 力扣](https://leetcode.cn/problems/remove-element/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,和一个值 $val$。 + +**要求**:不使用额外数组空间,将数组中所有数值等于 $val$ 值的元素移除掉,并且返回新数组的长度。 + +**说明**: + +- $0 \le nums.length \le 100$。 +- $0 \le nums[i] \le 50$。 +- $0 \le val \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,2,2,3], val = 3 +输出:2, nums = [2,2] +解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。 +``` + +- 示例 2: + +```python +输入:nums = [0,1,2,2,3,0,4,2], val = 2 +输出:5, nums = [0,1,4,0,3] +解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +1. 使用两个指针 $slow$,$fast$。$slow$ 指向处理好的非 $val$ 值元素数组的尾部,$fast$ 指针指向当前待处理元素。 +2. 不断向右移动 $fast$ 指针,每次移动到非 $val$ 值的元素,则将左右指针对应的数交换,交换同时将 $slow$ 右移。 +3. 这样就将非 $val$ 值的元素进行前移,$slow$ 指针左边均为处理好的非 $val$ 值元素,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边都为 $val $值。 +4. 遍历结束之后,则所有 $val$ 值元素都移动到了右侧,且保持了非零数的相对位置。此时 $slow$ 就是新数组的长度。 + +### 思路 1:代码 + +```python +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + slow = 0 + fast = 0 + while fast < len(nums): + if nums[fast] != val: + nums[slow], nums[fast] = nums[fast], nums[slow] + slow += 1 + fast += 1 + return slow +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md b/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md new file mode 100644 index 00000000..a2b3a524 --- /dev/null +++ b/docs/solutions/0001-0099/remove-nth-node-from-end-of-list.md @@ -0,0 +1,74 @@ +# [0019. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) + +- 标签:链表、双指针 +- 难度:中等 + +## 题目链接 + +- [0019. 删除链表的倒数第 N 个结点 - 力扣](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。 + +**说明**: + +- 要求使用一次遍历实现。 +- 链表中结点的数目为 `sz`。 +- $1 \le sz \le 30$。 +- $0 \le Node.val \le 100$。 +- $1 \le n \le sz$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg) + +```python +输入:head = [1,2,3,4,5], n = 2 +输出:[1,2,3,5] +``` + +- 示例 2: + +```python +输入:head = [1], n = 1 +输出:[] +``` + +## 解题思路 + +### 思路 1:快慢指针 + +常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 + +如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `n` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `n` 个节点位置。将该位置上的节点删除即可。 + +需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 + +### 思路 1:代码 + +```python +class Solution: + def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: + newHead = ListNode(0, head) + fast = head + slow = newHead + while n: + fast = fast.next + n -= 1 + while fast: + fast = fast.next + slow = slow.next + slow.next = slow.next.next + return newHead.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/restore-ip-addresses.md b/docs/solutions/0001-0099/restore-ip-addresses.md new file mode 100644 index 00000000..c5a335ae --- /dev/null +++ b/docs/solutions/0001-0099/restore-ip-addresses.md @@ -0,0 +1,122 @@ +# [0093. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) + +- 标签:字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [0093. 复原 IP 地址 - 力扣](https://leetcode.cn/problems/restore-ip-addresses/) + +## 题目大意 + +**描述**:给定一个只包含数字的字符串 `s`,用来表示一个 IP 地址 + +**要求**:返回所有由 `s` 构成的有效 IP 地址,这些地址可以通过在 `s` 中插入 `'.'` 来形成。不能重新排序或删除 `s` 中的任何数字。可以按任何顺序返回答案。 + +**说明**: + +- **有效 IP 地址**:正好由四个整数(每个整数由 $0 \sim 255$ 的数构成,且不能含有前导 0),整数之间用 `.` 分割。 +- $1 \le s.length \le 20$。 +- `s` 仅由数字组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "25525511135" +输出:["255.255.11.135","255.255.111.35"] +``` + +- 示例 2: + +```python +输入:s = "0000" +输出:["0.0.0.0"] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +一个有效 IP 地址由四个整数构成,中间用 $3$ 个点隔开。现在给定的是无分隔的整数字符串,我们可以通过在整数字符串中间的不同位置插入 $3$ 个点来生成不同的 IP 地址。这个过程可以通过回溯算法来生成。 + +根据回溯算法三步走,写出对应的回溯算法。 + +1. **明确所有选择**:全排列中每个位置上的元素都可以从剩余可选元素中选出。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数: + - `backtracking(index):` 函数的传入参数是 `index`(剩余字符开始位置),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 + - `backtracking(index):` 函数代表的含义是:递归从 `index` 位置开始,从剩下字符中,选择当前子段的值。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑的字符,到字符串结束为止,枚举出所有可作为当前子段值的字符。对于每一个子段值: + - 约束条件:只能从 `index` 位置开始选择,并且要符合规则要求。 + - 选择元素:将其添加到当前子集数组 `path` 中。 + - 递归搜索:在选择该子段值的情况下,继续递归从剩下字符中,选择下一个子段值。 + - 撤销选择:将该子段值从当前结果数组 `path` 中移除。 + ```python + for i in range(index, len(s)): # 枚举可选元素列表 + sub = s[index: i + 1] + # 如果当前值不在 0 ~ 255 之间,直接跳过 + if int(sub) > 255: + continue + # 如果当前值为 0,但不是单个 0("00..."),直接跳过 + if int(sub) == 0 and i != index: + continue + # 如果当前值大于 0,但是以 0 开头("0XX..."),直接跳过 + if int(sub) > 0 and s[index] == '0': + continue + + path.append(sub) # 选择元素 + backtracking(i + 1) # 递归搜索 + path.pop() # 撤销选择 + ``` + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是存放当前结果的数组 `path` 的长度等于 $4$,并且剩余字符开始位置为字符串结束位置(即 `len(path) == 4 and index == len(s)`)时,递归停止。 + - 如果回溯过程中,切割次数大于 4(即 `len(path) > 4`),递归停止,直接返回。 + +### 思路 1:代码 + +```python +class Solution: + def restoreIpAddresses(self, s: str) -> List[str]: + res = [] + path = [] + def backtracking(index): + # 如果切割次数大于 4,直接返回 + if len(path) > 4: + return + + # 切割完成,将当前结果加入答案结果数组中 + if len(path) == 4 and index == len(s): + res.append('.'.join(path)) + return + + for i in range(index, len(s)): + sub = s[index: i + 1] + # 如果当前值不在 0 ~ 255 之间,直接跳过 + if int(sub) > 255: + continue + # 如果当前值为 0,但不是单个 0("00..."),直接跳过 + if int(sub) == 0 and i != index: + continue + # 如果当前值大于 0,但是以 0 开头("0XX..."),直接跳过 + if int(sub) > 0 and s[index] == '0': + continue + + path.append(sub) + backtracking(i + 1) + path.pop() + + + backtracking(0) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(3^4 \times |s|)$,其中 $|s|$ 是字符串 `s` 的长度。由于 IP 地址的每一子段位数不会超过 $3$,因此在递归时,我们最多只会深入到下一层中的 $3$ 种情况。而 IP 地址由 $4$ 个子段构成,所以递归的最大层数为 $4$ 层,则递归的时间复杂度为 $O(3^4)$。而每次将有效的 IP 地址添加到答案数组的时间复杂度为 $|s|$,所以总的时间复杂度为 $3^4 \times |s|$。 +- **空间复杂度**:$O(|s|)$,只记录除了用来存储答案数组之外的空间复杂度。 + diff --git a/docs/solutions/0001-0099/reverse-integer.md b/docs/solutions/0001-0099/reverse-integer.md new file mode 100644 index 00000000..2fb6d63b --- /dev/null +++ b/docs/solutions/0001-0099/reverse-integer.md @@ -0,0 +1,56 @@ +# [0007. 整数反转](https://leetcode.cn/problems/reverse-integer/) + +- 标签:数学 +- 难度:中等 + +## 题目链接 + +- [0007. 整数反转 - 力扣](https://leetcode.cn/problems/reverse-integer/) + +## 题目大意 + +给定一个 32 位有符号整数 x,将 x 进行反转。 + +## 解题思路 + +x 的范围为 $[-2^{31}, 2^{31}-1]$,即 $[-2147483648 ,2147483647]$。 + +反转的步骤就是让 x 不断对 10 取余,再除以 10,得到每一位的数字,同时累积结果。 + +注意累积结果的时候需要判断是否溢出。 + +当 ans * 10 + pop > INT_MAX ,或者 ans * 10 + pop < INT_MIN 时就会溢出。 + +按题设要求,无法在溢出之后对其判断。那么如何在进行累积操作之前判断溢出呢? + +ans * 10 + pop > INT_MAX 有两种情况: + +1. ans > INT_MAX / 10,这种情况下,无论是否考虑 pop 进位都会溢出; +2. ans == INT_MAX / 10,这种情况下,考虑进位,如果 pop 大于 IN_MAX 的个位数,就会导致溢出。 + +同理 ans * 10 + pop < INT_MIN 也有两种情况: + +1. ans < INT_MIN / 10 +2. ans == INT_MIN / 10 且 pop < INT_MIN 的个位数,就会导致溢出 + +## 代码 + +```python +class Solution: + def reverse(self, x: int) -> int: + INT_MAX_10 = (1<<31)//10 + INT_MIN_10 = int((-1<<31)/10) + INT_MAX_LAST = (1<<31) % 10 + INT_MIN_LAST = (-1<<31) % -10 + ans = 0 + while x: + pop = x % 10 if x > 0 else x % -10 + x = x // 10 if x > 0 else int(x / 10) + if ans > INT_MAX_10 or (ans == INT_MAX_10 and pop > INT_MAX_LAST): + return 0 + if ans < INT_MIN_10 or (ans == INT_MIN_10 and pop < INT_MIN_LAST): + return 0 + ans = ans*10+pop + return ans +``` + diff --git a/docs/solutions/0001-0099/reverse-linked-list-ii.md b/docs/solutions/0001-0099/reverse-linked-list-ii.md new file mode 100644 index 00000000..d68daf7e --- /dev/null +++ b/docs/solutions/0001-0099/reverse-linked-list-ii.md @@ -0,0 +1,149 @@ +# [0092. 反转链表 II ](https://leetcode.cn/problems/reverse-linked-list-ii/) + +- 标签:链表 +- 难度:中等 + +## 题目链接 + +- [0092. 反转链表 II - 力扣](https://leetcode.cn/problems/reverse-linked-list-ii/) + +## 题目大意 + +**描述**:给定单链表的头指针 `head` 和两个整数 `left` 和 `right` ,其中 `left <= right`。 + +**要求**:反转从位置 `left` 到位置 `right` 的链表节点,返回反转后的链表 。 + +**说明**: + +- 链表中节点数目为 `n`。 +- $1 \le n \le 500$。 +- $-500 \le Node.val \le 500$。 +- $1 \le left \le right \le n$。 + +**示例**: + +- 示例 1: + +```python +输入:head = [1,2,3,4,5], left = 2, right = 4 +输出:[1,4,3,2,5] +``` + +## 解题思路 + +在「[0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)」中我们可以通过迭代、递归两种方法将整个链表反转。这道题而这道题要求对链表的部分区间进行反转。我们同样可以通过迭代、递归两种方法将链表的部分区间进行反转。 + +### 思路 1:迭代 + +我们可以先遍历到需要反转的链表区间的前一个节点,然后对需要反转的链表区间进行迭代反转。最后再返回头节点即可。 + +但是需要注意一点,如果需要反转的区间包含了链表的第一个节点,那么我们可以事先创建一个哑节点作为链表初始位置开始遍历,这样就能避免找不到需要反转的链表区间的前一个节点。 + +这道题的具体解题步骤如下: + +1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。使用 `index` 记录当前元素的序号。 +2. 我们使用一个指针 `reverse_start`,初始赋值为 `dummy_head`。然后向右逐步移动到需要反转的区间的前一个节点。 +3. 然后再使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置,即 `pre` 指向需要反转节点的前一个节点,`cur` 指向需要反转的节点。初始时,`pre` 指向 `reverse_start`,`cur` 指向 `pre.next`。 +4. 当当前节点 `cur` 不为空,且 `index` 在反转区间内时,将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: + 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; + 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; + 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; + 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 + 5. 然后令 `index` 加 `1`。 +5. 继续执行第 `4` 步中的 `1`、`2`、`3`、`4`、`5` 步。 +6. 最后等到 `cur` 遍历到链表末尾(即 `cur == None`)或者遍历到需要反转区间的末尾时(即 `index > right`) 时,将反转区间的头尾节点分别与之前保存的需要反转的区间的前一个节点 `reverse_start` 相连,即 `reverse_start.next.next = cur`,`reverse_start.next = pre`。 +7. 最后返回新的头节点 `dummy_head.next`。 + +### 思路 1:代码 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode: + index = 1 + dummy_head = ListNode(0) + dummy_head.next = head + pre = dummy_head + + reverse_start = dummy_head + while reverse_start.next and index < left: + reverse_start = reverse_start.next + index += 1 + + pre = reverse_start + cur = pre.next + while cur and index <= right: + next = cur.next + cur.next = pre + pre = cur + cur = next + index += 1 + + reverse_start.next.next = cur + reverse_start.next = pre + + return dummy_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是链表节点个数。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:递归算法 + +#### 1. 翻转链表前 n 个节点 + +1. 当 `left == 1` 时,无论 `right` 等于多少,实际上都是将当前链表到 `right` 部分进行翻转,也就是将前 `right` 个节点进行翻转。 + +2. 我们可以先定义一个递归函数 `reverseN(self, head, n)`,含义为:将链表前第 $n$ 个节点位置进行翻转。 + 1. 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表的的前 $n - 1$ 个位置进行反转,并返回该链表的新头节点 `new_head`。 + 2. 然后改变 `head`(原先头节点)和 `new_head`(新头节点)之间的指向关系,即将 `head` 指向的节点作为 `head` 下一个节点的下一个节点。 + 3. 先保存 `head.next` 的 `next` 指针,也就是新链表前 $n$ 个节点的尾指针,即 `last = head.next.next`。 + 4. 将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 + 5. 然后让当前节点 `head` 的 `next` 指针指向 `last`,则完成了前 $n - 1$ 个位置的翻转。 + +3. 递归终止条件:当 `n == 1` 时,相当于翻转第一个节点,直接返回 `head` 即可。 + +4. #### 翻转链表 `[left, right]` 上的节点。 + +接下来我们来翻转区间上的节点。 + +1. 定义递归函数 `reverseBetween(self, head, left, right)` 为 +2. + +### 思路 2:代码 + +```python +class Solution: + def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]: + if left == 1: + return self.reverseN(head, right) + + head.next = self.reverseBetween(head.next, left - 1, right - 1) + return head + + def reverseN(self, head, n): + if n == 1: + return head + last = self.reverseN(head.next, n - 1) + next = head.next.next + head.next.next = head + head.next = next + return last +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。最多需要 $n$ 层栈空间。 + +## 参考资料 + +- 【题解】[动画图解:翻转链表的指定区间 - 反转链表 II - 力扣](https://leetcode.cn/problems/reverse-linked-list-ii/solution/dong-hua-tu-jie-fan-zhuan-lian-biao-de-z-n4px/) +- 【题解】[【宫水三叶】一个能应用所有「链表」题里的「哨兵」技巧 - 反转链表 II - 力扣](https://leetcode.cn/problems/reverse-linked-list-ii/solution/yi-ge-neng-ying-yong-suo-you-lian-biao-t-vjx6/) + diff --git a/docs/solutions/0001-0099/reverse-nodes-in-k-group.md b/docs/solutions/0001-0099/reverse-nodes-in-k-group.md new file mode 100644 index 00000000..2df831e3 --- /dev/null +++ b/docs/solutions/0001-0099/reverse-nodes-in-k-group.md @@ -0,0 +1,107 @@ +# [0025. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) + +- 标签:递归、链表 +- 难度:困难 + +## 题目链接 + +- [0025. K 个一组翻转链表 - 力扣](https://leetcode.cn/problems/reverse-nodes-in-k-group/) + +## 题目大意 + +**描述**:给你链表的头节点 `head` ,再给定一个正整数 `k`,`k` 的值小于或等于链表的长度。 + +**要求**:每 `k` 个节点一组进行翻转,并返回修改后的链表。如果链表节点总数不是 `k` 的整数倍,则将最后剩余的节点保持原有顺序。 + +**说明**: + +- 不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。 +- 假设链表中的节点数目为 `n`。 +- $1 \le k \le n \le 5000$。 +- $0 \le Node.val \le 1000$。 +- 要求设计一个只用 `O(1)` 额外内存空间的算法解决此问题。 + +**示例**: + +- 示例 1: + +```python +输入:head = [1,2,3,4,5], k = 2 +输出:[2,1,4,3,5] +``` + +## 解题思路 + +### 思路 1:迭代 + +在「[0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)」中我们可以通过迭代、递归两种方法将整个链表反转。而这道题要求以 `k` 为单位,对链表的区间进行反转。而区间反转其实就是「[0092. 反转链表 II ](https://leetcode.cn/problems/reverse-linked-list-ii/)」这道题的题目要求。 + +本题中,我们可以以 `k` 为单位对链表进行切分,然后分别对每个区间部分进行反转。最后再返回头节点即可。 + +但是需要注意一点,如果需要反转的区间包含了链表的第一个节点,那么我们可以事先创建一个哑节点作为链表初始位置开始遍历,这样就能避免找不到需要反转的链表区间的前一个节点。 + +这道题的具体解题步骤如下: + +1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,避免找不到需要反转的链表区间的前一个节点。使用变量 `index` 记录当前元素的序号。 +2. 使用两个指针 `cur`、`tail` 分别表示链表中待反转区间的首尾节点。初始 `cur` 赋值为 `dummy_head`,`tail` 赋值为 `dummy_head.next`,也就是 `head`。 +3. 将 `tail` 向右移动,每移动一步,就领 `index` 加 `1`。 + 1. 当 `index % k != 0` 时,直接将 `tail` 向右移动,直到移动到当前待反转区间的结尾位置。 + 2. 当 `index % k == 0` 时,说明 `tail` 已经移动到了当前待反转区间的结尾位置,此时调用 `cur = self.reverse(cur, tail.next)` ,将待反转区间进行反转,并返回反转后区间的起始节点赋值给当前反转区间的首节点 `cur`。然后将 `tail` 移动到 `cur` 的下一个节点。 +4. 最后返回新的头节点 `dummy_head.next`。 + +关于 `def reverse(self, head, tail):` 方法这里也说下具体步骤: + +1. `head` 代表当前待反转区间的第一个节点的前一个节点,`tail` 代表当前待反转区间的最后一个节点的后一个节点。 +2. 先用 `first` 保存一下待反转区间的第一个节点(反转之后为区间的尾节点),方便反转之后进行连接。 +3. 我们使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置,即 `pre` 指向需要反转节点的前一个节点,`cur` 指向需要反转的节点。初始时,`pre` 指向待反转区间的第一个节点的前一个节点 `head`,`cur` 指向待反转区间的第一个节点,即 `pre.next`。 +4. 当当前节点 `cur` 不等于 `tail` 时,将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: + 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; + 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; + 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; + 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 +5. 继续执行第 `4` 步中的 `1`、`2`、`3`、`4`步。 +6. 最后等到 `cur` 遍历到链表末尾(即 `cur == tail`)时,令「当前待反转区间的第一个节点的前一个节点」指向「反转区间后的头节点」 ,即 `head.next = pre`。令「待反转区间的第一个节点(反转之后为区间的尾节点)」指向「待反转分区间的最后一个节点的后一个节点」,即 `first.next = tail`。 +7. 最后返回新的头节点 `dummy_head.next`。 + +### 思路 1:代码 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverse(self, head, tail): + pre = head + cur = pre.next + first = cur + while cur != tail: + next = cur.next + cur.next = pre + pre = cur + cur = next + head.next = pre + first.next = tail + return first + + def reverseKGroup(self, head: ListNode, k: int) -> ListNode: + dummy_head = ListNode(0) + dummy_head.next = head + cur = dummy_head + tail = dummy_head.next + index = 0 + while tail: + index += 1 + if index % k == 0: + cur = self.reverse(cur, tail.next) + tail = cur.next + else: + tail = tail.next + return dummy_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为链表的总长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0001-0099/roman-to-integer.md b/docs/solutions/0001-0099/roman-to-integer.md new file mode 100644 index 00000000..a6537c82 --- /dev/null +++ b/docs/solutions/0001-0099/roman-to-integer.md @@ -0,0 +1,50 @@ +# [0013. 罗马数字转整数](https://leetcode.cn/problems/roman-to-integer/) + +- 标签:哈希表、数学、字符串 +- 难度:简单 + +## 题目链接 + +- [0013. 罗马数字转整数 - 力扣](https://leetcode.cn/problems/roman-to-integer/) + +## 题目大意 + +给定一个罗马数字对应的字符串,将其转换为整数。 + +罗马数字规则: + +- I 代表数值 1,V 代表数值 5,X 代表数值 10,L 代表数值 50,C 代表数值 100,D 代表数值 500,M 代表数值 1000; +- 一般罗马数字较大数字在左边,较小数字在右边,此时值为两者之和,比如 XI = X + I = 10 + 1 = 11。 +- 例外情况下,较小数字在左边,较大数字在右边,此时值为后者减前者之差,比如 IX = X - I = 10 - 1 = 9。 + +## 解题思路 + +用一个哈希表存储罗马数字与对应数值关系。遍历罗马数字对应的字符串,判断相邻两个数大小关系,并计算对应结果。 + +## 代码 + +```python +class Solution: + def romanToInt(self, s: str) -> int: + nunbers = { + "I" : 1, + "V" : 5, + "X" : 10, + "L" : 50, + "C" : 100, + "D" : 500, + "M" : 1000 + } + sum = 0 + pre_num = nunbers[s[0]] + for i in range(1, len(s)): + cur_num = nunbers[s[i]] + if pre_num < cur_num: + sum -= pre_num + else: + sum += pre_num + pre_num = cur_num + sum += pre_num + return sum +``` + diff --git a/docs/solutions/0001-0099/rotate-image.md b/docs/solutions/0001-0099/rotate-image.md new file mode 100644 index 00000000..0531905e --- /dev/null +++ b/docs/solutions/0001-0099/rotate-image.md @@ -0,0 +1,99 @@ +# [0048. 旋转图像](https://leetcode.cn/problems/rotate-image/) + +- 标签:数组、数学、矩阵 +- 难度:中等 + +## 题目链接 + +- [0048. 旋转图像 - 力扣](https://leetcode.cn/problems/rotate-image/) + +## 题目大意 + +**描述**:给定一个 $n \times n$ 大小的二维矩阵(代表图像)$matrix$。 + +**要求**:将二维矩阵 $matrix$ 顺时针旋转 90°。 + +**说明**: + +- 不能使用额外的数组空间。 +- $n == matrix.length == matrix[i].length$。 +- $1 \le n \le 20$。 +- $-1000 \le matrix[i][j] \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/08/28/mat1.jpg) + +```python +输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] +输出:[[7,4,1],[8,5,2],[9,6,3]] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/08/28/mat2.jpg) + +```python +输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] +输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] +``` + +## 解题思路 + +### 思路 1:原地旋转 + +如果使用额外数组空间的话,将对应元素存放到对应位置即可。如果不使用额外的数组空间,则需要观察每一个位置上的点最初位置和最终位置有什么规律。 + +对于矩阵中第 $i$ 行的第 $j$ 个元素,在旋转后,它出现在倒数第 $i$ 列的第 $j$ 个位置。即 $matrixnew[j][n − i − 1] = matrix[i][j]$。 + +而 $matrixnew[j][n - i - 1]$ 的点经过旋转移动到了 $matrix[n − i − 1][n − j − 1]$ 的位置。 + +$matrix[n − i − 1][n − j − 1]$ 位置上的点经过旋转移动到了 $matrix[n − j − 1][i]$ 的位置。 + +$matrix[n− j − 1][i]$ 位置上的点经过旋转移动到了最初的 $matrix[i][j]$ 的位置。 + +这样就形成了一个循环,我们只需要通过一个临时变量 $temp$ 就可以将循环中的元素逐一进行交换。Python 中则可以直接使用语法直接交换。 + +### 思路 1:代码 + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + n = len(matrix) + + for i in range(n // 2): + for j in range((n + 1) // 2): + matrix[i][j], matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1] = matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1], matrix[i][j] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:原地翻转 + +通过观察可以得出:原矩阵可以通过一次「水平翻转」+「主对角线翻转」得到旋转后的二维矩阵。 + +### 思路 2:代码 + +```python +def rotate(self, matrix: List[List[int]]) -> None: + n = len(matrix) + + for i in range(n // 2): + for j in range(n): + matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j] + + for i in range(n): + for j in range(i): + matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/rotate-list.md b/docs/solutions/0001-0099/rotate-list.md new file mode 100644 index 00000000..01c9a67b --- /dev/null +++ b/docs/solutions/0001-0099/rotate-list.md @@ -0,0 +1,43 @@ +# [0061. 旋转链表](https://leetcode.cn/problems/rotate-list/) + +- 标签:链表、双指针 +- 难度:中等 + +## 题目链接 + +- [0061. 旋转链表 - 力扣](https://leetcode.cn/problems/rotate-list/) + +## 题目大意 + +给定一个链表和整数 k,将链表每个节点向右移动 k 个位置。 + +## 解题思路 + +我们可以将链表先连成环,然后将链表在指定位置断开。 + +先遍历一遍,求出链表节点个数 n。注意到 k 可能很大,我们只需将链表右移 k % n 个位置即可。 + +第二次遍历到 n - k % n 的位置,记录下断开后新链表头节点位置,再将其断开并返回新的头节点。 + +## 代码 + +```python +class Solution: + def rotateRight(self, head: ListNode, k: int) -> ListNode: + if k == 0 or not head or not head.next: + return head + curr = head + count = 1 + while curr.next: + count += 1 + curr = curr.next + cut = count - k % count + curr.next = head + while cut: + curr = curr.next + cut -= 1 + newHead = curr.next + curr.next = None + return newHead +``` + diff --git a/docs/solutions/0001-0099/search-a-2d-matrix.md b/docs/solutions/0001-0099/search-a-2d-matrix.md new file mode 100644 index 00000000..2a2596c3 --- /dev/null +++ b/docs/solutions/0001-0099/search-a-2d-matrix.md @@ -0,0 +1,122 @@ +# [0074. 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) + +- 标签:数组、二分查找、矩阵 +- 难度:中等 + +## 题目链接 + +- [0074. 搜索二维矩阵 - 力扣](https://leetcode.cn/problems/search-a-2d-matrix/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的有序二维矩阵 $matrix$。矩阵中每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 $target$。 + +**要求**:判断矩阵中是否存在目标值 $target$。 + +**说明**: + +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 100$。 +- $-10^4 \le matrix[i][j], target \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/05/mat.jpg) + +```python +输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 +输出:True +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/mat2.jpg) + +```python +输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 +输出:False +``` + +## 解题思路 + +### 思路 1:二分查找 + +二维矩阵是有序的,可以考虑使用二分搜索来进行查找。 + +1. 首先二分查找遍历对角线元素,假设对角线元素的坐标为 $(row, col)$。把数组元素按对角线分为右上角部分和左下角部分。 +2. 然后对于当前对角线元素右侧第 $row$ 行、对角线元素下侧第 $col$ 列进行二分查找。 + 1. 如果找到目标,直接返回 `True`。 + 2. 如果找不到目标,则缩小范围,继续查找。 + 3. 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + # 二分查找对角线元素 + def diagonalBinarySearch(self, matrix, diagonal, target): + left = 0 + right = diagonal + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][mid] < target: + left = mid + 1 + else: + right = mid + return left + + def rowBinarySearch(self, matrix, begin, cols, target): + left = begin + right = cols + while left < right: + mid = left + (right - left) // 2 + if matrix[begin][mid] < target: + left = mid + 1 + elif matrix[begin][mid] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= cols and matrix[begin][left] == target + + def colBinarySearch(self, matrix, begin, rows, target): + left = begin + 1 + right = rows + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][begin] < target: + left = mid + 1 + elif matrix[mid][begin] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= rows and matrix[left][begin] == target + + def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: + rows = len(matrix) + if rows == 0: + return False + cols = len(matrix[0]) + if cols == 0: + return False + + min_val = min(rows, cols) + index = self.diagonalBinarySearch(matrix, min_val - 1, target) + if matrix[index][index] == target: + return True + for i in range(index + 1): + row_search = self.rowBinarySearch(matrix, i, cols - 1, target) + col_search = self.colBinarySearch(matrix, i, rows - 1, target) + if row_search or col_search: + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log m + \log n)$,其中 $m$、$n$ 分别是矩阵的行数和列数。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md b/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md new file mode 100644 index 00000000..09a33ad6 --- /dev/null +++ b/docs/solutions/0001-0099/search-in-rotated-sorted-array-ii.md @@ -0,0 +1,123 @@ +# [0081. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0081. 搜索旋转排序数组 II - 力扣](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/) + +## 题目大意 + +**描述**:一个按照升序排列的整数数组 $nums$,在位置的某个下标 $k$ 处进行了旋转操作。(例如:$[0, 1, 2, 5, 6, 8]$ 可能变为 $[5, 6, 8, 0, 1, 2]$)。 + +现在给定旋转后的数组 $nums$ 和一个整数 $target$。 + +**要求**:编写一个函数来判断给定的 $target$ 是否存在与数组中。如果存在则返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le nums.length \le 5000$。 +- $-10^4 \le nums[i] \le 10^4$。 +- 题目数据保证 $nums$ 在预先未知的某个下标上进行了旋转。 +- $-10^4 \le target \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,5,6,0,0,1,2], target = 0 +输出:true +``` + +- 示例 2: + +```python +输入:nums = [2,5,6,0,0,1,2], target = 3 +输出:false +``` + +## 解题思路 + +### 思路 1:二分查找 + +这道题算是「[0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)」的变形,只不过输出变为了判断。 + +原本为升序排列的数组 nums 经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 + +``` + * + * + * + * + * +* +``` + +``` + * + * +* + * + * + * +``` + +最直接的办法就是遍历一遍,找到目标值 target。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 + +我们将旋转后的数组看成左右两个升序部分:左半部分和右半部分。 + +有人会说第一种情况不是只有一个部分吗?其实我们可以把第一种情况中的整个数组看做是左半部分,然后右半部分为空数组。 + +然后创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较,并考虑与 $target$ 的关系。 + +- 如果 $nums[mid] > nums[left]$,则 $mid$ 在左半部分(因为右半部分值都比 $nums[left]$ 小)。 + - 如果 $nums[mid] \ge target$,并且 $target \ge nums[left]$,则 $target$ 在左半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 + - 否则如果 $nums[mid] < target$,则 $target$ 在左半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$。 + - 否则如果 $nums[left] > target$,则 $target$ 在右半部分,应将 $left$ 移动到 $mid + 1$ 位置。 + +- 如果 $nums[mid] < nums[left]$,则 $mid$ 在右半部分(因为右半部分值都比 $nums[left]$ 小)。 + - 如果 $nums[mid] < target$,并且 $target \le nums[right]$,则 $target$ 在右半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 + - 否则如果 $nums[mid] \ge target$,则 $target$ 在右半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 + - 否则如果 $nums[right] < target$,则 $target$ 在左半部分,应将 $right$ 左移到 $mid - 1$ 位置。 +- 最终判断 $nums[left]$ 是否等于 $target$,如果等于,则返回 `True`,否则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> bool: + n = len(nums) + if n == 0: + return False + + left = 0 + right = len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + + if nums[mid] > nums[left]: + if nums[left] <= target and target <= nums[mid]: + right = mid + else: + left = mid + 1 + elif nums[mid] < nums[left]: + if nums[mid] < target and target <= nums[right]: + left = mid + 1 + else: + right = mid + else: + if nums[mid] == target: + return True + else: + left = left + 1 + + return nums[left] == target +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的长度。最坏情况下数组元素均相等且不为 $target$,我们需要访问所有位置才能得出结果。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/search-in-rotated-sorted-array.md b/docs/solutions/0001-0099/search-in-rotated-sorted-array.md new file mode 100644 index 00000000..a7fca5a0 --- /dev/null +++ b/docs/solutions/0001-0099/search-in-rotated-sorted-array.md @@ -0,0 +1,109 @@ +# [0033. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0033. 搜索旋转排序数组 - 力扣](https://leetcode.cn/problems/search-in-rotated-sorted-array/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$,数组中值互不相同。给定的 $nums$ 是经过升序排列后的又进行了「旋转」操作的。再给定一个整数 $target$。 + +**要求**:从 $nums$ 中找到 $target$ 所在位置,如果找到,则返回对应下标,找不到则返回 $-1$。 + +**说明**: + +- 旋转操作:升序排列的数组 nums 在预先未知的第 k 个位置进行了右移操作,变成了 $[nums[k]], nums[k+1], ... , nums[n-1], ... , nums[0], nums[1], ... , nums[k-1]$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [4,5,6,7,0,1,2], target = 0 +输出:4 +``` + +- 示例 2: + +```python +输入:nums = [4,5,6,7,0,1,2], target = 3 +输出:-1 +``` + +## 解题思路 + +### 思路 1:二分查找 + +原本为升序排列的数组 $nums$ 经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 + +```python + * + * + * + * + * +* +``` + +```python + * + * +* + * + * + * +``` + +最直接的办法就是遍历一遍,找到目标值 $target$。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 + +我们将旋转后的数组看成左右两个升序部分:左半部分和右半部分。 + +有人会说第一种情况不是只有一个部分吗?其实我们可以把第一种情况中的整个数组看做是左半部分,然后右半部分为空数组。 + +然后创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较,并考虑与 $target$ 的关系。 + +- 如果 $nums[mid] == target$,说明找到了 $target$,直接返回下标。 +- 如果 $nums[mid] \ge nums[left]$,则 $mid$ 在左半部分(因为右半部分值都比 $nums[left]$ 小)。 + - 如果 $nums[mid] \ge target$,并且 $target \ge nums[left]$,则 $target$ 在左半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 + - 否则如果 $nums[mid] \le target$,则 $target$ 在左半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 + - 否则如果 $nums[left] > target$,则 $target$ 在右半部分,应将 $left$ 移动到 $mid + 1$ 位置。 + +- 如果 $nums[mid] < nums[left]$,则 $mid$ 在右半部分(因为右半部分值都比 $nums[left]$ 小)。 + - 如果 $nums[mid] < target$,并且 $target \le nums[right]$,则 $target$ 在右半部分,并且在 $mid$ 右侧,此时应将 $left$ 右移到 $mid + 1$ 位置。 + - 否则如果 $nums[mid] \ge target$,则 $target$ 在右半部分,并且在 $mid$ 左侧,此时应将 $right$ 左移到 $mid - 1$ 位置。 + - 否则如果 $nums[right] < target$,则 $target$ 在左半部分,应将 $right$ 左移到 $mid - 1$ 位置。 + +### 思路 1:代码 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = left + (right - left) // 2 + if nums[mid] == target: + return mid + + if nums[mid] >= nums[left]: + if nums[mid] > target and target >= nums[left]: + right = mid - 1 + else: + left = mid + 1 + else: + if nums[mid] < target and target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0001-0099/search-insert-position.md b/docs/solutions/0001-0099/search-insert-position.md new file mode 100644 index 00000000..134d20d4 --- /dev/null +++ b/docs/solutions/0001-0099/search-insert-position.md @@ -0,0 +1,70 @@ +# [0035. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [0035. 搜索插入位置 - 力扣](https://leetcode.cn/problems/search-insert-position/) + +## 题目大意 + +**描述**:给定一个排好序的数组 $nums$,以及一个目标值 $target$。 + +**要求**:在数组中找到目标值,并返回下标。如果找不到,则返回目标值按顺序插入数组的位置。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $nums$ 为无重复元素的升序排列数组。 +- $-10^4 \le target \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,5,6], target = 5 +输出:2 +``` + +## 解题思路 + +### 思路 1:二分查找 + +设定左右节点为数组两端,即 `left = 0`,`right = len(nums) - 1`,代表待查找区间为 $[left, right]$(左闭右闭)。 + +取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 + +- 如果 $target == nums[mid]$,则当前中心位置为待插入数组的位置。 +- 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 +- 如果 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 + +直到查找到目标值返回待插入数组的位置,或者等到 $left > right$ 时停止查找,此时 $left$ 所在位置就是待插入数组的位置。 + +### 思路 1:二分查找代码 + +```python +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + size = len(nums) + left, right = 0, size - 1 + + while left <= right: + mid = left + (right - left) // 2 + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + 1 + else: + right = mid - 1 + + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0001-0099/set-matrix-zeroes.md b/docs/solutions/0001-0099/set-matrix-zeroes.md new file mode 100644 index 00000000..4dde4a72 --- /dev/null +++ b/docs/solutions/0001-0099/set-matrix-zeroes.md @@ -0,0 +1,105 @@ +# [0073. 矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) + +- 标签:数组、哈希表、矩阵 +- 难度:中等 + +## 题目链接 + +- [0073. 矩阵置零 - 力扣](https://leetcode.cn/problems/set-matrix-zeroes/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的矩阵 $matrix$。 + +**要求**:如果一个元素为 $0$,则将其所在行和列所有元素都置为 $0$。 + +**说明**: + +- 请使用「原地」算法。 +- $m == matrix.length$。 +- $n == matrix[0].length$。 +- $1 \le m, n \le 200$。 +- $-2^{31} \le matrix[i][j] \le 2^{31} - 1$。 +- **进阶**: + - 一个直观的解决方案是使用 $O(m \times n)$ 的额外空间,但这并不是一个好的解决方案。 + - 一个简单的改进方案是使用 $O(m + n)$ 的额外空间,但这仍然不是最好的解决方案。 + - 你能想出一个仅使用常量空间的解决方案吗? + + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/08/17/mat1.jpg) + +```python +输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] +输出:[[1,0,1],[0,0,0],[1,0,1]] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/08/17/mat2.jpg) + +``` +输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] +输出:[[1,0,1],[0,0,0],[1,0,1]] +``` + +## 解题思路 + +### 思路 1:使用标记变量 + +直观上可以使用两个数组来标记行和列出现 $0$ 的情况,但这样空间复杂度就是 $O(m+n)$ 了,不符合题意。 + +考虑使用数组原本的元素进行记录出现 $0$ 的情况。 + +1. 设定两个变量 $flag\underline{\hspace{0.5em}}row0$、$flag\underline{\hspace{0.5em}}col0$ 来标记第一行、第一列是否出现了 $0$。 +2. 接下来我们使用数组第一行、第一列来标记 $0$ 的情况。 +3. 对数组除第一行、第一列之外的每个元素进行遍历,如果某个元素出现 $0$ 了,则使用数组的第一行、第一列对应位置来存储 $0$ 的标记。 +4. 再对数组除第一行、第一列之外的每个元素进行遍历,通过对第一行、第一列的标记 $0$ 情况,进行置为 $0$ 的操作。 +5. 最后再根据 $flag\underline{\hspace{0.5em}}row0$、$flag\underline{\hspace{0.5em}}col0$ 的标记情况,对第一行、第一列进行置为 $0$ 的操作。 + +### 思路 1:代码 + +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + m = len(matrix) + n = len(matrix[0]) + flag_col0 = False + flag_row0 = False + for i in range(m): + if matrix[i][0] == 0: + flag_col0 = True + break + + for j in range(n): + if matrix[0][j] == 0: + flag_row0 = True + break + + for i in range(1, m): + for j in range(1, n): + if matrix[i][j] == 0: + matrix[i][0] = matrix[0][j] = 0 + + for i in range(1, m): + for j in range(1, n): + if matrix[i][0] == 0 or matrix[0][j] == 0: + matrix[i][j] = 0 + + if flag_col0: + for i in range(m): + matrix[i][0] = 0 + + if flag_row0: + for j in range(n): + matrix[0][j] = 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/sort-colors.md b/docs/solutions/0001-0099/sort-colors.md new file mode 100644 index 00000000..5b7f365a --- /dev/null +++ b/docs/solutions/0001-0099/sort-colors.md @@ -0,0 +1,78 @@ +# [0075. 颜色分类](https://leetcode.cn/problems/sort-colors/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0075. 颜色分类 - 力扣](https://leetcode.cn/problems/sort-colors/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,元素值只有 $0$、$1$、$2$,分别代表红色、白色、蓝色。 + +**要求**:将数组进行排序,使得红色在前,白色在中间,蓝色在最后。 + +**说明**: + +- 要求不使用标准库函数,同时仅用常数空间,一趟扫描解决。 +- $n == nums.length$。 +- $1 \le n \le 300$。 +- $nums[i]$ 为 $0$、$1$ 或 $2$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,0,2,1,1,0] +输出:[0,0,1,1,2,2] +``` + +- 示例 2: + +```python +输入:nums = [2,0,1] +输出:[0,1,2] +``` + +## 解题思路 + +### 思路 1:双指针 + 快速排序思想 + +快速排序算法中的 $partition$ 过程,利用双指针,将序列中比基准数 $pivot$ 大的元素移动到了基准数右侧,将比基准数 $pivot$ 小的元素移动到了基准数左侧。从而将序列分为了三部分:比基准数小的部分、基准数、比基准数大的部分。 + +这道题我们也可以借鉴快速排序算法中的 $partition$ 过程,将 $1$ 作为基准数 $pivot$,然后将序列分为三部分:$0$(即比 $1$ 小的部分)、等于 $1$ 的部分、$2$(即比 $1$ 大的部分)。具体步骤如下: + +1. 使用两个指针 $left$、$right$,分别指向数组的头尾。$left$ 表示当前处理好红色元素的尾部,$right$ 表示当前处理好蓝色的头部。 +2. 再使用一个下标 $index$ 遍历数组,如果遇到 $nums[index] == 0$,就交换 $nums[index]$ 和 $nums[left]$,同时将 $left$ 右移。如果遇到 $nums[index] == 2$,就交换 $nums[index]$ 和 $nums[right]$,同时将 $right$ 左移。 +3. 直到 $index$ 移动到 $right$ 位置之后,停止遍历。遍历结束之后,此时 $left$ 左侧都是红色,$right$ 右侧都是蓝色。 + +注意:移动的时候需要判断 $index$ 和 $left$ 的位置,因为 $left$ 左侧是已经处理好的数组,所以需要判断 $index$ 的位置是否小于 $left$,小于的话,需要更新 $index$ 位置。 + +### 思路 1:代码 + +```python +class Solution: + def sortColors(self, nums: List[int]) -> None: + left = 0 + right = len(nums) - 1 + index = 0 + while index <= right: + if index < left: + index += 1 + elif nums[index] == 0: + nums[index], nums[left] = nums[left], nums[index] + left += 1 + elif nums[index] == 2: + nums[index], nums[right] = nums[right], nums[index] + right -= 1 + else: + index += 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/spiral-matrix-ii.md b/docs/solutions/0001-0099/spiral-matrix-ii.md new file mode 100644 index 00000000..b61ba7f4 --- /dev/null +++ b/docs/solutions/0001-0099/spiral-matrix-ii.md @@ -0,0 +1,67 @@ +# [0059. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) + +- 标签:数组、矩阵、模拟 +- 难度:中等 + +## 题目链接 + +- [0059. 螺旋矩阵 II - 力扣](https://leetcode.cn/problems/spiral-matrix-ii/) + +## 题目大意 + +给你一个正整数 $n$。 + +要求:生成一个包含 $1 \sim n^2$ 的所有元素,且元素按顺时针顺序螺旋排列的 $n \times n$ 正方形矩阵 $matrix$。 + +## 解题思路 + +### 思路 1:模拟 + +这道题跟「[54. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)」思路是一样的。 + +1. 构建一个 $n \times n$ 大小的数组 $matrix$ 存储答案。然后定义一下上、下、左、右的边界。 +2. 然后按照顺时针的顺序从边界上依次给数组 $matrix$ 相应位置赋值。 +3. 当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 +4. 最后返回 $matrix$。 + +### 思路 1:代码 + +```python +class Solution: + def generateMatrix(self, n: int) -> List[List[int]]: + matrix = [[0 for _ in range(n)] for _ in range(n)] + up, down, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 + index = 1 + while True: + for i in range(left, right + 1): + matrix[up][i] = index + index += 1 + up += 1 + if up > down: + break + for i in range(up, down + 1): + matrix[i][right] = index + index += 1 + right -= 1 + if right < left: + break + for i in range(right, left - 1, -1): + matrix[down][i] = index + index += 1 + down -= 1 + if down < up: + break + for i in range(down, up - 1, -1): + matrix[i][left] = index + index += 1 + left += 1 + if left > right: + break + return matrix +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0001-0099/spiral-matrix.md b/docs/solutions/0001-0099/spiral-matrix.md new file mode 100644 index 00000000..b1dcba57 --- /dev/null +++ b/docs/solutions/0001-0099/spiral-matrix.md @@ -0,0 +1,87 @@ +# [0054. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) + +- 标签:数组、矩阵、模拟 +- 难度:中等 + +## 题目链接 + +- [0054. 螺旋矩阵 - 力扣](https://leetcode.cn/problems/spiral-matrix/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的二维矩阵 $matrix$。 + +**要求**:按照顺时针旋转的顺序,返回矩阵中的所有元素。 + +**说明**: + +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 10$。 +- $-100 \le matrix[i][j] \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/13/spiral1.jpg) + +```python +输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] +输出:[1,2,3,6,9,8,7,4,5] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/13/spiral.jpg) + +```python +输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] +输出:[1,2,3,4,8,12,11,10,9,5,6,7] +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 使用数组 $ans$ 存储答案。然后定义一下上、下、左、右的边界。 +2. 然后按照顺时针的顺序从边界上依次访问元素。 +3. 当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 +4. 最后返回答案数组 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + up, down, left, right = 0, len(matrix)-1, 0, len(matrix[0])-1 + ans = [] + while True: + for i in range(left, right + 1): + ans.append(matrix[up][i]) + up += 1 + if up > down: + break + for i in range(up, down + 1): + ans.append(matrix[i][right]) + right -= 1 + if right < left: + break + for i in range(right, left - 1, -1): + ans.append(matrix[down][i]) + down -= 1 + if down < up: + break + for i in range(down, up - 1, -1): + ans.append(matrix[i][left]) + left += 1 + if left > right: + break + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$、$n$ 分别为二维矩阵的行数和列数。 +- **空间复杂度**:$O(m \times n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(m \times n)$。不算上则空间复杂度为 $O(1)$。 + diff --git a/docs/solutions/0001-0099/sqrtx.md b/docs/solutions/0001-0099/sqrtx.md new file mode 100644 index 00000000..85f1299c --- /dev/null +++ b/docs/solutions/0001-0099/sqrtx.md @@ -0,0 +1,65 @@ +# [0069. x 的平方根](https://leetcode.cn/problems/sqrtx/) + +- 标签:数学、二分查找 +- 难度:简单 + +## 题目链接 + +- [0069. x 的平方根 - 力扣](https://leetcode.cn/problems/sqrtx/) + +## 题目大意 + +**要求**:实现 `int sqrt(int x)` 函数。计算并返回 $x$ 的平方根(只保留整数部分),其中 $x$ 是非负整数。 + +**说明**: + +- $0 \le x \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:x = 4 +输出:2 +``` + +- 示例 2: + +```python +输入:x = 8 +输出:2 +解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +因为求解的是 $x$ 开方的整数部分。所以我们可以从 $0 \sim x$ 的范围进行遍历,找到 $k^2 \le x$ 的最大结果。 + +为了减少算法的时间复杂度,我们使用二分查找的方法来搜索答案。 + +### 思路 1:代码 + +```python +class Solution: + def mySqrt(self, x: int) -> int: + left = 0 + right = x + ans = -1 + while left <= right: + mid = (left + right) // 2 + if mid * mid <= x: + ans = mid + left = mid + 1 + else: + right = mid - 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0001-0099/string-to-integer-atoi.md b/docs/solutions/0001-0099/string-to-integer-atoi.md new file mode 100644 index 00000000..15b0375f --- /dev/null +++ b/docs/solutions/0001-0099/string-to-integer-atoi.md @@ -0,0 +1,115 @@ +# [0008. 字符串转换整数 (atoi)](https://leetcode.cn/problems/string-to-integer-atoi/) + +- 标签:字符串 +- 难度:中等 + +## 题目链接 + +- [0008. 字符串转换整数 (atoi) - 力扣](https://leetcode.cn/problems/string-to-integer-atoi/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:实现一个 `myAtoi(s)` 函数。使其能换成一个 32 位有符号整数(类似 C / C++ 中的 `atoi` 函数)。需要检测有效性,无法读取返回 $0$。 + +**说明**: + +- 函数 `myAtoi(s)` 的算法如下: + 1. 读入字符串并丢弃无用的前导空格。 + 2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。 + 3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。 + 4. 将前面步骤读入的这些数字转换为整数(即,`"123"` -> `123`, `"0032"` -> `32`)。如果没有读入数字,则整数为 `0` 。必要时更改符号(从步骤 2 开始)。 + 5. 如果整数数超过 32 位有符号整数范围 $[−2^{31}, 2^{31} − 1]$ ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 $−2^{31}$ 的整数应该被固定为 $−2^{31}$ ,大于 $2^{31} − 1$ 的整数应该被固定为 $2^{31} − 1$。 + 6. 返回整数作为最终结果。 +- 本题中的空白字符只包括空格字符 `' '` 。 +- 除前导空格或数字后的其余字符串外,请勿忽略任何其他字符。 +- $0 \le s.length \le 200$。 +- `s` 由英文字母(大写和小写)、数字(`0-9`)、`' '`、`'+'`、`'-'` 和 `'.'` 组成 + +**示例**: + +- 示例 1: + +```python +输入:s = "42" +输出:42 +解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。 +第 1 步:"42"(当前没有读入字符,因为没有前导空格) + ^ +第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+') + ^ +第 3 步:"42"(读入 "42") + ^ +解析得到整数 42 。 +由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。 +``` + +- 示例 2: + +```python +输入:s = " -42" +输出:-42 +解释: +第 1 步:" -42"(读入前导空格,但忽视掉) + ^ +第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数) + ^ +第 3 步:" -42"(读入 "42") + ^ +解析得到整数 -42 。 +由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 先去除前后空格。 +2. 检测正负号。 +3. 读入数字,并用字符串存储数字结果。 +4. 将数字字符串转为整数,并根据正负号转换整数结果。 +5. 判断整数范围,并返回最终结果。 + +### 思路 1:代码 + +```python +class Solution: + def myAtoi(self, s: str) -> int: + num_str = "" + positive = True + start = 0 + + s = s.lstrip() + if not s: + return 0 + + if s[0] == '-': + positive = False + start = 1 + elif s[0] == '+': + positive = True + start = 1 + elif not s[0].isdigit(): + return 0 + + for i in range(start, len(s)): + if s[i].isdigit(): + num_str += s[i] + else: + break + if not num_str: + return 0 + num = int(num_str) + if not positive: + num = -num + return max(num, -2 ** 31) + else: + return min(num, 2 ** 31 - 1) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0001-0099/subsets-ii.md b/docs/solutions/0001-0099/subsets-ii.md new file mode 100644 index 00000000..ccd8dd05 --- /dev/null +++ b/docs/solutions/0001-0099/subsets-ii.md @@ -0,0 +1,140 @@ +# [0090. 子集 II](https://leetcode.cn/problems/subsets-ii/) + +- 标签:位运算、数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0090. 子集 II - 力扣](https://leetcode.cn/problems/subsets-ii/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`,其中可能包含重复元素。 + +**要求**:返回该数组所有可能的子集(幂集)。 + +**说明**: + +- 解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。 +- $1 \le nums.length \le 10$。 +- $-10 \le nums[i] \le 10$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,2] +输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +数组的每个元素都有两个选择:选与不选。 + +我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 + +因为数组中可能包含重复元素,所以我们可以先将数组排序,然后在回溯时,判断当前元素是否和上一个元素相同,如果相同,则直接跳过,从而去除重复元素。 + +回溯算法解决这道题的步骤如下: + +- 先对数组 `nums` 进行排序。 +- 从第 `0` 个位置开始,调用 `backtrack` 方法进行深度优先搜索。 +- 将当前子集数组 `sub_set` 添加到答案数组 `sub_sets` 中。 +- 然后从当前位置开始,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: + - 如果当前元素与上一个元素相同,则跳过当前生成的子集。 + - 将可选元素添加到当前子集数组 `sub_set` 中。 + - 在选择该元素的情况下,继续递归考虑下一个元素。 + - 进行回溯,撤销选择该元素。即从当前子集数组 `sub_set` 中移除之前添加的元素。 + +### 思路 1:代码 + +```python +class Solution: + def backtrack(self, nums, index, res, path): + res.append(path[:]) + + for i in range(index, len(nums)): + if i > index and nums[i] == nums[i - 1]: + continue + path.append(nums[i]) + self.backtrack(nums, i + 1, res, path) + path.pop() + + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + nums.sort() + res, path = [], [] + self.backtrack(nums, 0, res, path) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 +- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 + +### 思路 2:二进制枚举 + +对于一个元素个数为 `n` 的集合 `nums` 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 `1` 来表示选取该元素,用数字 `0` 来表示不选取该元素。 + +那么我们就可以用一个长度为 `n` 的二进制数来表示集合 `nums` 或者表示 `nums` 的子集。其中二进制的每一位数都对应了集合中某一个元素的选取状态。对于集合中第 `i` 个元素(`i` 从 `0` 开始编号)来说,二进制对应位置上的 `1` 代表该元素被选取,`0` 代表该元素未被选取。 + +举个例子来说明一下,比如长度为 `5` 的集合 `nums = {5, 4, 3, 2, 1}`,我们可以用一个长度为 `5` 的二进制数来表示该集合。 + +比如二进制数 `11111` 就表示选取集合的第 `0` 位、第 `1` 位、第 `2` 位、第 `3` 位、第 `4` 位元素,也就是集合 `{5, 4, 3, 2, 1}` ,即集合 `nums` 本身。如下表所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :--: | :--: | :--: | :--: | :--: | +| 二进制数对应位数 | 1 | 1 | 1 | 1 | 1 | +| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | + +再比如二进制数 `10101` 就表示选取集合的第 `0` 位、第 `2` 位、第 `5` 位元素,也就是集合 `{5, 3, 1}`。如下表所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :--: | :----: | :--: | :----: | :--: | +| 二进制数对应位数 | 1 | 0 | 1 | 0 | 1 | +| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | + +再比如二进制数 `01001` 就表示选取集合的第 `0` 位、第 `3` 位元素,也就是集合 `{5, 2}`。如下标所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :----: | :--: | :----: | :----: | :--: | +| 二进制数对应位数 | 0 | 1 | 0 | 0 | 1 | +| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | + +通过上面的例子我们可以得到启发:对于长度为 `5` 的集合 `nums` 来说,我们只需要从 `00000` ~ `11111` 枚举一次(对应十进制为 $0 \sim 2^4 - 1$)即可得到长度为 `5` 的集合 `S` 的所有子集。 + +我们将上面的例子拓展到长度为 `n` 的集合 `nums`。可以总结为: + +- 对于长度为 `5` 的集合 `nums` 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到所有的子集。 + +因为数组中可能包含重复元素,所以我们可以先对数组进行排序。然后在枚举过程中,如果发现当前元素和上一个元素相同,则直接跳过当前生层的子集,从而去除重复元素。 + +### 思路 2:代码 + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + nums.sort() + n = len(nums) # n 为集合 nums 的元素个数 + sub_sets = [] # sub_sets 用于保存所有子集 + for i in range(1 << n): # 枚举 0 ~ 2^n - 1 + sub_set = [] # sub_set 用于保存当前子集 + flag = True # flag 用于判断重复元素 + for j in range(n): # 枚举第 i 位元素 + if i >> j & 1: # 如果第 i 为元素对应二进制位为 1,则表示选取该元素 + if j > 0 and (i >> (j - 1) & 1) == 0 and nums[j] == nums[j - 1]: + flag = False # 如果出现重复元素,则跳过当前生成的子集 + break + sub_set.append(nums[j]) # 将选取的元素加入到子集 sub_set 中 + if flag: + sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 + return sub_sets # 返回所有子集 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 +- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 diff --git a/docs/solutions/0001-0099/subsets.md b/docs/solutions/0001-0099/subsets.md new file mode 100644 index 00000000..c75d2791 --- /dev/null +++ b/docs/solutions/0001-0099/subsets.md @@ -0,0 +1,154 @@ +# [0078. 子集](https://leetcode.cn/problems/subsets/) + +- 标签:位运算、数组、回溯 +- 难度:中等 + +## 题目链接 + +- [0078. 子集 - 力扣](https://leetcode.cn/problems/subsets/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`,数组中的元素互不相同。 + +**要求**:返回该数组所有可能的不重复子集。可以按任意顺序返回解集。 + +**说明**: + +- $1 \le nums.length \le 10$。 +- $-10 \le nums[i] \le 10$。 +- `nums` 中的所有元素互不相同。 + +**示例**: + +- 示例 1: + +```python +输入 nums = [1,2,3] +输出 [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] +``` + +- 示例 2: + +```python +输入:nums = [0] +输出:[[],[0]] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +数组的每个元素都有两个选择:选与不选。 + +我们可以通过向当前子集数组中添加可选元素来表示选择该元素。也可以在当前递归结束之后,将之前添加的元素从当前子集数组中移除(也就是回溯)来表示不选择该元素。 + +下面我们根据回溯算法三步走,写出对应的回溯算法。 + +![](https://qcdn.itcharge.cn/images/20220425210640.png) + +1. **明确所有选择**:根据数组中每个位置上的元素选与不选两种选择,画出决策树,如上图所示。 +2. **明确终止条件**: + - 当遍历到决策树的叶子节点时,就终止了。即当前路径搜索到末尾时,递归终止。 +3. **将决策树和终止条件翻译成代码**: + 1. 定义回溯函数: + - `backtracking(nums, index):` 函数的传入参数是 `nums`(可选数组列表)和 `index`(代表当前正在考虑元素是 `nums[i]` ),全局变量是 `res`(存放所有符合条件结果的集合数组)和 `path`(存放当前符合条件的结果)。 + - `backtracking(nums, index):` 函数代表的含义是:在选择 `nums[index]` 的情况下,递归选择剩下的元素。 + 2. 书写回溯函数主体(给出选择元素、递归搜索、撤销选择部分)。 + - 从当前正在考虑元素,到数组结束为止,枚举出所有可选的元素。对于每一个可选元素: + - 约束条件:之前选过的元素不再重复选用。每次从 `index` 位置开始遍历而不是从 `0` 位置开始遍历就是为了避免重复。集合跟全排列不一样,子集中 `{1, 2}` 和 `{2, 1}` 是等价的。为了避免重复,我们之前考虑过的元素,就不再重复考虑了。 + - 选择元素:将其添加到当前子集数组 `path` 中。 + - 递归搜索:在选择该元素的情况下,继续递归考虑下一个位置上的元素。 + - 撤销选择:将该元素从当前子集数组 `path` 中移除。 + ```python + for i in range(index, len(nums)): # 枚举可选元素列表 + path.append(nums[i]) # 选择元素 + backtracking(nums, i + 1) # 递归搜索 + path.pop() # 撤销选择 + ``` + 3. 明确递归终止条件(给出递归终止条件,以及递归终止时的处理方法)。 + - 当遍历到决策树的叶子节点时,就终止了。也就是当正在考虑的元素位置到达数组末尾(即 `start >= len(nums)`)时,递归停止。 + - 从决策树中也可以看出,子集需要存储的答案集合应该包含决策树上所有的节点,应该需要保存递归搜索的所有状态。所以无论是否达到终止条件,我们都应该将当前符合条件的结果放入到集合中。 + +### 思路 1:代码 + +```python +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + res = [] # 存放所有符合条件结果的集合 + path = [] # 存放当前符合条件的结果 + def backtracking(nums, index): # 正在考虑可选元素列表中第 index 个元素 + res.append(path[:]) # 将当前符合条件的结果放入集合中 + if index >= len(nums): # 遇到终止条件(本题) + return + + for i in range(index, len(nums)): # 枚举可选元素列表 + path.append(nums[i]) # 选择元素 + backtracking(nums, i + 1) # 递归搜索 + path.pop() # 撤销选择 + + backtracking(nums, 0) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 +- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 + +### 思路 2:二进制枚举 + +对于一个元素个数为 `n` 的集合 `nums` 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 `1` 来表示选取该元素,用数字 `0` 来表示不选取该元素。 + +那么我们就可以用一个长度为 `n` 的二进制数来表示集合 `nums` 或者表示 `nums` 的子集。其中二进制的每一位数都对应了集合中某一个元素的选取状态。对于集合中第 `i` 个元素(`i` 从 `0` 开始编号)来说,二进制对应位置上的 `1` 代表该元素被选取,`0` 代表该元素未被选取。 + +举个例子来说明一下,比如长度为 `5` 的集合 `nums = {5, 4, 3, 2, 1}`,我们可以用一个长度为 `5` 的二进制数来表示该集合。 + +比如二进制数 `11111` 就表示选取集合的第 `0` 位、第 `1` 位、第 `2` 位、第 `3` 位、第 `4` 位元素,也就是集合 `{5, 4, 3, 2, 1}` ,即集合 `nums` 本身。如下表所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :--: | :--: | :--: | :--: | :--: | +| 二进制数对应位数 | 1 | 1 | 1 | 1 | 1 | +| 对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 | + +再比如二进制数 `10101` 就表示选取集合的第 `0` 位、第 `2` 位、第 `5` 位元素,也就是集合 `{5, 3, 1}`。如下表所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :--: | :----: | :--: | :----: | :--: | +| 二进制数对应位数 | 1 | 0 | 1 | 0 | 1 | +| 对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 | + +再比如二进制数 `01001` 就表示选取集合的第 `0` 位、第 `3` 位元素,也就是集合 `{5, 2}`。如下标所示: + +| 集合 nums 对应位置(下标) | 4 | 3 | 2 | 1 | 0 | +| :------------------------- | :----: | :--: | :----: | :----: | :--: | +| 二进制数对应位数 | 0 | 1 | 0 | 0 | 1 | +| 对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 | + +通过上面的例子我们可以得到启发:对于长度为 `5` 的集合 `nums` 来说,我们只需要从 `00000` ~ `11111` 枚举一次(对应十进制为 $0 \sim 2^4 - 1$)即可得到长度为 `5` 的集合 `S` 的所有子集。 + +我们将上面的例子拓展到长度为 `n` 的集合 `nums`。可以总结为: + +- 对于长度为 `5` 的集合 `nums` 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到所有的子集。 + +### 思路 2:代码 + +```python +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + n = len(nums) # n 为集合 nums 的元素个数 + sub_sets = [] # sub_sets 用于保存所有子集 + for i in range(1 << n): # 枚举 0 ~ 2^n - 1 + sub_set = [] # sub_set 用于保存当前子集 + for j in range(n): # 枚举第 i 位元素 + if i >> j & 1: # 如果第 i 为元素对应二进制位为 1,则表示选取该元素 + sub_set.append(nums[j]) # 将选取的元素加入到子集 sub_set 中 + sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中 + return sub_sets # 返回所有子集 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 指的是数组 `nums` 的元素个数,$2^n$ 指的是所有状态数。每种状态需要 $O(n)$ 的时间来构造子集。 +- **空间复杂度**:$O(n)$,每种状态下构造子集需要使用 $O(n)$ 的空间。 + diff --git a/docs/solutions/0001-0099/sudoku-solver.md b/docs/solutions/0001-0099/sudoku-solver.md new file mode 100644 index 00000000..df5aa67b --- /dev/null +++ b/docs/solutions/0001-0099/sudoku-solver.md @@ -0,0 +1,99 @@ +# [0037. 解数独](https://leetcode.cn/problems/sudoku-solver/) + +- 标签:数组、哈希表、回溯、矩阵 +- 难度:困难 + +## 题目链接 + +- [0037. 解数独 - 力扣](https://leetcode.cn/problems/sudoku-solver/) + +## 题目大意 + +**描述**:给定一个二维的字符数组 $board$ 用来表示数独,其中数字 $1 \sim 9$ 表示该位置已经填入了数字,`.` 表示该位置还没有填入数字。 + +**要求**:现在编写一个程序,通过填充空格的方式来解决数独问题,最终不用返回答案,将题目给定 $board$ 修改为可行的方案即可。 + +**说明**: + +- 数独解法需遵循如下规则: + + - 数字 $1 \sim 9$ 在每一行只能出现一次。 + - 数字 $1 \sim 9$ 在每一列只能出现一次。 + - 数字 $1 \sim 9$ 在每一个以粗直线分隔的 $3 \times 3$ 宫格内只能出现一次。 +- $board.length == 9$。 +- $board[i].length == 9$。 +- $board[i][j]$ 是一位数字或者 `.`。 +- 题目数据保证输入数独仅有一个解。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714svg.png) + +```python +输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]] +输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]] +解释:输入的数独如上图所示,唯一有效的解决方案如下所示: +``` + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714_solutionsvg.png) + +## 解题思路 + +### 思路 1:回溯算法 + +对于每一行、每一列、每一个数字,都需要一重 `for` 循环来遍历,这样就是三重 `for` 循环。 + +对于第 $i$ 行、第 $j$ 列的元素来说,如果当前位置为空位,则尝试将第 $k$ 个数字置于此处,并检验数独的有效性。如果有效,则继续遍历下一个空位,直到遍历完所有空位,得到可行方案或者遍历失败时结束。 + +遍历完下一个空位之后再将此位置进行回退,置为 `.`。 + +### 思路 1:代码 + +```python +class Solution: + def backtrack(self, board: List[List[str]]): + for i in range(len(board)): + for j in range(len(board[0])): + if board[i][j] != '.': + continue + for k in range(1, 10): + if self.isValid(i, j, k, board): + board[i][j] = str(k) + if self.backtrack(board): + return True + board[i][j] = '.' + return False + return True + + def isValid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool: + for i in range(0, 9): + if board[row][i] == str(val): + return False + + for j in range(0, 9): + if board[j][col] == str(val): + return False + + start_row = (row // 3) * 3 + start_col = (col // 3) * 3 + + for i in range(start_row, start_row + 3): + for j in range(start_col, start_col + 3): + if board[i][j] == str(val): + return False + return True + + def solveSudoku(self, board: List[List[str]]) -> None: + self.backtrack(board) + """ + Do not return anything, modify board in-place instead. + """ +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(9^m)$,$m$ 为棋盘中 `.` 的数量。 +- **空间复杂度**:$O(9^2)$。 + diff --git a/docs/solutions/0001-0099/swap-nodes-in-pairs.md b/docs/solutions/0001-0099/swap-nodes-in-pairs.md new file mode 100644 index 00000000..fd9c75f1 --- /dev/null +++ b/docs/solutions/0001-0099/swap-nodes-in-pairs.md @@ -0,0 +1,70 @@ +# [0024. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) + +- 标签:递归、链表 +- 难度:中等 + +## 题目链接 + +- [0024. 两两交换链表中的节点 - 力扣](https://leetcode.cn/problems/swap-nodes-in-pairs/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:按顺序将链表中每两个节点交换一下,并返回交换后的链表。 + +**说明**: + +- 需要实际进行节点交换,而不是纸改变节点内部的值。 +- 链表中节点的数目在范围 $[0, 100]$ 内。 +- $0 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/03/swap_ex1.jpg) + +```python +输入:head = [1,2,3,4] +输出:[2,1,4,3] +``` + +- 示例 2: + +```python +输入:head = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:迭代 + +1. 创建一个哑节点 `new_head`,令 `new_head.next = head`。 +2. 遍历链表,并判断当前链表后两位节点是否为空。如果后两个节点不为空,则使用三个指针:`curr` 指向当前节点,`node1` 指向下一个节点,`node2` 指向下面第二个节点。 +3. 将 `curr` 指向 `node2`,`node1` 指向 `node2` 后边的节点,`node2` 指向 `node1`。则节点关系由 `curr → node1 → node2` 变为了 `curr → node2 → node1`。 +4. 依次类推,最终返回哑节点连接的后一个节点。 + +### 思路 1:代码 + +```python +class Solution: + def swapPairs(self, head: ListNode) -> ListNode: + new_head = ListNode(0) + new_head.next = head + curr = new_head + while curr.next and curr.next.next: + node1 = curr.next + node2 = curr.next.next + curr.next = node2 + node1.next = node2.next + node2.next = node1 + curr = node1 + return new_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为链表的节点数量。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/trapping-rain-water.md b/docs/solutions/0001-0099/trapping-rain-water.md new file mode 100644 index 00000000..9ae6bd99 --- /dev/null +++ b/docs/solutions/0001-0099/trapping-rain-water.md @@ -0,0 +1,78 @@ +# [0042. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) + +- 标签:栈、数组、双指针、动态规划、单调栈 +- 难度:困难 + +## 题目链接 + +- [0042. 接雨水 - 力扣](https://leetcode.cn/problems/trapping-rain-water/) + +## 题目大意 + +**描述**:给定 `n` 个非负整数表示每个宽度为 `1` 的柱子的高度图,用数组 `height` 表示,其中 `height[i]` 表示第 `i` 根柱子的高度。 + +**要求**:计算按此排列的柱子,下雨之后能接多少雨水。 + +**说明**: + +- $n == height.length$。 +- $1 \le n \le 2 * 10^4$。 +- $0 \le height[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/22/rainwatertrap.png) + +```python +输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] +输出:6 +解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 +``` + +- 示例 2: + +```python +输入:height = [4,2,0,3,2,5] +输出:9 +``` + +## 解题思路 + +### 思路 1:单调栈 + +1. 遍历高度数组 `height`。 +2. 如果当前柱体高度较小,小于等于栈顶柱体的高度,则将当前柱子高度入栈。 +3. 如果当前柱体高度较大,大于栈顶柱体的高度,则一直出栈,直到当前柱体小于等于栈顶柱体的高度。 +4. 假设当前柱体为 `C`,出栈柱体为 `B`,出栈之后新的栈顶柱体为 `A`。则说明: + 1. 当前柱体 `C` 是出栈柱体 `B` 向右找到的第一个大于当前柱体高度的柱体,那么以出栈柱体 `B` 为中心,可以向右将宽度扩展到当前柱体 `C`。 + 2. 新的栈顶柱体 `A` 是出栈柱体 `B` 向左找到的第一个大于当前柱体高度的柱体,那么以出栈柱体 `B` 为中心,可以向左将宽度扩展到当前柱体 `A`。 +5. 出栈后,以新的栈顶柱体 `A` 为左边界,以当前柱体 `C` 为右边界,以左右边界与出栈柱体 `B` 的高度差为深度,计算可以接到雨水的面积。然后记录并更新累积面积。 + +### 思路 1:代码 + +```python +class Solution: + def trap(self, height: List[int]) -> int: + ans = 0 + stack = [] + size = len(height) + for i in range(size): + while stack and height[i] > height[stack[-1]]: + cur = stack.pop(-1) + if stack: + left = stack[-1] + 1 + right = i - 1 + high = min(height[i], height[stack[-1]]) - height[cur] + ans += high * (right - left + 1) + else: + break + stack.append(i) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `height` 的长度。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/two-sum.md b/docs/solutions/0001-0099/two-sum.md new file mode 100644 index 00000000..c1b4d889 --- /dev/null +++ b/docs/solutions/0001-0099/two-sum.md @@ -0,0 +1,87 @@ +# [0001. 两数之和](https://leetcode.cn/problems/two-sum/) + +- 标签:数组、哈希表 +- 难度:简单 + +## 题目链接 + +- [0001. 两数之和 - 力扣](https://leetcode.cn/problems/two-sum/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数目标值 $target$。 + +**要求**:在该数组中找出和为 $target$ 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。 + +**说明**: + +- $2 \le nums.length \le 10^4$。 +- $-10^9 \le nums[i] \le 10^9$。 +- $-10^9 \le target \le 10^9$。 +- 只会存在一个有效答案。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,7,11,15], target = 9 +输出:[0,1] +解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 +``` + +- 示例 2: + +```python +输入:nums = [3,2,4], target = 6 +输出:[1,2] +``` + +## 解题思路 + +### 思路 1:枚举算法 + +1. 使用两重循环枚举数组中每一个数 $nums[i]$、$nums[j]$,判断所有的 $nums[i] + nums[j]$ 是否等于 $target$。 +2. 如果出现 $nums[i] + nums[j] == target$,则说明数组中存在和为 $target$ 的两个整数,将两个整数的下标 $i$、$j$ 输出即可。 + +### 思路 1:代码 + +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + for i in range(len(nums)): + for j in range(i + 1, len(nums)): + if i != j and nums[i] + nums[j] == target: + return [i, j] + return [] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 是数组 $nums$ 的元素数量。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:哈希表 + +哈希表中键值对信息为 $target-nums[i] :i,其中 $i$ 为下标。 + +1. 遍历数组,对于每一个数 $nums[i]$: + 1. 先查找字典中是否存在 $target - nums[i]$,存在则输出 $target - nums[i]$ 对应的下标和当前数组的下标 $i$。 + 2. 不存在则在字典中存入 $target - nums[i]$ 的下标 $i$。 + +### 思路 2:代码 + +```python +def twoSum(self, nums: List[int], target: int) -> List[int]: + numDict = dict() + for i in range(len(nums)): + if target-nums[i] in numDict: + return numDict[target-nums[i]], i + numDict[nums[i]] = i + return [0] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的元素数量。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/unique-binary-search-trees-ii.md b/docs/solutions/0001-0099/unique-binary-search-trees-ii.md new file mode 100644 index 00000000..1f96dbe7 --- /dev/null +++ b/docs/solutions/0001-0099/unique-binary-search-trees-ii.md @@ -0,0 +1,82 @@ +# [0095. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) + +- 标签:树、二叉搜索树、动态规划、回溯、二叉树 +- 难度:中等 + +## 题目链接 + +- [0095. 不同的二叉搜索树 II - 力扣](https://leetcode.cn/problems/unique-binary-search-trees-ii/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:请生成返回以 $1$ 到 $n$ 为节点构成的「二叉搜索树」,可以按任意顺序返回答案。 + +**说明**: + +- $1 \le n \le 8$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/01/18/uniquebstn3.jpg) + +```python +输入:n = 3 +输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]] +``` + +- 示例 2: + +```python +输入:n = 1 +输出:[[1]] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +如果根节点为 $i$,则左子树的节点为 $(1, 2, ..., i - 1)$,右子树的节点为 $(i + 1, i + 2, ..., n)$。可以递归的构建二叉树。 + +定义递归函数 `generateTrees(start, end)`,表示生成 $[left, ..., right]$ 构成的所有可能的二叉搜索树。 + +- 如果 $start > end$,返回 `[None]`。 +- 初始化存放所有可能二叉搜索树的数组。 +- 遍历 $[left, ..., right]$ 的每一个节点 $i$,将其作为根节点。 + - 递归构建左右子树。 + - 将所有符合要求的左右子树组合起来,将其加入到存放二叉搜索树的数组中。 +- 返回存放二叉搜索树的数组。 + +### 思路 1:代码 + +```python +class Solution: + def generateTrees(self, n: int) -> List[TreeNode]: + if n == 0: + return [] + + def generateTrees(start, end): + if start > end: + return [None] + trees = [] + for i in range(start, end+1): + left_trees = generateTrees(start, i - 1) + right_trees = generateTrees(i + 1, end) + for left_tree in left_trees: + for right_tree in right_trees: + curr_tree = TreeNode(i) + curr_tree.left = left_tree + curr_tree.right = right_tree + trees.append(curr_tree) + return trees + return generateTrees(1, n) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(C_n)$,其中 $C_n$ 是第 $n$ 个卡特兰数。 +- **空间复杂度**:$O(C_n)$,其中 $C_n$ 是第 $n$ 个卡特兰数。 + diff --git a/docs/solutions/0001-0099/unique-binary-search-trees.md b/docs/solutions/0001-0099/unique-binary-search-trees.md new file mode 100644 index 00000000..b9bd502a --- /dev/null +++ b/docs/solutions/0001-0099/unique-binary-search-trees.md @@ -0,0 +1,107 @@ +# [0096. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) + +- 标签:树、二叉搜索树、数学、动态规划、二叉树 +- 难度:中等 + +## 题目链接 + +- [0096. 不同的二叉搜索树 - 力扣](https://leetcode.cn/problems/unique-binary-search-trees/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:求以 $1$ 到 $n$ 为节点构成的「二叉搜索树」有多少种? + +**说明**: + +- $1 \le n \le 19$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/01/18/uniquebstn3.jpg) + +```python +输入:n = 3 +输出:5 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:动态规划 + +一棵搜索二叉树的左、右子树,要么也是搜索二叉树,要么就是空树。 + +如果定义 $f[i]$ 表示以 $i$ 为根的二叉搜索树个数,定义 $g(i)$ 表示 $i$ 个节点可以构成的二叉搜索树个数,则有: + +- $g(i) = f(1) + f(2) + f(3) + … + f(i)$。 + +其中当 $i$ 为根节点时,则用 $(1, 2, …, i - 1)$ 共 $i - 1$ 个节点去递归构建左子搜索二叉树,用 $(i + 1, i + 2, …, n)$ 共 $n - i$ 个节点去递归构建右子搜索树。则有: + +- $f(i) = g(i - 1) \times g(n - i)$。 + +综合上面两个式子 $\begin{cases} g(i) = f(1) + f(2) + f(3) + … + f(i) \cr f(i) = g(i - 1) \times g(n - i) \end{cases}$ 可得出: + +- $g(n) = g(0) \times g(n - 1) + g(1) \times g(n - 2) + … + g(n - 1) \times g(0)$。 + +将 $n$ 换为 $i$,可变为: + +- $g(i) = g(0) \times g(i - 1) + g(1) \times g(i - 2) + … + g(i - 1) \times g(0)$。 + +再转换一下,可变为: + +- $g(i) = \sum_{1 \le j \le i} \lbrace g(j - 1) \times g(i - j) \rbrace$。 + +则我们可以通过动态规划的方法,递推求解 $g(i)$,并求解出 $g(n)$。具体步骤如下: + +###### 1. 划分阶段 + +按照根节点的编号进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为: $i$ 个节点可以构成的二叉搜索树个数。 + +###### 3. 状态转移方程 + +$dp[i] = \sum_{1 \le j \le i} \lbrace dp[j - 1] \times dp[i - j] \rbrace$ + +###### 4. 初始条件 + +- $0$ 个节点可以构成的二叉搜索树个数为 $1$(空树),即 $dp[0] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为: $i$ 个节点可以构成的二叉搜索树个数。。 所以最终结果为 $dp[n]$。 + +### 思路 1:代码 + +```python +class Solution: + def numTrees(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + dp[0] = 1 + for i in range(1, n + 1): + for j in range(1, i + 1): + dp[i] += dp[j - 1] * dp[i - j] + return dp[n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[画解算法:96. 不同的二叉搜索树 - 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/solution/hua-jie-suan-fa-96-bu-tong-de-er-cha-sou-suo-shu-b/) + diff --git a/docs/solutions/0001-0099/unique-paths-ii.md b/docs/solutions/0001-0099/unique-paths-ii.md new file mode 100644 index 00000000..67584b59 --- /dev/null +++ b/docs/solutions/0001-0099/unique-paths-ii.md @@ -0,0 +1,104 @@ +# [0063. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) + +- 标签:数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [0063. 不同路径 II - 力扣](https://leetcode.cn/problems/unique-paths-ii/) + +## 题目大意 + +**描述**:一个机器人位于一个 $m \times n$ 网格的左上角。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。但是网格中有障碍物,不能通过。 + +现在给定一个二维数组表示网格,$1$ 代表障碍物,$0$ 表示空位。 + +**要求**:计算出从左上角到右下角会有多少条不同的路径。 + +**说明**: + +- $m == obstacleGrid.length$。 +- $n == obstacleGrid[i].length$。 +- $1 \le m, n \le 100$。 +- $obstacleGrid[i][j]$ 为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/04/robot1.jpg) + +```python +输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] +输出:2 +解释:3x3 网格的正中间有一个障碍物。 +从左上角到右下角一共有 2 条不同的路径: +1. 向右 -> 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 -> 向右 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/04/robot2.jpg) + +```python +输入:obstacleGrid = [[0,1],[0,0]] +输出:1 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:从 $(0, 0)$ 到 $(i, j)$ 的不同路径数。 + +###### 3. 状态转移方程 + +因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。则状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,其中 $obstacleGrid[i][j] == 0$。 + +###### 4. 初始条件 + +- 对于第一行、第一列,因为只能超一个方向走,所以 $dp[i][0] = 1$,$dp[0][j] = 1$。如果在第一行、第一列遇到障碍,则终止赋值,跳出循环。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:从 $(0, 0)$ 到 $(i, j)$ 的不同路径数。所以最终结果为 $dp[m - 1][n - 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int: + m = len(obstacleGrid) + n = len(obstacleGrid[0]) + dp = [[0 for _ in range(n)] for _ in range(m)] + + for i in range(m): + if obstacleGrid[i][0] == 1: + break + dp[i][0] = 1 + + for j in range(n): + if obstacleGrid[0][j] == 1: + break + dp[0][j] = 1 + + for i in range(1, m): + for j in range(1, n): + if obstacleGrid[i][j] == 1: + continue + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + return dp[m - 1][n - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0001-0099/unique-paths.md b/docs/solutions/0001-0099/unique-paths.md new file mode 100644 index 00000000..45ddea61 --- /dev/null +++ b/docs/solutions/0001-0099/unique-paths.md @@ -0,0 +1,92 @@ +# [0062. 不同路径](https://leetcode.cn/problems/unique-paths/) + +- 标签:数学、动态规划、组合数学 +- 难度:中等 + +## 题目链接 + +- [0062. 不同路径 - 力扣](https://leetcode.cn/problems/unique-paths/) + +## 题目大意 + +**描述**:给定两个整数 $m$ 和 $n$,代表大小为 $m \times n$ 的棋盘, 一个机器人位于棋盘左上角的位置,机器人每次只能向右、或者向下移动一步。 + +**要求**:计算出机器人从棋盘左上角到达棋盘右下角一共有多少条不同的路径。 + +**说明**: + +- $1 \le m, n \le 100$。 +- 题目数据保证答案小于等于 $2 \times 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:m = 3, n = 7 +输出:28 +``` + +![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) + +- 示例 2: + +```python +输入:m = 3, n = 2 +输出:3 +解释: +从左上角开始,总共有 3 条路径可以到达右下角。 +1. 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 +3. 向下 -> 向右 -> 向下 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照路径的结尾位置(行位置、列位置组成的二维坐标)进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:从左上角到达位置 $(i, j)$ 的路径数量。 + +###### 3. 状态转移方程 + +因为我们每次只能向右、或者向下移动一步,因此想要走到 $(i, j)$,只能从 $(i - 1, j)$ 向下走一步走过来;或者从 $(i, j - 1)$ 向右走一步走过来。所以可以写出状态转移方程为:$dp[i][j] = dp[i - 1][j] + dp[i][j - 1]$,此时 $i > 0, j > 0$。 + +###### 4. 初始条件 + +- 从左上角走到 $(0, 0)$ 只有一种方法,即 $dp[0][0] = 1$。 +- 第一行元素只有一条路径(即只能通过前一个元素向右走得到),所以 $dp[0][j] = 1$。 +- 同理,第一列元素只有一条路径(即只能通过前一个元素向下走得到),所以 $dp[i][0] = 1$。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[m - 1][n - 1]$,即从左上角到达右下角 $(m - 1, n - 1)$ 位置的路径数量为 $dp[m - 1][n - 1]$。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp = [[0 for _ in range(n)] for _ in range(m)] + + for j in range(n): + dp[0][j] = 1 + for i in range(m): + dp[i][0] = 1 + + for i in range(1, m): + for j in range(1, n): + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + + return dp[m - 1][n - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。初始条件赋值的时间复杂度为 $O(m + n)$,两重循环遍历的时间复杂度为 $O(m \times n)$,所以总体时间复杂度为 $O(m \times n)$。 +- **空间复杂度**:$O(m \times n)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(m \times n)$。因为 $dp[i][j]$ 的状态只依赖于上方值 $dp[i - 1][j]$ 和左侧值 $dp[i][j - 1]$,而我们在进行遍历时的顺序刚好是从上至下、从左到右。所以我们可以使用长度为 $n$ 的一维数组来保存状态,从而将空间复杂度优化到 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/valid-parentheses.md b/docs/solutions/0001-0099/valid-parentheses.md new file mode 100644 index 00000000..147cfb49 --- /dev/null +++ b/docs/solutions/0001-0099/valid-parentheses.md @@ -0,0 +1,90 @@ +# [0020. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) + +- 标签:栈、字符串 +- 难度:简单 + +## 题目链接 + +- [0020. 有效的括号 - 力扣](https://leetcode.cn/problems/valid-parentheses/) + +## 题目大意 + +**描述**:给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串 `s` 。 + +**要求**:判断字符串 `s` 是否有效(即括号是否匹配)。 + +**说明**: + +- 有效字符串需满足: + 1. 左括号必须用相同类型的右括号闭合。 + 2. 左括号必须以正确的顺序闭合。 + +**示例**: + +- 示例 1: + +```python +输入:s = "()" +输出:True +``` + +- 示例 2: + +```python +输入:s = "()[]{}" +输出:True +``` + +## 解题思路 + +### 思路 1:栈 + +括号匹配是「栈」的经典应用。我们可以用栈来解决这道题。具体做法如下: + +1. 先判断一下字符串的长度是否为偶数。因为括号是成对出现的,所以字符串的长度应为偶数,可以直接判断长度为奇数的字符串不匹配。如果字符串长度为奇数,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 +2. 使用栈 `stack` 来保存未匹配的左括号。然后依次遍历字符串 `s` 中的每一个字符。 + 1. 如果遍历到左括号时,将其入栈。 + 2. 如果遍历到右括号时,先看栈顶元素是否是与当前右括号相同类型的左括号。 + 1. 如果是与当前右括号相同类型的左括号,则令其出栈,继续向前遍历。 + 2. 如果不是与当前右括号相同类型的左括号,则说明字符串 `s` 中的括号不匹配,直接返回 `False`。 +3. 遍历完,还要再判断一下栈是否为空。 + 1. 如果栈为空,则说明字符串 `s` 中的括号匹配,返回 `True`。 + 2. 如果栈不为空,则说明字符串 `s` 中的括号不匹配,返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def isValid(self, s: str) -> bool: + if len(s) % 2 == 1: + return False + stack = list() + for ch in s: + if ch == '(' or ch == '[' or ch == '{': + stack.append(ch) + elif ch == ')': + if len(stack) !=0 and stack[-1] == '(': + stack.pop() + else: + return False + elif ch == ']': + if len(stack) !=0 and stack[-1] == '[': + stack.pop() + else: + return False + elif ch == '}': + if len(stack) !=0 and stack[-1] == '{': + stack.pop() + else: + return False + if len(stack) == 0: + return True + else: + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0001-0099/valid-sudoku.md b/docs/solutions/0001-0099/valid-sudoku.md new file mode 100644 index 00000000..dc7f5211 --- /dev/null +++ b/docs/solutions/0001-0099/valid-sudoku.md @@ -0,0 +1,88 @@ +# [0036. 有效的数独](https://leetcode.cn/problems/valid-sudoku/) + +- 标签:数组、哈希表、矩阵 +- 难度:中等 + +## 题目链接 + +- [0036. 有效的数独 - 力扣](https://leetcode.cn/problems/valid-sudoku/) + +## 题目大意 + +**描述**:给定一个数独,用 `9 * 9` 的二维字符数组 `board` 来表示,其中,未填入的空白用 "." 代替。 + +**要求**:判断该数独是否是一个有效的数独。 + +**说明**: + +- 一个有效的数独(部分已被填充)不一定是可解的。 +- 只需要根据以上规则,验证已经填入的数字是否有效即可。 +- 空白格用 `'.'` 表示。 + +一个有效的数独需满足: + +1. 数字 `1-9` 在每一行只能出现一次。 +2. 数字 `1-9` 在每一列只能出现一次。 +3. 数字 `1-9` 在每一个以粗实线分隔的 `3 * 3` 宫内只能出现一次。(请参考示例图) + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714svg.png) + +```python +输入:board = +[["5","3",".",".","7",".",".",".","."] +,["6",".",".","1","9","5",".",".","."] +,[".","9","8",".",".",".",".","6","."] +,["8",".",".",".","6",".",".",".","3"] +,["4",".",".","8",".","3",".",".","1"] +,["7",".",".",".","2",".",".",".","6"] +,[".","6",".",".",".",".","2","8","."] +,[".",".",".","4","1","9",".",".","5"] +,[".",".",".",".","8",".",".","7","9"]] +输出:True +``` + +## 解题思路 + +### 思路 1:哈希表 + +判断数独有效,需要分别看每一行、每一列、每一个 `3 * 3` 的小方格是否出现了重复数字,如果都没有出现重复数字就是一个有效的数独,如果出现了重复数字则不是有效的数独。 + +- 用 `3` 个 `9 * 9` 的数组分别来表示该数字是否在所在的行,所在的列,所在的方格出现过。其中方格角标的计算用 `box[(i / 3) * 3 + (j / 3)][n]` 来表示。 +- 双重循环遍历数独矩阵。如果对应位置上的数字如果已经在在所在的行 / 列 / 方格出现过,则返回 `False`。 +- 遍历完没有重复出现,则返回 `Ture`。 + +### 思路 1:代码 + +```python +class Solution: + def isValidSudoku(self, board: List[List[str]]) -> bool: + rows_map = [dict() for _ in range(9)] + cols_map = [dict() for _ in range(9)] + boxes_map = [dict() for _ in range(9)] + + for i in range(9): + for j in range(9): + if board[i][j] == '.': + continue + num = int(board[i][j]) + box_index = (i // 3) * 3 + j // 3 + row_num = rows_map[i].get(num, 0) + col_num = cols_map[j].get(num, 0) + box_num = boxes_map[box_index].get(num, 0) + if row_num > 0 or col_num > 0 or box_num > 0: + return False + rows_map[i][num] = 1 + cols_map[j][num] = 1 + boxes_map[box_index][num] = 1 + + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。数独总共 81 个单元格,对每个单元格遍历一次,可以看做是常数级的时间复杂度。 +- **空间复杂度**:$O(1)$。使用 81 个单位空间,可以看做是常数级的空间复杂度。 diff --git a/docs/solutions/0001-0099/validate-binary-search-tree.md b/docs/solutions/0001-0099/validate-binary-search-tree.md new file mode 100644 index 00000000..c2c5dbfe --- /dev/null +++ b/docs/solutions/0001-0099/validate-binary-search-tree.md @@ -0,0 +1,77 @@ +# [0098. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0098. 验证二叉搜索树 - 力扣](https://leetcode.cn/problems/validate-binary-search-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:判断其是否是一个有效的二叉搜索树。 + +**说明**: + +- **二叉搜索树特征**: + - 节点的左子树只包含小于当前节点的数。 + - 节点的右子树只包含大于当前节点的数。 + - 所有左子树和右子树自身必须也是二叉搜索树。 +- 树中节点数目范围在$[1, 10^4]$ 内。 +- $-2^{31} \le Node.val \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg) + +```python +输入:root = [2,1,3] +输出:true +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg) + +```python +输入:root = [5,1,4,null,null,3,6] +输出:false +解释:根节点的值是 5 ,但是右子节点的值是 4 。 +``` + +## 解题思路 + +### 思路 1:递归遍历 + +根据题意进行递归遍历即可。前序、中序、后序遍历都可以。 + +1. 以前序遍历为例,递归函数为:`preorderTraversal(root, min_v, max_v)`。 +2. 前序遍历时,先判断根节点的值是否在 `(min_v, max_v)` 之间。 + 1. 如果不在则直接返回 `False`。 + 2. 如果在区间内,则继续递归检测左右子树是否满足,都满足才是一棵二叉搜索树。 +3. 当递归遍历左子树的时候,要将上界 `max_v` 改为左子树的根节点值,因为左子树上所有节点的值均小于根节点的值。 +4. 当递归遍历右子树的时候,要将下界 `min_v` 改为右子树的根节点值,因为右子树上所有节点的值均大于根节点。 + +### 思路 1:代码 + +```python +class Solution: + def isValidBST(self, root: TreeNode) -> bool: + def preorderTraversal(root, min_v, max_v): + if root == None: + return True + if root.val >= max_v or root.val <= min_v: + return False + return preorderTraversal(root.left, min_v, root.val) and preorderTraversal(root.right, root.val, max_v) + + return preorderTraversal(root, float('-inf'), float('inf')) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0001-0099/wildcard-matching.md b/docs/solutions/0001-0099/wildcard-matching.md new file mode 100644 index 00000000..35db144c --- /dev/null +++ b/docs/solutions/0001-0099/wildcard-matching.md @@ -0,0 +1,102 @@ +# [0044. 通配符匹配](https://leetcode.cn/problems/wildcard-matching/) + +- 标签:贪心、递归、字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0044. 通配符匹配 - 力扣](https://leetcode.cn/problems/wildcard-matching/) + +## 题目大意 + +**描述**:给定一个字符串 `s` 和一个字符模式串 `p`。 + +**要求**:实现一个支持 `'?'` 和 `'*'` 的通配符匹配。两个字符串完全匹配才算匹配成功。如果匹配成功,则返回 `True`,否则返回 `False`。 + +- `'?'` 可以匹配任何单个字符。 +- `'*'` 可以匹配任意字符串(包括空字符串)。 + +**说明**: + +- `s` 可能为空,且只包含从 `a` ~ `z` 的小写字母。 +- `p` 可能为空,且只包含从 `a` ~ `z` 的小写字母,以及字符 `'?'` 和 `'*'`。 + +**示例**: + +- 示例 1: + +```python +输入:s = "aa" p = "a" +输出:False +解释:"a" 无法匹配 "aa" 整个字符串。 +``` + +- 示例 2: + +```python +输入:s = "aa" p = "*" +输出:True +解释:'*' 可以匹配任意字符串。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。 + +###### 3. 状态转移方程 + +- 如果 `s[i - 1] == p[j - 1]`,或者 `p[j - 1] == '?'`,则表示字符串 `s` 的第 `i` 个字符与字符串 `p` 的第 `j` 个字符是匹配的。此时「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j - 1] `。 +- 如果 `p[j - 1] == '*'`,则字符串 `p` 的第 `j` 个字符可以对应字符串 `s` 中 `0` ~ 若干个字符。则: + - 如果当前星号没有匹配当前第 `i` 个字符,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」,即 `dp[i][j] = dp[i - 1][j]`。 + - 如果当前星号匹配了当前第 `i` 个字符,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」,即 `dp[i][j] = dp[i][j - 1]`。 + - 这两种情况只需匹配一种,就视为匹配,所以 `dp[i][j] = dp[i - 1][j] or dp[i][j - 1] `。 + +则动态转移方程为: + +$dp[i][j] = \begin{cases} dp[i - 1][j - 1] & s[i - 1] == p[j - 1] \or p[j - 1] == '?' \cr dp[i - 1][j] or dp[i][j - 1] & p[j - 1] == '*' \end{cases}$ + +###### 4. 初始条件 + +- 默认状态下,两个空字符串是匹配的,即 `dp[0][0] = True`。 +- 当字符串 `s` 为空,字符串 `p` 开始字符为若干个 `*` 时,两个字符串是匹配的,即 `p[j - 1] == '*'` 时,`dp[0][j] = True`。 + +###### 5. 最终结果 + +根据我们之前定义的状态, `dp[i][j]` 表示为:字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配。则最终结果为 `dp[size_s][size_p]`,其实 `size_s` 是字符串 `s` 的长度,`size_p` 是字符串 `p` 的长度。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def isMatch(self, s: str, p: str) -> bool: + size_s, size_p = len(s), len(p) + dp = [[False for _ in range(size_p + 1)] for _ in range(size_s + 1)] + dp[0][0] = True + + for j in range(1, size_p + 1): + if p[j - 1] != '*': + break + dp[0][j] = True + + for i in range(1, size_s + 1): + for j in range(1, size_p + 1): + if s[i - 1] == p[j - 1] or p[j - 1] == '?': + dp[i][j] = dp[i - 1][j - 1] + elif p[j - 1] == '*': + dp[i][j] = dp[i - 1][j] or dp[i][j - 1] + + return dp[size_s][size_p] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了两重循环,外层循环遍历的时间复杂度是 $O(m)$,内层循环遍历的时间复杂度是 $O(n)$,所以总体的时间复杂度为 $O(m n)$。 +- **空间复杂度**:$O(m n)$,其中 $m$ 是字符串 `s` 的长度,$n$ 是字符串 `p` 的长度。使用了二维数组保存状态,且第一维的空间复杂度为 $O(m)$,第二位的空间复杂度为 $O(n)$,所以总体的空间复杂度为 $O(m n)$。 diff --git a/docs/solutions/0001-0099/word-search.md b/docs/solutions/0001-0099/word-search.md new file mode 100644 index 00000000..1d19b015 --- /dev/null +++ b/docs/solutions/0001-0099/word-search.md @@ -0,0 +1,97 @@ +# [0079. 单词搜索](https://leetcode.cn/problems/word-search/) + +- 标签:数组、回溯、矩阵 +- 难度:中等 + +## 题目链接 + +- [0079. 单词搜索 - 力扣](https://leetcode.cn/problems/word-search/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的二维字符矩阵 $board$ 和一个字符串单词 $word$。 + +**要求**:如果 $word$ 存在于网格中,返回 `True`,否则返回 `False`。 + +**说明**: + +- 单词必须按照字母顺序通过上下左右相邻的单元格字母构成。且同一个单元格内的字母不允许被重复使用。 +- $m == board.length$。 +- $n == board[i].length$。 +- $1 \le m, n \le 6$。 +- $1 \le word.length \le 15$。 +- $board$ 和 $word$ 仅由大小写英文字母组成。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/04/word2.jpg) + +```python +输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" +输出:true +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/04/word-1.jpg) + +```python +输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE" +输出:true +``` + +## 解题思路 + +### 思路 1:回溯算法 + +使用回溯算法在二维矩阵 $board$ 中按照上下左右四个方向递归搜索。 + +设函数 `backtrack(i, j, index)` 表示从 $board[i][j]$ 出发,能否搜索到单词字母 $word[index]$,以及 $index$ 位置之后的后缀子串。如果能搜索到,则返回 `True`,否则返回 `False`。 + +`backtrack(i, j, index)` 执行步骤如下: + +1. 如果 $board[i][j] = word[index]$,而且 index 已经到达 word 字符串末尾,则返回 True。 +2. 如果 $board[i][j] = word[index]$,而且 index 未到达 word 字符串末尾,则遍历当前位置的所有相邻位置。如果从某个相邻位置能搜索到后缀子串,则返回 True,否则返回 False。 +3. 如果 $board[i][j] \ne word[index]$,则当前字符不匹配,返回 False。 + +### 思路 1:代码 + +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + rows = len(board) + if rows == 0: + return False + cols = len(board[0]) + visited = [[False for _ in range(cols)] for _ in range(rows)] + + def backtrack(i, j, index): + if index == len(word) - 1: + return board[i][j] == word[index] + + if board[i][j] == word[index]: + visited[i][j] = True + for direct in directs: + new_i = i + direct[0] + new_j = j + direct[1] + if 0 <= new_i < rows and 0 <= new_j < cols and visited[new_i][new_j] == False: + if backtrack(new_i, new_j, index + 1): + return True + visited[i][j] = False + return False + + for i in range(rows): + for j in range(cols): + if backtrack(i, j, 0): + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n \times 2^l)$,其中 $m$、$n$ 为二维矩阵 $board$的行数和列数。$l$ 为字符串 $word$ 的长度。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0100-0199/balanced-binary-tree.md b/docs/solutions/0100-0199/balanced-binary-tree.md new file mode 100644 index 00000000..d79ad4bf --- /dev/null +++ b/docs/solutions/0100-0199/balanced-binary-tree.md @@ -0,0 +1,71 @@ +# [0110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0110. 平衡二叉树 - 力扣](https://leetcode.cn/problems/balanced-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:判断该二叉树是否是高度平衡的二叉树。 + +**说明**: + +- **高度平衡二叉树**:二叉树中每个节点的左右两个子树的高度差的绝对值不超过 $1$。 +- 树中的节点数在范围 $[0, 5000]$ 内。 +- $-10^4 \le Node.val \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg) + +```python +输入:root = [3,9,20,null,null,15,7] +输出:True +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg) + +```python +输入:root = [1,2,2,3,3,null,null,4,4] +输出:False +``` + +## 解题思路 + +### 思路 1:递归遍历 + +1. 先递归遍历左右子树,判断左右子树是否平衡,再判断以当前节点为根节点的左右子树是否平衡。 +2. 如果遍历的子树是平衡的,则返回它的高度,否则返回 -1。 +3. 只要出现不平衡的子树,则该二叉树一定不是平衡二叉树。 + +### 思路 1:代码 + +```python +class Solution: + def isBalanced(self, root: TreeNode) -> bool: + def height(root: TreeNode) -> int: + if root == None: + return False + leftHeight = height(root.left) + rightHeight = height(root.right) + if leftHeight == -1 or rightHeight == -1 or abs(leftHeight-rightHeight) > 1: + return -1 + else: + return max(leftHeight, rightHeight)+1 + return height(root) >= 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md new file mode 100644 index 00000000..0dd6553f --- /dev/null +++ b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md @@ -0,0 +1,69 @@ +# [0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0122. 买卖股票的最佳时机 II - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) + +## 题目大意 + +**描述**:给定一个整数数组 `prices` ,其中 `prices[i]` 表示某支股票第 `i` 天的价格。在每一天,你可以决定是否购买 / 出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。 + +**要求**:计算出能获取的最大利润。 + +**说明**: + +- $1 \le prices.length \le 3 * 10^4$。 +- $0 \le prices[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:prices = [7,1,5,3,6,4] +输出:7 +解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 + 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 + 总利润为 4 + 3 = 7。 +``` + +- 示例 2: + +```python +输入:prices = [1,2,3,4,5] +输出:4 +解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 + 总利润为 4 。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +股票买卖获取利润主要是看差价,必然是低点买入,高点卖出才会赚钱。而要想获取最大利润,就要在跌入谷底的时候买入,在涨到波峰的时候卖出利益才会最大化。所以我们购买股票的策略变为了: + +1. 连续跌的时候不买。 +2. 跌到最低点买入。 +3. 涨到最高点卖出。 + +在这种策略下,只要计算波峰和谷底的差值即可。而波峰和谷底的差值可以通过两两相减所得的差值来累加计算。 + +### 思路 1:代码 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + ans = 0 + for i in range(1, len(prices)): + ans += max(0, prices[i]-prices[i-1]) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `prices` 的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md new file mode 100644 index 00000000..48cfaef1 --- /dev/null +++ b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md @@ -0,0 +1,79 @@ +# [0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0123. 买卖股票的最佳时机 III - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) + +## 题目大意 + +给定一个数组 `prices` 代表一只股票,其中 `prices[i]` 代表这只股票第 `i` 天的价格。最多可完成两笔交易,且不同同时参与躲避交易(必须在再次购买前出售掉之前的股票)。 + +现在要求:计算所能获取的最大利润。 + +## 解题思路 + +动态规划求解。 + +最多可完成两笔交易意味着总共有三种情况:买卖一次,买卖两次,不买卖。 + +具体到每一天结束总共有 5 种状态: + +0. 未进行买卖状态; +1. 第一次买入状态; +2. 第一次卖出状态; +3. 第二次买入状态; +4. 第二次卖出状态。 + +所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 4`)下,所获取的最大利润。 + +注意:这里第第 `j` 种情况,并不一定是这一天一定要买入或卖出,而是这一天所处于的买入卖出状态。比如说前一天是第一次买入,第二天没有操作,则第二天就沿用前一天的第一次买入状态。 + +接下来确定状态转移公式: + +- 第 `0` 种状态下显然利润为 `0`,可以直接赋值为昨天获取的最大利润,即 `dp[i][0] = dp[i - 1][0]`。 +- 第 `1` 种状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][1] = dp[i - 1][1]`。 + - 第一次买入:`dp[i][1] = dp[i - 1][0] - prices[i]`。 +- 第 `2` 种状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][2] = dp[i - 1][2]`。 + - 第一次卖出:`dp[i][2] = dp[i - 1][1] + prices[i]`。 +- 第 `3` 种状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][3] = dp[i - 1][3]`。 + - 第二次买入:`dp[i][3] = dp[i - 1][2] - prices[i]`。 +- 第 `4` 种状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][4] = dp[i - 1][4]`。 + - 第二次卖出:`dp[i][4] = dp[i - 1][3] + prices[i]`。 + +下面确定初始化的边界值: + +可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第一次买入就是 `dp[0][1] = -prices[i]`。 + +第一次卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][2] = 0`。第二次买入的话,就是 `dp[0][3] = -prices[i]`。同理第二次卖出就是 `dp[0][4] = 0`。 + +在递推结束后,最大利润肯定是无操作、第一次卖出、第二次卖出这三种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第一次卖出、第二次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为一笔交易,那么在转移状态时,我们允许在一天内进行两次交易,则一笔交易的状态可以转移至两笔交易。所以最终答案为 `dp[size - 1][4]`。`size` 为股票天数。 + +## 代码 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + size = len(prices) + if size == 0: + return 0 + dp = [[0 for _ in range(5)] for _ in range(size)] + + dp[0][1] = -prices[0] + dp[0][3] = -prices[0] + + for i in range(1, size): + dp[i][0] = dp[i - 1][0] + dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) + dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]) + dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]) + dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]) + return dp[size - 1][4] +``` + diff --git a/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md new file mode 100644 index 00000000..c9edac5c --- /dev/null +++ b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md @@ -0,0 +1,94 @@ +# [0188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0188. 买卖股票的最佳时机 IV - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) + +## 题目大意 + +给定一个数组 `prices` 代表一只股票,其中 `prices[i]` 代表这只股票第 `i` 天的价格。再给定一个整数 `k`,表示最多可完成 `k` 笔交易,且不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。 + +现在要求:计算所能获取的最大利润。 + +## 解题思路 + +动态规划求解。这道题是「[0123. 买卖股票的最佳时机 III](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/)」的升级版,不过思路一样 + +最多可完成两笔交易意味着总共有三种情况:买卖一次,买卖两次,不买卖。 + +具体到每一天结束总共有 `2 * k + 1` 种状态: + +0. 未进行买卖状态; +1. 第 `1` 次买入状态; +2. 第 `1` 次卖出状态; +3. 第 `2` 次买入状态; +4. 第 `2` 次卖出状态。 +5. ... +6. 第 `m` 次买入状态。 +7. 第 `m` 次卖出状态。 + +因为买入、卖出为两种状态,干脆我们直接让偶数序号表示买入状态,奇数序号表示卖出状态。 + +所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 2 * k`)下,所获取的最大利润。 + +注意:这里第 `j` 种情况,并不一定是这一天一定要买入或卖出,而是这一天所处于的买入卖出状态。比如说前一天是第一次买入,第二天没有操作,则第二天就沿用前一天的第一次买入状态。 + +接下来确定状态转移公式: + +- 第 `0` 种状态下显然利润为 `0`,可以直接赋值为昨天获取的最大利润,即 `dp[i][0] = dp[i - 1][0]`。 +- 第 `1` 次买入状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][1] = dp[i - 1][1]`。 + - 第 `1` 次买入:`dp[i][1] = dp[i - 1][0] - prices[i]`。 +- 第 `1` 次卖出状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][2] = dp[i - 1][2]`。 + - 第 `1` 次卖出:`dp[i][2] = dp[i - 1][1] + prices[i]`。 +- 第 `2` 次买入状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天买入状态所得的最大利润:`dp[i][3] = dp[i - 1][3]`。 + - 第 `2` 次买入:`dp[i][3] = dp[i - 1][2] - prices[i]`。 +- 第 `2` 次卖出状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][4] = dp[i - 1][4]`。 + - 第 `2` 次卖出:`dp[i][4] = dp[i - 1][3] + prices[i]`。 +- ... +- 第 `m` 次(`j = 2 * m`)买入状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][j] = dp[i - 1][j]`。 + - 第 `m` 次买入:`dp[i][j] = dp[i - 1][j - 1] - prices[i]`。 +- 第 `m` 次(`j = 2 * m + 1`)卖出状态下可以有两种状态推出,取最大的那一种赋值: + - 不做任何操作,直接沿用前一天卖出状态所得的最大利润:`dp[i][j] = dp[i - 1][j]`。 + - 第 `m` 次卖出:`dp[i][j] = dp[i - 1][j - 1] + prices[i]`。 + +下面确定初始化的边界值: + +可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第 `m` 次买入(`j = 2 * m`)就是 `dp[0][j] = -prices[i]`。 + +第 `m` 次(`j = 2 * m + 1`)卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][j] = 0`。 + +在递推结束后,最大利润肯定是无操作、第 `m` 次卖出这几种种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第 `m` 次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为 `m - 1` 笔交易,那么在转移状态时,我们允许在一天内进行多次交易,则 `m - 1` 笔交易的状态可以转移至 `m` 笔交易,最终都可以转移至 `k` 比交易。 + +所以最终答案为 `dp[size - 1][2 * k]`。`size` 为股票天数。 + +## 代码 + +```python +class Solution: + def maxProfit(self, k: int, prices: List[int]) -> int: + size = len(prices) + if size == 0: + return 0 + + dp = [[0 for _ in range(2 * k + 1)] for _ in range(size)] + + for j in range(1, 2 * k, 2): + dp[0][j] = -prices[0] + + for i in range(1, size): + for j in range(1, 2 * k + 1): + if j % 2 == 1: + dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]) + else: + dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]) + return dp[size - 1][2 * k] +``` + diff --git a/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md new file mode 100644 index 00000000..1a0198cf --- /dev/null +++ b/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md @@ -0,0 +1,72 @@ +# [0121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) + +- 标签:数组、动态规划 +- 难度:简单 + +## 题目链接 + +- [0121. 买卖股票的最佳时机 - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) + +## 题目大意 + +**描述**:给定一个数组 `prices` ,它的第 `i` 个元素 `prices[i]` 表示一支给定股票第 `i` 天的价格。只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。 + +**要求**:计算出能获取的最大利润。如果你不能获取任何利润,返回 $0$。 + +**说明**: + +- $1 \le prices.length \le 10^5$。 +- $0 \le prices[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:[7,1,5,3,6,4] +输出:5 +解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 + 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 +``` + +- 示例 2: + +```python +输入:prices = [7,6,4,3,1] +输出:0 +解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 +``` + +## 解题思路 + +最简单的思路当然是两重循环暴力枚举,寻找不同天数下的最大利润。但更好的做法是进行一次遍历,递推求解。 + +### 思路 1:递推 + + +1. 设置两个变量 `minprice`(用来记录买入的最小值)、`maxprofit`(用来记录可获取的最大利润)。 +2. 从左到右进行遍历数组 `prices`。 +3. 如果遇到当前价格比 `minprice` 还要小的,就更新 `minprice`。 +4. 如果遇到当前价格大于或者等于 `minprice`,则判断一下以当前价格卖出的话能卖多少,如果比 `maxprofit` 还要大,就更新 `maxprofit`。 +5. 最后输出 `maxprofit`。 + +### 思路 1:代码 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + minprice = 10010 + maxprofit = 0 + for price in prices: + if price < minprice: + minprice = price + elif price - minprice > maxprofit: + maxprofit = price - minprice + return maxprofit +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `prices` 的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/binary-search-tree-iterator.md b/docs/solutions/0100-0199/binary-search-tree-iterator.md new file mode 100644 index 00000000..f24122e9 --- /dev/null +++ b/docs/solutions/0100-0199/binary-search-tree-iterator.md @@ -0,0 +1,79 @@ +# [0173. 二叉搜索树迭代器](https://leetcode.cn/problems/binary-search-tree-iterator/) + +- 标签:栈、树、设计、二叉搜索树、二叉树、迭代器 +- 难度:中等 + +## 题目链接 + +- [0173. 二叉搜索树迭代器 - 力扣](https://leetcode.cn/problems/binary-search-tree-iterator/) + +## 题目大意 + +**要求**:实现一个二叉搜索树的迭代器 BSTIterator。表示一个按中序遍历二叉搜索树(BST)的迭代器: + +- `def __init__(self, root: TreeNode):`:初始化 BSTIterator 类的一个对象,会给出二叉搜索树的根节点。 +- `def hasNext(self) -> bool:`:如果向右指针遍历存在数字,则返回 True,否则返回 False。 +- `def next(self) -> int:`:将指针向右移动,返回指针处的数字。 + +**说明**: + +- 指针初始化为一个不存在于 BST 中的数字,所以对 `next()` 的首次调用将返回 BST 中的最小元素。 +- 可以假设 `next()` 调用总是有效的,也就是说,当调用 `next()` 时,BST 的中序遍历中至少存在一个下一个数字。 +- 树中节点的数目在范围 $[1, 10^5]$ 内。 +- $0 \le Node.val \le 10^6$。 +- 最多调用 $10^5$ 次 `hasNext` 和 `next` 操作。 +- 进阶:设计一个满足下述条件的解决方案,`next()` 和 `hasNext()` 操作均摊时间复杂度为 `O(1)` ,并使用 `O(h)` 内存。其中 `h` 是树的高度。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2018/12/25/bst-tree.png) + +```python +输入 +["BSTIterator", "next", "next", "hasNext", "next", "hasNext", "next", "hasNext", "next", "hasNext"] +[[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], [], []] +输出 +[null, 3, 7, true, 9, true, 15, true, 20, false] +``` + +## 解题思路 + +### 思路 1:中序遍历二叉搜索树 + +中序遍历的顺序是:左、根、右。我们使用一个栈来保存节点,以便于迭代的时候取出对应节点。 + +- 初始的遍历当前节点的左子树,将其路径上的节点存储到栈中。 +- 调用 next 方法的时候,从栈顶取出节点,因为之前已经将路径上的左子树全部存入了栈中,所以此时该节点的左子树为空,这时候取出节点右子树,再将右子树的左子树进行递归遍历,并将其路径上的节点存储到栈中。 +- 调用 hasNext 的方法的时候,直接判断栈中是否有值即可。 + +### 思路 1:代码 + +```python +class BSTIterator: + + def __init__(self, root: TreeNode): + self.stack = [] + self.in_order(root) + + def in_order(self, node): + while node: + self.stack.append(node) + node = node.left + + def next(self) -> int: + node = self.stack.pop() + if node.right: + self.in_order(node.right) + return node.val + + def hasNext(self) -> bool: + return len(self.stack) != 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中节点数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md b/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md new file mode 100644 index 00000000..5bb69661 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md @@ -0,0 +1,82 @@ +# [0107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0107. 二叉树的层序遍历 II - 力扣](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:返回其节点值按照「自底向上」的「层序遍历」(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。 + +**说明**: + +- 树中节点数目在范围 $[0, 2000]$ 内。 +- $-1000 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) + +```python +输入:root = [3,9,20,null,null,15,7] +输出:[[15,7],[9,20],[3]] +``` + +- 示例 2: + +```python +输入:root = [1] +输出:[[1]] +``` + +## 解题思路 + +### 思路 1:二叉树的层次遍历 + +先得到层次遍历的节点顺序,再将其进行反转返回即可。 + +其中层次遍历用到了广度优先搜索,不过需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 + +具体步骤如下: + +1. 根节点入队。 +2. 当队列不为空时,求出当前队列长度 $s_i$。 +3. 依次从队列中取出这 $s_i$ 个元素,将其左右子节点入队,然后继续迭代。 +4. 当队列为空时,结束。 + +### 思路 1:代码 + +```python +class Solution: + def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + while queue: + level = [] + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + level.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(level) + return order[::-1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中节点个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/binary-tree-level-order-traversal.md b/docs/solutions/0100-0199/binary-tree-level-order-traversal.md new file mode 100644 index 00000000..422d0d82 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-level-order-traversal.md @@ -0,0 +1,79 @@ +# [0102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0102. 二叉树的层序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-level-order-traversal/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:返回该二叉树按照「层序遍历」得到的节点值。 + +**说明**: + +- 返回结果为二维数组,每一层都要存为数组返回。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) + +```python +输入:root = [3,9,20,null,null,15,7] +输出:[[3],[9,20],[15,7]] +``` + +- 示例 2: + +```python +输入:root = [1] +输出:[[1] +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +广度优先搜索,需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 $i$ 层上所有元素。 + +具体步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 令根节点入队。 +3. 当队列不为空时,求出当前队列长度 $s_i$。 +4. 依次从队列中取出这 $s_i$ 个元素,并对这 $s_i$ 个元素依次进行访问。然后将其左右孩子节点入队,然后继续遍历下一层节点。 +5. 当队列为空时,结束遍历。 + +### 思路 1:代码 + +```python +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + while queue: + level = [] + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + level.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(level) + return order +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md b/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md new file mode 100644 index 00000000..aa70d4c5 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md @@ -0,0 +1,111 @@ +# [0124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) + +- 标签:树、深度优先搜索、动态规划、二叉树 +- 难度:困难 + +## 题目链接 + +- [0124. 二叉树中的最大路径和 - 力扣](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:返回其最大路径和。 + +**说明**: + +- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 +- **路径和**:路径中各节点值的总和。 +- 树中节点数目范围是 $[1, 3 * 10^4]$。 +- $-1000 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/13/exx1.jpg) + +```python +输入:root = [1,2,3] +输出:6 +解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/10/13/exx2.jpg) + +```python +输入:root = [-10,9,20,null,null,15,7] +输出:42 +解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +根据最大路径和中对应路径是否穿过根节点,我们可以将二叉树分为两种: + +1. 最大路径和中对应路径穿过根节点。 +2. 最大路径和中对应路径不穿过根节点。 + +如果最大路径和中对应路径穿过根节点,则:**该二叉树的最大路径和 = 左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值**。 + +而如果最大路径和中对应路径不穿过根节点,则:**该二叉树的最大路径和 = 所有子树中最大路径和**。 + +即:**该二叉树的最大路径和 = max(左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值,所有子树中最大路径和)**。 + +对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大路径和变量 $ans$。 + +然后定义函数 ` def dfs(self, node):` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 + +计算的结果可能的情况有 $2$ 种: + +1. 经过空节点的最大贡献值等于 $0$。 +2. 经过非空节点的最大贡献值等于 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。如果该贡献值为负数,可以考虑舍弃,即最大贡献值为 $0$。 + +在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 $ans$ 即为答案。具体步骤如下: + +1. 如果根节点 $root$ 为空,则返回 $0$。 +2. 递归计算左子树的最大贡献值为 $left\underline{\hspace{0.5em}}max$。 +3. 递归计算右子树的最大贡献值为 $right\underline{\hspace{0.5em}}max$。 +4. 更新维护最大路径和变量,即 $self.ans = max \lbrace self.ans, \quad left\underline{\hspace{0.5em}}max + right\underline{\hspace{0.5em}}max + node.val \rbrace$。 +5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回 **当前节点值 + 左右子节点提供的最大贡献值中较大的一个**。 +6. 最终 $self.ans$ 即为答案。 + +### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def __init__(self): + self.ans = float('-inf') + + def dfs(self, node): + if not node: + return 0 + left_max = max(self.dfs(node.left), 0) # 左子树提供的最大贡献值 + right_max = max(self.dfs(node.right), 0) # 右子树提供的最大贡献值 + + cur_max = left_max + right_max + node.val # 包含当前节点和左右子树的最大路径和 + self.ans = max(self.ans, cur_max) # 更新所有路径中的最大路径和 + + return max(left_max, right_max) + node.val # 返回包含当前节点的子树的最大贡献值 + + def maxPathSum(self, root: Optional[TreeNode]) -> int: + self.dfs(root) + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/binary-tree-postorder-traversal.md b/docs/solutions/0100-0199/binary-tree-postorder-traversal.md new file mode 100644 index 00000000..eeeb7808 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-postorder-traversal.md @@ -0,0 +1,121 @@ +# [0145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) + +- 标签:栈、树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0145. 二叉树的后序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-postorder-traversal/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:返回该二叉树的后序遍历结果。 + +**说明**: + +- 树中节点数目在范围 $[0, 100]$ 内。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2020/08/28/pre1.jpg) + +```python +输入:root = [1,null,2,3] +输出:[3,2,1] +``` + +- 示例 2: + +```python +输入:root = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +二叉树的后序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先递归遍历左子树。 +3. 然后递归遍历右子树。 +4. 最后访问根节点。 + +### 思路 1:代码 + +```python +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def postorder(root): + if not root: + return + postorder(root.left) + postorder(root.right) + res.append(root.val) + + postorder(root) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:模拟栈迭代遍历 + +我们可以使用一个显式栈 `stack` 来模拟二叉树的后序遍历递归的过程。 + +与前序、中序遍历不同,在后序遍历中,根节点的访问要放在左右子树访问之后。因此,我们要保证:**在左右孩子节点访问结束之前,当前节点不能提前出栈**。 + +我们应该从根节点开始,先将根节点放入栈中,然后依次遍历左子树,不断将当前子树的根节点放入栈中,直到遍历到左子树最左侧的那个节点,从栈中弹出该元素,并判断该元素的右子树是否已经访问完毕,如果访问完毕,则访问该元素。如果未访问完毕,则访问该元素的右子树。 + +二叉树的后序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个空栈,使用 `prev` 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕。 +3. 当根节点或者栈不为空时,从当前节点开始: + 1. 如果当前节点有左子树,则不断遍历左子树,并将当前根节点压入栈中。 + 2. 如果当前节点无左子树,则弹出栈顶元素 `node`。 + 3. 如果栈顶元素 `node` 无右子树(即 `not node.right`)或者右子树已经访问完毕(即 `node.right == prev`),则访问该元素,然后记录前一节点,并将当前节点标记为空节点。 + 4. 如果栈顶元素有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子树。 + +### 思路 2:代码 + +```python +class Solution: + def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + res = [] + stack = [] + prev = None # 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕 + + while root or stack: # 根节点或栈不为空 + while root: + stack.append(root) # 将当前树的根节点入栈 + root = root.left # 继续访问左子树,找到最左侧节点 + + node = stack.pop() # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出 + + # 如果当前节点无右子树或者右子树访问完毕 + if not node.right or node.right == prev: + res.append(node.val)# 访问该节点 + prev = node # 记录前一节点 + root = None # 将当前根节点标记为空 + else: + stack.append(node) # 右子树尚未访问完毕,将当前节点重新压回栈中 + root = node.right # 继续访问右子树 + + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/binary-tree-preorder-traversal.md b/docs/solutions/0100-0199/binary-tree-preorder-traversal.md new file mode 100644 index 00000000..9e5db713 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-preorder-traversal.md @@ -0,0 +1,115 @@ +# [0144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) + +- 标签:栈、树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0144. 二叉树的前序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-preorder-traversal/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:返回该二叉树的前序遍历结果。 + +**说明**: + +- 树中节点数目在范围 $[0, 100]$ 内。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) + +```python +输入:root = [1,null,2,3] +输出:[1,2,3] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/09/15/inorder_4.jpg) + +```python +输入:root = [1,null,2] +输出:[1,2] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +二叉树的前序遍历递归实现步骤为: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 先访问根节点。 +3. 然后递归遍历左子树。 +4. 最后递归遍历右子树。 + +### 思路 1:代码 + +```python +class Solution: + def preorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + + def preorder(root): + if not root: + return + res.append(root.val) + preorder(root.left) + preorder(root.right) + + preorder(root) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:模拟栈迭代遍历 + +二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 `stack` 来模拟递归的过程。 + +前序遍历的顺序为:根节点 - 左子树 - 右子树,而根据栈的「先入后出」特点,所以入栈的顺序应该为:先放入右子树,再放入左子树。这样可以保证最终为前序遍历顺序。 + +二叉树的前序遍历显式栈实现步骤如下: + +1. 判断二叉树是否为空,为空则直接返回。 +2. 初始化维护一个栈,将根节点入栈。 +3. 当栈不为空时: + 1. 弹出栈顶元素 `node`,并访问该元素。 + 2. 如果 `node` 的右子树不为空,则将 `node` 的右子树入栈。 + 3. 如果 `node` 的左子树不为空,则将 `node` 的左子树入栈。 + +### 思路 2:代码 + +```python +class Solution: + def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + if not root: # 二叉树为空直接返回 + return [] + + res = [] + stack = [root] + + while stack: # 栈不为空 + node = stack.pop() # 弹出根节点 + res.append(node.val) # 访问根节点 + if node.right: + stack.append(node.right) # 右子树入栈 + if node.left: + stack.append(node.left) # 左子树入栈 + + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/binary-tree-right-side-view.md b/docs/solutions/0100-0199/binary-tree-right-side-view.md new file mode 100644 index 00000000..2db6632c --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-right-side-view.md @@ -0,0 +1,73 @@ +# [0199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0199. 二叉树的右视图 - 力扣](https://leetcode.cn/problems/binary-tree-right-side-view/) + +## 题目大意 + +**描述**:给定一棵二叉树的根节点 `root`。 + +**要求**:按照从顶部到底部的顺序,返回从右侧能看到的节点值。 + +**说明**: + +- 二叉树的节点个数的范围是 $[0,100]$。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/14/tree.jpg) + +```python +输入: [1,2,3,null,5,null,4] +输出: [1,3,4] +``` + +- 示例 2: + +```python +输入: [1,null,3] +输出: [1,3] +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +使用广度优先搜索对二叉树进行层次遍历。在遍历每层节点的时候,只需要将最后一个节点加入结果数组即可。 + +### 思路 1:代码 + +```python +class Solution: + def rightSideView(self, root: TreeNode) -> List[int]: + if not root: + return [] + queue = [root] + order = [] + while queue: + size = len(queue) + for i in range(size): + curr = queue.pop(0) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if i == size - 1: + order.append(curr.val) + return order +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + + + diff --git a/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md b/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md new file mode 100644 index 00000000..084d01c9 --- /dev/null +++ b/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md @@ -0,0 +1,96 @@ +# [0103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0103. 二叉树的锯齿形层序遍历 - 力扣](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:返回其节点值的锯齿形层序遍历结果。 + +**说明**: + +- **锯齿形层序遍历**:从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) + +```python +输入:root = [3,9,20,null,null,15,7] +输出:[[3],[20,9],[15,7]] +``` + +- 示例 2: + +```python +输入:root = [1] +输出:[[1]] +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +在二叉树的层序遍历的基础上需要增加一些变化。 + +普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 `i` 层上所有元素。 + +新增一个变量 `odd`,用于判断当前层数是奇数层,还是偶数层。从而判断元素遍历方向。 + +存储每层元素的 `level` 列表改用双端队列,如果是奇数层,则从末尾添加元素。如果是偶数层,则从头部添加元素。 + +具体步骤如下: + +1. 使用列表 `order` 存放锯齿形层序遍历结果,使用整数 `odd` 变量用于判断奇偶层,使用双端队列 `level` 存放每层元素,使用列表 `queue` 用于进行广度优先搜索。 +2. 将根节点放入入队列中,即 `queue = [root]`。 +3. 当队列 `queue` 不为空时,求出当前队列长度 $s_i$,并判断当前层数的奇偶性。 +4. 依次从队列中取出这 $s_i$ 个元素。 + 1. 如果当前层为奇数层,如果是奇数层,则从 `level` 末尾添加元素。 + 2. 如果当前层是偶数层,则从 `level` 头部添加元素。 + 3. 然后将当前元素的左右子节点加入队列 `queue` 中,然后继续迭代。 +5. 将存储当前层元素的 `level` 存入答案列表 `order` 中。 +6. 当队列为空时,结束。返回锯齿形层序遍历结果 `order`。 + +### 思路 1:代码 + +```python +import collections +class Solution: + def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + odd = True + while queue: + level = collections.deque() + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + if odd: + level.append(curr.val) + else: + level.appendleft(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(list(level)) + odd = not odd + return order +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/candy.md b/docs/solutions/0100-0199/candy.md new file mode 100644 index 00000000..f81a6918 --- /dev/null +++ b/docs/solutions/0100-0199/candy.md @@ -0,0 +1,89 @@ +# [0135. 分发糖果](https://leetcode.cn/problems/candy/) + +- 标签:贪心、数组 +- 难度:困难 + +## 题目链接 + +- [0135. 分发糖果 - 力扣](https://leetcode.cn/problems/candy/) + +## 题目大意 + +**描述**:$n$ 个孩子站成一排。老师会根据每个孩子的表现,给每个孩子进行评分。然后根据下面的规则给孩子们分发糖果: + +- 每个孩子至少得 $1$ 个糖果。 +- 评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果。 + +现在给定 $n$ 个孩子的表现分数数组 `ratings`,其中 `ratings[i]` 表示第 $i$ 个孩子的评分。 + +**要求**:返回最少需要准备的糖果数目。 + +**说明**: + +- $n == ratings.length$。 +- $1 \le n \le 2 \times 10^4$。 +- $0 \le ratings[i] \le 2 * 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:ratings = [1,0,2] +输出:5 +解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。 +``` + +- 示例 2: + +```python +输入:ratings = [1,2,2] +输出:4 +解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 + 第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +先来看分发糖果的规则。 + +「每个孩子至少得 1 个糖果」:说明糖果数目至少为 N 个。 + +「评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果」:可以看做为以下两种条件: + +- 当 $ratings[i - 1] < ratings[i]$ 时,第 i 个孩子的糖果数量比第 $i - 1$ 个孩子的糖果数量多; +- 当 $ratings[i] > ratings[i + 1]$ 时,第 i 个孩子的糖果数量比第$ i + 1$ 个孩子的糖果数量多。 + +根据以上信息,我们可以设定一个长度为 N 的数组 sweets 来表示每个孩子分得的最少糖果数,初始每个孩子分得糖果数都为 1。 + +然后遍历两遍数组,第一遍遍历满足当 $ratings[i - 1] < ratings[i]$ 时,第 $i$ 个孩子的糖果数量比第 $i - 1$ 个孩子的糖果数量多 $1$ 个。第二遍遍历满足当 $ratings[i] > ratings[i + 1]$ 时,第 $i$ 个孩子的糖果数量取「第 $i + 1$ 个孩子的糖果数量多 $1$ 个」和「第 $i + 1$ 个孩子目前拥有的糖果数量」中的最大值。 + +然后再遍历求所有孩子的糖果数量和即为答案。 + +### 思路 1:代码 + +```python +class Solution: + def candy(self, ratings: List[int]) -> int: + size = len(ratings) + sweets = [1 for _ in range(size)] + + for i in range(1, size): + if ratings[i] > ratings[i - 1]: + sweets[i] = sweets[i - 1] + 1 + + for i in range(size - 2, -1, -1): + if ratings[i] > ratings[i + 1]: + sweets[i] = max(sweets[i], sweets[i + 1] + 1) + + res = sum(sweets) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `ratings` 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/clone-graph.md b/docs/solutions/0100-0199/clone-graph.md new file mode 100644 index 00000000..7ed17f1e --- /dev/null +++ b/docs/solutions/0100-0199/clone-graph.md @@ -0,0 +1,131 @@ +# [0133. 克隆图](https://leetcode.cn/problems/clone-graph/) + +- 标签:深度优先搜索、广度优先搜索、图、哈希表 +- 难度:中等 + +## 题目链接 + +- [0133. 克隆图 - 力扣](https://leetcode.cn/problems/clone-graph/) + +## 题目大意 + +**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。 + +**要求**:返回该图的深拷贝。 + +**说明**: + +- 节点数不超过 $100$。 +- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。 +- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。 +- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。 +- 图是连通图,你可以从给定节点访问到所有节点。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png) + +```python +输入:adjList = [[2,4],[1,3],[2,4],[1,3]] +输出:[[2,4],[1,3],[2,4],[1,3]] +解释: +图中有 4 个节点。 +节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 +节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 +节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 +节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png) + +```python +输入:adjList = [[2],[1]] +输出:[[2],[1]] +``` + +## 解题思路 + +所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。 + +可以用深度优先搜索或者广度优先搜索来做。 + +### 思路 1:深度优先搜索 + +1. 使用哈希表 $visitedDict$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。 +2. 从给定节点开始,以深度优先搜索的方式遍历原图。 + 1. 如果当前节点被访问过,则返回隆图中对应节点。 + 2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。 + 3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。 +3. 递归结束,返回克隆节点。 + +### 思路 1:代码 + +```python +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + if not node: + return node + visited = dict() + + def dfs(node: 'Node') -> 'Node': + if node in visited: + return visited[node] + + clone_node = Node(node.val, []) + visited[node] = clone_node + for neighbor in node.neighbors: + clone_node.neighbors.append(dfs(neighbor)) + return clone_node + + return dfs(node) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:广度优先搜索 + +1. 使用哈希表 $visited$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。使用队列 $queue$ 存放节点。 +2. 根据起始节点 $node$,创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列中,即 `queue.append(node)`。 +3. 从队列中取出第一个节点 $node\underline{\hspace{0.5em}}u$。访问节点 $node\underline{\hspace{0.5em}}u$。 +4. 遍历节点 $node\underline{\hspace{0.5em}}u$ 的所有未访问邻接节点 $node\underline{\hspace{0.5em}}v$(节点 $node\underline{\hspace{0.5em}}v$ 不在 $visited$ 中)。 +5. 根据节点 $node\underline{\hspace{0.5em}}v$ 创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node_v] = Node(node_v.val, [])`。 +6. 然后将节点 $node\underline{\hspace{0.5em}}v$ 放入队列 $queue$ 中,即 `queue.append(node_v)`。 +7. 重复步骤 $3 \sim 6$,直到队列 $queue$ 为空。 +8. 广度优先搜索结束,返回起始节点的克隆节点(即 $visited[node]$)。 + +### 思路 2:代码 + +```python +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + if not node: + return node + + visited = dict() + queue = collections.deque() + + visited[node] = Node(node.val, []) + queue.append(node) + + while queue: + node_u = queue.popleft() + for node_v in node_u.neighbors: + if node_v not in visited: + visited[node_v] = Node(node_v.val, []) + queue.append(node_v) + visited[node_u].neighbors.append(visited[node_v]) + + return visited[node] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md b/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md new file mode 100644 index 00000000..fc2abb52 --- /dev/null +++ b/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md @@ -0,0 +1,78 @@ +# [0106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) + +- 标签:树、数组、哈希表、分治、二叉树 +- 难度:中等 + +## 题目链接 + +- [0106. 从中序与后序遍历序列构造二叉树 - 力扣](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) + +## 题目大意 + +**描述**:给定一棵二叉树的中序遍历结果 `inorder` 和后序遍历结果 `postorder`。 + +**要求**:构造出该二叉树并返回其根节点。 + +**说明**: + +- $1 \le inorder.length \le 3000$。 +- $postorder.length == inorder.length$。 +- $-3000 \le inorder[i], postorder[i] \le 3000$。 +- `inorder` 和 `postorder` 都由不同的值组成。 +- `postorder` 中每一个值都在 `inorder` 中。 +- `inorder` 保证是二叉树的中序遍历序列。 +- `postorder` 保证是二叉树的后序遍历序列。 +- `inorder` 保证为二叉树的中序遍历序列。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) + +```python +输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] +输出:[3,9,20,null,null,15,7] +``` + +- 示例 2: + +```python +输入:inorder = [-1], postorder = [-1] +输出:[-1] +``` + +## 解题思路 + +### 思路 1:递归 + +中序遍历的顺序是:左 -> 根 -> 右。后序遍历的顺序是:左 -> 右 -> 根。根据后序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: + +1. 从后序遍历顺序中当前根节点的位置在 `postorder[n - 1]`。 +2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 +3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 思路 1:代码 + +```python +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: + def createTree(inorder, postorder, n): + if n == 0: + return None + k = 0 + while postorder[n-1] != inorder[k]: + k += 1 + node = TreeNode(inorder[k]) + node.right = createTree(inorder[k+1: n], postorder[k: n-1], n-k-1) + node.left = createTree(inorder[0: k], postorder[0: k], k) + return node + return createTree(inorder, postorder, len(postorder)) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md b/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md new file mode 100644 index 00000000..58f7605b --- /dev/null +++ b/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md @@ -0,0 +1,76 @@ +# [0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) + +- 标签:树、数组、哈希表、分治、二叉树 +- 难度:中等 + +## 题目链接 + +- [0105. 从前序与中序遍历序列构造二叉树 - 力扣](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) + +## 题目大意 + +**描述**:给定一棵二叉树的前序遍历结果 `preorder` 和中序遍历结果 `inorder`。 + +**要求**:构造出该二叉树并返回其根节点。 + +**说明**: + +- $1 \le preorder.length \le 3000$。 +- $inorder.length == preorder.length$。 +- $-3000 \le preorder[i], inorder[i] \le 3000$。 +- `preorder` 和 `inorder` 均无重复元素。 +- `inorder` 均出现在 `preorder`。 +- `preorder` 保证为二叉树的前序遍历序列。 +- `inorder` 保证为二叉树的中序遍历序列。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) + +```python +输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] +输出: [3,9,20,null,null,15,7] +``` + +- 示例 2: + +```python +输入: preorder = [-1], inorder = [-1] +输出: [-1] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +前序遍历的顺序是:根 -> 左 -> 右。中序遍历的顺序是:左 -> 根 -> 右。根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: + +1. 从前序遍历顺序中当前根节点的位置在 `postorder[0]`。 +2. 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 +3. 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 思路 1:代码 + +```python +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: + def createTree(preorder, inorder, n): + if n == 0: + return None + k = 0 + while preorder[0] != inorder[k]: + k += 1 + node = TreeNode(inorder[k]) + node.left = createTree(preorder[1: k+1], inorder[0: k], k) + node.right = createTree(preorder[k+1:], inorder[k+1:], n-k-1) + return node + return createTree(preorder, inorder, len(inorder)) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md b/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md new file mode 100644 index 00000000..982e5e48 --- /dev/null +++ b/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md @@ -0,0 +1,71 @@ +# [0108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) + +- 标签:树、二叉搜索树、数组、分治、二叉树 +- 难度:简单 + +## 题目链接 + +- [0108. 将有序数组转换为二叉搜索树 - 力扣](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) + +## 题目大意 + +**描述**:给定一个升序的有序数组 `nums`。 + +**要求**:将其转换为一棵高度平衡的二叉搜索树。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- `nums` 按严格递增顺序排列。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2021/02/18/btree1.jpg) + +```python +输入:nums = [-10,-3,0,5,9] +输出:[0,-3,9,-10,null,5] +解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案 +``` + +- 示例 2: + +![img](https://assets.leetcode.com/uploads/2021/02/18/btree.jpg) + +```python +输入:nums = [1,3] +输出:[3,1] +解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。 +``` + +## 解题思路 + +### 思路 1:递归遍历 + +直观上,如果把数组的中间元素当做根,那么数组左侧元素都小于根节点,右侧元素都大于根节点,且左右两侧元素个数相同,或最多相差 $1$ 个。那么构建的树高度差也不会超过 $1$。 + +所以猜想出:如果左右子树越平均,树就越平衡。这样我们就可以每次取中间元素作为当前的根节点,两侧的元素作为左右子树递归建树,左侧区间 $[L, mid - 1]$ 作为左子树,右侧区间 $[mid + 1, R]$ 作为右子树。 + +### 思路 1:代码 + +```python +class Solution: + def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]: + def build(left, right): + if left > right: + return + mid = left + (right - left) // 2 + root = TreeNode(nums[mid]) + root.left = build(left, mid - 1) + root.right = build(mid + 1, right) + return root + return build(0, len(nums) - 1) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是数组的长度。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/copy-list-with-random-pointer.md b/docs/solutions/0100-0199/copy-list-with-random-pointer.md new file mode 100644 index 00000000..b5db18c4 --- /dev/null +++ b/docs/solutions/0100-0199/copy-list-with-random-pointer.md @@ -0,0 +1,76 @@ +# [0138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) + +- 标签:哈希表、链表 +- 难度:中等 + +## 题目链接 + +- [0138. 随机链表的复制 - 力扣](https://leetcode.cn/problems/copy-list-with-random-pointer/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`,链表中每个节点除了 `next` 指针之外,还包含一个随机指针 `random`,该指针可以指向链表中的任何节点或者空节点。 + +**要求**:将该链表进行深拷贝。返回复制链表的头节点。 + +**说明**: + +- $0 \le n \le 1000$。 +- $-10^4 \le Node.val \le 10^4$。 +- `Node.random` 为 `null` 或指向链表中的节点。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e1.png) + +```python +输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] +输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e2.png) + +```python +输入:head = [[1,1],[2,1]] +输出:[[1,1],[2,1]] +``` + +## 解题思路 + +### 思路 1:迭代 + +1. 遍历链表,利用哈希表,以 `旧节点: 新节点` 为映射关系,将节点关系存储下来。 +2. 再次遍历链表,将新链表的 `next` 和 `random` 指针设置好。 + +### 思路 1:代码 + +```python +class Solution: + def copyRandomList(self, head: 'Node') -> 'Node': + if not head: + return None + node_dict = dict() + curr = head + while curr: + new_node = Node(curr.val, None, None) + node_dict[curr] = new_node + curr = curr.next + curr = head + while curr: + if curr.next: + node_dict[curr].next = node_dict[curr.next] + if curr.random: + node_dict[curr].random = node_dict[curr.random] + curr = curr.next + return node_dict[head] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/distinct-subsequences.md b/docs/solutions/0100-0199/distinct-subsequences.md new file mode 100644 index 00000000..5e198c1f --- /dev/null +++ b/docs/solutions/0100-0199/distinct-subsequences.md @@ -0,0 +1,89 @@ +# [0115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) + +- 标签:字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0115. 不同的子序列 - 力扣](https://leetcode.cn/problems/distinct-subsequences/) + +## 题目大意 + +**描述**:给定两个字符串 `s` 和 `t`。 + +**要求**:计算在 `s` 的子序列中 `t` 出现的个数。 + +**说明**: + +- **字符串的子序列**:通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,`"ACE"` 是 `"ABCDE"` 的一个子序列,而 `"AEC"` 不是)。 +- $0 \le s.length, t.length \le 1000$。 +- `s` 和 `t` 由英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "rabbbit", t = "rabbit" +输出:3 +解释:如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 +``` + +$\underline{rabb}b\underline{it}$ +$\underline{ra}b\underline{bbit}$ +$\underline{rab}b\underline{bit}$ + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照子序列的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。 + +###### 3. 状态转移方程 + +双重循环遍历字符串 `s` 和 `t`,则状态转移方程为: + +- 如果 `s[i - 1] == t[j - 1]`,则:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`。即 `dp[i][j]` 来源于两部分: + - 使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 2` 为结尾的 `t` 的个数,即 `dp[i - 1][j - 1]`。 + - 不使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数,即 `dp[i - 1][j]`。 +- 如果 `s[i - 1] != t[j - 1]`,那么肯定不能用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于 `dp[i - 1][j]`。 + +###### 4. 初始条件 + +- `dp[i][0]` 表示以 `i - 1` 为结尾的 `s` 子序列中出现空字符串的个数。把 `s` 中的元素全删除,出现空字符串的个数就是 `1`,则 `dp[i][0] = 1`。 +- `dp[0][j]` 表示空字符串中出现以 `j - 1` 结尾的 `t` 的个数,空字符串无论怎么变都不会变成 `t`,则 `dp[0][j] = 0` +- `dp[0][0]` 表示空字符串中出现空字符串的个数,这个应该是 `1`,即 `dp[0][0] = 1`。 + +##### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。则最终结果为 `dp[size_s][size_t]`,将其返回即可。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def numDistinct(self, s: str, t: str) -> int: + size_s = len(s) + size_t = len(t) + dp = [[0 for _ in range(size_t + 1)] for _ in range(size_s + 1)] + for i in range(size_s): + dp[i][0] = 1 + for i in range(1, size_s + 1): + for j in range(1, size_t + 1): + if s[i - 1] == t[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] + else: + dp[i][j] = dp[i - 1][j] + return dp[size_s][size_t] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 diff --git a/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md b/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md new file mode 100644 index 00000000..635ede0a --- /dev/null +++ b/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md @@ -0,0 +1,89 @@ +# [0150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) + +- 标签:栈、数组、数学 +- 难度:中等 + +## 题目链接 + +- [0150. 逆波兰表达式求值 - 力扣](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) + +## 题目大意 + +**描述**:给定一个字符串数组 `tokens`,表示「逆波兰表达式」。 + +**要求**:求解表达式的值。 + +**说明**: + +- **逆波兰表达式**:也称为后缀表达式。 + - 中缀表达式 `( 1 + 2 ) * ( 3 + 4 ) `,对应的逆波兰表达式为 ` ( ( 1 2 + ) ( 3 4 + ) * )` 。 + +- $1 \le tokens.length \le 10^4$。 +- `tokens[i]` 是一个算符(`+`、`-`、`*` 或 `/`),或是在范围 $[-200, 200]$ 内的一个整数。 + +**示例**: + +- 示例 1: + +```python +输入:tokens = ["4","13","5","/","+"] +输出:6 +解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 +``` + +- 示例 2: + +```python +输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] +输出:22 +解释:该算式转化为常见的中缀算术表达式为: + ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 += ((10 * (6 / (12 * -11))) + 17) + 5 += ((10 * (6 / -132)) + 17) + 5 += ((10 * 0) + 17) + 5 += (0 + 17) + 5 += 17 + 5 += 22 +``` + +## 解题思路 + +### 思路 1:栈 + +这道题是栈的典型应用。我们先来简单介绍一下逆波兰表达式。 + +逆波兰表达式,也叫做后缀表达式,特点是:没有括号,运算符总是放在和它相关的操作数之后。 +我们平常见到的表达式是中缀表达式,可写为:`A 运算符 B`。其中 `A`、`B` 都是操作数。 +而后缀表达式可写为:`A B 运算符`。 + +逆波兰表达式的计算遵循从左到右的规律。我们在计算逆波兰表达式的值时,可以使用一个栈来存放当前的操作数,从左到右依次遍历逆波兰表达式,计算出对应的值。具体操作步骤如下: + +1. 使用列表 `stack` 作为栈存放操作数,然后遍历表达式的字符串数组。 +2. 如果当前字符为运算符,则取出栈顶两个元素,在进行对应的运算之后,再将运算结果入栈。 +3. 如果当前字符为数字,则直接将数字入栈。 +4. 遍历结束后弹出栈中最后剩余的元素,这就是最终结果。 + +### 思路 1:代码 + +```python +class Solution: + def evalRPN(self, tokens: List[str]) -> int: + stack = [] + for token in tokens: + if token == '+': + stack.append(stack.pop() + stack.pop()) + elif token == '-': + stack.append(-stack.pop() + stack.pop()) + elif token == '*': + stack.append(stack.pop() * stack.pop()) + elif token == '/': + stack.append(int(1 / stack.pop() * stack.pop())) + else: + stack.append(int(token)) + return stack.pop() +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0100-0199/excel-sheet-column-number.md b/docs/solutions/0100-0199/excel-sheet-column-number.md new file mode 100644 index 00000000..710a0621 --- /dev/null +++ b/docs/solutions/0100-0199/excel-sheet-column-number.md @@ -0,0 +1,36 @@ +# [0171. Excel 表列序号](https://leetcode.cn/problems/excel-sheet-column-number/) + +- 标签:数学、字符串 +- 难度:简单 + +## 题目链接 + +- [0171. Excel 表列序号 - 力扣](https://leetcode.cn/problems/excel-sheet-column-number/) + +## 题目大意 + +给你一个字符串 `columnTitle` ,表示 Excel 表格中的列名称。 + +要求:返回该列名称对应的列序号。 + +## 解题思路 + +Excel 表的列名称由大写字母组成,共有 26 个,因此列名称的表示实质是 26 进制,需要将 26 进制转换成十进制。转换过程如下: + +- 将每一位对应列名称转换成整数(注意列序号从 `1` 开始)。 +- 将当前结果乘上进制数(`26`),然后累加上当前位上的整数。 + +最后输出答案。 + +## 代码 + +```python +class Solution: + def titleToNumber(self, columnTitle: str) -> int: + ans = 0 + for ch in columnTitle: + num = ord(ch) - ord('A') + 1 + ans = ans * 26 + num + return ans +``` + diff --git a/docs/solutions/0100-0199/excel-sheet-column-title.md b/docs/solutions/0100-0199/excel-sheet-column-title.md new file mode 100644 index 00000000..bbbcb500 --- /dev/null +++ b/docs/solutions/0100-0199/excel-sheet-column-title.md @@ -0,0 +1,34 @@ +# [0168. Excel 表列名称](https://leetcode.cn/problems/excel-sheet-column-title/) + +- 标签:数学、字符串 +- 难度:简单 + +## 题目链接 + +- [0168. Excel 表列名称 - 力扣](https://leetcode.cn/problems/excel-sheet-column-title/) + +## 题目大意 + +描述:给定一个正整数 columnNumber。 + +要求:返回它在 Excel 表中相对应的列名称。 + +1 -> A,2 -> B,3 -> C,…,26 -> Z,…,28 -> AB + +## 解题思路 + +实质上就是 10 进制转 26 进制。不过映射范围是 1~26,而不是 0~25,如果将 columnNumber 直接对 26 取余,则结果为 0~25,而本题余数为 1~26。可以直接将 columnNumber = columnNumber - 1,这样就可以将范围变为 0~25 就更加容易判断了。 + +## 代码 + +```python +class Solution: + def convertToTitle(self, columnNumber: int) -> str: + s = "" + while columnNumber: + columnNumber -= 1 + s = chr(65 + columnNumber % 26) + s + columnNumber //= 26 + return s +``` + diff --git a/docs/solutions/0100-0199/factorial-trailing-zeroes.md b/docs/solutions/0100-0199/factorial-trailing-zeroes.md new file mode 100644 index 00000000..644d9f6a --- /dev/null +++ b/docs/solutions/0100-0199/factorial-trailing-zeroes.md @@ -0,0 +1,33 @@ +# [0172. 阶乘后的零](https://leetcode.cn/problems/factorial-trailing-zeroes/) + +- 标签:数学 +- 难度:中等 + +## 题目链接 + +- [0172. 阶乘后的零 - 力扣](https://leetcode.cn/problems/factorial-trailing-zeroes/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:返回 `n!` 结果中尾随零的数量。 + +注意:$0 <= n <= 10^4$ + +## 解题思路 + +阶乘中,末尾 `0` 的来源只有 `2 * 5`。所以尾随 `0` 的个数为 `2` 的倍数个数和 `5` 的倍数个数的最小值。又因为 `2 < 5`,`2` 的倍数个数肯定小于等于 `5` 的倍数,所以直接统计 `5` 的倍数个数即可。 + +## 代码 + +```python +class Solution: + def trailingZeroes(self, n: int) -> int: + count = 0 + while n > 0: + count += n // 5 + n = n // 5 + return count +``` + diff --git a/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md b/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md new file mode 100644 index 00000000..5a63ffa3 --- /dev/null +++ b/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md @@ -0,0 +1,98 @@ +# [154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) + +- 标签:数组、二分查找 +- 难度:困难 + +## 题目链接 + +- [154. 寻找旋转排序数组中的最小值 II - 力扣](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,$nums$ 是有升序数组经过 $1 \sim n$ 次「旋转」得到的。但是旋转次数未知。数组中可能存在重复元素。 + +**要求**:找出数组中的最小元素。 + +**说明**: + +- 旋转:将数组整体右移 $1$ 位。数组 $[a[0], a[1], a[2], ..., a[n-1]]$ 旋转一次的结果为数组 $[a[n-1], a[0], a[1], a[2], ..., a[n-2]]$。 +- $n == nums.length$。 +- $1 \le n \le 5000$。 +- $-5000 \le nums[i] \le 5000$ +- $nums$ 原来是一个升序排序的数组,并进行了 $1 \sim n$ 次旋转。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,5] +输出:1 +``` + +- 示例 2: + +```python +输入:nums = [2,2,2,0,1] +输出:0 +``` + +## 解题思路 + +### 思路 1:二分查找 + +数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 + +第一种的最小值在最左边。 + +``` + * + * + * + * + * +* +``` + +第二种最小值在第二段升序序列的第一个元素。 + +``` + * + * +* + * + * + * +``` + +最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 + +创建两个指针 $left$、$right$,分别指向数组首尾。然后计算出两个指针中间值 $mid$。将 $mid$ 与右边界进行比较。 + +1. 如果 $nums[mid] > nums[right]$,则最小值不可能在 $mid$ 左侧,一定在 $mid$ 右侧,则将 $left$ 移动到 $mid + 1$ 位置,继续查找右侧区间。 +2. 如果 $nums[mid] < nums[right]$,则最小值一定在 $mid$ 左侧,令右边界 $right$ 为 $mid$,继续查找左侧区间。 +3. 如果 $nums[mid] == nums[right]$,无法判断在 $mid$ 的哪一侧,可以采用 `right = right - 1` 逐步缩小区域。 + +### 思路 1:代码 + +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + if nums[mid] > nums[right]: + left = mid + 1 + elif nums[mid] < nums[right]: + right = mid + else: + right = right - 1 + return nums[left] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md b/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md new file mode 100644 index 00000000..4f6deffb --- /dev/null +++ b/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md @@ -0,0 +1,96 @@ +# [0153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0153. 寻找旋转排序数组中的最小值 - 力扣](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,$nums$ 是有升序数组经过「旋转」得到的。但是旋转次数未知。数组中不存在重复元素。 + +**要求**:找出数组中的最小元素。 + +**说明**: + +- 旋转操作:将数组整体右移若干位置。 +- $n == nums.length$。 +- $1 \le n \le 5000$。 +- $-5000 \le nums[i] \le 5000$。 +- $nums$ 中的所有整数互不相同。 +- $nums$ 原来是一个升序排序的数组,并进行了 $1$ 至 $n$ 次旋转。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,4,5,1,2] +输出:1 +解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。 +``` + +- 示例 2: + +```python +输入:nums = [4,5,6,7,0,1,2] +输出:0 +解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 + +第一种的最小值在最左边。第二种最小值在第二段升序序列的第一个元素。 + +```python + * + * + * + * + * +* +``` + +```python + * + * +* + * + * + * +``` + +最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 + +创建两个指针 $left$、$right$,分别指向数组首尾。让后计算出两个指针中间值 $mid$。将 $mid$ 与两个指针做比较。 + +1. 如果 $nums[mid] > nums[right]$,则最小值不可能在 $mid$ 左侧,一定在 $mid$ 右侧,则将 $left$ 移动到 $mid + 1$ 位置,继续查找右侧区间。 +2. 如果 $nums[mid] \le nums[right]$,则最小值一定在 $mid$ 左侧,或者 $mid$ 位置,将 $right$ 移动到 $mid$ 位置上,继续查找左侧区间。 + +### 思路 1:代码 + +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + if nums[mid] > nums[right]: + left = mid + 1 + else: + right = mid + return nums[left] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0100-0199/find-peak-element.md b/docs/solutions/0100-0199/find-peak-element.md new file mode 100644 index 00000000..5a28cbc0 --- /dev/null +++ b/docs/solutions/0100-0199/find-peak-element.md @@ -0,0 +1,72 @@ +# [0162. 寻找峰值](https://leetcode.cn/problems/find-peak-element/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0162. 寻找峰值 - 力扣](https://leetcode.cn/problems/find-peak-element/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`。 + +**要求**:找到峰值元素并返回其索引。必须实现时间复杂度为 $O(\log n)$ 的算法来解决此问题。 + +**说明**: + +- **峰值元素**:指其值严格大于左右相邻值的元素。 +- 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 +- 可以假设 $nums[-1] = nums[n] = -∞$。 +- $1 \le nums.length \le 1000$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- 对于所有有效的 $i$ 都有 $nums[i] != nums[i + 1]$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,1] +输出:2 +解释:3 是峰值元素,你的函数应该返回其索引 2。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,1,3,5,6,4] +输出:1 或 5 +解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +1. 使用两个指针 `left`、`right` 。`left` 指向数组第一个元素,`right` 指向数组最后一个元素。 +2. 取区间中间节点 `mid`,并比较 `nums[mid]` 和 `nums[mid + 1]` 的值大小。 + 1. 如果 `nums[mid]` 小于 `nums[mid + 1]`,则右侧存在峰值,令 `left = mid + 1`。 + 2. 如果 `nums[mid]` 大于等于 `nums[mid + 1]`,则左侧存在峰值,令 `right = mid`。 +3. 最后,当 `left == right` 时,跳出循环,返回 `left`。 + +### 思路 1:代码 + +```python +class Solution: + def findPeakElement(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + if nums[mid] < nums[mid + 1]: + left = mid + 1 + else: + right = mid + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log_2 n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/fraction-to-recurring-decimal.md b/docs/solutions/0100-0199/fraction-to-recurring-decimal.md new file mode 100644 index 00000000..1ebc3258 --- /dev/null +++ b/docs/solutions/0100-0199/fraction-to-recurring-decimal.md @@ -0,0 +1,56 @@ +# [0166. 分数到小数](https://leetcode.cn/problems/fraction-to-recurring-decimal/) + +- 标签:哈希表、数学、字符串 +- 难度:中等 + +## 题目链接 + +- [0166. 分数到小数 - 力扣](https://leetcode.cn/problems/fraction-to-recurring-decimal/) + +## 题目大意 + +给定两个整数,分别表示分数的分子 numerator 和分母 denominator,要求以字符串的形式返回该分数对应小数结果。 + +- 如果小数部分为循环小数,则将循环的小数部分括在括号内。 + +## 解题思路 + +先处理特殊数据,例如 0、负数等。 + +然后利用整除运算,计算出分数的整数部分。在根据取余运算结果,判断是否含有小数部分。 + +因为小数部分可能会有循环部分,所以使用哈希表来判断是否出现了循环小数。哈希表所存键值为 数字:数字开始位置。 + +然后计算小数部分,每次将被除数 * 10 然后对除数进行整除,再对被除数进行取余操作,直到被除数变为 0,或者在字典中出现了循环小数为止。 + +## 代码 + +```python +class Solution: + def fractionToDecimal(self, numerator: int, denominator: int) -> str: + if numerator == 0: + return '0' + res = [] + if numerator ^ denominator < 0: + res.append('-') + numerator, denominator = abs(numerator), abs(denominator) + res.append(str(numerator // denominator)) + numerator %= denominator + if numerator == 0: + return ''.join(res) + res.append('.') + + record = dict() + while numerator: + if numerator not in record: + record[numerator] = len(res) + numerator *= 10 + res.append(str(numerator // denominator)) + numerator %= denominator + else: + res.insert(record[numerator], '(') + res.append(')') + break + return ''.join(res) +``` + diff --git a/docs/solutions/0100-0199/gas-station.md b/docs/solutions/0100-0199/gas-station.md new file mode 100644 index 00000000..60dc48de --- /dev/null +++ b/docs/solutions/0100-0199/gas-station.md @@ -0,0 +1,59 @@ +# [0134. 加油站](https://leetcode.cn/problems/gas-station/) + +- 标签:贪心、数组 +- 难度:中等 + +## 题目链接 + +- [0134. 加油站 - 力扣](https://leetcode.cn/problems/gas-station/) + +## 题目大意 + +一条环路上有 N 个加油站,第 i 个加油站有 gas[i] 升汽油。 + +现在有一辆油箱无限容量的汽车,从第 i 个加油站开往第 i + 1 个加油站需要消耗汽油 cost[i] 升。如果汽车上携带的有两不够 cost[i],则无法从第 i 个加油站开往第 i + 1 个加油站。 + +现在从其中一个加油站开始出发,且出发时油箱为空。如果能绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。 + +## 解题思路 + +1. 暴力求解 + +分别考虑从第 0 个点、第 1 个点、…、第 i 个点出发,能否回到第 0 个点、第 1 个点、…、第 i 个点。 + +2. 贪心算法 + +- 如果加油站提供的油总和大于等于消耗的汽油量,则必定可以绕环路行驶一周 +- 假设先不考虑油量为负的情况,我们从「第 0 个加油站」出发,环行一周。记录下汽油量 gas[i] 和 cost[i] 差值总和 sum_diff,同时记录下油箱剩余油量的最小值 min_sum。 +- 如果差值总和 sum_diff < 0,则无论如何都不能环行一周。油不够啊,亲!! +- 如果 min_sum ≥ 0,则行驶过程中油箱始终有油,则可以从 0 个加油站出发环行一周。 +- 如果 min_sum < 0,则说明行驶过程中油箱油不够了,那么考虑更换开始的起点。 + - 从右至左遍历,计算汽油量 gas[i] 和 cost[i] 差值,看哪个加油站能将 min_sum 填平。如果最终达到 min_sum ≥ 0,则说明从该点开始出发,油箱中的油始终不为空,则返回该点下标。 + - 如果找不到最返回 -1。 + +## 代码 + +```python +class Solution: + def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int: + sum_diff, min_sum = 0, float('inf') + for i in range(len(gas)): + sum_diff += gas[i] - cost[i] + min_sum = min(min_sum, sum_diff) + + if sum_diff < 0: + return -1 + + if min_sum >= 0: + return 0 + + for i in range(len(gas)-1, -1, -1): + min_sum += gas[i] - cost[i] + if min_sum >= 0: + return i + return -1 +``` + +## 参考链接 + +- [贪心算法/前缀和 - 加油站 - 力扣(LeetCode)](https://leetcode.cn/problems/gas-station/solution/tan-xin-suan-fa-qian-zhui-he-by-antione/) diff --git a/docs/solutions/0100-0199/house-robber.md b/docs/solutions/0100-0199/house-robber.md new file mode 100644 index 00000000..00f70c15 --- /dev/null +++ b/docs/solutions/0100-0199/house-robber.md @@ -0,0 +1,98 @@ +# [0198. 打家劫舍](https://leetcode.cn/problems/house-robber/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0198. 打家劫舍 - 力扣](https://leetcode.cn/problems/house-robber/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,$nums[i]$ 代表第 $i$ 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 + +**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 + +**说明**: + +- $1 \le nums.length \le 100$。 +- $0 \le nums[i] \le 400$。 + +**示例**: + +- 示例 1: + +```python +输入:[1,2,3,1] +输出:4 +解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 + 偷窃到的最高金额 = 1 + 3 = 4。 +``` + +- 示例 2: + +```python +输入:[2,7,9,3,1] +输出:12 +解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 + 偷窃到的最高金额 = 2 + 9 + 1 = 12。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照房屋序号进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。 + +###### 3. 状态转移方程 + +$i$ 间房屋的最后一个房子是 $nums[i - 1]$。 + +如果房屋数大于等于 $2$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态: + +1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$; +1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$。 + +然后这两种状态取最大值即可,即状态转移方程为: + +$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$ + +###### 4. 初始条件 + +- 前 $0$ 间房屋所能偷窃到的最高金额为 $0$,即 $dp[0] = 0$。 +- 前 $1$ 间房屋所能偷窃到的最高金额为 $nums[0]$,即:$dp[1] = nums[0]$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。则最终结果为 $dp[size]$,$size$ 为总的房屋数。 + +### 思路 1:代码 + +```python +class Solution: + def rob(self, nums: List[int]) -> int: + size = len(nums) + if size == 0: + return 0 + + dp = [0 for _ in range(size + 1)] + dp[0] = 0 + dp[1] = nums[0] + + for i in range(2, size + 1): + dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1]) + + return dp[size] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/index.md b/docs/solutions/0100-0199/index.md new file mode 100644 index 00000000..61dcf2cf --- /dev/null +++ b/docs/solutions/0100-0199/index.md @@ -0,0 +1,72 @@ +## 本章内容 + +- [0100. 相同的树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/same-tree.md) +- [0101. 对称二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/symmetric-tree.md) +- [0102. 二叉树的层序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal.md) +- [0103. 二叉树的锯齿形层序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-zigzag-level-order-traversal.md) +- [0104. 二叉树的最大深度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md) +- [0105. 从前序与中序遍历序列构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-preorder-and-inorder-traversal.md) +- [0106. 从中序与后序遍历序列构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/construct-binary-tree-from-inorder-and-postorder-traversal.md) +- [0107. 二叉树的层序遍历 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-level-order-traversal-ii.md) +- [0108. 将有序数组转换为二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/convert-sorted-array-to-binary-search-tree.md) +- [0110. 平衡二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/balanced-binary-tree.md) +- [0111. 二叉树的最小深度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md) +- [0112. 路径总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum.md) +- [0113. 路径总和 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/path-sum-ii.md) +- [0115. 不同的子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/distinct-subsequences.md) +- [0116. 填充每个节点的下一个右侧节点指针](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md) +- [0117. 填充每个节点的下一个右侧节点指针 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md) +- [0118. 杨辉三角](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle.md) +- [0119. 杨辉三角 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/pascals-triangle-ii.md) +- [0120. 三角形最小路径和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/triangle.md) +- [0121. 买卖股票的最佳时机](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock.md) +- [0122. 买卖股票的最佳时机 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-ii.md) +- [0123. 买卖股票的最佳时机 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iii.md) +- [0124. 二叉树中的最大路径和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-maximum-path-sum.md) +- [0125. 验证回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/valid-palindrome.md) +- [0127. 单词接龙](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-ladder.md) +- [0128. 最长连续序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-consecutive-sequence.md) +- [0129. 求根节点到叶节点数字之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md) +- [0130. 被围绕的区域](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/surrounded-regions.md) +- [0131. 分割回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/palindrome-partitioning.md) +- [0133. 克隆图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/clone-graph.md) +- [0134. 加油站](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/gas-station.md) +- [0135. 分发糖果](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/candy.md) +- [0136. 只出现一次的数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number.md) +- [0137. 只出现一次的数字 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/single-number-ii.md) +- [0138. 随机链表的复制](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/copy-list-with-random-pointer.md) +- [0139. 单词拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break.md) +- [0140. 单词拆分 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/word-break-ii.md) +- [0141. 环形链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle.md) +- [0142. 环形链表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/linked-list-cycle-ii.md) +- [0143. 重排链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reorder-list.md) +- [0144. 二叉树的前序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-preorder-traversal.md) +- [0145. 二叉树的后序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-postorder-traversal.md) +- [0147. 对链表进行插入排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/insertion-sort-list.md) +- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) +- [0149. 直线上最多的点数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/max-points-on-a-line.md) +- [0150. 逆波兰表达式求值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/evaluate-reverse-polish-notation.md) +- [0151. 反转字符串中的单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-words-in-a-string.md) +- [0152. 乘积最大子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-product-subarray.md) +- [0153. 寻找旋转排序数组中的最小值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array.md) +- [0154. 寻找旋转排序数组中的最小值 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-minimum-in-rotated-sorted-array-ii.md) +- [0155. 最小栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/min-stack.md) +- [0159. 至多包含两个不同字符的最长子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md) +- [0160. 相交链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/intersection-of-two-linked-lists.md) +- [0162. 寻找峰值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/find-peak-element.md) +- [0164. 最大间距](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/maximum-gap.md) +- [0166. 分数到小数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/fraction-to-recurring-decimal.md) +- [0167. 两数之和 II - 输入有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md) +- [0168. Excel 表列名称](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/excel-sheet-column-title.md) +- [0169. 多数元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/majority-element.md) +- [0170. 两数之和 III - 数据结构设计](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/two-sum-iii-data-structure-design.md) +- [0171. Excel 表列序号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/excel-sheet-column-number.md) +- [0172. 阶乘后的零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/factorial-trailing-zeroes.md) +- [0173. 二叉搜索树迭代器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-search-tree-iterator.md) +- [0179. 最大数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/largest-number.md) +- [0188. 买卖股票的最佳时机 IV](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/best-time-to-buy-and-sell-stock-iv.md) +- [0189. 轮转数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/rotate-array.md) +- [0190. 颠倒二进制位](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/reverse-bits.md) +- [0191. 位1的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/number-of-1-bits.md) +- [0198. 打家劫舍](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/house-robber.md) +- [0199. 二叉树的右视图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/binary-tree-right-side-view.md) diff --git a/docs/solutions/0100-0199/insertion-sort-list.md b/docs/solutions/0100-0199/insertion-sort-list.md new file mode 100644 index 00000000..e02ac0f8 --- /dev/null +++ b/docs/solutions/0100-0199/insertion-sort-list.md @@ -0,0 +1,95 @@ +# [0147. 对链表进行插入排序](https://leetcode.cn/problems/insertion-sort-list/) + +- 标签:链表、排序 +- 难度:中等 + +## 题目链接 + +- [0147. 对链表进行插入排序 - 力扣](https://leetcode.cn/problems/insertion-sort-list/) + +## 题目大意 + +**描述**:给定链表的头节点 `head`。 + +**要求**:对链表进行插入排序。 + +**说明**: + +- 插入排序算法: + - 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 + - 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 + - 重复直到所有输入数据插入完为止。 +- 列表中的节点数在 $[1, 5000]$ 范围内。 +- $-5000 \le Node.val \le 5000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/04/sort1linked-list.jpg) + +```python +输入: head = [4,2,1,3] +输出: [1,2,3,4] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/04/sort2linked-list.jpg) + +```python +输入: head = [-1,5,3,4,0] +输出: [-1,0,3,4,5] +``` + +## 解题思路 + +### 思路 1:链表插入排序 + +1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 + +2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 +3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 +4. 比较 `sorted_list` 和 `cur` 的节点值。 + + - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 + - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 + +5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 +6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 + +### 思路 1:代码 + +```python + + def insertionSortList(self, head: ListNode) -> ListNode: + if not head or not head.next: + return head + + dummy_head = ListNode(-1) + dummy_head.next = head + sorted_list = head + cur = head.next + + while cur: + if sorted_list.val <= cur.val: + # 将 cur 插入到 sorted_list 之后 + sorted_list = sorted_list.next + else: + prev = dummy_head + while prev.next.val <= cur.val: + prev = prev.next + # 将 cur 到链表中间 + sorted_list.next = cur.next + cur.next = prev.next + prev.next = cur + cur = sorted_list.next + + return dummy_head.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/intersection-of-two-linked-lists.md b/docs/solutions/0100-0199/intersection-of-two-linked-lists.md new file mode 100644 index 00000000..3a0f883a --- /dev/null +++ b/docs/solutions/0100-0199/intersection-of-two-linked-lists.md @@ -0,0 +1,91 @@ +# [0160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) + +- 标签:哈希表、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [0160. 相交链表 - 力扣](https://leetcode.cn/problems/intersection-of-two-linked-lists/) + +## 题目大意 + +**描述**:给定 `listA`、`listB` 两个链表。 + +**要求**:判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 `None`。 + +**说明**: + +- `listA` 中节点数目为 $m$。 +- `listB` 中节点数目为 $n$。 +- $1 \le m, n \le 3 * 10^4$。 +- $1 \le Node.val \le 10^5$。 +- $0 \le skipA \le m$。 +- $0 \le skipB \le n$。 +- 如果 `listA` 和 `listB` 没有交点,`intersectVal` 为 $0$。 +- 如果 `listA` 和 `listB` 有交点,`intersectVal == listA[skipA] == listB[skipB]`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) + +```python +输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 +输出:Intersected at '8' +解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 +从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 +在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 +— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/05/160_example_2.png) + +```python +输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 +输出:Intersected at '2' +解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 +从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。 +在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 +``` + +## 解题思路 + +### 思路 1:双指针 + +如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 `listA` 的长度为 $m$、链表 `listB` 的长度为 $n$,他们的相交序列有 $k$ 个,则相交情况可以如下如所示: + +![](https://qcdn.itcharge.cn/images/20210401113538.png) + +现在问题是如何找到 $m - k$ 或者 $n - k$ 的位置。 + +考虑将链表 `listA` 的末尾拼接上链表 `listB`,链表 `listB` 的末尾拼接上链表 `listA`。 + +然后使用两个指针 `pA` 、`pB`,分别从链表 `listA`、链表 `listB` 的头节点开始遍历,如果走到共同的节点,则返回该节点。 + +否则走到两个链表末尾,返回 `None`。 + +![](https://qcdn.itcharge.cn/images/20210401114100.png) + +### 思路 1:代码 + +```python +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + if headA == None or headB == None: + return None + pA = headA + pB = headB + while pA != pB: + pA = pA.next if pA != None else headB + pB = pB.next if pB != None else headA + return pA +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/largest-number.md b/docs/solutions/0100-0199/largest-number.md new file mode 100644 index 00000000..81e87c46 --- /dev/null +++ b/docs/solutions/0100-0199/largest-number.md @@ -0,0 +1,67 @@ +# [0179. 最大数](https://leetcode.cn/problems/largest-number/) + +- 标签:贪心、数组、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [0179. 最大数 - 力扣](https://leetcode.cn/problems/largest-number/) + +## 题目大意 + +**描述**:给定一个非负整数数组 `nums`。 + +**要求**:重新排列数组中每个数的顺序,使之将数组中所有数字按顺序拼接起来所组成的整数最大。 + +**说明**: + +- $1 \le nums.length \le 100$。 +- $0 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [10,2] +输出:"210" +``` + +- 示例 2: + +```python +输入:nums = [3,30,34,5,9] +输出:"9534330" +``` + +## 解题思路 + +### 思路 1:排序 + +本质上是给数组进行排序。假设 `x`、`y` 是数组 `nums` 中的两个元素。如果拼接字符串 `x + y < y + x`,则 `y > x `。`y` 应该排在 `x` 前面。反之,则 `y < x`。 + +按照上述规则,对原数组进行排序即可。这里我们使用了 `functools.cmp_to_key` 自定义排序函数。 + +### 思路 1:代码 + +```python +import functools + +class Solution: + def largestNumber(self, nums: List[int]) -> str: + def cmp(a, b): + if a + b == b + a: + return 0 + elif a + b > b + a: + return 1 + else: + return -1 + nums_s = list(map(str, nums)) + nums_s.sort(key=functools.cmp_to_key(cmp), reverse=True) + return str(int(''.join(nums_s))) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。其中 $n$ 是给定数组 `nums` 的大小。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/linked-list-cycle-ii.md b/docs/solutions/0100-0199/linked-list-cycle-ii.md new file mode 100644 index 00000000..a592fa20 --- /dev/null +++ b/docs/solutions/0100-0199/linked-list-cycle-ii.md @@ -0,0 +1,82 @@ +# [0142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) + +- 标签:哈希表、链表、双指针 +- 难度:中等 + +## 题目链接 + +- [0142. 环形链表 II - 力扣](https://leetcode.cn/problems/linked-list-cycle-ii/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 `None`。 + +**说明**: + +- 链表中节点的数目范围在范围 $[0, 10^4]$ 内。 +- $-10^5 \le Node.val \le 10^5$。 +- `pos` 的值为 `-1` 或者链表中的一个有效索引。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) + +```python +输入:head = [3,2,0,-4], pos = 1 +输出:返回索引为 1 的链表节点 +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) + +```python +输入:head = [1,2], pos = 0 +输出:返回索引为 0 的链表节点 +解释:链表中有一个环,其尾部连接到第一个节点。 +``` + +## 解题思路 + +### 思路 1:快慢指针(Floyd 判圈算法) + +1. 利用两个指针,一个慢指针 `slow` 每次前进一步,快指针 `fast` 每次前进两步(两步或多步效果是等价的)。 +2. 如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环。 +3. 否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 +4. 如果有环,则再定义一个指针 `ans`,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 + +这是因为:假设入环位置为 `A`,快慢指针在 `B` 点相遇,则相遇时慢指针走了 $a + b$ 步,快指针走了 $a + n(b+c) + b$ 步。 + +因为快指针总共走的步数是慢指针走的步数的两倍,即 $2(a + b) = a + n(b + c) + b$,所以可以推出:$a = c + (n-1)(b + c)$。 + +我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 + +### 思路 1:代码 + +```python +class Solution: + def detectCycle(self, head: ListNode) -> ListNode: + fast, slow = head, head + while True: + if not fast or not fast.next: + return None + fast = fast.next.next + slow = slow.next + if fast == slow: + break + + ans = head + while ans != slow: + ans, slow = ans.next, slow.next + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0100-0199/linked-list-cycle.md b/docs/solutions/0100-0199/linked-list-cycle.md new file mode 100644 index 00000000..227bbcbc --- /dev/null +++ b/docs/solutions/0100-0199/linked-list-cycle.md @@ -0,0 +1,99 @@ +# [0141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) + +- 标签:哈希表、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [0141. 环形链表 - 力扣](https://leetcode.cn/problems/linked-list-cycle/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:判断链表中是否有环。如果有环则返回 `True`,否则返回 `False`。 + +**说明**: + +- 链表中节点的数目范围是 $[0, 10^4]$。 +- $-10^5 \le Node.val \le 10^5$。 +- `pos` 为 `-1` 或者链表中的一个有效索引。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) + +```python +输入:head = [3,2,0,-4], pos = 1 +输出:True +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) + +```python +输入:head = [1,2], pos = 0 +输出:True +解释:链表中有一个环,其尾部连接到第一个节点。 +``` + +## 解题思路 + +### 思路 1:哈希表 + +最简单的思路是遍历所有节点,每次遍历节点之前,使用哈希表判断该节点是否被访问过。如果访问过就说明存在环,如果没访问过则将该节点添加到哈希表中,继续遍历判断。 + +### 思路 1:代码 + +```python +class Solution: + def hasCycle(self, head: ListNode) -> bool: + nodeset = set() + + while head: + if head in nodeset: + return True + nodeset.add(head) + head = head.next + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:快慢指针(Floyd 判圈算法) + +这种方法类似于在操场跑道跑步。两个人从同一位置同时出发,如果跑道有环(环形跑道),那么快的一方总能追上慢的一方。 + +基于上边的想法,Floyd 用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 + +### 思路 2:代码 + +```python +class Solution: + def hasCycle(self, head: ListNode) -> bool: + if head == None or head.next == None: + return False + + slow = head + fast = head.next + + while slow != fast: + if fast == None or fast.next == None: + return False + slow = slow.next + fast = fast.next.next + + return True +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/longest-consecutive-sequence.md b/docs/solutions/0100-0199/longest-consecutive-sequence.md new file mode 100644 index 00000000..8f24d76d --- /dev/null +++ b/docs/solutions/0100-0199/longest-consecutive-sequence.md @@ -0,0 +1,81 @@ +# [0128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) + +- 标签:并查集、数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [0128. 最长连续序列 - 力扣](https://leetcode.cn/problems/longest-consecutive-sequence/) + +## 题目大意 + +**描述**:给定一个未排序的整数数组 `nums`。 + +**要求**:找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。并且要用时间复杂度为 $O(n)$ 的算法解决此问题。 + +**说明**: + +- $0 \le nums.length \le 10^5$。 +- $-10^9 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [100,4,200,1,3,2] +输出:4 +解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 +``` + +- 示例 2: + +```python +输入:nums = [0,3,7,2,5,8,4,6,0,1] +输出:9 +``` + +## 解题思路 + +暴力做法有两种思路。 + +- 第 1 种思路是先排序再依次判断,这种做法时间复杂度最少是 $O(n \log_2 n)$。 +- 第 2 种思路是枚举数组中的每个数 `num`,考虑以其为起点,不断尝试匹配 `num + 1`、`num + 2`、`...` 是否存在,最长匹配次数为 `len(nums)`。这样下来时间复杂度为 $O(n^2)$。 + +我们可以使用哈希表优化这个过程。 + +### 思路 1:哈希表 + +1. 先将数组存储到集合中进行去重,然后使用 `curr_streak` 维护当前连续序列长度,使用 `ans` 维护最长连续序列长度。 +2. 遍历集合中的元素,对每个元素进行判断,如果该元素不是序列的开始(即 `num - 1` 在集合中),则跳过。 +3. 如果 `num - 1` 不在集合中,说明 `num` 是序列的开始,判断 `num + 1` 、`nums + 2`、`...` 是否在哈希表中,并不断更新当前连续序列长度 `curr_streak`。并在遍历结束之后更新最长序列的长度。 +4. 最后输出最长序列长度。 + +### 思路 1:代码 + +```python +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + ans = 0 + nums_set = set(nums) + for num in nums_set: + if num - 1 not in nums_set: + curr_num = num + curr_streak = 1 + + while curr_num + 1 in nums_set: + curr_num += 1 + curr_streak += 1 + ans = max(ans, curr_streak) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。将数组存储到集合中进行去重的操作的时间复杂度是 $O(n)$。查询每个数是否在集合中的时间复杂度是 $O(1)$ ,并且跳过了所有不是起点的元素。更新当前连续序列长度 `curr_streak` 的时间复杂度是 $O(n)$,所以最终的时间复杂度是 $O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[128. 最长连续序列 - 力扣(Leetcode)](https://leetcode.cn/problems/longest-consecutive-sequence/solutions/1176496/xiao-bai-lang-ha-xi-ji-he-ha-xi-biao-don-j5a2/) diff --git a/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md b/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md new file mode 100644 index 00000000..8ae925fc --- /dev/null +++ b/docs/solutions/0100-0199/longest-substring-with-at-most-two-distinct-characters.md @@ -0,0 +1,50 @@ +# [0159. 至多包含两个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0159. 至多包含两个不同字符的最长子串 - 力扣](https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/) + +## 题目大意 + +给定一个字符串 s,找出之多包含两个不同字符的最长子串 t,并返回该子串的长度。 + +## 解题思路 + +使用滑动窗口来求解。 + +left,right 指向字符串开始位置。 + +不断向右移动 right 指针,使用 count 变量来统计滑动窗口中共有多少个字符,以及使用哈希表来统计当前字符的频数。 + +当滑动窗口的字符多于 2 个时,向右 移动 left 指针,并减少哈希表中对应原 left 指向字符的频数。 + +最后使用 max_count 来维护最长子串 t 的长度。 + +## 代码 + +```python +import collections +class Solution: + def lengthOfLongestSubstringTwoDistinct(self, s: str) -> int: + max_count = 0 + k = 2 + counts = collections.defaultdict(int) + count = 0 + left, right = 0, 0 + while right < len(s): + if counts[s[right]] == 0: + count += 1 + counts[s[right]] += 1 + right += 1 + if count > k: + if counts[s[left]] == 1: + count -= 1 + counts[s[left]] -= 1 + left += 1 + max_count = max(max_count, right - left) + return max_count +``` + diff --git a/docs/solutions/0100-0199/majority-element.md b/docs/solutions/0100-0199/majority-element.md new file mode 100644 index 00000000..fbc2e228 --- /dev/null +++ b/docs/solutions/0100-0199/majority-element.md @@ -0,0 +1,120 @@ +# [0169. 多数元素](https://leetcode.cn/problems/majority-element/) + +- 标签:数组、哈希表、分治、计数、排序 +- 难度:简单 + +## 题目链接 + +- [0169. 多数元素 - 力扣](https://leetcode.cn/problems/majority-element/) + +## 题目大意 + +**描述**:给定一个大小为 $n$ 的数组 $nums$。 + +**要求**:返回其中的多数元素。 + +**说明**: + +- **多数元素**:指在数组中出现次数大于 $\lfloor \frac{n}{2} \rfloor$ 的元素。 +- 假设数组是非空的,并且给定的数组总是存在多数元素。 +- $n == nums.length$。 +- $1 \le n \le 5 \times 10^4$。 +- $-10^9 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,2,3] +输出:3 +``` + +- 示例 2: + +```python +输入:nums = [2,2,1,1,1,2,2] +输出:2 +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 遍历数组 $nums$。 +2. 对于当前元素 $num$,用哈希表统计每个元素 $num$ 出现的次数。 +3. 再遍历一遍哈希表,找出元素个数最多的元素即可。 + +### 思路 1:代码 + +```python +class Solution: + def majorityElement(self, nums: List[int]) -> int: + numDict = dict() + for num in nums: + if num in numDict: + numDict[num] += 1 + else: + numDict[num] = 1 + max = float('-inf') + max_index = -1 + for num in numDict: + if numDict[num] > max: + max = numDict[num] + max_index = num + return max_index +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:分治算法 + +如果 $num$ 是数组 $nums$ 的众数,那么我们将 $nums$ 分为两部分,则 $num$ 至少是其中一部分的众数。 + +则我们可以用分治法来解决这个问题。具体步骤如下: + +1. 将数组 $nums$ 递归地将当前序列平均分成左右两个数组,直到所有子数组长度为 $1$。 +2. 长度为 $1$ 的子数组众数肯定是数组中唯一的数,将其返回即可。 +3. 将两个子数组依次向上两两合并。 + 1. 如果两个子数组的众数相同,则说明合并后的数组众数为:两个子数组的众数。 + 2. 如果两个子数组的众数不同,则需要比较两个众数在整个区间的众数。 + +4. 最后返回整个数组的众数。 + +### 思路 2:代码 + +```python +class Solution: + def majorityElement(self, nums: List[int]) -> int: + def get_mode(low, high): + if low == high: + return nums[low] + + mid = low + (high - low) // 2 + left_mod = get_mode(low, mid) + right_mod = get_mode(mid + 1, high) + + if left_mod == right_mod: + return left_mod + + left_mod_cnt, right_mod_cnt = 0, 0 + for i in range(low, high + 1): + if nums[i] == left_mod: + left_mod_cnt += 1 + if nums[i] == right_mod: + right_mod_cnt += 1 + + if left_mod_cnt > right_mod_cnt: + return left_mod + return right_mod + + return get_mode(0, len(nums) - 1) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(\log n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/max-points-on-a-line.md b/docs/solutions/0100-0199/max-points-on-a-line.md new file mode 100644 index 00000000..a850785a --- /dev/null +++ b/docs/solutions/0100-0199/max-points-on-a-line.md @@ -0,0 +1,69 @@ +# [0149. 直线上最多的点数](https://leetcode.cn/problems/max-points-on-a-line/) + +- 标签:几何、数组、哈希表、数学 +- 难度:困难 + +## 题目链接 + +- [0149. 直线上最多的点数 - 力扣](https://leetcode.cn/problems/max-points-on-a-line/) + +## 题目大意 + +给定一个平面上的 n 个点的坐标数组 points,求解最多有多少个点在同一条直线上。 + +## 解题思路 + +两个点可以确定一条直线,固定其中一个点,求其他点与该点的斜率,斜率相同的点则在同一条直线上。可以考虑把斜率当做哈希表的键值,存储经过该点,不同斜率的直线上经过的点数目。 + +对于点 i,查找经过该点的直线只需要考虑 (i+1,n-1) 位置上的点即可,因为 i-1 之前的点已经在遍历点 i-2 的时候考虑过了。 + +斜率的计算公式为 $\frac{dy}{dx} = \frac{y_j - y_i}{x_j - x_i}$。 + +因为斜率是小数会有精度误差,所以我们考虑使用 (dx, dy) 的元组作为哈希表的 key。 + +> 注意: +> +> 需要处理倍数关系,dy、dx 异号情况,以及处理垂直直线(两点横坐标差为 0)的水平直线(两点横坐标差为 0)的情况。 + +## 代码 + +```python +class Solution: + def maxPoints(self, points: List[List[int]]) -> int: + n = len(points) + if n < 3: + return n + ans = 0 + for i in range(n): + line_dict = dict() + line_dict[0] = 0 + same = 1 + for j in range(i+1, n): + dx = points[j][0] - points[i][0] + dy = points[j][1] - points[i][1] + if dx == 0 and dy == 0: + same += 1 + continue + gcd_dx_dy = math.gcd(abs(dx), abs(dy)) + if (dx > 0 and dy > 0) or (dx < 0 and dy < 0): + dx = abs(dx) // gcd_dx_dy + dy = abs(dy) // gcd_dx_dy + elif dx < 0 and dy > 0: + dx = -dx // gcd_dx_dy + dy = -dy // gcd_dx_dy + elif dx > 0 and dy < 0: + dx = dx // gcd_dx_dy + dy = dy // gcd_dx_dy + elif dx == 0 and dy != 0: + dy = 1 + elif dx != 0 and dy == 0: + dx = 1 + key = (dx, dy) + if key in line_dict: + line_dict[key] += 1 + else: + line_dict[key] = 1 + ans = max(ans, same + max(line_dict.values())) + return ans +``` + diff --git a/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md b/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md new file mode 100644 index 00000000..8041e932 --- /dev/null +++ b/docs/solutions/0100-0199/maximum-depth-of-binary-tree.md @@ -0,0 +1,65 @@ +# [0104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0104. 二叉树的最大深度 - 力扣](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:找出该二叉树的最大深度。 + +**说明**: + +- **二叉树的深度**:根节点到最远叶子节点的最长路径上的节点数。 +- **叶子节点**:没有子节点的节点。 + +**示例**: + +- 示例 1: + +```python +输入:[3,9,20,null,null,15,7] +对应二叉树 + 3 + / \ + 9 20 + / \ + 15 7 +输出:3 +解释:该二叉树的最大深度为 3 +``` + +## 解题思路 + +### 思路 1: 递归算法 + +根据递归三步走策略,写出对应的递归代码。 + +1. 写出递推公式:`当前二叉树的最大深度 = max(当前二叉树左子树的最大深度, 当前二叉树右子树的最大深度) + 1`。 + - 即:先得到左右子树的高度,在计算当前节点的高度。 +2. 明确终止条件:当前二叉树为空。 +3. 翻译为递归代码: + 1. 定义递归函数:`maxDepth(self, root)` 表示输入参数为二叉树的根节点 `root`,返回结果为该二叉树的最大深度。 + 2. 书写递归主体:`return max(self.maxDepth(root.left) + self.maxDepth(root.right))`。 + 3. 明确递归终止条件:`if not root: return 0` + +### 思路 1:代码 + +```python +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + + return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/maximum-gap.md b/docs/solutions/0100-0199/maximum-gap.md new file mode 100644 index 00000000..ad55f0d0 --- /dev/null +++ b/docs/solutions/0100-0199/maximum-gap.md @@ -0,0 +1,87 @@ +# [0164. 最大间距](https://leetcode.cn/problems/maximum-gap/) + +- 标签:数组、桶排序、基数排序、排序 +- 难度:困难 + +## 题目链接 + +- [0164. 最大间距 - 力扣](https://leetcode.cn/problems/maximum-gap/) + +## 题目大意 + +**描述**:给定一个无序数组 $nums$。 + +**要求**:找出数组在排序之后,相邻元素之间最大的差值。如果数组元素个数小于 $2$,则返回 $0$。 + +**说明**: + +- 所有元素都是非负整数,且数值在 $32$ 位有符号整数范围内。 +- 请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [3,6,9,1] +输出: 3 +解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。 +``` + +- 示例 2: + +```python +输入: nums = [10] +输出: 0 +解释: 数组元素个数小于 2,因此返回 0。 +``` + +## 解题思路 + +### 思路 1:基数排序 + +这道题的难点在于要求时间复杂度和空间复杂度为 $O(n)$。 + +这道题分为两步: + +1. 数组排序。 +2. 计算相邻元素之间的差值。 + +第 2 步直接遍历数组求解即可,时间复杂度为 $O(n)$。所以关键点在于找到一个时间复杂度和空间复杂度为 $O(n)$ 的排序算法。根据题意可知所有元素都是非负整数,且数值在 32 位有符号整数范围内。所以我们可以选择基数排序。基数排序的步骤如下: + +- 遍历数组元素,获取数组最大值元素,并取得位数。 +- 以个位元素为索引,对数组元素排序。 +- 合并数组。 +- 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,进行排序,并合并数组,最终完成排序。 + +最后,还要注意数组元素个数小于 $2$ 的情况需要特别判断一下。 + +### 思路 1:代码 + +```python +class Solution: + def radixSort(self, arr): + size = len(str(max(arr))) + + for i in range(size): + buckets = [[] for _ in range(10)] + for num in arr: + buckets[num // (10 ** i) % 10].append(num) + arr.clear() + for bucket in buckets: + for num in bucket: + arr.append(num) + + return arr + + def maximumGap(self, nums: List[int]) -> int: + if len(nums) < 2: + return 0 + arr = self.radixSort(nums) + return max(arr[i] - arr[i - 1] for i in range(1, len(arr))) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/maximum-product-subarray.md b/docs/solutions/0100-0199/maximum-product-subarray.md new file mode 100644 index 00000000..fd325db7 --- /dev/null +++ b/docs/solutions/0100-0199/maximum-product-subarray.md @@ -0,0 +1,122 @@ +# [0152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0152. 乘积最大子数组 - 力扣](https://leetcode.cn/problems/maximum-product-subarray/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`。 + +**要求**:找出数组中乘积最大的连续子数组(最少包含一个数字),并返回该子数组对应的乘积。 + +**说明**: + +- 测试用例的答案是一个 32-位整数。 +- **子数组**:数组的连续子序列。 +- $1 \le nums.length \le 2 * 10^4$。 +- $-10 \le nums[i] \le 10$。 +- `nums` 的任何前缀或后缀的乘积都保证是一个 32-位整数。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [2,3,-2,4] +输出: 6 +解释: 子数组 [2,3] 有最大乘积 6。 +``` + +- 示例 2: + +```python +输入: nums = [-2,0,-1] +输出: 0 +解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题跟「[0053. 最大子序和](https://leetcode.cn/problems/maximum-subarray/)」有点相似,不过一个求的是和的最大值,这道题求解的是乘积的最大值。 + +乘积有个特殊情况,两个正数、两个负数相乘都会得到正数。所以求解的时候需要考虑负数的情况。 + +若想要最终的乘积最大,则应该使子数组中的正数元素尽可能的大,负数元素尽可能的小。所以我们可以维护一个最大值变量和最小值变量。 + +###### 1. 划分阶段 + +按照子数组的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp_max[i]` 为:以第 $i$ 个元素结尾的乘积最大子数组的乘积。 + +定义状态 `dp_min[i]` 为:以第 $i$ 个元素结尾的乘积最小子数组的乘积。 + +###### 3. 状态转移方程 + +- `dp_max[i] = max(dp_max[i - 1] * nums[i], nums[i], dp_min[i - 1] * nums[i])` +- `dp_min[i] = min(dp_min[i - 1] * nums[i], nums[i], dp_max[i - 1] * nums[i])` + +###### 4. 初始条件 + +- 以第 $0$ 个元素结尾的乘积最大子数组的乘积为 `nums[0]`,即 `dp_max[0] = nums[0]`。 +- 以第 $0$ 个元素结尾的乘积最小子数组的乘积为 `nums[0]`,即 `dp_min[0] = nums[0]`。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp_{max}$ 中最大值,即乘积最大子数组的乘积。 + +### 思路 1:代码 + +```python +class Solution: + def maxProduct(self, nums: List[int]) -> int: + size = len(nums) + dp_max = [0 for _ in range(size)] + dp_min = [0 for _ in range(size)] + dp_max[0] = nums[0] + dp_min[0] = nums[0] + ans = nums[0] + for i in range(1, size): + dp_max[i] = max(dp_max[i - 1] * nums[i], nums[i], dp_min[i - 1] * nums[i]) + dp_min[i] = min(dp_min[i - 1] * nums[i], nums[i], dp_max[i - 1] * nums[i]) + return max(dp_max) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为整数数组 `nums` 的元素个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:动态规划 + 滚动优化 + +因为状态转移方程中只涉及到当前元素和前一个元素,所以我们也可以不使用数组,只使用两个变量来维护 $dp_{max}[i]$ 和 $dp_{min}[i]$。 + +### 思路 2:代码 + +```python +class Solution: + def maxProduct(self, nums: List[int]) -> int: + size = len(nums) + max_num, min_num = nums[0], nums[0] + ans = nums[0] + for i in range(1, size): + temp_max = max_num + temp_min = min_num + max_num = max(temp_max * nums[i], nums[i], temp_min * nums[i]) + min_num = min(temp_min * nums[i], nums[i], temp_max * nums[i]) + ans = max(max_num, ans) + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为整数数组 `nums` 的元素个数。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0100-0199/min-stack.md b/docs/solutions/0100-0199/min-stack.md new file mode 100644 index 00000000..2eeaf841 --- /dev/null +++ b/docs/solutions/0100-0199/min-stack.md @@ -0,0 +1,137 @@ +# [0155. 最小栈](https://leetcode.cn/problems/min-stack/) + +- 标签:栈、设计 +- 难度:中等 + +## 题目链接 + +- [0155. 最小栈 - 力扣](https://leetcode.cn/problems/min-stack/) + +## 题目大意 + +**要求**:设计一个「栈」。实现 `push` ,`pop` ,`top` ,`getMin` 操作,其中 `getMin` 要求能在常数时间内实现。 + +**说明**: + +- $-2^{31} \le val \le 2^{31} - 1$。 +- `pop`、`top` 和 `getMin` 操作总是在非空栈上调用 +- `push`,`pop`,`top` 和 `getMin` 最多被调用 $3 * 10^4$ 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["MinStack","push","push","push","getMin","pop","top","getMin"] +[[],[-2],[0],[-3],[],[],[],[]] + +输出: +[null,null,null,null,-3,null,0,-2] + +解释: +MinStack minStack = new MinStack(); +minStack.push(-2); +minStack.push(0); +minStack.push(-3); +minStack.getMin(); --> 返回 -3. +minStack.pop(); +minStack.top(); --> 返回 0. +minStack.getMin(); --> 返回 -2. +``` + +## 解题思路 + +题目要求在常数时间内获取最小值,所以我们不能在 `getMin` 操作时,再去计算栈中的最小值。而是应该在 `push`、`pop` 操作时就已经计算好了最小值。我们有两种思路来解决这道题。 + +### 思路 1:辅助栈 + +使用辅助栈保存当前栈中的最小值。在元素入栈出栈时,两个栈同步保持插入和删除。具体做法如下: + +- `push` 操作:当一个元素入栈时,取辅助栈的栈顶存储的最小值,与当前元素进行比较得出最小值,将最小值插入到辅助栈中;该元素也插入到正常栈中。 +- `pop` 操作:当一个元素要出栈时,将辅助栈的栈顶元素一起弹出。 +- `top` 操作:返回正常栈的栈顶元素值。 +- `getMin` 操作:返回辅助栈的栈顶元素值。 + +### 思路 1:代码 + +```python +class MinStack: + + def __init__(self): + self.stack = [] + self.minstack = [] + + def push(self, val: int) -> None: + if not self.stack: + self.stack.append(val) + self.minstack.append(val) + else: + self.stack.append(val) + self.minstack.append(min(val, self.minstack[-1])) + + def pop(self) -> None: + self.stack.pop() + self.minstack.pop() + + def top(self) -> int: + return self.stack[-1] + + def getMin(self) -> int: + return self.minstack[-1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。栈的插入、删除、读取操作都是 $O(1)$。 +- **空间复杂度**:$O(n)$。其中 $n$ 为总操作数。 + +### 思路 2:单个栈 + +使用单个栈,保存元组:(当前元素值,当前栈内最小值)。具体操作如下: + +- `push` 操作:如果栈不为空,则判断当前元素值与栈顶元素所保存的最小值,并更新当前最小值,然后将新元素和当前最小值组成的元组保存到栈中。 +- `pop`操作:正常出栈,即将栈顶元素弹出。 +- `top` 操作:返回栈顶元素保存的值。 +- `getMin` 操作:返回栈顶元素保存的最小值。 + +### 思路 2:代码 + +```python +class MinStack: + def __init__(self): + """ + initialize your data structure here. + """ + self.stack = [] + + class Node: + def __init__(self, x): + self.val = x + self.min = x + + def push(self, val: int) -> None: + node = self.Node(val) + if len(self.stack) == 0: + self.stack.append(node) + else: + topNode = self.stack[-1] + if node.min > topNode.min: + node.min = topNode.min + + self.stack.append(node) + + def pop(self) -> None: + self.stack.pop() + + def top(self) -> int: + return self.stack[-1].val + + def getMin(self) -> int: + return self.stack[-1].min +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(1)$。栈的插入、删除、读取操作都是 $O(1)$。 +- **空间复杂度**:$O(n)$。其中 $n$ 为总操作数。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md b/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md new file mode 100644 index 00000000..bee2e4cd --- /dev/null +++ b/docs/solutions/0100-0199/minimum-depth-of-binary-tree.md @@ -0,0 +1,80 @@ +# [0111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索 +- 难度:简单 + +## 题目链接 + +- [0111. 二叉树的最小深度 - 力扣](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:找出该二叉树的最小深度。 + +**说明**: + +- **最小深度**:从根节点到最近叶子节点的最短路径上的节点数量。 +- **叶子节点**:指没有子节点的节点。 +- 树中节点数的范围在 $[0, 10^5]$ 内。 +- $-1000 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/12/ex_depth.jpg) + +```python +输入:root = [3,9,20,null,null,15,7] +输出:2 +``` + +- 示例 2: + +```python +输入:root = [2,null,3,null,4,null,5,null,6] +输出:5 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +深度优先搜索递归遍历左右子树,记录最小深度。 + +对于每一个非叶子节点,计算其左右子树的最小叶子节点深度,将较小的深度+1 即为当前节点的最小叶子节点深度。 + +### 思路 1:代码 + +```python +class Solution: + def minDepth(self, root: TreeNode) -> int: + # 遍历到空节点,直接返回 0 + if root == None: + return 0 + + # 左右子树为空,说明为叶子节点 返回 1 + if root.left == None and root.right == None: + return 1 + + leftHeight = self.minDepth(root.left) + rightHeight = self.minDepth(root.right) + + # 当前节点的左右子树的最小叶子节点深度 + min_depth = 0xffffff + if root.left: + min_depth = min(leftHeight, min_depth) + if root.right: + min_depth = min(rightHeight, min_depth) + + # 当前节点的最小叶子节点深度 + return min_depth + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是树中的节点数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/number-of-1-bits.md b/docs/solutions/0100-0199/number-of-1-bits.md new file mode 100644 index 00000000..3b367155 --- /dev/null +++ b/docs/solutions/0100-0199/number-of-1-bits.md @@ -0,0 +1,85 @@ +# [0191. 位1的个数](https://leetcode.cn/problems/number-of-1-bits/) + +- 标签:位运算、分治 +- 难度:简单 + +## 题目链接 + +- [0191. 位1的个数 - 力扣](https://leetcode.cn/problems/number-of-1-bits/) + +## 题目大意 + +**描述**:给定一个无符号整数 $n$。 + +**要求**:统计其对应二进制表达式中 $1$ 的个数。 + +**说明**: + +- 输入必须是长度为 $32$ 的二进制串。 + +**示例**: + +- 示例 1: + +```python +输入:n = 00000000000000000000000000001011 +输出:3 +解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 +``` + +- 示例 2: + +```python +输入:n = 00000000000000000000000010000000 +输出:1 +解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 +``` + +## 解题思路 + +### 思路 1:循环按位计算 + +1. 对整数 $n$ 的每一位进行按位与运算,并统计结果。 + +### 思路 1:代码 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + ans = 0 + while n: + ans += (n & 1) + n = n >> 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(k)$,其中 $k$ 是二进位的位数,$k = 32$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:改进位运算 + +利用 `n & (n - 1)`。这个运算刚好可以将 $n$ 的二进制中最低位的 $1$ 变为 $0$。 + +比如 $n = 6$ 时,$6 = 110_{(2)}$,$6 - 1 = 101_{(2)}$,`110 & 101 = 100`。 + +利用这个位运算,不断的将 $n$ 中最低位的 $1$ 变为 $0$,直到 $n$ 变为 $0$ 即可,其变换次数就是我们要求的结果。 + +### 思路 2:代码 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + ans = 0 + while n: + n = n & (n - 1) + ans += 1 + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/palindrome-partitioning.md b/docs/solutions/0100-0199/palindrome-partitioning.md new file mode 100644 index 00000000..ee2268a6 --- /dev/null +++ b/docs/solutions/0100-0199/palindrome-partitioning.md @@ -0,0 +1,60 @@ +# [0131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) + +- 标签:字符串、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [0131. 分割回文串 - 力扣](https://leetcode.cn/problems/palindrome-partitioning/) + +## 题目大意 + +给定一个字符串 `s`,将 `s` 分割成一些子串,保证每个子串都是「回文串」。返回 `s` 所有可能的分割方案。 + +## 解题思路 + +回溯算法,建立两个数组 res、path。res 用于存放所有满足题意的组合,path 用于存放当前满足题意的一个组合。 + +在回溯的时候判断当前子串是否为回文串,如果不是则跳过,如果是则继续向下一层遍历。 + +定义判断是否为回文串的方法和回溯方法,从 `start_index = 0` 的位置开始回溯。 + +- 如果 `start_index >= len(s)`,则将 path 中的元素加入到 res 数组中。 +- 然后对 `[start_index, len(s) - 1]` 范围内的子串进行遍历取值。 + - 如果字符串 `s` 在范围 `[start_index, i]` 所代表的子串是回文串,则将其加入 path 数组。 + - 递归遍历 `[i + 1, len(s) - 1]` 范围上的子串。 + - 然后将遍历的范围 `[start_index, i]` 所代表的子串进行回退。 +- 最终返回 res 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, s: str, start_index: int): + if start_index >= len(s): + self.res.append(self.path[:]) + return + for i in range(start_index, len(s)): + if self.ispalindrome(s, start_index, i): + self.path.append(s[start_index: i+1]) + self.backtrack(s, i + 1) + self.path.pop() + + def ispalindrome(self, s: str, start: int, end: int): + i, j = start, end + while i < j: + if s[i] != s[j]: + return False + i += 1 + j -= 1 + return True + + def partition(self, s: str) -> List[List[str]]: + self.res.clear() + self.path.clear() + self.backtrack(s, 0) + return self.res +``` + diff --git a/docs/solutions/0100-0199/pascals-triangle-ii.md b/docs/solutions/0100-0199/pascals-triangle-ii.md new file mode 100644 index 00000000..c37b67a2 --- /dev/null +++ b/docs/solutions/0100-0199/pascals-triangle-ii.md @@ -0,0 +1,118 @@ +# [0119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) + +- 标签:数组、动态规划 +- 难度:简单 + +## 题目链接 + +- [0119. 杨辉三角 II - 力扣](https://leetcode.cn/problems/pascals-triangle-ii/) + +## 题目大意 + +**描述**:给定一个非负整数 $rowIndex$。 + +**要求**:返回杨辉三角的第 $rowIndex$ 行。 + +**说明**: + +- $0 \le rowIndex \le 33$。 +- 要求使用 $O(k)$ 的空间复杂度。 + +**示例**: + +- 示例 1: + +```python +输入:rowIndex = 3 +输出:[1,3,3,1] +``` + +## 解题思路 + +### 思路 1:动态规划 + +因为这道题是从 $0$ 行开始计算,则可以先将 $rowIndex$ 加 $1$,计算出总共的行数,即 $numRows = rowIndex + 1$。 + +###### 1. 划分阶段 + +按照行数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:杨辉三角第 $i$ 行、第 $j$ 列位置上的值。 + +###### 3. 状态转移方程 + +根据观察,很容易得出状态转移方程为:$dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]$,此时 $i > 0$,$j > 0$。 + +###### 4. 初始条件 + +- 每一行第一列都为 $1$,即 $dp[i][0] = 1$。 +- 每一行最后一列都为 $1$,即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +根据题意和状态定义,将 $dp$ 最后一行返回。 + +### 思路 1:代码 + +```python +class Solution: + def getRow(self, rowIndex: int) -> List[int]: + # 本题从 0 行开始计算 + numRows = rowIndex + 1 + + dp = [[0] * i for i in range(1, numRows + 1)] + + for i in range(numRows): + dp[i][0] = 1 + dp[i][i] = 1 + + for i in range(numRows): + for j in range(i): + if i != 0 and j != 0: + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] + + return dp[-1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。初始条件赋值的时间复杂度为 $O(n)$,两重循环遍历的时间复杂度为 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 + +### 思路 2:动态规划 + 滚动数组优化 + +因为 $dp[i][j]$ 仅依赖于上一行(第 $i - 1$ 行)的 $dp[i - 1][j - 1]$ 和 $dp[i - 1][j]$,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 + +其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 + +定义 $dp[j]$ 为杨辉三角第 $i$ 行第 $j$ 列位置上的值。则第 $i + 1$ 行、第 $j$ 列的值可以通过 $dp[j]$ + $dp[j - 1]$ 所得到。 + +这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 + +需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 $dp[j]$ 已经更新为当前阶段第 $j$ 列位置的状态值之后,右侧 $dp[j + 1]$ 想要更新的话,需要的是上一阶段的状态值 $dp[j]$,而此时 $dp[j]$ 已经更新了,会破坏当前阶段的状态值。而是用从左向左的顺序,则不会出现该问题。 + +### 思路 2:动态规划 + 滚动数组优化代码 + +```python +class Solution: + def getRow(self, rowIndex: int) -> List[int]: + # 本题从 0 行开始计算 + numRows = rowIndex + 1 + + dp = [1 for _ in range(numRows)] + + for i in range(numRows): + for j in range(i - 1, -1, -1): + if i != 0 and j != 0: + dp[j] = dp[j - 1] + dp[j] + + return dp +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n)$。不考虑最终返回值的空间占用,则总的空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/pascals-triangle.md b/docs/solutions/0100-0199/pascals-triangle.md new file mode 100644 index 00000000..ffa95931 --- /dev/null +++ b/docs/solutions/0100-0199/pascals-triangle.md @@ -0,0 +1,128 @@ +# [0118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) + +- 标签:数组、动态规划 +- 难度:简单 + +## 题目链接 + +- [0118. 杨辉三角 - 力扣](https://leetcode.cn/problems/pascals-triangle/) + +## 题目大意 + +**描述**:给定一个整数 $numRows$。 + +**要求**:生成前 $numRows$ 行的杨辉三角。 + +**说明**: + +- $1 \le numRows \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入:numRows = 5 +输出:[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] +即 +[ + [1], + [1,1], + [1,2,1], + [1,3,3,1], + [1,4,6,4,1] +] +``` + +- 示例 2: + +```python +输入: numRows = 1 +输出: [[1]] +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照行数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:杨辉三角第 $i$ 行、第 $j$ 列位置上的值。 + +###### 3. 状态转移方程 + +根据观察,很容易得出状态转移方程为:$dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]$,此时 $i > 0$,$j > 0$。 + +###### 4. 初始条件 + +- 每一行第一列都为 $1$,即 $dp[i][0] = 1$。 +- 每一行最后一列都为 $1$,即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +根据题意和状态定义,我们将每行结果存入答案数组中,将其返回。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def generate(self, numRows: int) -> List[List[int]]: + dp = [[0] * i for i in range(1, numRows + 1)] + + for i in range(numRows): + dp[i][0] = 1 + dp[i][i] = 1 + + res = [] + for i in range(numRows): + for j in range(i): + if i != 0 and j != 0: + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] + res.append(dp[i]) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。初始条件赋值的时间复杂度为 $O(n)$,两重循环遍历的时间复杂度为 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 + +### 思路 2:动态规划 + 滚动数组优化 + +因为 $dp[i][j]$ 仅依赖于上一行(第 $i - 1$ 行)的 $dp[i - 1][j - 1]$ 和 $dp[i - 1][j]$,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 + +其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 + +定义 $dp[j]$ 为杨辉三角第 $i$ 行第 $j$ 列位置上的值。则第 $i + 1$ 行、第 $j$ 列的值可以通过 $dp[j]$ + $dp[j - 1]$ 所得到。 + +这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 + +需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 $dp[j]$ 已经更新为当前阶段第 $j$ 列位置的状态值之后,右侧 $dp[j + 1]$ 想要更新的话,需要的是上一阶段的状态值 $dp[j]$,而此时 $dp[j]$ 已经更新了,会破坏当前阶段的状态值。而如果用从右向左的顺序,则不会出现该问题。 + +### 思路 2:动态规划 + 滚动数组优化代码 + +```python +class Solution: + def generate(self, numRows: int) -> List[List[int]]: + dp = [1 for _ in range(numRows + 1)] + + res = [] + + for i in range(numRows): + for j in range(i - 1, -1, -1): + if i != 0 and j != 0: + dp[j] = dp[j - 1] + dp[j] + res.append(dp[:i + 1]) + + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n)$。不考虑最终返回值的空间占用,则总的空间复杂度为 $O(n)$。 diff --git a/docs/solutions/0100-0199/path-sum-ii.md b/docs/solutions/0100-0199/path-sum-ii.md new file mode 100644 index 00000000..0254b76f --- /dev/null +++ b/docs/solutions/0100-0199/path-sum-ii.md @@ -0,0 +1,87 @@ +# [0113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/) + +- 标签:树、深度优先搜索、回溯、二叉树 +- 难度:中等 + +## 题目链接 + +- [0113. 路径总和 II - 力扣](https://leetcode.cn/problems/path-sum-ii/) + +## 题目大意 + +**描述**:给定一棵二叉树的根节点 `root` 和一个整数目标 `targetSum`。 + +**要求**:找出「所有从根节点到叶子节点路径总和」等于给定目标和 `targetSum` 的路径。 + +**说明**: + +- **叶子节点**:指没有子节点的节点。 +- 树中节点总数在范围 $[0, 5000]$ 内。 +- $-1000 \le Node.val \le 1000$。 +- $-1000 \le targetSum \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/01/18/pathsumii1.jpg) + +```python +输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 +输出:[[5,4,11,2],[5,8,4,5]] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/01/18/pathsum2.jpg) + +```python +输入:root = [1,2,3], targetSum = 5 +输出:[] +``` + +## 解题思路 + +### 思路 1:回溯 + +在回溯的同时,记录下当前路径。同时维护 `targetSum`,每遍历到一个节点,就减去该节点值。如果遇到叶子节点,并且 `targetSum == 0` 时,将当前路径加入答案数组中。然后递归遍历左右子树,并回退当前节点,继续遍历。 + +具体步骤如下: + +1. 使用列表 `res` 存储所有路径,使用列表 `path` 存储当前路径。 +2. 如果根节点为空,则直接返回。 +3. 将当前节点值添加到当前路径 `path` 中。 +4. `targetSum` 减去当前节点值。 +5. 如果遇到叶子节点,并且 `targetSum == 0` 时,将当前路径加入答案数组中。 +6. 递归遍历左子树。 +7. 递归遍历右子树。 +8. 回退当前节点,继续递归遍历。 + +### 思路 1:代码 + +```python +class Solution: + def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]: + res = [] + path = [] + + def dfs(root: TreeNode, targetSum: int): + if not root: + return + path.append(root.val) + targetSum -= root.val + if not root.left and not root.right and targetSum == 0: + res.append(path[:]) + dfs(root.left, targetSum) + dfs(root.right, targetSum) + path.pop() + + dfs(root, targetSum) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/path-sum.md b/docs/solutions/0100-0199/path-sum.md new file mode 100644 index 00000000..d8a6c5e7 --- /dev/null +++ b/docs/solutions/0100-0199/path-sum.md @@ -0,0 +1,78 @@ +# [0112. 路径总和](https://leetcode.cn/problems/path-sum/) + +- 标签:树、深度优先搜索 +- 难度:简单 + +## 题目链接 + +- [0112. 路径总和 - 力扣](https://leetcode.cn/problems/path-sum/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root` 和一个值 `targetSum`。 + +**要求**:判断该树中是否存在从根节点到叶子节点的路径,使得这条路径上所有节点值相加等于 `targetSum`。如果存在,返回 `True`;否则,返回 `False`。 + +**说明**: + +- 树中节点的数目在范围 $[0, 5000]$ 内。 +- $-1000 \le Node.val \le 1000$。 +- $-1000 \le targetSum \le 1000$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2021/01/18/pathsum1.jpg) + +```python +输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 +输出:true +解释:等于目标和的根节点到叶节点路径如上图所示。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/01/18/pathsum2.jpg) + +```python +输入:root = [1,2,3], targetSum = 5 +输出:false +解释:树中存在两条根节点到叶子节点的路径: +(1 --> 2): 和为 3 +(1 --> 3): 和为 4 +不存在 sum = 5 的根节点到叶子节点的路径。 +``` + +## 解题思路 + +### 思路 1:递归遍历 + +1. 定义一个递归函数,递归函数传入当前根节点 `root`,目标节点和 `targetSum`,以及新增变量 `currSum`(表示为从根节点到当前节点的路径上所有节点值之和)。 +2. 递归遍历左右子树,同时更新维护 `currSum` 值。 +3. 如果当前节点为叶子节点时,判断 `currSum` 是否与 `targetSum` 相等。 + 1. 如果 `currSum` 与 `targetSum` 相等,则返回 `True`。 + 2. 如果 `currSum` 不与 `targetSum` 相等,则返回 `False`。 +4. 如果当前节点不为叶子节点,则继续递归遍历左右子树。 + +### 思路 1:代码 + +```python +class Solution: + def hasPathSum(self, root: TreeNode, targetSum: int) -> bool: + return self.sum(root, targetSum, 0) + + def sum(self, root: TreeNode, targetSum: int, curSum:int) -> bool: + if root == None: + return False + curSum += root.val + if root.left == None and root.right == None: + return curSum == targetSum + else: + return self.sum(root.left, targetSum, curSum) or self.sum(root.right, targetSum, curSum) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md b/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md new file mode 100644 index 00000000..617c94dc --- /dev/null +++ b/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node-ii.md @@ -0,0 +1,88 @@ +# [0117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) + +- 标签:树、深度优先搜索、广度优先搜索、链表、二叉树 +- 难度:中等 + +## 题目链接 + +- [0117. 填充每个节点的下一个右侧节点指针 II - 力扣](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) + +## 题目大意 + +**描述**:给定一个二叉树。二叉树结构如下: + +```python +struct Node { + int val; + Node *left; + Node *right; + Node *next; +} +``` + +**要求**:填充每个 `next` 指针,使得这个指针指向下一个右侧节点。如果找不到下一个右侧节点,则将 `next` 置为 `None`。 + +**说明**: + +- 初始状态下,所有 next 指针都被设置为 `None`。 +- 树中节点的数量在 $[0, 6000]$ 范围内。 +- $-100 \le Node.val \le 100$。 +- 进阶: + - 只能使用常量级额外空间。 + - 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2019/02/15/117_sample.png) + +```python +输入:root = [1,2,3,4,5,null,7] +输出:[1,#,2,3,#,4,5,7,#] +解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。 +``` + +- 示例 2: + +```python +输入:root = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:层次遍历 + +在层次遍历的过程中,依次取出每一层的节点,并进行连接。然后再扩展下一层节点。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def connect(self, root: 'Node') -> 'Node': + if not root: + return root + queue = collections.deque() + queue.append(root) + while queue: + size = len(queue) + for i in range(size): + node = queue.popleft() + if i < size - 1: + node.next = queue[0] + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return root +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中的节点数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md b/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md new file mode 100644 index 00000000..e25bf145 --- /dev/null +++ b/docs/solutions/0100-0199/populating-next-right-pointers-in-each-node.md @@ -0,0 +1,89 @@ +# [0116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) + +- 标签:树、深度优先搜索、广度优先搜索、链表、二叉树 +- 难度:中等 + +## 题目链接 + +- [0116. 填充每个节点的下一个右侧节点指针 - 力扣](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) + +## 题目大意 + +**描述**:给定一个完美二叉树,所有叶子节点都在同一层,每个父节点都有两个子节点。完美二叉树结构如下: + +```python +struct Node { + int val; + Node *left; + Node *right; + Node *next; +} +``` + +**要求**:填充每个 `next` 指针,使得这个指针指向下一个右侧节点。如果找不到下一个右侧节点,则将 `next` 置为 `None`。 + +**说明**: + +- 初始状态下,所有 next 指针都被设置为 `None`。 +- 树中节点的数量在 $[0, 2^{12} - 1]$ 范围内。 +- $-1000 \le node.val \le 1000$。 +- 进阶: + - 只能使用常量级额外空间。 + - 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。 + + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2019/02/14/116_sample.png) + +```python +输入:root = [1,2,3,4,5,6,7] +输出:[1,#,2,3,#,4,5,6,7,#] +解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。 +``` + +- 示例 2: + +```python +输入:root = [] +输出:[] +``` + +## 解题思路 + +### 思路 1:层次遍历 + +在层次遍历的过程中,依次取出每一层的节点,并进行连接。然后再扩展下一层节点。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def connect(self, root: 'Node') -> 'Node': + if not root: + return root + queue = collections.deque() + queue.append(root) + while queue: + size = len(queue) + for i in range(size): + node = queue.popleft() + if i < size - 1: + node.next = queue[0] + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return root +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中的节点数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/reorder-list.md b/docs/solutions/0100-0199/reorder-list.md new file mode 100644 index 00000000..3240f331 --- /dev/null +++ b/docs/solutions/0100-0199/reorder-list.md @@ -0,0 +1,78 @@ +# [0143. 重排链表](https://leetcode.cn/problems/reorder-list/) + +- 标签:栈、递归、链表、双指针 +- 难度:中等 + +## 题目链接 + +- [0143. 重排链表 - 力扣](https://leetcode.cn/problems/reorder-list/) + +## 题目大意 + +**描述**:给定一个单链表 $L$ 的头节点 $head$,单链表 $L$ 表示为:$L_0 \rightarrow L_1 \rightarrow L_2 \rightarrow ... \rightarrow L_{n-1} \rightarrow L_n$。 + +**要求**:将单链表 $L$ 重新排列为:$L_0 \rightarrow L_n \rightarrow L_1 \rightarrow L_{n-1} \rightarrow L_2 \rightarrow L_{n-2} \rightarrow L_3 \rightarrow L_{n-3} \rightarrow ...$。 + +**说明**: + +- 需要将实际节点进行交换。 + +**示例**: + +- 示例 1: + +![](https://pic.leetcode-cn.com/1626420311-PkUiGI-image.png) + +```python +输入:head = [1,2,3,4] +输出:[1,4,2,3] +``` + +- 示例 2: + +![](https://pic.leetcode-cn.com/1626420320-YUiulT-image.png) + +```python +输入:head = [1,2,3,4,5] +输出:[1,5,2,4,3] +``` + +## 解题思路 + +### 思路 1:线性表 + +因为链表无法像数组那样直接进行随机访问。所以我们可以先将链表转为线性表,然后直接按照提要要求的排列顺序访问对应数据元素,重新建立链表。 + +### 思路 1:代码 + +```python +class Solution: + def reorderList(self, head: ListNode) -> None: + """ + Do not return anything, modify head in-place instead. + """ + if not head: + return + + vec = [] + node = head + while node: + vec.append(node) + node = node.next + + left, right = 0, len(vec) - 1 + while left < right: + vec[left].next = vec[right] + left += 1 + if left == right: + break + vec[right].next = vec[left] + right -= 1 + vec[left].next = None +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/reverse-bits.md b/docs/solutions/0100-0199/reverse-bits.md new file mode 100644 index 00000000..41657640 --- /dev/null +++ b/docs/solutions/0100-0199/reverse-bits.md @@ -0,0 +1,63 @@ +# [0190. 颠倒二进制位](https://leetcode.cn/problems/reverse-bits/) + +- 标签:位运算、分治 +- 难度:简单 + +## 题目链接 + +- [0190. 颠倒二进制位 - 力扣](https://leetcode.cn/problems/reverse-bits/) + +## 题目大意 + +**描述**:给定一个 $32$ 位无符号整数 $n$。 + +**要求**:将 $n$ 所有二进位进行翻转,并返回翻转后的整数。 + +**说明**: + +- 输入是一个长度为 $32$ 的二进制字符串。 + +**示例**: + +- 示例 1: + +```python +输入:n = 00000010100101000001111010011100 +输出:964176192 (00111001011110000010100101000000) +解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, + 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 +``` + +- 示例 2: + +```python +输入:n = 11111111111111111111111111111101 +输出:3221225471 (10111111111111111111111111111111) +解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293, + 因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111。 +``` + +## 解题思路 + +### 思路 1:逐位翻转 + +1. 用一个变量 $res$ 存储翻转后的结果。 +2. 将 $n$ 不断进行右移(即 `n >> 1`),从低位到高位进行枚举,此时 $n$ 的最低位就是我们枚举的二进位。 +3. 同时 $res$ 不断左移(即 `res << 1`),并将当前枚举的二进位翻转后的结果(即 `n & 1`)拼接到 $res$ 的末尾(即 `(res << 1) | (n & 1)`)。 + +### 思路 1:代码 + +```python +class Solution: + def reverseBits(self, n: int) -> int: + res = 0 + for i in range(32): + res = (res << 1) | (n & 1) + n >>= 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/reverse-words-in-a-string.md b/docs/solutions/0100-0199/reverse-words-in-a-string.md new file mode 100644 index 00000000..cc5cce8a --- /dev/null +++ b/docs/solutions/0100-0199/reverse-words-in-a-string.md @@ -0,0 +1,103 @@ +# [0151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) + +- 标签:双指针、字符串 +- 难度:中等 + +## 题目链接 + +- [0151. 反转字符串中的单词 - 力扣](https://leetcode.cn/problems/reverse-words-in-a-string/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:反转字符串中所有单词的顺序。 + +**说明**: + +- **单词**:由非空格字符组成的字符串。`s` 中使用至少一个空格将字符串中的单词分隔开。 +- 输入字符串 `s`中可能会存在前导空格、尾随空格或者单词间的多个空格。 +- 返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 +- $1 \le s.length \le 10^4$。 +- `s` 包含英文大小写字母、数字和空格 `' '` +- `s` 中至少存在一个单词。 + +**示例**: + +- 示例 1: + +```python +输入:s = " hello world " +输出:"world hello" +解释:反转后的字符串中不能存在前导空格和尾随空格。 +``` + +- 示例 2: + +```python +输入:s = "a good example" +输出:"example good a" +解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。 +``` + +## 解题思路 + +### 思路 1:调用库函数 + +直接调用 Python 的库函数,对字符串进行切片,翻转,然后拼合成字符串。 + +### 思路 1:代码 + +```python +class Solution: + def reverseWords(self, s: str) -> str: + return " ".join(reversed(s.split())) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:模拟 + +第二种思路根据 API 的思路写出模拟代码,具体步骤如下: + +- 使用数组 `words` 存放单词,使用字符串变量 `cur` 存放当前单词。 +- 遍历字符串,对于当前字符 `ch`。 +- 如果遇到空格,则: + - 如果当前单词不为空,则将当前单词存入数组 `words` 中,并将当前单词置为空串 +- 如果遇到字符,则: + - 将其存入当前单词中,即 `cur += ch`。 +- 如果遍历完,当前单词不为空,则将当前单词存入数组 `words` 中。 +- 然后对数组 `words` 进行翻转操作,令 `words[i]`, `words[len(words) - 1 - i]` 交换元素。 +- 最后将 `words` 中的单词连接起来,中间拼接上空格,将其作为答案返回。 + +### 思路 2:代码 + +```python +class Solution: + def reverseWords(self, s: str) -> str: + words = [] + cur = "" + for ch in s: + if ch == ' ': + if cur: + words.append(cur) + cur = "" + else: + cur += ch + + if cur: + words.append(cur) + + for i in range(len(words) // 2): + words[i], words[len(words) - 1 - i] = words[len(words) - 1 - i], words[i] + + return " ".join(words) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是字符串 `s` 的长度。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/rotate-array.md b/docs/solutions/0100-0199/rotate-array.md new file mode 100644 index 00000000..bcdab031 --- /dev/null +++ b/docs/solutions/0100-0199/rotate-array.md @@ -0,0 +1,82 @@ +# [0189. 轮转数组](https://leetcode.cn/problems/rotate-array/) + +- 标签:数组、数学、双指针 +- 难度:中等 + +## 题目链接 + +- [0189. 轮转数组 - 力扣](https://leetcode.cn/problems/rotate-array/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,再给定一个数字 $k$。 + +**要求**:将数组中的元素向右移动 $k$ 个位置。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- $0 \le k \le 10^5$。 +- 使用空间复杂度为 $O(1)$ 的原地算法解决这个问题。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [1,2,3,4,5,6,7], k = 3 +输出: [5,6,7,1,2,3,4] +解释: +向右轮转 1 步: [7,1,2,3,4,5,6] +向右轮转 2 步: [6,7,1,2,3,4,5] +向右轮转 3 步: [5,6,7,1,2,3,4] +``` + +- 示例 2: + +```py +输入:nums = [-1,-100,3,99], k = 2 +输出:[3,99,-1,-100] +解释: +向右轮转 1 步: [99,-1,-100,3] +向右轮转 2 步: [3,99,-1,-100] +``` + +## 解题思路 + +### 思路 1: 数组翻转 + +可以用一个新数组,先保存原数组的后 $k$ 个元素,再保存原数组的前 $n - k$ 个元素。但题目要求不使用额外的数组空间,那么就需要在原数组上做操作。 + +我们可以先把整个数组翻转一下,这样后半段元素就到了前边,前半段元素就到了后边,只不过元素顺序是反着的。我们再从 $k$ 位置分隔开,将 $[0...k - 1]$ 区间上的元素和 $[k...n - 1]$ 区间上的元素再翻转一下,就得到了最终结果。 + +具体步骤: + +1. 将数组 $[0, n - 1]$ 位置上的元素全部翻转。 +2. 将数组 $[0, k - 1]$ 位置上的元素进行翻转。 +3. 将数组 $[k, n - 1]$ 位置上的元素进行翻转。 + +### 思路 1:代码 + +```python +class Solution: + def rotate(self, nums: List[int], k: int) -> None: + n = len(nums) + k = k % n + self.reverse(nums, 0, n-1) + self.reverse(nums, 0, k-1) + self.reverse(nums, k, n-1) + def reverse(self, nums: List[int], left: int, right: int) -> None: + while left < right : + tmp = nums[left] + nums[left] = nums[right] + nums[right] = tmp + left += 1 + right -= 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。翻转的时间复杂度为 $O(n)$ 。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/same-tree.md b/docs/solutions/0100-0199/same-tree.md new file mode 100644 index 00000000..d3978e41 --- /dev/null +++ b/docs/solutions/0100-0199/same-tree.md @@ -0,0 +1,66 @@ +# [0100. 相同的树](https://leetcode.cn/problems/same-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0100. 相同的树 - 力扣](https://leetcode.cn/problems/same-tree/) + +## 题目大意 + +**描述**:给定两个二叉树的根节点 $p$ 和 $q$。 + +**要求**:判断这两棵树是否相同。 + +**说明**: + +- **两棵树相同的定义**:结构上相同;节点具有相同的值。 +- 两棵树上的节点数目都在范围 $[0, 100]$ 内。 +- $-10^4 \le Node.val \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/12/20/ex1.jpg) + +```python +输入:p = [1,2,3], q = [1,2,3] +输出:True +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/12/20/ex2.jpg) + +```python +输入:p = [1,2], q = [1,null,2] +输出:False +``` + +## 解题思路 + +### 思路 1:递归 + +1. 先判断两棵树的根节点是否相同。 +2. 然后再递归地判断左右子树是否相同。 + +### 思路 1:代码 + +```python +class Solution: + def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: + if not p and not q: + return True + if not p or not q: + return False + if p.val != q.val: + return False + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(min(m, n))$,其中 $m$、$n$ 分别为两棵树中的节点数量。 +- **空间复杂度**:$O(min(m, n))$。 diff --git a/docs/solutions/0100-0199/single-number-ii.md b/docs/solutions/0100-0199/single-number-ii.md new file mode 100644 index 00000000..f15493c7 --- /dev/null +++ b/docs/solutions/0100-0199/single-number-ii.md @@ -0,0 +1,98 @@ +# [0137. 只出现一次的数字 II](https://leetcode.cn/problems/single-number-ii/) + +- 标签:位运算、数组 +- 难度:中等 + +## 题目链接 + +- [0137. 只出现一次的数字 II - 力扣](https://leetcode.cn/problems/single-number-ii/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$,除了某个元素仅出现一次外,其余每个元素恰好出现三次。 + +**要求**:找到并返回那个只出现了一次的元素。 + +**说明**: + +- $1 \le nums.length \le 3 * 10^4$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- $nums$ 中,除某个元素仅出现一次外,其余每个元素都恰出现三次。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,2,3,2] +输出:3 +``` + +- 示例 2: + +```python +输入:nums = [0,1,0,1,0,1,99] +输出:99 +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 利用哈希表统计出每个元素的出现次数。 +2. 再遍历一次哈希表,找到仅出现一次的元素。 + +### 思路 1:代码 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + nums_dict = dict() + for num in nums: + if num in nums_dict: + nums_dict[num] += 1 + else: + nums_dict[num] = 1 + for key in nums_dict: + value = nums_dict[key] + if value == 1: + return key + return 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:位运算 + +将出现三次的元素换成二进制形式放在一起,其二进制对应位置上,出现 $1$ 的个数一定是 $3$ 的倍数(包括 $0$)。此时,如果在放进来只出现一次的元素,则某些二进制位置上出现 $1$ 的个数就不是 $3$ 的倍数了。 + +将这些二进制位置上出现 $1$ 的个数不是 $3$ 的倍数位置值置为 $1$,是 $3$ 的倍数则置为 $0$。这样对应下来的二进制就是答案所求。 + +注意:因为 Python 的整数没有位数限制,所以不能通过最高位确定正负。所以 Python 中负整数的补码会被当做正整数。所以在遍历到最后 $31$ 位时进行 $ans -= (1 << 31)$ 操作,目的是将负数的补码转换为「负号 + 原码」的形式。这样就可以正常识别二进制下的负数。参考:[Two's Complement Binary in Python? - Stack Overflow](https://stackoverflow.com/questions/12946116/twos-complement-binary-in-python/12946226) + +### 思路 2:代码 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + ans = 0 + for i in range(32): + count = 0 + for j in range(len(nums)): + count += (nums[j] >> i) & 1 + if count % 3 != 0: + if i == 31: + ans -= (1 << 31) + else: + ans = ans | 1 << i + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \log m)$,其中 $n$ 是数组 $nums$ 的长度,$m$ 是数据范围,本题中 $m = 32$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0100-0199/single-number.md b/docs/solutions/0100-0199/single-number.md new file mode 100644 index 00000000..a758c6fe --- /dev/null +++ b/docs/solutions/0100-0199/single-number.md @@ -0,0 +1,68 @@ +# [0136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) + +- 标签:位运算、数组 +- 难度:简单 + +## 题目链接 + +- [0136. 只出现一次的数字 - 力扣](https://leetcode.cn/problems/single-number/) + +## 题目大意 + +**描述**:给定一个非空整数数组 `nums`,`nums` 中除了某个元素只出现一次以外,其余每个元素均出现两次。 + +**要求**:找出那个只出现了一次的元素。 + +**说明**: + +- 要求不能使用额外的存储空间。 + +**示例**: + +- 示例 1: + +```python +输入: [2,2,1] +输出: 1 +``` + +- 示例 2: + +```python +输入: [4,1,2,1,2] +输出: 4 +``` + +## 解题思路 + +### 思路 1:位运算 + +如果没有时间复杂度和空间复杂度的限制,可以使用哈希表 / 集合来存储每个元素出现的次数,如果哈希表中没有该数字,则将该数字加入集合,如果集合中有了该数字,则从集合中删除该数字,最终成对的数字都被删除了,只剩下单次出现的元素。 + +但是题目要求不使用额外的存储空间,就需要用到位运算中的异或运算。 + +> 异或运算 $\oplus$ 的三个性质: +> +> 1. 任何数和 $0$ 做异或运算,结果仍然是原来的数,即 $a \oplus 0 = a$。 +> 2. 数和其自身做异或运算,结果是 $0$,即 $a \oplus a = 0$。 +> 3. 异或运算满足交换率和结合律:$a \oplus b \oplus a = b \oplus a \oplus a = b \oplus (a \oplus a) = b \oplus 0 = b$。 + +根据异或运算的性质,对 $n$ 个数不断进行异或操作,最终可得到单次出现的元素。 + +### 思路 1:代码 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + if len(nums) == 1: + return nums[0] + ans = 0 + for i in range(len(nums)): + ans ^= nums[i] + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/sort-list.md b/docs/solutions/0100-0199/sort-list.md new file mode 100644 index 00000000..33520d9b --- /dev/null +++ b/docs/solutions/0100-0199/sort-list.md @@ -0,0 +1,523 @@ +# [0148. 排序链表](https://leetcode.cn/problems/sort-list/) + +- 标签:链表、双指针、分治、排序、归并排序 +- 难度:中等 + +## 题目链接 + +- [0148. 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/) + +## 题目大意 + +**描述**:给定链表的头节点 `head`。 + +**要求**:按照升序排列并返回排序后的链表。 + +**说明**: + +- 链表中节点的数目在范围 $[0, 5 * 10^4]$ 内。 +- $-10^5 \le Node.val \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/09/14/sort_list_1.jpg) + +```python +输入:head = [4,2,1,3] +输出:[1,2,3,4] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/09/14/sort_list_2.jpg) + +```python +输入:head = [-1,5,3,4,0] +输出:[-1,0,3,4,5] +``` + +## 解题思路 + +### 思路 1:链表冒泡排序(超时) + +1. 使用三个指针 `node_i`、`node_j` 和 `tail`。其中 `node_i` 用于控制外循环次数,循环次数为链节点个数(链表长度)。`node_j` 和 `tail` 用于控制内循环次数和循环结束位置。 + +2. 排序开始前,将 `node_i` 、`node_j` 置于头节点位置。`tail` 指向链表末尾,即 `None`。 + +3. 比较链表中相邻两个元素 `node_j.val` 与 `node_j.next.val` 的值大小,如果 `node_j.val > node_j.next.val`,则值相互交换。否则不发生交换。然后向右移动 `node_j` 指针,直到 `node_j.next == tail` 时停止。 + +4. 一次循环之后,将 `tail` 移动到 `node_j` 所在位置。相当于 `tail` 向左移动了一位。此时 `tail` 节点右侧为链表中最大的链节点。 + +5. 然后移动 `node_i` 节点,并将 `node_j` 置于头节点位置。然后重复第 3、4 步操作。 +6. 直到 `node_i` 节点移动到链表末尾停止,排序结束。 +7. 返回链表的头节点 `head`。 + +### 思路 1:代码 + +```python +class Solution: + def bubbleSort(self, head: ListNode): + node_i = head + tail = None + # 外层循环次数为 链表节点个数 + while node_i: + node_j = head + while node_j and node_j.next != tail: + if node_j.val > node_j.next.val: + # 交换两个节点的值 + node_j.val, node_j.next.val = node_j.next.val, node_j.val + node_j = node_j.next + # 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表 + tail = node_j + node_i = node_i.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.bubbleSort(head) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:链表选择排序(超时) + +1. 使用两个指针 `node_i`、`node_j`。`node_i` 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。 +2. 使用 `min_node` 记录当前未排序链表中值最小的链节点。 +3. 每一趟排序开始时,先令 `min_node = node_i`(即暂时假设链表中 `node_i` 节点为值最小的节点,经过比较后再确定最小值节点位置)。 +4. 然后依次比较未排序链表中 `node_j.val` 与 `min_node.val` 的值大小。如果 `node_j.val < min_node.val`,则更新 `min_node` 为 `node_j`。 +5. 这一趟排序结束时,未排序链表中最小值节点为 `min_node`,如果 `node_i != min_node`,则将 `node_i` 与 `min_node` 值进行交换。如果 `node_i == min_node`,则不用交换。 +6. 排序结束后,继续向右移动 `node_i`,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 `node_i` 进行比较和交换,直到 `node_i == None` 或者 `node_i.next == None` 时,停止排序。 +7. 返回链表的头节点 `head`。 + +### 思路 2:代码 + +```python +class Solution: + def sectionSort(self, head: ListNode): + node_i = head + # node_i 为当前未排序链表的第一个链节点 + while node_i and node_i.next: + # min_node 为未排序链表中的值最小节点 + min_node = node_i + node_j = node_i.next + while node_j: + if node_j.val < min_node.val: + min_node = node_j + node_j = node_j.next + # 交换值最小节点与未排序链表中第一个节点的值 + if node_i != min_node: + node_i.val, min_node.val = min_node.val, node_i.val + node_i = node_i.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.sectionSort(head) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 3:链表插入排序(超时) + +1. 先使用哑节点 `dummy_head` 构造一个指向 `head` 的指针,使得可以从 `head` 开始遍历。 +2. 维护 `sorted_list` 为链表的已排序部分的最后一个节点,初始时,`sorted_list = head`。 +3. 维护 `prev` 为插入元素位置的前一个节点,维护 `cur` 为待插入元素。初始时,`prev = head`,`cur = head.next`。 +4. 比较 `sorted_list` 和 `cur` 的节点值。 + + - 如果 `sorted_list.val <= cur.val`,说明 `cur` 应该插入到 `sorted_list` 之后,则将 `sorted_list` 后移一位。 + - 如果 `sorted_list.val > cur.val`,说明 `cur` 应该插入到 `head` 与 `sorted_list` 之间。则使用 `prev` 从 `head` 开始遍历,直到找到插入 `cur` 的位置的前一个节点位置。然后将 `cur` 插入。 + +5. 令 `cur = sorted_list.next`,此时 `cur` 为下一个待插入元素。 +6. 重复 4、5 步骤,直到 `cur` 遍历结束为空。返回 `dummy_head` 的下一个节点。 + +### 思路 3:代码 + +```python +class Solution: + def insertionSort(self, head: ListNode): + if not head or not head.next: + return head + + dummy_head = ListNode(-1) + dummy_head.next = head + sorted_list = head + cur = head.next + + while cur: + if sorted_list.val <= cur.val: + # 将 cur 插入到 sorted_list 之后 + sorted_list = sorted_list.next + else: + prev = dummy_head + while prev.next.val <= cur.val: + prev = prev.next + # 将 cur 到链表中间 + sorted_list.next = cur.next + cur.next = prev.next + prev.next = cur + cur = sorted_list.next + + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.insertionSort(head) +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 4:链表归并排序(通过) + +1. **分割环节**:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。 + 1. 使用快慢指针 `fast = head.next`、`slow = head`,让 `fast` 每次移动 `2` 步,`slow` 移动 `1` 步,移动到链表末尾,从而找到链表中心链节点,即 `slow`。 + 2. 从中心位置将链表从中心位置分为左右两个链表 `left_head` 和 `right_head`,并从中心位置将其断开,即 `slow.next = None`。 + 3. 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。 +2. **归并环节**:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。 + 1. 使用哑节点 `dummy_head` 构造一个头节点,并使用 `cur` 指向 `dummy_head` 用于遍历。 + 2. 比较两个链表头节点 `left` 和 `right` 的值大小。将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 + 3. 然后重复上一步操作,直到两个链表中出现链表为空的情况。 + 4. 将剩余链表插入到合并中的链表中。 + 5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为合并后的头节点返回。 + +### 思路 4:代码 + +```python +class Solution: + def merge(self, left, right): + # 归并环节 + dummy_head = ListNode(-1) + cur = dummy_head + while left and right: + if left.val <= right.val: + cur.next = left + left = left.next + else: + cur.next = right + right = right.next + cur = cur.next + + if left: + cur.next = left + elif right: + cur.next = right + + return dummy_head.next + + def mergeSort(self, head: ListNode): + # 分割环节 + if not head or not head.next: + return head + + # 快慢指针找到中心链节点 + slow, fast = head, head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # 断开左右链节点 + left_head, right_head = head, slow.next + slow.next = None + + # 归并操作 + return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.mergeSort(head) +``` + +### 思路 4:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 5:链表快速排序(超时) + +1. 从链表中找到一个基准值 `pivot`,这里以头节点为基准值。 +2. 然后通过快慢指针 `node_i`、`node_j` 在链表中移动,使得 `node_i` 之前的节点值都小于基准值,`node_i` 之后的节点值都大于基准值。从而把数组拆分为左右两个部分。 +3. 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。 + +> 注意: +> +> 虽然链表快速排序算法的平均时间复杂度为 $O(n \times \log_2n)$。但链表快速排序算法中基准值 `pivot` 的取值做不到数组快速排序算法中的随机选择。一旦给定序列是有序链表,时间复杂度就会退化到 $O(n^2)$。这也是这道题目使用链表快速排序容易超时的原因。 + +### 思路 5:代码 + +```python +class Solution: + def partition(self, left: ListNode, right: ListNode): + # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点 + if left == right or left.next == right: + return left + # 选择头节点为基准节点 + pivot = left.val + # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值 + node_i, node_j = left, left.next + + while node_j != right: + # 发现一个小与基准值的元素 + if node_j.val < pivot: + # 因为 node_i 之前节点都小于基准值,所以先将 node_i 向右移动一位(此时 node_i 节点值大于等于基准节点值) + node_i = node_i.next + # 将小于基准值的元素 node_j 与当前 node_i 换位,换位后可以保证 node_i 之前的节点都小于基准节点值 + node_i.val, node_j.val = node_j.val, node_i.val + node_j = node_j.next + # 将基准节点放到正确位置上 + node_i.val, left.val = left.val, node_i.val + return node_i + + def quickSort(self, left: ListNode, right: ListNode): + if left == right or left.next == right: + return left + pi = self.partition(left, right) + self.quickSort(left, pi) + self.quickSort(pi.next, right) + return left + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + if not head or not head.next: + return head + return self.quickSort(head, None) +``` + +### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 6:链表计数排序(通过) + +1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 +2. 使用数组 `counts` 存储节点出现次数。 +3. 再次使用 `cur` 指针遍历一遍链表。将链表中每个值为 `cur.val` 的节点出现次数,存入数组对应第 `cur.val - list_min` 项中。 +4. 反向填充目标链表: + 1. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 + 2. 从小到大遍历一遍数组 `counts`。对于每个 `counts[i] != 0` 的元素建立一个链节点,值为 `i + list_min`,将其插入到 `cur.next` 上。并向右移动 `cur`。同时 `counts[i] -= 1`。直到 `counts[i] == 0` 后继续向后遍历数组 `counts`。 +5. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 + +### 思路 6:代码 + +```python +class Solution: + def countingSort(self, head: ListNode): + if not head: + return head + + # 找出链表中最大值 list_max 和最小值 list_min + list_min, list_max = float('inf'), float('-inf') + cur = head + while cur: + if cur.val < list_min: + list_min = cur.val + if cur.val > list_max: + list_max = cur.val + cur = cur.next + + size = list_max - list_min + 1 + counts = [0 for _ in range(size)] + + cur = head + while cur: + counts[cur.val - list_min] += 1 + cur = cur.next + + dummy_head = ListNode(-1) + cur = dummy_head + for i in range(size): + while counts[i]: + cur.next = ListNode(i + list_min) + counts[i] -= 1 + cur = cur.next + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.countingSort(head) +``` + +### 思路 6:复杂度分析 + +- **时间复杂度**:$O(n + k)$,其中 $k$ 代表待排序链表中所有元素的值域。 +- **空间复杂度**:$O(k)$。 + +### 思路 7:链表桶排序(通过) + +1. 使用 `cur` 指针遍历一遍链表。找出链表中最大值 `list_max` 和最小值 `list_min`。 +2. 通过 `(最大值 - 最小值) / 每个桶的大小` 计算出桶的个数,即 `bucket_count = (list_max - list_min) // bucket_size + 1` 个桶。 +3. 定义数组 `buckets` 为桶,桶的个数为 `bucket_count` 个。 +4. 使用 `cur` 指针再次遍历一遍链表,将每个元素装入对应的桶中。 +5. 对每个桶内的元素单独排序,可以使用链表插入排序(超时)、链表归并排序(通过)、链表快速排序(超时)等算法。 +6. 最后按照顺序将桶内的元素拼成新的链表,并返回。 + +### 思路 7:代码 + +```python +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + # 将链表节点值 val 添加到对应桶 buckets[index] 中 + def insertion(self, buckets, index, val): + if not buckets[index]: + buckets[index] = ListNode(val) + return + + node = ListNode(val) + node.next = buckets[index] + buckets[index] = node + + # 归并环节 + def merge(self, left, right): + dummy_head = ListNode(-1) + cur = dummy_head + while left and right: + if left.val <= right.val: + cur.next = left + left = left.next + else: + cur.next = right + right = right.next + cur = cur.next + + if left: + cur.next = left + elif right: + cur.next = right + + return dummy_head.next + + def mergeSort(self, head: ListNode): + # 分割环节 + if not head or not head.next: + return head + + # 快慢指针找到中心链节点 + slow, fast = head, head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # 断开左右链节点 + left_head, right_head = head, slow.next + slow.next = None + + # 归并操作 + return self.merge(self.mergeSort(left_head), self.mergeSort(right_head)) + + def bucketSort(self, head: ListNode, bucket_size=5): + if not head: + return head + + # 找出链表中最大值 list_max 和最小值 list_min + list_min, list_max = float('inf'), float('-inf') + cur = head + while cur: + if cur.val < list_min: + list_min = cur.val + if cur.val > list_max: + list_max = cur.val + cur = cur.next + + # 计算桶的个数,并定义桶 + bucket_count = (list_max - list_min) // bucket_size + 1 + buckets = [[] for _ in range(bucket_count)] + + # 将链表节点值依次添加到对应桶中 + cur = head + while cur: + index = (cur.val - list_min) // bucket_size + self.insertion(buckets, index, cur.val) + cur = cur.next + + dummy_head = ListNode(-1) + cur = dummy_head + # 将元素依次出桶,并拼接成有序链表 + for bucket_head in buckets: + bucket_cur = self.mergeSort(bucket_head) + while bucket_cur: + cur.next = bucket_cur + cur = cur.next + bucket_cur = bucket_cur.next + + return dummy_head.next + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.bucketSort(head) +``` + +### 思路 7:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 + +### 思路 8:链表基数排序(解答错误,普通链表基数排序只适合非负数) + +1. 使用 `cur` 指针遍历链表,获取节点值位数最长的位数 `size`。 +2. 从个位到高位遍历位数。因为 `0` ~ `9` 共有 `10` 位数字,所以建立 `10` 个桶。 +3. 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。 +4. 建立一个哑节点 `dummy_head`,作为链表的头节点。使用 `cur` 指针指向 `dummy_head`。 +5. 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。 +6. 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。 +7. 将哑节点 `dummy_dead` 的下一个链节点 `dummy_head.next` 作为新链表的头节点返回。 + +### 思路 8:代码 + +```python +class Solution: + def radixSort(self, head: ListNode): + # 计算位数最长的位数 + size = 0 + cur = head + while cur: + val_len = len(str(cur.val)) + if val_len > size: + size = val_len + cur = cur.next + + # 从个位到高位遍历位数 + for i in range(size): + buckets = [[] for _ in range(10)] + cur = head + while cur: + # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中 + buckets[cur.val // (10 ** i) % 10].append(cur.val) + cur = cur.next + + # 生成新的链表 + dummy_head = ListNode(-1) + cur = dummy_head + for bucket in buckets: + for num in bucket: + cur.next = ListNode(num) + cur = cur.next + head = dummy_head.next + + return head + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + return self.radixSort(head) +``` + +### 思路 8:复杂度分析 + +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 + +## 参考资料 + +- 【文章】[单链表的冒泡排序_zhao_miao的博客 - CSDN博客](https://blog.csdn.net/zhao_miao/article/details/81708454) +- 【文章】[链表排序总结(全)(C++)- 阿祭儿 - CSDN博客](https://blog.csdn.net/qq_32523711/article/details/107402873) +- 【题解】[快排、冒泡、选择排序实现列表排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/kuai-pai-mou-pao-xuan-ze-pai-xu-shi-xian-ula7/) +- 【题解】[归并排序+快速排序 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/gui-bing-pai-xu-kuai-su-pai-xu-by-datacruiser/) +- 【题解】[排序链表(递归+迭代)详解 - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/pai-xu-lian-biao-di-gui-die-dai-xiang-jie-by-cherr/) +- 【题解】[Sort List (归并排序链表) - 排序链表 - 力扣](https://leetcode.cn/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) diff --git a/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md b/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md new file mode 100644 index 00000000..8f2e745f --- /dev/null +++ b/docs/solutions/0100-0199/sum-root-to-leaf-numbers.md @@ -0,0 +1,82 @@ +# [0129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0129. 求根节点到叶节点数字之和 - 力扣](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`,树中每个节点都存放有一个 `0` 到 `9` 之间的数字。每条从根节点到叶节点的路径都代表一个数字。例如,从根节点到叶节点的路径是 `1` -> `2` -> `3`,表示数字 `123`。 + +**要求**:计算从根节点到叶节点生成的所有数字的和。 + +**说明**: + +- **叶节点**:指没有子节点的节点。 +- 树中节点的数目在范围 $[1, 1000]$ 内。 +- $0 \le Node.val \le 9$。 +- 树的深度不超过 $10$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/num1tree.jpg) + +```python +输入:root = [1,2,3] +输出:25 +解释: +从根到叶子节点路径 1->2 代表数字 12 +从根到叶子节点路径 1->3 代表数字 13 +因此,数字总和 = 12 + 13 = 25 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/19/num2tree.jpg) + +```python +输入:root = [4,9,0,5,1] +输出:1026 +解释: +从根到叶子节点路径 4->9->5 代表数字 495 +从根到叶子节点路径 4->9->1 代表数字 491 +从根到叶子节点路径 4->0 代表数字 40 +因此,数字总和 = 495 + 491 + 40 = 1026 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +1. 记录下路径上所有节点构成的数字,使用变量 `pre_total` 保存下当前路径上构成的数字。 +2. 如果遇到叶节点,则直接返回当前数字。 +3. 如果没有遇到叶节点,则递归遍历左右子树,并累加对应结果。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, root, pre_total): + if not root: + return 0 + total = pre_total * 10 + root.val + if not root.left and not root.right: + return total + return self.dfs(root.left, total) + self.dfs(root.right, total) + + def sumNumbers(self, root: Optional[TreeNode]) -> int: + return self.dfs(root, 0) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + + + diff --git a/docs/solutions/0100-0199/surrounded-regions.md b/docs/solutions/0100-0199/surrounded-regions.md new file mode 100644 index 00000000..1521a584 --- /dev/null +++ b/docs/solutions/0100-0199/surrounded-regions.md @@ -0,0 +1,92 @@ +# [0130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0130. 被围绕的区域 - 力扣](https://leetcode.cn/problems/surrounded-regions/) + +## 题目大意 + +**描述**:给定一个 `m * n` 的矩阵 `board`,由若干字符 `X` 和 `O` 构成。 + +**要求**:找到所有被 `X` 围绕的区域,并将这些区域里所有的 `O` 用 `X` 填充。 + +**说明**: + +- $m == board.length$。 +- $n == board[i].length$。 +- $1 <= m, n <= 200$。 +- $board[i][j]$ 为 `'X'` 或 `'O'`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/xogrid.jpg) + +```python +输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] +输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] +解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 +``` + +- 示例 2: + +```python +输入:board = [["X"]] +输出:[["X"]] +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +根据题意,任何边界上的 `O` 都不会被填充为`X`。而被填充 `X` 的 `O` 一定在内部不在边界上。 + +所以我们可以用深度优先搜索先搜索边界上的 `O` 以及与边界相连的 `O`,将其先标记为 `#`。 + +最后遍历一遍 `board`,将所有 `#` 变换为 `O`,将所有 `O` 变换为 `X`。 + +### 思路 1:代码 + +```python +class Solution: + def solve(self, board: List[List[str]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + if not board: + return + rows, cols = len(board), len(board[0]) + + def dfs(x, y): + if not 0 <= x < rows or not 0 <= y < cols or board[x][y] != 'O': + return + board[x][y] = '#' + dfs(x + 1, y) + dfs(x - 1, y) + dfs(x, y + 1) + dfs(x, y - 1) + + for i in range(rows): + dfs(i, 0) + dfs(i, cols - 1) + + for j in range(cols - 1): + dfs(0, j) + dfs(rows - 1, j) + + for i in range(rows): + for j in range(cols): + if board[i][j] == '#': + board[i][j] = 'O' + elif board[i][j] == 'O': + board[i][j] = 'X' +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(n \times m)$。 \ No newline at end of file diff --git a/docs/solutions/0100-0199/symmetric-tree.md b/docs/solutions/0100-0199/symmetric-tree.md new file mode 100644 index 00000000..d249c173 --- /dev/null +++ b/docs/solutions/0100-0199/symmetric-tree.md @@ -0,0 +1,88 @@ +# [0101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0101. 对称二叉树 - 力扣](https://leetcode.cn/problems/symmetric-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:判断该二叉树是否是左右对称的。 + +**说明**: + +- 树中节点数目在范围 $[1, 1000]$ 内。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/19/symtree1.jpg) + +```python +输入:root = [1,2,2,3,4,4,3] +输出:true +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/19/symtree2.jpg) + +```python +输入:root = [1,2,2,null,3,null,3] +输出:false +``` + +## 解题思路 + +### 思路 1:递归遍历 + +如果一棵二叉树是对称的,那么其左子树和右子树的外侧节点的节点值应当是相等的,并且其左子树和右子树的内侧节点的节点值也应当是相等的。 + +那么我们可以通过递归方式,检查其左子树与右子树外侧节点和内测节点是否相等。即递归检查左子树的左子节点值与右子树的右子节点值是否相等(外侧节点值是否相等),递归检查左子树的右子节点值与右子树的左子节点值是否相等(内测节点值是否相等)。 + +具体步骤如下: + +1. 如果当前根节点为 `None`,则直接返回 `True`。 +2. 如果当前根节点不为 `None`,则调用 `check(left, right)` 方法递归检查其左右子树是否对称。 + 1. 如果左子树节点为 `None`,并且右子树节点也为 `None`,则直接返回 `True`。 + 2. 如果左子树节点为 `None`,并且右子树节点不为 `None`,则直接返回 `False`。 + 3. 如果左子树节点不为 `None`,并且右子树节点为 `None`,则直接返回 `False`。 + 4. 如果左子树节点值不等于右子树节点值,则直接返回 `False`。 + 5. 如果左子树节点不为 `None`,并且右子树节点不为 `None`,并且左子树节点值等于右子树节点值,则: + 1. 递归检测左右子树的外侧节点是否相等。 + 2. 递归检测左右子树的内测节点是否相等。 + 3. 如果左右子树的外侧节点、内测节点值相等,则返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: + if root == None: + return True + return self.check(root.left, root.right) + + def check(self, left: TreeNode, right: TreeNode): + if left == None and right == None: + return True + elif left == None and right != None: + return False + elif left != None and right == None: + return False + elif left.val != right.val: + return False + + return self.check(left.left, right.right) and self.check(left.right, right.left) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0100-0199/triangle.md b/docs/solutions/0100-0199/triangle.md new file mode 100644 index 00000000..28e57394 --- /dev/null +++ b/docs/solutions/0100-0199/triangle.md @@ -0,0 +1,94 @@ +# [0120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0120. 三角形最小路径和 - 力扣](https://leetcode.cn/problems/triangle/) + +## 题目大意 + +**描述**:给定一个代表三角形的二维数组 $triangle$,$triangle$ 共有 $n$ 行,其中第 $i$ 行(从 $0$ 开始编号)包含了 $i + 1$ 个数。 + +我们每一步只能从当前位置移动到下一行中相邻的节点上。也就是说,如果正位于第 $i$ 行第 $j$ 列的节点,那么下一步可以移动到第 $i + 1$ 行第 $j$ 列的位置上,或者第 $i + 1$ 行,第 $j + 1$ 列的位置上。 + +**要求**:找出自顶向下的最小路径和。 + +**说明**: + +- $1 \le triangle.length \le 200$。 +- $triangle[0].length == 1$。 +- $triangle[i].length == triangle[i - 1].length + 1$。 +- $-10^4 \le triangle[i][j] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] +输出:11 +解释:如下面简图所示: + 2 + 3 4 + 6 5 7 +4 1 8 3 +自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 +``` + +- 示例 2: + +```python +输入:triangle = [[-10]] +输出:-10 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照行数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:从顶部走到第 $i$ 行(从 $0$ 开始编号)、第 $j$ 列的位置时的最小路径和。 + +###### 3. 状态转移方程 + +由于每一步只能从当前位置移动到下一行中相邻的节点上,想要移动到第 $i$ 行、第 $j$ 列的位置,那么上一步只能在第 $i - 1$ 行、第 $j - 1$ 列的位置上,或者在第 $i - 1$ 行、第 $j$ 列的位置上。则状态转移方程为: + +$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]$。其中 $triangle[i][j]$ 表示第 $i$ 行、第 $j$ 列位置上的元素值。 + +###### 4. 初始条件 + + 在第 $0$ 行、第 $j$ 列时,最小路径和为 $triangle[0][0]$,即 $dp[0][0] = triangle[0][0]$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:从顶部走到第 $i$ 行(从 $0$ 开始编号)、第 $j$ 列的位置时的最小路径和。为了计算出最小路径和,则需要再遍历一遍 $dp[size - 1]$ 行的每一列,求出最小值即为最终结果。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def minimumTotal(self, triangle: List[List[int]]) -> int: + size = len(triangle) + dp = [[0 for _ in range(size)] for _ in range(size)] + dp[0][0] = triangle[0][0] + + for i in range(1, size): + dp[i][0] = dp[i - 1][0] + triangle[i][0] + for j in range(1, i): + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j] + dp[i][i] = dp[i - 1][i - 1] + triangle[i][i] + + return min(dp[size - 1]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最小值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 diff --git a/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md b/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md new file mode 100644 index 00000000..6a793753 --- /dev/null +++ b/docs/solutions/0100-0199/two-sum-ii-input-array-is-sorted.md @@ -0,0 +1,130 @@ +# [0167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) + +- 标签:数组、双指针、二分查找 +- 难度:中等 + +## 题目链接 + +- [0167. 两数之和 II - 输入有序数组 - 力扣](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) + +## 题目大意 + +**描述**:给定一个下标从 $1$ 开始计数、升序排列的整数数组:$numbers$ 和一个目标值 $target$。 + +**要求**:从数组中找出满足相加之和等于 $target$ 的两个数,并返回两个数在数组中下的标值。 + +**说明**: + +- $2 \le numbers.length \le 3 \times 10^4$。 +- $-1000 \le numbers[i] \le 1000$。 +- $numbers$ 按非递减顺序排列。 +- $-1000 \le target \le 1000$。 +- 仅存在一个有效答案。 + +**示例**: + +- 示例 1: + +```python +输入:numbers = [2,7,11,15], target = 9 +输出:[1,2] +解释:2 与 7 之和等于目标数 9。因此 index1 = 1, index2 = 2。返回 [1, 2]。 +``` + +- 示例 2: + +```python +输入:numbers = [2,3,4], target = 6 +输出:[1,3] +解释:2 与 4 之和等于目标数 6。因此 index1 = 1, index2 = 3。返回 [1, 3]。 +``` + +## 解题思路 + +这道题如果暴力遍历数组,从中找到相加之和等于 $target$ 的两个数,时间复杂度为 $O(n^2)$,可以尝试一下。 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + size = len(numbers) + for i in range(size): + for j in range(i + 1, size): + if numbers[i] + numbers[j] == target: + return [i + 1, j + 1] + return [-1, -1] +``` + +结果不出意外的超时了。所以我们要想办法降低时间复杂度。 + +### 思路 1:二分查找 + +因为数组是有序的,可以考虑使用二分查找来减少时间复杂度。具体做法如下: + +1. 使用一重循环遍历数组,先固定第一个数,即 $numsbers[i]$。 +2. 然后使用二分查找的方法寻找符合要求的第二个数。 +3. 使用两个指针 $left$,$right$。$left$ 指向数组第一个数的下一个数,$right$ 指向数组值最大元素位置。 +4. 判断第一个数 $numsbers[i]$ 和两个指针中间元素 $numbers[mid]$ 的和与目标值的关系。 + 1. 如果 $numbers[mid] + numbers[i] < target$,排除掉不可能区间 $[left, mid]$,在 $[mid + 1, right]$ 中继续搜索。 + 2. 如果 $numbers[mid] + numbers[i] \ge target$,则第二个数可能在 $[left, mid]$ 中,则在 $[left, mid]$ 中继续搜索。 +5. 直到 $left$ 和 $right$ 移动到相同位置停止检测。如果 $numbers[left] + numbers[i] == target$,则返回两个元素位置 $[left + 1, i + 1]$(下标从 $1$ 开始计数)。 +6. 如果最终仍没找到,则返回 $[-1, -1]$。 + +### 思路 1:代码 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + for i in range(len(numbers)): + left, right = i + 1, len(numbers) - 1 + while left < right: + mid = left + (right - left) // 2 + if numbers[mid] + numbers[i] < target: + left = mid + 1 + else: + right = mid + if numbers[left] + numbers[i] == target: + return [i + 1, left + 1] + + return [-1, -1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:对撞指针 + +可以考虑使用对撞指针来减少时间复杂度。具体做法如下: + +1. 使用两个指针 $left$,$right$。$left$ 指向数组第一个值最小的元素位置,$right$ 指向数组值最大元素位置。 +2. 判断两个位置上的元素的和与目标值的关系。 + 1. 如果元素和等于目标值,则返回两个元素位置。 + 2. 如果元素和大于目标值,则让 $right$ 左移,继续检测。 + 3. 如果元素和小于目标值,则让 $left$ 右移,继续检测。 +3. 直到 $left$ 和 $right$ 移动到相同位置停止检测。 +4. 如果最终仍没找到,则返回 $[-1, -1]$。 + +### 思路 2:代码 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + left = 0 + right = len(numbers) - 1 + while left < right: + total = numbers[left] + numbers[right] + if total == target: + return [left + 1, right + 1] + elif total < target: + left += 1 + else: + right -= 1 + return [-1, -1] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0100-0199/two-sum-iii-data-structure-design.md b/docs/solutions/0100-0199/two-sum-iii-data-structure-design.md new file mode 100644 index 00000000..6964c88e --- /dev/null +++ b/docs/solutions/0100-0199/two-sum-iii-data-structure-design.md @@ -0,0 +1,63 @@ +# [0170. 两数之和 III - 数据结构设计](https://leetcode.cn/problems/two-sum-iii-data-structure-design/) + +- 标签:设计、数组、哈希表、双指针、数据流 +- 难度:简单 + +## 题目链接 + +- [0170. 两数之和 III - 数据结构设计 - 力扣](https://leetcode.cn/problems/two-sum-iii-data-structure-design/) + +## 题目大意 + +设计一个接受整数流的数据结构,使该数据结构支持检查是否存在两数之和等于特定值。 + +实现 TwoSum 类: + +- `TwoSum()`:使用空数组初始化 TwoSum 对象 +- `def add(self, number: int) -> None:`向数据结构添加一个数 number +- `def find(self, value: int) -> bool:`寻找数据结构中是否存在一对整数,使得两数之和与给定的值 value 相等。如果存在,返回 True ;否则,返回 False 。 + +## 解题思路 + +使用哈希表存储数组元素值与元素频数的关系。哈希表中键值对信息为 number: count。count 为 number 在数组中的频数。 + +- `add(number)` 函数中:在哈希表添加 number 与其频数之间的关系。 +- `find(number)` 函数中:遍历哈希表,对于每个 number,检测哈希表中是否存在 value - number,如果存在则终止循环并返回结果。 + - 如果 `number == value - number`,则判断哈希表中 number 的数目是否大于等于 2。 + +## 代码 + +```python +class TwoSum: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.num_counts = dict() + + + def add(self, number: int) -> None: + """ + Add the number to an internal data structure.. + """ + if number in self.num_counts: + self.num_counts[number] += 1 + else: + self.num_counts[number] = 1 + + + def find(self, value: int) -> bool: + """ + Find if there exists any pair of numbers which sum is equal to the value. + """ + for number in self.num_counts.keys(): + number2 = value - number + if number == number2: + if self.num_counts[number] > 1: + return True + else: + if number2 in self.num_counts: + return True + return False +``` diff --git a/docs/solutions/0100-0199/valid-palindrome.md b/docs/solutions/0100-0199/valid-palindrome.md new file mode 100644 index 00000000..74a46955 --- /dev/null +++ b/docs/solutions/0100-0199/valid-palindrome.md @@ -0,0 +1,78 @@ +# [0125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0125. 验证回文串 - 力扣](https://leetcode.cn/problems/valid-palindrome/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 + +**说明**: + +- 回文串:正着读和反着读都一样的字符串。 +- $1 \le s.length \le 2 * 10^5$。 +- `s` 仅由可打印的 ASCII 字符组成。 + +**示例**: + +- 示例 1: + +```python +输入: "A man, a plan, a canal: Panama" +输出:true +解释:"amanaplanacanalpanama" 是回文串。 +``` + +- 示例 2: + +```python +输入:"race a car" +输出:false +解释:"raceacar" 不是回文串。 +``` + +## 解题思路 + +### 思路 1:对撞指针 + +1. 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 +2. 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 +3. 然后判断 `s[left]` 是否和 `s[right]` 相等(注意大小写)。 + 1. 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 + 2. 如果不相等,则说明不是回文串,直接返回 `False`。 +4. 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + left = 0 + right = len(s) - 1 + + while left < right: + if not s[left].isalnum(): + left += 1 + continue + if not s[right].isalnum(): + right -= 1 + continue + + if s[left].lower() == s[right].lower(): + left += 1 + right -= 1 + else: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(len(s))$。 +- **空间复杂度**:$O(len(s))$。 diff --git a/docs/solutions/0100-0199/word-break-ii.md b/docs/solutions/0100-0199/word-break-ii.md new file mode 100644 index 00000000..53226b80 --- /dev/null +++ b/docs/solutions/0100-0199/word-break-ii.md @@ -0,0 +1,57 @@ +# [0140. 单词拆分 II](https://leetcode.cn/problems/word-break-ii/) + +- 标签:字典树、记忆化搜索、数组、哈希表、字符串、动态规划、回溯 +- 难度:困难 + +## 题目链接 + +- [0140. 单词拆分 II - 力扣](https://leetcode.cn/problems/word-break-ii/) + +## 题目大意 + +给定一个非空字符串 `s` 和一个包含非空单词列表的字典 `wordDict`。 + +要求:在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。 + +说明: + +- 分隔时可以重复使用字典中的单词。 +- 你可以假设字典中没有重复的单词。 + +## 解题思路 + +回溯 + 记忆化搜索。 + +对于字符串 `s`,如果某个位置左侧部分是单词列表中的单词,则拆分出该单词,然后对 `s` 右侧剩余部分进行递归拆分。如果可以将整个字符串 `s` 拆分成单词列表中的单词,则得到一个句子。 + +使用 `memo` 数组进行记忆化存储,这样可以减少重复计算。 + +## 代码 + +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: + size = len(s) + memo = [None for _ in range(size + 1)] + + def dfs(start): + if start > size - 1: + return [[]] + if memo[start]: + return memo[start] + res = [] + for i in range(start, size): + word = s[start: i + 1] + if word in wordDict: + rest_res = dfs(i + 1) + for item in rest_res: + res.append([word] + item) + memo[start] = res + return res + res = dfs(0) + ans = [] + for item in res: + ans.append(" ".join(item)) + return ans +``` + diff --git a/docs/solutions/0100-0199/word-break.md b/docs/solutions/0100-0199/word-break.md new file mode 100644 index 00000000..309de356 --- /dev/null +++ b/docs/solutions/0100-0199/word-break.md @@ -0,0 +1,93 @@ +# [0139. 单词拆分](https://leetcode.cn/problems/word-break/) + +- 标签:字典树、记忆化搜索、数组、哈希表、字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0139. 单词拆分 - 力扣](https://leetcode.cn/problems/word-break/) + +## 题目大意 + +**描述**:给定一个非空字符串 $s$ 和一个包含非空单词的列表 $wordDict$ 作为字典。 + +**要求**:判断是否可以利用字典中出现的单词拼接出 $s$ 。 + +**说明**: + +- 不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 +- $1 \le s.length \le 300$。 +- $1 \le wordDict.length \le 1000$。 +- $1 \le wordDict[i].length \le 20$。 +- $s$ 和 $wordDict[i]$ 仅有小写英文字母组成。 +- $wordDict$ 中的所有字符串互不相同。 + +**示例**: + +- 示例 1: + +```python +输入: s = "leetcode", wordDict = ["leet", "code"] +输出: true +解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。 +``` + +- 示例 2: + +```python +输入: s = "applepenapple", wordDict = ["apple", "pen"] +输出: true +解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。 + 注意,你可以重复使用字典中的单词。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照单词结尾位置进行阶段划分。 + +###### 2. 定义状态 + +$s$ 能否拆分为单词表的单词,可以分解为: + +- 前 $i$ 个字符构成的字符串,能否分解为单词。 +- 剩余字符串,能否分解为单词。 + +定义状态 $dp[i]$ 表示:长度为 $i$ 的字符串 $s[0: i]$ 能否拆分成单词,如果为 $True$ 则表示可以拆分,如果为 $False$ 则表示不能拆分。 + +###### 3. 状态转移方程 + +- 如果 $s[0: j]$ 可以拆分为单词(即 $dp[j] == True$),并且字符串 $s[j: i]$ 出现在字典中,则 `dp[i] = True`。 +- 如果 $s[0: j]$ 不可以拆分为单词(即 $dp[j] == False$),或者字符串 $s[j: i]$ 没有出现在字典中,则 `dp[i] = False`。 + +###### 4. 初始条件 + +- 长度为 $0$ 的字符串 $s[0: i]$ 可以拆分为单词,即 $dp[0] = True$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示:长度为 $i$ 的字符串 $s[0: i]$ 能否拆分成单词。则最终结果为 $dp[size]$,$size$ 为字符串长度。 + +### 思路 1:代码 + +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + size = len(s) + dp = [False for _ in range(size + 1)] + dp[0] = True + for i in range(size + 1): + for j in range(i): + if dp[j] and s[j: i] in wordDict: + dp[i] = True + return dp[size] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0100-0199/word-ladder.md b/docs/solutions/0100-0199/word-ladder.md new file mode 100644 index 00000000..42ebe090 --- /dev/null +++ b/docs/solutions/0100-0199/word-ladder.md @@ -0,0 +1,54 @@ +# [0127. 单词接龙](https://leetcode.cn/problems/word-ladder/) + +- 标签:广度优先搜索、哈希表、字符串 +- 难度:困难 + +## 题目链接 + +- [0127. 单词接龙 - 力扣](https://leetcode.cn/problems/word-ladder/) + +## 题目大意 + +给定两个单词 `beginWord` 和 `endWord`,以及一个字典 `wordList`。找到从 `beginWord` 到 `endWord` 的最短转换序列中的单词数目。如果不存在这样的转换序列,则返回 0。 + +转换需要遵守的规则如下: + +- 每次转换只能改变一个字母。 +- 转换过程中的中间单词必须为字典中的单词。 + +## 解题思路 + +广度优先搜索。使用队列存储将要遍历的单词和单词数目。 + +从 `beginWord` 开始变换,把单词的每个字母都用 `a ~ z` 变换一次,变换后的单词是否是 `endWord`,如果是则直接返回。 + +否则查找变换后的词是否在 `wordList` 中。如果在 `wordList` 中找到就加入队列,找不到就输出 `0`。然后按照广度优先搜索的算法急需要遍历队列中的节点,直到所有单词都出队时结束。 + +## 代码 + +```python +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + if not wordList or endWord not in wordList: + return 0 + word_set = set(wordList) + if beginWord in word_set: + word_set.remove(beginWord) + + queue = collections.deque() + queue.append((beginWord, 1)) + while queue: + word, level = queue.popleft() + if word == endWord: + return level + + for i in range(len(word)): + for j in range(26): + new_word = word[:i] + chr(ord('a') + j) + word[i + 1:] + if new_word in word_set: + word_set.remove(new_word) + queue.append((new_word, level + 1)) + + return 0 +``` + diff --git a/docs/solutions/0200-0299/3sum-smaller.md b/docs/solutions/0200-0299/3sum-smaller.md new file mode 100644 index 00000000..c8640507 --- /dev/null +++ b/docs/solutions/0200-0299/3sum-smaller.md @@ -0,0 +1,83 @@ +# [0259. 较小的三数之和](https://leetcode.cn/problems/3sum-smaller/) + +- 标签:数组、双指针、二分查找、排序 +- 难度:中等 + +## 题目链接 + +- [0259. 较小的三数之和 - 力扣](https://leetcode.cn/problems/3sum-smaller/) + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的整数数组和一个目标值 $target$。 + +**要求**:寻找能够使条件 $nums[i] + nums[j] + nums[k] < target$ 成立的三元组 ($i$, $j$, $k$) 的个数($0 <= i < j < k < n$)。 + +**说明**: + +- 最好在 $O(n^2)$ 的时间复杂度内解决问题。 +- $n == nums.length$。 +- $0 \le n \le 3500$。 +- $-100 \le nums[i] \le 100$。 +- $-100 \le target \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [-2,0,1,3], target = 2 +输出: 2 +解释: 因为一共有两个三元组满足累加和小于 2: + [-2,0,1] + [-2,0,3] +``` + +- 示例 2: + +```python +输入: nums = [], target = 0 +输出: 0 +``` + +## 解题思路 + +### 思路 1:排序 + 双指针 + +三元组直接枚举的时间复杂度是 $O(n^3)$,明显不符合题目要求。那么可以考虑使用双指针减少循环内的时间复杂度。具体做法如下: + +- 先对数组进行从小到大排序。 +- 遍历数组,对于数组元素 $nums[i]$,使用两个指针 $left$、$right$。$left$ 指向第 $i + 1$ 个元素位置,$right$ 指向数组的最后一个元素位置。 +- 在区间 $[left, right]$ 中查找满足 $nums[i] + nums[left] + nums[right] < target$的方案数。 +- 计算 $nums[i]$、$nums[left]$、$nums[right]$ 的和,将其与 $target$ 比较。 + - 如果 $nums[i] + nums[left] + nums[right] < target$,则说明 $i$、$left$、$right$ 作为三元组满足题目要求,同时说明区间 $[left, right]$ 中的元素作为 $right$ 都满足条件,此时将 $left$ 右移,继续判断。 + - 如果 $nums[i] + nums[left] + nums[right] \ge target$,则说明 $right$ 太大了,应该缩小 $right$,然后继续判断。 +- 当 $left == right$ 时,区间搜索完毕,继续遍历 $nums[i + 1]$。 + +这种思路使用了两重循环,其中内层循环当 $left == right$ 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$,符合题目要求。 + +### 思路 1:代码 + +```python +class Solution: + def threeSumSmaller(self, nums: List[int], target: int) -> int: + nums.sort() + size = len(nums) + res = 0 + for i in range(size): + left, right = i + 1, size - 1 + while left < right: + total = nums[i] + nums[left] + nums[right] + if total < target: + res += (right - left) + left += 1 + else: + right -= 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0200-0299/add-digits.md b/docs/solutions/0200-0299/add-digits.md new file mode 100644 index 00000000..c2e1e7e4 --- /dev/null +++ b/docs/solutions/0200-0299/add-digits.md @@ -0,0 +1,31 @@ +# [0258. 各位相加](https://leetcode.cn/problems/add-digits/) + +- 标签:数学、数论、模拟 +- 难度:简单 + +## 题目链接 + +- [0258. 各位相加 - 力扣](https://leetcode.cn/problems/add-digits/) + +## 题目大意 + +给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。 + +## 解题思路 + +根据题意,循环模拟累加即可。 + +## 代码 + +```python +class Solution: + def addDigits(self, num: int) -> int: + while num >= 10: + cur = 0 + while num: + cur += num % 10 + num //= 10 + num = cur + return num +``` + diff --git a/docs/solutions/0200-0299/basic-calculator-ii.md b/docs/solutions/0200-0299/basic-calculator-ii.md new file mode 100644 index 00000000..1b873ca6 --- /dev/null +++ b/docs/solutions/0200-0299/basic-calculator-ii.md @@ -0,0 +1,97 @@ +# [0227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) + +- 标签:栈、数学、字符串 +- 难度:中等 + +## 题目链接 + +- [0227. 基本计算器 II - 力扣](https://leetcode.cn/problems/basic-calculator-ii/) + +## 题目大意 + +**描述**:给定一个字符串表达式 `s`,表达式中所有整数为非负整数,运算符只有 `+`、`-`、`*`、`/`,没有括号。 + +**要求**:实现一个基本计算器来计算并返回它的值。 + +**说明**: + +- $1 \le s.length \le 3 * 10^5$。 +- `s` 由整数和算符(`+`、`-`、`*`、`/`)组成,中间由一些空格隔开。 +- `s` 表示一个有效表达式。 +- 表达式中的所有整数都是非负整数,且在范围 $[0, 2^{31} - 1]$ 内。 +- 题目数据保证答案是一个 32-bit 整数。 + +**示例**: + +- 示例 1: + +```python +输入:s = "3+2*2" +输出:7 +``` + +- 示例 2: + +```python +输入:s = " 3/2 " +输出:1 +``` + +## 解题思路 + +### 思路 1:栈 + +计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 + +可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 + +具体做法: + +1. 遍历字符串 `s`,使用变量 `op` 来标记数字之前的运算符,默认为 `+`。 +2. 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 + 1. 如果 `op` 为 `+`,则将 `num` 压入栈中。 + 2. 如果 `op` 为 `-`,则将 `-num` 压入栈中。 + 3. 如果 `op` 为 `*`,则将栈顶元素 `top` 取出,计算 `top * num`,并将计算结果压入栈中。 + 4. 如果 `op` 为 `/`,则将栈顶元素 `top` 取出,计算 `int(top / num)`,并将计算结果压入栈中。 +3. 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 `op`。 +4. 最后将栈中整数进行累加,并返回结果。 + +### 思路 1:代码 + +```python +class Solution: + def calculate(self, s: str) -> int: + size = len(s) + stack = [] + op = '+' + index = 0 + while index < size: + if s[index] == ' ': + index += 1 + continue + if s[index].isdigit(): + num = ord(s[index]) - ord('0') + while index + 1 < size and s[index+1].isdigit(): + index += 1 + num = 10 * num + ord(s[index]) - ord('0') + if op == '+': + stack.append(num) + elif op == '-': + stack.append(-num) + elif op == '*': + top = stack.pop() + stack.append(top * num) + elif op == '/': + top = stack.pop() + stack.append(int(top / num)) + elif s[index] in "+-*/": + op = s[index] + index += 1 + return sum(stack) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/binary-tree-paths.md b/docs/solutions/0200-0299/binary-tree-paths.md new file mode 100644 index 00000000..28157c8d --- /dev/null +++ b/docs/solutions/0200-0299/binary-tree-paths.md @@ -0,0 +1,43 @@ +# [0257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) + +- 标签:树、深度优先搜索、字符串、回溯、二叉树 +- 难度:简单 + +## 题目链接 + +- [0257. 二叉树的所有路径 - 力扣](https://leetcode.cn/problems/binary-tree-paths/) + +## 题目大意 + +给定一个二叉树,返回所有从根节点到叶子节点的路径。 + +## 解题思路 + +深度优先搜索。在递归遍历时,需考虑当前节点和左右孩子节点。 + +- 如果当前节点不是叶子节点,则当前拼接路径中加入该点,并继续递归遍历。 +- 如果当前节点是叶子节点,则当前拼接路径中加入该点,并将当前路径加入答案数组。 + +## 代码 + +```python +class Solution: + def binaryTreePaths(self, root: TreeNode) -> List[str]: + res = [] + def dfs(root, path): + if not root: + return + path += str(root.val) + if not root.left and not root.right: + res.append(path) + elif not root.right: + dfs(root.left, path + "->") + elif not root.left: + dfs(root.right, path + "->") + else: + dfs(root.left, path + "->") + dfs(root.right, path + "->") + dfs(root, "") + return res +``` + diff --git a/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md b/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md new file mode 100644 index 00000000..90995d1f --- /dev/null +++ b/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md @@ -0,0 +1,99 @@ +# [0201. 数字范围按位与](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) + +- 标签:位运算 +- 难度:中等 + +## 题目链接 + +- [0201. 数字范围按位与 - 力扣](https://leetcode.cn/problems/bitwise-and-of-numbers-range/) + +## 题目大意 + +**描述**:给定两个整数 $left$ 和 $right$,表示区间 $[left, right]$。 + +**要求**:返回此区间内所有数字按位与的结果(包含 $left$、$right$ 端点)。 + +**说明**: + +- $0 \le left \le right \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:left = 5, right = 7 +输出:4 +``` + +- 示例 2: + +```python +输入:left = 1, right = 2147483647 +输出:0 +``` + +## 解题思路 + +### 思路 1:位运算 + +很容易想到枚举算法:对于区间 $[left, right]$,如果使用枚举算法,对区间范围内的数依次进行按位与操作,最后输出结果。 + +但是枚举算法在区间范围很大的时候会超时,所以我们应该换个思路来解决这道题。 + +我们知道与运算的规则如下: + +- `0 & 0 == 0` +- `0 & 1 == 0` +- `1 & 0 == 0` +- `1 & 1 == 1`。 + +只有对应位置上都为 $1$ 的情况下,按位与才能得到 $1$。而对应位置上只要出现 $0$,则该位置上最终的按位与结果一定为 $0$。 + +那么我们可以先来求一下区间所有数对应二进制的公共前缀,假设这个前缀的长度为 $x$。 + +公共前缀部分因为每个位置上的二进制值完全一样,所以按位与的结果也相同。 + +接下来考虑除了公共前缀的剩余的二进制位部分。 + +这时候剩余部分有两种情况: + +- $x = 31$。则 $left == right$,其按位与结果就是 $left$ 本身。 +- $0 \le x < 31$。这种情况下因为 $left < right$,所以 $left$ 的第 $x + 1$ 位必然为 $0$,$right$ 的第 $x + 1$ 位必然为 $1$。 + - 注意:$left$、$right$ 第 $x + 1$ 位上不可能同为 $0$ 或 $1$,这样就是公共前缀了。 + - 注意:同样不可能是 $left$ 第 $x + 1$ 位为 $1$,$right$ 第 $x + 1$ 位为 $0$,这样就是 $left > right$ 了。 + +而从第 $x + 1$ 位起,从 $left$ 到 $right$。肯定会经过 $10000...$ 的位置,从而使得除了公共前缀的剩余部分(后面的 $31 - x$ 位)的按位与结果一定为 $0$。 + +举个例子,$x = 27$,则除了公共前缀的剩余部分长度为 $4$。则剩余部分从 $0XXX$ 到 $1XXX$ 必然会经过 $1000$,则剩余部分的按位与结果为 $0000$。 + +那么这道题就转变为了求 $[left, right]$ 区间范围内所有数的二进制公共前缀,然后在后缀位置上补上 $0$。 + +求解公共前缀,我们借助于 Brian Kernigham 算法中的 `n & (n - 1)` 公式来计算。 + +- `n & (n - 1)` 公式:对 $n$ 和 $n - 1$ 进行按位与运算后,$n$ 最右边的 $1$ 会变成 $0$,也就是清除了 $n$ 对应二进制的最右侧的 $1$。比如 $n = 10110100_{(2)}$,进行 `n & (n - 1)` 操作之后,就变为了 $n = 10110000_{(2)}$。 + +具体计算步骤如下: + +1. 对于给定的区间范围 $[left, right]$,对 $right$ 进行 `right & (right - 1)` 迭代。 +2. 直到 $right$ 小于等于 $left$,此时区间内非公共前缀的 $1$ 均变为了 $0$。 +3. 最后输出 $right$ 作为答案。 + +### 思路 1:位运算代码 + +```python +class Solution: + def rangeBitwiseAnd(self, left: int, right: int) -> int: + while left < right: + right = right & (right - 1) + return right +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- 【题解】[巨好理解的位运算思路 - 数字范围按位与 - 力扣](https://leetcode.cn/problems/bitwise-and-of-numbers-range/solution/ju-hao-li-jie-de-wei-yun-suan-si-lu-by-time-limit/) diff --git a/docs/solutions/0200-0299/closest-binary-search-tree-value.md b/docs/solutions/0200-0299/closest-binary-search-tree-value.md new file mode 100644 index 00000000..1205ba22 --- /dev/null +++ b/docs/solutions/0200-0299/closest-binary-search-tree-value.md @@ -0,0 +1,73 @@ +# [0270. 最接近的二叉搜索树值](https://leetcode.cn/problems/closest-binary-search-tree-value/) + +- 标签:树、深度优先搜索、二叉搜索树、二分查找、二叉树 +- 难度:简单 + +## 题目链接 + +- [0270. 最接近的二叉搜索树值 - 力扣](https://leetcode.cn/problems/closest-binary-search-tree-value/) + +## 题目大意 + +**描述**:给定一个不为空的二叉搜索树的根节点,以及一个目标值 $target$。 + +**要求**:在二叉搜索树中找到最接近目标值 $target$ 的数值。 + +**说明**: + +- 树中节点的数目在范围 $[1, 10^4]$ 内。 +- $0 \le Node.val \le 10^9$。 +- $-10^9 \le target \le 10^9$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/12/closest1-1-tree.jpg) + +```python +输入:root = [4,2,5,1,3], target = 3.714286 +输出:4 +``` + +- 示例 2: + +```python +输入:root = [1], target = 4.428571 +输出:1 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +题目中最接近目标值 $target$ 的数值指的就是与 $target$ 相减绝对值最小的数值。 + +而且根据二叉搜索树的性质,我们可以利用二分搜索的方式,查找与 $target$ 相减绝对值最小的数值。具体做法为: + +- 定义一个变量 $closest$ 表示与 $target$ 最接近的数值,初始赋值为根节点的值 $root.val$。 +- 判断当前节点的值域 $closet$ 值哪个更接近 $target$,如果当前值更接近,则更新 $closest$。 +- 如果 $target$ < 当前节点值,则从当前节点的左子树继续查找。 +- 如果 $target$ ≥ 当前节点值,则从当前节点的右子树继续查找。 + +### 思路 1:代码 + +```python +class Solution: + def closestValue(self, root: TreeNode, target: float) -> int: + closest = root.val + while root: + if abs(target - root.val) < abs(target - closest): + closest = root.val + if target < root.val: + root = root.left + else: + root = root.right + return closest +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$,其中 $n$ 为二叉搜索树的节点个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/contains-duplicate-ii.md b/docs/solutions/0200-0299/contains-duplicate-ii.md new file mode 100644 index 00000000..a418505f --- /dev/null +++ b/docs/solutions/0200-0299/contains-duplicate-ii.md @@ -0,0 +1,61 @@ +# [0219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) + +- 标签:数组、哈希表、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [0219. 存在重复元素 II - 力扣](https://leetcode.cn/problems/contains-duplicate-ii/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 + +**要求**:判断是否存在 $nums[i] == nums[j]$($i \ne j$),并且 $i$ 和 $j$ 的差绝对值至多为 $k$。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-10^9 <= nums[i] <= 10^9$。 +- $0 \le k \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,1], k = 3 +输出:True +``` + +## 解题思路 + +### 思路 1:哈希表 + +维护一个最多有 $k$ 个元素的哈希表。遍历 $nums$,对于数组中的每个整数 $nums[i]$,判断哈希表中是否存在这个整数。 + +- 如果存在,则说明出现了两次,且 $i \ne j$,直接返回 $True$。 + +- 如果不存在,则将 $nums[i]$ 加入哈希表。 +- 判断哈希表长度是否超过了 $k$,如果超过了 $k$,则删除哈希表中最旧的元素 $nums[i - k]$。 +- 如果遍历完仍旧找不到,则返回 $False$。 + +### 思路 1:代码 + +```python +class Solution: + def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: + nums_dict = dict() + for i in range(len(nums)): + if nums[i] in nums_dict: + return True + nums_dict[nums[i]] = 1 + if len(nums_dict) > k: + del nums_dict[nums[i - k]] + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0200-0299/contains-duplicate-iii.md b/docs/solutions/0200-0299/contains-duplicate-iii.md new file mode 100644 index 00000000..53c4f784 --- /dev/null +++ b/docs/solutions/0200-0299/contains-duplicate-iii.md @@ -0,0 +1,153 @@ +# [0220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) + +- 标签:数组、桶排序、有序集合、排序、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0220. 存在重复元素 III - 力扣](https://leetcode.cn/problems/contains-duplicate-iii/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$,以及两个整数 $k$、$t$。 + +**要求**:判断数组中是否存在两个不同下标的 $i$ 和 $j$,其对应元素满足 $abs(nums[i] - nums[j]) \le t$,同时满足 $abs(i - j) \le k$。如果满足条件则返回 `True`,不满足条件返回 `False`。 + +**说明**: + +- $0 \le nums.length \le 2 \times 10^4$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- $0 \le k \le 10^4$。 +- $0 \le t \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,1], k = 3, t = 0 +输出:True +``` + +- 示例 2: + +```python +输入:nums = [1,0,1,1], k = 1, t = 2 +输出:True +``` + +## 解题思路 + +题目中需要满足两个要求,一个是元素值的要求($abs(nums[i] - nums[j]) \le t$) ,一个是下标范围的要求($abs(i - j) \le k$)。 + +对于任意一个位置 $i$ 来说,合适的 $j$ 应该在区间 $[i - k, i + k]$ 内,同时 $nums[j]$ 值应该在区间 $[nums[i] - t, nums[i] + t]$ 内。 + +最简单的做法是两重循环遍历数组,第一重循环遍历位置 $i$,第二重循环遍历 $[i - k, i + k]$ 的元素,判断是否满足 $abs(nums[i] - nums[j]) \le t$。但是这样做的时间复杂度为 $O(n \times k)$,其中 $n$ 是数组 $nums$ 的长度。 + +我们需要优化一下检测相邻 $2 \times k$ 个元素是否满足 $abs(nums[i] - nums[j]) \le t$ 的方法。有两种思路:「桶排序」和「滑动窗口(固定长度)」。 + +### 思路 1:桶排序 + +1. 利用桶排序的思想,将桶的大小设置为 $t + 1$。只需要使用一重循环遍历位置 $i$,然后根据 $\lfloor \frac{nums[i]}{t + 1} \rfloor$,从而决定将 $nums[i]$ 放入哪个桶中。 +2. 这样在同一个桶内各个元素之间的差值绝对值都小于等于 $t$。而相邻桶之间的元素,只需要校验一下两个桶之间的差值是否不超过 $t$。这样就可以以 $O(1)$ 的时间复杂度检测相邻 $2 \times k$ 个元素是否满足 $abs(nums[i] - nums[j]) \le t$。 +3. 而 $abs(i - j) \le k$ 条件则可以通过在一重循环遍历时,将超出范围的 $nums[i - k]$ 从对应桶中删除,从而保证桶中元素一定满足 $abs(i - j) \le k$。 + +具体步骤如下: + +1. 将每个桶的大小设置为 $t + 1$。我们将元素按照大小依次放入不同的桶中。 +2. 遍历数组 $nums$ 中的元素,对于元素$ nums[i]$ : + 1. 如果 $nums[i]$ 放入桶之前桶里已经有元素了,那么这两个元素必然满足 $abs(nums[i] - nums[j]) \le t$, + 2. 如果之前桶里没有元素,那么就将 $nums[i]$ 放入对应桶中。 + 3. 再判断左右桶的左右两侧桶中是否有元素满足 $abs(nums[i] - nums[j]) <= t$。 + 4. 然后将 $nums[i - k]$ 之前的桶清空,因为这些桶中的元素与 $nums[i]$ 已经不满足 $abs(i - j) \le k$ 了。 +3. 最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + bucket_dict = dict() + for i in range(len(nums)): + # 将 nums[i] 划分到大小为 t + 1 的不同桶中 + num = nums[i] // (t + 1) + + # 桶中已经有元素了 + if num in bucket_dict: + return True + + # 把 nums[i] 放入桶中 + bucket_dict[num] = nums[i] + + # 判断左侧桶是否满足条件 + if (num - 1) in bucket_dict and abs(bucket_dict[num - 1] - nums[i]) <= t: + return True + # 判断右侧桶是否满足条件 + if (num + 1) in bucket_dict and abs(bucket_dict[num + 1] - nums[i]) <= t: + return True + # 将 i - k 之前的旧桶清除,因为之前的桶已经不满足条件了 + if i >= k: + bucket_dict.pop(nums[i - k] // (t + 1)) + + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。$n$ 是给定数组长度。 +- **空间复杂度**:$O(min(n, k))$。桶中最多包含 $min(n, k + 1)$ 个元素。 + +### 思路 2:滑动窗口(固定长度) + +1. 使用一个长度为 $k$ 的滑动窗口,每次遍历到 $nums[right]$ 时,滑动窗口内最多包含 $nums[right]$ 之前最多 $k$ 个元素。只需要检查前 $k$ 个元素是否在 $[nums[right] - t, nums[right] + t]$ 区间内即可。 +2. 检查 $k$ 个元素是否在 $[nums[right] - t, nums[right] + t]$ 区间,可以借助保证有序的数据结构(比如 `SortedList`)+ 二分查找来解决,从而减少时间复杂度。 + +具体步骤如下: + +1. 使用有序数组类 $window$ 维护一个长度为 $k$ 的窗口,满足数组内元素有序,且支持增加和删除操作。 +2. $left$、$right$ 都指向序列的第一个元素。即:`left = 0`,`right = 0`。 +3. 将当前元素填入窗口中,即 `window.add(nums[right])`。 +4. 当窗口元素大于 $k$ 个时,即当 $right - left > k$ 时,移除窗口最左侧元素,并向右移动 $left$。 +5. 当窗口元素小于等于 $k$ 个时: + 1. 使用二分查找算法,查找 $nums[right]$ 在 $window$ 中的位置 $idx$。 + 2. 判断 $window[idx]$ 与相邻位置上元素差值绝对值,若果满足 $abs(window[idx] - window[idx - 1]) \le t$ 或者 $abs(window[idx + 1] - window[idx]) \le t$ 时返回 `True`。 +6. 向右移动 $right$。 +7. 重复 $3 \sim 6$ 步,直到 $right$ 到达数组末尾,如果还没找到满足条件的情况,则返回 `False`。 + +### 思路 2:代码 + +```python +from sortedcontainers import SortedList + +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + size = len(nums) + window = SortedList() + left, right = 0, 0 + while right < size: + window.add(nums[right]) + + if right - left > k: + window.remove(nums[left]) + left += 1 + + idx = bisect.bisect_left(window, nums[right]) + + if idx > 0 and abs(window[idx] - window[idx - 1]) <= t: + return True + if idx < len(window) - 1 and abs(window[idx + 1] - window[idx]) <= t: + return True + + right += 1 + + return False +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log (min(n, k)))$。 +- **空间复杂度**:$O(min(n, k))$。 + +## 参考资料 + +- 【题解】[利用桶的原理O(n),Python3 - 存在重复元素 III - 力扣](https://leetcode.cn/problems/contains-duplicate-iii/solution/li-yong-tong-de-yuan-li-onpython3-by-zhou-pen-chen/) diff --git a/docs/solutions/0200-0299/contains-duplicate.md b/docs/solutions/0200-0299/contains-duplicate.md new file mode 100644 index 00000000..5a383469 --- /dev/null +++ b/docs/solutions/0200-0299/contains-duplicate.md @@ -0,0 +1,105 @@ +# [0217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) + +- 标签:数组、哈希表、排序 +- 难度:简单 + +## 题目链接 + +- [0217. 存在重复元素 - 力扣](https://leetcode.cn/problems/contains-duplicate/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`。 + +**要求**:判断是否存在重复元素。如果有元素在数组中出现至少两次,返回 `True`;否则返回 `False`。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-10^9 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,1] +输出:True +``` + +- 示例 2: + +```python +输入:nums = [1,2,3,4] +输出:False +``` + +## 解题思路 + +### 思路 1:哈希表 + +- 使用一个哈希表存储元素和对应元素数量。 +- 遍历元素,如果哈希表中出现了该元素,则直接输出 `True`。如果没有出现,则向哈希表中插入该元素。 +- 如果遍历完也没发现重复元素,则输出 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + numDict = dict() + for num in nums: + if num in numDict: + return True + else: + numDict[num] = num + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:集合 + +- 使用一个 `set` 集合存储数组中所有元素。 +- 如果集合中元素个数与数组元素个数不同,则说明出现了重复元素,返回 `True`。 +- 如果集合中元素个数与数组元素个数相同,则说明没有出现了重复元素,返回 `False`。 + +### 思路 2:集合代码 + +```python +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + return len(set(nums)) != len(nums) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 3:排序 + +- 对数组进行排序。 +- 排序之后,遍历数组,判断相邻元素之间是否出现重复元素。 +- 如果相邻元素相同,则说明出现了重复元素,返回 `True`。 +- 如果遍历完也没发现重复元素,则输出 `False`。 + +### 思路 3:排序代码 + +```python +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + nums.sort() + for i in range(1, len(nums)): + if nums[i - 1] == nums[i]: + return True + return False +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0200-0299/count-complete-tree-nodes.md b/docs/solutions/0200-0299/count-complete-tree-nodes.md new file mode 100644 index 00000000..6ca278b0 --- /dev/null +++ b/docs/solutions/0200-0299/count-complete-tree-nodes.md @@ -0,0 +1,31 @@ +# [0222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) + +- 标签:树、深度优先搜索、二分查找、二叉树 +- 难度:中等 + +## 题目链接 + +- [0222. 完全二叉树的节点个数 - 力扣](https://leetcode.cn/problems/count-complete-tree-nodes/) + +## 题目大意 + +给定一棵完全二叉树的根节点 `root`,返回该树的节点个数。 + +- 完全二叉树:除了最底层节点可能没有填满外,其余各层节点数都达到了最大值,并且最下面一层的节点都集中在盖层最左边的若干位置。若最底层在第 `h` 层,则该层包含 $1 \sim 2^h$ 个节点。 + +## 解题思路 + +根据题意可知公式:当前根节点的节点个数 = 左子树节点个数 + 右子树节点个数 + 1。 + +根据上述公式递归遍历左右子树节点,并返回左右子树节点数 + 1。 + +## 代码 + +```python +class Solution: + def countNodes(self, root: TreeNode) -> int: + if not root: + return 0 + return 1 + self.countNodes(root.left) + self.countNodes(root.right) +``` + diff --git a/docs/solutions/0200-0299/count-primes.md b/docs/solutions/0200-0299/count-primes.md new file mode 100644 index 00000000..f51d749f --- /dev/null +++ b/docs/solutions/0200-0299/count-primes.md @@ -0,0 +1,104 @@ +# [0204. 计数质数](https://leetcode.cn/problems/count-primes/) + +- 标签:数组、数学、枚举、数论 +- 难度:中等 + +## 题目链接 + +- [0204. 计数质数 - 力扣](https://leetcode.cn/problems/count-primes/) + +## 题目大意 + +**描述**:给定 一个非负整数 $n$。 + +**要求**:统计小于 $n$ 的质数数量。 + +**说明**: + +- $0 \le n \le 5 * 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入 n = 10 +输出 4 +解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:0 +``` + +## 解题思路 + +### 思路 1:枚举算法(超时) + +对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整数的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 + +这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。 + +在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终返回该数目作为答案。 + +考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。 + +利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n})$,检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。 + +### 思路 1:代码 + +```python +class Solution: + def isPrime(self, x): + for i in range(2, int(pow(x, 0.5)) + 1): + if x % i == 0: + return False + return True + + def countPrimes(self, n: int) -> int: + cnt = 0 + for x in range(2, n): + if self.isPrime(x): + cnt += 1 + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \sqrt{n})$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:埃氏筛法 + +可以用「埃氏筛」进行求解。这种方法是由古希腊数学家埃拉托斯尼斯提出的,具体步骤如下: + +- 使用长度为 $n$ 的数组 `is_prime` 来判断一个数是否是质数。如果 `is_prime[i] == True` ,则表示 $i$ 是质数,如果 `is_prime[i] == False`,则表示 $i$ 不是质数。并使用变量 `count` 标记质数个数。 +- 然后从 $[2, n - 1]$ 的第一个质数(即数字 $2$) 开始,令 `count` 加 $1$,并将该质数在 $[2, n - 1]$ 范围内所有倍数(即 $4$、$6$、$8$、...)都标记为非质数。 +- 然后根据数组 `is_prime` 中的信息,找到下一个没有标记为非质数的质数(即数字 $3$),令 `count` 加 $1$,然后将该质数在 $[2, n - 1]$ 范围内的所有倍数(即 $6$、$9$、$12$、…)都标记为非质数。 +- 以此类推,直到所有小于或等于 $n - 1$ 的质数和质数的倍数都标记完毕时,输出 `count`。 + +优化:对于一个质数 $x$,我们可以直接从 $x \times x$ 开始标记,这是因为 $2 \times x$、$3 \times x$、… 这些数已经在 $x$ 之前就被其他数的倍数标记过了,例如 $2$ 的所有倍数、$3$ 的所有倍数等等。 + +### 思路 2:代码 + +```python +class Solution: + def countPrimes(self, n: int) -> int: + is_prime = [True] * n + count = 0 + for i in range(2, n): + if is_prime[i]: + count += 1 + for j in range(i * i, n, i): + is_prime[j] = False + return count +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2{log_2n})$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/course-schedule-ii.md b/docs/solutions/0200-0299/course-schedule-ii.md new file mode 100644 index 00000000..ba691a45 --- /dev/null +++ b/docs/solutions/0200-0299/course-schedule-ii.md @@ -0,0 +1,102 @@ +# [0210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [0210. 课程表 II - 力扣](https://leetcode.cn/problems/course-schedule-ii/) + +## 题目大意 + +**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 + +**要求**:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 + +**说明**: + +- $1 \le numCourses \le 2000$。 +- $0 \le prerequisites.length \le numCourses \times (numCourses - 1)$。 +- $prerequisites[i].length == 2$。 +- $0 \le ai, bi < numCourses$。 +- $ai \ne bi$。 +- 所有$[ai, bi]$ 互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:numCourses = 2, prerequisites = [[1,0]] +输出:[0,1] +解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]。 +``` + +- 示例 2: + +```python +输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] +输出:[0,2,1,3] +解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 +因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]。 +``` + +## 解题思路 + +### 思路 1:拓扑排序 + +这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组 $order$ 即可。 + +1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 +2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 +3. 从队列中选择一个节点 $u$,并将其加入到答案数组 $order$ 中。 +4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 +5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 +6. 最后判断总的顶点数和拓扑序列中的顶点数是否相等,如果相等,则返回答案数组 $order$,否则,返回空数组。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingKahn(self, graph: dict): + indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度 + for u in graph: + for v in graph[u]: + indegrees[v] += 1 # 统计所有顶点入度 + + # 将入度为 0 的顶点存入集合 S 中 + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + order = [] # order 用于存储拓扑序列 + + while S: + u = S.pop() # 从集合中选择一个没有前驱的顶点 0 + order.append(u) # 将其输出到拓扑序列 order 中 + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 + if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 + S.append(v) # 将其放入集合 S 中 + + if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列 + return [] + return order # 返回拓扑序列 + + + def findOrder(self, numCourses: int, prerequisites): + graph = dict() + for i in range(numCourses): + graph[i] = [] + + for v, u in prerequisites: + graph[u].append(v) + + return self.topologicalSortingKahn(graph) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 +- **空间复杂度**:$O(n + m)$。 + diff --git a/docs/solutions/0200-0299/course-schedule.md b/docs/solutions/0200-0299/course-schedule.md new file mode 100644 index 00000000..14a817b3 --- /dev/null +++ b/docs/solutions/0200-0299/course-schedule.md @@ -0,0 +1,94 @@ +# [0207. 课程表](https://leetcode.cn/problems/course-schedule/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [0207. 课程表 - 力扣](https://leetcode.cn/problems/course-schedule/) + +## 题目大意 + +**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要先完成课程 $bi$。 + +**要求**:判断是否可能完成所有课程的学习。如果可以,返回 `True`,否则,返回 `False`。 + +**说明**: + +- $1 \le numCourses \le 10^5$。 +- $0 \le prerequisites.length \le 5000$。 +- $prerequisites[i].length == 2$。 +- $0 \le ai, bi < numCourses$。 +- $prerequisites[i]$ 中所有课程对互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:numCourses = 2, prerequisites = [[1,0]] +输出:true +解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。这是可能的。 +``` + +- 示例 2: + +```python +输入:numCourses = 2, prerequisites = [[1,0],[0,1]] +输出:false +解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。 +``` + +## 解题思路 + +### 思路 1:拓扑排序 + +1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。 +2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。 +3. 从队列中选择一个节点 $u$,并令课程数减 $1$。 +4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。 +5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。 +6. 最后判断剩余课程数是否为 $0$,如果为 $0$,则返回 `True`,否则,返回 `False`。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def topologicalSorting(self, numCourses, graph): + indegrees = {u: 0 for u in graph} + for u in graph: + for v in graph[u]: + indegrees[v] += 1 + + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + + while S: + u = S.pop() + numCourses -= 1 + for v in graph[u]: + indegrees[v] -= 1 + if indegrees[v] == 0: + S.append(v) + + if numCourses == 0: + return True + return False + + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + graph = dict() + for i in range(numCourses): + graph[i] = [] + + for v, u in prerequisites: + graph[u].append(v) + + return self.topologicalSorting(numCourses, graph) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。 +- **空间复杂度**:$O(n + m)$。 + diff --git a/docs/solutions/0200-0299/delete-node-in-a-linked-list.md b/docs/solutions/0200-0299/delete-node-in-a-linked-list.md new file mode 100644 index 00000000..f42ef5d4 --- /dev/null +++ b/docs/solutions/0200-0299/delete-node-in-a-linked-list.md @@ -0,0 +1,26 @@ +# [0237. 删除链表中的节点](https://leetcode.cn/problems/delete-node-in-a-linked-list/) + +- 标签:链表 +- 难度:中等 + +## 题目链接 + +- [0237. 删除链表中的节点 - 力扣](https://leetcode.cn/problems/delete-node-in-a-linked-list/) + +## 题目大意 + +删除链表的给定节点。 + +## 解题思路 + +直接将该节点的后续节点覆盖该节点即可。即让该节点的值等于下一节点值,并让其 next 指针指向下一节点的下一节点。 + +## 代码 + +```python +class Solution: + def deleteNode(self, node): + node.val = node.next.val + node.next = node.next.next +``` + diff --git a/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md b/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md new file mode 100644 index 00000000..51223aaa --- /dev/null +++ b/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md @@ -0,0 +1,130 @@ +# [0211. 添加与搜索单词 - 数据结构设计](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) + +- 标签:深度优先搜索、设计、字典树、字符串 +- 难度:中等 + +## 题目链接 + +- [0211. 添加与搜索单词 - 数据结构设计 - 力扣](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) + +## 题目大意 + +**要求**:设计一个数据结构,支持「添加新单词」和「查找字符串是否与任何先前添加的字符串匹配」。 + +实现词典类 WordDictionary: + +- `WordDictionary()` 初始化词典对象。 +- `void addWord(word)` 将 `word` 添加到数据结构中,之后可以对它进行匹配 +- `bool search(word)` 如果数据结构中存在字符串与 `word` 匹配,则返回 `True`;否则,返回 `False`。`word` 中可能包含一些 `.`,每个 `.` 都可以表示任何一个字母。 + +**说明**: + +- $1 \le word.length \le 25$。 +- `addWord` 中的 `word` 由小写英文字母组成。 +- `search` 中的 `word` 由 `'.'` 或小写英文字母组成。 +- 最多调用 $10^4$ 次 `addWord` 和 `search`。 + +**示例**: + +- 示例 1: + +```python +输入: +["WordDictionary","addWord","addWord","addWord","search","search","search","search"] +[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] +输出: +[null,null,null,null,false,true,true,true] + +解释: +WordDictionary wordDictionary = new WordDictionary(); +wordDictionary.addWord("bad"); +wordDictionary.addWord("dad"); +wordDictionary.addWord("mad"); +wordDictionary.search("pad"); // 返回 False +wordDictionary.search("bad"); // 返回 True +wordDictionary.search(".ad"); // 返回 True +wordDictionary.search("b.."); // 返回 True +``` + +## 解题思路 + +### 思路 1:字典树 + +使用前缀树(字典树)。具体做法如下: + +- 初始化词典对象时,构造一棵字典树。 +- 添加 `word` 时,将 `word` 插入到字典树中。 +- 搜索 `word` 时: + - 如果遇到 `.`,则递归匹配当前节点所有子节点,并依次向下查找。匹配到了,则返回 `True`,否则返回 `False`。 + - 如果遇到其他小写字母,则按 `word` 顺序匹配节点。 + - 如果当前节点为 `word` 的结尾,则放回 `True`。 + +### 思路 1:代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + + def dfs(index, node) -> bool: + if index == len(word): + return node.isEnd + + ch = word[index] + if ch == '.': + for child in node.children.values(): + if child is not None and dfs(index + 1, child): + return True + else: + if ch not in node.children: + return False + child = node.children[ch] + if child is not None and dfs(index + 1, child): + return True + return False + + return dfs(0, self) + + +class WordDictionary: + + def __init__(self): + self.trie_tree = Trie() + + + def addWord(self, word: str) -> None: + self.trie_tree.insert(word) + + + def search(self, word: str) -> bool: + return self.trie_tree.search(word) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:初始化操作为 $O(1)$。添加单词为 $O(|S|)$,搜索单词的平均时间复杂度为 $O(|S|)$,最坏情况下所有字符都是 `'.'`,所以最坏时间复杂度为 $O(|S|^\sum)$。其中 $|S|$ 为单词长度,$\sum$ 为字符集的大小,此处为 $26$。 +- **空间复杂度**:$O(|T| * n)$。其中 $|T|$ 为所有添加单词的最大长度,$n$ 为添加字符串个数。 + diff --git a/docs/solutions/0200-0299/different-ways-to-add-parentheses.md b/docs/solutions/0200-0299/different-ways-to-add-parentheses.md new file mode 100644 index 00000000..da1094fb --- /dev/null +++ b/docs/solutions/0200-0299/different-ways-to-add-parentheses.md @@ -0,0 +1,87 @@ +# [0241. 为运算表达式设计优先级](https://leetcode.cn/problems/different-ways-to-add-parentheses/) + +- 标签:递归、记忆化搜索、数学、字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0241. 为运算表达式设计优先级 - 力扣](https://leetcode.cn/problems/different-ways-to-add-parentheses/) + +## 题目大意 + +**描述**:给定一个由数字和运算符组成的字符串 `expression`。 + +**要求**:按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以按任意顺序返回答案。 + +**说明**: + +- 生成的测试用例满足其对应输出值符合 $32$ 位整数范围,不同结果的数量不超过 $10^4$。 +- $1 \le expression.length \le 20$。 +- `expression` 由数字和算符 `'+'`、`'-'` 和 `'*'` 组成。 +- 输入表达式中的所有整数值在范围 $[0, 99]$。 + +**示例**: + +- 示例 1: + +```python +输入:expression = "2-1-1" +输出:[0,2] +解释: +((2-1)-1) = 0 +(2-(1-1)) = 2 +``` + +- 示例 2: + +```python +输入:expression = "2*3-4*5" +输出:[-34,-14,-10,-10,10] +解释: +(2*(3-(4*5))) = -34 +((2*3)-(4*5)) = -14 +((2*(3-4))*5) = -10 +(2*((3-4)*5)) = -10 +(((2*3)-4)*5) = 10 +``` + +## 解题思路 + +### 思路 1:分治算法 + +给定的字符串 `expression` 只包含有数字和字符,可以写成类似 `x op y` 的形式,其中 $x$、$y$ 为表达式或数字,$op$ 为字符。 + +则我们可以根据字符的位置,将其递归分解为 $x$、$y$ 两个部分,接着分别计算 $x$ 部分的结果与 $y$ 部分的结果。然后再将其合并。 + +### 思路 1:代码 + +```python +class Solution: + def diffWaysToCompute(self, expression: str) -> List[int]: + res = [] + if len(expression) <= 2: + res.append(int(expression)) + return res + + for i in range(len(expression)): + ch = expression[i] + if ch == '+' or ch == '-' or ch == '*': + left_cnts = self.diffWaysToCompute(expression[ :i]) + right_cnts = self.diffWaysToCompute(expression[i + 1:]) + + for left in left_cnts: + for right in right_cnts: + if ch == '+': + res.append(left + right) + elif ch == '-': + res.append(left - right) + else: + res.append(left * right) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(C_n)$,其中 $n$ 为结果数组的大小,$C_n$ 是第 $n$ 个卡特兰数。 +- **空间复杂度**:$O(C_n)$。 diff --git a/docs/solutions/0200-0299/find-median-from-data-stream.md b/docs/solutions/0200-0299/find-median-from-data-stream.md new file mode 100644 index 00000000..dd7c40e8 --- /dev/null +++ b/docs/solutions/0200-0299/find-median-from-data-stream.md @@ -0,0 +1,60 @@ +# [0295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) + +- 标签:设计、双指针、数据流、排序、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0295. 数据流的中位数 - 力扣](https://leetcode.cn/problems/find-median-from-data-stream/) + +## 题目大意 + +要求:设计一个支持一下两种操作的数组结构: + +- `void addNum(int num)`:从数据流中添加一个整数到数据结构中。 +- `double findMedian()`:返回目前所有元素的中位数。 + +## 解题思路 + +使用一个大顶堆 `queMax` 记录大于中位数的数,使用一个小顶堆 `queMin` 小于中位数的数。 + +- 当添加元素数量为偶数: `queMin` 和 `queMax` 中元素数量相同,则中位数为它们队头的平均值。 +- 当添加元素数量为奇数:`queMin` 中的数比 `queMax` 多一个,此时中位数为 `queMin` 的队头。 + +为了满足上述条件,在进行 `addNum` 操作时,我们应当分情况处理: + +- `num > max{queMin}`:此时 `num` 大于中位数,将该数添加到大顶堆 `queMax` 中。新的中位数将大于原来的中位数,所以可能需要将 `queMax` 中的最小数移动到 `queMin` 中。 +- `num ≤ max{queMin}`:此时 `num` 小于中位数,将该数添加到小顶堆 `queMin` 中。新的中位数将小于等于原来的中位数,所以可能需要将 `queMin` 中最大数移动到 `queMax` 中。 + +## 代码 + +```python +import heapq + +class MedianFinder: + + def __init__(self): + """ + initialize your data structure here. + """ + self.queMin = list() + self.queMax = list() + + + def addNum(self, num: int) -> None: + if not self.queMin or num < -self.queMin[0]: + heapq.heappush(self.queMin, -num) + if len(self.queMax) + 1 < len(self.queMin): + heapq.heappush(self.queMax, -heapq.heappop(self.queMin)) + else: + heapq.heappush(self.queMax, num) + if len(self.queMax) > len(self.queMin): + heapq.heappush(self.queMin, -heapq.heappop(self.queMax)) + + + def findMedian(self) -> float: + if len(self.queMin) > len(self.queMax): + return -self.queMin[0] + return (-self.queMin[0] + self.queMax[0]) / 2 +``` + diff --git a/docs/solutions/0200-0299/find-the-duplicate-number.md b/docs/solutions/0200-0299/find-the-duplicate-number.md new file mode 100644 index 00000000..4e2e6bf6 --- /dev/null +++ b/docs/solutions/0200-0299/find-the-duplicate-number.md @@ -0,0 +1,78 @@ +# [0287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) + +- 标签:位运算、数组、双指针、二分查找 +- 难度:中等 + +## 题目链接 + +- [0287. 寻找重复数 - 力扣](https://leetcode.cn/problems/find-the-duplicate-number/) + +## 题目大意 + +**描述**:给定一个包含 $n + 1$ 个整数的数组 $nums$,里边包含的值都在 $1 \sim n$ 之间。可知至少存在一个重复的整数。 + +**要求**:假设 $nums$ 中只存在一个重复的整数,要求找出这个重复的数。 + +**说明**: + +- $1 \le n \le 10^5$。 +- $nums.length == n + 1$。 +- $1 \le nums[i] \le n$。 +- 要求使用空间复杂度为常数级 $O(1)$,时间复杂度小于 $O(n^2)$ 的解决方法。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,4,2,2] +输出:2 +``` + +- 示例 2: + +```python +输入:nums = [3,1,3,4,2] +输出:3 +``` + +## 解题思路 + +### 思路 1:二分查找 + +利用二分查找的思想。 + +1. 使用两个指针 $left$,$right$。$left$ 指向 $1$,$right$ 指向 $n$。 +2. 将区间 $[1, n]$ 分为 $[left, mid]$ 和 $[mid + 1, right]$。 +3. 对于中间数 $mid$,统计 $nums$ 中小于等于 $mid$ 的数个数 $cnt$。 +4. 如果 $cnt \le mid$,则重复数一定不会出现在左侧区间,那么从右侧区间开始搜索。 +5. 如果 $cut > mid$,则重复数出现在左侧区间,则从左侧区间开始搜索。 + +### 思路 1:代码 + +```python +class Solution: + def findDuplicate(self, nums: List[int]) -> int: + n = len(nums) + left = 1 + right = n - 1 + while left < right: + mid = left + (right - left) // 2 + cnt = 0 + for num in nums: + if num <= mid: + cnt += 1 + + if cnt <= mid: + left = mid + 1 + else: + right = mid + + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/first-bad-version.md b/docs/solutions/0200-0299/first-bad-version.md new file mode 100644 index 00000000..f40f0694 --- /dev/null +++ b/docs/solutions/0200-0299/first-bad-version.md @@ -0,0 +1,70 @@ +# [0278. 第一个错误的版本](https://leetcode.cn/problems/first-bad-version/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [0278. 第一个错误的版本 - 力扣](https://leetcode.cn/problems/first-bad-version/) + +## 题目大意 + +**描述**:给你一个整数 $n$,代表已经发布的版本号。还有一个用于检测版本是否出错的接口 `isBadVersion(version):` 。 + +**要求**:找出第一次出错的版本号 $bad$。 + +**说明**: + +- 要求尽可能减少对 `isBadVersion(version):` 接口的调用。 +- $1 \le bad \le n \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 5, bad = 4 +输出:4 +解释: +调用 isBadVersion(3) -> false +调用 isBadVersion(5) -> true +调用 isBadVersion(4) -> true +所以,4 是第一个错误的版本。 +``` + +- 示例 2: + +```python +输入:n = 1, bad = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:二分查找 + +题目要求尽可能减少对 `isBadVersion(version):` 接口的调用,所以不能对每个版本都调用接口,而是应该将接口调用的次数降到最低。 + +可以注意到:如果检测某个版本不是错误版本时,则该版本之前的所有版本都不是错误版本。而当某个版本是错误版本时,则该版本之后的所有版本都是错误版本。我们可以利用这样的性质,在 $[1, n]$ 的区间内使用二分查找方法,从而在 $O(\log n)$ 时间复杂度内找到第一个出错误的版本。 + +### 思路 1:代码 + +```python +class Solution: + def firstBadVersion(self, n): + left = 1 + right = n + while left < right: + mid = (left + right) // 2 + if isBadVersion(mid): + right = mid + else: + left = mid + 1 + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/0200-0299/game-of-life.md b/docs/solutions/0200-0299/game-of-life.md new file mode 100644 index 00000000..a27dd0b6 --- /dev/null +++ b/docs/solutions/0200-0299/game-of-life.md @@ -0,0 +1,113 @@ +# [0289. 生命游戏](https://leetcode.cn/problems/game-of-life/) + +- 标签:数组、矩阵、模拟 +- 难度:中等 + +## 题目链接 + +- [0289. 生命游戏 - 力扣](https://leetcode.cn/problems/game-of-life/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的二维数组 $board$,每一个格子都可以看做是一个细胞。每个细胞都有一个初始状态:$1$ 代表活细胞,$0$ 代表死细胞。每个细胞与其相邻的八个位置(水平、垂直、对角线)细胞遵循以下生存规律: + +- 如果活细胞周围八个位置的活细胞数少于 $2$ 个,则该位置活细胞死亡; +- 如果活细胞周围八个位置有 $2$ 个或 $3$ 个活细胞,则该位置活细胞仍然存活; +- 如果活细胞周围八个位置有超过 $3$ 个活细胞,则该位置活细胞死亡; +- 如果死细胞周围正好有 $3$ 个活细胞,则该位置死细胞复活。 + +二维数组代表的下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的的。其中细胞的出生和死亡是同时发生的。 + +现在给定 $m \times n$ 的二维数组 $board$ 的当前状态。 + +**要求**:返回下一个状态。 + +**说明**: + +- $m == board.length$。 +- $n == board[i].length$。 +- $1 \le m, n \le 25$。 +- $board[i][j]$ 为 $0$ 或 $1$。 +- **进阶**: + - 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。 + - 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题? + + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/12/26/grid1.jpg) + +```python +输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]] +输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/12/26/grid2.jpg) + +```python +输入:board = [[1,1],[1,0]] +输出:[[1,1],[1,1]] +``` + +## 解题思路 + +### 思路 1:模拟 + +因为下一个状态隐含了过去细胞的状态,所以不能直接在原二维数组上直接进行修改。细胞的状态总共有四种情况: + +- 死细胞 -> 死细胞,即 $0 \rightarrow 0$。 +- 死细胞 -> 活细胞,即 $0 \rightarrow 1$。 +- 活细胞 -> 活细胞,即 $1 \rightarrow 1$。 +- 活细胞 -> 死细胞,即 $1 \rightarrow 0$。 + +死细胞 -> 死细胞,活细胞 -> 活细胞,不会对前后状态造成影响,所以主要考虑另外两种情况。我们把活细胞 -> 死细胞暂时标记为 $-1$,并且统计每个细胞周围活细胞数量时,使用绝对值统计,这样 $abs(-1)$ 也可以暂时标记为活细胞。然后把死细胞 -> 活细胞暂时标记为 $2$,这样判断的时候也不会统计上去。然后开始遍历。 + +- 遍历二维数组的每一个位置。并对该位置遍历周围八个位置,计算出八个位置上的活细胞数量。 + - 如果此位置是活细胞,并且周围活细胞少于 $2$ 个或超过 $3$ 个,则将其暂时标记为 $-1$,意为此细胞死亡。 + - 如果此位置是死细胞,并且周围有 $3$ 个活细胞,则将暂时标记为 $2$,意为此细胞复活。 +- 遍历完之后,再次遍历一遍二维数组,如果该位置为 $-1$,将其赋值为 $0$,如果该位置为 $2$,将其赋值为 $1$。 + +### 思路 1:代码 + +```python +class Solution: + def gameOfLife(self, board: List[List[int]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + directions = {(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)} + + rows = len(board) + cols = len(board[0]) + + for row in range(rows): + for col in range(cols): + lives = 0 + for direction in directions: + new_row = row + direction[0] + new_col = col + direction[1] + + if 0 <= new_row < rows and 0 <= new_col < cols and abs(board[new_row][new_col]) == 1: + lives += 1 + if board[row][col] == 1 and (lives < 2 or lives > 3): + board[row][col] = -1 + if board[row][col] == 0 and lives == 3: + board[row][col] = 2 + + for row in range(rows): + for col in range(cols): + if board[row][col] == -1: + board[row][col] = 0 + elif board[row][col] == 2: + board[row][col] = 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为 $board$ 的行数和列数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0200-0299/group-shifted-strings.md b/docs/solutions/0200-0299/group-shifted-strings.md new file mode 100644 index 00000000..b018c4ab --- /dev/null +++ b/docs/solutions/0200-0299/group-shifted-strings.md @@ -0,0 +1,42 @@ +# [0249. 移位字符串分组](https://leetcode.cn/problems/group-shifted-strings/) + +- 标签:数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0249. 移位字符串分组 - 力扣](https://leetcode.cn/problems/group-shifted-strings/) + +## 题目大意 + +给定一个仅包含小写字母的字符串列表。其中每个字符串都可以进行「移位」操作,也就是将字符串中的每个字母变为其在字母表中后续的字母。比如:`abc` -> `bcd`。 + +要求:将该列表中满足「移位」操作规律的组合进行分组并返回。 + +## 解题思路 + +我们可以先将满足相同「移位」操作规律的组合翻译为相同的模式,然后利用哈希表进行存储。哈希表对应关系为 翻译后模式:该模式对应的原字符串列表。 + +## 代码 + +```python +import collections +class Solution: + def groupStrings(self, strings: List[str]) -> List[List[str]]: + str_dict = collections.defaultdict(list) + for string in strings: + if string[0] == 'a': + str_dict[string].append(string) + else: + list_string = list(string) + for i in range(len(list_string)): + num = (ord(list_string[i]) - ord(string[0]) + 26) % 26 + list_string[i] = chr(num + ord('a')) + temp_string = ''.join(list_string) + str_dict[temp_string].append(string) + res = list() + for string, sublist in str_dict.items(): + res.append(sublist) + return res +``` + diff --git a/docs/solutions/0200-0299/happy-number.md b/docs/solutions/0200-0299/happy-number.md new file mode 100644 index 00000000..6d767f65 --- /dev/null +++ b/docs/solutions/0200-0299/happy-number.md @@ -0,0 +1,76 @@ +# [0202. 快乐数](https://leetcode.cn/problems/happy-number/) + +- 标签:哈希表、数学、双指针 +- 难度:简单 + +## 题目链接 + +- [0202. 快乐数 - 力扣](https://leetcode.cn/problems/happy-number/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:判断 $n$ 是否为快乐数。 + +**说明**: + +- 快乐数定义: + + - 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 + - 然后重复这个过程直到这个数变为 $1$,也可能是 无限循环 但始终变不到 $1$。 + - 如果 可以变为 $1$,那么这个数就是快乐数。 +- $1 \le n \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 19 +输出:True +解释: +12 + 92 = 82 +82 + 22 = 68 +62 + 82 = 100 +12 + 02 + 02 = 1 +``` + +- 示例 2: + +```python +输入:n = 2 +输出:False +``` + +## 解题思路 + +### 思路 1:哈希表 / 集合 + +根据题意,不断重复操作,数可能变为 $1$,也可能是无限循环。无限循环其实就相当于链表形成了闭环,可以用哈希表来存储为一位生成的数,每次判断该数是否存在于哈希表中。如果已经出现在哈希表里,则说明进入了无限循环,该数就不是快乐数。如果没有出现则将该数加入到哈希表中,进行下一次计算。不断重复这个过程,直到形成闭环或者变为 $1$。 + +### 思路 1:代码 + +```python +class Solution: + def getNext(self, n: int): + total_sum = 0 + while n > 0: + n, digit = divmod(n, 10) + total_sum += digit ** 2 + return total_sum + + + def isHappy(self, n: int) -> bool: + num_set = set() + while n != 1 and n not in num_set: + num_set.add(n) + n = self.getNext(n) + return n == 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0200-0299/house-robber-ii.md b/docs/solutions/0200-0299/house-robber-ii.md new file mode 100644 index 00000000..33b70033 --- /dev/null +++ b/docs/solutions/0200-0299/house-robber-ii.md @@ -0,0 +1,113 @@ +# [0213. 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0213. 打家劫舍 II - 力扣](https://leetcode.cn/problems/house-robber-ii/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,$num[i]$ 代表第 $i$ 间房屋存放的金额,假设房屋可以围成一圈,最后一间房屋跟第一间房屋可以相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。 + +**要求**:假如你是一名专业的小偷,计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 + +**说明**: + +- $1 \le nums.length \le 100$。 +- $0 \le nums[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,3,2] +输出:3 +解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3,1] +输出:4 +解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题可以看做是「[198. 打家劫舍](https://leetcode.cn/problems/house-robber/)」的升级版。 + +如果房屋数大于等于 $3$ 间,偷窃了第 $1$ 间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第 $1$ 间房屋。 + +假设总共房屋数量为 $size$,这种情况可以转换为分别求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额,然后再取这两种情况下的最大值。而求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额问题就跟「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」所求问题一致了。 + +这里来复习一下「[198. 打家劫舍](https://leetcode.cn/problems/house-robber)」的解题思路。 + +###### 1. 划分阶段 + +按照房屋序号进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。 + +###### 3. 状态转移方程 + +$i$ 间房屋的最后一个房子是 $nums[i - 1]$。 + +如果房屋数大于等于 $2$ 间,则偷窃第 $i - 1$ 间房屋的时候,就有两种状态: + +1. 偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋就不能偷窃了,偷窃的最高金额为:前 $i - 2$ 间房屋的最高总金额 + 第 $i - 1$ 间房屋的金额,即 $dp[i] = dp[i - 2] + nums[i - 1]$; +1. 不偷窃第 $i - 1$ 间房屋,那么第 $i - 2$ 间房屋可以偷窃,偷窃的最高金额为:前 $i - 1$ 间房屋的最高总金额,即 $dp[i] = dp[i - 1]$。 + +然后这两种状态取最大值即可,即状态转移方程为: + +$dp[i] = \begin{cases} nums[0] & i = 1 \cr max(dp[i - 2] + nums[i - 1], dp[i - 1]) & i \ge 2\end{cases}$ + +###### 4. 初始条件 + +- 前 $0$ 间房屋所能偷窃到的最高金额为 $0$,即 $dp[0] = 0$。 +- 前 $1$ 间房屋所能偷窃到的最高金额为 $nums[0]$,即:$dp[1] = nums[0]$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:前 $i$ 间房屋所能偷窃到的最高金额。假设求解 $[0, size - 2]$ 和 $[1, size - 1]$ 范围下( $size$ 为总的房屋数)首尾不相连的房屋所能偷窃的最高金额问题分别为 $ans1$、$ans2$,则最终结果为 $max(ans1, ans2)$。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def helper(self, nums): + size = len(nums) + if size == 0: + return 0 + + dp = [0 for _ in range(size + 1)] + dp[0] = 0 + dp[1] = nums[0] + + for i in range(2, size + 1): + dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1]) + + return dp[size] + + def rob(self, nums: List[int]) -> int: + size = len(nums) + if size == 1: + return nums[0] + + ans1 = self.helper(nums[:size - 1]) + ans2 = self.helper(nums[1:]) + return max(ans1, ans2) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0200-0299/implement-queue-using-stacks.md b/docs/solutions/0200-0299/implement-queue-using-stacks.md new file mode 100644 index 00000000..68a37503 --- /dev/null +++ b/docs/solutions/0200-0299/implement-queue-using-stacks.md @@ -0,0 +1,117 @@ +# [0232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) + +- 标签:栈、设计、队列 +- 难度:简单 + +## 题目链接 + +- [0232. 用栈实现队列 - 力扣](https://leetcode.cn/problems/implement-queue-using-stacks/) + +## 题目大意 + +**要求**:仅使用两个栈实现先入先出队列。 + +要求实现 `MyQueue` 类: + +- `void push(int x)` 将元素 `x` 推到队列的末尾。 +- `int pop()` 从队列的开头移除并返回元素。 +- `int peek()` 返回队列开头的元素。 +- `boolean empty()` 如果队列为空,返回 `True`;否则,返回 `False`。 + +**说明**: + +- 只能使用标准的栈操作 —— 也就是只有 `push to top`, `peek / pop from top`, `size`, 和 `is empty` 操作是合法的。 +- 可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 +- $1 <= x <= 9$。 +- 最多调用 $100$ 次 `push`、`pop`、`peek` 和 `empty`。 +- 假设所有操作都是有效的 (例如,一个空的队列不会调用 `pop` 或者 `peek` 操作)。 +- 进阶:实现每个操作均摊时间复杂度为 `O(1)` 的队列。换句话说,执行 `n` 个操作的总时间复杂度为 `O(n)`,即使其中一个操作可能花费较长时间。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyQueue", "push", "push", "peek", "pop", "empty"] +[[], [1], [2], [], [], []] +输出: +[null, null, null, 1, 1, false] + +解释: +MyQueue myQueue = new MyQueue(); +myQueue.push(1); // queue is: [1] +myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) +myQueue.peek(); // return 1 +myQueue.pop(); // return 1, queue is [2] +myQueue.empty(); // return false +``` + +## 解题思路 + +### 思路 1:双栈 + +使用两个栈,`inStack` 用于输入,`outStack` 用于输出。 + +- `push` 操作:将元素压入 `inStack` 中。 +- `pop` 操作:如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 +- `peek` 操作:和 `pop` 操作类似,只不过最后一步不需要取出顶层元素,直接将其返回即可。 +- `empty` 操作:如果 `inStack` 和 `outStack` 都为空,则队列为空,否则队列不为空。 + +### 思路 1:代码 + +```python +class MyQueue: + + def __init__(self): + self.inStack = [] + self.outStack = [] + """ + Initialize your data structure here. + """ + + + def push(self, x: int) -> None: + self.inStack.append(x) + """ + Push element x to the back of queue. + """ + + + def pop(self) -> int: + if(len(self.outStack) == 0): + while(len(self.inStack) != 0): + self.outStack.append(self.inStack[-1]) + self.inStack.pop() + top = self.outStack[-1] + self.outStack.pop() + return top + """ + Removes the element from in front of queue and returns that element. + """ + + + def peek(self) -> int: + if (len(self.outStack) == 0): + while (len(self.inStack) != 0): + self.outStack.append(self.inStack[-1]) + self.inStack.pop() + top = self.outStack[-1] + return top + """ + Get the front element. + """ + + + def empty(self) -> bool: + return len(self.outStack) == 0 and len(self.inStack) == 0 + """ + Returns whether the queue is empty. + """ +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:`push` 和 `empty` 为 $O(1)$,`pop` 和 `peek` 为均摊 $O(1)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/implement-stack-using-queues.md b/docs/solutions/0200-0299/implement-stack-using-queues.md new file mode 100644 index 00000000..2c8c6116 --- /dev/null +++ b/docs/solutions/0200-0299/implement-stack-using-queues.md @@ -0,0 +1,111 @@ +# [0225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) + +- 标签:栈、设计、队列 +- 难度:简单 + +## 题目链接 + +- [0225. 用队列实现栈 - 力扣](https://leetcode.cn/problems/implement-stack-using-queues/) + +## 题目大意 + +**要求**:仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的四种操作:`push`、`top`、`pop` 和 `empty`。 + +要求实现 `MyStack` 类: + +- `void push(int x)` 将元素 `x` 压入栈顶。 +- `int pop()` 移除并返回栈顶元素。 +- `int top()` 返回栈顶元素。 +- `boolean empty()` 如果栈是空的,返回 `True`;否则,返回 `False`。 + +**说明**: + +- 只能使用队列的基本操作 —— 也就是 `push to back`、`peek/pop from front`、`size` 和 `is empty` 这些操作。 +- 所使用的语言也许不支持队列。 你可以使用 `list` (列表)或者 `deque`(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyStack", "push", "push", "top", "pop", "empty"] +[[], [1], [2], [], [], []] +输出: +[null, null, null, 2, 2, false] + +解释: +MyStack myStack = new MyStack(); +myStack.push(1); +myStack.push(2); +myStack.top(); // 返回 2 +myStack.pop(); // 返回 2 +myStack.empty(); // 返回 False +``` + +## 解题思路 + +### 思路 1:双队列 + +使用两个队列。`pushQueue` 用作入栈,`popQueue` 用作出栈。 + +- `push` 操作:将新加入的元素压入 `pushQueue` 队列中,并且将之前保存在 `popQueue` 队列中的元素从队头开始依次压入 `pushQueue` 中,此时 `pushQueue` 队列中头节点存放的是新加入的元素,尾部存放的是之前的元素。 而 `popQueue` 则为空。再将 `pushQueue` 和 `popQueue` 相互交换,保持 `pushQueue` 为空,`popQueue` 则用于 `pop`、`top` 等操作。 +- `pop` 操作:直接将 `popQueue` 队头元素取出。 +- `top` 操作:返回 `popQueue` 队头元素。 +- `empty`:判断 `popQueue` 是否为空。 + +### 思路 1:代码 + +```python +class MyStack: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.pushQueue = collections.deque() + self.popQueue = collections.deque() + + + def push(self, x: int) -> None: + """ + Push element x onto stack. + """ + self.pushQueue.append(x) + while self.popQueue: + self.pushQueue.append(self.popQueue.popleft()) + self.pushQueue, self.popQueue = self.popQueue, self.pushQueue + + def pop(self) -> int: + """ + Removes the element on top of the stack and returns that element. + """ + return self.popQueue.popleft() + + + def top(self) -> int: + """ + Get the top element. + """ + return self.popQueue[0] + + + def empty(self) -> bool: + """ + Returns whether the stack is empty. + """ + return not self.popQueue + + +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:入栈操作的时间复杂度为 $O(n)$。出栈、取栈顶元素、判断栈是否为空的时间复杂度为 $O(1)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0200-0299/implement-trie-prefix-tree.md b/docs/solutions/0200-0299/implement-trie-prefix-tree.md new file mode 100644 index 00000000..34144ed7 --- /dev/null +++ b/docs/solutions/0200-0299/implement-trie-prefix-tree.md @@ -0,0 +1,113 @@ +# [0208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0208. 实现 Trie (前缀树) - 力扣](https://leetcode.cn/problems/implement-trie-prefix-tree/) + +## 题目大意 + +**要求**:实现前缀树数据结构的相关类 `Trie` 类。 + +`Trie` 类: + +- `Trie()` 初始化前缀树对象。 +- `void insert(String word)` 向前缀树中插入字符串 `word`。 +- `boolean search(String word)` 如果字符串 `word` 在前缀树中,返回 `True`(即,在检索之前已经插入);否则,返回 `False`。 +- `boolean startsWith(String prefix)` 如果之前已经插入的字符串 `word` 的前缀之一为 `prefix`,返回 `True`;否则,返回 `False`。 + +**说明**: + +- $1 \le word.length, prefix.length \le 2000$。 +- `word` 和 `prefix` 仅由小写英文字母组成。 +- `insert`、`search` 和 `startsWith` 调用次数 **总计** 不超过 $3 * 10^4$ 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["Trie", "insert", "search", "search", "startsWith", "insert", "search"] +[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] +输出: +[null, null, true, false, true, null, true] + +解释: +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 +``` + +## 解题思路 + +### 思路 1:前缀树(字典树) + +前缀树(字典树)是一棵多叉树,其中每个节点包含指向子节点的指针数组 `children`,以及布尔变量 `isEnd`。`children` 用于存储当前字符节点,一般长度为所含字符种类个数,也可以使用哈希表代替指针数组。`isEnd` 用于判断该节点是否为字符串的结尾。 + +下面依次讲解插入、查找前缀的具体步骤: + +**插入字符串**: + +- 从根节点开始插入字符串。对于待插入的字符,有两种情况: + - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续处理下一个字符。 + - 如果该字符对应的节点不存在,则创建一个新的节点,保存在 `children` 中对应位置上,然后沿着指针移动到子节点,继续处理下一个字符。 +- 重复上述步骤,直到最后一个字符,然后将该节点标记为字符串的结尾。 + +**查找前缀**: + +- 从根节点开始查找前缀,对于待查找的字符,有两种情况: + - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续查找下一个字符。 + - 如果该字符对应的节点不存在,则说明字典树中不包含该前缀,直接返回空指针。 +- 重复上述步骤,直到最后一个字符搜索完毕,则说明字典树中存在该前缀。 + +### 思路 1:代码 + +```python +class Node: + def __init__(self): + self.children = dict() + self.isEnd = False + +class Trie: + + def __init__(self): + self.root = Node() + + def insert(self, word: str) -> None: + cur = self.root + for ch in word: + if ch not in cur.children: + cur.children[ch] = Node() + cur = cur.children[ch] + cur.isEnd = True + + def search(self, word: str) -> bool: + cur = self.root + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + + def startsWith(self, prefix: str) -> bool: + cur = self.root + for ch in prefix: + if ch not in cur.children: + return False + cur = cur.children[ch] + return cur is not None +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:初始化为 $O(1)$。插入操作、查找操作的时间复杂度为 $O(|S|)$。其中 $|S|$ 是每次插入或查找字符串的长度。 +- **空间复杂度**:$O(|T| \times \sum)$。其中 $|T|$ 是所有插入字符串的长度之和,$\sum$ 是字符集的大小。 + diff --git a/docs/solutions/0200-0299/index.md b/docs/solutions/0200-0299/index.md new file mode 100644 index 00000000..ebd58d96 --- /dev/null +++ b/docs/solutions/0200-0299/index.md @@ -0,0 +1,60 @@ +## 本章内容 + +- [0200. 岛屿数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-islands.md) +- [0201. 数字范围按位与](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/bitwise-and-of-numbers-range.md) +- [0202. 快乐数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/happy-number.md) +- [0203. 移除链表元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/remove-linked-list-elements.md) +- [0204. 计数质数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-primes.md) +- [0205. 同构字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/isomorphic-strings.md) +- [0206. 反转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/reverse-linked-list.md) +- [0207. 课程表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule.md) +- [0208. 实现 Trie (前缀树)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-trie-prefix-tree.md) +- [0209. 长度最小的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/minimum-size-subarray-sum.md) +- [0210. 课程表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/course-schedule-ii.md) +- [0211. 添加与搜索单词 - 数据结构设计](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/design-add-and-search-words-data-structure.md) +- [0212. 单词搜索 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/word-search-ii.md) +- [0213. 打家劫舍 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/house-robber-ii.md) +- [0215. 数组中的第K个最大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/kth-largest-element-in-an-array.md) +- [0217. 存在重复元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate.md) +- [0218. 天际线问题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/the-skyline-problem.md) +- [0219. 存在重复元素 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-ii.md) +- [0220. 存在重复元素 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/contains-duplicate-iii.md) +- [0221. 最大正方形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/maximal-square.md) +- [0222. 完全二叉树的节点个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/count-complete-tree-nodes.md) +- [0223. 矩形面积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/rectangle-area.md) +- [0225. 用队列实现栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-stack-using-queues.md) +- [0226. 翻转二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/invert-binary-tree.md) +- [0227. 基本计算器 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/basic-calculator-ii.md) +- [0231. 2 的幂](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/power-of-two.md) +- [0232. 用栈实现队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/implement-queue-using-stacks.md) +- [0233. 数字 1 的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/number-of-digit-one.md) +- [0234. 回文链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/palindrome-linked-list.md) +- [0235. 二叉搜索树的最近公共祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md) +- [0236. 二叉树的最近公共祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md) +- [0237. 删除链表中的节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/delete-node-in-a-linked-list.md) +- [0238. 除自身以外数组的乘积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/product-of-array-except-self.md) +- [0239. 滑动窗口最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/sliding-window-maximum.md) +- [0240. 搜索二维矩阵 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/search-a-2d-matrix-ii.md) +- [0241. 为运算表达式设计优先级](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/different-ways-to-add-parentheses.md) +- [0242. 有效的字母异位词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/valid-anagram.md) +- [0249. 移位字符串分组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/group-shifted-strings.md) +- [0257. 二叉树的所有路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/binary-tree-paths.md) +- [0258. 各位相加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/add-digits.md) +- [0259. 较小的三数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/3sum-smaller.md) +- [0260. 只出现一次的数字 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/single-number-iii.md) +- [0263. 丑数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/ugly-number.md) +- [0264. 丑数 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/ugly-number-ii.md) +- [0268. 丢失的数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/missing-number.md) +- [0270. 最接近的二叉搜索树值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/closest-binary-search-tree-value.md) +- [0278. 第一个错误的版本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/first-bad-version.md) +- [0279. 完全平方数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/perfect-squares.md) +- [0283. 移动零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/move-zeroes.md) +- [0285. 二叉搜索树中的中序后继](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/inorder-successor-in-bst.md) +- [0286. 墙与门](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/walls-and-gates.md) +- [0287. 寻找重复数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-the-duplicate-number.md) +- [0288. 单词的唯一缩写](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/unique-word-abbreviation.md) +- [0289. 生命游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/game-of-life.md) +- [0290. 单词规律](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/word-pattern.md) +- [0292. Nim 游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/nim-game.md) +- [0295. 数据流的中位数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/find-median-from-data-stream.md) +- [0297. 二叉树的序列化与反序列化](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md) diff --git a/docs/solutions/0200-0299/inorder-successor-in-bst.md b/docs/solutions/0200-0299/inorder-successor-in-bst.md new file mode 100644 index 00000000..3ad6e959 --- /dev/null +++ b/docs/solutions/0200-0299/inorder-successor-in-bst.md @@ -0,0 +1,54 @@ +# [0285. 二叉搜索树中的中序后继](https://leetcode.cn/problems/inorder-successor-in-bst/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0285. 二叉搜索树中的中序后继 - 力扣](https://leetcode.cn/problems/inorder-successor-in-bst/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root`。 + +要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 + +## 解题思路 + +可以分为两步: + +1. 中序遍历二叉搜索树,将节点先存储到列表中。 +2. 将列表中的节点构造成一棵递增顺序搜索树。 + +中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 + +构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 + +最后返回 `head.right` 即可。 + +## 代码 + +```python +class Solution: + def inOrder(self, root, res): + if not root: + return + self.inOrder(root.left, res) + res.append(root) + self.inOrder(root.right, res) + + def increasingBST(self, root: TreeNode) -> TreeNode: + res = [] + self.inOrder(root, res) + + if not res: + return + head = TreeNode(-1) + cur = head + for node in res: + node.left = node.right = None + cur.right = node + cur = cur.right + return head.right +``` + diff --git a/docs/solutions/0200-0299/invert-binary-tree.md b/docs/solutions/0200-0299/invert-binary-tree.md new file mode 100644 index 00000000..847a11af --- /dev/null +++ b/docs/solutions/0200-0299/invert-binary-tree.md @@ -0,0 +1,90 @@ +# [0226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0226. 翻转二叉树 - 力扣](https://leetcode.cn/problems/invert-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:将该二叉树进行左右翻转。 + +**说明**: + +- 树中节点数目范围在 $[0, 100]$ 内。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/14/invert1-tree.jpg) + +```python +输入:root = [4,2,7,1,3,6,9] +输出:[4,7,2,9,6,3,1] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/14/invert2-tree.jpg) + +```python +输入:root = [2,1,3] +输出:[2,3,1] +``` + +## 解题思路 + +### 思路 1:递归遍历 + +根据我们的递推三步走策略,写出对应的递归代码。 + +1. 写出递推公式: + + 1. 递归遍历翻转左子树。 + 2. 递归遍历翻转右子树。 + 3. 交换当前根节点 `root` 的左右子树。 + +2. 明确终止条件:当前节点 `root` 为 `None`。 + +3. 翻译为递归代码: + 1. 定义递归函数:`invertTree(self, root)` 表示输入参数为二叉树的根节点 `root`,返回结果为翻转后二叉树的根节点。 + + 2. 书写递归主体: + + ```python + left = self.invertTree(root.left) + right = self.invertTree(root.right) + root.left = right + root.right = left + return root + ``` + + 3. 明确递归终止条件:`if not root: return None` + +4. 返回根节点 `root`。 + +### 思路 1:代码 + +```python +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + if not root: + return None + left = self.invertTree(root.left) + right = self.invertTree(root.right) + root.left = right + root.right = left + return root +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0200-0299/isomorphic-strings.md b/docs/solutions/0200-0299/isomorphic-strings.md new file mode 100644 index 00000000..3b1be027 --- /dev/null +++ b/docs/solutions/0200-0299/isomorphic-strings.md @@ -0,0 +1,67 @@ +# [0205. 同构字符串](https://leetcode.cn/problems/isomorphic-strings/) + +- 标签:哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0205. 同构字符串 - 力扣](https://leetcode.cn/problems/isomorphic-strings/) + +## 题目大意 + +**描述**:给定两个字符串 $s$ 和 $t$。 + +**要求**:判断字符串 $s$ 和 $t$ 是否是同构字符串。 + +**说明**: + +- **同构字符串**:如果 $s$ 中的字符可以按某种映射关系替换得到 $t$ 相同位置上的字符,那么两个字符串是同构的。 +- 每个字符都应当映射到另一个字符,且不改变字符顺序。不同字符不能映射到统一字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。 +- $1 \le s.length \le 5 \times 10^4$。 +- $t.length == s.length$。 +- $s$ 和 $t$ 由任意有效的 ASCII 字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "egg", t = "add" +输出:True +``` + +- 示例 2: + +```python +输入:s = "foo", t = "bar" +输出:False +``` + +## 解题思路 + +### 思路 1:哈希表 + +根据题目意思,字符串 $s$ 和 $t$ 每个位置上的字符是一一对应的。$s$ 的每个字符都与 $t$ 对应位置上的字符对应。可以考虑用哈希表来存储 $s[i]: t[i]$ 的对应关系。但是这样不能只能保证对应位置上的字符是对应的,但不能保证是唯一对应的。所以还需要另一个哈希表来存储 $t[i]:s[i]$ 的对应关系来判断是否是唯一对应的。 + +### 思路 1:代码 + +```python +class Solution: + def isIsomorphic(self, s: str, t: str) -> bool: + s_dict = dict() + t_dict = dict() + for i in range(len(s)): + if s[i] in s_dict and s_dict[s[i]] != t[i]: + return False + if t[i] in t_dict and t_dict[t[i]] != s[i]: + return False + s_dict[s[i]] = t[i] + t_dict[t[i]] = s[i] + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串长度。 +- **空间复杂度**:$O(|S|)$ ,其中 $S$ 是字符串字符集。 + diff --git a/docs/solutions/0200-0299/kth-largest-element-in-an-array.md b/docs/solutions/0200-0299/kth-largest-element-in-an-array.md new file mode 100644 index 00000000..42b08315 --- /dev/null +++ b/docs/solutions/0200-0299/kth-largest-element-in-an-array.md @@ -0,0 +1,220 @@ +# [0215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) + +- 标签:数组、分治、快速排序、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0215. 数组中的第K个最大元素 - 力扣](https://leetcode.cn/problems/kth-largest-element-in-an-array/) + +## 题目大意 + +**描述**:给定一个未排序的整数数组 $nums$ 和一个整数 $k$。 + +**要求**:返回数组中第 $k$ 个最大的元素。 + +**说明**: + +- 要求使用时间复杂度为 $O(n)$ 的算法解决此问题。 +- $1 \le k \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入: [3,2,1,5,6,4], k = 2 +输出: 5 +``` + +- 示例 2: + +```python +输入: [3,2,3,1,2,4,5,5,6], k = 4 +输出: 4 +``` + +## 解题思路 + +很不错的一道题,面试常考。 + +直接可以想到的思路是:排序后输出数组上对应第 $k$ 位大的数。所以问题关键在于排序方法的复杂度。 + +冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,很容易超时。 + +可考虑堆排序、归并排序、快速排序。 + +这道题的要求是找到第 $k$ 大的元素,使用归并排序只有到最后排序完毕才能返回第 $k$ 大的数。而堆排序每次排序之后,就会确定一个元素的准确排名,同理快速排序也是如此。 + +### 思路 1:堆排序 + +升序堆排序的思路如下: + +1. 将无序序列构造成第 $1$ 个大顶堆(初始堆),使得 $n$ 个元素的最大值处于序列的第 $1$ 个位置。 + +2. **调整堆**:交换序列的第 $1$ 个元素(最大值元素)与第 $n$ 个元素的位置。将序列前 $n - 1$ 个元素组成的子序列调整成一个新的大顶堆,使得 $n - 1$ 个元素的最大值处于序列第 $1$ 个位置,从而得到第 $2$ 个最大值元素。 + +3. **调整堆**:交换子序列的第 $1$ 个元素(最大值元素)与第 $n - 1$ 个元素的位置。将序列前 $n - 2$ 个元素组成的子序列调整成一个新的大顶堆,使得 $n - 2$ 个元素的最大值处于序列第 $1$ 个位置,从而得到第 $3$ 个最大值元素。 + +4. 依次类推,不断交换子序列的第 $1$ 个元素(最大值元素)与当前子序列最后一个元素位置,并将其调整成新的大顶堆。直到获取第 $k$ 个最大值元素为止。 + + +### 思路 1:代码 + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + # 调整为大顶堆 + def heapify(nums, index, end): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if nums[left] > nums[max_index]: + max_index = left + if right <= end and nums[right] > nums[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(nums): + size = len(nums) + # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + heapify(nums, i, size - 1) + return nums + + buildMaxHeap(nums) + size = len(nums) + for i in range(k-1): + nums[0], nums[size-i-1] = nums[size-i-1], nums[0] + heapify(nums, 0, size-i-2) + return nums[0] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:快速排序 + +使用快速排序在每次调整时,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了左右两个子数组,左子数组中的元素都比该元素小,右子树组中的元素都比该元素大。 + +这样,只要某次划分的元素恰好是第 $k$ 个下标就找到了答案。并且我们只需关注第 $k$ 个最大元素所在区间的排序情况,与第 $k$ 个最大元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 + +### 思路 2:代码 + +```python +import random + +class Solution: + # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 + def randomPartition(self, nums: [int], low: int, high: int) -> int: + # 随机挑选一个基准数 + i = random.randint(low, high) + # 将基准数与最低位互换 + nums[i], nums[low] = nums[low], nums[i] + # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + return self.partition(nums, low, high) + + # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 + def partition(self, nums: [int], low: int, high: int) -> int: + # 以第 1 位元素为基准数 + pivot = nums[low] + + i, j = low, high + while i < j: + # 从右向左找到第 1 个小于基准数的元素 + while i < j and nums[j] >= pivot: + j -= 1 + # 从左向右找到第 1 个大于基准数的元素 + while i < j and nums[i] <= pivot: + i += 1 + # 交换元素 + nums[i], nums[j] = nums[j], nums[i] + + # 将基准数放到正确位置上 + nums[j], nums[low] = nums[low], nums[j] + return j + + def quickSort(self, nums: [int], low: int, high: int, k: int, size: int) -> [int]: + if low < high: + # 按照基准数的位置,将数组划分为左右两个子数组 + pivot_i = self.randomPartition(nums, low, high) + if pivot_i == size - k: + return nums[size - k] + if pivot_i > size - k: + self.quickSort(nums, low, pivot_i - 1, k, size) + if pivot_i < size - k: + self.quickSort(nums, pivot_i + 1, high, k, size) + + return nums[size - k] + + + def findKthLargest(self, nums: List[int], k: int) -> int: + size = len(nums) + return self.quickSort(nums, 0, len(nums) - 1, k, size) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。证明过程可参考「算法导论 9.2:期望为线性的选择算法」。 +- **空间复杂度**:$O(\log n)$。递归使用栈空间的空间代价期望为 $O(\log n)$。 + +### 思路 3:借用标准库(不建议) + +提交代码中的最快代码是调用了 Python 的 `sort` 方法。这种做法适合在打算法竞赛的时候节省时间,日常练习可以尝试一下自己写。 + +### 思路 3:代码 + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + nums.sort() + return nums[len(nums) - k] +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 4:优先队列 + +1. 遍历数组元素,对于当前元素 $num$: + 1. 如果优先队列中的元素个数小于 $k$ 个,则将当前元素 $num$ 放入优先队列中。 + 2. 如果优先队列中的元素个数大于等于 $k$ 个,并且当前元素 $num$ 大于优先队列的队头元素,则弹出队头元素,并将当前元素 $num$ 插入到优先队列中。 +2. 遍历完,此时优先队列的队头元素就是第 $k$ 个最大元素,将其弹出并返回即可。 + +这里我们借助了 Python 中的 `heapq` 模块实现优先队列算法,这一步也可以通过手写堆的方式实现优先队列。 + +### 思路 4:代码 + +```python +import heapq +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + res = [] + for num in nums: + if len(res) < k: + heapq.heappush(res, num) + elif num > res[0]: + heapq.heappop(res) + heapq.heappush(res, num) + return heapq.heappop(res) +``` + +### 思路 4:复杂度分析 + +- **时间复杂度**:$O(n \times \log k)$。 +- **空间复杂度**:$O(k)$。 diff --git a/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md b/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md new file mode 100644 index 00000000..8a9d081a --- /dev/null +++ b/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-search-tree.md @@ -0,0 +1,75 @@ +# [0235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0235. 二叉搜索树的最近公共祖先 - 力扣](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) + +## 题目大意 + +**描述**:给定一个二叉搜索树的根节点 `root`,以及两个指定节点 `p` 和 `q`。 + +**要求**:找到该树中两个指定节点的最近公共祖先。 + +**说明**: + +- **祖先**:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 +- **最近公共祖先**:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 +- 所有节点的值都是唯一的。 +- `p`、`q` 为不同节点且均存在于给定的二叉搜索树中。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/binarysearchtree_improved.png) + +```python +输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 +输出: 6 +解释: 节点 2 和节点 8 的最近公共祖先是 6。 +``` + +- 示例 2: + +```python +输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 +输出: 2 +解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 +``` + +## 解题思路 + +### 思路 1:递归遍历 + +对于节点 `p`、节点 `q`,最近公共祖先就是从根节点分别到它们路径上的分岔点,也是路径中最后一个相同的节点,现在我们的问题就是求这个分岔点。 + +我们可以使用递归遍历查找二叉搜索树的最近公共祖先,具体方法如下。 + +1. 从根节点 `root` 开始遍历。 +2. 如果当前节点的值大于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的左子树,因此将当前节点移动到它的左子节点,继续遍历; +3. 如果当前节点的值小于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的右子树,因此将当前节点移动到它的右子节点,继续遍历; +4. 如果当前节点不满足上面两种情况,则说明 `p` 和 `q` 分别在当前节点的左右子树上,则当前节点就是分岔点,直接返回该节点即可。 + +### 思路 1:代码 + +```python +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + ancestor = root + while True: + if ancestor.val > p.val and ancestor.val > q.val: + ancestor = ancestor.left + elif ancestor.val < p.val and ancestor.val < q.val: + ancestor = ancestor.right + else: + break + return ancestor +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点个数。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md b/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md new file mode 100644 index 00000000..89082b1e --- /dev/null +++ b/docs/solutions/0200-0299/lowest-common-ancestor-of-a-binary-tree.md @@ -0,0 +1,98 @@ +# [0236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0236. 二叉树的最近公共祖先 - 力扣](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`,以及二叉树中两个节点 `p` 和 `q`。 + +**要求**:找到该二叉树中指定节点 `p`、`q` 的最近公共祖先。 + +**说明**: + +- **祖先**:如果节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 +- **最近公共祖先**:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 +- 树中节点数目在范围 $[2, 10^5]$ 内。 +- $-10^9 \le Node.val \le 10^9$。 +- 所有 `Node.val` 互不相同。 +- `p != q`。 +- `p` 和 `q` 均存在于给定的二叉树中。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) + +```python +输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +输出:3 +解释:节点 5 和节点 1 的最近公共祖先是节点 3 。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) + +```python +输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +输出:5 +解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。 +``` + +## 解题思路 + +### 思路 1:递归遍历 + +设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: + +1. `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 +2. `p == lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 +3. `q == lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 + +下面递归求解 `lca_node`。递归需要满足以下条件: + +- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 +- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 +- 如果 `p`、`q` 都不存在,则返回 `None`。 + +具体思路为: + +1. 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node`。 +2. 如果当前节点 `node` 不为 `None`,则递归遍历左子树、右子树,并判断左右子树结果。 + 1. 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 + 2. 如果左子树为空,则返回右子树。 + 3. 如果右子树为空,则返回左子树。 + 4. 如果左右子树都为空,则返回 `None`。 +3. 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 + +### 思路 1:代码 + +```python +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if root == p or root == q: + return root + + if root: + node_left = self.lowestCommonAncestor(root.left, p, q) + node_right = self.lowestCommonAncestor(root.right, p, q) + if node_left and node_right: + return root + elif not node_left: + return node_right + else: + return node_left + return None +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/maximal-square.md b/docs/solutions/0200-0299/maximal-square.md new file mode 100644 index 00000000..13ca0919 --- /dev/null +++ b/docs/solutions/0200-0299/maximal-square.md @@ -0,0 +1,92 @@ +# [0221. 最大正方形](https://leetcode.cn/problems/maximal-square/) + +- 标签:数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [0221. 最大正方形 - 力扣](https://leetcode.cn/problems/maximal-square/) + +## 题目大意 + +**描述**:给定一个由 `'0'` 和 `'1'` 组成的二维矩阵 $matrix$。 + +**要求**:找到只包含 `'1'` 的最大正方形,并返回其面积。 + +**说明**: + +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 300$。 +- $matrix[i][j]$ 为 `'0'` 或 `'1'`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/26/max1grid.jpg) + +```python +输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] +输出:4 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/26/max2grid.jpg) + +```python +输入:matrix = [["0","1"],["1","0"]] +输出:1 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照正方形的右下角坐标进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。 + +###### 3. 状态转移方程 + +只有当矩阵位置 $(i, j)$ 值为 $1$ 时,才有可能存在正方形。 + +- 如果矩阵位置 $(i, j)$ 上值为 $0$,则 $dp[i][j] = 0$。 +- 如果矩阵位置 $(i, j)$ 上值为 $1$,则 $dp[i][j]$ 的值由该位置上方、左侧、左上方三者共同约束的,为三者中最小值加 $1$。即:$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1$。 + +###### 4. 初始条件 + +- 默认所有以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长都为 $0$,即 $dp[i][j] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[i][j]$ 表示为:以矩阵位置 $(i, j)$ 为右下角,且值包含 $1$ 的正方形的最大边长。则最终结果为所有 $dp[i][j]$ 中的最大值。 + +### 思路 1:代码 + +```python +class Solution: + def maximalSquare(self, matrix: List[List[str]]) -> int: + rows, cols = len(matrix), len(matrix[0]) + max_size = 0 + dp = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] + for i in range(rows): + for j in range(cols): + if matrix[i][j] == '1': + if i == 0 or j == 0: + dp[i][j] = 1 + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + max_size = max(max_size, dp[i][j]) + return max_size * max_size +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为二维矩阵 $matrix$ 的行数和列数。 +- **空间复杂度**:$O(m \times n)$。 diff --git a/docs/solutions/0200-0299/minimum-size-subarray-sum.md b/docs/solutions/0200-0299/minimum-size-subarray-sum.md new file mode 100644 index 00000000..9f6e11f9 --- /dev/null +++ b/docs/solutions/0200-0299/minimum-size-subarray-sum.md @@ -0,0 +1,81 @@ +# [0209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) + +- 标签:数组、二分查找、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0209. 长度最小的子数组 - 力扣](https://leetcode.cn/problems/minimum-size-subarray-sum/) + +## 题目大意 + +**描述**:给定一个只包含正整数的数组 $nums$ 和一个正整数 $target$。 + +**要求**:找出数组中满足和大于等于 $target$ 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 $0$。 + +**说明**: + +- $1 \le target \le 10^9$。 +- $1 \le nums.length \le 10^5$。 +- $1 \le nums[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:target = 7, nums = [2,3,1,2,4,3] +输出:2 +解释:子数组 [4,3] 是该条件下的长度最小的子数组。 +``` + +- 示例 2: + +```python +输入:target = 4, nums = [1,4,4] +输出:1 +``` + +## 解题思路 + +### 思路 1:滑动窗口(不定长度) + +最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 + +用滑动窗口来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 $target$。 + +1. 一开始,$left$、$right$ 都指向 $0$。 +2. 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{\hspace{0.5em}}sum$ 中。 +3. 如果 $window\underline{\hspace{0.5em}}sum \ge target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{\hspace{0.5em}}sum < target$。 +4. 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +5. 输出窗口和的最小值作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def minSubArrayLen(self, target: int, nums: List[int]) -> int: + size = len(nums) + ans = size + 1 + left = 0 + right = 0 + window_sum = 0 + + while right < size: + window_sum += nums[right] + + while window_sum >= target: + ans = min(ans, right - left + 1) + window_sum -= nums[left] + left += 1 + + right += 1 + + return ans if ans != size + 1 else 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/missing-number.md b/docs/solutions/0200-0299/missing-number.md new file mode 100644 index 00000000..a74594af --- /dev/null +++ b/docs/solutions/0200-0299/missing-number.md @@ -0,0 +1,86 @@ +# [0268. 丢失的数字](https://leetcode.cn/problems/missing-number/) + +- 标签:位运算、数组、哈希表、数学、二分查找、排序 +- 难度:简单 + +## 题目链接 + +- [0268. 丢失的数字 - 力扣](https://leetcode.cn/problems/missing-number/) + +## 题目大意 + +**描述**:给定一个包含 $[0, n]$ 中 $n$ 个数的数组 $nums$。 + +**要求**:找出 $[0, n]$ 这个范围内没有出现在数组中的那个数。 + +**说明**: + +- $n == nums.length$ +- $1 \le n \le 10^4$ +- $0 \le nums[i] \le n$。 +- $nums$ 中的所有数字都独一无二。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,0,1] +输出:2 +解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。 +``` + +- 示例 2: + +```python +输入:nums = [0,1] +输出:2 +解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。 +``` + +## 解题思路 + +$[0, n]$ 的范围有 $n + 1$ 个数(包含 $0$)。现在给了我们 $n$ 个数,要求找出其中缺失的那个数。 + +### 思路 1:哈希表 + +将 $nums$ 中所有元素插入到哈希表中,然后遍历 $[0, n]$,找到缺失的数字。 + +这里的哈希表也可以用长度为 $n + 1$ 的数组代替。 + +### 思路 1:代码 + +```python +class Solution: + def missingNumber(self, nums: List[int]) -> int: + numSet = set(nums) + + for num in range(len(nums)+1): + if num not in numSet: + return num +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:数学计算 + +已知 $[0, n]$ 的求和公式为:$\sum_{i=0}^n i = \frac{n*(n+1)}{2}$,则用 $[0, n]$ 的和,减去数组中所有元素的和,就得到了缺失数字。 + +### 思路 2:代码 + +```python +class Solution: + def missingNumber(self, nums: List[int]) -> int: + sum_nums = sum(nums) + n = len(nums) + return (n + 1) * n // 2 - sum_nums +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/move-zeroes.md b/docs/solutions/0200-0299/move-zeroes.md new file mode 100644 index 00000000..44cea885 --- /dev/null +++ b/docs/solutions/0200-0299/move-zeroes.md @@ -0,0 +1,90 @@ +# [0283. 移动零](https://leetcode.cn/problems/move-zeroes/) + +- 标签:数组、双指针 +- 难度:简单 + +## 题目链接 + +- [0283. 移动零 - 力扣](https://leetcode.cn/problems/move-zeroes/) + +## 题目大意 + +**描述**:给定一个数组 $nums$。 + +**要求**:将所有 $0$ 移动到末尾,并保持原有的非 $0$ 数字的相对顺序。 + +**说明**: + +- 只能在原数组上进行操作。 +- $1 \le nums.length \le 10^4$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [0,1,0,3,12] +输出: [1,3,12,0,0] +``` + +- 示例 2: + +```python +输入: nums = [0] +输出: [0] +``` + +## 解题思路 + +### 思路 1:冒泡排序(超时) + +冒泡排序的思想,就是通过相邻元素的比较与交换,使得较大元素从前面移到后面。 + +我们可以借用冒泡排序的思想,将值为 $0$ 的元素移动到数组末尾。 + +因为数据规模为 $10^4$,而冒泡排序的时间复杂度为 $O(n^2)$。所以这种做法会导致超时。 + +### 思路 1:代码 + +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + for i in range(len(nums)): + for j in range(len(nums) - i - 1): + if nums[j] == 0 and nums[j + 1] != 0: + nums[j], nums[j + 1] = nums[j + 1], nums[j] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:快慢指针 + +1. 使用两个指针 $slow$,$fast$。$slow$ 指向处理好的非 $0$ 数字数组的尾部,$fast$ 指针指向当前待处理元素。 +2. 不断向右移动 $fast$ 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 $slow$ 右移。 +3. 此时,$slow$ 指针左侧均为处理好的非零数,而从 $slow$ 指针指向的位置开始, $fast$ 指针左边为止都为 $0$。 + +遍历结束之后,则所有 $0$ 都移动到了右侧,且保持了非零数的相对位置。 + +### 思路 2:代码 + +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + slow = 0 + fast = 0 + while fast < len(nums): + if nums[fast] != 0: + nums[slow], nums[fast] = nums[fast], nums[slow] + slow += 1 + fast += 1 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/nim-game.md b/docs/solutions/0200-0299/nim-game.md new file mode 100644 index 00000000..0c1922df --- /dev/null +++ b/docs/solutions/0200-0299/nim-game.md @@ -0,0 +1,41 @@ +# [0292. Nim 游戏](https://leetcode.cn/problems/nim-game/) + +- 标签:脑筋急转弯、数学、博弈 +- 难度:简单 + +## 题目链接 + +- [0292. Nim 游戏 - 力扣](https://leetcode.cn/problems/nim-game/) + +## 题目大意 + +两个人玩 Nim 游戏。游戏规则是这样的: + +- 桌上有一堆石子,两个人轮流从石子堆中拿走 1~3 块石头。拿掉最后一块石头的人就是获胜者。 + +- 假如每个人都尽可能的想赢得比赛,所以每一轮都是最优解。 + +现在给定一个整数 n 代表石头数目。如果你作为先手,问最终能否赢得比赛。 + +## 解题思路 + +假设石子的数量为 1~3,那么我作为先手,肯定第一次就将所有的石子都拿完了,所以肯定能赢。 + +假设石子的数量为 4,那么我作为先手,无论第一次拿走 1、2、3 块石头,都不能拿完,而第二个人再拿的时候,会直接将剩下的石头一次性全拿走,所以肯定不会赢。 + +如果石子数量多于 4,那么我作为先手,为了赢,应该尽可能使得本轮拿走后的石子数为 4,这样对手拿完一次之后,自己肯定会获胜。 + +所以石子树为 5、6、7 块的时候,我可以通过分别拿走 1、2、3 块石头,使得剩下的石头数为 4,从而在下一轮获得胜利。 + +如果石子数为 8 块的时候,我无论怎么拿都不能使剩下石子为 4。而对方又会利用这个机会使得他拿走之后的石子数变为 4,从而使我失败。 + +所以,很显然:当 n 不是 4 的整数倍时,我一定赢得比赛。当 n 为 4 的整数倍时,我一定赢不了比赛。 + +## 代码 + +```python +class Solution: + def canWinNim(self, n: int) -> bool: + return n % 4 != 0 +``` + diff --git a/docs/solutions/0200-0299/number-of-digit-one.md b/docs/solutions/0200-0299/number-of-digit-one.md new file mode 100644 index 00000000..ffd8c360 --- /dev/null +++ b/docs/solutions/0200-0299/number-of-digit-one.md @@ -0,0 +1,92 @@ +# [0233. 数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) + +- 标签:递归、数学、动态规划 +- 难度:困难 + +## 题目链接 + +- [0233. 数字 1 的个数 - 力扣](https://leetcode.cn/problems/number-of-digit-one/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算所有小于等于 $n$ 的非负整数中数字 $1$ 出现的个数。 + +**说明**: + +- $0 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 13 +输出:6 +``` + +- 示例 2: + +```python +输入:n = 0 +输出:0 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $1$ 出现的个数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始数字 $1$ 出现的个数为 $0$。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $1$ 出现的个数 $cnt$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 + 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 + 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX)`。 + 1. `cnt + (d == 1)` 表示之前数字 $1$ 出现的个数加上当前位为数字 $1$ 的个数。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 +6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def countDigitOne(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # cnt: 之前数字 1 出现的个数。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + def dfs(pos, cnt, isLimit): + if pos == len(s): + return cnt + + ans = 0 + # 不需要考虑前导 0,则最小可选择数字为 0 + minX = 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX) + return ans + + return dfs(0, 0, True) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0200-0299/number-of-islands.md b/docs/solutions/0200-0299/number-of-islands.md new file mode 100644 index 00000000..db162364 --- /dev/null +++ b/docs/solutions/0200-0299/number-of-islands.md @@ -0,0 +1,94 @@ +# [0200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0200. 岛屿数量 - 力扣](https://leetcode.cn/problems/number-of-islands/) + +## 题目大意 + +**描述**:给定一个由字符 `'1'`(陆地)和字符 `'0'`(水)组成的的二维网格 $grid$。 + +**要求**:计算网格中岛屿的数量。 + +**说明**: + +- 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 +- 此外,你可以假设该网格的四条边均被水包围。 +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 300$。 +- $grid[i][j]$ 的值为 `'0'` 或 `'1'`。 + +**示例**: + +- 示例 1: + +```python +输入:grid = [ + ["1","1","1","1","0"], + ["1","1","0","1","0"], + ["1","1","0","0","0"], + ["0","0","0","0","0"] +] +输出:1 +``` + +- 示例 2: + +```python +输入:grid = [ + ["1","1","0","0","0"], + ["1","1","0","0","0"], + ["0","0","1","0","0"], + ["0","0","0","1","1"] +] +输出:3 +``` + +## 解题思路 + +如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。 + +使用深度优先搜索或者广度优先搜索都可以。 + +### 思路 1:深度优先搜索 + +1. 遍历 $grid$。 +2. 对于每一个字符为 `'1'` 的元素,遍历其上下左右四个方向,并将该字符置为 `'0'`,保证下次不会被重复遍历。 +3. 如果超出边界,则返回 $0$。 +4. 对于 $(i, j)$ 位置的元素来说,递归遍历的位置就是 $(i - 1, j)$、$(i, j - 1)$、$(i + 1, j)$、$(i, j + 1)$ 四个方向。每次遍历到底,统计数记录一次。 +5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, grid, i, j): + n = len(grid) + m = len(grid[0]) + if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0': + return 0 + grid[i][j] = '0' + self.dfs(grid, i + 1, j) + self.dfs(grid, i, j + 1) + self.dfs(grid, i - 1, j) + self.dfs(grid, i, j - 1) + + def numIslands(self, grid: List[List[str]]) -> int: + count = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == '1': + self.dfs(grid, i, j) + count += 1 + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0200-0299/palindrome-linked-list.md b/docs/solutions/0200-0299/palindrome-linked-list.md new file mode 100644 index 00000000..ed25b828 --- /dev/null +++ b/docs/solutions/0200-0299/palindrome-linked-list.md @@ -0,0 +1,66 @@ +# [0234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) + +- 标签:栈、递归、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [0234. 回文链表 - 力扣](https://leetcode.cn/problems/palindrome-linked-list/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:判断该链表是否为回文链表。 + +**说明**: + +- 链表中节点数目在范围 $[1, 10^5]$ 内。 +- $0 \le Node.val \le 9$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/03/pal1linked-list.jpg) + +```python +输入:head = [1,2,2,1] +输出:True +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/03/pal2linked-list.jpg) + +```python +输入:head = [1,2] +输出:False +``` + +## 解题思路 + +### 思路 1:利用数组 + 双指针 + +1. 利用数组,将链表元素依次存入。 +2. 然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置。 +3. 依次判断首尾对应元素是否相等,如果都相等,则为回文链表。如果不相等,则不是回文链表。 + +### 思路 1:代码 + +```python +class Solution: + def isPalindrome(self, head: ListNode) -> bool: + nodes = [] + p1 = head + while p1 != None: + nodes.append(p1.val) + p1 = p1.next + return nodes == nodes[::-1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/perfect-squares.md b/docs/solutions/0200-0299/perfect-squares.md new file mode 100644 index 00000000..a8606b19 --- /dev/null +++ b/docs/solutions/0200-0299/perfect-squares.md @@ -0,0 +1,157 @@ +# [0279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) + +- 标签:广度优先搜索、数学、动态规划 +- 难度:中等 + +## 题目链接 + +- [0279. 完全平方数 - 力扣](https://leetcode.cn/problems/perfect-squares/) + +## 题目大意 + +**描述**:给定一个正整数 $n$。从中找到若干个完全平方数(比如 $1, 4, 9, 16 …$),使得它们的和等于 $n$。 + +**要求**:返回和为 $n$ 的完全平方数的最小数量。 + +**说明**: + +- $1 \le n \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 12 +输出:3 +解释:12 = 4 + 4 + 4 +``` + +- 示例 2: + +```python +输入:n = 13 +输出:2 +解释:13 = 4 + 9 +``` + +## 解题思路 + +暴力枚举思路:对于小于 $n$ 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 + +并且对于所有小于 $n$ 的完全平方数($k = 1, 4, 9, 16, ...$),存在公式:$ans(n) = min(ans(n - k) + 1), k = 1, 4, 9, 16 ...$ + +即: **n 的完全平方数的最小数量 == n - k 的完全平方数的最小数量 + 1**。 + +我们可以使用递归解决这个问题。但是因为重复计算了中间解,会产生堆栈溢出。 + +那怎么解决重复计算问题和避免堆栈溢出? + +我们可以转换一下思维。 + +1. 将 $n$ 作为根节点,构建一棵多叉数。 +2. 从 $n$ 节点出发,如果一个小于 $n$ 的数刚好与 $n$ 相差一个平方数,则以该数为值构造一个节点,与 $n$ 相连。 + +那么求解和为 $n$ 的完全平方数的最小数量就变成了求解这棵树从根节点 $n$ 到节点 $0$ 的最短路径,或者说树的最小深度。 + +这个过程可以通过广度优先搜索来做。 + +### 思路 1:广度优先搜索 + +1. 定义 $visited$ 为标记访问节点的 set 集合变量,避免重复计算。定义 $queue$ 为存放节点的队列。使用 $count$ 表示为树的最小深度,也就是和为 $n$ 的完全平方数的最小数量。 +2. 首先,我们将 $n$ 标记为已访问,即 `visited.add(n)`。并将其加入队列 $queue$ 中,即 `queue.append(n)`。 +3. 令 $count$ 加 $1$,表示最小深度加 $1$。然后依次将队列中的节点值取出。 +4. 对于取出的节点值 $value$,遍历可能出现的平方数(即遍历 $[1, \sqrt{value} + 1]$ 中的数)。 +5. 每次从当前节点值减去一个平方数,并将减完的数加入队列。 + 1. 如果此时的数等于 $0$,则满足题意,返回当前树的最小深度。 + 2. 如果此时的数不等于 $0$,则将其加入队列,继续查找。 + +### 思路 1:代码 + +```python +class Solution: + def numSquares(self, n: int) -> int: + if n == 0: + return 0 + + visited = set() + queue = collections.deque([]) + + visited.add(n) + queue.append(n) + + count = 0 + while queue: + // 最少步数 + count += 1 + size = len(queue) + for _ in range(size): + value = queue.pop() + for i in range(1, int(math.sqrt(value)) + 1): + x = value - i * i + if x == 0: + return count + if x not in visited: + queue.appendleft(x) + visited.add(x) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \sqrt{n})$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:动态规划 + +我们可以将这道题转换为「完全背包问题」中恰好装满背包的方案数问题。 + +1. 将 $k = 1, 4, 9, 16, ...$ 看做是 $k$ 种物品,每种物品都可以无限次使用。 +2. 将 $n$ 看做是背包的装载上限。 +3. 这道题就变成了,从 $k$ 种物品中选择一些物品,装入装载上限为 $n$ 的背包中,恰好装满背包最少需要多少件物品。 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:从完全平方数中挑选一些数,使其和恰好凑成 $w$ ,最少需要多少个完全平方数。 + +###### 3. 状态转移方程 + +$dp[w] = min \lbrace dp[w], dp[w - num] + 1$ + +###### 4. 初始条件 + +- 恰好凑成和为 $0$,最少需要 $0$ 个完全平方数。 +- 默认情况下,在不使用完全平方数时,都不能恰好凑成和为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入装载上限为 $w$ 的背包中,恰好装满背包,最少需要多少件物品。 所以最终结果为 $dp[n]$。 + +1. 如果 $dp[n] \ne n + 1$,则说明:$dp[n]$ 为装入装载上限为 $n$ 的背包,恰好装满背包,最少需要的物品数量,则返回 $dp[n]$。 +2. 如果 $dp[n] = n + 1$,则说明:无法恰好装满背包,则返回 $-1$。因为 $n$ 肯定能由 $n$ 个 $1$ 组成,所以这种情况并不会出现。 + +### 思路 2:代码 + +```python +class Solution: + def numSquares(self, n: int) -> int: + dp = [n + 1 for _ in range(n + 1)] + dp[0] = 0 + + for i in range(1, int(sqrt(n)) + 1): + num = i * i + for w in range(num, n + 1): + dp[w] = min(dp[w], dp[w - num] + 1) + + if dp[n] != n + 1: + return dp[n] + return -1 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \sqrt{n})$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0200-0299/power-of-two.md b/docs/solutions/0200-0299/power-of-two.md new file mode 100644 index 00000000..f4a157e5 --- /dev/null +++ b/docs/solutions/0200-0299/power-of-two.md @@ -0,0 +1,83 @@ +# [0231. 2 的幂](https://leetcode.cn/problems/power-of-two/) + +- 标签:位运算、递归、数学 +- 难度:简单 + +## 题目链接 + +- [0231. 2 的幂 - 力扣](https://leetcode.cn/problems/power-of-two/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:判断该整数 $n$ 是否是 $2$ 的幂次方。如果是,返回 `True`;否则,返回 `False`。 + +**说明**: + +- $-2^{31} \le n \le 2^{31} - 1$ + +**示例**: + +- 示例 1: + +```python +输入:n = 1 +输出:True +解释:2^0 = 1 +``` + +- 示例 2: + +```python +输入:n = 16 +输出:True +解释:2^4 = 16 +``` + +## 解题思路 + +### 思路 1:循环判断 + +1. 不断判断 $n$ 是否能整除 $2$。 + 1. 如果不能整除,则返回 `False`。 + 2. 如果能整除,则让 $n$ 整除 $2$,直到 $n < 2$。 +2. 如果最后 $n == 1$,则返回 `True`,否则则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def isPowerOfTwo(self, n: int) -> bool: + if n <= 0: + return False + + while n % 2 == 0: + n //= 2 + return n == 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log_2 n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:数论判断 + +因为 $n$ 能取的最大值为 $2^{31}-1$。我们可以计算出:在 $n$ 的范围内,$2$ 的幂次方最大为 $2^{30} = 1073741824$。 + +因为 $2$ 为质数,则 $2^{30}$ 的除数只有 $2^0, 2^1, …, 2^{30}$。所以如果 $n$ 为 $2$ 的幂次方,则 $2^{30}$ 肯定能被 $n$ 整除,直接判断即可。 + +### 思路 2:代码 + +```python +class Solution: + def isPowerOfTwo(self, n: int) -> bool: + return n > 0 and 1073741824 % n == 0 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/product-of-array-except-self.md b/docs/solutions/0200-0299/product-of-array-except-self.md new file mode 100644 index 00000000..7b807e92 --- /dev/null +++ b/docs/solutions/0200-0299/product-of-array-except-self.md @@ -0,0 +1,74 @@ +# [0238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) + +- 标签:数组、前缀和 +- 难度:中等 + +## 题目链接 + +- [0238. 除自身以外数组的乘积 - 力扣](https://leetcode.cn/problems/product-of-array-except-self/) + +## 题目大意 + +**描述**:给定一个数组 nums。 + +**要求**:返回数组 $answer$,其中 $answer[i]$ 等于 $nums$ 中除 $nums[i]$ 之外其余各元素的乘积。 + +**说明**: + +- 题目数据保证数组 $nums$ 之中任意元素的全部前缀元素和后缀的乘积都在 $32$ 位整数范围内。 +- 请不要使用除法,且在 $O(n)$ 时间复杂度内解决问题。 +- **进阶**:在 $O(1)$ 的额外空间复杂度内完成这个题目。 +- $2 \le nums.length \le 10^5$。 +- $-30 \le nums[i] \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [1,2,3,4] +输出: [24,12,8,6] +``` + +- 示例 2: + +```python +输入: nums = [-1,1,0,-3,3] +输出: [0,0,9,0,0] +``` + +## 解题思路 + +### 思路 1:两次遍历 + +1. 构造一个答案数组 $res$,长度和数组 $nums$ 长度一致。 +2. 先从左到右遍历一遍 $nums$ 数组,将 $nums[i]$ 左侧的元素乘积累积起来,存储到 $res$ 数组中。 +3. 再从右到左遍历一遍,将 $nums[i]$ 右侧的元素乘积累积起来,再乘以原本 $res[i]$ 的值,即为 $nums$ 中除了 $nums[i]$ 之外的其他所有元素乘积。 + +### 思路 1:代码 + +```python +class Solution: + def productExceptSelf(self, nums: List[int]) -> List[int]: + size = len(nums) + res = [1 for _ in range(size)] + + left = 1 + for i in range(size): + res[i] *= left + left *= nums[i] + + right = 1 + for i in range(size-1, -1, -1): + res[i] *= right + right *= nums[i] + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + + + diff --git a/docs/solutions/0200-0299/rectangle-area.md b/docs/solutions/0200-0299/rectangle-area.md new file mode 100644 index 00000000..00b5b453 --- /dev/null +++ b/docs/solutions/0200-0299/rectangle-area.md @@ -0,0 +1,35 @@ +# [0223. 矩形面积](https://leetcode.cn/problems/rectangle-area/) + +- 标签:几何、数学 +- 难度:中等 + +## 题目链接 + +- [0223. 矩形面积 - 力扣](https://leetcode.cn/problems/rectangle-area/) + +## 题目大意 + +给定两个矩形的左下角坐标、右上角坐标 `(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)`。其中 `(ax1, ay1)` 表示第一个矩形左下角坐标,`(ax2, ay2)` 表示第一个矩形右上角坐标,`(bx1, by1)` 表示第二个矩形左下角坐标,`(bx2, by2)` 表示第二个矩形右上角坐标。 + +要求:计算出两个矩形覆盖的总面积。 + +## 解题思路 + +两个矩形覆盖的总面积 = 第一个矩形面积 + 第二个矩形面积 - 重叠部分面积。 + +需要分别计算出两个矩形面积,还有求出相交部分的长、宽,并计算出对应重叠部分的面积。 + +## 代码 + +```python +class Solution: + def computeArea(self, ax1: int, ay1: int, ax2: int, ay2: int, bx1: int, by1: int, bx2: int, by2: int) -> int: + area_a = (ax2 - ax1) * (ay2 - ay1) + area_b = (bx2 - bx1) * (by2 - by1) + overlap_width = max(0, min(ax2, bx2) - max(ax1, bx1)) + overlap_height = max(0, min(ay2, by2) - max(ay1, by1)) + area_overlap = overlap_width * overlap_height + + return area_a + area_b - area_overlap +``` + diff --git a/docs/solutions/0200-0299/remove-linked-list-elements.md b/docs/solutions/0200-0299/remove-linked-list-elements.md new file mode 100644 index 00000000..71ad73e0 --- /dev/null +++ b/docs/solutions/0200-0299/remove-linked-list-elements.md @@ -0,0 +1,72 @@ +# [0203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [0203. 移除链表元素 - 力扣](https://leetcode.cn/problems/remove-linked-list-elements/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head` 和一个值 `val`。 + +**要求**:删除链表中值为 `val` 的节点,并返回新的链表头节点。 + +**说明**: + +- 列表中的节点数目在范围 $[0, 10^4]$ 内。 +- $1 \le Node.val \le 50$。 +- $0 \le val \le 50$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/06/removelinked-list.jpg) + +```python +输入:head = [1,2,6,3,4,5,6], val = 6 +输出:[1,2,3,4,5] +``` + +- 示例 2: + +```python +输入:head = [], val = 1 +输出:[] +``` + +## 解题思路 + +### 思路 1:迭代 + +- 使用两个指针 `prev` 和 `curr`。`prev` 指向前一节点和当前节点,`curr` 指向当前节点。 +- 从前向后遍历链表,遇到值为 `val` 的节点时,将 `prev` 的 `next` 指针指向当前节点的下一个节点,继续递归遍历。没有遇到则将 `prev` 指针向后移动一步。 +- 向右移动 `curr`,继续遍历。 + +需要注意的是:因为要删除的节点可能包含了头节点,我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则直接删除原头节点,然后最后返回新建头节点的下一个节点即可。 + +### 思路 1:代码 + +```python +class Solution: + def removeElements(self, head: ListNode, val: int) -> ListNode: + newHead = ListNode(0, head) + newHead.next = head + + prev, curr = newHead, head + while curr: + if curr.val == val: + prev.next = curr.next + else: + prev = curr + curr = curr.next + return newHead.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/reverse-linked-list.md b/docs/solutions/0200-0299/reverse-linked-list.md new file mode 100644 index 00000000..a26e7504 --- /dev/null +++ b/docs/solutions/0200-0299/reverse-linked-list.md @@ -0,0 +1,108 @@ +# [0206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [0206. 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/) + +## 题目大意 + +**描述**:给定一个单链表的头节点 `head`。 + +**要求**:将该单链表进行反转。可以迭代或递归地反转链表。 + +**说明**: + +- 链表中节点的数目范围是 $[0, 5000]$。 +- $-5000 \le Node.val \le 5000$。 + +**示例**: + +- 示例 1: + +```python +输入:head = [1,2,3,4,5] +输出:[5,4,3,2,1] +解释: +翻转前 1->2->3->4->5->NULL +反转后 5->4->3->2->1->NULL +``` + +## 解题思路 + +### 思路 1:迭代 + +1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 + +2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: + 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; + 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; + 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; + 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 +3. 继续执行第 2 步中的 1、2、3、4。 +4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 + +使用迭代法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111133639.png) + +### 思路 1:代码 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + pre = None + cur = head + while cur != None: + next = cur.next + cur.next = pre + pre = cur + cur = next + return pre +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:递归 + +具体做法如下: + +1. 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 +2. 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 +3. 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 +4. 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 +5. 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 +6. 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 +7. 最后返回反转后的链表头节点 `new_head`。 + +使用递归法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111134246.png) + +### 思路 2:代码 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head == None or head.next == None: + return head + new_head = self.reverseList(head.next) + head.next.next = head + head.next = None + return new_head +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$ +- **空间复杂度**:$O(n)$。最多需要 $n$ 层栈空间。 + +## 参考资料 + +- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) +- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) diff --git a/docs/solutions/0200-0299/search-a-2d-matrix-ii.md b/docs/solutions/0200-0299/search-a-2d-matrix-ii.md new file mode 100644 index 00000000..6af57d6f --- /dev/null +++ b/docs/solutions/0200-0299/search-a-2d-matrix-ii.md @@ -0,0 +1,124 @@ +# [0240. 搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) + +- 标签:二分查找、分治算法 +- 难度:中等 + +## 题目链接 + +- [0240. 搜索二维矩阵 II - 力扣](https://leetcode.cn/problems/search-a-2d-matrix-ii/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的有序整数矩阵 $matrix$。$matrix$ 中的每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 $target$。 + +**要求**:判断矩阵中是否可以找到 $target$,如果可以找到 $target$,返回 `True`,否则返回 `False`。 + +**说明**: + +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le n, m \le 300$。 +- $-10^9 \le matrix[i][j] \le 10^9$。 +- 每行的所有元素从左到右升序排列。 +- 每列的所有元素从上到下升序排列。 +- $-10^9 \le target \le 10^9$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid2.jpg) + +```python +输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5 +输出:True +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid.jpg) + +```python +输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20 +输出:False +``` + +## 解题思路 + +### 思路 1:二分查找 + +矩阵是有序的,可以考虑使用二分查找来做。 + +1. 迭代对角线元素,假设对角线元素的坐标为 $(row, col)$。把数组元素按对角线分为右上角部分和左下角部分。 +2. 对于当前对角线元素右侧第 $row$ 行、对角线元素下侧第 $col$ 列分别进行二分查找。 + 1. 如果找到目标,直接返回 `True`。 + 2. 如果找不到目标,则缩小范围,继续查找。 + 3. 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def diagonalBinarySearch(self, matrix, diagonal, target): + left = 0 + right = diagonal + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][mid] < target: + left = mid + 1 + else: + right = mid + return left + + def rowBinarySearch(self, matrix, begin, cols, target): + left = begin + right = cols + while left < right: + mid = left + (right - left) // 2 + if matrix[begin][mid] < target: + left = mid + 1 + elif matrix[begin][mid] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= cols and matrix[begin][left] == target + + def colBinarySearch(self, matrix, begin, rows, target): + left = begin + 1 + right = rows + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][begin] < target: + left = mid + 1 + elif matrix[mid][begin] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= rows and matrix[left][begin] == target + + def searchMatrix(self, matrix, target: int) -> bool: + rows = len(matrix) + if rows == 0: + return False + cols = len(matrix[0]) + if cols == 0: + return False + + min_val = min(rows, cols) + index = self.diagonalBinarySearch(matrix, min_val - 1, target) + if matrix[index][index] == target: + return True + for i in range(index + 1): + row_search = self.rowBinarySearch(matrix, i, cols - 1, target) + col_search = self.colBinarySearch(matrix, i, rows - 1, target) + if row_search or col_search: + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(min(m, n) \times (\log_2 m + \log_2 n))$,其中 $m$ 是矩阵的行数,$n$ 是矩阵的列数。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md b/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md new file mode 100644 index 00000000..95d0bc0f --- /dev/null +++ b/docs/solutions/0200-0299/serialize-and-deserialize-binary-tree.md @@ -0,0 +1,94 @@ +# [0297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 +- 难度:困难 + +## 题目链接 + +- [0297. 二叉树的序列化与反序列化 - 力扣](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) + +## 题目大意 + +**要求**:设计一个算法,来实现二叉树的序列化与反序列化。 + +**说明**: + +- 不限定序列化 / 反序列化算法执行逻辑,只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 +- 树中结点数在范围 $[0, 10^4]$ 内。 +- $-1000 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) + +```python +输入:root = [1,2,3,null,null,4,5] +输出:[1,2,3,null,null,4,5] +``` + +- 示例 2: + +```python +输入:root = [1,2] +输出:[1,2] +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +#### 1. 序列化:将二叉树转为字符串数据表示 + +1. 按照前序顺序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 + +> 注意:如果遇到空节点,则将其标记为 `None`,这样在反序列化时才能唯一确定一棵二叉树。 + +#### 2. 反序列化:将字符串数据转为二叉树结构 + +1. 先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 +2. 从数组左侧取出一个元素。 + 1. 如果当前元素为 `None`,则返回 `None`。 + 2. 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 +3. 最后返回当前根节点。 + +### 思路 1:代码 + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + if not root: + return 'None' + return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + def dfs(datalist): + val = datalist.pop(0) + if val == 'None': + return None + root = TreeNode(int(val)) + root.left = dfs(datalist) + root.right = dfs(datalist) + return root + + datalist = data.split(',') + return dfs(datalist) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0200-0299/single-number-iii.md b/docs/solutions/0200-0299/single-number-iii.md new file mode 100644 index 00000000..f8bbd418 --- /dev/null +++ b/docs/solutions/0200-0299/single-number-iii.md @@ -0,0 +1,85 @@ +# [0260. 只出现一次的数字 III](https://leetcode.cn/problems/single-number-iii/) + +- 标签:位运算、数组 +- 难度:中等 + +## 题目链接 + +- [0260. 只出现一次的数字 III - 力扣](https://leetcode.cn/problems/single-number-iii/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。$nums$ 中恰好有两个元素只出现一次,其余所有元素均出现两次。 + +**要求**:找出只出现一次的那两个元素。可以按任意顺序返回答案。要求时间复杂度是 $O(n)$,空间复杂度是 $O(1)$。 + +**说明**: + +- $2 \le nums.length \le 3 \times 10^4$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 +- 除两个只出现一次的整数外,$nums$ 中的其他数字都出现两次。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,1,3,2,5] +输出:[3,5] +解释:[5, 3] 也是有效的答案。 +``` + +- 示例 2: + +```python +输入:nums = [-1,0] +输出:[-1,0] +``` + +## 解题思路 + +### 思路 1:位运算 + +求解这道题之前,我们先来看看如何求解「一个数组中除了某个元素只出现一次以外,其余每个元素均出现两次。」即「[136. 只出现一次的数字](https://leetcode.cn/problems/single-number/)」问题。 + +我们可以对所有数不断进行异或操作,最终可得到单次出现的元素。 + +下面我们再来看这道题。 + +如果数组中有两个数字只出现一次,其余每个元素均出现两次。那么经过全部异或运算。我们可以得到只出现一次的两个数字的异或结果。 + +根据异或结果的性质,异或运算中如果某一位上为 $1$,则说明异或的两个数在该位上是不同的。根据这个性质,我们将数字分为两组: + +1. 一组是和该位为 $0$ 的数字, +2. 一组是该位为 $1$ 的数字。 + +然后将这两组分别进行异或运算,就可以得到最终要求的两个数字。 + +### 思路 1:代码 + +```python +class Solution: + def singleNumbers(self, nums: List[int]) -> List[int]: + all_xor = 0 + for num in nums: + all_xor ^= num + # 获取所有异或中最低位的 1 + mask = 1 + while all_xor & mask == 0: + mask <<= 1 + + a_xor, b_xor = 0, 0 + for num in nums: + if num & mask == 0: + a_xor ^= num + else: + b_xor ^= num + + return a_xor, b_xor +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 中的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0200-0299/sliding-window-maximum.md b/docs/solutions/0200-0299/sliding-window-maximum.md new file mode 100644 index 00000000..d0409ba8 --- /dev/null +++ b/docs/solutions/0200-0299/sliding-window-maximum.md @@ -0,0 +1,83 @@ +# [0239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) + +- 标签:队列、数组、滑动窗口、单调队列、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0239. 滑动窗口最大值 - 力扣](https://leetcode.cn/problems/sliding-window-maximum/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`,再给定一个整数 `k`,表示为大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 `k` 个数字,滑动窗口每次只能向右移动一位。 + +**要求**:返回滑动窗口中的最大值。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $1 \le k \le nums.length$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 +输出:[3,3,5,5,6,7] +解释: +滑动窗口的位置 最大值 +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 +``` + +- 示例 2: + +```python +输入:nums = [1], k = 1 +输出:[1] +``` + +## 解题思路 + +暴力求解的话,需要使用二重循环遍历,其时间复杂度为 $O(n * k)$。根据题目给定的数据范围,肯定会超时。 + +我们可以使用优先队列来做。 + +### 思路 1:优先队列 + +1. 初始的时候将前 `k` 个元素加入优先队列的二叉堆中。存入优先队列的是数组值与索引构成的元组。优先队列将数组值作为优先级。 +2. 然后滑动窗口从第 `k` 个元素开始遍历,将当前数组值和索引的元组插入到二叉堆中。 +3. 当二叉堆堆顶元素的索引已经不在滑动窗口的范围中时,即 `q[0][1] <= i - k` 时,不断删除堆顶元素,直到最大值元素的索引在滑动窗口的范围中。 +4. 将最大值加入到答案数组中,继续向右滑动。 +5. 滑动结束时,输出答案数组。 + +### 思路 1:代码 + +```python +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + size = len(nums) + q = [(-nums[i], i) for i in range(k)] + heapq.heapify(q) + res = [-q[0][0]] + + for i in range(k, size): + heapq.heappush(q, (-nums[i], i)) + while q[0][1] <= i - k: + heapq.heappop(q) + res.append(-q[0][0]) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(k)$。 + diff --git a/docs/solutions/0200-0299/the-skyline-problem.md b/docs/solutions/0200-0299/the-skyline-problem.md new file mode 100644 index 00000000..e4ef5f6a --- /dev/null +++ b/docs/solutions/0200-0299/the-skyline-problem.md @@ -0,0 +1,88 @@ +# [0218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem/) + +- 标签:树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0218. 天际线问题 - 力扣](https://leetcode.cn/problems/the-skyline-problem/) + +## 题目大意 + +城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。 + +给定所有建筑物的位置和高度所组成的数组 `buildings`。其中三元素 `buildings[i] = [left_i, right_i, height_i]` 表示 `left_i` 是第 `i` 座建筑物左边界的 `x` 坐标。`right_i` 是第 `i` 座建筑物右边界的 `x` 坐标,`height_i` 是第 `i` 做建筑物的高度。 + +要求:返回由这些建筑物形成的天际线 。 + +- 天际线:由 “关键点” 组成的列表,格式 `[[x1, y1], [x2, y2], [x3, y3], ...]`,并按 `x` 坐标进行排序。 +- 关键点:水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,`y` 坐标始终为 `0`,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。 + +注意:输出天际线中不得有连续的相同高度的水平线。 + +- 例如 `[..., [2 3], [4 5], [7 5], [11 5], [12 7], ...]` 是不正确的答案;三条高度为 `5` 的线应该在最终输出中合并为一个:`[..., [2 3], [4 5], [12 7], ...]`。 + +示例: + +![](https://assets.leetcode.com/uploads/2020/12/01/merged.jpg) + +- 图 A 显示输入的所有建筑物的位置和高度。 +- 图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。 + +## 解题思路 + +可以看出来:关键点的横坐标都在建筑物的左右边界上。 + +我们可以将左右边界最高处的坐标存入 `points` 数组中,然后按照建筑物左边界、右边界的高度进行排序。 + +然后用一条条「垂直于 x 轴的扫描线」,从所有建筑物的最左侧依次扫描到最右侧。从而将建筑物分割成规则的矩形。 + +不难看出:相邻的两个坐标的横坐标与矩形所能达到的最大高度构成了一个矩形。相邻两个坐标的横坐标可以从排序过的 `points` 数组中依次获取,矩形所能达到的最大高度可以用一个优先队列(堆)`max_heap` 来维护。使用数组 `ans` 来作为答案答案。 + +在依次从左到右扫描坐标时: + +- 当扫描到建筑物的左边界时,说明必然存在一条向右延伸的边。此时将高度加入到优先队列中。 +- 当扫描到建筑物的右边界时,说明从之前的左边界延伸的边结束了,此时将高度从优先队列中移除。 + +因为三条高度相同的线应该合并为一个,所以我们用 `prev` 来记录之前上一个矩形高度。 + +- 如果当前矩形高度 `curr` 与之前矩形高度 `prev` 相同,则跳过。 +- 如果当前矩形高度 `curr` 与之前矩形高度 `prev `不相同,则将其加入到答案数组中,并更新上一矩形高度 `prev` 的值。 + +最后,输出答案 `ans`。 + +## 代码 + +```python +from sortedcontainers import SortedList + +class Solution: + def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]: + ans = [] + points = [] + for building in buildings: + left, right, hight = building[0], building[1], building[2] + points.append([left, -hight]) + points.append([right, hight]) + points.sort(key=lambda x:(x[0], x[1])) + + prev = 0 + max_heap = SortedList([prev]) + + for point in points: + x, height = point[0], point[1] + if height < 0: + max_heap.add(-height) + else: + max_heap.remove(height) + + curr = max_heap[-1] + if curr != prev: + ans.append([x, curr]) + prev = curr + return ans +``` + +## 参考资料 + +- 【题解】[【宫水三叶】扫描线算法基本思路 & 优先队列维护当前最大高度 - 天际线问题 - 力扣](https://leetcode.cn/problems/the-skyline-problem/solution/gong-shui-san-xie-sao-miao-xian-suan-fa-0z6xc/) diff --git a/docs/solutions/0200-0299/ugly-number-ii.md b/docs/solutions/0200-0299/ugly-number-ii.md new file mode 100644 index 00000000..89fc28bb --- /dev/null +++ b/docs/solutions/0200-0299/ugly-number-ii.md @@ -0,0 +1,43 @@ +# [0264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) + +- 标签:哈希表、数学、动态规划、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0264. 丑数 II - 力扣](https://leetcode.cn/problems/ugly-number-ii/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:找出并返回第 `n` 个丑数。 + +- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 + +## 解题思路 + +动态规划求解。 + +定义状态 `dp[i]` 表示第 `i` 个丑数。 + +状态转移方程为:`dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)` ,其中 `p2`、`p3`、`p5` 分别表示当前 `i` 中 `2`、`3`、`5` 的质因子数量。 + +## 代码 + +```python +class Solution: + def nthUglyNumber(self, n: int) -> int: + dp = [1 for _ in range(n)] + p2, p3, p5 = 0, 0, 0 + for i in range(1, n): + dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5) + if dp[i] == dp[p2] * 2: + p2 += 1 + if dp[i] == dp[p3] * 3: + p3 += 1 + if dp[i] == dp[p5] * 5: + p5 += 1 + return dp[n - 1] +``` + diff --git a/docs/solutions/0200-0299/ugly-number.md b/docs/solutions/0200-0299/ugly-number.md new file mode 100644 index 00000000..1b86e79c --- /dev/null +++ b/docs/solutions/0200-0299/ugly-number.md @@ -0,0 +1,37 @@ +# [0263. 丑数](https://leetcode.cn/problems/ugly-number/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [0263. 丑数 - 力扣](https://leetcode.cn/problems/ugly-number/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:判断 `n` 是否为丑数。如果是,则返回 `True`,否则,返回 `False`。 + +- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 + +## 解题思路 + +- 如果 `n <= 0`,则 `n` 必然不是丑数,直接返回 `False`。 +- 对 `n` 分别进行 `2`、`3`、`5` 的整除操作,直到 `n` 被除完,如果 `n` 最终为 `1`,则 `n` 是丑数,否则不是丑数。 + +## 代码 + +```python +class Solution: + def isUgly(self, n: int) -> bool: + if n <= 0: + return False + factors = [2, 3, 5] + for factor in factors: + while n % factor == 0: + n //= factor + + return n == 1 +``` + diff --git a/docs/solutions/0200-0299/unique-word-abbreviation.md b/docs/solutions/0200-0299/unique-word-abbreviation.md new file mode 100644 index 00000000..c38ece79 --- /dev/null +++ b/docs/solutions/0200-0299/unique-word-abbreviation.md @@ -0,0 +1,55 @@ +# [0288. 单词的唯一缩写](https://leetcode.cn/problems/unique-word-abbreviation/) + +- 标签:设计、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0288. 单词的唯一缩写 - 力扣](https://leetcode.cn/problems/unique-word-abbreviation/) + +## 题目大意 + +单词缩写规则:<起始字母><中间字母><结尾字母>。如果单词长度不超过 2,则单词本身就是缩写。 + +举例: + +- `dog --> d1g`:第一个字母`d`,最后一个字母 `g`,中间隔着 1 个字母。 +- `internationalization --> i18n`:第一个字母 `i` ,最后一个字母 `n`,中间隔着 18 个字母。 +- `it --> it`:单词只有两个字符,它就是它自身的缩写。 + +要求实现 ValidWordAbbr 类: + +- `ValidWordAbbr(dictionary: List[str]):`使用单词字典初始化对象 +- `def isUnique(self, word: str) -> bool:` + - 如果字典 dictionary 中没有其他单词的缩写与该单词 word 的缩写相同,返回 True。 + - 如果字典 dictionary 中所有与该单词 word 的缩写相同的单词缩写都与 word 相同。 + +## 解题思路 + +将相同缩写的单词进行分类,利用哈希表进行存储。键值对格式为 缩写:该缩写对应的 word 列表。 + +然后初始化的时候,将 dictionary 里的单词按照缩写进行哈希表存储。 + +在判断的时候,先判断单词 word 的缩写是否能在哈希表中找到对应的映射关系。 + +- 如果 word 的缩写 abbr 没有在哈希表中,则返回 True。 +- 如果 word 的缩写 abbr 在哈希表中: + - 如果缩写 abbr 对应的字符串列表只有一个字符串,并且就是 word,则返回 True。Ï + - 否则返回 False。 +- 不满足上述要求也返回 False。 + +## 代码 + +```python + def isUnique(self, word: str) -> bool: + if len(word) <= 2: + abbr = word + else: + abbr = word[0] + chr(len(word)-2) + word[-1] + if abbr not in self.abbr_dict: + return True + if len(set(self.abbr_dict[abbr])) == 1 and word in set(self.abbr_dict[abbr]): + return True + return False +``` + diff --git a/docs/solutions/0200-0299/valid-anagram.md b/docs/solutions/0200-0299/valid-anagram.md new file mode 100644 index 00000000..ee8bb2ef --- /dev/null +++ b/docs/solutions/0200-0299/valid-anagram.md @@ -0,0 +1,73 @@ +# [0242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/) + +- 标签:哈希表、字符串、排序 +- 难度:简单 + +## 题目链接 + +- [0242. 有效的字母异位词 - 力扣](https://leetcode.cn/problems/valid-anagram/) + +## 题目大意 + +**描述**:给定两个字符串 $s$ 和 $t$。 + +**要求**:判断 $t$ 和 $s$ 是否使用了相同的字符构成(字符出现的种类和数目都相同)。 + +**说明**: + +- **字母异位词**:如果 $s$ 和 $t$ 中每个字符出现的次数都相同,则称 $s$ 和 $t$ 互为字母异位词。 +- $1 \le s.length, t.length \le 5 \times 10^4$。 +- $s$ 和 $t$ 仅包含小写字母。 + +**示例**: + +- 示例 1: + +```python +输入: s = "anagram", t = "nagaram" +输出: True +``` + +- 示例 2: + +```python +输入: s = "rat", t = "car" +输出: False +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 先判断字符串 $s$ 和 $t$ 的长度,不一样直接返回 `False`; +2. 分别遍历字符串 $s$ 和 $t$。先遍历字符串 $s$,用哈希表存储字符串 $s$ 中字符出现的频次; +3. 再遍历字符串 $t$,哈希表中减去对应字符的频次,出现频次小于 $0$ 则输出 `False`; +4. 如果没出现频次小于 $0$,则输出 `True`。 + +### 思路 1:代码 + +```python +def isAnagram(self, s: str, t: str) -> bool: + if len(s) != len(t): + return False + strDict = dict() + for ch in s: + if ch in strDict: + strDict[ch] += 1 + else: + strDict[ch] = 1 + for ch in t: + if ch in strDict: + strDict[ch] -= 1 + if strDict[ch] < 0: + return False + else: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$、$m$ 分别为字符串 $s$、$t$ 的长度。 +- **空间复杂度**:$O(|S|)$,其中 $S$ 为字符集大小,此处 $S == 26$。 + diff --git a/docs/solutions/0200-0299/walls-and-gates.md b/docs/solutions/0200-0299/walls-and-gates.md new file mode 100644 index 00000000..e966f2dd --- /dev/null +++ b/docs/solutions/0200-0299/walls-and-gates.md @@ -0,0 +1,54 @@ +# [0286. 墙与门](https://leetcode.cn/problems/walls-and-gates/) + +- 标签:广度优先搜索、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0286. 墙与门 - 力扣](https://leetcode.cn/problems/walls-and-gates/) + +## 题目大意 + +给定一个 `m * n` 的二维网络 `rooms`。其中每个元素有三种初始值: + +- `-1` 表示墙或者障碍物 +- `0` 表示一扇门 +- `INF` 表示为一个空的房间。这里用 $2^{31} = 2147483647$ 表示 `INF`。通往门的距离总是小于 $2^{31}$。 + +要求:给每个空房间填上该房间到最近的门的距离,如果无法到达门,则填 `INF`。 + +## 解题思路 + +从每个表示门开始,使用广度优先搜索去照门。因为广度优先搜索保证我们在搜索 `dist + 1` 距离的位置时,距离为 `dist` 的位置都已经搜索过了。所以每到达一个房间的时候一定是最短距离。 + +## 代码 + +```python +class Solution: + def wallsAndGates(self, rooms: List[List[int]]) -> None: + """ + Do not return anything, modify rooms in-place instead. + """ + INF = 2147483647 + rows = len(rooms) + if rows == 0: + return + cols = len(rooms[0]) + + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + queue = [] + for i in range(rows): + for j in range(cols): + if rooms[i][j] == 0: + queue.append((i, j, 0)) + + while queue: + i, j, dist = queue.pop(0) + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + if 0 <= new_i < rows and 0 <= new_j < cols and rooms[new_i][new_j] == INF: + rooms[new_i][new_j] = dist + 1 + queue.append((new_i, new_j, dist + 1)) +``` + diff --git a/docs/solutions/0200-0299/word-pattern.md b/docs/solutions/0200-0299/word-pattern.md new file mode 100644 index 00000000..87c1c644 --- /dev/null +++ b/docs/solutions/0200-0299/word-pattern.md @@ -0,0 +1,53 @@ +# [0290. 单词规律](https://leetcode.cn/problems/word-pattern/) + +- 标签:哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0290. 单词规律 - 力扣](https://leetcode.cn/problems/word-pattern/) + +## 题目大意 + +给定一种规律 `pattern` 和一个字符串 `str` ,判断 `str` 是否完全匹配相同的规律。 + +- 完全匹配相同的规律:pattern 的每个字母和字符串 str 中的每个非空单词之间存在这双向连接的对应规律。 +- 比如:pattern = "abba", str = "dog cat cat dog",其对应关系为:`a <=> dog,b <=> cat` + +## 解题思路 + +这道题要求判断规律串中的字符与所给字符串中的非空单词,是否是一一对应的。即每个字符都能映射到对应的非空单词,每个非空单词也能映射为字符。 + +考虑使用两个哈希表,一个用来存储字符到非空单词的映射,另一个用来存储非空单词到字符的映射。 + +遍历 pattern 中的字符: + +- 如果字符出现在第一个字典中,且字典中的值不等于对应的非空单词,则返回 False。 +- 如果单词出现在第二个字典中,且字典中的值不等于对应的字符,则返回 False。 + +- 如果遍历完仍没发现不满足要求的情况,则返回 True。 + +## 代码 + +```python +class Solution: + def wordPattern(self, pattern: str, s: str) -> bool: + pattern_dict = dict() + word_dict = dict() + words = s.split() + + if len(pattern) != len(words): + return False + + for i in range(len(words)): + p = pattern[i] + word = words[i] + if p in pattern_dict and pattern_dict[p] != word: + return False + if word in word_dict and word_dict[word] != p: + return False + pattern_dict[p] = word + word_dict[word] = p + return True +``` + diff --git a/docs/solutions/0200-0299/word-search-ii.md b/docs/solutions/0200-0299/word-search-ii.md new file mode 100644 index 00000000..959a119b --- /dev/null +++ b/docs/solutions/0200-0299/word-search-ii.md @@ -0,0 +1,103 @@ +# [0212. 单词搜索 II](https://leetcode.cn/problems/word-search-ii/) + +- 标签:字典树、数组、字符串、回溯、矩阵 +- 难度:困难 + +## 题目链接 + +- [0212. 单词搜索 II - 力扣](https://leetcode.cn/problems/word-search-ii/) + +## 题目大意 + +给定一个 `m * n` 二维字符网格 `board` 和一个单词(字符串)列表 `words`。 + +要求:找出所有同时在二维网格和字典中出现的单词。 + +注意:单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中「相邻」单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 + +## 解题思路 + +- 先将单词列表 `words` 中的所有单词存入字典树中。 + +- 然后遍历二维字符网络 `board` 的每一个字符 `board[i][j]`。 + +- 从当前单元格出发,从上下左右四个方向深度优先搜索遍历路径。每经过一个单元格,就将该单元格的字母修改为特殊字符,避免重复遍历,深度优先搜索完毕之后再恢复该单元格。 + - 如果当前路径恰好是 `words` 列表中的单词,则将结果添加到答案数组中。 + - 如果是 `words` 列表中单词的前缀,则继续搜索。 + - 如果不是 `words` 列表中单词的前缀,则停止搜索。 +- 最后输出答案数组。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.word = "" + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.word = word + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + rows = len(board) + cols = len(board[0]) + + def dfs(cur, row, col): + ch = board[row][col] + if ch not in cur.children: + return + + cur = cur.children[ch] + if cur.isEnd: + ans.add(cur.word) + + board[row][col] = "#" + for direct in directs: + new_row = row + direct[0] + new_col = col + direct[1] + if 0 <= new_row < rows and 0 <= new_col < cols: + dfs(cur, new_row, new_col) + board[row][col] = ch + + ans = set() + for i in range(rows): + for j in range(cols): + dfs(trie_tree, i, j) + + return list(ans) +``` + diff --git a/docs/solutions/0300-0399/android-unlock-patterns.md b/docs/solutions/0300-0399/android-unlock-patterns.md new file mode 100644 index 00000000..e29b9f99 --- /dev/null +++ b/docs/solutions/0300-0399/android-unlock-patterns.md @@ -0,0 +1,113 @@ +# [0351. 安卓系统手势解锁](https://leetcode.cn/problems/android-unlock-patterns/) + +- 标签:动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [0351. 安卓系统手势解锁 - 力扣](https://leetcode.cn/problems/android-unlock-patterns/) + +## 题目大意 + +**描述**:安卓系统手势解锁的界面是一个编号为 $1 \sim 9$、大小为 $3 \times 3$ 的网格。用户可以设定一个「解锁模式」,按照一定顺序经过 $k$ 个点,构成一个「解锁手势」。现在给定两个整数,分别为 $m$ 和 $n$。 + +**要求**:计算出有多少种不同且有效的解锁模式数量,其中每种解锁模式至少需要经过 $m$ 个点,但是不超过 $n$ 个点。 + +**说明**: + +- **有效的解锁模式**: + - 解锁模式中所有点不能重复。 + - 如果解锁模式中两个点是按顺序经过的,那么这两个点之间的手势轨迹不能跨过其他任何未被经过的点。 + +- 一些有效和无效解锁模式示例:![](https://assets.leetcode.com/uploads/2018/10/12/android-unlock.png) + - 无效手势:$[4,1,3,6]$,连接点 $1$ 和点 $3$ 时经过了未被连接过的 $2$ 号点。 + - 无效手势:$[4,1,9,2]$,连接点 $1$ 和点 $9$ 时经过了未被连接过的 $5$ 号点。 + - 有效手势:$[2,4,1,3,6]$,连接点 $1$ 和点 $3$ 是有效的,因为虽然它经过了点 $2$,但是点 $2$ 在该手势中之前已经被连过了。 + - 有效手势:$[6,5,4,1,9,2]$,连接点 $1$ 和点 $9$ 是有效的,因为虽然它经过了按键 $5$,但是点 $5$ 在该手势中之前已经被连过了。 + +- $1 \le m, n \le 9$。 +- 如果经过的点不同或者经过点的顺序不同,表示为不同的解锁模式。 + +**示例**: + +- 示例 1: + +```python +输入:m = 1, n = 1 +输出:9 +``` + +- 示例 2: + +```python +输入:m = 1, n = 2 +输出:65 +``` + +## 解题思路 + +### 思路 1:状态压缩 + 记忆化搜索 + +因为手势解锁的界面是一个编号为 $1 \sim 9$、大小为 $3 \times 3$ 的网格,所以我们可以用一个 $9$ 位长度的二进制数 $state$ 来表示当前解锁模式中按键的选取情况。 + +因为解锁模式中两个点之间的手势轨迹不能跨过其他任何未被经过的点,所以我们可以预先使用一个哈希表 $graph$ 将手势轨迹跨过其他点的情况存储下来,便于判断当前手势轨迹是否有效。 + +接下来我们使用深度优先搜索方法,将所有有效的解锁模式统计出来,具体做法如下: + +1. 定义一个全局变量 $ans$ 用于统计所有有效的解锁模式的方案数。 +2. 定义一个深度优先搜索方法为 `def dfs(state, cur, step):`,表示当前键位选择情况为 $state$,从当前键位 $cur$ 出发,已经走了 $step$ 的有效解锁模式。 + 1. 当 $step$ 在区间 $[m, n]$ 中时,统计有效解锁模式方案数,即:令 $ans$ 加 $1$。 + 2. 当 $step$ 到达步数上限 $n$ 时,直接返回。 + 3. 遍历下一步(第 $step + 1$ 步)可选择的键位 $k$,判断键位 $k$ 是否有效。 + 4. 如果到达 $k$ 没有跨过其他键($k$ 不在 $graph[cur]$ 中),或者到达 $k$ 跨过的键位是已经经过的键 ($state >> graph[cur][k] \text{ \& } 1 == 1$),则继续调用 `dfs(state | (1 << k), k, step + 1)`,其中 `stete | (1 << k)` 表示下一步选择 $k$ 的状态。 +3. 遍历开始位置 $1 \sim 9$,从 1 ~ 9 每个数字开始出发,调用 `dfs(1 << i, i, 1)`,进行所有有效的解锁模式的统计。 +4. 最后输出 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def numberOfPatterns(self, m: int, n: int) -> int: + # 将手势轨迹跨过点的情况存入哈希表中 + graph = { + 1: {3: 2, 7: 4, 9: 5}, + 2: {8: 5}, + 3: {1: 2, 7: 5, 9: 6}, + 4: {6: 5}, + 5: {}, + 6: {4: 5}, + 7: {1: 4, 3: 5, 9: 8}, + 8: {2: 5}, + 9: {1: 5, 3: 6, 7: 8}, + } + + ans = 0 + + def dfs(state, cur, step): + nonlocal ans + if m <= step <= n: + ans += 1 + + if step == n: + return + + for k in range(1, 10): + if state >> k & 1 != 0: + continue + if k not in graph[cur] or state >> graph[cur][k] & 1: + dfs(state | (1 << k), k, step + 1) + + for i in range(1, 10): + dfs(1 << i, i, 1) # 从 1 ~ 9 每个数字开始出发 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n!)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- 【题解】[LeetCode-351. 安卓系统手势解锁 - mkdocs_blog](https://github.com/zhanguohao/mkdocs_blog/blob/mkdocs_blog/docs/problem/leetcode/LeetCode-351.%20%E5%AE%89%E5%8D%93%E7%B3%BB%E7%BB%9F%E6%89%8B%E5%8A%BF%E8%A7%A3%E9%94%81.md) diff --git a/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md b/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md new file mode 100644 index 00000000..6d0c1530 --- /dev/null +++ b/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md @@ -0,0 +1,85 @@ +# [0309. 买卖股票的最佳时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0309. 买卖股票的最佳时机含冷冻期 - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) + +## 题目大意 + +给定一个整数数组,其中第 `i` 个元素代表了第 `i` 天的股票价格 。 + +设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + +- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 +- 卖出股票后,你无法在第二天买入股票(即冷冻期为 `1` 天)。 + +## 解题思路 + +这道题是「[0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)」的升级版。 + +冷冻期的意思是:如果昨天卖出了,那么今天不能买。在考虑的时候只要判断一下前一天是不是刚卖出。 + +对于每一天结束时的状态总共有以下几种: + +- 买入状态: + - 今日买入 + - 之前买入,之后一直持有无操作 +- 卖出状态: + - 今日买出,正处于冷冻期 + - 昨天卖出,今天结束后度过了冷冻期 + - 之前卖出,度过了冷冻期后无操作 + +在买入状态中,今日买入和之前买入的状态其实可以看做是股票的持有状态,可以将其合并为一种状态。 + +在卖出状态中,昨天卖出和之前卖出的状态其实可以看做是无股票并度过了冷冻期状态,可以将其合并为一种状态。 + +这样总结下来可以划分为三个状态: + +- 股票的持有状态。 +- 无股票,并且处于冷冻期状态。 +- 无股票,并且不处于冷冻期状态。 + +所以我们可以定义状态 `dp[i][j]` ,表示为:第 `i` 天第 `j` 种情况(`0 <= j <= 2`)下,所获取的最大利润。 + +注意:这里第第 `j` 种情况, + +接下来确定状态转移公式: + +- 第 `0` 种状态(股票的持有状态)下可以有两种状态推出,取最大的那一种赋值: + - 昨天就已经持有的:`dp[i][0] = dp[i - 1][0]`: + - 今天刚买入的(则昨天不能持有股票也不能处于冷冻期,应来自于前天卖出状态):`dp[i][0] = dp[i - 1][2] - prices[i]` +- 第 `1` 种状态(无股票,并且处于冷冻期状态)下可以有一种状态推出: + - 今天卖出:`dp[i] = dp[i - 1][0] + prices[i]` +- 第 `2` 种状态(无股票,并且不处于冷冻期状态)下可以有两种状态推出,取最大的那一种赋值: + - 昨天卖出:`dp[i] = dp[i - 1][1]` + - 之前卖出:`dp[i] = dp[i - 1][2]` + +下面确定初始化的边界值: + +可以很明显看出第一天不做任何操作就是 `dp[0][0] = 0`,第一次买入就是 `dp[0][1] = -prices[i]`。 + +第一次卖出的话,可以视作为没有盈利(当天买卖,价格没有变化),即 `dp[0][2] = 0`。第二次买入的话,就是 `dp[0][3] = -prices[i]`。同理第二次卖出就是 `dp[0][4] = 0`。 + +在递推结束后,最大利润肯定是无操作、第一次卖出、第二次卖出这三种情况里边,且为最大值。我们在维护的时候维护的是最大值,则第一次卖出、第二次卖出所获得的利润肯定大于等于 0。而且,如果最优情况为一笔交易,那么在转移状态时,我们允许在一天内进行两次交易,则一笔交易的状态可以转移至两笔交易。所以最终答案为 `dp[size - 1][4]`。`size` 为股票天数。 + +## 代码 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + size = len(prices) + if size == 0: + return 0 + dp = [[0 for _ in range(4)] for _ in range(size)] + + dp[0][0] = -prices[0] + for i in range(1, size): + dp[i][0] = max(dp[i - 1][0], dp[i - 1][2] - prices[i]) + dp[i][1] = dp[i - 1][0] + prices[i] + dp[i][2] = max(dp[i - 1][1], dp[i - 1][2]) + return max(dp[size - 1][0], dp[size - 1][1], dp[size - 1][2]) +``` + diff --git a/docs/solutions/0300-0399/burst-balloons.md b/docs/solutions/0300-0399/burst-balloons.md new file mode 100644 index 00000000..859f66a7 --- /dev/null +++ b/docs/solutions/0300-0399/burst-balloons.md @@ -0,0 +1,103 @@ +# [0312. 戳气球](https://leetcode.cn/problems/burst-balloons/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/) + +## 题目大意 + +**描述**:有 $n$ 个气球,编号为 $0 \sim n - 1$,每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。 + +**要求**:求出能获得硬币的最大数量。 + +**说明**: + +- $n == nums.length$。 +- $1 \le n \le 300$。 +- $0 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,1,5,8] +输出:167 +解释: +nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] +coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 +``` + +- 示例 2: + +```python +输入:nums = [1,5] +输出:10 +解释: +nums = [1,5] --> [5] --> [] +coins = 1*1*5 + 1*5*1 = 10 +``` + +## 解题思路 + +### 思路 1:动态规划 + +根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 $1$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 $1$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 $0 \sim n + 1$。 + +对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 $1$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 $0 \sim n + 1$ 之间的所有气球(不包括编号 $0$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 + +###### 3. 状态转移方程 + +假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 + +$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: + +$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 $0$,即 $dp[i][j] = 0, \quad i \ge j - 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。。所以最终结果为 $dp[0][n + 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def maxCoins(self, nums: List[int]) -> int: + size = len(nums) + arr = [0 for _ in range(size + 2)] + arr[0] = arr[size + 1] = 1 + for i in range(1, size + 1): + arr[i] = nums[i - 1] + + dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] + + for l in range(3, size + 3): + for i in range(0, size + 2): + j = i + l - 1 + if j >= size + 2: + break + for k in range(i + 1, j): + dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) + + return dp[0][size + 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为气球数量。 +- **空间复杂度**:$O(n^2)$。 diff --git a/docs/solutions/0300-0399/coin-change.md b/docs/solutions/0300-0399/coin-change.md new file mode 100644 index 00000000..cd162862 --- /dev/null +++ b/docs/solutions/0300-0399/coin-change.md @@ -0,0 +1,146 @@ +# [0322. 零钱兑换](https://leetcode.cn/problems/coin-change/) + +- 标签:广度优先搜索、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0322. 零钱兑换 - 力扣](https://leetcode.cn/problems/coin-change/) + +## 题目大意 + +**描述**:给定代表不同面额的硬币数组 $coins$ 和一个总金额 $amount$。 + +**要求**:求出凑成总金额所需的最少的硬币个数。如果无法凑出,则返回 $-1$。 + +**说明**: + +- $1 \le coins.length \le 12$。 +- $1 \le coins[i] \le 2^{31} - 1$。 +- $0 \le amount \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:coins = [1, 2, 5], amount = 11 +输出:3 +解释:11 = 5 + 5 + 1 +``` + +- 示例 2: + +```python +输入:coins = [2], amount = 3 +输出:-1 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +我们可以从 $amount$ 开始,每次从 $coins$ 的硬币中选中 $1$ 枚硬币,并记录当前挑选硬币的次数。则最快减到 $0$ 的次数就是凑成总金额所需的最少的硬币个数。这道题就变成了从 $amount$ 减到 $0$ 的最短路径问题。我们可以用广度优先搜索的方法来做。 + +1. 定义 $visited$ 为标记已访问值的集合变量,$queue$ 为存放值的队列。 +2. 将 $amount$ 状态标记为访问,并将其加入队列 $queue$。 +3. 令当前步数加 $1$,然后将当前队列中的所有值依次出队,并遍历硬币数组: + 1. 如果当前值等于当前硬币值,则说明当前硬币刚好能凑成当前值,则直接返回当前次数。 + 2. 如果当前值大于当前硬币值,并且当前值减去当前硬币值的差值没有出现在已访问集合 $visited$ 中,则将差值添加到队列和访问集合中。 + +4. 重复执行第 $3$ 步,直到队列为空。 +5. 如果队列为空,也未能减到 $0$,则返回 $-1$。 + +### 思路 1:代码 + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + if amount == 0: + return 0 + + visited = set([amount]) + queue = collections.deque([amount]) + + step = 0 + while queue: + step += 1 + size = len(queue) + for _ in range(size): + cur = queue.popleft() + for coin in coins: + if cur == coin: + step += 1 + return step + elif cur > coin and cur - coin not in visited: + queue.append(cur - coin) + visited.add(cur - coin) + + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(amount \times size)$。其中 $amount$ 表示总金额,$size$ 表示硬币的种类数。 +- **空间复杂度**:$O(amount)$。 + +### 思路 2:完全背包问题 + +这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问恰好凑成总金额为 $amount$ 的背包,最少需要多少硬币? + +与普通完全背包问题不同的是,这里求解的是最少硬币数量。我们可以改变一下「状态定义」和「状态转移方程」。 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。 + +###### 3. 状态转移方程 + +$dp[c] = \begin{cases} dp[c] & c < coins[i - 1] \cr min \lbrace dp[c], dp[c - coins[i - 1]] + 1 \rbrace & c \ge coins[i - 1] \end{cases}$ + +1. 当 $c < coins[i - 1]$ 时: + 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 +2. 当 $c \ge coins[i - 1]$ 时,取下面两种情况中的较小值: + 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 + 2. 凑成金额 $c - coins[i - 1]$ 的最少硬币数量,再加上当前硬币的数量 $1$,即 $dp[c - coins[i - 1]] + 1$。 + +###### 4. 初始条件 + +- 凑成总金额为 $0$ 的最少硬币数量为 $0$,即 $dp[0] = 0$。 +- 默认情况下,在不使用硬币时,都不能恰好凑成总金额为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。则最终结果为 $dp[amount]$。 + +1. 如果 $dp[amount] \ne amount + 1$,则说明: $dp[amount]$ 为凑成金额 $amount$ 的最少硬币数量,则返回 $dp[amount]$。 +2. 如果 $dp[amount] = amount + 1$,则说明:无法凑成金额 $amount$,则返回 $-1$。 + +### 思路 2:代码 + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + size = len(coins) + dp = [(amount + 1) for _ in range(amount + 1)] + dp[0] = 0 + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 正序枚举背包装载重量 + for c in range(coins[i - 1], amount + 1): + dp[c] = min(dp[c], dp[c - coins[i - 1]] + 1) + + if dp[amount] != amount + 1: + return dp[amount] + return -1 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(amount \times size)$。其中 $amount$ 表示总金额,$size$ 表示硬币的种类数。 +- **空间复杂度**:$O(amount)$。 \ No newline at end of file diff --git a/docs/solutions/0300-0399/combination-sum-iv.md b/docs/solutions/0300-0399/combination-sum-iv.md new file mode 100644 index 00000000..82e3ba25 --- /dev/null +++ b/docs/solutions/0300-0399/combination-sum-iv.md @@ -0,0 +1,107 @@ +# [0377. 组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0377. 组合总和 Ⅳ - 力扣](https://leetcode.cn/problems/combination-sum-iv/) + +## 题目大意 + +**描述**:给定一个由不同整数组成的数组 $nums$ 和一个目标整数 $target$。 + +**要求**:从 $nums$ 中找出并返回总和为 $target$ 的元素组合个数。 + +**说明**: + +- 题目数据保证答案符合 32 位整数范围。 +- $1 \le nums.length \le 200$。 +- $1 \le nums[i] \le 1000$。 +- $nums$ 中的所有元素互不相同。 +- $1 \le target \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3], target = 4 +输出:7 +解释: +所有可能的组合为: +(1, 1, 1, 1) +(1, 1, 2) +(1, 2, 1) +(1, 3) +(2, 1, 1) +(2, 2) +(3, 1) +请注意,顺序不同的序列被视作不同的组合。 +``` + +- 示例 2: + +```python +输入:nums = [9], target = 3 +输出:0 +``` + +## 解题思路 + +### 思路 1:动态规划 + +「完全背包问题求方案数」的变形。本题与「完全背包问题求方案数」不同点在于:方案中不同的物品顺序代表不同方案。 + +比如「完全背包问题求方案数」中,凑成总和为 $4$ 的方案 $[1, 3]$ 算 $1$ 种方案,但是在本题中 $[1, 3]$、$[3, 1]$ 算 $2$ 种方案数。 + +我们需要在考虑某一总和 $w$ 时,需要将 $nums$ 中所有元素都考虑到。对应到循环关系时,即将总和 $w$ 的遍历放到外侧循环,将 $nums$ 数组元素的遍历放到内侧循环,即: + +```python +for w in range(target + 1): + for i in range(1, len(nums) + 1): + xxxx +``` + +###### 1. 划分阶段 + +按照总和进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:凑成总和 $w$ 的组合数。 + +###### 3. 状态转移方程 + +凑成总和为 $w$ 的组合数 = 「不使用当前 $nums[i - 1]$,只使用之前整数凑成和为 $w$ 的组合数」+「使用当前 $nums[i - 1]$ 凑成和为 $w - nums[i - 1]$ 的方案数」。即状态转移方程为:$dp[w] = dp[w] + dp[w - nums[i - 1]]$。 + +###### 4. 初始条件 + +- 凑成总和 $0$ 的组合数为 $1$,即 $dp[0] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:凑成总和 $w$ 的组合数。 所以最终结果为 $dp[target]$。 + +### 思路 1:代码 + +```python +class Solution: + def combinationSum4(self, nums: List[int], target: int) -> int: + size = len(nums) + dp = [0 for _ in range(target + 1)] + dp[0] = 1 + + for w in range(target + 1): + for i in range(1, size + 1): + if w >= nums[i - 1]: + dp[w] = dp[w] + dp[w - nums[i - 1]] + + return dp[target] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 为目标整数。 +- **空间复杂度**:$O(target)$。 + diff --git a/docs/solutions/0300-0399/count-numbers-with-unique-digits.md b/docs/solutions/0300-0399/count-numbers-with-unique-digits.md new file mode 100644 index 00000000..c9553448 --- /dev/null +++ b/docs/solutions/0300-0399/count-numbers-with-unique-digits.md @@ -0,0 +1,104 @@ +# [0357. 统计各位数字都不同的数字个数](https://leetcode.cn/problems/count-numbers-with-unique-digits/) + +- 标签:数学、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [0357. 统计各位数字都不同的数字个数 - 力扣](https://leetcode.cn/problems/count-numbers-with-unique-digits/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:统计并返回区间 $[0, 10^n)$ 上各位数字都不相同的数字 $x$ 的个数。 + +**说明**: + +- $0 \le n \le 8$。 +- $0 \le x < 10^n$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:91 +解释:答案应为除去 11、22、33、44、55、66、77、88、99 外,在 0 ≤ x < 100 范围内的所有数字。 +``` + +- 示例 2: + +```python +输入:n = 0 +输出:1 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +题目求解区间 $[0, 10^n)$ 范围内各位数字都不相同的数字个数。则我们先将 $10^n - 1$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 + 4. 开始时没有填写数字。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), + 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 + 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 + 3. $isNum == True$ 表示 $pos$ 位选择了数字。 +6. 最后的方案数为 `dfs(0, 0, True, False) + 1`,因为之前计算时没有考虑 $0$,所以最后统计方案数时要加 $1$。 + +### 思路 1:代码 + +```python +class Solution: + def countNumbersWithUniqueDigits(self, n: int) -> int: + s = str(10 ** n - 1) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + # d 不在选择的数字集合中,即之前没有选择过 d + if (state >> d) & 1 == 0: + ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) + return ans + + return dfs(0, 0, True, False) + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 10 \times 2^{10})$。 +- **空间复杂度**:$O(n \times 2^{10})$。 + diff --git a/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md b/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md new file mode 100644 index 00000000..7b759380 --- /dev/null +++ b/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md @@ -0,0 +1,180 @@ +# [0315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) + +- 标签:树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 +- 难度:困难 + +## 题目链接 + +- [0315. 计算右侧小于当前元素的个数 - 力扣](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 。 + +**要求**:返回一个新数组 $counts$ 。其中 $counts[i]$ 的值是 $nums[i]$ 右侧小于 $nums[i]$ 的元素的数量。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [5,2,6,1] +输出:[2,1,1,0] +解释: +5 的右侧有 2 个更小的元素 (2 和 1) +2 的右侧仅有 1 个更小的元素 (1) +6 的右侧有 1 个更小的元素 (1) +1 的右侧有 0 个更小的元素 +``` + +- 示例 2: + +```python +输入:nums = [-1] +输出:[0] +``` + +## 解题思路 + +### 思路 1:归并排序 + +在使用归并排序对数组进行排序时,每当遇到 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i] \le right\underline{\hspace{0.5em}}nums[right\underline{\hspace{0.5em}}i]$ 时,意味着:在合并前,左子数组当前元素 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 右侧一定有 $left\underline{\hspace{0.5em}}i$ 个元素比 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 小。则我们可以在归并排序的同时,记录 $nums[i]$ 右侧小于 $nums[i]$ 的元素的数量。 + +1. 将元素值、对应下标、右侧小于 nums[i] 的元素的数量存入数组中。 +2. 对其进行归并排序。 +3. 当遇到 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i] \le right\underline{\hspace{0.5em}}nums[right\underline{\hspace{0.5em}}i]$ 时,记录 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 右侧比 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 小的元素数量,即:`left_nums[left_i][2] += right_i`。 +4. 当合并时 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 仍有剩余时,说明 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 右侧有 $right\underline{\hspace{0.5em}}i$ 个小于 $left\underline{\hspace{0.5em}}nums[left\underline{\hspace{0.5em}}i]$ 的元素,记录下来,即:`left_nums[left_i][2] += right_i`。 +5. 根据下标及右侧小于 $nums[i]$ 的元素的数量,组合出答案数组,并返回答案数组。 + +### 思路 1:代码 + +```python +class Solution: + # 合并过程 + def merge(self, left_nums, right_nums): + nums = [] + left_i, right_i = 0, 0 + while left_i < len(left_nums) and right_i < len(right_nums): + # 将两个有序子数组中较小元素依次插入到结果数组中 + if left_nums[left_i] <= right_nums[right_i]: + nums.append(left_nums[left_i]) + # left_nums[left_i] 右侧有 right_i 个比 left_nums[left_i] 小的 + left_nums[left_i][2] += right_i + left_i += 1 + else: + nums.append(right_nums[right_i]) + right_i += 1 + + # 如果左子数组有剩余元素,则将其插入到结果数组中 + while left_i < len(left_nums): + nums.append(left_nums[left_i]) + # left_nums[left_i] 右侧有 right_i 个比 left_nums[left_i] 小的 + left_nums[left_i][2] += right_i + left_i += 1 + + # 如果右子数组有剩余元素,则将其插入到结果数组中 + while right_i < len(right_nums): + nums.append(right_nums[right_i]) + right_i += 1 + + # 返回合并后的结果数组 + return nums + + # 分解过程 + def mergeSort(self, nums) : + # 数组元素个数小于等于 1 时,直接返回原数组 + if len(nums) <= 1: + return nums + + mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 + left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 + right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 + return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 + + + def countSmaller(self, nums: List[int]) -> List[int]: + size = len(nums) + + # 将元素值、对应下标、右侧小于 nums[i] 的元素的数量存入数组中 + nums = [[num, i, 0] for i, num in enumerate(nums)] + nums = self.mergeSort(nums) + ans = [0 for _ in range(size)] + + for num in nums: + ans[num[1]] = num[2] + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:树状数组 + +1. 首先对数组进行离散化处理。把原始数组中的数据映射到 $[0, len(nums) - 1]$ 这个区间。 +2. 然后逆序顺序从数组 $nums$ 中遍历元素 $nums[i]$。 + 1. 计算其离散化后的排名 $index$,查询比 $index$ 小的数有多少个。将其记录到答案数组的对应位置 $ans[i]$ 上。 + 2. 然后在树状数组下标为 $index$ 的位置上,更新值为 $1$。 +3. 遍历完所有元素,最后输出答案数组 $ans$ 即可。 + +### 思路 2:代码 + +```python +import bisect + +class BinaryIndexTree: + + def __init__(self, n): + self.size = n + self.tree = [0 for _ in range(n + 1)] + + def lowbit(self, index): + return index & (-index) + + def update(self, index, delta): + while index <= self.size: + self.tree[index] += delta + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + +class Solution: + def countSmaller(self, nums: List[int]) -> List[int]: + size = len(nums) + if size == 0: + return [] + if size == 1: + return [0] + + # 离散化 + sort_nums = list(set(nums)) + sort_nums.sort() + size_s = len(sort_nums) + bit = BinaryIndexTree(size_s) + + ans = [0 for _ in range(size)] + for i in range(size - 1, -1, -1): + index = bisect.bisect_left(sort_nums, nums[i]) + 1 + ans[i] = bit.query(index - 1) + bit.update(index, 1) + + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0300-0399/counting-bits.md b/docs/solutions/0300-0399/counting-bits.md new file mode 100644 index 00000000..12962015 --- /dev/null +++ b/docs/solutions/0300-0399/counting-bits.md @@ -0,0 +1,90 @@ +# [0338. 比特位计数](https://leetcode.cn/problems/counting-bits/) + +- 标签:位运算、动态规划 +- 难度:简单 + +## 题目链接 + +- [0338. 比特位计数 - 力扣](https://leetcode.cn/problems/counting-bits/) + +## 题目大意 + +**描述**:给定一个整数 `n`。 + +**要求**:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 + +**说明**: + +- $0 \le n \le 10^5$。 +- 使用线性时间复杂度 $O(n)$ 解决此问题。 +- 不使用任何内置函数解决此问题。 + +**示例**: + +- 示例 1: + +```python +输入:n = 5 +输出:[0,1,1,2,1,2] +解释: +0 --> 0 +1 --> 1 +2 --> 10 +3 --> 11 +4 --> 100 +5 --> 101 +``` + +## 解题思路 + +### 思路 1:动态规划 + +根据整数的二进制特点可以将整数分为两类: + +- 奇数:其二进制表示中 $1$ 的个数一定比前面相邻的偶数多一个 $1$。 +- 偶数:其二进制表示中 $1$ 的个数一定与该数除以 $2$ 之后的数一样多。 + +另外,边界 $0$ 的二进制表示中 $1$ 的个数为 $0$。 + +于是可以根据规律,从 $0$ 开始到 $n$ 进行递推求解。 + +###### 1. 划分阶段 + +按照整数 $n$ 进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:整数 $i$ 对应二进制表示中 $1$ 的个数。 + +###### 3. 状态转移方程 + +- 如果 $i$ 为奇数,则整数 $i$ 对应二进制表示中 $1$ 的个数等于整数 $i - 1$ 对应二进制表示中 $1$ 的个数加 $1$,即 $dp[i] = dp[i - 1] + 1$。 +- 如果 $i$ 为偶数,则整数 $i$ 对应二进制表示中 $1$ 的个数等于整数 $i // 2$ 对应二进制表示中 $1$ 的个数,即 $dp[i] = dp[i // 2]$。 + +###### 4. 初始条件 + +整数 $0$ 对应二进制表示中 $1$ 的个数为 $0$。 + +###### 5. 最终结果 + +整个 $dp$ 数组即为最终结果,将其返回即可。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def countBits(self, n: int) -> List[int]: + dp = [0 for _ in range(n + 1)] + for i in range(1, n + 1): + if i % 2 == 1: + dp[i] = dp[i - 1] + 1 + else: + dp[i] = dp[i // 2] + return dp +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一位数组保存状态,所以总的时间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0300-0399/decode-string.md b/docs/solutions/0300-0399/decode-string.md new file mode 100644 index 00000000..a31f3b37 --- /dev/null +++ b/docs/solutions/0300-0399/decode-string.md @@ -0,0 +1,83 @@ +# [0394. 字符串解码](https://leetcode.cn/problems/decode-string/) + +- 标签:栈、递归、字符串 +- 难度:中等 + +## 题目链接 + +- [0394. 字符串解码 - 力扣](https://leetcode.cn/problems/decode-string/) + +## 题目大意 + +**描述**:给定一个经过编码的字符串 `s`。 + +**要求**:返回 `s` 经过解码之后的字符串。 + +**说明**: + +- 编码规则:`k[encoded_string]`。`encoded_string` 为字符串,`k` 为整数。表示字符串 `encoded_string` 重复 `k` 次。 +- $1 \le s.length \le 30$。 +- `s` 由小写英文字母、数字和方括号 `[]` 组成。 +- `s` 保证是一个有效的输入。 +- `s` 中所有整数的取值范围为 $[1, 300]$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "3[a]2[bc]" +输出:"aaabcbc" +``` + +- 示例 2: + +```python +输入:s = "3[a2[c]]" +输出:"accaccacc" +``` + +## 解题思路 + +### 思路 1:栈 + +1. 使用两个栈 `stack1`、`stack2`。`stack1` 用来保存左括号前已经解码的字符串,`stack2` 用来存储左括号前的数字。 +2. 用 `res` 存储待解码的字符串、`num` 存储当前数字。 +3. 遍历字符串。 + 1. 如果遇到数字,则累加数字到 `num`。 + 2. 如果遇到左括号,将当前待解码字符串入栈 `stack1`,当前数字入栈 `stack2`,然后将 `res`、`nums` 清空。 + 3. 如果遇到右括号,则从 `stack1` 的取出待解码字符串 `res`,从 `stack2` 中取出当前数字 `num`,将其解码拼合成字符串赋值给 `res`。 + 4. 如果遇到其他情况(遇到字母),则将当前字母加入 `res` 中。 +4. 遍历完输出解码之后的字符串 `res`。 + +### 思路 1:代码 + +```python +class Solution: + def decodeString(self, s: str) -> str: + stack1 = [] + stack2 = [] + num = 0 + res = "" + for ch in s: + if ch.isdigit(): + num = num * 10 + int(ch) + elif ch == '[': + stack1.append(res) + stack2.append(num) + res = "" + num = 0 + elif ch == ']': + cur_res = stack1.pop() + cur_num = stack2.pop() + res = cur_res + res * cur_num + else: + res += ch + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0300-0399/evaluate-division.md b/docs/solutions/0300-0399/evaluate-division.md new file mode 100644 index 00000000..078058c3 --- /dev/null +++ b/docs/solutions/0300-0399/evaluate-division.md @@ -0,0 +1,158 @@ +# [0399. 除法求值](https://leetcode.cn/problems/evaluate-division/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图、数组、最短路 +- 难度:中等 + +## 题目链接 + +- [0399. 除法求值 - 力扣](https://leetcode.cn/problems/evaluate-division/) + +## 题目大意 + +**描述**:给定一个变量对数组 $equations$ 和一个实数数组 $values$ 作为已知条件,其中 $equations[i] = [Ai, Bi]$ 和 $values[i]$ 共同表示 `Ai / Bi = values[i]`。每个 $Ai$ 或 $Bi$ 是一个表示单个变量的字符串。 + +再给定一个表示多个问题的数组 $queries$,其中 $queries[j] = [Cj, Dj]$ 表示第 $j$ 个问题,要求:根据已知条件找出 `Cj / Dj = ?` 的结果作为答案。 + +**要求**:返回所有问题的答案。如果某个答案无法确定,则用 $-1.0$ 代替,如果问题中出现了给定的已知条件中没有出现的表示变量的字符串,则也用 $-1.0$ 代替这个答案。 + +**说明**: + +- 未在等式列表中出现的变量是未定义的,因此无法确定它们的答案。 +- $1 \le equations.length \le 20$。 +- $equations[i].length == 2$。 +- $1 \le Ai.length, Bi.length \le 5$。 +- $values.length == equations.length$。 +- $0.0 < values[i] \le 20.0$。 +- $1 \le queries.length \le 20$。 +- $queries[i].length == 2$。 +- $1 \le Cj.length, Dj.length \le 5$。 +- $Ai, Bi, Cj, Dj$ 由小写英文字母与数字组成。 + +**示例**: + +- 示例 1: + +```python +输入:equations = [["a","b"],["b","c"]], values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]] +输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000] +解释: +条件:a / b = 2.0, b / c = 3.0 +问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? +结果:[6.0, 0.5, -1.0, 1.0, -1.0 ] +注意:x 是未定义的 => -1.0 +``` + +- 示例 2: + +```python +输入:equations = [["a","b"],["b","c"],["bc","cd"]], values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]] +输出:[3.75000,0.40000,5.00000,0.20000] +``` + +## 解题思路 + +### 思路 1:并查集 + +在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」的基础上增加了倍数关系。在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」中我们处理传递关系使用了并查集,这道题也是一样,不过在使用并查集的同时还要维护倍数关系。 + +举例说明: + +- `a / b = 2.0`:说明 $a == 2b$,$a$ 和 $b$ 在同一个集合。 +- `b / c = 3.0`:说明 $b == 3c$,$b$ 和 $c$ 在同一个集合。 + +根据上述两式可得:$a$、$b$、$c$ 都在一个集合中,且 $a == 2b == 6c$。 + +我们可以将同一集合中的变量倍数关系都转换为与根节点变量的倍数关系,比如上述例子中都转变为与 $a$ 的倍数关系。 + +具体操作如下: + +- 定义并查集结构,并在并查集中定义一个表示倍数关系的 $multiples$ 数组。 +- 遍历 $equations$ 数组、$values$ 数组,将每个变量按顺序编号,并使用 `union` 将其并入相同集合。 +- 遍历 $queries$ 数组,判断两个变量是否在并查集中,并且是否在同一集合。如果找到对应关系,则将计算后的倍数关系存入答案数组,否则则将 $-1$ 存入答案数组。 +- 最终输出答案数组。 + +并查集中维护倍数相关方法说明: + +- `find` 方法: + - 递推寻找根节点,并将倍数累乘,然后进行路径压缩,并且更新当前节点的倍数关系。 +- `union` 方法: + - 如果两个节点属于同一集合,则直接返回。 + - 如果两个节点不属于同一个集合,合并之前当前节点的倍数关系更新,然后再进行更新。 +- `is_connected` 方法: + - 如果两个节点不属于同一集合,返回 $-1$。 + - 如果两个节点属于同一集合,则返回倍数关系。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.multiples = [1 for _ in range(n)] + + def find(self, x): + multiple = 1.0 + origin = x + while x != self.parent[x]: + multiple *= self.multiples[x] + x = self.parent[x] + self.parent[origin] = x + self.multiples[origin] = multiple + return x + + def union(self, x, y, multiple): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + self.parent[root_x] = root_y + self.multiples[root_x] = multiple * self.multiples[y] / self.multiples[x] + return + + def is_connected(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x != root_y: + return -1.0 + + return self.multiples[x] / self.multiples[y] + +class Solution: + def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: + equations_size = len(equations) + hash_map = dict() + union_find = UnionFind(2 * equations_size) + + id = 0 + for i in range(equations_size): + equation = equations[i] + var1, var2 = equation[0], equation[1] + if var1 not in hash_map: + hash_map[var1] = id + id += 1 + if var2 not in hash_map: + hash_map[var2] = id + id += 1 + union_find.union(hash_map[var1], hash_map[var2], values[i]) + + queries_size = len(queries) + res = [] + for i in range(queries_size): + query = queries[i] + var1, var2 = query[0], query[1] + if var1 not in hash_map or var2 not in hash_map: + res.append(-1.0) + else: + id1 = hash_map[var1] + id2 = hash_map[var2] + res.append(union_find.is_connected(id1, id2)) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O((m + n) \times \alpha(m + n))$,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(m + n)$。 + diff --git a/docs/solutions/0300-0399/find-the-difference.md b/docs/solutions/0300-0399/find-the-difference.md new file mode 100644 index 00000000..6762a864 --- /dev/null +++ b/docs/solutions/0300-0399/find-the-difference.md @@ -0,0 +1,36 @@ +# [0389. 找不同](https://leetcode.cn/problems/find-the-difference/) + +- 标签:位运算、哈希表、字符串、排序 +- 难度:简单 + +## 题目链接 + +- [0389. 找不同 - 力扣](https://leetcode.cn/problems/find-the-difference/) + +## 题目大意 + +给定两个只包含小写字母的字符串 s、t。字符串 t 是由 s 进行随机重拍之后,再在随机位置添加一个字母得到的。要求:找出字符串 t 中被添加的字母。 + +## 解题思路 + +字符串 t 比字符串 s 多了一个随机字母。可以使用哈希表存储一下字符串 s 中各个字符的数量,再遍历一遍字符串 t 中的字符,从哈希表中减去对应数量的字符,最后剩的那一个字符就是多余的字符。 + +## 代码 + +```python +class Solution: + def findTheDifference(self, s: str, t: str) -> str: + s_dict = dict() + for ch in s: + if ch in s_dict: + s_dict[ch] += 1 + else: + s_dict[ch] = 1 + + for ch in t: + if ch in s_dict and s_dict[ch] != 0: + s_dict[ch] -= 1 + else: + return ch +``` + diff --git a/docs/solutions/0300-0399/first-unique-character-in-a-string.md b/docs/solutions/0300-0399/first-unique-character-in-a-string.md new file mode 100644 index 00000000..01a2c917 --- /dev/null +++ b/docs/solutions/0300-0399/first-unique-character-in-a-string.md @@ -0,0 +1,38 @@ +# [0387. 字符串中的第一个唯一字符](https://leetcode.cn/problems/first-unique-character-in-a-string/) + +- 标签:队列、哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [0387. 字符串中的第一个唯一字符 - 力扣](https://leetcode.cn/problems/first-unique-character-in-a-string/) + +## 题目大意 + +给定一个只包含小写字母的字符串 `s`。 + +要求:找到第一个不重复的字符,并返回它的索引。 + +## 解题思路 + +遍历字符串,使用哈希表存储字符串中每个字符的出现次数。然后第二次遍历时,找出只出现一次的字符。 + +## 代码 + +```python +class Solution: + def firstUniqChar(self, s: str) -> int: + strDict = dict() + for i in range(len(s)): + if s[i] in strDict: + strDict[s[i]] += 1 + else: + strDict[s[i]] = 1 + + for i in range(len(s)): + if s[i] in strDict and strDict[s[i]] == 1: + return i + return -1 +``` + +- 思路 2 代码: diff --git a/docs/solutions/0300-0399/flatten-nested-list-iterator.md b/docs/solutions/0300-0399/flatten-nested-list-iterator.md new file mode 100644 index 00000000..0940327e --- /dev/null +++ b/docs/solutions/0300-0399/flatten-nested-list-iterator.md @@ -0,0 +1,58 @@ +# [0341. 扁平化嵌套列表迭代器](https://leetcode.cn/problems/flatten-nested-list-iterator/) + +- 标签:栈、树、深度优先搜索、设计、队列、迭代器 +- 难度:中等 + +## 题目链接 + +- [0341. 扁平化嵌套列表迭代器 - 力扣](https://leetcode.cn/problems/flatten-nested-list-iterator/) + +## 题目大意 + +给定一个嵌套的整数列表 `nestedList` 。列表中元素类型为 NestedInteger 类。每个元素(NestedInteger 对象)要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。 + +NestedInteger 类提供了三个方法: + +- `isInteger()`,判断当前存储的对象是否为 int; +- `getInteger()` ,如果当前存储的元素是 int 型的,那么返回当前的结果 int,否则调用会失败; +- `getList()`,如果当前存储的元素是 `List` 型的,那么返回该 List,否则调用会失败。 + +要求:实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。 + +实现扁平迭代器类 NestedIterator: + +- `NestedIterator(List nestedList)` 用嵌套列表 `nestedList` 初始化迭代器。 +- `int next()` 返回嵌套列表的下一个整数。 +- `boolean hasNext()` 如果仍然存在待迭代的整数,返回 `True`;否则,返回 `False`。 + +## 解题思路 + +初始化时不对元素进行预处理。而是将所有的 `NestedInteger` 逆序放到栈中,当需要展开的时候才进行展开。 + +## 代码 + +```python +class NestedIterator: + def __init__(self, nestedList: [NestedInteger]): + self.stack = [] + size = len(nestedList) + for i in range(size - 1, -1, -1): + self.stack.append(nestedList[i]) + + + def next(self) -> int: + cur = self.stack.pop() + return cur.getInteger() + + + def hasNext(self) -> bool: + while self.stack: + cur = self.stack[-1] + if cur.isInteger(): + return True + self.stack.pop() + for i in range(len(cur.getList()) - 1, -1, -1): + self.stack.append(cur.getList()[i]) + return False +``` + diff --git a/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md b/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md new file mode 100644 index 00000000..d63c784c --- /dev/null +++ b/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md @@ -0,0 +1,140 @@ +# [0375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) + +- 标签:数学、动态规划、博弈 +- 难度:中等 + +## 题目链接 + +- [0375. 猜数字大小 II - 力扣](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/) + +## 题目大意 + +**描述**:现在两个人来玩一个猜数游戏,游戏规则如下: + +1. 对方从 $1 \sim n$ 中选择一个数字。 +2. 我们来猜对方选了哪个数字。 +3. 如果我们猜到了正确数字,就会赢得游戏。 +4. 如果我们猜错了,那么对方就会告诉我们,所选的数字比我们猜的数字更大或者更小,并且需要我们继续猜数。 +5. 每当我们猜了数字 $x$ 并且猜错了的时候,我们需要支付金额为 $x$ 的现金。如果我们花光了钱,就会输掉游戏。 + +现在给定一个特定数字 $n$。 + +**要求**:返回能够确保我们获胜的最小现金数(不管对方选择哪个数字)。 + +**说明**: + +- $1 \le n \le 200$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/09/10/graph.png) + +```python +输入:n = 10 +输出:16 +解释:制胜策略如下: +- 数字范围是 [1,10]。你先猜测数字为 7 。 + - 如果这是我选中的数字,你的总费用为 $0。否则,你需要支付 $7。 + - 如果我的数字更大,则下一步需要猜测的数字范围是 [8, 10] 。你可以猜测数字为 9。 + - 如果这是我选中的数字,你的总费用为 $7。否则,你需要支付 $9。 + - 如果我的数字更大,那么这个数字一定是 10。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16。 + - 如果我的数字更小,那么这个数字一定是 8。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16。 + - 如果我的数字更小,则下一步需要猜测的数字范围是 [1, 6]。你可以猜测数字为 3。 + - 如果这是我选中的数字,你的总费用为 $7。否则,你需要支付 $3。 + - 如果我的数字更大,则下一步需要猜测的数字范围是 [4, 6]。你可以猜测数字为 5。 + - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5。 + - 如果我的数字更大,那么这个数字一定是 6。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15。 + - 如果我的数字更小,那么这个数字一定是 4。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15。 + - 如果我的数字更小,则下一步需要猜测的数字范围是 [1, 2]。你可以猜测数字为 1。 + - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10。否则,你需要支付 $1。 + - 如果我的数字更大,那么这个数字一定是 2。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11。 +在最糟糕的情况下,你需要支付 $16。因此,你只需要 $16 就可以确保自己赢得游戏。 +``` + +- 示例 2: + +```python +输入:n = 2 +输出:1 +解释:有两个可能的数字 1 和 2 。 +- 你可以先猜 1 。 + - 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $1 。 + - 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $1 。 +最糟糕的情况下,你需要支付 $1。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +直觉上这道题应该通过二分查找来求解,但实际上并不能通过二分查找来求解。 + +因为我们可以通过二分查找方法,能够找到猜中的最小次数,但这个猜中的最小次数所对应的支付金额,并不是最小现金数。 + +也就是说,通过二分查找的策略,并不能找到确保我们获胜的最小现金数。所以我们需要转换思路。 + +我们可以用递归的方式来思考。 + +对于 $1 \sim n$ 中每一个数 $x$: + +1. 如果 $x$ 恰好是正确数字,则获胜,付出的现金数为 $0$。 +2. 如果 $x$ 不是正确数字,则付出现金数为 $x$,同时我们得知,正确数字比 $x$ 更大还是更小。 + 1. 如果正确数字比 $x$ 更小,我们只需要求出 $1 \sim x - 1$ 中能够获胜的最小现金数,再加上 $x$ 就是确保我们获胜的最小现金数。 + 2. 如果正确数字比 $x$ 更大,我们只需要求出 $x + 1 \sim n$ 中能够获胜的最小现金数,再加上 $x$ 就是确保我们获胜的最小现金数。 + 3. 因为正确数字可能比 $x$ 更小,也可能比 $x$ 更大。在考虑最坏情况下也能获胜,我们需要准备的最小现金应该为两种情况下的最小代价的最大值,再加上 $x$ 本身。 + +我们可以通过枚举 $x$,并求出所有情况下的最小值,即为确保我们获胜的最小现金数。 + +我们可以定义一个方法 $f(1)(n)$ 来表示 $1 \sim n$ 中能够获胜的最小现金数,则可以得到递推公式:$f(1)(n) = min_{x = 1}^{x = n} \lbrace max \lbrace f(1)(x - 1), f(x + 1)(n) \rbrace + x \rbrace)$。 + +将递推公式应用到 $i \sim j$ 中,可得:$f(i)(j) = min_{x = i}^{x = j} \lbrace max \lbrace f(i)(x - 1), f(x + 1)(j) \rbrace + x \rbrace)$ + +接下来我们就可以通过动态规划的方式解决这道题了。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:数字 $i \sim j$ 中能够确保我们获胜的最小现金数。 + +###### 3. 状态转移方程 + +$dp[i][j] = min_{x = i}^{x = j} \lbrace max \lbrace dp[i][x - 1], dp[x + 1][j] \rbrace + x \rbrace)$ + +###### 4. 初始条件 + +- 默认数字 $i \sim j$ 中能够确保我们获胜的最小现金数为无穷大。 +- 当区间长度为 $1$ 时,区间中只有 $1$ 个数,肯定为正确数字,则付出最小现金数为 $0$,即 $dp[i][i] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:数字 $i \sim j$ 中能够确保我们获胜的最小现金数。所以最终结果为 $dp[1][n]$。 + +### 思路 1:代码 + +```python +class Solution: + def getMoneyAmount(self, n: int) -> int: + dp = [[0 for _ in range(n + 2)] for _ in range(n + 2)] + for l in range(2, n + 1): + for i in range(1, n + 1): + j = i + l - 1 + if j > n: + break + dp[i][j] = float('inf') + for k in range(i, j): + dp[i][j] = min(dp[i][j], max(dp[i][k - 1] + k, dp[k + 1][j] + k)) + + return dp[1][n] + +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0300-0399/guess-number-higher-or-lower.md b/docs/solutions/0300-0399/guess-number-higher-or-lower.md new file mode 100644 index 00000000..5a3a652f --- /dev/null +++ b/docs/solutions/0300-0399/guess-number-higher-or-lower.md @@ -0,0 +1,71 @@ +# [0374. 猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) + +- 标签:二分查找、交互 +- 难度:简单 + +## 题目链接 + +- [0374. 猜数字大小 - 力扣](https://leetcode.cn/problems/guess-number-higher-or-lower/) + +## 题目大意 + +**描述**:猜数字游戏。给定一个整数 $n$ 和一个接口 `def guess(num: int) -> int:`,题目会从 $1 \sim n$ 中随机选取一个数 $x$。我们只能通过调用接口来判断自己猜测的数是否正确。 + +**要求**:要求返回题目选取的数字 $x$。 + +**说明**: + +- `def guess(num: int) -> int:` 返回值: + - $-1$:我选出的数字比你猜的数字小,即 $pick < num$; + - $1$:我选出的数字比你猜的数字大 $pick > num$; + - $0$:我选出的数字和你猜的数字一样。恭喜!你猜对了!$pick == num$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 10, pick = 6 +输出:6 +``` + +- 示例 2: + +```python +输入:n = 1, pick = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:二分查找 + +利用两个指针 $left$、$right$。$left$ 指向数字 $1$,$right$ 指向数字 $n$。每次从中间开始调用接口猜测是否正确。 + +- 如果猜测的数比选中的数大,则将 $right$ 向左移,令 `right = mid - 1`,继续从中间调用接口猜测; +- 如果猜测的数比选中的数小,则将 $left$ 向右移,令 `left = mid + 1`,继续从中间调用的接口猜测; +- 如果猜测正确,则直接返回该数。 + +### 思路 1:二分查找代码 + +```python +class Solution: + def guessNumber(self, n: int) -> int: + left = 1 + right = n + while left <= right: + mid = left + (right - left) // 2 + ans = guess(mid) + if ans == 1: + left = mid + 1 + elif ans == -1: + right = mid - 1 + else: + return mid + return 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 diff --git a/docs/solutions/0300-0399/house-robber-iii.md b/docs/solutions/0300-0399/house-robber-iii.md new file mode 100644 index 00000000..b349e40c --- /dev/null +++ b/docs/solutions/0300-0399/house-robber-iii.md @@ -0,0 +1,59 @@ +# [0337. 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii/) + +- 标签:树、深度优先搜索、动态规划、二叉树 +- 难度:中等 + +## 题目链接 + +- [0337. 打家劫舍 III - 力扣](https://leetcode.cn/problems/house-robber-iii/) + +## 题目大意 + +小偷发现了一个新的可行窃的地区,这个地区的形状是一棵二叉树。这个地区只有一个入口,称为「根」。除了「根」之外,每栋房子只有一个「父」房子与之相连。如果两个直接相连的房子在同一天被打劫,房屋将自动报警。 + +现在给定这个代表地区房间的二叉树,每个节点值代表该房间所拥有的金额。要求计算在不触动警报的情况下,小偷一晚上能盗取的最高金额。 + +## 解题思路 + +树形动态规划问题。 + +对于当前节点 `cur`,不能选择子节点,也不能选择父节点。所以对于一棵子树来说,有两种情况: + +- 选择了根节点 +- 没有选择根节点 + +### 1. 选择根节点 + +如果选择了根节点,则不能再选择左右儿子节点,这种情况下的最大值为:当前节点 + 左子树不选择根节点 + 右子树不选择根节点。 + +### 2. 不选择根节点 + +如果不选择根节点,则可以选择左右儿子节点,共四种可能: + +- 左子树选择根节点 + 右子树选择根节点 +- 左子树选择根节点 + 右子树不选根节点 +- 左子树不选根节点 + 右子树选择根节点 +- 左子树不选根节点 + 右子树不选根节点 + +选择其中最大值。 + +上述描述中,当前节点的选择来自于子节点信息的选择,然后逐层向上,直到根节点。所以我们使用「后序遍历」的方式进行递归遍历。 + +## 代码 + +```python +class Solution: + def dfs(self, root: TreeNode): + if not root: + return [0, 0] + left = self.dfs(root.left) + right = self.dfs(root.right) + + val_steal = root.val + left[1] + right[1] + val_no_steal = max(left[0], left[1]) + max(right[0], right[1]) + return [val_steal, val_no_steal] + def rob(self, root: TreeNode) -> int: + res = self.dfs(root) + return max(res[0], res[1]) +``` + diff --git a/docs/solutions/0300-0399/increasing-triplet-subsequence.md b/docs/solutions/0300-0399/increasing-triplet-subsequence.md new file mode 100644 index 00000000..86f080b1 --- /dev/null +++ b/docs/solutions/0300-0399/increasing-triplet-subsequence.md @@ -0,0 +1,86 @@ +# [0334. 递增的三元子序列](https://leetcode.cn/problems/increasing-triplet-subsequence/) + +- 标签:贪心、数组 +- 难度:中等 + +## 题目链接 + +- [0334. 递增的三元子序列 - 力扣](https://leetcode.cn/problems/increasing-triplet-subsequence/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:判断数组中是否存在长度为 3 的递增子序列。 + +**说明**: + +- 要求算法时间复杂度为 $O(n)$、空间复杂度为 $O(1)$。 +- **长度为 $3$ 的递增子序列**:存在这样的三元组下标 ($i$, $j$, $k$) 且满足 $i < j < k$ ,使得 $nums[i] < nums[j] < nums[k]$。 +- $1 \le nums.length \le 5 \times 10^5$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4,5] +输出:true +解释:任何 i < j < k 的三元组都满足题意 +``` + +- 示例 2: + +```python +输入:nums = [5,4,3,2,1] +输出:false +解释:不存在满足题意的三元组 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +常规方法是三重 `for` 循环遍历三个数,但是时间复杂度为 $O(n^3)$,肯定会超时的。 + +那么如何才能只进行一次遍历,就找到长度为 3 的递增子序列呢? + +假设长度为 3 的递增子序列元素为 $a$、$b$、$c$,$a < b < c$。 + +先来考虑 $a$ 和 $b$。如果我们要使得一个数组 $i < j$,并且 $nums[i] < nums[j]$。那么应该使得 $a$ 尽可能的小,这样子我们下一个数字 $b$ 才可以尽可能地满足条件。 + +同样对于 $b$ 和 $c$,也应该使得 $b$ 尽可能的小,下一个数字 $c$ 才可以尽可能的满足条件。 + +所以,我们的目的是:在 $a < b$ 的前提下,保证 a 尽可能小。在 $b < c$ 的条件下,保证 $b$ 尽可能小。 + +我们可以使用两个数 $a$、$b$ 指向无穷大。遍历数组: + +- 如果当前数字小于等于 $a$ ,则更新 `a = num`; +- 如果当前数字大于等于 $a$,则说明当前数满足 $num > a$,则判断: + - 如果 $num \le b$,则更新 `b = num`; + - 如果 $num > b$,则说明找到了长度为 3 的递增子序列,直接输出 $True$。 +- 如果遍历完仍未找到,则输出 $False$。 + +### 思路 1:代码 + +```python +class Solution: + def increasingTriplet(self, nums: List[int]) -> bool: + a = float('inf') + b = float('inf') + for num in nums: + if num <= a: + a = num + elif num <= b: + b = num + else: + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0300-0399/index.md b/docs/solutions/0300-0399/index.md new file mode 100644 index 00000000..6bd837d2 --- /dev/null +++ b/docs/solutions/0300-0399/index.md @@ -0,0 +1,56 @@ +## 本章内容 + +- [0300. 最长递增子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-subsequence.md) +- [0303. 区域和检索 - 数组不可变](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-immutable.md) +- [0304. 二维区域和检索 - 矩阵不可变](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-2d-immutable.md) +- [0307. 区域和检索 - 数组可修改](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-sum-query-mutable.md) +- [0309. 买卖股票的最佳时机含冷冻期](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/best-time-to-buy-and-sell-stock-with-cooldown.md) +- [0310. 最小高度树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/minimum-height-trees.md) +- [0312. 戳气球](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/burst-balloons.md) +- [0315. 计算右侧小于当前元素的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-of-smaller-numbers-after-self.md) +- [0316. 去除重复字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/remove-duplicate-letters.md) +- [0318. 最大单词长度乘积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/maximum-product-of-word-lengths.md) +- [0322. 零钱兑换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/coin-change.md) +- [0323. 无向图中连通分量的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md) +- [0324. 摆动排序 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/wiggle-sort-ii.md) +- [0326. 3 的幂](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/power-of-three.md) +- [0328. 奇偶链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/odd-even-linked-list.md) +- [0329. 矩阵中的最长递增路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md) +- [0334. 递增的三元子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/increasing-triplet-subsequence.md) +- [0336. 回文对](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/palindrome-pairs.md) +- [0337. 打家劫舍 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/house-robber-iii.md) +- [0338. 比特位计数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/counting-bits.md) +- [0340. 至多包含 K 个不同字符的最长子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md) +- [0341. 扁平化嵌套列表迭代器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/flatten-nested-list-iterator.md) +- [0342. 4的幂](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/power-of-four.md) +- [0343. 整数拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/integer-break.md) +- [0344. 反转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-string.md) +- [0345. 反转字符串中的元音字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/reverse-vowels-of-a-string.md) +- [0346. 数据流中的移动平均值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/moving-average-from-data-stream.md) +- [0347. 前 K 个高频元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/top-k-frequent-elements.md) +- [0349. 两个数组的交集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays.md) +- [0350. 两个数组的交集 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md) +- [0351. 安卓系统手势解锁](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/android-unlock-patterns.md) +- [0354. 俄罗斯套娃信封问题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/russian-doll-envelopes.md) +- [0357. 统计各位数字都不同的数字个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/count-numbers-with-unique-digits.md) +- [0359. 日志速率限制器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/logger-rate-limiter.md) +- [0360. 有序转化数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sort-transformed-array.md) +- [0367. 有效的完全平方数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/valid-perfect-square.md) +- [0370. 区间加法](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/range-addition.md) +- [0371. 两整数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/sum-of-two-integers.md) +- [0374. 猜数字大小](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower.md) +- [0375. 猜数字大小 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/guess-number-higher-or-lower-ii.md) +- [0376. 摆动序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/wiggle-subsequence.md) +- [0377. 组合总和 Ⅳ](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/combination-sum-iv.md) +- [0378. 有序矩阵中第 K 小的元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix.md) +- [0380. O(1) 时间插入、删除和获取随机元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/insert-delete-getrandom-o1.md) +- [0383. 赎金信](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/ransom-note.md) +- [0384. 打乱数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/shuffle-an-array.md) +- [0386. 字典序排数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/lexicographical-numbers.md) +- [0387. 字符串中的第一个唯一字符](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/first-unique-character-in-a-string.md) +- [0389. 找不同](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/find-the-difference.md) +- [0391. 完美矩形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/perfect-rectangle.md) +- [0392. 判断子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/is-subsequence.md) +- [0394. 字符串解码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/decode-string.md) +- [0395. 至少有 K 个重复字符的最长子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters.md) +- [0399. 除法求值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/evaluate-division.md) diff --git a/docs/solutions/0300-0399/insert-delete-getrandom-o1.md b/docs/solutions/0300-0399/insert-delete-getrandom-o1.md new file mode 100644 index 00000000..67fafee2 --- /dev/null +++ b/docs/solutions/0300-0399/insert-delete-getrandom-o1.md @@ -0,0 +1,72 @@ +# [0380. 常数时间插入、删除和获取随机元素](https://leetcode.cn/problems/insert-delete-getrandom-o1/) + +- 标签:设计、数组、哈希表、数学、随机化 +- 难度:中等 + +## 题目链接 + +- [0380. 常数时间插入、删除和获取随机元素 - 力扣](https://leetcode.cn/problems/insert-delete-getrandom-o1/) + +## 题目大意 + +设计一个数据结构 ,支持时间复杂度为 O(1) 的以下操作: + +- insert(val):当元素 val 不存在时,向集合中插入该项。 +- remove(val):元素 val 存在时,从集合中移除该项。 +- getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 + +## 解题思路 + +普通动态数组进行访问操作,需要线性时间查找解决。我们可以利用哈希表记录下每个元素的下标,这样在访问时可以做到常数时间内访问元素了。对应的插入、删除、后去随机元素需要做相应的变化。 + +- 插入操作:将元素直接插入到数组尾部,并用哈希表记录插入元素的下标位置。 +- 删除操作:使用哈希表找到待删除元素所在位置,将其与数组末尾位置元素相互交换,更新哈希表中交换后元素的下标值,并将末尾元素删除。 +- 获取随机元素:使用` random.choice` 获取。 + +## 代码 + +```python +import random + +class RandomizedSet: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.dict = dict() + self.list = list() + + + def insert(self, val: int) -> bool: + """ + Inserts a value to the set. Returns true if the set did not already contain the specified element. + """ + if val in self.dict: + return False + self.dict[val] = len(self.list) + self.list.append(val) + return True + + def remove(self, val: int) -> bool: + """ + Removes a value from the set. Returns true if the set contained the specified element. + """ + if val in self.dict: + idx = self.dict[val] + last = self.list[-1] + self.list[idx] = last + self.dict[last] = idx + self.list.pop() + self.dict.pop(val) + return True + return False + + + def getRandom(self) -> int: + """ + Get a random element from the set. + """ + return random.choice(self.list) +``` + diff --git a/docs/solutions/0300-0399/integer-break.md b/docs/solutions/0300-0399/integer-break.md new file mode 100644 index 00000000..3b7181e8 --- /dev/null +++ b/docs/solutions/0300-0399/integer-break.md @@ -0,0 +1,87 @@ +# [0343. 整数拆分](https://leetcode.cn/problems/integer-break/) + +- 标签:数学、动态规划 +- 难度:中等 + +## 题目链接 + +- [0343. 整数拆分 - 力扣](https://leetcode.cn/problems/integer-break/) + +## 题目大意 + +**描述**:给定一个正整数 $n$,将其拆分为 $k (k \ge 2)$ 个正整数的和,并使这些整数的乘积最大化。 + +**要求**:返回可以获得的最大乘积。 + +**说明**: + +- $2 \le n \le 58$。 + +**示例**: + +- 示例 1: + +```python +输入: n = 2 +输出: 1 +解释: 2 = 1 + 1, 1 × 1 = 1。 +``` + +- 示例 2: + +```python +输入: n = 10 +输出: 36 +解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照正整数进行划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。 + +###### 3. 状态转移方程 + +当 $i \ge 2$ 时,假设正整数 $i$ 拆分出的第 $1$ 个正整数是 $j(1 \le j < i)$,则有两种方法: + +1. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 不再拆分为多个正整数,此时乘积为:$j \times (i - j)$。 +2. 将 $i$ 拆分为 $j$ 和 $i - j$ 的和,且 $i - j$ 继续拆分为多个正整数,此时乘积为:$j \times dp[i - j]$。 + +则 $dp[i]$ 取两者中的最大值。即:$dp[i] = max(j \times (i - j), j \times dp[i - j])$。 + +由于 $1 \le j < i$,需要遍历 $j$ 得到 $dp[i]$ 的最大值,则状态转移方程如下: + +$dp[i] = max_{1 \le j < i}\lbrace max(j \times (i - j), j \times dp[i - j]) \rbrace$。 + +###### 4. 初始条件 + +- $0$ 和 $1$ 都不能被拆分,所以 $dp[0] = 0, dp[1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:将正整数 $i$ 拆分为至少 $2$ 个正整数的和之后,这些正整数的最大乘积。则最终结果为 $dp[n]$。 + +### 思路 1:代码 + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + for i in range(2, n + 1): + for j in range(i): + dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) + return dp[n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md b/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md new file mode 100644 index 00000000..1c61b03d --- /dev/null +++ b/docs/solutions/0300-0399/intersection-of-two-arrays-ii.md @@ -0,0 +1,63 @@ +# [0350. 两个数组的交集 II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) + +- 标签:数组、哈希表 +- 难度:简单 + +## 题目链接 + +- [0350. 两个数组的交集 II - 力扣](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) + +## 题目大意 + +**描述**:给定两个数组 $nums1$ 和 $nums2$。 + +**要求**:返回两个数组的交集。可以不考虑输出结果的顺序。 + +**说明**: + +- 输出结果中,每个元素出现的次数,应该与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。 +- $1 \le nums1.length, nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 1000$。 + +**示例**: + +```python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2,2] + + +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[4,9] +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 先遍历第一个数组,利用字典来存放第一个数组的元素出现次数。 +2. 然后遍历第二个数组,如果字典中存在该元素,则将该元素加入到答案数组中,并减少字典中该元素出现的次数。 +3. 遍历完之后,返回答案数组。 + +### 思路 1:代码 + +```python +class Solution: + def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: + numDict = dict() + nums = [] + for num in nums1: + if num in numDict: + numDict[num] += 1 + else: + numDict[num] = 1 + for num in nums2: + if num in numDict and numDict[num] != 0: + numDict[num] -= 1 + nums.append(num) + return nums +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0300-0399/intersection-of-two-arrays.md b/docs/solutions/0300-0399/intersection-of-two-arrays.md new file mode 100644 index 00000000..557e6034 --- /dev/null +++ b/docs/solutions/0300-0399/intersection-of-two-arrays.md @@ -0,0 +1,104 @@ +# [0349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) + +- 标签:数组、哈希表、双指针、二分查找、排序 +- 难度:简单 + +## 题目链接 + +- [0349. 两个数组的交集 - 力扣](https://leetcode.cn/problems/intersection-of-two-arrays/) + +## 题目大意 + +**描述**:给定两个数组 $nums1$ 和 $nums2$。 + +**要求**:返回两个数组的交集。重复元素只计算一次。 + +**说明**: + +- $1 \le nums1.length, nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2] +示例 2: +``` + +- 示例 2: + +```python +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[9,4] +解释:[4,9] 也是可通过的 +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 先遍历第一个数组,利用哈希表来存放第一个数组的元素,对应字典值设为 $1$。 +2. 然后遍历第二个数组,如果哈希表中存在该元素,则将该元素加入到答案数组中,并且将该键值清空。 + +### 思路 1:代码 + +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + numDict = dict() + nums = [] + for num in nums1: + if num not in numDict: + numDict[num] = 1 + for num in nums2: + if num in numDict and numDict[num] != 0: + numDict[num] -= 1 + nums.append(num) + return nums +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:分离双指针 + +1. 对数组 $nums1$、$nums2$ 先排序。 +2. 使用两个指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$。$left\underline{\hspace{0.5em}}1$ 指向第一个数组的第一个元素,即:$left\underline{\hspace{0.5em}}1 = 0$,$left\underline{\hspace{0.5em}}2$ 指向第二个数组的第一个元素,即:$left\underline{\hspace{0.5em}}2 = 0$。 +3. 如果 $nums1[left_1]$ 等于 $nums2[left_2]$,则将其加入答案数组(注意去重),并将 $left\underline{\hspace{0.5em}}1$ 和 $left\underline{\hspace{0.5em}}2$ 右移。 +4. 如果 $nums1[left_1]$ 小于 $nums2[left_2]$,则将 $left\underline{\hspace{0.5em}}1$ 右移。 +5. 如果 $nums1[left_1]$ 大于 $nums2[left_2]$,则将 $left\underline{\hspace{0.5em}}2$ 右移。 +6. 最后返回答案数组。 + +### 思路 2:代码 + +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums1.sort() + nums2.sort() + + left_1 = 0 + left_2 = 0 + res = [] + while left_1 < len(nums1) and left_2 < len(nums2): + if nums1[left_1] == nums2[left_2]: + if nums1[left_1] not in res: + res.append(nums1[left_1]) + left_1 += 1 + left_2 += 1 + elif nums1[left_1] < nums2[left_2]: + left_1 += 1 + elif nums1[left_1] > nums2[left_2]: + left_2 += 1 + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0300-0399/is-subsequence.md b/docs/solutions/0300-0399/is-subsequence.md new file mode 100644 index 00000000..c8611840 --- /dev/null +++ b/docs/solutions/0300-0399/is-subsequence.md @@ -0,0 +1,68 @@ +# [0392. 判断子序列](https://leetcode.cn/problems/is-subsequence/) + +- 标签:双指针、字符串、动态规划 +- 难度:简单 + +## 题目链接 + +- [0392. 判断子序列 - 力扣](https://leetcode.cn/problems/is-subsequence/) + +## 题目大意 + +**描述**:给定字符串 $s$ 和 $t$。 + +**要求**:判断 $s$ 是否为 $t$ 的子序列。 + +**说明**: + +- $0 \le s.length \le 100$。 +- $0 \le t.length \le 10^4$。 +- 两个字符串都只由小写字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "abc", t = "ahbgdc" +输出:True +``` + +- 示例 2: + +```python +输入:s = "axc", t = "ahbgdc" +输出:False +``` + +## 解题思路 + +### 思路 1:双指针 + +使用两个指针 $i$、$j$ 分别指向字符串 $s$ 和 $t$,然后对两个字符串进行遍历。 + +- 遇到 $s[i] == t[j]$ 的情况,则 $i$ 向右移。 +- 不断右移 $j$。 +- 如果超过 $s$ 或 $t$ 的长度则跳出。 +- 最后判断指针 $i$ 是否指向了 $s$ 的末尾,即:判断 $i$ 是否等于 $s$ 的长度。如果等于,则说明 $s$ 是 $t$ 的子序列,如果不等于,则不是。 + +### 思路 1:代码 + +```python +class Solution: + def isSubsequence(self, s: str, t: str) -> bool: + size_s = len(s) + size_t = len(t) + i, j = 0, 0 + while i < size_s and j < size_t: + if s[i] == t[j]: + i += 1 + j += 1 + return i == size_s +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$、$m$ 分别为字符串 $s$、$t$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix.md b/docs/solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix.md new file mode 100644 index 00000000..13218bea --- /dev/null +++ b/docs/solutions/0300-0399/kth-smallest-element-in-a-sorted-matrix.md @@ -0,0 +1,52 @@ +# [0378. 有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) + +- 标签:数组、二分查找、矩阵、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0378. 有序矩阵中第 K 小的元素 - 力扣](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) + +## 题目大意 + +给定一个 `n * n` 矩阵 `matrix`,其中每行和每列元素均按升序排序。 + +要求:找到矩阵中第 `k` 小的元素。 + +注意:它是排序后的第 `k` 小元素,而不是第 `k` 个 不同的元素。 + +## 解题思路 + +已知二维矩阵 `matrix` 每行每列是按照升序排序的。那么二维矩阵的下界就是左上角元素 `matrix[0][0]`,上界就是右下角元素 `matrix[rows - 1][cols - 1]`。那么我们可以使用二分查找的方法在上界、下界之间搜索所有值,找到第 `k` 小的元素。 + +我们可以通过判断矩阵中比 `mid` 小的元素个数是否等于 `k` 来确定是否找到第 `k` 小的元素。 + +- 如果比 `mid` 小的元素个数大于等于 `k`,说明最终答案 `ans` 小于等于 `mid`。 +- 如果比 `mid` 小的元素个数小于 `k`,说明最终答案 `ans` 大于 `k`。 + +## 代码 + +```python +class Solution: + def kthSmallest(self, matrix: List[List[int]], k: int) -> int: + rows, cols = len(matrix), len(matrix[0]) + left, right = matrix[0][0], matrix[rows - 1][cols - 1] + 1 + while left < right: + mid = left + (right - left) // 2 + if self.counterKthSmallest(mid, matrix) >= k: + right = mid + else: + left = mid + 1 + return left + + def counterKthSmallest(self, mid, matrix): + rows, cols = len(matrix), len(matrix[0]) + count = 0 + j = cols - 1 + for i in range(rows): + while j >= 0 and mid < matrix[i][j]: + j -= 1 + count += j + 1 + return count +``` + diff --git a/docs/solutions/0300-0399/lexicographical-numbers.md b/docs/solutions/0300-0399/lexicographical-numbers.md new file mode 100644 index 00000000..14851cbe --- /dev/null +++ b/docs/solutions/0300-0399/lexicographical-numbers.md @@ -0,0 +1,40 @@ +# [0386. 字典序排数](https://leetcode.cn/problems/lexicographical-numbers/) + +- 标签:深度优先搜索、字典树 +- 难度:中等 + +## 题目链接 + +- [0386. 字典序排数 - 力扣](https://leetcode.cn/problems/lexicographical-numbers/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:按字典序返回范围 `[1, n]` 的所有整数。并且要求时间复杂度为 `O(n)`,空间复杂度为 `o(1)`。 + +## 解题思路 + +按照字典序进行深度优先搜索。实质上算是构造一棵字典树,然后将 `[1, n]` 中的数插入到字典树中,并将遍历结果存储到列表中。 + +## 代码 + +```python +class Solution: + def dfs(self, cur, n, res): + if cur > n: + return + res.append(cur) + for i in range(10): + num = 10 * cur + i + if num > n: + return + self.dfs(num, n, res) + + def lexicalOrder(self, n: int) -> List[int]: + res = [] + for i in range(1, 10): + self.dfs(i, n, res) + return res +``` + diff --git a/docs/solutions/0300-0399/logger-rate-limiter.md b/docs/solutions/0300-0399/logger-rate-limiter.md new file mode 100644 index 00000000..c65c8161 --- /dev/null +++ b/docs/solutions/0300-0399/logger-rate-limiter.md @@ -0,0 +1,57 @@ +# [0359. 日志速率限制器](https://leetcode.cn/problems/logger-rate-limiter/) + +- 标签:设计、哈希表 +- 难度:简单 + +## 题目链接 + +- [0359. 日志速率限制器 - 力扣](https://leetcode.cn/problems/logger-rate-limiter/) + +## 题目大意 + +设计一个日志系统,可以流式接受消息和消息的时间戳。每条不重复的信息最多每 10 秒打印一次。即如果在时间 t 打印了 A 信息,则直到 t+10 的时间,才能再次打印这条信息。 + +要求实现 Logger 类: + +- `def __init__(self):` 初始化 logger 对象 +- `def shouldPrintMessage(self, timestamp: int, message: str) -> bool:` + - 如果该条消息 message 在给定时间戳 timestamp 能够打印出来,则返回 True,否则返回 False。 + +## 解题思路 + +初始化一个哈希表,用来存储消息 message 最后一次打印的时间戳。 + +当新的消息到达是,先判断之前是否出现过相同的消息,如果未出现则可打印,存储时间戳,并返回 True。 + +如果出现过,且上一次相同的消息在 10 秒之前打印的,则该消息也可打印,更新时间戳,并返回 True。 + +如果上一次相同的消息是在 10 秒内打印的,则该信息不可打印,直接返回 False。 + +## 代码 + +```python +class Logger: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.msg_dict = dict() + + + def shouldPrintMessage(self, timestamp: int, message: str) -> bool: + """ + Returns true if the message should be printed in the given timestamp, otherwise returns false. + If this method returns false, the message will not be printed. + The timestamp is in seconds granularity. + """ + if message not in self.msg_dict: + self.msg_dict[message] = timestamp + return True + if timestamp - self.msg_dict[message] >= 10: + self.msg_dict[message] = timestamp + return True + else: + return False +``` + diff --git a/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md b/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md new file mode 100644 index 00000000..81f52cf1 --- /dev/null +++ b/docs/solutions/0300-0399/longest-increasing-path-in-a-matrix.md @@ -0,0 +1,49 @@ +# [0329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 +- 难度:困难 + +## 题目链接 + +- [0329. 矩阵中的最长递增路径 - 力扣](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) + +## 题目大意 + +给定一个 `m * n` 大小的整数矩阵 `matrix`。要求:找出其中最长递增路径的长度。 + +对于每个单元格,可以往上、下、左、右四个方向移动,不能向对角线方向移动或移动到边界外。 + +## 解题思路 + +深度优先搜索。使用二维数组 `record` 存储遍历过的单元格最大路径长度,已经遍历过的单元格就不需要再次遍历了。 + +## 代码 + +```python +class Solution: + max_len = 0 + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + + def longestIncreasingPath(self, matrix: List[List[int]]) -> int: + if not matrix: + return 0 + rows, cols = len(matrix), len(matrix[0]) + record = [[0 for _ in range(cols)] for _ in range(rows)] + + def dfs(i, j): + record[i][j] = 1 + for direction in self.directions: + new_i, new_j = i + direction[0], j + direction[1] + if 0 <= new_i < rows and 0 <= new_j < cols and matrix[new_i][new_j] > matrix[i][j]: + if record[new_i][new_j] == 0: + dfs(new_i, new_j) + record[i][j] = max(record[i][j], record[new_i][new_j] + 1) + self.max_len = max(self.max_len, record[i][j]) + + for i in range(rows): + for j in range(cols): + if record[i][j] == 0: + dfs(i, j) + return self.max_len +``` + diff --git a/docs/solutions/0300-0399/longest-increasing-subsequence.md b/docs/solutions/0300-0399/longest-increasing-subsequence.md new file mode 100644 index 00000000..11661045 --- /dev/null +++ b/docs/solutions/0300-0399/longest-increasing-subsequence.md @@ -0,0 +1,91 @@ +# [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) + +- 标签:数组、二分查找、动态规划 +- 难度:中等 + +## 题目链接 + +- [0300. 最长递增子序列 - 力扣](https://leetcode.cn/problems/longest-increasing-subsequence/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:找到其中最长严格递增子序列的长度。 + +**说明**: + +- **子序列**:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,$[3,6,2,7]$ 是数组 $[0,3,1,6,2,2,7]$ 的子序列。 +- $1 \le nums.length \le 2500$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [10,9,2,5,3,7,101,18] +输出:4 +解释:最长递增子序列是 [2,3,7,101],因此长度为 4。 +``` + +- 示例 2: + +```python +输入:nums = [0,1,0,3,2,3] +输出:4 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照子序列的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。 + +###### 3. 状态转移方程 + +一个较小的数后边如果出现一个较大的数,则会形成一个更长的递增子序列。 + +对于满足 $0 \le j < i$ 的数组元素 $nums[j]$ 和 $nums[i]$ 来说: + +- 如果 $nums[j] < nums[i]$,则 $nums[i]$ 可以接在 $nums[j]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[j]$ 结尾的最长递增子序列长度」的基础上加 $1$,即 $dp[i] = dp[j] + 1$。 + +- 如果 $nums[j] \le nums[i]$,则 $nums[i]$ 不可以接在 $nums[j]$ 后面,可以直接跳过。 + +综上,我们的状态转移方程为:$dp[i] = max(dp[i], dp[j] + 1), 0 \le j < i, nums[j] < nums[i]$。 + +###### 4. 初始条件 + +默认状态下,把数组中的每个元素都作为长度为 $1$ 的递增子序列。即 $dp[i] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长递增子序列长度。那为了计算出最大的最长递增子序列长度,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + size = len(nums) + dp = [1 for _ in range(size)] + + for i in range(size): + for j in range(i): + if nums[i] > nums[j]: + dp[i] = max(dp[i], dp[j] + 1) + + return max(dp) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters.md b/docs/solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters.md new file mode 100644 index 00000000..cf5c480c --- /dev/null +++ b/docs/solutions/0300-0399/longest-substring-with-at-least-k-repeating-characters.md @@ -0,0 +1,78 @@ +# [0395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) + +- 标签:哈希表、字符串、分治、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0395. 至少有 K 个重复字符的最长子串 - 力扣](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) + +## 题目大意 + +给定一个字符串 `s` 和一个整数 `k`。 + +要求:找出 `s` 中的最长子串, 要求该子串中的每一字符出现次数都不少于 `k` 。返回这一子串的长度。 + +注意:`s` 仅由小写英文字母构成。 + +## 解题思路 + +这道题看起来很像是常规滑动窗口套路的问题,但是用普通滑动窗口思路无法解决问题。 + +如果窗口需要保证「各种字符出现次数都大于等于 `k`」这一性质。那么当向右移动 `right`,扩大窗口时,如果 `s[right]` 是第一次出现的元素,窗口内的字符种类数量必然会增加,此时缩小 `s[left]` 也不一定满足窗口内「各种字符出现次数都大于等于 `k`」这一性质。那么我们就无法通过这种方式来进行滑动窗口。 + +但是我们可以通过固定字符种类数的方式进行滑动窗口。因为给定字符串 `s` 仅有小写字母构成,则最长子串中的字符种类数目,最少为 `1` 种,最多为 `26` 种。我们通过枚举最长子串中可能出现的字符种类数目,从而固定窗口中出现的字符种类数目 `i (1 <= i <= 26)`,再进行滑动数组。窗口内需要保证出现的字符种类数目等于 `i`。向右移动 `right`,扩大窗口时,记录窗口内各种类字符数量。当窗口内出现字符数量大于 `i` 时,则不断右移 `right`,保证窗口内出现字符种类等于 `i`。同时,记录窗口内出现次数小于 `k` 的字符数量,当窗口中出现次数小于 `k` 的字符数量为 `0` 时,就可以记录答案,并维护答案最大值了。 + +整个算法的具体步骤如下: + +- 使用 `ans` 记录满足要求的最长子串长度。 + +- 枚举最长子串中的字符种类数目 `i`,最小为 `1` 种,最大为 `26` 种。对于给定字符种类数目 `i`: + - 使用两个指针 `left`、`right` 指向滑动窗口的左右边界。 + - 使用 `window_count` 变量来统计窗口内字符种类数目,保证窗口中的字符种类数目 `window_count` 不多于 `i`。 + - 使用 `letter_map` 哈希表记录窗口中各个字符出现的数目。使用 `less_k_count` 记录窗口内出现次数小于 `k` 次的字符数量。 + - 向右移动 `right`,将最右侧字符 `s[right]` 加入当前窗口,用 `letter_map` 记录该字符个数。 + - 如果该字符第一次出现,即 `letter_map[s[right]] == 1`,则窗口内字符种类数目 + 1,即 `window_count += 1`。同时窗口内小于 `k` 次的字符数量 + 1(等到 `letter_map[s[right]] >= k` 时再减去),即 `less_k_count += 1`。 + - 如果该字符已经出现过 `k` 次,即 `letter_map[s[right]] == k`,则窗口内小于 `k` 次的字符数量 -1,即 `less_k_count -= 1`。 + - 当窗口内字符种类数目 `window_count` 大于给定字符种类数目 `i` 时,即 `window_count > i`,则不断右移 `left`,缩小滑动窗口长度,直到 `window_count == i`。 + - 如果此时窗口内字符种类数目 `window_count` 等于给定字符种类 `i` 并且小于 `k` 次的字符数量为 `0`,即 `window_count == i and less_k_count == 0` 时,维护更新答案为 `ans = max(right - left + 1, ans)`。 +- 最后输出答案 `ans`。 + +## 代码 + +```python +class Solution: + def longestSubstring(self, s: str, k: int) -> int: + ans = 0 + for i in range(1, 27): + left, right = 0, 0 + window_count = 0 + less_k_count = 0 + letter_map = dict() + while right < len(s): + if s[right] in letter_map: + letter_map[s[right]] += 1 + else: + letter_map[s[right]] = 1 + + if letter_map[s[right]] == 1: + window_count += 1 + less_k_count += 1 + if letter_map[s[right]] == k: + less_k_count -= 1 + + while window_count > i: + letter_map[s[left]] -= 1 + if letter_map[s[left]] == 0: + window_count -= 1 + less_k_count -= 1 + if letter_map[s[left]] == k - 1: + less_k_count += 1 + left += 1 + + if window_count == i and less_k_count == 0: + ans = max(right - left + 1, ans) + right += 1 + return ans +``` + diff --git a/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md b/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md new file mode 100644 index 00000000..2ab5d145 --- /dev/null +++ b/docs/solutions/0300-0399/longest-substring-with-at-most-k-distinct-characters.md @@ -0,0 +1,53 @@ +# [0340. 至多包含 K 个不同字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0340. 至多包含 K 个不同字符的最长子串 - 力扣](https://leetcode.cn/problems/longest-substring-with-at-most-k-distinct-characters/) + +## 题目大意 + +给定一个字符串 `s`, + +要求:返回至多包含 `k` 个不同字符的最长子串 `t` 的长度。 + +## 解题思路 + +用滑动窗口 `window_counts` 来记录各个字符个数,`window_counts` 为哈希表类型。用 `ans` 来维护至多包含 `k` 个不同字符的最长子串 `t` 的长度。 + +设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中不超过 `k` 种字符。 + +- 一开始,`left`、`right` 都指向 `0`。 +- 将最右侧字符 `s[right]` 加入当前窗口 `window_counts` 中,记录该字符个数,向右移动 `right`。 +- 如果该窗口中字符的种数多于 `k` 个,即 `len(window_counts) > k`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `len(window_counts) <= k`。 +- 维护更新至多包含 `k` 个不同字符的最长子串 `t` 的长度。然后继续右移 `right`,直到 `right >= len(nums)` 结束。 +- 输出答案 `ans`。 + +## 代码 + +```python +class Solution: + def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int: + ans = 0 + window_counts = dict() + left, right = 0, 0 + + while right < len(s): + if s[right] in window_counts: + window_counts[s[right]] += 1 + else: + window_counts[s[right]] = 1 + + while(len(window_counts) > k): + window_counts[s[left]] -= 1 + if window_counts[s[left]] == 0: + del window_counts[s[left]] + left += 1 + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + diff --git a/docs/solutions/0300-0399/maximum-product-of-word-lengths.md b/docs/solutions/0300-0399/maximum-product-of-word-lengths.md new file mode 100644 index 00000000..f02f2730 --- /dev/null +++ b/docs/solutions/0300-0399/maximum-product-of-word-lengths.md @@ -0,0 +1,44 @@ +# [0318. 最大单词长度乘积](https://leetcode.cn/problems/maximum-product-of-word-lengths/) + +- 标签:位运算、数组、字符串 +- 难度:中等 + +## 题目链接 + +- [0318. 最大单词长度乘积 - 力扣](https://leetcode.cn/problems/maximum-product-of-word-lengths/) + +## 题目大意 + +给定一个字符串数组 `words`。字符串中只包含英语的小写字母。 + +要求:计算当两个字符串 `words[i]` 和 `words[j]` 不包含相同字符时,它们长度的乘积的最大值。如果没有不包含相同字符的一对字符串,返回 0。 + +## 解题思路 + +这道题的核心难点是判断任意两个字符串之间是否包含相同字符。最直接的做法是先遍历第一个字符串的每个字符,再遍历第二个字符串查看是否有相同字符。但是这样做的话,时间复杂度过高。考虑怎么样可以优化一下。 + +题目中说字符串中只包含英语的小写字母,也就是 `26` 种字符。一个 `32` 位的 `int` 整数每一个二进制位都可以表示一种字符的有无,那么我们就可以通过一个整数来表示一个字符串中所拥有的字符种类。延伸一下,我们可以用一个整数数组来表示一个字符串数组中,每个字符串所拥有的字符种类。 + +接下来事情就简单了,两重循环遍历整数数组,遇到两个字符串不包含相同字符的情况,就计算一下他们长度的乘积,并维护一个乘积最大值。最后输出最大值即可。 + +## 代码 + +```python +class Solution: + def maxProduct(self, words: List[str]) -> int: + size = len(words) + arr = [0 for _ in range(size)] + for i in range(size): + word = words[i] + len_word = len(word) + for j in range(len_word): + arr[i] |= 1 << (ord(word[j]) - ord('a')) + ans = 0 + for i in range(size): + for j in range(i + 1, size): + if arr[i] & arr[j] == 0: + k = len(words[i]) * len(words[j]) + ans = k if ans < k else ans + return ans +``` + diff --git a/docs/solutions/0300-0399/minimum-height-trees.md b/docs/solutions/0300-0399/minimum-height-trees.md new file mode 100644 index 00000000..9728d5de --- /dev/null +++ b/docs/solutions/0300-0399/minimum-height-trees.md @@ -0,0 +1,152 @@ +# [0310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [0310. 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/) + +## 题目大意 + +**描述**:有一棵包含 $n$ 个节点的树,节点编号为 $0 \sim n - 1$。给定一个数字 $n$ 和一个有 $n - 1$ 条无向边的 $edges$ 列表来表示这棵树。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间存在一条无向边。 + +可以选择树中的任何一个节点作为根,当选择节点 $x$ 作为根节点时,设结果树的高度为 $h$。在所有可能的树种,具有最小高度的树(即 $min(h)$)被成为最小高度树。 + +**要求**:找到所有的最小高度树并按照任意顺序返回他们的根节点编号列表。 + +**说明**: + +- **树的高度**:指根节点和叶子节点之间最长向下路径上边的数量。 +- $1 \le n \le 2 * 10^4$。 +- $edges.length == n - 1$。 +- $0 \le ai, bi < n$。 +- $ai \ne bi$。 +- 所有 $(ai, bi)$ 互不相同。 +- 给定的输入保证是一棵树,并且不会有重复的边。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/09/01/e1.jpg) + +```python +输入:n = 4, edges = [[1,0],[1,2],[1,3]] +输出:[1] +解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/09/01/e2.jpg) + +```python +输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] +输出:[3,4] +``` + +## 解题思路 + +### 思路 1:树形 DP + 二次遍历换根法 + +最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点,然后进行深度优先搜索,求出每棵树的高度。最后求出所有树中的最小高度即为答案。但这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 2 * 10^4]$,这样做会导致超时,因此需要进行优化。 + +在上面的算法中,在一轮深度优先搜索中,除了可以得到整棵树的高度之外,在搜索过程中,其实还能得到以每个子节点为根节点的树的高度。如果我们能够利用这些子树的高度信息,快速得到以其他节点为根节点的树的高度,那么我们就能改进算法,以更小的时间复杂度解决这道题。这就是二次遍历与换根法的思想。 + +1. 第一次遍历:自底向上的计算出每个节点 $u$ 向下走(即由父节点 $u$ 向子节点 $v$ 走)的最长路径 $down1[u]$、次长路径 $down2[i]$,并记录向下走最长路径所经过的子节点 $p[u]$,方便第二次遍历时计算。 +2. 第二次遍历:自顶向下的计算出每个节点 $v$ 向上走(即由子节点 $v$ 向父节点 $u$ 走)的最长路径 $up[v]$。需要注意判断 $u$ 向下走的最长路径是否经过了节点 $v$。 + 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$。 + 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$。 +3. 接下来,我们通过枚举 $n$​ 个节点向上走的最长路径与向下走的最长路径,从而找出所有树中的最小高度,并将所有最小高度树的根节点放入答案数组中并返回。 + +整个算法具体步骤如下: + +1. 使用邻接表的形式存储树。 +3. 定义第一个递归函数 `dfs(u, fa)` 用于计算每个节点向下走的最长路径 $down1[u]$、次长路径 $down2[u]$,并记录向下走的最长路径所经过的子节点 $p[u]$。 + 1. 对当前节点的相邻节点进行遍历。 + 2. 如果相邻节点是父节点,则跳过。 + 3. 递归调用 `dfs(v, u)` 函数计算邻居节点的信息。 + 4. 根据邻居节点的信息计算当前节点的高度,并更新当前节点向下走的最长路径 $down1[u]$、当前节点向下走的次长路径 $down2$、取得最长路径的子节点 $p[u]$。 +4. 定义第二个递归函数 `reroot(u, fa)` 用于计算每个节点作为新的根节点时向上走的最长路径 $up[v]$。 + 1. 对当前节点的相邻节点进行遍历。 + 2. 如果相邻节点是父节点,则跳过。 + 3. 根据当前节点 $u$ 的高度和相邻节点 $v$ 的信息更新 $up[v]$。同时需要判断节点 $u$ 向下走的最长路径是否经过了节点 $v$。 + 1. 如果经过了节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的次长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down2[u]) + 1$。 + 2. 如果没有经过节点 $v$,则向上走的最长路径,取决于「父节点 $u$ 向上走的最长路径」与「父节点 $u$ 向下走的最长路径」 的较大值,再加上 $1$,即:$up[v] = max(up[u], down1[u]) + 1$。 + 4. 递归调用 `reroot(v, u)` 函数计算邻居节点的信息。 +5. 调用 `dfs(0, -1)` 函数计算每个节点的最长路径。 +6. 调用 `reroot(0, -1)` 函数计算每个节点作为新的根节点时的最长路径。 +7. 找到所有树中的最小高度。 +8. 将所有最小高度的节点放入答案数组中并返回。 + +### 思路 1:代码 + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + graph = [[] for _ in range(n)] + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + # down1 用于记录向下走的最长路径 + down1 = [0 for _ in range(n)] + # down2 用于记录向下走的最长路径 + down2 = [0 for _ in range(n)] + p = [0 for _ in range(n)] + # 自底向上记录最长路径、次长路径 + def dfs(u, fa): + for v in graph[u]: + if v == fa: + continue + # 自底向上统计信息 + dfs(v, u) + height = down1[v] + 1 + if height >= down1[u]: + down2[u] = down1[u] + down1[u] = height + p[u] = v + elif height > down2[u]: + down2[u] = height + + # 进行换根动态规划,自顶向下统计向上走的最长路径 + up = [0 for _ in range(n)] + def reroot(u, fa): + for v in graph[u]: + if v == fa: + continue + if p[u] == v: + up[v] = max(up[u], down2[u]) + 1 + else: + up[v] = max(up[u], down1[u]) + 1 + # 自顶向下统计信息 + reroot(v, u) + + dfs(0, -1) + reroot(0, -1) + + # 找到所有树中的最小高度 + min_h = 1e9 + for i in range(n): + min_h = min(min_h, max(down1[i], up[i])) + + # 将所有最小高度的节点放入答案数组中并返回 + res = [] + for i in range(n): + if max(down1[i], up[i]) == min_h: + res.append(i) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[C++ 容易理解的换根动态规划解法 - 最小高度树](https://leetcode.cn/problems/minimum-height-trees/solution/c-huan-gen-by-vclip-sa84/) +- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) +- 【题解】[310. 最小高度树 - 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-trees/solution/310-zui-xiao-gao-du-shu-by-vincent-40-teg8/) diff --git a/docs/solutions/0300-0399/moving-average-from-data-stream.md b/docs/solutions/0300-0399/moving-average-from-data-stream.md new file mode 100644 index 00000000..62ab03e8 --- /dev/null +++ b/docs/solutions/0300-0399/moving-average-from-data-stream.md @@ -0,0 +1,54 @@ +# [0346. 数据流中的移动平均值](https://leetcode.cn/problems/moving-average-from-data-stream/) + +- 标签:设计、队列、数组、数据流 +- 难度:简单 + +## 题目链接 + +- [0346. 数据流中的移动平均值 - 力扣](https://leetcode.cn/problems/moving-average-from-data-stream/) + +## 题目大意 + +给定一个整数 `val` 和一个窗口大小 `size`。 + +要求:根据滑动窗口的大小,计算滑动窗口里所有数字的平均值。要实现 `MovingAverage` 类: + +- `MovingAverage(int size)` 用窗口大小 `size` 初始化对象。 +- `double next(int val)` 成员函数 `next` 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 `size` 个值的移动平均值,即滑动窗口里所有数字的平均值。 + +## 解题思路 + +使用队列保存滑动窗口的元素,并记录对应窗口大小和元素和。 + +在小于窗口大小的时候,直接向队列中添加元素,并记录元素和。 + +在等于窗口大小的时候,先将队列头部元素弹出,再添加元素,并记录元素和。 + +然后根据元素和和队列中元素个数计算出平均值。 + +## 代码 + +```python +class MovingAverage: + + def __init__(self, size: int): + """ + Initialize your data structure here. + """ + self.queue = [] + self.size = size + self.sum = 0 + + + def next(self, val: int) -> float: + if len(self.queue) < self.size: + self.queue.append(val) + else: + if self.queue: + self.sum -= self.queue[0] + self.queue.pop(0) + self.queue.append(val) + self.sum += val + return self.sum / len(self.queue) +``` + diff --git a/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md b/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md new file mode 100644 index 00000000..b37d4b4f --- /dev/null +++ b/docs/solutions/0300-0399/number-of-connected-components-in-an-undirected-graph.md @@ -0,0 +1,140 @@ +# [0323. 无向图中连通分量的数目](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0323. 无向图中连通分量的数目 - 力扣](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) + +## 题目大意 + +**描述**:给定 `n` 个节点(编号从 `0` 到 `n - 1`)的图的无向边列表 `edges`,其中 `edges[i] = [u, v]` 表示节点 `u` 和节点 `v` 之间有一条无向边。 + +**要求**:计算该无向图中连通分量的数量。 + +**说明**: + +- $1 \le n \le 2000$。 +- $1 \le edges.length \le 5000$。 +- $edges[i].length == 2$。 +- $0 \le ai \le bi < n$。 +- $ai != bi$。 +- `edges` 中不会出现重复的边。 + +**示例**: + +- 示例 1: + +```python +输入: n = 5 和 edges = [[0, 1], [1, 2], [3, 4]] + 0 3 + | | + 1 --- 2 4 +输出: 2 +``` + +- 示例 2: + +```python +输入: n = 5 和 edges = [[0, 1], [1, 2], [2, 3], [3, 4]] + 0 4 + | | + 1 --- 2 --- 3 +输出: 1 +``` + +## 解题思路 + +先来看一下图论中相关的名次解释。 + +- **连通图**:在无向图中,如果可以从顶点 $v_i$ 到达 $v_j$,则称 $v_i$ 和 $v_j$ 连通。如果图中任意两个顶点之间都连通,则称该图为连通图。 +- **无向图的连通分量**:如果该图为连通图,则连通分量为本身;否则将无向图中的极大连通子图称为连通分量,每个连通分量都是一个连通图。 +- **无向图的连通分量个数**:无向图的极大连通子图的个数。 + +接下来我们来解决这道题。 + +### 思路 1:深度优先搜索 + +1. 使用 `visited` 数组标记遍历过的节点,使用 `count` 记录连通分量数量。 +2. 从未遍历过的节点 `u` 出发,连通分量数量加 1。然后遍历与 `u` 节点构成无向边,且为遍历过的的节点 `v`。 +3. 再从 `v` 出发继续深度遍历。 +4. 直到遍历完与`u` 直接相关、间接相关的节点之后,再遍历另一个未遍历过的节点,继续上述操作。 +5. 最后输出连通分量数目。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, visited, i, graph): + visited[i] = True + for j in graph[i]: + if not visited[j]: + self.dfs(visited, j, graph) + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + count = 0 + visited = [False for _ in range(n)] + graph = [[] for _ in range(n)] + + for x, y in edges: + graph[x].append(y) + graph[y].append(x) + + for i in range(n): + if not visited[i]: + count += 1 + self.dfs(visited, i, graph) + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中$n$ 是顶点个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:广度优先搜索 + +1. 使用变量 `count` 记录连通分量个数。使用集合变量 `visited` 记录访问过的节点,使用邻接表 `graph` 记录图结构。 +2. 从 `0` 开始,依次遍历 `n` 个节点。 +3. 如果第 `i` 个节点未访问过: + 1. 将其添加到 `visited` 中。 + 2. 并且连通分量个数累加,即 `count += 1`。 + 3. 定义一个队列 `queue`,将第 `i` 个节点加入到队列中。 + 4. 从队列中取出第一个节点,遍历与其链接的节点,并将未遍历过的节点加入到队列 `queue` 和 `visited` 中。 + 5. 直到队列为空,则继续向后遍历。 +4. 最后输出连通分量数目 `count`。 + +### 思路 2:代码 + +```python +import collections + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + count = 0 + visited = set() + graph = [[] for _ in range(n)] + + for x, y in edges: + graph[x].append(y) + graph[y].append(x) + + for i in range(n): + if i not in visited: + visited.add(i) + count += 1 + queue = collections.deque([i]) + while queue: + node_u = queue.popleft() + for node_v in graph[node_u]: + if node_v not in visited: + visited.add(node_v) + queue.append(node_v) + return count +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。其中$n$ 是顶点个数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0300-0399/odd-even-linked-list.md b/docs/solutions/0300-0399/odd-even-linked-list.md new file mode 100644 index 00000000..3f24c732 --- /dev/null +++ b/docs/solutions/0300-0399/odd-even-linked-list.md @@ -0,0 +1,83 @@ +# [0328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) + +- 标签:链表 +- 难度:中等 + +## 题目链接 + +- [0328. 奇偶链表 - 力扣](https://leetcode.cn/problems/odd-even-linked-list/) + +## 题目大意 + +**描述**:给定一个单链表的头节点 `head`。 + +**要求**:将链表中的奇数位置上的节点排在前面,偶数位置上的节点排在后面,返回新的链表节点。 + +**说明**: + +- 要求空间复杂度为 $O(1)$。 +- $n$ 等于链表中的节点数。 +- $0 \le n \le 10^4$。 +- $-10^6 \le Node.val \le 10^6$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/10/oddeven-linked-list.jpg) + +```python +输入: head = [1,2,3,4,5] +输出: [1,3,5,2,4] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/10/oddeven2-linked-list.jpg) + +```python +输入: head = [2,1,3,5,6,4,7] +输出: [2,3,6,7,1,5,4] +``` + +## 解题思路 + +### 思路 1:拆分后合并 + +1. 使用两个指针 `odd`、`even` 分别表示奇数节点链表和偶数节点链表。 +2. 先将奇数位置上的节点和偶数位置上的节点分成两个链表,再将偶数节点的链表接到奇数链表末尾。 +3. 过程中需要使用几个必要指针用于保留必要位置(比如原链表初始位置、偶数链表初始位置、当前遍历节点位置)。 + +### 思路 1:代码 + +```python +class Solution: + def oddEvenList(self, head: ListNode) -> ListNode: + if not head or not head.next or not head.next.next: + return head + + evenHead = head.next + odd, even = head, evenHead + isOdd = True + + curr = head.next.next + + while curr: + if isOdd: + odd.next = curr + odd = curr + else: + even.next = curr + even = curr + isOdd = not isOdd + curr = curr.next + odd.next = evenHead + even.next = None + return head +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0300-0399/palindrome-pairs.md b/docs/solutions/0300-0399/palindrome-pairs.md new file mode 100644 index 00000000..f65d6333 --- /dev/null +++ b/docs/solutions/0300-0399/palindrome-pairs.md @@ -0,0 +1,107 @@ +# [0336. 回文对](https://leetcode.cn/problems/palindrome-pairs/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:困难 + +## 题目链接 + +- [0336. 回文对 - 力扣](https://leetcode.cn/problems/palindrome-pairs/) + +## 题目大意 + +给定一组互不相同的单词列表 `words`。 + +要求:找出所有不同的索引对 `(i, j)`,使得列表中的两个单词 `words[i] + words[j]` ,可拼接成回文串。 + +## 解题思路 + +如果字符串 `words[i] + words[j]` 能构成一个回文串,把 `words[i]` 分成 `words_left[i]` 和 `words_right[i]` 两部分。即 `words[i] + words[j] = words_left[i] + words_right[i] + words[j]`。则: + +- `words_right[i]` 本身是回文串,`words_left[i]` 和 `words[j]` 互为逆序。 + +同理,如果 `words[j] + word[i]` 能构成一个回文串,把 `word[i]` 分成 `words_left[i]` 和 `words_right[i]` 两部分。即 `words[j] + word[i] = words[j] + words_left[i] + words_right[i]`。则: + +- `words_left[i]` 本身是回文串,`words[j]` 和 `words_right[i]` 互为逆序。 + +从上面的表述可以得知,`words[j]` 可以通过拆分 `words[i]` 之后逆序得出。 + +我们使用两重循环遍历。一重循环遍历单词列表 `words` 中的每一个单词 `words[i]`,二重循环遍历每个单词的拆分位置 `j`。然后将每一个单词 `words[i]` 拆分成 `words[i][0:j+1]` 和 `words[i][j+1:]`。然后分别判断 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序是否在单词列表中,如果在单词列表中,则将「`words[i]` 和 `words[i][0:j+1]` 对应的索引」或者 「`words[i]` 和 `words[i][j+1:]` 对应的索引」插入到答案数组中。 + +至于判断 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序是否在单词列表中,以及获取 `words[i][0:j+1]` 的逆序和 `words[i][j+1:]` 的逆序所对应单词的索引下标可以通过构建字典树的方式获取。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.index = -1 + + + def insert(self, word: str, index: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.index = index + + def search(self, word: str) -> int: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return -1 + cur = cur.children[ch] + + if cur is not None and cur.isEnd: + return cur.index + return -1 + +class Solution: + def isPalindrome(self, word: str) -> bool: + left, right = 0, len(word) - 1 + while left < right: + if word[left] != word[right]: + return False + left += 1 + right -= 1 + return True + + def palindromePairs(self, words: List[str]) -> List[List[int]]: + trie_tree = Trie() + size = len(words) + for i in range(size): + word = words[i] + trie_tree.insert(word, i) + + res = [] + for i in range(size): + word = words[i] + for j in range(len(word)): + if self.isPalindrome(word[:j+1]): + temp = word[j+1:][::-1] + index = trie_tree.search(temp) + if index != i and index != -1: + res.append([index, i]) + if temp == "": + res.append([i, index]) + if self.isPalindrome(word[j+1:]): + temp = word[:j+1][::-1] + index = trie_tree.search(temp) + if index != i and index != -1: + res.append([i, index]) + return res +``` + diff --git a/docs/solutions/0300-0399/perfect-rectangle.md b/docs/solutions/0300-0399/perfect-rectangle.md new file mode 100644 index 00000000..a940e0c9 --- /dev/null +++ b/docs/solutions/0300-0399/perfect-rectangle.md @@ -0,0 +1,281 @@ +# [0391. 完美矩形](https://leetcode.cn/problems/perfect-rectangle/) + +- 标签:数组、扫描线 +- 难度:困难 + +## 题目链接 + +- [0391. 完美矩形 - 力扣](https://leetcode.cn/problems/perfect-rectangle/) + +## 题目大意 + +**描述**:给定一个数组 `rectangles`,其中 `rectangles[i] = [xi, yi, ai, bi]` 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 `(xi, yi)`,右上顶点是 `(ai, bi)`。 + +**要求**:如果所有矩形一起精确覆盖了某个矩形区域,则返回 `True`;否则,返回 `False`。 + +**说明**: + +- $1 \le rectangles.length \le 2 * 10^4$。 +- $rectangles[i].length == 4$。 +- $-10^5 \le xi, yi, ai, bi \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/03/27/perectrec1-plane.jpg) + +```python +输入:rectangles = [[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]] +输出:True +解释:5 个矩形一起可以精确地覆盖一个矩形区域。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/03/27/perfectrec2-plane.jpg) + +```python +输入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]] +输出:false +解释:两个矩形之间有间隔,无法覆盖成一个矩形。 +``` + +- 示例 3: + +![](https://assets.leetcode.com/uploads/2021/03/27/perfecrrec4-plane.jpg) + +```python +输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]] +输出:False +解释:因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。 +``` + + +## 解题思路 + +### 思路 1:线段树 + +首先我们要先判断所有小矩形的面积和是否等于外接矩形区域的面积。如果不相等,则说明出现了重叠或者空缺,明显不符合题意。 + +在两者面积相等的情况下,还可能会发生重叠的情况。接下来我们要思考如何判断重叠。 + +- 第一种思路:暴力枚举所有矩形对,两两进行比较,判断是否出现了重叠。这样的时间复杂度是 $O(n^2)$,容易超时。 +- 第二种思路: + - 如果所有小矩形可以精确覆盖某个矩形区域,那这些小矩形一定是相互挨着的,也就是说相邻两个矩形的边会重合在一起。比如说 矩形 `A` 下边刚好是 `B` 的上边,或者是 `B` 的上边的一部分。 + - 我们可以固定一个坐标轴,比如说固定 `y` 轴,然后只看水平方向上所有矩形的边。然后我们就会发现,满足题意要求的矩形区域中,纵坐标为 `y` 的平行线上,「所有上边纵坐标为 `y` 的矩形上边区间」与「所有下边纵坐标为 `y` 的矩形下边区间」是完全一样,或者说重合在一起的(除了矩形矩形最上边和最下边只有一条,不会重合之外)。 + - 这样我们就可以用扫描线的思路,建立一个线段树。然后先固定纵坐标 `y`,将「所有上边纵坐标为 `y` 的矩形上边区间」对应的区间值减 `1`,再将「所有下边纵坐标为 `y` 的矩形下边区间」对应的区间值加 `1`。然后查询整个线代树区间值,如果区间值超过 `1`,则说明发生了重叠,不符合题目要求。如果扫描完所有的纵坐标,没有发生重叠,则说明符合题意要求。 + - 因为横坐标的范围为 $[-10^5,10^5]$,但是最多只有 $2 * 10^4$ 个横坐标,所以我们可以先对所有坐标做一下离散化处理,再根据离散化之后的横坐标建立线段树。 + +具体步骤如下: + +1. 通过遍历所有小矩形,计算出所有小矩形的面积和为 `area`。同时计算出矩形区域四个顶点位置,并根据四个顶点计算出矩形区域的面积为 `total_area`。如果所有小矩形面积不等于矩形区域的面积,则直接返回 `False`。 +2. 再次遍历所有小矩形,将所有坐标点进行离散化处理,将其编号存入两个哈希表 `x_dict`、`y_dict`。 +3. 使用哈希表 `top_dict`、`bottom_dict` 分别存储每个矩阵的上下两条边。将上下两条边的横坐标 `x1`、`x2`。分别存入到 `top_dict[y_dict[y2]]`、`top_dict[y_dict[y2]]` 中。 +4. 建立区间长度为横坐标个数的线段树 `STree`。 +5. 遍历所有的纵坐标,对于纵坐标 `i`: + 1. 先遍历当前纵坐标下矩阵的上边数组,即 `top_dict[i]`,取出边的横坐标 `x1`、`x2`。令区间 `[x1, x2 - 1]` 上的值减 `1`。 + 2. 再遍历当前纵坐标下矩阵的下边数组,即 `bottom_dict[i]`,取出边的横坐标 `x1`、`x2`。令区间 `[x1, x2 - 1]` 上的值加 `1`。 + 3. 如果上下边覆盖完之后,被覆盖次数超过了 `1`,则说明出现了重叠,直接返回 `Fasle`。 +6. 如果遍历完所有的纵坐标,没有发现重叠,则返回 `True`。 + +### 思路 1:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if self.tree[index].lazy_tag is not None: + self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val + else: + self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val + self.tree[index].val += val # 当前节点所在区间每个元素值增加 val + return + + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + self.__pushdown(index) # 向下更新节点的区间值 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if lazy_tag is None: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + if self.tree[left_index].lazy_tag is not None: + self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + self.tree[left_index].lazy_tag = lazy_tag + self.tree[left_index].val += lazy_tag + + if self.tree[right_index].lazy_tag is not None: + self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + self.tree[right_index].lazy_tag = lazy_tag + self.tree[right_index].val += lazy_tag + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 + + +class Solution: + def isRectangleCover(self, rectangles) -> bool: + left, right, bottom, top = math.inf, -math.inf, math.inf, -math.inf + area = 0 + x_set, y_set = set(), set() + + for rectangle in rectangles: + x1, y1, x2, y2 = rectangle + left, right = min(left, x1), max(right, x2) + bottom, top = min(bottom, y1), max(top, y2) + area += (y2 - y1) * (x2 - x1) + x_set.add(x1) + x_set.add(x2) + y_set.add(y1) + y_set.add(y2) + + total_area = (top - bottom) * (right - left) + + # 判断所有小矩形面积是否等于所有矩形顶点构成最大矩形面积,不等于则直接返回 False + if area != total_area: + return False + + # 离散化处理所有点的横坐标、纵坐标 + x_dict, y_dict = dict(), dict() + + idx = 0 + for x in sorted(list(x_set)): + x_dict[x] = idx + idx += 1 + + idy = 0 + for y in sorted(list(y_set)): + y_dict[y] = idy + idy += 1 + + # 使用哈希表 top_dict、bottom_dict 分别存储每个矩阵的上下两条边。 + bottom_dict, top_dict = collections.defaultdict(list), collections.defaultdict(list) + for i in range(len(rectangles)): + x1, y1, x2, y2 = rectangles[i] + bottom_dict[y_dict[y1]].append([x_dict[x1], x_dict[x2]]) + top_dict[y_dict[y2]].append([x_dict[x1], x_dict[x2]]) + + # 建立线段树 + self.STree = SegmentTree([0 for _ in range(len(x_set))], lambda x, y: max(x, y)) + + for i in range(idy): + for x1, x2 in top_dict[i]: + self.STree.update_interval(x1, x2 - 1, -1) + for x1, x2 in bottom_dict[i]: + self.STree.update_interval(x1, x2 - 1, 1) + cnt = self.STree.query_interval(0, len(x_set) - 1) + if cnt > 1: + return False + return True +``` + +## 参考资料 + +- 【题解】[线段树+扫描线 - 完美矩形 - 力扣](https://leetcode.cn/problems/perfect-rectangle/solution/xian-duan-shu-sao-miao-xian-by-lucifer10-raw5/) \ No newline at end of file diff --git a/docs/solutions/0300-0399/power-of-four.md b/docs/solutions/0300-0399/power-of-four.md new file mode 100644 index 00000000..05ede6ed --- /dev/null +++ b/docs/solutions/0300-0399/power-of-four.md @@ -0,0 +1,36 @@ +# [0342. 4的幂](https://leetcode.cn/problems/power-of-four/) + +- 标签:位运算、递归、数学 +- 难度:简单 + +## 题目链接 + +- [0342. 4的幂 - 力扣](https://leetcode.cn/problems/power-of-four/) + +## 题目大意 + +给定一个整数 $n$,判断 $n$ 是否是 $4$ 的幂次方,如果是的话,返回 True。不是的话,返回 False。 + +## 解题思路 + +通过循环可以直接做。但有更好的方法。 + +$n$ 如果是 $4$ 的幂次方,那么 $n$ 肯定是 $2$ 的幂次方,$2$ 的幂次方二进制表示只含有一个 $1$,可以通过 $n \text{ \& } (n - 1)$ 将 $n$ 的最后位置上 的 $1$ 置为 $0$,通过判断 $n$ 是否满足 $n \text { \& } (n - 1) == 0$ 来判断 $n$ 是否是 $2$ 的幂次方。 + +若根据上述判断,得出 $n$ 是 $2$ 的幂次方,则可以写为:$n = x^{2k}$ 或者 $n = x^{2k+1}$。如果 $n$ 是 $4$ 的幂次方,则 $n = 2^{k}$。 + +下面来看一下 $2^{2x}$、$2^{2x}+1$ 的情况: + +- $(2^{2x} \mod 3) = (4^x \mod 3) = ((3+1)^x \mod 3) == 1$ +- $(2^{2x+1} \mod 3) = ((2 \times 4^x) \mod 3) = ((2 \times (3+1)^x) \mod 3) == 2$ + +则如果 $n \mod 3 == 1$,则 $n$ 为 $4$ 的幂次方。 + +## 代码 + +```python +class Solution: + def isPowerOfFour(self, n: int) -> bool: + return n > 0 and (n & (n-1)) == 0 and (n-1) % 3 == 0 +``` + diff --git a/docs/solutions/0300-0399/power-of-three.md b/docs/solutions/0300-0399/power-of-three.md new file mode 100644 index 00000000..c733d394 --- /dev/null +++ b/docs/solutions/0300-0399/power-of-three.md @@ -0,0 +1,33 @@ +# [0326. 3 的幂](https://leetcode.cn/problems/power-of-three/) + +- 标签:递归、数学 +- 难度:简单 + +## 题目链接 + +- [0326. 3 的幂 - 力扣](https://leetcode.cn/problems/power-of-three/) + +## 题目大意 + +给定一个整数 n,判断 n 是否是 3 的幂次方。$-2^{31} \le n \le 2^{31}-1$ + +## 解题思路 + +首先排除负数,因为 3 的幂次方不可能为负数。 + +因为 n 的最大值为 $2^{31}-1$。计算出在 n 的范围内,3 的幂次方最大为 $3^{19} = 1162261467$。 + +3 为质数,则 $3^{19}$ 的除数只有 $3^0, 3^1, …, 3^{19}$。所以若 n 为 3 的幂次方,则 n 肯定能被 $3^{19}$ 整除,直接判断即可。 + +## 代码 + +```python +class Solution: + def isPowerOfThree(self, n: int) -> bool: + if n <= 0: + return False + if (3 ** 19) % n == 0: + return True + return False +``` + diff --git a/docs/solutions/0300-0399/range-addition.md b/docs/solutions/0300-0399/range-addition.md new file mode 100644 index 00000000..5cb4ce19 --- /dev/null +++ b/docs/solutions/0300-0399/range-addition.md @@ -0,0 +1,211 @@ +# [0370. 区间加法](https://leetcode.cn/problems/range-addition/) + +- 标签:数组、前缀和 +- 难度:中等 + +## 题目链接 + +- [0370. 区间加法 - 力扣](https://leetcode.cn/problems/range-addition/) + +## 题目大意 + +**描述**:给定一个数组的长度 `length` ,初始情况下数组中所有数字均为 `0`。再给定 `k` 个更新操作。其中每个操作是一个三元组 `[startIndex, endIndex, inc]`,表示将子数组 `nums[startIndex ... endIndex]` (包括 `startIndex`、`endIndex`)上所有元素增加 `inc`。 + +**要求**:返回 `k` 次操作后的数组。 + +**示例**: + +- 示例 1: + +``` +给定 length = 5,即 nums = [0, 0, 0, 0, 0] + +操作 [1, 3, 2] -> [0, 2, 2, 2, 0] +操作 [2, 4, 3] -> [0, 2, 5, 5, 3] +操作 [0, 2, -2] -> [-2, 0, 3, 5, 3] +``` + +## 解题思路 + +### 思路 1:线段树 + +- 初始化一个长度为 `length`,值全为 `0` 的 `nums` 数组。 +- 然后根据 `nums` 数组构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。并且线段树使用延迟标记。 +- 然后遍历三元组操作,进行区间累加运算。 +- 最后从线段树中查询数组所有元素,返回该数组即可。 + +这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 + +### 思路 1:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if self.tree[index].lazy_tag is not None: + self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val + else: + self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val + interval_size = (right - left + 1) # 当前节点所在区间大小 + self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val + return + + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + self.__pushdown(index) # 向下更新节点的区间值 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if lazy_tag is None: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + if self.tree[left_index].lazy_tag is not None: + self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + self.tree[left_index].lazy_tag = lazy_tag + left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) + self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag + + if self.tree[right_index].lazy_tag is not None: + self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + self.tree[right_index].lazy_tag = lazy_tag + right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) + self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag + + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 + +class Solution: + def getModifiedArray(self, length: int, updates: List[List[int]]) -> List[int]: + nums = [0 for _ in range(length)] + self.ST = SegmentTree(nums, lambda x, y: x + y) + for update in updates: + self.ST.update_interval(update[0], update[1], update[2]) + + return self.ST.get_nums() +``` + diff --git a/docs/solutions/0300-0399/range-sum-query-2d-immutable.md b/docs/solutions/0300-0399/range-sum-query-2d-immutable.md new file mode 100644 index 00000000..1d9cace0 --- /dev/null +++ b/docs/solutions/0300-0399/range-sum-query-2d-immutable.md @@ -0,0 +1,44 @@ +# [0304. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) + +- 标签:设计、数组、矩阵、前缀和 +- 难度:中等 + +## 题目链接 + +- [0304. 二维区域和检索 - 矩阵不可变 - 力扣](https://leetcode.cn/problems/range-sum-query-2d-immutable/) + +## 题目大意 + +给定一个二维矩阵 `matrix`。 + +要求:满足以下多个请求: + +- ` def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:`计算以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和。 +- `def __init__(self, matrix: List[List[int]]):` 对二维矩阵 `matrix` 进行初始化操作。 + +## 解题思路 + +在进行初始化的时候做预处理,这样在多次查询时可以减少重复计算,也可以减少时间复杂度。 + +在进行初始化的时候,使用一个二维数组 `pre_sum` 记录下以 `(0, 0)` 为左上角,以当前 `(row, col)` 为右下角的子数组各个元素和,即 `pre_sum[row + 1][col + 1]`。 + +则在查询时,以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和就等于以 `(0, 0)` 到 `(row2, col2)` 的大子矩阵减去左边 `(0, 0)` 到 `(row2, col1 - 1)`的子矩阵,再减去上边 `(0, 0)` 到 `(row1 - 1, col2)` 的子矩阵,再加上左上角 `(0, 0)` 到 `(row1 - 1, col1 - 1)` 的子矩阵(因为之前重复减了)。即 `pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1]`。 + +## 代码 + +```python +class NumMatrix: + + def __init__(self, matrix: List[List[int]]): + rows = len(matrix) + cols = len(matrix[0]) + self.pre_sum = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] + for row in range(rows): + for col in range(cols): + self.pre_sum[row + 1][col + 1] = self.pre_sum[row + 1][col] + self.pre_sum[row][col + 1] - self.pre_sum[row][col] + matrix[row][col] + + + def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int: + return self.pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1] +``` + diff --git a/docs/solutions/0300-0399/range-sum-query-immutable.md b/docs/solutions/0300-0399/range-sum-query-immutable.md new file mode 100644 index 00000000..73111598 --- /dev/null +++ b/docs/solutions/0300-0399/range-sum-query-immutable.md @@ -0,0 +1,161 @@ +# [0303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) + +- 标签:设计、数组、前缀和 +- 难度:简单 + +## 题目链接 + +- [0303. 区域和检索 - 数组不可变 - 力扣](https://leetcode.cn/problems/range-sum-query-immutable/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`。 + +**要求**:实现 `NumArray` 类,该类能处理区间为 `[left, right]` 之间的区间求和的多次查询。 + +`NumArray` 类: + +- `NumArray(int[] nums)` 使用数组 `nums` 初始化对象。 +- `int sumRange(int i, int j)` 返回数组 `nums` 中索引 `left` 和 `right` 之间的元素的 总和 ,包含 `left` 和 `right` 两点(也就是 `nums[left] + nums[left + 1] + ... + nums[right]`)。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $-10^5 \le nums[i] \le 10^5$。 +- $0 \le left \le right < nums.length$。 +- `sumRange` 方法调用次数不超过 $10^4$ 次。 + +**示例**: + +- 示例 1: + +```python +给定 nums = [-2, 0, 3, -5, 2, -1] + +求和 sumRange(0, 2) -> 1 +求和 sumRange(2, 5) -> -1 +求和 sumRange(0, 5) -> -3 +``` + +## 解题思路 + +### 思路 1:线段树 + +- 根据 `nums` 数组,构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。 + +这样构建线段树的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 + +### 思路 1 线段树代码: + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + + # 单点更新实现方法:将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + + # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + +class NumArray: + + def __init__(self, nums: List[int]): + self.STree = SegmentTree(nums, lambda x, y: x + y) + + + def sumRange(self, left: int, right: int) -> int: + return self.STree.query_interval(left, right) +``` diff --git a/docs/solutions/0300-0399/range-sum-query-mutable.md b/docs/solutions/0300-0399/range-sum-query-mutable.md new file mode 100644 index 00000000..b7085f7e --- /dev/null +++ b/docs/solutions/0300-0399/range-sum-query-mutable.md @@ -0,0 +1,169 @@ +# [0307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) + +- 标签:设计、树状数组、线段树、数组 +- 难度:中等 + +## 题目链接 + +- [0307. 区域和检索 - 数组可修改 - 力扣](https://leetcode.cn/problems/range-sum-query-mutable/) + +## 题目大意 + +**描述**:给定一个数组 `nums`。 + +**要求**: + +1. 完成两类查询: + 1. 要求将数组元素 `nums[index]` 的值更新为 `val`。 + 2. 要求返回数组 `nums` 中区间 `[left, right]` 之间(包含 `left`、`right`)的 `nums` 元素的和。其中 $left \le right$。 +2. 实现 `NumArray` 类: + 1. `NumArray(int[] nums)` 用整数数组 `nums` 初始化对象。 + 2. `void update(int index, int val)` 将 `nums[index]` 的值更新为 `val`。 + 3. `int sumRange(int left, int right)` 返回数组 `nums` 中索引 `left` 和索引 `right` 之间( 包含 )的 `nums` 元素的和(即 `nums[left] + nums[left + 1], ..., nums[right]`)。 + +**说明**: + +- $1 \le nums.length \le 3 * 10^4$。 +- $-100 \le nums[i] \le 100$。 +- $0 <= index < num.length$。 +- $0 \le left \le right < nums.length$。 +- 调用 `update` 和 `sumRange` 的方法次数不大于 $3 * 10^4$ 次。 + +**示例**: + +- 示例 1: + +``` +给定 nums = [1, 3, 5] + +求和 sumRange(0, 2) -> 9 +更新 update(1, 2) +求和 sumRange(0, 2) -> 8 +``` + +## 解题思路 + +### 思路 1:线段树 + +根据 `nums` 数组,构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。 + +这样构建线段树的时间复杂度为 $O(\log n)$,每次单点更新的时间复杂度为 $O(\log n)$,每次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 + +### 思路 1 线段树代码: + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + + # 单点更新实现方法:将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right] + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + + # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + +class NumArray: + + def __init__(self, nums: List[int]): + self.STree = SegmentTree(nums, lambda x, y: x + y) + + + def update(self, index: int, val: int) -> None: + self.STree.update_point(index, val) + + + def sumRange(self, left: int, right: int) -> int: + return self.STree.query_interval(left, right) +``` + diff --git a/docs/solutions/0300-0399/ransom-note.md b/docs/solutions/0300-0399/ransom-note.md new file mode 100644 index 00000000..d3e4e605 --- /dev/null +++ b/docs/solutions/0300-0399/ransom-note.md @@ -0,0 +1,77 @@ +# [0383. 赎金信](https://leetcode.cn/problems/ransom-note/) + +- 标签:哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [0383. 赎金信 - 力扣](https://leetcode.cn/problems/ransom-note/) + +## 题目大意 + +**描述**:为了不在赎金信中暴露字迹,从杂志上搜索各个需要的字母,组成单词来表达意思。 + +给定一个赎金信字符串 $ransomNote$ 和一个杂志字符串 $magazine$。 + +**要求**:判断 $ransomNote$ 能不能由 $magazines$ 里面的字符构成。如果可以构成,返回 `True`;否则返回 `False`。 + +**说明**: + +- $magazine$ 中的每个字符只能在 $ransomNote$ 中使用一次。 +- $1 \le ransomNote.length, magazine.length \le 10^5$。 +- $ransomNote$ 和 $magazine$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:ransomNote = "a", magazine = "b" +输出:False +``` + +- 示例 2: + +```python +输入:ransomNote = "aa", magazine = "ab" +输出:False +``` + +## 解题思路 + +### 思路 1:哈希表 + +暴力做法是双重循环遍历字符串 $ransomNote$ 和 $magazines$。我们可以用哈希表来减少算法的时间复杂度。具体做法如下: + +- 先用哈希表存储 $magazines$ 中各个字符的个数(哈希表可用字典或数组实现)。 +- 再遍历字符串 $ransomNote$ 中每个字符,对于每个字符: + - 如果在哈希表中个数为 $0$,直接返回 `False`。 + - 如果在哈希表中个数不为 $0$,将其个数减 $1$。 +- 遍历到最后,则说明 $ransomNote$ 能由 $magazines$ 里面的字符构成。返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + magazine_counts = [0 for _ in range(26)] + + for ch in magazine: + num = ord(ch) - ord('a') + magazine_counts[num] += 1 + + for ch in ransomNote: + num = ord(ch) - ord('a') + if magazine_counts[num] == 0: + return False + else: + magazine_counts[num] -= 1 + + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$,其中 $m$ 是字符串 $ransomNote$ 的长度,$n$ 是字符串 $magazines$ 的长度。 +- **空间复杂度**:$O(|S|)$,其中 $S$ 是字符集,本题中 $|S| = 26$。 + diff --git a/docs/solutions/0300-0399/remove-duplicate-letters.md b/docs/solutions/0300-0399/remove-duplicate-letters.md new file mode 100644 index 00000000..45202841 --- /dev/null +++ b/docs/solutions/0300-0399/remove-duplicate-letters.md @@ -0,0 +1,90 @@ +# [0316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) + +- 标签:栈、贪心、字符串、单调栈 +- 难度:中等 + +## 题目链接 + +- [0316. 去除重复字母 - 力扣](https://leetcode.cn/problems/remove-duplicate-letters/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:去除字符串中重复的字母,使得每个字母只出现一次。需要保证 **「返回结果的字典序最小(要求不能打乱其他字符的相对位置)」**。 + +**说明**: + +- $1 \le s.length \le 10^4$。 +- `s` 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "bcabc" +输出:"abc" +``` + +- 示例 2: + +```python +输入:s = "cbacdcbc" +输出:"acdb" +``` + +## 解题思路 + +### 思路 1:哈希表 + 单调栈 + +针对题目的三个要求:去重、不能打乱其他字符顺序、字典序最小。我们来一一分析。 + +1. **去重**:可以通过 **「使用哈希表存储字母出现次数」** 的方式,将每个字母出现的次数统计起来,再遍历一遍,去除重复的字母。 +2. **不能打乱其他字符顺序**:按顺序遍历,将非重复的字母存储到答案数组或者栈中,最后再拼接起来,就能保证不打乱其他字符顺序。 +3. **字典序最小**:意味着字典序小的字母应该尽可能放在前面。 + 1. 对于第 `i` 个字符 `s[i]` 而言,如果第 `0` ~ `i - 1` 之间的某个字符 `s[j]` 在 `s[i]` 之后不再出现了,那么 `s[j]` 必须放到 `s[i]` 之前。 + 2. 而如果 `s[j]` 在之后还会出现,并且 `s[j]` 的字典序大于 `s[i]`,我们则可以先舍弃 `s[j]`,把 `s[i]` 尽可能的放到前面。后边再考虑使用 `s[j]` 所对应的字符。 + + +要满足第 3 条需求,我们可以使用 **「单调栈」** 来解决。我们使用单调栈存储 `s[i]` 之前出现的非重复、并且字典序最小的字符序列。整个算法步骤如下: + +1. 先遍历一遍字符串,用哈希表 `letter_counts` 统计出每个字母出现的次数。 +2. 然后使用单调递减栈保存当前字符之前出现的非重复、并且字典序最小的字符序列。 +3. 当遍历到 `s[i]` 时,如果 `s[i]` 没有在栈中出现过: + 1. 比较 `s[i]` 和栈顶元素 `stack[-1]` 的字典序。如果 `s[i]` 的字典序小于栈顶元素 `stack[-1]`,并且栈顶元素之后的出现次数大于 `0`,则将栈顶元素弹出。 + 2. 然后继续判断 `s[i]` 和栈顶元素 `stack[-1]`,并且知道栈顶元素出现次数为 `0` 时停止弹出。此时将 `s[i]` 添加到单调栈中。 +4. 从哈希表 `letter_counts` 中减去 `s[i]` 出现的次数,继续遍历。 +5. 最后将单调栈中的字符依次拼接为答案字符串,并返回。 + +### 思路 1:代码 + +```python +class Solution: + def removeDuplicateLetters(self, s: str) -> str: + stack = [] + letter_counts = dict() + for ch in s: + if ch in letter_counts: + letter_counts[ch] += 1 + else: + letter_counts[ch] = 1 + + for ch in s: + if ch not in stack: + while stack and ch < stack[-1] and stack[-1] in letter_counts and letter_counts[stack[-1]] > 0: + stack.pop() + stack.append(ch) + letter_counts[ch] -= 1 + + return ''.join(stack) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 为字符集合,$|\sum|$ 为字符种类个数。由于栈中字符不能重复,因此栈中最多有 $|\sum|$ 个字符。 + +## 参考资料 + +- 【题解】[去除重复数组 - 去除重复字母 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicate-letters/solution/qu-chu-zhong-fu-shu-zu-by-lu-shi-zhe-sokp/) diff --git a/docs/solutions/0300-0399/reverse-string.md b/docs/solutions/0300-0399/reverse-string.md new file mode 100644 index 00000000..e8867fd1 --- /dev/null +++ b/docs/solutions/0300-0399/reverse-string.md @@ -0,0 +1,61 @@ +# [0344. 反转字符串](https://leetcode.cn/problems/reverse-string/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0344. 反转字符串 - 力扣](https://leetcode.cn/problems/reverse-string/) + +## 题目大意 + +**描述**:给定一个字符数组 $s$。 + +**要求**:将其反转。 + +**说明**: + +- 不能使用额外的数组空间,必须原地修改输入数组、使用 $O(1)$ 的额外空间解决问题。 +- $1 \le s.length \le 10^5$。 +- $s[i]$ 都是 ASCII 码表中的可打印字符。 + +**示例**: + +- 示例 1: + +```python +输入:s = ["h","e","l","l","o"] +输出:["o","l","l","e","h"] +``` + +- 示例 2: + +```python +输入:s = ["H","a","n","n","a","h"] +输出:["h","a","n","n","a","H"] +``` + +## 解题思路 + +### 思路 1:对撞指针 + +1. 使用两个指针 $left$,$right$。$left$ 指向字符数组开始位置,$right$ 指向字符数组结束位置。 +2. 交换 $s[left]$ 和 $s[right]$,将 $left$ 右移、$right$ 左移。 +3. 如果遇到 $left == right$,跳出循环。 + +### 思路 1:代码 + +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + left, right = 0, len(s) - 1 + while left < right: + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0300-0399/reverse-vowels-of-a-string.md b/docs/solutions/0300-0399/reverse-vowels-of-a-string.md new file mode 100644 index 00000000..03a132a1 --- /dev/null +++ b/docs/solutions/0300-0399/reverse-vowels-of-a-string.md @@ -0,0 +1,74 @@ +# [0345. 反转字符串中的元音字母](https://leetcode.cn/problems/reverse-vowels-of-a-string/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0345. 反转字符串中的元音字母 - 力扣](https://leetcode.cn/problems/reverse-vowels-of-a-string/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:将字符串中的元音字母进行反转。 + +**说明**: + +- 元音字母包括 `'a'`、`'e'`、`'i'`、`'o'`、`'u'`,且可能以大小写两种形式出现不止一次。 +- $1 \le s.length \le 3 \times 10^5$。 +- $s$ 由可打印的 ASCII 字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "hello" +输出:"holle" +``` + +- 示例 2: + +```python +输入:s = "leetcode" +输出:"leotcede" +``` + +## 解题思路 + +### 思路 1:对撞指针 + +1. 因为 Python 的字符串是不可变的,所以我们先将字符串转为数组。 +2. 使用两个指针 $left$,$right$。$left$ 指向字符串开始位置,$right$ 指向字符串结束位置。 +3. 然后 $left$ 依次从左到右移动查找元音字母,$right$ 依次从右到左查找元音字母。 +4. 如果都找到了元音字母,则交换字符,然后继续进行查找。 +5. 如果遇到 $left == right$ 时停止。 +6. 最后返回对应的字符串即可。 + +### 思路 1:代码 + +```python +class Solution: + def reverseVowels(self, s: str) -> str: + vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'] + left = 0 + right = len(s)-1 + s_list = list(s) + while left < right: + if s_list[left] not in vowels: + left += 1 + continue + if s_list[right] not in vowels: + right -= 1 + continue + s_list[left], s_list[right] = s_list[right], s_list[left] + left += 1 + right -= 1 + return "".join(s_list) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0300-0399/russian-doll-envelopes.md b/docs/solutions/0300-0399/russian-doll-envelopes.md new file mode 100644 index 00000000..3792521e --- /dev/null +++ b/docs/solutions/0300-0399/russian-doll-envelopes.md @@ -0,0 +1,61 @@ +# [0354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) + +- 标签:数组、二分查找、动态规划、排序 +- 难度:困难 + +## 题目链接 + +- [0354. 俄罗斯套娃信封问题 - 力扣](https://leetcode.cn/problems/russian-doll-envelopes/) + +## 题目大意 + +给定一个二维整数数组 envelopes 表示信封,其中 $envelopes[i] = [wi, hi]$,表示第 $i$ 个信封的宽度 $w_i$ 和高度 $h_i$。 + +当一个信封的宽度和高度比另一个信封大时,则小的信封可以放进大信封里,就像俄罗斯套娃一样。 + +现在要求:计算最多能有多少个信封组成一组「俄罗斯套娃」信封。 + +注意:不允许旋转信封(也就是说宽高不能互换)。 + +## 解题思路 + +如果最多有 k 个信封可以组成「俄罗斯套娃」信封。那么这 k 个信封按照宽高关系排序一定满足: + +- $w_0 < w_1 < ... < w_{k-1}$ +- $h_0 < h_1 < ... < h_{k-1}$ + +因为原二维数组是无序的,直接暴力搜素宽高升序序列并不容易。所以我们可以先固定一个维度,将其变为升序状态。再在另一个维度上进行选择。比如固定宽度为升序,则我们的问题就变为了:在高度这一维度下,求解数组的最长递增序列的长度。就变为了经典的「最长递增序列的长度问题」。即 [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)。 + +「最长递增序列的长度问题」的思路如下: + +动态规划的状态 `dp[i]` 表示为:以第 i 个数字结尾的前 i 个元素中最长严格递增子序列的长度。 + +遍历前 i 个数字,`0 ≤ j ≤ i`: + +- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 i 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。 +- 当 `nums[j] ≥ nums[i]` 时,可以直接跳过。 + +则状态转移方程为:`dp[i] = max(dp[i], dp[j] + 1)`,`0 ≤ j ≤ i`,`nums[j] < nums[i]`。 + +最后再遍历一遍 dp 数组,求出最大值即可。 + +## 代码 + +```python +class Solution: + def maxEnvelopes(self, envelopes: List[List[int]]) -> int: + if not envelopes: + return 0 + size = len(envelopes) + envelopes.sort(key=lambda x: (x[0], -x[1])) + + dp = [1 for _ in range(size)] + + for i in range(size): + for j in range(i): + if envelopes[j][1] < envelopes[i][1]: + dp[i] = max(dp[i], dp[j] + 1) + + return max(dp) +``` + diff --git a/docs/solutions/0300-0399/shuffle-an-array.md b/docs/solutions/0300-0399/shuffle-an-array.md new file mode 100644 index 00000000..6808e03d --- /dev/null +++ b/docs/solutions/0300-0399/shuffle-an-array.md @@ -0,0 +1,81 @@ +# [0384. 打乱数组](https://leetcode.cn/problems/shuffle-an-array/) + +- 标签:数组、数学、随机化 +- 难度:中等 + +## 题目链接 + +- [0384. 打乱数组 - 力扣](https://leetcode.cn/problems/shuffle-an-array/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是等可能的。 + +实现 `Solution class`: + +- `Solution(int[] nums)` 使用整数数组 $nums$ 初始化对象。 +- `int[] reset()` 重设数组到它的初始状态并返回。 +- `int[] shuffle()` 返回数组随机打乱后的结果。 + +**说明**: + +- $1 \le nums.length \le 50$。 +- $-10^6 \le nums[i] \le 10^6$。 +- $nums$ 中的所有元素都是 唯一的。 +- 最多可以调用 $10^4$ 次 `reset` 和 `shuffle`。 + +**示例**: + +- 示例 1: + +```python +输入: +["Solution", "shuffle", "reset", "shuffle"] +[[[1, 2, 3]], [], [], []] +输出: +[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]] + +解释: +Solution solution = new Solution([1, 2, 3]); +solution.shuffle(); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2] +solution.reset(); // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3] +solution.shuffle(); // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2] +``` + +## 解题思路 + +### 思路 1:洗牌算法 + +题目要求在打乱顺序后,数组的所有排列应该是等可能的。对于长度为 $n$ 的数组,我们可以把问题转换为:分别在 $n$ 个位置上,选择填入某个数的概率是相同。具体选择方法如下: + +- 对于第 $0$ 个位置,我们从 $0 \sim n - 1$ 总共 $n$ 个数中随机选择一个数,将该数与第 $0$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{1}{n}$。 +- 对于第 $1$ 个位置,我们从剩下 $n - 1$ 个数中随机选择一个数,将该数与第 $1$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{n - 1}{n} \times \frac{1}{n - 1} = \frac{1}{n}$ (第一次没选到并且第二次被选中)。 +- 对于第 $2$ 个位置,我们从剩下 $n - 2$ 个数中随机选择一个数,将该数与第 $2$ 个位置上的数进行交换。则每个数被选到的概率为 $\frac{n - 1}{n} \times \frac{n - 2}{n - 1} \times \frac{1}{n - 2} = \frac{1}{n}$ (第一次没选到、第二次没选到,并且第三次被选中)。 +- 依次类推,对于每个位置上,每个数被选中的概率都是 $\frac{1}{n}$。 + +### 思路 1:洗牌算法代码 + +```python +class Solution: + + def __init__(self, nums: List[int]): + self.nums = nums + + + def reset(self) -> List[int]: + return self.nums + + + def shuffle(self) -> List[int]: + self.shuffle_nums = self.nums.copy() + for i in range(len(self.shuffle_nums)): + swap_index = random.randrange(i, len(self.shuffle_nums)) + self.shuffle_nums[i], self.shuffle_nums[swap_index] = self.shuffle_nums[swap_index], self.shuffle_nums[i] + return self.shuffle_nums +``` + +## 参考资料 + +- 【题解】[「Python/Java/JavaScript/Go」 洗牌算法 - 打乱数组 - 力扣](https://leetcode.cn/problems/shuffle-an-array/solution/pythonjavajavascriptgo-xi-pai-suan-fa-by-k7i2/) \ No newline at end of file diff --git a/docs/solutions/0300-0399/sort-transformed-array.md b/docs/solutions/0300-0399/sort-transformed-array.md new file mode 100644 index 00000000..7720eceb --- /dev/null +++ b/docs/solutions/0300-0399/sort-transformed-array.md @@ -0,0 +1,123 @@ +# [0360. 有序转化数组](https://leetcode.cn/problems/sort-transformed-array/) + +- 标签:数组、数学、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0360. 有序转化数组 - 力扣](https://leetcode.cn/problems/sort-transformed-array/) + +## 题目大意 + +**描述**:给定一个已经排好的整数数组 $nums$ 和整数 $a$、$b$、$c$。 + +**要求**:对于数组中的每一个数 $x$,计算函数值 $f(x) = ax^2 + bx + c$,请将函数值产生的数组返回。 + +**说明**: + +- 返回的这个数组必须按照升序排列,并且我们所期望的解法时间复杂度为 $O(n)$。 +- $1 \le nums.length \le 200$。 +- $-100 \le nums[i], a, b, c \le 100$。 +- $nums$ 按照升序排列。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [-4,-2,2,4], a = 1, b = 3, c = 5 +输出: [3,9,15,33] +``` + +- 示例 2: + +```python +输入: nums = [-4,-2,2,4], a = -1, b = 3, c = 5 +输出: [-23,-5,1,7] +``` + +## 解题思路 + +### 思路 1: 数学 + 对撞指针 + +这是一道数学题。需要根据一元二次函数的性质来解决问题。因为返回的数组必须按照升序排列,并且期望的解法时间复杂度为 $O(n)$。这就不能先计算再排序了,而是要在线性时间复杂度内考虑问题。 + +我们先定义一个函数用来计算 $f(x)$。然后进行分情况讨论。 + +- 如果 $a == 0$,说明函数是一条直线。则根据 $b$ 值的正负来确定数组遍历顺序。 + - 如果 $b \ge 0$,说明这条直线是一条递增直线。则按照从头到尾的顺序依次计算函数值,并依次存入答案数组。 + - 如果 $b < 0$,说明这条直线是一条递减直线。则按照从尾到头的顺序依次计算函数值,并依次存入答案数组。 +- 如果 $a > 0$,说明函数是一条开口向上的抛物线,最小值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越大。则可以使用双指针从远到近,由大到小依次填入数组。具体步骤如下: + - 使用双指针 $left$、$right$,令 $left$ 指向数组第一个元素位置,$right$ 指向数组最后一个元素位置。再定义 $index = len(nums) - 1$ 作为答案数组填入顺序的索引值。 + - 比较 $left - diad$ 与 $right - diad$ 的绝对值大小。大的就是目前距离 $diad$ 最远的那个。 + - 如果 $abs(nums[left] - diad)$ 更大,则将其填入答案数组对应位置,并令 $left += 1$。 + - 如果 $abs(nums[right] - diad)$ 更大,则将其填入答案数组对应位置,并令 $right -= 1$。 + - 令 $index -= 1$。 + - 直到 $left == right$,最后将 $nums[left]$ 填入答案数组对应位置。 +- 如果 $a < 0$,说明函数是一条开口向下的抛物线,最大值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越小。则可以使用双指针从远到近,由小到大一次填入数组。具体步骤如下: + - 使用双指针 $left$、$right$,令 $left$ 指向数组第一个元素位置,$right$ 指向数组最后一个元素位置。再定义 $index = 0$ 作为答案数组填入顺序的索引值。 + - 比较 $left - diad$ 与 $right - diad$ 的绝对值大小。大的就是目前距离 $diad$ 最远的那个。 + - 如果 $abs(nums[left] - diad)$ 更大,则将其填入答案数组对应位置,并令 $left += 1$。 + - 如果 $abs(nums[right] - diad)$ 更大,则将其填入答案数组对应位置,并令 $right -= 1$。 + - 令 $index += 1$。 + - 直到 $left == right$,最后将 $nums[left]$ 填入答案数组对应位置。 + +### 思路 1:代码 + +```python +class Solution: + def calFormula(self, x, a, b, c): + return a * x * x + b * x + c + + def sortTransformedArray(self, nums: List[int], a: int, b: int, c: int) -> List[int]: + size = len(nums) + res = [0 for _ in range(size)] + + # 直线 + if a == 0: + if b >= 0: + index = 0 + for i in range(size): + res[index] = self.calFormula(nums[i], a, b, c) + index += 1 + else: + index = 0 + for i in range(size - 1, -1, -1): + res[index] = self.calFormula(nums[i], a, b, c) + index += 1 + else: + diad = -(b / (2.0 * a)) + left, right = 0, size - 1 + + if a > 0: + index = size - 1 + while left < right: + if abs(diad - nums[left]) > abs(diad - nums[right]): + res[index] = self.calFormula(nums[left], a, b, c) + left += 1 + else: + res[index] = self.calFormula(nums[right], a, b, c) + right -= 1 + index -= 1 + res[index] = self.calFormula(nums[left], a, b, c) + else: + diad = -(b / (2.0 * a)) + left, right = 0, size - 1 + index = 0 + while left < right: + if abs(diad - nums[left]) > abs(diad - nums[right]): + res[index] = self.calFormula(nums[left], a, b, c) + left += 1 + else: + res[index] = self.calFormula(nums[right], a, b, c) + right -= 1 + index += 1 + res[index] = self.calFormula(nums[left], a, b, c) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$,不考虑最终返回值的空间占用。 + diff --git a/docs/solutions/0300-0399/sum-of-two-integers.md b/docs/solutions/0300-0399/sum-of-two-integers.md new file mode 100644 index 00000000..50e9e1f4 --- /dev/null +++ b/docs/solutions/0300-0399/sum-of-two-integers.md @@ -0,0 +1,79 @@ +# [0371. 两整数之和](https://leetcode.cn/problems/sum-of-two-integers/) + +- 标签:位运算、数学 +- 难度:中等 + +## 题目链接 + +- [0371. 两整数之和 - 力扣](https://leetcode.cn/problems/sum-of-two-integers/) + +## 题目大意 + +**描述**:给定两个整数 $a$ 和 $b$。 + +**要求**:不使用运算符 `+` 和 `-` ,计算两整数 $a$ 和 $b$ 的和。 + +**说明**: + +- $-1000 \le a, b \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:a = 1, b = 2 +输出:3 +``` + +- 示例 2: + +```python +输入:a = 2, b = 3 +输出:5 +``` + +## 解题思路 + +### 思路 1:位运算 + +需要用到位运算的一些知识。 + +- 异或运算 `a ^ b`:可以获得 $a + b$ 无进位的加法结果。 +- 与运算 `a & b`:对应位置为 $1$,说明 $a$、$b$ 该位置上原来都为 $1$,则需要进位。 +- 左移运算 `a << 1`:将 $a$ 对应二进制数左移 $1$ 位。 + +这样,通过 `a ^ b` 运算,我们可以得到相加后无进位结果,再根据 `(a & b) << 1`,计算进位后结果。 + +进行 `a ^ b` 和 `(a & b) << 1` 操作之后判断进位是否为 $0$,若不为 $0$,则继续上一步操作,直到进位为 $0$。 + +> 注意: +> +> Python 的整数类型是无限长整数类型,负数不确定符号位是第几位。所以我们可以将输入的数字手动转为 $32$ 位无符号整数。 +> +> 通过 `a &= 0xFFFFFFFF` 即可将 $a$ 转为 $32$ 位无符号整数。最后通过对 $a$ 的范围判断,将其结果映射为有符号整数。 + +### 思路 1:代码 + +```python +class Solution: + def getSum(self, a: int, b: int) -> int: + MAX_INT = 0x7FFFFFFF + MASK = 0xFFFFFFFF + a &= MASK + b &= MASK + while b: + carry = ((a & b) << 1) & MASK + a ^= b + b = carry + if a <= MAX_INT: + return a + else: + return ~(a ^ MASK) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log k)$,其中 $k$ 为 $int$ 所能表达的最大整数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0300-0399/top-k-frequent-elements.md b/docs/solutions/0300-0399/top-k-frequent-elements.md new file mode 100644 index 00000000..e25c98ab --- /dev/null +++ b/docs/solutions/0300-0399/top-k-frequent-elements.md @@ -0,0 +1,137 @@ +# [0347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) + +- 标签:数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0347. 前 K 个高频元素 - 力扣](https://leetcode.cn/problems/top-k-frequent-elements/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 + +**要求**:返回出现频率前 $k$ 高的元素。可以按任意顺序返回答案。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $k$ 的取值范围是 $[1, \text{ 数组中不相同的元素的个数}]$。 +- 题目数据保证答案唯一,换句话说,数组中前 $k$ 个高频元素的集合是唯一的。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [1,1,1,2,2,3], k = 2 +输出: [1,2] +``` + +- 示例 2: + +```python +输入: nums = [1], k = 1 +输出: [1] +``` + +## 解题思路 + +### 思路 1:哈希表 + 优先队列 + +1. 使用哈希表记录下数组中各个元素的频数。 +2. 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +3. 使用二叉堆构建优先队列,优先级为元素频数。此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +4. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(log{n})$。 + - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 +5. 不断重复第 4 步,直到 $k$ 次结束。调整 $k$ 次的时间复杂度 $O(n \times \log n)$。 + +### 思路 1:代码 + +```python +class Heapq: + # 堆调整方法:调整为大顶堆 + def heapAdjust(self, nums: [int], nums_dict, index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子结点 + max_index = index + if nums_dict[nums[left]] > nums_dict[nums[max_index]]: + max_index = left + if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 将数组构建为二叉堆 + def heapify(self, nums: [int], nums_dict): + size = len(nums) + # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + # 调用调整堆函数 + self.heapAdjust(nums, nums_dict, i, size - 1) + + # 入队操作 + def heappush(self, nums: list, nums_dict, value): + nums.append(value) + size = len(nums) + i = size - 1 + # 寻找插入位置 + while (i - 1) // 2 >= 0: + cur_root = (i - 1) // 2 + # value 小于当前根节点,则插入到当前位置 + if nums_dict[nums[cur_root]] > nums_dict[value]: + break + # 继续向上查找 + nums[i] = nums[cur_root] + i = cur_root + # 找到插入位置或者到达根位置,将其插入 + nums[i] = value + + # 出队操作 + def heappop(self, nums: list, nums_dict) -> int: + size = len(nums) + nums[0], nums[-1] = nums[-1], nums[0] + # 得到最大值(堆顶元素)然后调整堆 + top = nums.pop() + if size > 0: + self.heapAdjust(nums, nums_dict, 0, size - 2) + + return top + +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + # 统计元素频数 + nums_dict = dict() + for num in nums: + if num in nums_dict: + nums_dict[num] += 1 + else: + nums_dict[num] = 1 + + # 使用 set 方法去重,得到新数组 + new_nums = list(set(nums)) + size = len(new_nums) + + heap = Heapq() + queue = [] + for num in new_nums: + heap.heappush(queue, nums_dict, num) + + res = [] + for i in range(k): + res.append(heap.heappop(queue, nums_dict)) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0300-0399/valid-perfect-square.md b/docs/solutions/0300-0399/valid-perfect-square.md new file mode 100644 index 00000000..f6483ec9 --- /dev/null +++ b/docs/solutions/0300-0399/valid-perfect-square.md @@ -0,0 +1,70 @@ +# [0367. 有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) + +- 标签:数学、二分查找 +- 难度:简单 + +## 题目链接 + +- [0367. 有效的完全平方数 - 力扣](https://leetcode.cn/problems/valid-perfect-square/) + +## 题目大意 + +**描述**:给定一个正整数 $num$。 + +**要求**:判断 num 是不是完全平方数。 + +**说明**: + +- 要求不能使用内置的库函数,如 `sqrt`。 +- $1 \le num \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:num = 16 +输出:True +解释:返回 true,因为 4 * 4 = 16 且 4 是一个整数。 +``` + +- 示例 2: + +```python +输入:num = 14 +输出:False +解释:返回 false,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +如果 $num$ 是完全平方数,则 $num = x \times x$,$x$ 为整数。问题就变为了对于正整数 $num$,是否能找到一个整数 $x$,使得 $x \times x = num$。 + +而对于 $x$,我们可以通过二分查找算法快速找到。 + +### 思路 1:代码 + +```python +class Solution: + def isPerfectSquare(self, num: int) -> bool: + left = 0 + right = num + while left < right: + mid = left + (right - left) // 2 + if mid * mid > num: + right = mid - 1 + elif mid * mid < num: + left = mid + 1 + else: + left = mid + break + return left * left == num +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$,其中 $n$ 为正整数 $num$ 的最大值。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0300-0399/wiggle-sort-ii.md b/docs/solutions/0300-0399/wiggle-sort-ii.md new file mode 100644 index 00000000..2387e012 --- /dev/null +++ b/docs/solutions/0300-0399/wiggle-sort-ii.md @@ -0,0 +1,53 @@ +# [0324. 摆动排序 II](https://leetcode.cn/problems/wiggle-sort-ii/) + +- 标签:数组、分治、快速选择、排序 +- 难度:中等 + +## 题目链接 + +- [0324. 摆动排序 II - 力扣](https://leetcode.cn/problems/wiggle-sort-ii/) + +## 题目大意 + +给你一个整数数组 `nums`。 + +要求:将它重新排列成 `nums[0] < nums[1] > nums[2] < nums[3] ...` 的顺序。可以假设所有输入数组都可以得到满足题目要求的结果。 + +注意: + +- $1 \le nums.length \le 5 * 10^4$。 +- $0 \le nums[i] \le 5000$。 + +## 解题思路 + +`num[i]` 的取值在 `[0, 5000]`。所以我们可以用桶排序算法将排序算法的时间复杂度降到 $O(n)$。然后按照下标的奇偶性遍历两次数组,第一次遍历将桶中的元素从末尾到头部依次放到对应奇数位置上。第二次遍历将桶中剩余元素从末尾到头部依次放到对应偶数位置上。 + +## 代码 + +```python +class Solution: + def wiggleSort(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + buckets = [0 for _ in range(5010)] + for num in nums: + buckets[num] += 1 + + size = len(nums) + big = size - 2 if (size & 1) == 1 else size - 1 + small = size - 1 if (size & 1) == 1 else size - 2 + + index = 5000 + for i in range(1, big + 1, 2): + while buckets[index] == 0: + index -= 1 + nums[i] = index + buckets[index] -= 1 + for i in range(0, small + 1, 2): + while buckets[index] == 0: + index -= 1 + nums[i] = index + buckets[index] -= 1 +``` + diff --git a/docs/solutions/0300-0399/wiggle-subsequence.md b/docs/solutions/0300-0399/wiggle-subsequence.md new file mode 100644 index 00000000..42054408 --- /dev/null +++ b/docs/solutions/0300-0399/wiggle-subsequence.md @@ -0,0 +1,66 @@ +# [0376. 摆动序列](https://leetcode.cn/problems/wiggle-subsequence/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0376. 摆动序列 - 力扣](https://leetcode.cn/problems/wiggle-subsequence/) + +## 题目大意 + +如果一个数组序列中,连续项之间的差值是严格的在正数、负数之间交替,则称该数组序列为「摆动序列」。第一个差值可能为正数,也可能为负数。只有一个元素或者还有两个不等元素的数组序列也可以看做是摆动序列。 + +- 例如:`[1, 7, 4, 9, 2, 5]` 是摆动序列 ,因为差值 `(6, -3, 5, -7, 3)` 是正负交替出现的。 +- 相反,`[1, 4, 7, 2, 5]` 和 `[1, 7, 4, 5, 5]` 不是摆动序列。第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 + +现在给定一个整数数组 nums,返回 nums 中作为「摆动序列」的「最长子序列长度」。 + +## 解题思路 + +我们先通过一个例子来说明如何求摆动数组最长子序列的长度。 + +下图是 `nums = [1,17,5,10,13,15,10,5,16,8]` 的图示。 + +![](http://qcdn.itcharge.cn/images/20210805131834.png) + +根据题意可知,摆动数组中连续项的差值是正负交替的,直观表现就像是一条高低起伏的山脉,或者像一把锯齿。 + +观察图像可知,貌似当我们不断交错的选择山脉的「波峰」和「波谷」作为子序列的元素,就会使摆动数组的子序列尽可能的长。例如下图选择 `[1, 17, 5, 15, 5, 16, 8]`。 + +![](http://qcdn.itcharge.cn/images/20210805131848.png) + +可是为什么选择「峰」「谷」就能使摆动数组的子序列尽可能的长?为什么我们不选择「中间元素」呢? + +其实也可以选择「中间元素」,**因为一路从波谷爬坡到波峰再到波谷,和从波谷爬坡到半山腰再回到波谷所形成的摆动数组最长子序列的长度是一样的。** + +只不过如果选择中间元素,这个中间元素两侧必有波峰和波谷,我们假设选择的序列出现顺序为:「谷 -> 中间元素 -> 谷」,则「谷」和「谷」中间的「峰」必定没有出现在选择的序列中,我们必然可以将选择的「中间元素」替换为「峰」。 + +同理,「峰 -> 中间元素 -> 峰」中选择的「中间元素」必然也可以替换为「谷」。 + +所以既然中可以替换,所以我们干脆直接选择「峰」「谷」就可以满足最长子序列的长度。 + +所以题目就变为了:统计序列中「峰」「谷」的数量。 + +记录下前一对连续项的差值、当前对连续项的差值,并判断是否是互为正负的。 + +- 如果互为正负,则为「峰」或「谷」,记录下个数,并更新前一对连续项的差值。 +- 如果符号相同,则继续向后判断。 + +## 代码 + +```python +class Solution: + def wiggleMaxLength(self, nums: List[int]) -> int: + size = len(nums) + cur_diff = 0 + pre_diff = 0 + res = 1 + for i in range(size - 1): + cur_diff = nums[i + 1] - nums[i] + if (cur_diff > 0 and pre_diff <= 0) or (pre_diff >= 0 and cur_diff < 0): + res += 1 + pre_diff = cur_diff + return res +``` + diff --git a/docs/solutions/0400-0499/4sum-ii.md b/docs/solutions/0400-0499/4sum-ii.md new file mode 100644 index 00000000..118c27f2 --- /dev/null +++ b/docs/solutions/0400-0499/4sum-ii.md @@ -0,0 +1,87 @@ +# [0454. 四数相加 II](https://leetcode.cn/problems/4sum-ii/) + +- 标签:数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [0454. 四数相加 II - 力扣](https://leetcode.cn/problems/4sum-ii/) + +## 题目大意 + +**描述**:给定四个整数数组 $nums1$、$nums2$、$nums3$、$nums4$。 + +**要求**:计算有多少不同的 $(i, j, k, l)$ 满足以下条件。 + +1. $0 \le i, j, k, l < n$。 +2. $nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0$。 + +**说明**: + +- $n == nums1.length$。 +- $n == nums2.length$。 +- $n == nums3.length$。 +- $n == nums4.length$。 +- $1 \le n \le 200$。 +- $-2^{28} \le nums1[i], nums2[i], nums3[i], nums4[i] \le 2^{28}$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] +输出:2 +解释: +两个元组如下: +1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0 +2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0 +``` + +- 示例 2: + +```python +输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0] +输出:1 +``` + +## 解题思路 + +### 思路 1:哈希表 + +直接暴力搜索的时间复杂度是 $O(n^4)$。我们可以降低一下复杂度。 + +将四个数组分为两组。$nums1$ 和 $nums2$ 分为一组,$nums3$ 和 $nums4$ 分为一组。 + +已知 $nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0$,可以得到 $nums1[i] + nums2[j] = -(nums3[k] + nums4[l])$ + +建立一个哈希表。两重循环遍历数组 $nums1$、$nums2$,先将 $nums[i] + nums[j]$ 的和个数记录到哈希表中,然后再用两重循环遍历数组 $nums3$、$nums4$。如果 $-(nums3[k] + nums4[l])$ 的结果出现在哈希表中,则将结果数累加到答案中。最终输出累加之后的答案。 + +### 思路 1:代码 + +```python +class Solution: + def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int: + nums_dict = dict() + for num1 in nums1: + for num2 in nums2: + sum = num1 + num2 + if sum in nums_dict: + nums_dict[sum] += 1 + else: + nums_dict[sum] = 1 + count = 0 + for num3 in nums3: + for num4 in nums4: + sum = num3 + num4 + if -sum in nums_dict: + count += nums_dict[-sum] + + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组的元素个数。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0400-0499/add-strings.md b/docs/solutions/0400-0499/add-strings.md new file mode 100644 index 00000000..53724198 --- /dev/null +++ b/docs/solutions/0400-0499/add-strings.md @@ -0,0 +1,87 @@ +# [0415. 字符串相加](https://leetcode.cn/problems/add-strings/) + +- 标签:数学、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [0415. 字符串相加 - 力扣](https://leetcode.cn/problems/add-strings/) + +## 题目大意 + +**描述**:给定两个字符串形式的非负整数 `num1` 和`num2`。 + +**要求**:计算它们的和,并同样以字符串形式返回。 + +**说明**: + +- $1 \le num1.length, num2.length \le 10^4$。 +- $num1$ 和 $num2$ 都只包含数字 $0 \sim 9$。 +- $num1$ 和 $num2$ 都不包含任何前导零。 +- 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 + +**示例**: + +- 示例 1: + +```python +输入:num1 = "11", num2 = "123" +输出:"134" +``` + +- 示例 2: + +```python +输入:num1 = "456", num2 = "77" +输出:"533" +``` + +## 解题思路 + +### 思路 1:双指针 + +需要用字符串的形式来模拟大数加法。 + +加法的计算方式是:从个位数开始,由低位到高位,按位相加,如果相加之后超过 `10`,就需要向前进位。 + +模拟加法的做法是: + +1. 用一个数组存储按位相加后的结果,每一位对应一位数。 +2. 然后分别使用一个指针变量,对两个数 `num1`、`num2` 字符串进行反向遍历,将相加后的各个位置上的结果保存在数组中,这样计算完成之后就得到了一个按位反向的结果。 +3. 最后返回结果的时候将数组反向转为字符串即可。 + +注意需要考虑 `num1`、`num2` 不等长的情况,让短的那个字符串对应位置按 $0$ 计算即可。 + +### 思路 1:代码 + +```python +class Solution: + def addStrings(self, num1: str, num2: str) -> str: + # num1 位数 + digit1 = len(num1) - 1 + # num2 位数 + digit2 = len(num2) - 1 + + # 进位 + carry = 0 + # sum 存储反向结果 + sum = [] + # 逆序相加 + while carry > 0 or digit1 >= 0 or digit2 >= 0: + # 获取对应位数上的数字 + num1_d = int(num1[digit1]) if digit1 >= 0 else 0 + num2_d = int(num2[digit2]) if digit2 >= 0 else 0 + digit1 -= 1 + digit2 -= 1 + # 计算结果,存储,进位 + num = num1_d+num2_d+carry + sum.append('%d'%(num%10)) + carry = num // 10 + # 返回计算结果 + return "".join(sum[::-1]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(max(m + n))$。其中 $m$ 是字符串 $num1$ 的长度,$n$ 是字符串 $num2$ 的长度。 +- **空间复杂度**:$O(max(m + n))$。 \ No newline at end of file diff --git a/docs/solutions/0400-0499/add-two-numbers-ii.md b/docs/solutions/0400-0499/add-two-numbers-ii.md new file mode 100644 index 00000000..6a4e0214 --- /dev/null +++ b/docs/solutions/0400-0499/add-two-numbers-ii.md @@ -0,0 +1,58 @@ +# [0445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) + +- 标签:栈、链表、数学 +- 难度:中等 + +## 题目链接 + +- [0445. 两数相加 II - 力扣](https://leetcode.cn/problems/add-two-numbers-ii/) + +## 题目大意 + +给定两个非空链表的头节点 `l1` 和 `l2` 来代表两个非负整数。数字最高位位于链表开始位置。每个节点只储存一位数字。除了数字 `0` 之外,这两个链表代表的数字都不会以 `0` 开头。 + +要求:将这两个数相加会返回一个新的链表。 + +## 解题思路 + +链表中最高位位于链表开始位置,最低位位于链表结束位置。这与我们做加法的数位顺序是相反的。为了将链表逆序,从而从低位开始处理数位,我们可以借用两个栈:将链表中所有数字分别压入两个栈中,再依次取出相加。 + +同时,在相加的时候,还要考虑进位问题。具体步骤如下: + +- 将链表 `l1` 中所有节点值压入 `stack1` 栈中,再将链表 `l2` 中所有节点值压入 `stack2` 栈中。 +- 使用 `res` 存储新的结果链表,一开始指向 `None`,`carry` 记录进位。 +- 如果 `stack1` 或 `stack2` 不为空,或着进位 `carry` 不为 `0`,则: + - 从 `stack1` 中取出栈顶元素 `num1`,如果 `stack1` 为空,则 `num1 = 0`。 + - 从 `stack2` 中取出栈顶元素 `num2`,如果 `stack2` 为空,则 `num2 = 0`。 + - 计算相加结果,并计算进位。 + - 建立新节点,存储进位后余下的值,并令其指向 `res`。 + - `res` 指向新节点,继续判断。 +- 如果 `stack1`、`stack2` 都为空,并且进位 `carry` 为 `0`,则输出 `res`。 + +## 代码 + +```python +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + stack1, stack2 = [], [] + while l1: + stack1.append(l1.val) + l1 = l1.next + while l2: + stack2.append(l2.val) + l2 = l2.next + + res = None + carry = 0 + while stack1 or stack2 or carry != 0: + num1 = stack1.pop() if stack1 else 0 + num2 = stack2.pop() if stack2 else 0 + cur_sum = num1 + num2 + carry + carry = cur_sum // 10 + cur_sum %= 10 + cur_node = ListNode(cur_sum) + cur_node.next = res + res = cur_node + return res +``` + diff --git a/docs/solutions/0400-0499/assign-cookies.md b/docs/solutions/0400-0499/assign-cookies.md new file mode 100644 index 00000000..11628322 --- /dev/null +++ b/docs/solutions/0400-0499/assign-cookies.md @@ -0,0 +1,87 @@ +# [0455. 分发饼干](https://leetcode.cn/problems/assign-cookies/) + +- 标签:贪心、数组、双指针、排序 +- 难度:简单 + +## 题目链接 + +- [0455. 分发饼干 - 力扣](https://leetcode.cn/problems/assign-cookies/) + +## 题目大意 + +**描述**:一位很棒的家长为孩子们分发饼干。对于每个孩子 `i`,都有一个胃口值 `g[i]`,即每个小孩希望得到饼干的最小尺寸值。对于每块饼干 `j`,都有一个尺寸值 `s[j]`。只有当 `s[j] > g[i]` 时,我们才能将饼干 `j` 分配给孩子 `i`。每个孩子最多只能给一块饼干。 + +现在给定代表所有孩子胃口值的数组 `g` 和代表所有饼干尺寸的数组 `j`。 + +**要求**:尽可能满足越多数量的孩子,并求出这个最大数值。 + +**说明**: + +- $1 \le g.length \le 3 * 10^4$。 +- $0 \le s.length \le 3 * 10^4$。 +- $1 \le g[i], s[j] \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:g = [1,2,3], s = [1,1] +输出:1 +解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1, 2, 3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以应该输出 1。 +``` + +- 示例 2: + +```python +输入: g = [1,2], s = [1,2,3] +输出: 2 +解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1, 2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +为了尽可能的满⾜更多的⼩孩,而且一块饼干不能掰成两半,所以我们应该尽量让胃口小的孩子吃小块饼干,这样胃口大的孩子才有大块饼干吃。 + +所以,从贪心算法的角度来考虑,我们应该按照孩子的胃口从小到大对数组 `g` 进行排序,然后按照饼干的尺寸大小从小到大对数组 `s` 进行排序,并且对于每个孩子,应该选择满足这个孩子的胃口且尺寸最小的饼干。 + +下面我们使用贪心算法三步走的方法解决这道题。 + +1. **转换问题**:将原问题转变为,当胃口最小的孩子选择完满足这个孩子的胃口且尺寸最小的饼干之后,再解决剩下孩子的选择问题(子问题)。 +2. **贪心选择性质**:对于当前孩子,用尺寸尽可能小的饼干满足这个孩子的胃口。 +3. **最优子结构性质**:在上面的贪心策略下,当前孩子的贪心选择 + 剩下孩子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得满足胃口的孩子数量达到最大。 + +使用贪心算法的代码解决步骤描述如下: + +1. 对数组 `g`、`s` 进行从小到大排序,使用变量 `index_g` 和 `index_s` 分别指向 `g`、`s` 初始位置,使用变量 `res` 保存结果,初始化为 `0`。 +2. 对比每个元素 `g[index_g]` 和 `s[index_s]`: + 1. 如果 `g[index_g] <= s[index_s]`,说明当前饼干满足当前孩子胃口,则答案数量加 `1`,并且向右移动 `index_g` 和 `index_s`。 + 2. 如果 `g[index_g] > s[index_s]`,说明当前饼干无法满足当前孩子胃口,则向右移动 `index_s`,判断下一块饼干是否可以满足当前孩子胃口。 +3. 遍历完输出答案 `res`。 + +### 思路 1:代码 + +```python +class Solution: + def findContentChildren(self, g: List[int], s: List[int]) -> int: + g.sort() + s.sort() + index_g, index_s = 0, 0 + res = 0 + while index_g < len(g) and index_s < len(s): + if g[index_g] <= s[index_s]: + res += 1 + index_g += 1 + index_s += 1 + else: + index_s += 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times \log m + n \times \log n)$,其中 $m$ 和 $n$ 分别是数组 $g$ 和 $s$ 的长度。 +- **空间复杂度**:$O(\log m + \log n)$。 diff --git a/docs/solutions/0400-0499/can-i-win.md b/docs/solutions/0400-0499/can-i-win.md new file mode 100644 index 00000000..b1129749 --- /dev/null +++ b/docs/solutions/0400-0499/can-i-win.md @@ -0,0 +1,97 @@ +# [0464. 我能赢吗](https://leetcode.cn/problems/can-i-win/) + +- 标签:位运算、记忆化搜索、数学、动态规划、状态压缩、博弈 +- 难度:中等 + +## 题目链接 + +- [0464. 我能赢吗 - 力扣](https://leetcode.cn/problems/can-i-win/) + +## 题目大意 + +**描述**:给定两个整数,$maxChoosableInteger$ 表示可以选择的最大整数,$desiredTotal$ 表示累计和。现在开始玩一个游戏,两个玩家轮流从 $1 \sim maxChoosableInteger$ 中不重复的抽取一个整数,直到累积整数和大于等于 $desiredTotal$ 时,这个人就赢得比赛。假设两位玩家玩游戏时都表现最佳。 + +**要求**:判断先出手的玩家是否能够稳赢,如果能稳赢,则返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le maxChoosableInteger \le 20$。 +- $0 \le desiredTotal \le 300$。 + +**示例**: + +- 示例 1: + +```python +输入:maxChoosableInteger = 10, desiredTotal = 11 +输出:False +解释: +无论第一个玩家选择哪个整数,他都会失败。 +第一个玩家可以选择从 1 到 10 的整数。 +如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。 +第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利. +同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。 +``` + +- 示例 2: + +```python +输入:maxChoosableInteger = 10, desiredTotal = 0 +输出:True +``` + +## 解题思路 + +### 思路 1:状态压缩 + 记忆化搜索 + +$maxChoosableInteger$ 的区间范围是 $[1, 20]$,数据量不是很大,我们可以使用状态压缩来判断当前轮次中数字的选取情况。 + +题目假设两位玩家玩游戏时都表现最佳,则每个人都会尽力去赢,在每轮次中,每个人都会分析此次选择后,对后续轮次的影响,判断自己是必赢还是必输。 + +1. 如果当前轮次选择某个数之后,自己一定会赢时,才会选择这个数。 +2. 如果当前轮次无论选择哪个数,自己一定会输时,那无论选择哪个数其实都已经无所谓了。 + +这样我们可以定义一个递归函数 `dfs(state, curTotal)`,用于判断处于状态 $state$,并且当前累计和为 $curTotal$ 时,自己是否一定会赢。如果自己一定会赢,返回 `True`,否则返回 `False`。递归函数内容如下: + +1. 从 $1 \sim maxChoosableInteger$ 中选择一个之前没有选过的数 $k$。 +2. 如果选择的数 $k$ 加上当前的整数和 $curTotal$ 之后大于等于 $desiredTotal$,则自己一定会赢。 +3. 如果选择的数 $k$ 之后,对方必输(即递归调用 `dfs(state | (1 << (k - 1)), curTotal + k)` 为 `Flase` 时),则自己一定会赢。 +4. 如果无论选择哪个数,自己都赢不了,则自己必输,返回 `False`。 + +这样,我们从 $state = 0, curTotal = 0$ 开始调用递归方法 `dfs(state, curTotal)`,即可判断先出手的玩家是否能够稳赢。 + +接下来,我们还需要考虑一些边界条件。 + +1. 当 $maxChoosableInteger$ 直接大于等于 $desiredTotal$,则先手玩家无论选什么,直接就赢了,这种情况下,我们直接返回 `True`。 +2. 当 $1 \sim maxChoosableInteger$ 中所有数加起来都小于 $desiredTotal$,则先手玩家无论怎么选,都无法稳赢,题目要求我们判断先出手的玩家是否能够稳赢,既然先手无法稳赢,我们直接返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def canIWin(self, maxChoosableInteger: int, desiredTotal: int) -> bool: + @cache + def dfs(state, curTotal): + for k in range(1, maxChoosableInteger + 1): # 从 1 ~ maxChoosableInteger 中选择一个数 + if state >> (k - 1) & 1 != 0: # 如果之前选过该数则跳过 + continue + if curTotal + k >= desiredTotal: # 如果选择了 k,累积整数和大于等于 desiredTotal,则该玩家一定赢 + return True + if not dfs(state | (1 << (k - 1)), curTotal + k): # 如果当前选择了 k 之后,对手一定输,则当前玩家一定赢 + return True + return False # 以上都赢不了的话,当前玩家一定输 + + # maxChoosableInteger 直接大于等于 desiredTotal,则先手玩家一定赢 + if maxChoosableInteger >= desiredTotal: + return True + + # 1 ~ maxChoosableInteger 所有数加起来都不够 desiredTotal,则先手玩家一定输 + if (1 + maxChoosableInteger) * maxChoosableInteger // 2 < desiredTotal: + return False + return dfs(0, 0) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为 $maxChoosableInteger$。 +- **空间复杂度**:$O(2^n)$。 diff --git a/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md b/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md new file mode 100644 index 00000000..6d17c2f0 --- /dev/null +++ b/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md @@ -0,0 +1,84 @@ +# [0405. 数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) + +- 标签:位运算、数学 +- 难度:简单 + +## 题目链接 + +- [0405. 数字转换为十六进制数 - 力扣](https://leetcode.cn/problems/convert-a-number-to-hexadecimal/) + +## 题目大意 + +**描述**:给定一个整数 $num$。 + +**要求**:编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用「补码运算」方法。 + +**说明**: + +- 十六进制中所有字母($a \sim f$)都必须是小写。 +- 十六进制字符串中不能包含多余的前导零。如果要转化的数为 $0$,那么以单个字符 $0$ 来表示。 +- 对于其他情况,十六进制字符串中的第一个字符将不会是 $0$ 字符。 +- 给定的数确保在 $32$ 位有符号整数范围内。 +- 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。 + +**示例**: + +- 示例 1: + +```python +输入: +26 + +输出: +"1a" +``` + +- 示例 2: + +```python +输入: +-1 + +输出: +"ffffffff" +``` + +## 解题思路 + +### 思路 1:模拟 + +主要是对不同情况的处理。 + +- 当 $num$ 为 0 时,直接返回 $0$。 +- 当 $num$ 为负数时,对负数进行「补码运算」,转换为对应的十进制正数(将其绝对值与 $2^{32} - 1$ 异或再加 1),然后执行和 $nums$ 为正数一样的操作。 +- 当 $num$ 为正数时,将其对 $16$ 取余,并转为对应的十六进制字符,并按位拼接到字符串中,再将 $num$ 除以 $16$,继续对 $16$ 取余,直到 $num$ 变为为 0。 +- 最后将拼接好的字符串逆序返回就是答案。 + +### 思路 1:代码 + +```python +class Solution: + def toHex(self, num: int) -> str: + res = '' + if num == 0: + return '0' + + if num < 0: + num = (abs(num) ^ (2 ** 32 - 1)) + 1 + + while num: + digit = num % 16 + if digit >= 10: + digit = chr(ord('a') + digit - 10) + else: + digit = str(digit) + res += digit + num >>= 4 + return res[::-1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(C)$,其中 $C$ 为构造的十六进制数的长度。 +- **空间复杂度**:$O(C)$。 + diff --git a/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md b/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md new file mode 100644 index 00000000..8f9ebc7e --- /dev/null +++ b/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md @@ -0,0 +1,58 @@ +# [0426. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) + +- 标签:栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 +- 难度:中等 + +## 题目链接 + +- [0426. 将二叉搜索树转化为排序的双向链表 - 力扣](https://leetcode.cn/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:将这棵二叉树转换为一个已排序的双向循环链表。要求不能创建新的节点,只能调整树中节点指针的指向。 + +## 解题思路 + +通过中序递归遍历可以将二叉树升序排列输出。这道题需要在中序遍历的同时,将节点的左右指向进行改变。使用 `head`、`tail` 存放双向链表的头尾节点,然后从根节点开始,进行中序递归遍历。 + +具体做法如下: + +- 如果当前节点为空,直接返回。 +- 如果当前节点不为空: + - 递归遍历左子树。 + - 如果尾节点不为空,则将尾节点与当前节点进行连接。 + - 如果尾节点为空,则初始化头节点。 + - 将当前节点标记为尾节点。 + - 递归遍历右子树。 +- 最后将头节点和尾节点进行连接。 + +## 代码 + +```python +class Solution: + def treeToDoublyList(self, root: 'Node') -> 'Node': + def dfs(node: 'Node'): + if not node: + return + + dfs(node.left) + if self.tail: + self.tail.right = node + node.left = self.tail + else: + self.head = node + self.tail = node + dfs(node.right) + + if not root: + return None + + self.head, self.tail = None, None + dfs(root) + self.head.left = self.tail + self.tail.right = self.head + return self.head +``` + diff --git a/docs/solutions/0400-0499/delete-node-in-a-bst.md b/docs/solutions/0400-0499/delete-node-in-a-bst.md new file mode 100644 index 00000000..fbb009ba --- /dev/null +++ b/docs/solutions/0400-0499/delete-node-in-a-bst.md @@ -0,0 +1,91 @@ +# [0450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) + +- 标签:树、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0450. 删除二叉搜索树中的节点 - 力扣](https://leetcode.cn/problems/delete-node-in-a-bst/) + +## 题目大意 + +**描述**:给定一个二叉搜索树的根节点 `root`,以及一个值 `key`。 + +**要求**:从二叉搜索树中删除 key 对应的节点。并保证删除后的树仍是二叉搜索树。要求算法时间复杂度为 $0(h)$,$h$ 为树的高度。最后返回二叉搜索树的根节点。 + +**说明**: + +- 节点数的范围 $[0, 10^4]$。 +- $-10^5 \le Node.val \le 10^5$。 +- 节点值唯一。 +- `root` 是合法的二叉搜索树。 +- $-10^5 \le key \le 10^5$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2020/09/04/del_node_1.jpg) + +```python +输入:root = [5,3,6,2,4,null,7], key = 3 +输出:[5,4,6,2,null,null,7] +解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 +一个正确的答案是 [5,4,6,2,null,null,7], 如上图所示。 +另一个正确答案是 [5,2,6,null,4,null,7]。 +``` + +- 示例 2: + +```python +输入: root = [5,3,6,2,4,null,7], key = 0 +输出: [5,3,6,2,4,null,7] +解释: 二叉树不包含值为 0 的节点 +``` + +## 解题思路 + +### 思路 1:递归 + +删除分两个步骤:查找和删除。查找通过递归查找,删除的话需要考虑情况。 + +1. 从根节点 `root` 开始,递归遍历搜索二叉树。 + 1. 如果当前节点节点为空,返回当前节点。 + 2. 如果当前节点值大于 `key`,则去左子树中搜索并删除,此时 `root.left` 也要跟着递归更新,递归完成后返回当前节点。 + 3. 如果当前节点值小于 `key`,则去右子树中搜索并删除,此时 `root.right` 也要跟着递归更新,递归完成后返回当前节点。 + 4. 如果当前节点值等于 `key`,则该节点就是待删除节点。 + 1. 如果当前节点的左子树为空,则删除该节点之后,则右子树代替当前节点位置,返回右子树。 + 2. 如果当前节点的右子树为空,则删除该节点之后,则左子树代替当前节点位置,返回左子树。 + 3. 如果当前节点的左右子树都有,则将左子树转移到右子树最左侧的叶子节点位置上,然后右子树代替当前节点位置。返回右子树。 + +### 思路 1:代码 + +```python +class Solution: + def deleteNode(self, root: TreeNode, key: int) -> TreeNode: + if not root: + return root + + if root.val > key: + root.left = self.deleteNode(root.left, key) + return root + elif root.val < key: + root.right = self.deleteNode(root.right, key) + return root + else: + if not root.left: + return root.right + elif not root.right: + return root.left + else: + curr = root.right + while curr.left: + curr = curr.left + curr.left = root.left + return root.right +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0400-0499/diagonal-traverse.md b/docs/solutions/0400-0499/diagonal-traverse.md new file mode 100644 index 00000000..494233c8 --- /dev/null +++ b/docs/solutions/0400-0499/diagonal-traverse.md @@ -0,0 +1,111 @@ +# [0498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) + +- 标签:数组、矩阵、模拟 +- 难度:中等 + +## 题目链接 + +- [0498. 对角线遍历 - 力扣](https://leetcode.cn/problems/diagonal-traverse/) + +## 题目大意 + +**描述**:给定一个大小为 $m \times n$ 的矩阵 $mat$ 。 + +**要求**:以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。 + +**说明**: + +- $m == mat.length$。 +- $n == mat[i].length$。 +- $1 \le m, n \le 10^4$。 +- $1 \le m \times n \le 10^4$。 +- $-10^5 \le mat[i][j] \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/04/10/diag1-grid.jpg) + +```python +输入:mat = [[1,2,3],[4,5,6],[7,8,9]] +输出:[1,2,4,7,5,3,6,8,9] +``` + +- 示例 2: + +```python +输入:mat = [[1,2],[3,4]] +输出:[1,2,3,4] +``` + +## 解题思路 + +### 思路 1:找规律 + 考虑边界问题 + +这道题的关键是「找规律」和「考虑边界问题」。 + +找规律: + +1. 当「行号 + 列号」为偶数时,遍历方向为从左下到右上。可以记为右上方向 $(-1, +1)$,即行号减 $1$,列号加 $1$。 +2. 当「行号 + 列号」为奇数时,遍历方向为从右上到左下。可以记为左下方向 $(+1, -1)$,即行号加 $1$,列号减 $1$。 + +边界情况: + +1. 向右上方向移动时: + 1. 如果在最后一列,则向下方移动,即 `x += 1`。 + 2. 如果在第一行,则向右方移动,即 `y += 1`。 + 3. 其余情况想右上方向移动,即 `x -= 1`、`y += 1`。 +2. 向左下方向移动时: + 1. 如果在最后一行,则向右方移动,即 `y += 1`。 + 2. 如果在第一列,则向下方移动,即 `x += 1`。 + 3. 其余情况向左下方向移动,即 `x += 1`、`y -= 1`。 + +### 思路 1:代码 + +```python +class Solution: + def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]: + rows = len(mat) + cols = len(mat[0]) + count = rows * cols + x, y = 0, 0 + ans = [] + + for i in range(count): + ans.append(mat[x][y]) + + if (x + y) % 2 == 0: + # 最后一列 + if y == cols - 1: + x += 1 + # 第一行 + elif x == 0: + y += 1 + # 右上方向 + else: + x -= 1 + y += 1 + else: + # 最后一行 + if x == rows - 1: + y += 1 + # 第一列 + elif y == 0: + x += 1 + # 左下方向 + else: + x += 1 + y -= 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$、$n$ 分别为二维矩阵的行数、列数。 +- **空间复杂度**:$O(m \times n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(m \times n)$。不算上则空间复杂度为 $O(1)$。 + +## 参考资料 + +- 【题解】[「498. 对角线遍历」最简单易懂! - 对角线遍历 - 力扣(LeetCode)](https://leetcode.cn/problems/diagonal-traverse/solution/498-dui-jiao-xian-bian-li-zui-jian-dan-y-ibu3/) + diff --git a/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md b/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md new file mode 100644 index 00000000..87fdc2a5 --- /dev/null +++ b/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md @@ -0,0 +1,96 @@ +# [0438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0438. 找到字符串中所有字母异位词 - 力扣](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) + +## 题目大意 + +**描述**:给定两个字符串 $s$ 和 $p$。 + +**要求**:找到 $s$ 中所有 $p$ 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 + +**说明**: + +- **异位词**:指由相同字母重排列形成的字符串(包括相同的字符串)。 +- $1 <= s.length, p.length <= 3 * 10^4$。 +- $s$ 和 $p$ 仅包含小写字母。 + +**示例**: + +- 示例 1: + +```python +输入: s = "cbaebabacd", p = "abc" +输出: [0,6] +解释: +起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 +起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。 +``` + +- 示例 2: + +```python +输入: s = "abab", p = "ab" +输出: [0,1,2] +解释: +起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。 +起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。 +起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +维护一个固定长度为 $len(p)$ 的滑动窗口。于是问题的难点变为了如何判断 $s$ 的子串和 $p$ 是异位词。可以使用两个字典来分别存储 $s$ 的子串中各个字符个数和 $p$ 中各个字符个数。如果两个字典对应的键值全相等,则说明 $s$ 的子串和 $p$ 是异位词。但是这样每一次比较的操作时间复杂度是 $O(n)$,我们可以通过在滑动数组中逐字符比较的方式来减少两个字典之间相互比较的复杂度,并用 $valid$ 记录经过验证的字符个数。整个算法步骤如下: + +- 使用哈希表 $need$ 记录 $p$ 中各个字符出现次数。使用字典 $window$ 记录 $s$ 的子串中各个字符出现的次数。使用数组 $res$ 记录答案。使用 $valid$ 记录 $s$ 的子串中经过验证的字符个数。使用 $window\underline{\hspace{0.5em}}size$ 表示窗口大小,值为 $len(p)$。使用两个指针 $left$、$right$。分别指向滑动窗口的左右边界。 +- 一开始,$left$、$right$ 都指向 $0$。 +- 如果 $s[right]$ 出现在 $need$ 中,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。并验证该字符是否和 $need$ 中个对应字符个数相等。如果相等则验证的字符个数加 $1$,即 `valid += 1`。 +- 如果该窗口字符长度大于等于 $window\underline{\hspace{0.5em}}size$ 个,即 $right - left + 1 \ge window\underline{\hspace{0.5em}}size$。则不断右移 $left$,缩小滑动窗口长度。 + - 如果验证字符个数 $valid$ 等于窗口长度 $window\underline{\hspace{0.5em}}size$,则 $s[left, right + 1]$ 为 $p$ 的异位词,所以将 $left$ 加入到答案数组中。 + - 如果$s[left]$ 在 $need$ 中,则更新窗口中对应字符的个数,同时维护 $valid$ 值。 +- 右移 $right$,直到 $right \ge len(nums)$ 结束。 +- 输出答案数组 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def findAnagrams(self, s: str, p: str) -> List[int]: + need = collections.defaultdict(int) + for ch in p: + need[ch] += 1 + + window = collections.defaultdict(int) + window_size = len(p) + res = [] + left, right = 0, 0 + valid = 0 + while right < len(s): + if s[right] in need: + window[s[right]] += 1 + if window[s[right]] == need[s[right]]: + valid += 1 + + if right - left + 1 >= window_size: + if valid == len(need): + res.append(left) + if s[left] in need: + if window[s[left]] == need[s[left]]: + valid -= 1 + window[s[left]] -= 1 + left += 1 + right += 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m + |\sum|)$,其中 $n$、$m$ 分别为字符串 $s$、$p$ 的长度,$\sum$ 为字符集,本题中 $|\sum| = 26$。 +- **空间复杂度**:$|\sum|$。 + diff --git a/docs/solutions/0400-0499/fizz-buzz.md b/docs/solutions/0400-0499/fizz-buzz.md new file mode 100644 index 00000000..d8f3d342 --- /dev/null +++ b/docs/solutions/0400-0499/fizz-buzz.md @@ -0,0 +1,41 @@ +# [0412. Fizz Buzz](https://leetcode.cn/problems/fizz-buzz/) + +- 标签:数学、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [0412. Fizz Buzz - 力扣](https://leetcode.cn/problems/fizz-buzz/) + +## 题目大意 + +给定一个整数 n,按照规则,输出 1~n 的字符串表示。 + +规则: + +- 如果 i 是 3 的倍数,输出 "Fizz"; +- 如果 i 是 5 的倍数,输出 "Buzz"; +- 如果 i 是 3 和 5 的倍数,则输出 "FizzBuzz"。 + +## 解题思路 + +简单题,按照题目规则输出即可。 + +## 代码 + +```python +class Solution: + def fizzBuzz(self, n: int) -> List[str]: + ans = [] + for i in range(1,n+1): + if i % 15 == 0: + ans.append("FizzBuzz") + elif i % 3 == 0: + ans.append("Fizz") + elif i % 5 == 0: + ans.append("Buzz") + else: + ans.append(str(i)) + return ans +``` + diff --git a/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md b/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md new file mode 100644 index 00000000..c9a3d3bb --- /dev/null +++ b/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md @@ -0,0 +1,64 @@ +# [0430. 扁平化多级双向链表](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) + +- 标签:深度优先搜索、链表、双向链表 +- 难度:中等 + +## 题目链接 + +- [0430. 扁平化多级双向链表 - 力扣](https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/) + +## 题目大意 + +给定一个带子链表指针 child 的双向链表,将 child 的子链表进行扁平化处理,使所有节点出现在单级双向链表中。 + +扁平化处理如下: + +``` +原链表: +1---2---3---4---5---6--NULL + | + 7---8---9---10--NULL + | + 11--12--NULL +扁平化之后: +1---2---3---7---8---11---12---9---10---4---5---6--NULL +``` + + + +## 解题思路 + +递归处理多层链表的扁平化。遍历链表,找到 child 非空的节点, 将其子链表链接到当前节点的 next 位置(自身扁平化处理)。然后继续向后遍历,不断找到 child 节点,并进行链接。直到处理到尾部位置。 + +## 代码 + +```python +class Solution: + def dfs(self, node: 'Node'): + # 找到链表的尾节点或 child 链表不为空的节点 + while node.next and not node.child: + node = node.next + tail = None + if node.child: + # 如果 child 链表不为空,将 child 链表扁平化 + tail = self.dfs(node.child) + + # 将扁平化的 child 链表链接在该节点之后 + temp = node.next + node.next = node.child + node.next.prev = node + node.child = None + tail.next = temp + if temp: + temp.prev = tail + # 链接之后,从 child 链表的尾节点继续向后处理链表 + return self.dfs(tail) + # child 链表为空,则该节点是尾节点,直接返回 + return node + def flatten(self, head: 'Node') -> 'Node': + if not head: + return head + self.dfs(head) + return head +``` + diff --git a/docs/solutions/0400-0499/frog-jump.md b/docs/solutions/0400-0499/frog-jump.md new file mode 100644 index 00000000..0f2f17e8 --- /dev/null +++ b/docs/solutions/0400-0499/frog-jump.md @@ -0,0 +1,109 @@ +# [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0403. 青蛙过河 - 力扣](https://leetcode.cn/problems/frog-jump/) + +## 题目大意 + +**描述**:一只青蛙要过河,这条河被等分为若干个单元格,每一个单元格内可能放油一块石子(也可能没有)。青蛙只能跳到有石子的单元格内,不能跳到没有石子的单元格内。 + +现在给定一个严格按照升序排序的数组 $stones$,其中 $stones[i]$ 代表第 $i$ 块石子所在的单元格序号。默认第 $0$ 块石子序号为 $0$(即 $stones[0] == 0$)。 + +开始时,青蛙默认站在序号为 $0$ 石子上(即 $stones[0]$),并且假定它第 $1$ 步只能跳跃 $1$ 个单位(即只能从序号为 $0$ 的单元格跳到序号为 $1$ 的单元格)。 + +如果青蛙在上一步向前跳跃了 $k$ 个单位,则下一步只能向前跳跃 $k - 1$、$k$ 或者 $k + 1$ 个单位。 + +**要求**:判断青蛙能否成功过河(即能否在最后一步跳到最后一块石子上)。如果能,则返回 `True`;否则,则返回 `False`。 + +**说明**: + +- $2 \le stones.length \le 2000$。 +- $0 \le stones[i] \le 2^{31} - 1$。 +- $stones[0] == 0$。 +- $stones$ 按严格升序排列。 + +**示例**: + +- 示例 1: + +```python +输入:stones = [0,1,3,5,6,8,12,17] +输出:true +解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +题目中说:如果青蛙在上一步向前跳跃了 $k$ 个单位,则下一步只能向前跳跃 $k - 1$、$k$ 或者 $k + 1$ 个单位。则下一步的状态可以由 $3$ 种状态转移而来。 + +- 上一步所在石子到下一步所在石头的距离为 $k - 1$。 +- 上一步所在石子到下一步所在石头的距离为 $k$。 +- 上一步所在石子到下一步所在石头的距离为 $k + 1$。 + +则我们可以通过石子块数,跳跃距离来进行阶段划分和定义状态,以及推导状态转移方程。 + +###### 1. 划分阶段 + +按照石子块数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][k]$ 表示为:青蛙能否以长度为 $k$ 的距离,到达第 $i$ 块石子。 + +###### 3. 状态转移方程 + +1. 外层循环遍历每一块石子 $i$,对于每一块石子 $i$,使用内层循环遍历石子 $i$ 之前所有的石子 $j$。 +2. 并计算出上一步所在石子 $j$ 到当前所在石子 $i$ 之间的距离为 $k$。 +3. 如果上一步所在石子 $j$ 通过上上一步以长度为 $k - 1$、$k$ 或者 $k + 1$ 的距离到达石子 $j$,那么当前步所在石子也可以通过 $k$ 的距离到达石子 $i$。即通过检查 $dp[j][k - 1]$、$dp[j][k]$、$dp[j][k + 1]$ 中是否至少有一个为真,即可判断 $dp[i][k]$ 是否为真。 + - 即:$dp[i][k] = dp[j][k - 1] \text{ or } dp[j][k] or dp[j][k + 1] $。 + +###### 4. 初始条件 + +刚开始青蛙站在序号为 $0$ 石子上(即 $stones[0]$),肯定能以长度为 $0$ 的距离,到达第 $0$ 块石子,即 $dp[0][0] = True$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][k]$ 表示为:青蛙能否以长度为 $k$ 的距离,到达第 $i$ 块石子。则如果 $dp[size - 1][k]$ 为真,则说明青蛙能成功过河(即能在最后一步跳到最后一块石子上);否则则说明青蛙不能成功过河。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def canCross(self, stones: List[int]) -> bool: + size = len(stones) + + stone_dict = dict() + for i in range(size): + stone_dict[stones[i]] = i + + dp = [[False for _ in range(size + 1)] for _ in range(size)] + dp[0][0] = True + + for i in range(1, size): + for j in range(i): + k = stones[i] - stones[j] + if k <= 0 or k > j + 1: + continue + + dp[i][k] = dp[j][k - 1] or dp[j][k] or dp[j][k + 1] + + if dp[size - 1][k]: + return True + + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 + +## 参考资料 + +- 【题解】[【403. 青蛙过河】理解理解动态规划与dfs - 青蛙过河 - 力扣](https://leetcode.cn/problems/frog-jump/solution/403-qing-wa-guo-he-li-jie-li-jie-dong-ta-oyt9/) \ No newline at end of file diff --git a/docs/solutions/0400-0499/hamming-distance.md b/docs/solutions/0400-0499/hamming-distance.md new file mode 100644 index 00000000..97dc0c4e --- /dev/null +++ b/docs/solutions/0400-0499/hamming-distance.md @@ -0,0 +1,52 @@ +# [0461. 汉明距离](https://leetcode.cn/problems/hamming-distance/) + +- 标签:位运算 +- 难度:简单 + +## 题目链接 + +- [0461. 汉明距离 - 力扣](https://leetcode.cn/problems/hamming-distance/) + +## 题目大意 + +给定两个整数 x 和 y,计算他们之间的汉明距离。 + +- 汉明距离:两个数字对应二进制位上不同的位置的数目 + +## 解题思路 + +先对两个数进行异或运算(相同位置上,值相同,结果为 0,值不同,结果为 1),用于记录 x 和 y 不同位置上的异同情况。 + +然后再按位统计异或结果中 1 的位数。 + +这里统计 1 的位数可以逐位移动,检查每一位是否为 1。 + +也可以借助 $n \text{ \& } (n - 1)$ 运算。这个运算刚好可以将 n 的二进制中最低位的 1 变为 0。 + +## 代码 + +1. 逐位移动 +```python +class Solution: + def hammingDistance(self, x: int, y: int) -> int: + xor = x ^ y + distance = 0 + while xor: + if xor & 1: + distance += 1 + xor >>= 1 + return distance +``` + +2. $n \text{ \& } (n - 1)$ 运算 +```python +class Solution: + def hammingDistance(self, x: int, y: int) -> int: + xor = x ^ y + distance = 0 + while xor: + distance += 1 + xor = xor & (xor - 1) + return distance +``` + diff --git a/docs/solutions/0400-0499/index.md b/docs/solutions/0400-0499/index.md new file mode 100644 index 00000000..8b26bf6f --- /dev/null +++ b/docs/solutions/0400-0499/index.md @@ -0,0 +1,47 @@ +## 本章内容 + +- [0400. 第 N 位数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/nth-digit.md) +- [0403. 青蛙过河](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/frog-jump.md) +- [0404. 左叶子之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sum-of-left-leaves.md) +- [0405. 数字转换为十六进制数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-a-number-to-hexadecimal.md) +- [0406. 根据身高重建队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/queue-reconstruction-by-height.md) +- [0409. 最长回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/longest-palindrome.md) +- [0410. 分割数组的最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/split-array-largest-sum.md) +- [0412. Fizz Buzz](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/fizz-buzz.md) +- [0415. 字符串相加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-strings.md) +- [0416. 分割等和子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/partition-equal-subset-sum.md) +- [0417. 太平洋大西洋水流问题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/pacific-atlantic-water-flow.md) +- [0421. 数组中两个数的最大异或值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md) +- [0424. 替换后的最长重复字符](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/longest-repeating-character-replacement.md) +- [0425. 单词方块](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/word-squares.md) +- [0426. 将二叉搜索树转化为排序的双向链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/convert-binary-search-tree-to-sorted-doubly-linked-list.md) +- [0428. 序列化和反序列化 N 叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/serialize-and-deserialize-n-ary-tree.md) +- [0429. N 叉树的层序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/n-ary-tree-level-order-traversal.md) +- [0430. 扁平化多级双向链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/flatten-a-multilevel-doubly-linked-list.md) +- [0435. 无重叠区间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-overlapping-intervals.md) +- [0437. 路径总和 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/path-sum-iii.md) +- [0438. 找到字符串中所有字母异位词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/find-all-anagrams-in-a-string.md) +- [0443. 压缩字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/string-compression.md) +- [0445. 两数相加 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/add-two-numbers-ii.md) +- [0447. 回旋镖的数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/number-of-boomerangs.md) +- [0450. 删除二叉搜索树中的节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/delete-node-in-a-bst.md) +- [0451. 根据字符出现频率排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sort-characters-by-frequency.md) +- [0452. 用最少数量的箭引爆气球](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md) +- [0454. 四数相加 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/4sum-ii.md) +- [0455. 分发饼干](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/assign-cookies.md) +- [0459. 重复的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/repeated-substring-pattern.md) +- [0461. 汉明距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/hamming-distance.md) +- [0463. 岛屿的周长](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/island-perimeter.md) +- [0464. 我能赢吗](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/can-i-win.md) +- [0467. 环绕字符串中唯一的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md) +- [0468. 验证IP地址](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/validate-ip-address.md) +- [0473. 火柴拼正方形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/matchsticks-to-square.md) +- [0474. 一和零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/ones-and-zeroes.md) +- [0480. 滑动窗口中位数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/sliding-window-median.md) +- [0485. 最大连续 1 的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones.md) +- [0486. 预测赢家](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/predict-the-winner.md) +- [0487. 最大连续1的个数 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/max-consecutive-ones-ii.md) +- [0491. 非递减子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/non-decreasing-subsequences.md) +- [0494. 目标和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/target-sum.md) +- [0496. 下一个更大元素 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/next-greater-element-i.md) +- [0498. 对角线遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/diagonal-traverse.md) diff --git a/docs/solutions/0400-0499/island-perimeter.md b/docs/solutions/0400-0499/island-perimeter.md new file mode 100644 index 00000000..0d4cf576 --- /dev/null +++ b/docs/solutions/0400-0499/island-perimeter.md @@ -0,0 +1,99 @@ +# [0463. 岛屿的周长](https://leetcode.cn/problems/island-perimeter/) + +- 标签:深度优先搜索、广度优先搜索、数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [0463. 岛屿的周长 - 力扣](https://leetcode.cn/problems/island-perimeter/) + +## 题目大意 + +**描述**:给定一个 `row * col` 大小的二维网格地图 `grid` ,其中:`grid[i][j] = 1` 表示陆地,`grid[i][j] = 0` 表示水域。 + +网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(多个表示陆地的格子相连组成)。 + +岛屿内部中没有「湖」(指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。 + +**要求**:计算这个岛屿的周长。 + +**说明**: + +- $row == grid.length$。 +- $col == grid[i].length$。 +- $1 <= row, col <= 100$。 +- $grid[i][j]$ 为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/island.png) + +```python +输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]] +输出:16 +解释:它的周长是上面图片中的 16 个黄色的边 +``` + +- 示例 2: + +```python +输入:grid = [[1]] +输出:4 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +1. 使用整形变量 `count` 存储周长,使用队列 `queue` 用于进行广度优先搜索。 +2. 遍历一遍二维数组 `grid`,对 `grid[row][col] == 1` 的区域进行广度优先搜索。 +3. 先将起始点 `(row, col)` 加入队列。 +4. 如果队列不为空,则取出队头坐标 `(row, col)`。先将 `(row, col)` 标记为 `2`,避免重复统计。 +5. 然后遍历上、下、左、右四个方向的相邻区域,如果遇到边界或者水域,则周长加 1。 +6. 如果相邻区域 `grid[new_row][new_col] == 1`,则将其赋值为 `2`,并将坐标加入队列。 +7. 继续执行 4 ~ 6 步,直到队列为空时返回 `count`。 + +### 思路 1:代码 + +```python +class Solution: + def bfs(self, grid, rows, cols, row, col): + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + queue = collections.deque([(row, col)]) + + count = 0 + while queue: + row, col = queue.popleft() + # 避免重复统计 + grid[row][col] = 2 + for direct in directs: + new_row = row + direct[0] + new_col = col + direct[1] + # 遇到边界或者水域,则周长加 1 + if new_row < 0 or new_row >= rows or new_col < 0 or new_col >= cols or grid[new_row][new_col] == 0: + count += 1 + # 相邻区域为陆地,则将其标记为 2,加入队列 + elif grid[new_row][new_col] == 1: + grid[new_row][new_col] = 2 + queue.append((new_row, new_col)) + # 相邻区域为 2 的情况不做处理 + return count + + def islandPerimeter(self, grid: List[List[int]]) -> int: + rows, cols = len(grid), len(grid[0]) + for row in range(rows): + for col in range(cols): + if grid[row][col] == 1: + return self.bfs(grid, rows, cols, row, col) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(n \times m)$。 + +## 参考资料 + +- 【题解】[Golang BFS 实现,性能比dfs要高 - 岛屿的周长 - 力扣](https://leetcode.cn/problems/island-perimeter/solution/golang-bfs-shi-xian-xing-neng-bi-dfsyao-nln2g/) diff --git a/docs/solutions/0400-0499/longest-palindrome.md b/docs/solutions/0400-0499/longest-palindrome.md new file mode 100644 index 00000000..a3a23102 --- /dev/null +++ b/docs/solutions/0400-0499/longest-palindrome.md @@ -0,0 +1,51 @@ +# [0409. 最长回文串](https://leetcode.cn/problems/longest-palindrome/) + +- 标签:贪心、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0409. 最长回文串 - 力扣](https://leetcode.cn/problems/longest-palindrome/) + +## 题目大意 + +给定一个包含大写字母和小写字母的字符串 `s`。 + +要求:找到通过这些字母构造成的最长的回文串。 + +注意: + +- 在构造过程中,请注意区分大小写。比如 `Aa` 不能当做一个回文字符串。 +- 假设字符串的长度不会超过 `1010`。 + +## 解题思路 + +这道题目是通过给定字母构造回文串,并找到最长的回文串长度。那就要先看看回文串的特点。在回文串中,最多只有一个字母出现过奇数次,其余字符都出现过偶数次。且相同字母是中心对称的。 + +则我们可以用哈希表统计字符出现次数。对于每个字符,使用尽可能多的偶数次字符作为回文串的两侧,并记录下使用的字符个数,记录到答案中。再使用一个 `flag` 标记下是否有奇数次的字符,如果有的话,最终答案再加 1。最后输出答案。 + +## 代码 + +```python +class Solution: + def longestPalindrome(self, s: str) -> int: + word_dict = dict() + for ch in s: + if ch in word_dict: + word_dict[ch] += 1 + else: + word_dict[ch] = 1 + + ans = 0 + flag = False + for value in word_dict.values(): + ans += value // 2 * 2 + if value % 2 == 1: + flag = True + + if flag: + ans += 1 + + return ans +``` + diff --git a/docs/solutions/0400-0499/longest-repeating-character-replacement.md b/docs/solutions/0400-0499/longest-repeating-character-replacement.md new file mode 100644 index 00000000..4668e405 --- /dev/null +++ b/docs/solutions/0400-0499/longest-repeating-character-replacement.md @@ -0,0 +1,83 @@ +# [0424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0424. 替换后的最长重复字符 - 力扣](https://leetcode.cn/problems/longest-repeating-character-replacement/) + +## 题目大意 + +**描述**:给定一个仅由大写英文字母组成的字符串 $s$,以及一个整数 $k$。可以将任意位置上的字符替换成另外的大写字母,最多可替换 $k$ 次。 + +**要求**:在进行上述操作后,找到包含重复字母的最长子串长度。 + +**说明**: + +- $1 \le s.length \le 10^5$。 +- $s$ 仅由大写英文字母组成。 +- $0 \le k \le s.length$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "ABAB", k = 2 +输出:4 +解释:用两个'A'替换为两个'B',反之亦然。 +``` + +- 示例 2: + +```python +输入:s = "AABABBA", k = 1 +输出:4 +解释: +将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。 +子串 "BBBB" 有最长重复字母, 答案为 4。 +可能存在其他的方法来得到同样的结果。 +``` + +## 解题思路 + +先来考虑暴力求法。枚举字符串 s 的所有子串,对于每一个子串: + +- 统计子串中出现次数最多的字符,替换除它以外的字符 k 次。 +- 维护最长子串的长度。 + +但是这种暴力求法中,枚举子串的时间复杂度为 $O(n^2)$,统计出现次数最多的字符和替换字符时间复杂度为 $0(n)$,且两者属于平行处理,总体下来的时间复杂度为 $O(n^3)$。这样做会超时。 + +### 思路 1:滑动窗口 + +1. 使用 counts 数组来统计字母频数。使用 left、right 双指针分别指向滑动窗口的首尾位置,使用 max_count 来维护最长子串的长度。 +2. 不断右移 right 指针,增加滑动窗口的长度。 +3. 对于当前滑动窗口的子串,如果当前窗口的间距 > 当前出现最大次数的字符的次数 + k 时,意味着替换 k 次仍不能使当前窗口中的字符全变为相同字符,则此时应该将左边界右移,同时将原先左边界的字符频次减少。 + +### 思路 1:代码 + +```python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + max_count = 0 + left, right = 0, 0 + counts = [0 for _ in range(26)] + while right < len(s): + num_right = ord(s[right]) - ord('A') + counts[num_right] += 1 + max_count = max(max_count, counts[num_right]) + right += 1 + if right - left > max_count + k: + num_left = ord(s[left]) - ord('A') + counts[num_left] -= 1 + left += 1 + + return right - left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串的长度。 +- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 是字符集,本题中 $| \sum | = 26$。 + diff --git a/docs/solutions/0400-0499/matchsticks-to-square.md b/docs/solutions/0400-0499/matchsticks-to-square.md new file mode 100644 index 00000000..0fe3cbf7 --- /dev/null +++ b/docs/solutions/0400-0499/matchsticks-to-square.md @@ -0,0 +1,86 @@ +# [0473. 火柴拼正方形](https://leetcode.cn/problems/matchsticks-to-square/) + +- 标签:位运算、数组、动态规划、回溯、状态压缩 +- 难度:中等 + +## 题目链接 + +- [0473. 火柴拼正方形 - 力扣](https://leetcode.cn/problems/matchsticks-to-square/) + +## 题目大意 + +**描述**:给定一个表示火柴长度的数组 $matchsticks$,其中 $matchsticks[i]$ 表示第 $i$ 根火柴的长度。 + +**要求**:找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以将火柴连接起来,并且每根火柴都要用到。如果能拼成正方形,则返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le matchsticks.length \le 15$。 +- $1 \le matchsticks[i] \le 10^8$。 + +**示例**: + +- 示例 1: + +```python +输入: matchsticks = [1,1,2,2,2] +输出: True +解释: 能拼成一个边长为 2 的正方形,每边两根火柴。 +``` + +- 示例 2: + +```python +输入: matchsticks = [3,3,3,3,4] +输出: False +解释: 不能用所有火柴拼成一个正方形。 +``` + +## 解题思路 + +### 思路 1:回溯算法 + +1. 先排除数组为空和火柴总长度不是 $4$ 的倍数的情况,直接返回 `False`。 +2. 然后将火柴按照从大到小排序。用数组 $sums$ 记录四个边长分组情况。 +3. 将火柴分为 $4$ 组,把每一根火柴依次向 $4$ 条边上放。 +4. 直到放置最后一根,判断能否构成正方形,若能构成正方形,则返回 `True`,否则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, index, sums, matchsticks, size, side_len): + if index == size: + return True + + for i in range(4): + # 如果两条边的情况相等,只需要计算一次,没必要多次重复计算 + if i > 0 and sums[i] == sums[i - 1]: + continue + sums[i] += matchsticks[index] + if sums[i] <= side_len and self.dfs(index + 1, sums, matchsticks, size, side_len): + return True + sums[i] -= matchsticks[index] + + return False + + def makesquare(self, matchsticks: List[int]) -> bool: + if not matchsticks: + return False + size = len(matchsticks) + sum_len = sum(matchsticks) + if sum_len % 4 != 0: + return False + + side_len = sum_len // 4 + matchsticks.sort(reverse=True) + + sums = [0 for _ in range(4)] + return self.dfs(0, sums, matchsticks, size, side_len) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(4^n)$。$n$ 是火柴的数目。 +- **空间复杂度**:$O(n)$。递归栈的空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0400-0499/max-consecutive-ones-ii.md b/docs/solutions/0400-0499/max-consecutive-ones-ii.md new file mode 100644 index 00000000..a7b648ed --- /dev/null +++ b/docs/solutions/0400-0499/max-consecutive-ones-ii.md @@ -0,0 +1,80 @@ +# [0487. 最大连续1的个数 II](https://leetcode.cn/problems/max-consecutive-ones-ii/) + +- 标签:数组、动态规划、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0487. 最大连续1的个数 II - 力扣](https://leetcode.cn/problems/max-consecutive-ones-ii/) + +## 题目大意 + +**描述**:给定一个二进制数组 $nums$,可以最多将 $1$ 个 $0$ 翻转为 $1$。 + +**要求**:如果最多可以翻转一个 $0$,则返回数组中连续 $1$ 的最大个数。 + +**说明**: + +- 1 <= nums.length <= 105 + nums[i] 不是 0 就是 1. + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,0,1,1,0] +输出:4 +解释:翻转第一个 0 可以得到最长的连续 1。当翻转以后,最大连续 1 的个数为 4。 +``` + +- 示例 2: + +```python +输入:nums = [1,0,1,1,0,1] +输出:4 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +暴力做法是尝试将每个位置的 $0$ 分别变为 $1$,然后统计最大连续 $1$ 的个数。但这样复杂度就太高了。 + +我们可以使用滑动窗口来解决问题。保证滑动窗口内最多有 $1$ 个 $0$。具体做法如下: + +设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证滑动窗口内最多有 $1$ 个 $0$。使用 $zero\underline{\hspace{0.5em}}count$ 统计窗口内 $1$ 的个数。使用 $ans$ 记录答案。 + +- 一开始,$left$、$right$ 都指向 $0$。 +- 如果 $nums[right] == 0$,则窗口内 $1$ 的个数加 $1$。 +- 如果该窗口中 $1$ 的个数多于 $1$ 个,即 $zero\underline{\hspace{0.5em}}count > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中 $1$ 的个数,直到 $zero\underline{\hspace{0.5em}}count \le 1$。 +- 维护更新最大连续 $1$ 的个数。然后右移 $right$,直到 $right \ge len(nums)$ 结束。 +- 输出最大连续 $1$ 的个数。 + +### 思路 1:代码 + +```python +class Solution: + def findMaxConsecutiveOnes(self, nums: List[int]) -> int: + left, right = 0, 0 + ans = 0 + zero_count = 0 + + while right < len(nums): + if nums[right] == 0: + zero_count += 1 + while zero_count > 1: + if nums[left] == 0: + zero_count -= 1 + left += 1 + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0400-0499/max-consecutive-ones.md b/docs/solutions/0400-0499/max-consecutive-ones.md new file mode 100644 index 00000000..f93f9d34 --- /dev/null +++ b/docs/solutions/0400-0499/max-consecutive-ones.md @@ -0,0 +1,66 @@ +# [0485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [0485. 最大连续 1 的个数 - 力扣](https://leetcode.cn/problems/max-consecutive-ones/) + +## 题目大意 + +**描述**:给定一个二进制数组 $nums$, 数组中只包含 $0$ 和 $1$。 + +**要求**:计算其中最大连续 $1$ 的个数。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $nums[i]$ 不是 $0$ 就是 $1$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,0,1,1,1] +输出:3 +解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3. +``` + +- 示例 2: + +```python +输入:nums = [1,0,1,1,0,1] +输出:2 +``` + +## 解题思路 + +### 思路 1:一次遍历 + +1. 使用两个变量 $cnt$ 和 $ans$。$cnt$ 用于存储当前连续 $1$ 的个数,$ans$ 用于存储最大连续 $1$ 的个数。 +2. 然后进行一次遍历,统计当前连续 $1$ 的个数,并更新最大的连续 $1$ 个数。 +3. 最后返回 $ans$ 作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def findMaxConsecutiveOnes(self, nums: List[int]) -> int: + ans = 0 + cnt = 0 + for num in nums: + if num == 1: + cnt += 1 + ans = max(ans, cnt) + else: + cnt = 0 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md b/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md new file mode 100644 index 00000000..5063c584 --- /dev/null +++ b/docs/solutions/0400-0499/maximum-xor-of-two-numbers-in-an-array.md @@ -0,0 +1,80 @@ +# [0421. 数组中两个数的最大异或值](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) + +- 标签:位运算、字典树、数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [0421. 数组中两个数的最大异或值 - 力扣](https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/) + +## 题目大意 + +给定一个整数数组 `nums`。 + +要求:返回 `num[i] XOR nums[j]` 的最大运算结果。其中 `0 ≤ i ≤ j < n`。 + +## 解题思路 + +最直接的想法暴力求解。两层循环计算两两之间的异或结果,记录并更新最大异或结果。 + +更好的做法可以减少一重循环。首先,要取得异或结果的最大值,那么从二进制的高位到低位,尽可能的让每一位异或结果都为 `1`。 + +将数组中所有数字的二进制形式从高位到低位依次存入字典树中。然后是利用异或运算交换律:如果 `a ^ b = max` 成立,那么 `a ^ max = b` 与 `b ^ max = a` 均成立。这样当我们知道 `a` 和 `max` 时,可以通过交换律求出 `b`。`a` 是我们遍历的每一个数,`max` 是我们想要尝试的最大值,从 `111111...` 开始,从高位到低位依次填 `1`。 + +对于 `a` 和 `max`,如果我们所求的 `b` 也在字典树中,则表示 `max` 是可以通过 `a` 和 `b` 得到的,那么 `max` 就是所求最大的异或。如果 `b` 不在字典树中,则减小 `max` 值继续判断,或者继续查询下一个 `a`。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, num: int, max_bit: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for i in range(max_bit, -1, -1): + bit = num >> i & 1 + if bit not in cur.children: + cur.children[bit] = Trie() + cur = cur.children[bit] + cur.isEnd = True + + def search(self, num: int, max_bit: int) -> int: + """ + Returns if the word is in the trie. + """ + cur = self + res = 0 + for i in range(max_bit, -1, -1): + bit = num >> i & 1 + if 1 - bit not in cur.children: + res = res * 2 + cur = cur.children[bit] + else: + res = res * 2 + 1 + cur = cur.children[1 - bit] + return res + +class Solution: + def findMaximumXOR(self, nums: List[int]) -> int: + trie_tree = Trie() + max_bit = len(format(max(nums), 'b')) - 1 + ans = 0 + for num in nums: + trie_tree.insert(num, max_bit) + ans = max(ans, trie_tree.search(num, max_bit)) + + return ans +``` + + + diff --git a/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md b/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md new file mode 100644 index 00000000..0bc549da --- /dev/null +++ b/docs/solutions/0400-0499/minimum-number-of-arrows-to-burst-balloons.md @@ -0,0 +1,124 @@ +# [0452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) + +- 标签:贪心、数组、排序 +- 难度:中等 + +## 题目链接 + +- [0452. 用最少数量的箭引爆气球 - 力扣](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) + +## 题目大意 + +**描述**:在一个坐标系中有许多球形的气球。对于每个气球,给定气球在 x 轴上的开始坐标和结束坐标 $(x_{start}, x_{end})$。 + +同时,在 $x$ 轴的任意位置都能垂直发出弓箭,假设弓箭发出的坐标就是 x。那么如果有气球满足 $x_{start} \le x \le x_{end}$,则该气球就会被引爆,且弓箭可以无限前进,可以将满足上述要求的气球全部引爆。 + +现在给定一个数组 `points`,其中 $points[i] = [x_{start}, x_{end}]$ 代表每个气球的开始坐标和结束坐标。 + +**要求**:返回能引爆所有气球的最小弓箭数。 + +**说明**: + +- $1 \le points.length \le 10^5$。 +- $points[i].length == 2$。 +- $-2^{31} \le x_{start} < x_{end} \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:points = [[10,16],[2,8],[1,6],[7,12]] +输出:2 +解释:气球可以用 2 支箭来爆破: +- 在x = 6 处射出箭,击破气球 [2,8] 和 [1,6]。 +- 在x = 11 处发射箭,击破气球 [10,16] 和 [7,12]。 +``` + +- 示例 2: + +```python +输入:points = [[1,2],[3,4],[5,6],[7,8]] +输出:4 +解释:每个气球需要射出一支箭,总共需要 4 支箭。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +弓箭的起始位置和结束位置可以看做是一段区间,直观上来看,为了使用最少的弓箭数,可以尽量射中区间重叠最多的地方。 + +所以问题变为了:**如何寻找区间重叠最多的地方,也就是区间交集最多的地方。** + +我们将 `points` 按结束坐标升序排序(为什么按照结束坐标排序后边说)。 + +然后维护两个变量:一个是当前弓箭的坐标 `arrow_pos`、另一个是弓箭的数目 `count`。 + +为了尽可能的穿过更多的区间,所以每一支弓箭都应该尽可能的从区间的结束位置穿过,这样才能覆盖更多的区间。 + +初始情况下,第一支弓箭的坐标为第一个区间的结束位置,然后弓箭数为 $1$。然后依次遍历每段区间。 + +如果遇到弓箭坐标小于区间起始位置的情况,说明该弓箭不能引爆该区间对应的气球,需要用新的弓箭来射,所以弓箭数加 $1$,弓箭坐标也需要更新为新区间的结束位置。 + +最终返回弓箭数目。 + +再来看为什么将 `points` 按结束坐标升序排序而不是按照开始坐标升序排序? + +其实也可以,但是按开始坐标排序不如按结束坐标排序简单。 + +按开始坐标升序排序需要考虑一种情况:有交集关系的区间中,有的区间结束位置比较早。比如 `[0, 6]、[1, 2] [4, 5]`,按照开始坐标升序排序的话,就像下图一样: + +``` +[0..................6] + [1..2] + [4..5] +``` + +第一箭的位置需要进行迭代判断,取区间 `[0, 6]、[1, 2]` 中结束位置最小的位置,即 `arrow_pos = min(points[i][1], arrow_pos)`,然后再判断接下来的区间是否能够引爆。 + +而按照结束坐标排序的话,箭的位置一开始就确定了,不需要再改变和判断箭的位置,直接判断区间即可。 + +### 思路 1:代码 + +1. 按照结束位置升序排序 + +```python +class Solution: + def findMinArrowShots(self, points: List[List[int]]) -> int: + if not points: + return 0 + points.sort(key=lambda x: x[1]) + arrow_pos = points[0][1] + count = 1 + for i in range(1, len(points)): + if arrow_pos < points[i][0]: + count += 1 + arrow_pos = points[i][1] + return count +``` + +2. 按照开始位置升序排序 + +```python +class Solution: + def findMinArrowShots(self, points: List[List[int]]) -> int: + if not points: + return 0 + points.sort(key=lambda x: x[0]) + arrow_pos = points[0][1] + count = 1 + for i in range(1, len(points)): + if arrow_pos < points[i][0]: + count += 1 + arrow_pos = points[i][1] + else: + arrow_pos = min(points[i][1], arrow_pos) + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$, 其中 $n$ 是数组 `points` 的长度。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0400-0499/n-ary-tree-level-order-traversal.md b/docs/solutions/0400-0499/n-ary-tree-level-order-traversal.md new file mode 100644 index 00000000..b479252b --- /dev/null +++ b/docs/solutions/0400-0499/n-ary-tree-level-order-traversal.md @@ -0,0 +1,51 @@ +# [0429. N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) + +- 标签:树、广度优先搜索 +- 难度:中等 + +## 题目链接 + +- [0429. N 叉树的层序遍历 - 力扣](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) + +## 题目大意 + +给定一个 N 叉树的根节点 `root`。 + +要求:返回其节点值的层序遍历(即从左到右,逐层遍历)。 + +树的序列化输入是用层序遍历,每组子节点都由 null 值分隔。 + +## 解题思路 + +和二叉树的层序遍历类似。广度优先搜索每次取出第 `i` 层上所有元素。具体步骤如下: + +- 根节点入队。 +- 当队列不为空时,求出当前队列长度 $size$。 + - 依次从队列中取出这 $size$ 个元素,并将元素值存入当前层级列表 `level` 中。 + - 将该层所有节点的所有孩子节点入队,遍历完之后将这层节点数组加入答案数组中,然后继续迭代。 +- 当队列为空时,结束。 + +## 代码 + +```python +class Solution: + def levelOrder(self, root: 'Node') -> List[List[int]]: + ans = [] + if not root: + return ans + + queue = [root] + + while queue: + level = [] + size = len(queue) + for _ in range(size): + cur = queue.pop(0) + level.append(cur.val) + for child in cur.children: + queue.append(child) + ans.append(level) + + return ans +``` + diff --git a/docs/solutions/0400-0499/next-greater-element-i.md b/docs/solutions/0400-0499/next-greater-element-i.md new file mode 100644 index 00000000..ac089808 --- /dev/null +++ b/docs/solutions/0400-0499/next-greater-element-i.md @@ -0,0 +1,90 @@ +# [0496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) + +- 标签:栈、数组、哈希表、单调栈 +- 难度:简单 + +## 题目链接 + +- [0496. 下一个更大元素 I - 力扣](https://leetcode.cn/problems/next-greater-element-i/) + +## 题目大意 + +**描述**:给定两个没有重复元素的数组 `nums1` 和 `nums2` ,其中 `nums1` 是 `nums2` 的子集。 + +**要求**:找出 `nums1` 中每个元素在 `nums2` 中的下一个比其大的值。 + +**说明**: + +- `nums1` 中数字 `x` 的下一个更大元素是指: `x` 在 `nums2` 中对应位置的右边的第一个比 `x` 大的元素。如果不存在,对应位置输出 `-1`。 +- $1 \le nums1.length \le nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 10^4$。 +- $nums1$ 和 $nums2$ 中所有整数互不相同。 +- $nums1$ 中的所有整数同样出现在 $nums2$ 中。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. +输出:[-1,3,-1] +解释:nums1 中每个值的下一个更大元素如下所述: +- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 +- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。 +- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 +``` + +- 示例 2: + +```python +输入:nums1 = [2,4], nums2 = [1,2,3,4]. +输出:[3,-1] +解释:nums1 中每个值的下一个更大元素如下所述: +- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。 +- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。 +``` + +## 解题思路 + +最直接的思路是根据题意直接暴力求解。遍历 `nums1` 中的每一个元素。对于 `nums1` 的每一个元素 `nums1[i]`,再遍历一遍 `nums2`,查找 `nums2` 中对应位置右边第一个比 `nums1[i]` 大的元素。这种解法的时间复杂度是 $O(n^2)$。 + +另一种思路是单调栈。 + +### 思路 1:单调栈 + +因为 `nums1` 是 `nums2` 的子集,所以我们可以先遍历一遍 `nums2`,并构造单调递增栈,求出 `nums2` 中每个元素右侧下一个更大的元素。然后将其存储到哈希表中。然后再遍历一遍 `nums1`,从哈希表中取出对应结果,存放到答案数组中。这种解法的时间复杂度是 $O(n)$。具体做法如下: + +1. 使用数组 `res` 存放答案。使用 `stack` 表示单调递增栈。使用哈希表 `num_map` 用于存储 `nums2` 中下一个比当前元素大的数值,映射关系为 `当前元素值:下一个比当前元素大的数值`。 + +2. 遍历数组 `nums2`,对于当前元素: + 1. 如果当前元素值较小,则直接让当前元素值入栈。 + 2. 如果当前元素值较大,则一直出栈,直到当前元素值小于栈顶元素。 + 1. 出栈时,第一个大于栈顶元素值的元素,就是当前元素。则将其映射到 `num_map` 中。 +3. 遍历完数组 `nums2`,建立好所有元素下一个更大元素的映射关系之后,再遍历数组 `nums1`。 +4. 从 `num_map` 中取出对应的值,将其加入到答案数组中。 +5. 最终输出答案数组 `res`。 + +### 思路 1:代码 + +```python +class Solution: + def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: + res = [] + stack = [] + num_map = dict() + for num in nums2: + while stack and num > stack[-1]: + num_map[stack[-1]] = num + stack.pop() + stack.append(num) + + for num in nums1: + res.append(num_map.get(num, -1)) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0400-0499/non-decreasing-subsequences.md b/docs/solutions/0400-0499/non-decreasing-subsequences.md new file mode 100644 index 00000000..0943125c --- /dev/null +++ b/docs/solutions/0400-0499/non-decreasing-subsequences.md @@ -0,0 +1,56 @@ +# [0491. 非递减子序列](https://leetcode.cn/problems/non-decreasing-subsequences/) + +- 标签:位运算、数组、哈希表、回溯 +- 难度:中等 + +## 题目链接 + +- [0491. 非递减子序列 - 力扣](https://leetcode.cn/problems/non-decreasing-subsequences/) + +## 题目大意 + +给定一个整数数组 `nums`,找出并返回该数组的所有递增子序列,递增子序列的长度至少为 2。 + +## 解题思路 + +可以利用回溯算法求解。 + +建立两个数组 res、path。res 用于存放所有递增子序列,path 用于存放当前的递增子序列。 + +定义回溯方法,从 `start_index = 0` 的位置开始遍历。 + +- 如果当前子序列的长度大于等于 2,则将当前递增子序列添加到 res 数组中(注意:不用返回,因为还要继续向下查找) +- 对数组 `[start_index, len(nums) - 1]` 范围内的元素进行取值,判断当前元素是否在本层出现过。如果出现过则跳出循环。 + - 将 `nums[i]` 标记为使用过。 + - 将 `nums[i]` 加入到当前 path 中。 + - 继续从 `i + 1` 开发遍历下一节点。 + - 进行回退操作。 +- 最终返回 res 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, nums: List[int], start_index): + if len(self.path) > 1: + self.res.append(self.path[:]) + + num_set = set() + for i in range(start_index, len(nums)): + if self.path and nums[i] < self.path[-1] or nums[i] in num_set: + continue + + num_set.add(nums[i]) + self.path.append(nums[i]) + self.backtrack(nums, i + 1) + self.path.pop() + + def findSubsequences(self, nums: List[int]) -> List[List[int]]: + self.res.clear() + self.path.clear() + self.backtrack(nums, 0) + return self.res +``` + diff --git a/docs/solutions/0400-0499/non-overlapping-intervals.md b/docs/solutions/0400-0499/non-overlapping-intervals.md new file mode 100644 index 00000000..5b911c02 --- /dev/null +++ b/docs/solutions/0400-0499/non-overlapping-intervals.md @@ -0,0 +1,82 @@ +# [0435. 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/) + +- 标签:贪心、数组、动态规划、排序 +- 难度:中等 + +## 题目链接 + +- [0435. 无重叠区间 - 力扣](https://leetcode.cn/problems/non-overlapping-intervals/) + +## 题目大意 + +**描述**:给定一个区间的集合 `intervals`,其中 `intervals[i] = [starti, endi]`。从集合中移除部分区间,使得剩下的区间互不重叠。 + +**要求**:返回需要移除区间的最小数量。 + +**说明**: + +- $1 \le intervals.length \le 10^5$。 +- $intervals[i].length == 2$。 +- $-5 * 10^4 \le starti < endi \le 5 * 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:intervals = [[1,2],[2,3],[3,4],[1,3]] +输出:1 +解释:移除 [1,3] 后,剩下的区间没有重叠。 +``` + +- 示例 2: + +```python +输入: intervals = [ [1,2], [1,2], [1,2] ] +输出: 2 +解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +这道题我们可以转换一下思路。原题要求保证移除区间最少,使得剩下的区间互不重叠。换个角度就是:「如何使得剩下互不重叠区间的数目最多」。那么答案就变为了:「总区间个数 - 不重叠区间的最多个数」。我们的问题也变成了求所有区间中不重叠区间的最多个数。 + +从贪心算法的角度来考虑,我们应该将区间按照结束时间排序。每次选择结束时间最早的区间,然后再在剩下的时间内选出最多的区间。 + +我们用贪心三部曲来解决这道题。 + +1. **转换问题**:将原问题转变为,当选择结束时间最早的区间之后,再在剩下的时间内选出最多的区间(子问题)。 +2. **贪心选择性质**:每次选择时,选择结束时间最早的区间。这样选出来的区间一定是原问题最优解的区间之一。 +3. **最优子结构性质**:在上面的贪心策略下,贪心选择当前时间最早的区间 + 剩下的时间内选出最多区间的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使所有区间中不重叠区间的个数最多。 + +使用贪心算法的代码解决步骤描述如下: + +1. 将区间集合按照结束坐标升序排列,然后维护两个变量,一个是当前不重叠区间的结束时间 `end_pos`,另一个是不重叠区间的个数 `count`。初始情况下,结束坐标 `end_pos` 为第一个区间的结束坐标,`count` 为 `1`。 +2. 依次遍历每段区间。对于每段区间:`intervals[i]`: + 1. 如果 `end_pos <= intervals[i][0]`,即 `end_pos` 小于等于区间起始位置,则说明出现了不重叠区间,令不重叠区间数 `count` 加 `1`,`end_pos` 更新为新区间的结束位置。 +3. 最终返回「总区间个数 - 不重叠区间的最多个数」即 `len(intervals) - count` 作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + if not intervals: + return 0 + intervals.sort(key=lambda x: x[1]) + end_pos = intervals[0][1] + count = 1 + for i in range(1, len(intervals)): + if end_pos <= intervals[i][0]: + count += 1 + end_pos = intervals[i][1] + + return len(intervals) - count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是区间的数量。 +- **空间复杂度**:$O(\log n)$。 diff --git a/docs/solutions/0400-0499/nth-digit.md b/docs/solutions/0400-0499/nth-digit.md new file mode 100644 index 00000000..914cb2d6 --- /dev/null +++ b/docs/solutions/0400-0499/nth-digit.md @@ -0,0 +1,138 @@ +# [0400. 第 N 位数字](https://leetcode.cn/problems/nth-digit/) + +- 标签:数学、二分查找 +- 难度:中等 + +## 题目链接 + +- [0400. 第 N 位数字 - 力扣](https://leetcode.cn/problems/nth-digit/) + +## 题目大意 + +**描述**:给你一个整数 $n$。 + +**要求**:在无限的整数序列 $[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]$ 中找出并返回第 $n$ 位上的数字。 + +**说明**: + +- $1 \le n \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 3 +输出:3 +``` + +- 示例 2: + +```python +输入:n = 11 +输出:0 +解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。 +``` + +## 解题思路 + +### 思路 1:找规律 + +数字以 $0123456789101112131415…$ 的格式序列化到一个字符序列中。在这个序列中,第 $5$ 位(从下标 $0$ 开始计数)是 $5$,第 $13$ 位是 $1$,第 $19$ 位是 $4$,等等。 + +根据题意中的字符串,找数学规律: + +- $1$ 位数字有 $9$ 个,共 $9$ 位:$123456789$。 +- $2$ 位数字有 $90$ 个,共 $2 \times 90$ 位:$10111213...9899$。 +- $3$ 位数字有 $900$ 个,共 $3 \times 900$ 位:$100...999$。 +- $4$ 位数字有 $9000$ 个,共 $4 \times 9000$ 位: $1000...9999$。 +- $……$ + +则我们可以按照以下步骤解决这道题: + +1. 我们可以先找到第 $n$ 位所在整数 $number$ 所对应的位数 $digit$。 +2. 同时找到该位数 $digit$ 的起始整数 $start$。 +3. 再计算出 $n$ 所在整数 $number$。$number$ 等于从起始数字 $start$ 开始的第 $\lfloor \frac{n - 1}{digit} \rfloor$ 个数字。即 `number = start + (n - 1) // digit`。 +4. 然后确定 $n$ 对应的是数字 $number$ 中的哪一位。即 $digit\underline{\hspace{0.5em}}idx = (n - 1) \mod digit$。 +5. 最后返回结果。 + +### 思路 1:代码 + +```python +class Solution: + def findNthDigit(self, n: int) -> int: + digit = 1 + start = 1 + base = 9 + while n > base: + n -= base + digit += 1 + start *= 10 + base = start * digit * 9 + + number = start + (n - 1) // digit + digit_idx = (n - 1) % digit + return int(str(number)[digit_idx]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:二分查找 + +假设第 $n$ 位数字所在的整数是 $digit$ 位数,我们可以定义一个方法 $totalDigits(x)$ 用于计算所有位数不超过 $x$ 的整数的所有位数和。 + +根据题意我们可知,所有位数不超过 $digit - 1$ 的整数的所有位数和一定小于 $n$,并且所有不超过 $digit$ 的整数的所有位数和一定大于等于 $n$。 + +因为所有位数不超过 $x$ 的整数的所有位数和 $totalDigits(x)$ 是关于 $x$ 单调递增的,所以我们可以使用二分查找的方式,确定第 $n$ 位数字所在的整数的位数 $digit$。 + +$n$ 的最大值为 $2^{31} - 1$,约为 $2 \times 10^9$。而 $9$ 位数字有 $9 \times 10^8$ 个,共 $9 \times 9 \times 10^8 = 8.1 \times 10^9 > 2 \times 10 ^ 9$,所以第 $n$ 位所在整数的位数 $digit$ 最多为 $9$ 位,最小为 $1$ 位。即 $digit$ 的取值范围为 $[1, 9]$。 + +我们使用二分查找算法得到 $digit$ 之后,还可以计算出不超过 $digit - 1$ 的整数的所有位数和 $pre\underline{\hspace{0.5em}}digits = totalDigits(digit - 1)$,则第 $n$ 位数字所在整数在所有 $digit$ 位数中的下标是 $idx = n - pre\underline{\hspace{0.5em}}digits - 1$。 + +得到下标 $idx$ 后,可以计算出 $n$ 所在整数 $number$。$number$ 等于从起始数字 $10^{digit - 1}$ 开始的第 $\lfloor \frac{idx}{digit} \rfloor$ 个数字。即 `number = 10 ** (digit - 1) + idx // digit`。 + +该整数 $number$ 中第 $idx \mod digit$ 即为第 $n$ 位上的数字,将其作为答案返回即可。 + +### 思路 2:代码 + +```python +class Solution: + def totalDigits(self, x): + digits = 0 + digit, cnt = 1, 9 + while digit <= x: + digits += digit * cnt + digit += 1 + cnt *= 10 + return digits + + def findNthDigit(self, n: int) -> int: + left, right = 1, 9 + while left < right: + mid = left + (right - left) // 2 + if self.totalDigits(mid) < n: + left = mid + 1 + else: + right = mid + + digit = left + pre_digits = self.totalDigits(digit - 1) + idx = n - pre_digits - 1 + number = 10 ** (digit - 1) + idx // digit + digit_idx = idx % digit + + return int(str(number)[digit_idx]) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$\log n \times \log \log n$,位数上限 $D$ 为 $\log n$,二分查找的时间复杂度为 $\log D$,每次执行的时间复杂度为 $D$,总的时间复杂度为 $D \times \log D = O(\log n \times \log \log n)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- 【题解】[400. 第 N 位数字 - 清晰易懂的找规律解法(击败100%, 几乎双百)](https://leetcode.cn/problems/nth-digit/solutions/1129463/geekplayers-leetcode-ac-qing-xi-yi-dong-uasjy/) +- 【题解】[400. 第 N 位数字 - 方法一:二分查找](https://leetcode.cn/problems/nth-digit/solutions/1128000/di-n-wei-shu-zi-by-leetcode-solution-mdl2/) diff --git a/docs/solutions/0400-0499/number-of-boomerangs.md b/docs/solutions/0400-0499/number-of-boomerangs.md new file mode 100644 index 00000000..d5eaea85 --- /dev/null +++ b/docs/solutions/0400-0499/number-of-boomerangs.md @@ -0,0 +1,39 @@ +# [0447. 回旋镖的数量](https://leetcode.cn/problems/number-of-boomerangs/) + +- 标签:数组、哈希表、数学 +- 难度:中等 + +## 题目链接 + +- [0447. 回旋镖的数量 - 力扣](https://leetcode.cn/problems/number-of-boomerangs/) + +## 题目大意 + +给定平面上点坐标的数组 points,其中 $points[i] = [x_i, y_i]$。判断 points 中是否存在三个点 i,j,k,满足 i 和 j 之间的距离等于 i 和 k 之间的距离,即 $dist[i, j] = dist[i, k]$。找出满足上述关系的答案数量。 + +## 解题思路 + +使用哈希表记录每两个点之间的距离。然后使用两重循环遍历坐标数组,对于每两个点 i、点 j,计算两个点之间的距离,并将距离存进哈希表中。再从哈希表中选取距离相同的关系中依次选出两个,作为三个点之间的距离关系 $dist[i, j] =dist[i, k]$,因为还需考虑顺序,所以共有 $value * (value-1)$ 种情况。累加到答案中。 + +## 代码 + +```python +class Solution: + def numberOfBoomerangs(self, points: List[List[int]]) -> int: + ans = 0 + for point_i in points: + dis_dict = dict() + for point_j in points: + if point_i != point_j: + dx = point_i[0] - point_j[0] + dy = point_i[1] - point_j[1] + dis = dx * dx + dy * dy + if dis in dis_dict: + dis_dict[dis] += 1 + else: + dis_dict[dis] = 1 + for value in dis_dict.values(): + ans += value*(value-1) + return ans +``` + diff --git a/docs/solutions/0400-0499/ones-and-zeroes.md b/docs/solutions/0400-0499/ones-and-zeroes.md new file mode 100644 index 00000000..e177ff0d --- /dev/null +++ b/docs/solutions/0400-0499/ones-and-zeroes.md @@ -0,0 +1,102 @@ +# [0474. 一和零](https://leetcode.cn/problems/ones-and-zeroes/) + +- 标签:数组、字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0474. 一和零 - 力扣](https://leetcode.cn/problems/ones-and-zeroes/) + +## 题目大意 + +**描述**:给定一个二进制字符串数组 $strs$,以及两个整数 $m$ 和 $n$。 + +**要求**:找出并返回 $strs$ 的最大子集的大小,该子集中最多有 $m$ 个 $0$ 和 $n$ 个 $1$。 + +**说明**: + +- 如果 $x$ 的所有元素也是 $y$ 的元素,集合 $x$ 是集合 $y$ 的子集。 +- $1 \le strs.length \le 600$。 +- $1 \le strs[i].length \le 100$。 +- $strs[i]$ 仅由 `'0'` 和 `'1'` 组成。 +- $1 \le m, n \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 +输出:4 +解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 +其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3。 +``` + +- 示例 2: + +```python +输入:strs = ["10", "0", "1"], m = 1, n = 1 +输出:2 +解释:最大的子集是 {"0", "1"} ,所以答案是 2。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题可以转换为「二维 0-1 背包问题」来做。 + +把 $0$ 的个数和 $1$ 的个数视作一个二维背包的容量。每一个字符串都当做是一件物品,其成本为字符串中 $1$ 的数量和 $0$ 的数量,每个字符串的价值为 $1$。 + +###### 1. 划分阶段 + +按照物品的序号、当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:最多有 $i$ 个 $0$ 和 $j$ 个 $1$ 的字符串 $strs$ 的最大子集的大小。 + +###### 3. 状态转移方程 + +填满最多由 $i$ 个 $0$ 和 $j$ 个 $1$ 构成的二维背包的最多物品数为下面两种情况中的最大值: + +- 使用之前字符串填满容量为 $i - zero\underline{\hspace{0.5em}}num$、$j - one\underline{\hspace{0.5em}}num$ 的背包的物品数 + 当前字符串价值 +- 选择之前字符串填满容量为 $i$、$j$ 的物品数。 + +则状态转移方程为:$dp[i][j] = max(dp[i][j], dp[i - zero\underline{\hspace{0.5em}}num][j - one\underline{\hspace{0.5em}}num] + 1)$。 + +###### 4. 初始条件 + +- 无论有多少个 $0$,多少个 $1$,只要不选 $0$,也不选 $1$,则最大子集的大小为 $0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:最多有 $i$ 个 $0$ 和 $j$ 个 $1$ 的字符串 $strs$ 的最大子集的大小。所以最终结果为 $dp[m][n]$。 + +### 思路 1:代码 + +```python +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] + + for str in strs: + one_num = 0 + zero_num = 0 + for ch in str: + if ch == '0': + zero_num += 1 + else: + one_num += 1 + for i in range(m, zero_num - 1, -1): + for j in range(n, one_num - 1, -1): + dp[i][j] = max(dp[i][j], dp[i - zero_num][j - one_num] + 1) + + return dp[m][n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(l \times m \times n)$,其中 $l$ 为字符串 $strs$ 的长度。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0400-0499/pacific-atlantic-water-flow.md b/docs/solutions/0400-0499/pacific-atlantic-water-flow.md new file mode 100644 index 00000000..5071e89f --- /dev/null +++ b/docs/solutions/0400-0499/pacific-atlantic-water-flow.md @@ -0,0 +1,91 @@ +# [0417. 太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) + +- 标签:深度优先搜索、广度优先搜索、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0417. 太平洋大西洋水流问题 - 力扣](https://leetcode.cn/problems/pacific-atlantic-water-flow/) + +## 题目大意 + +**描述**:给定一个 `m * n` 大小的二维非负整数矩阵 `heights` 来表示一片大陆上各个单元格的高度。`heights[i][j]` 表示第 `i` 行第 `j` 列所代表的陆地高度。这个二维矩阵所代表的陆地被太平洋和大西洋所包围着。左上角是「太平洋」,右下角是「大西洋」。规定水流只能按照上、下、左、右四个方向流动,且只能从高处流到低处,或者在同等高度上流动。 + +**要求**:找出代表陆地的二维矩阵中,水流既可以从该处流动到太平洋,又可以流动到大西洋的所有坐标。以二维数组 `res` 的形式返回,其中 `res[i] = [ri, ci]` 表示雨水从单元格 `(ri, ci)` 既可流向太平洋也可流向大西洋。 + +**说明**: + +- $m == heights.length$。 +- $n == heights[r].length$。 +- $1 \le m, n \le 200$。 +- $0 \le heights[r][c] \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/06/08/waterflow-grid.jpg) + +```python +输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] +输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]] +``` + +- 示例 2: + +```python +输入: heights = [[2,1],[1,2]] +输出: [[0,0],[0,1],[1,0],[1,1]] +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +雨水由高处流向低处,如果我们根据雨水的流向搜索,来判断是否能从某一位置流向太平洋和大西洋不太容易。我们可以换个思路。 + +1. 分别从太平洋和大西洋(就是矩形边缘)出发,逆流而上,找出水流逆流能达到的地方,可以用两个二维数组 `pacific`、`atlantic` 分别记录太平洋和大西洋能到达的位置。 +2. 然后再对二维数组进行一次遍历,找出两者交集的位置,就是雨水既可流向太平洋也可流向大西洋的位置,将其加入答案数组 `res` 中。 +3. 最后返回答案数组 `res`。 + +### 思路 1:代码 + +```python +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + rows, cols = len(heights), len(heights[0]) + pacific = [[False for _ in range(cols)] for _ in range(rows)] + atlantic = [[False for _ in range(cols)] for _ in range(rows)] + + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(i, j, visited): + visited[i][j] = True + for direct in directs: + new_i = i + direct[0] + new_j = j + direct[1] + if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: + continue + if heights[new_i][new_j] >= heights[i][j] and not visited[new_i][new_j]: + dfs(new_i, new_j, visited) + + for j in range(cols): + dfs(0, j, pacific) + dfs(rows - 1, j, atlantic) + + for i in range(rows): + dfs(i, 0, pacific) + dfs(i, cols - 1, atlantic) + + res = [] + for i in range(rows): + for j in range(cols): + if pacific[i][j] and atlantic[i][j]: + res.append([i, j]) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git a/docs/solutions/0400-0499/partition-equal-subset-sum.md b/docs/solutions/0400-0499/partition-equal-subset-sum.md new file mode 100644 index 00000000..93a170e4 --- /dev/null +++ b/docs/solutions/0400-0499/partition-equal-subset-sum.md @@ -0,0 +1,107 @@ +# [0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0416. 分割等和子集 - 力扣](https://leetcode.cn/problems/partition-equal-subset-sum/) + +## 题目大意 + +**描述**:给定一个只包含正整数的非空数组 $nums$。 + +**要求**:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 + +**说明**: + +- $1 \le nums.length \le 200$。 +- $1 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,5,11,5] +输出:true +解释:数组可以分割成 [1, 5, 5] 和 [11]。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3,5] +输出:false +解释:数组不能分割成两个元素和相等的子集。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题换一种说法就是:从数组中选择一些元素组成一个子集,使子集的元素和恰好等于整个数组元素和的一半。 + +这样的话,这道题就可以转变为「0-1 背包问题」。 + +1. 把整个数组中的元素和记为 $sum$,把元素和的一半 $target = \frac{sum}{2}$ 看做是「0-1 背包问题」中的背包容量。 +2. 把数组中的元素 $nums[i]$ 看做是「0-1 背包问题」中的物品。 +3. 第 $i$ 件物品的重量为 $nums[i]$,价值也为 $nums[i]$。 +4. 因为物品的重量和价值相等,如果能装满载重上限为 $target$ 的背包,那么得到的最大价值也应该是 $target$。 + +这样问题就转变为:给定一个数组 $nums$ 代表物品,数组元素和的一半 $target = \frac{sum}{2}$ 代表背包的载重上限。其中第 $i$ 件物品的重量为 $nums[i]$,价值为 $nums[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包装载重量上限的情况下,能否将背包装满从而得到最大价值? + +###### 1. 划分阶段 + +当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $w$ 的背包中,得到的元素和最大为多少。 + +###### 3. 状态转移方程 + +$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], \quad dp[w - nums[i - 1]] + nums[i - 1] \rbrace & w \ge nums[i - 1] \end{cases}$ + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[target]$ 表示为:从数组 $nums$ 中选择一些元素,放入最多能装元素和为 $target = \frac{sum}{2}$ 的背包中,得到的元素和最大值。 + +所以最后判断一下 $dp[target]$ 是否等于 $target$。如果 $dp[target] == target$,则说明集合中的子集刚好能够凑成总和 $target$,此时返回 `True`;否则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + # 思路 2:动态规划 + 滚动数组优化 + def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): + size = len(weight) + dp = [0 for _ in range(W + 1)] + + # 枚举前 i 种物品 + for i in range(1, size + 1): + # 逆序枚举背包装载重量(避免状态值错误) + for w in range(W, weight[i - 1] - 1, -1): + # dp[w] 取「前 i - 1 件物品装入载重为 w 的背包中的最大价值」与「前 i - 1 件物品装入载重为 w - weight[i - 1] 的背包中,再装入第 i - 1 物品所得的最大价值」两者中的最大值 + dp[w] = max(dp[w], dp[w - weight[i - 1]] + value[i - 1]) + + return dp[W] + + def canPartition(self, nums: List[int]) -> bool: + sum_nums = sum(nums) + if sum_nums & 1: + return False + + target = sum_nums // 2 + return self.zeroOnePackMethod2(nums, nums, target) == target +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 是整个数组元素和的一半。 +- **空间复杂度**:$O(target)$。 + diff --git a/docs/solutions/0400-0499/path-sum-iii.md b/docs/solutions/0400-0499/path-sum-iii.md new file mode 100644 index 00000000..141efdcb --- /dev/null +++ b/docs/solutions/0400-0499/path-sum-iii.md @@ -0,0 +1,67 @@ +# [0437. 路径总和 III](https://leetcode.cn/problems/path-sum-iii/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0437. 路径总和 III - 力扣](https://leetcode.cn/problems/path-sum-iii/) + +## 题目大意 + +给定一个二叉树的根节点 `root`,和一个整数 `sum`。 + +要求:求出该二叉树里节点值之和等于 `sum` 的路径的数目。 + +- 路径:不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 + +## 解题思路 + +直观想法是: + +以每一个节点 `node` 为起始节点,向下检测延伸的路径。递归遍历每一个节点所有可能的路径,然后将这些路径数目加起来即为答案。 + +但是这样会存在许多重复计算。我们可以定义节点的前缀和来减少重复计算。 + +- 节点的前缀和:从根节点到当前节点路径上所有节点的和。 + +有了节点的前缀和,我们就可以通过前缀和来计算两节点之间的路劲和。即:`则两节点之间的路径和 = 两节点之间的前缀和之差`。 + +为了计算符合要求的路径数量,我们用哈希表存储「前缀和的节点数量」。哈希表以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值。这样就能通过哈希表直接计算出符合要求的路径数量,从而累加到答案上。 + +整个算法的具体步骤如下: + +- 通过先序遍历方式递归遍历二叉树,计算每一个节点的前缀和 `cur_sum`。 +- 从哈希表中取出 `cur_sum - sum` 的路径数量(也就是表示存在从前缀和为 `cur_sum - sum` 所对应的节点到前缀和为 `cur_sum` 所对应的节点的路径个数)累加到答案 `res` 中。 +- 然后以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值,存入哈希表中。 +- 递归遍历二叉树,并累加答案值。 +- 恢复哈希表「当前前缀和的节点数量」,返回答案。 + +## 代码 + +```python +class Solution: + prefixsum_count = dict() + + def dfs(self, root, prefixsum_count, target_sum, cur_sum): + if not root: + return 0 + res = 0 + cur_sum += root.val + res += prefixsum_count.get(cur_sum - target_sum, 0) + prefixsum_count[cur_sum] = prefixsum_count.get(cur_sum, 0) + 1 + + res += self.dfs(root.left, prefixsum_count, target_sum, cur_sum) + res += self.dfs(root.right, prefixsum_count, target_sum, cur_sum) + + prefixsum_count[cur_sum] -= 1 + return res + + def pathSum(self, root: TreeNode, sum: int) -> int: + if not root: + return 0 + prefixsum_count = dict() + prefixsum_count[0] = 1 + return self.dfs(root, prefixsum_count, sum, 0) +``` + diff --git a/docs/solutions/0400-0499/predict-the-winner.md b/docs/solutions/0400-0499/predict-the-winner.md new file mode 100644 index 00000000..a01377b4 --- /dev/null +++ b/docs/solutions/0400-0499/predict-the-winner.md @@ -0,0 +1,99 @@ +# [0486. 预测赢家](https://leetcode.cn/problems/predict-the-winner/) + +- 标签:递归、数组、数学、动态规划、博弈 +- 难度:中等 + +## 题目链接 + +- [0486. 预测赢家 - 力扣](https://leetcode.cn/problems/predict-the-winner/) + +## 题目大意 + +**描述**:给定搞一个整数数组 $nums$。玩家 $1$ 和玩家 $2$ 基于这个数组设计了一个游戏。 + +玩家 $1$ 和玩家 $2$ 轮流进行自己的回合,玩家 $1$ 先手。 + +开始时,两个玩家的初始分值都是 $0$。每一回合,玩家从数组的任意一端取一个数字(即 $nums[0]$ 或 $nums[nums.length - 1]$),取到的数字将会从数组中移除(数组长度减 $1$)。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。 + +**要求**:如果玩家 $1$ 能成为赢家,则返回 `True`。否则返回 `False`。如果两个玩家得分相等,同样认为玩家 $1$ 是游戏的赢家,也返回 `True`。假设每个玩家的玩法都会使他的分数最大化。 + +**说明**: + +- $1 \le nums.length \le 20$。 +- $0 \le nums[i] \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,5,2] +输出:False +解释:一开始,玩家 1 可以从 1 和 2 中进行选择。 +如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。 +所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。 +因此,玩家 1 永远不会成为赢家,返回 False。 +``` + +- 示例 2: + +```python +输入:nums = [1,5,233,7] +输出:True +解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 5 和 7 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。 +最终,玩家 1(234 分)比玩家 2(12 分)获得更多的分数,所以返回 True,表示玩家 1 可以成为赢家。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:玩家 $1$ 与玩家 $2$ 在 $nums[i]...nums[j]$ 之间互相选取,玩家 $1$ 比玩家 $2$ 多的最大分数。 + +###### 3. 状态转移方程 + +根据状态的定义,只有在 $i \le j$ 时才有意义,所以当 $i > j$ 时,$dp[i][j] = 0$。 + +1. 当 $i == j$ 时,当前玩家只能拿取 $nums[i]$,因此对于所有 $0 \le i < nums.length$,都有:$dp[i][i] = nums[i]$。 +2. 当 $i < j$ 时,当前玩家可以选择 $nums[i]$ 或 $nums[j]$,并是自己的分数最大化,然后换另一位玩家从剩下部分选取数字。则转移方程为:$dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])$。 + +###### 4. 初始条件 + +- 当 $i > j$ 时,$dp[i][j] = 0$。 +- 当 $i == j$ 时,$dp[i][j] = nums[i]$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:玩家 $1$ 与玩家 $2$ 在 $nums[i]...nums[j]$ 之间互相选取,玩家 $1$ 比玩家 $2$ 多的最大分数。则如果玩家 $1$ 想要赢,则 $dp[0][size - 1]$ 必须大于等于 $0$。所以最终结果为 $dp[0][size - 1] >= 0$。 + +### 思路 1:代码 + +```python +class Solution: + def PredictTheWinner(self, nums: List[int]) -> bool: + size = len(nums) + dp = [[0 for _ in range(size)] for _ in range(size)] + + for l in range(1, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + if l == 1: + dp[i][j] = nums[i] + else: + dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]) + return dp[0][size - 1] >= 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0400-0499/queue-reconstruction-by-height.md b/docs/solutions/0400-0499/queue-reconstruction-by-height.md new file mode 100644 index 00000000..017f6249 --- /dev/null +++ b/docs/solutions/0400-0499/queue-reconstruction-by-height.md @@ -0,0 +1,35 @@ +# [0406. 根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) + +- 标签:贪心、树状数组、线段树、数组、排序 +- 难度:中等 + +## 题目链接 + +- [0406. 根据身高重建队列 - 力扣](https://leetcode.cn/problems/queue-reconstruction-by-height/) + +## 题目大意 + +n 个人打乱顺序排成一排,给定一个数组 people 表示队列中人的属性(顺序是打乱的)。其中 $people[i] = [h_i, k_i]$ 表示第 i 个人的身高为 $h_i$,前面正好有 $k_i$ 个身高大于或等于 $h_i$ 的人。 + +现在重新构造并返回输入数组 people 所表示的队列 queue。其中 $queue[j] = [h_j, k_j]$ 是队列中第 j 个人的信息,表示为身高为 $h_j$,前面正好有 $k_j$ 个身高大于或等于 $h_j$​ 的人。 + +## 解题思路 + +这道题目有两个维度,身高 $h_j$ 和满足条件的数量 $k_j$。进行排序的时候如果同时考虑两个维度条件,就有点复杂了。我们可以考虑固定一个维度,先排好序,再考虑另一个维度的要求。 + +我们可以先确定身高维度。将数组按身高从高到低进行排序,身高相同的则按照 k 值升序排列。这样排序之后可以确定目前对于第 j 个人来说,前面的 j - 1 个人肯定比他都高。 + +然后建立一个包含 n 个位置的空队列 queue,按照上边排好的顺序遍历,依次将其插入到第 $k_j$​ 位置上。最后返回新的队列。 + +## 代码 + +```python +class Solution: + def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]: + queue = [] + people.sort(key = lambda x: (-x[0], x[1])) + for p in people: + queue.insert(p[1], p) + return queue +``` + diff --git a/docs/solutions/0400-0499/repeated-substring-pattern.md b/docs/solutions/0400-0499/repeated-substring-pattern.md new file mode 100644 index 00000000..16a72227 --- /dev/null +++ b/docs/solutions/0400-0499/repeated-substring-pattern.md @@ -0,0 +1,84 @@ +# [0459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/) + +- 标签:字符串、字符串匹配 +- 难度:简单 + +## 题目链接 + +- [0459. 重复的子字符串 - 力扣](https://leetcode.cn/problems/repeated-substring-pattern/) + +## 题目大意 + +**描述**:给定一个非空的字符串 `s`。 + +**要求**:检查该字符串 `s` 是否可以通过由它的一个子串重复多次构成。 + +**说明**: + +- $1 \le s.length \le 10^4$。 +- `s` 由小写英文字母组成 + +**示例**: + +- 示例 1: + +```python +输入: s = "abab" +输出: true +解释: 可由子串 "ab" 重复两次构成。 +``` + +- 示例 2: + +```python +输入: s = "aba" +输出: false +``` + +## 解题思路 + +### 思路 1:KMP 算法 + +这道题我们可以使用 KMP 算法的 `next` 数组来解决。我们知道 `next[j]` 表示的含义是:**记录下标 `j` 之前(包括 `j`)的模式串 `p` 中,最长相等前后缀的长度。** + +而如果整个模式串 `p` 的最长相等前后缀长度不为 `0`,即 `next[len(p) - 1] != 0` ,则说明整个模式串 `p` 中有最长相同的前后缀,假设 `next[len(p) - 1] == k`,则说明 `p[0: k] == p[m - k: m]`。比如字符串 `"abcabcabc"`,最长相同前后缀为 `"abcabc" = "abcabc"`。 + +- 如果最长相等的前后缀是重叠的,比如之前的例子 `"abcabcabc"`。 + - 如果我们去除字符串中相同的前后缀的重叠部分,剩下两头前后缀部分(这两部分是相同的)。然后再去除剩余的后缀部分,只保留剩余的前缀部分。比如字符串 `"abcabcabc"` 去除重叠部分和剩余的后缀部分之后就是 `"abc"`。实际上这个部分就是字符串去除整个后缀部分的剩余部分。 + - 如果整个字符串可以通过子串重复构成的话,那么这部分就是最小周期的子串。 + - 我们只需要判断整个子串的长度是否是剩余部分长度的整数倍即可。也就是判断 `len(p) % (len(p) - next[size - 1]) == 0` 是否成立,如果成立,则字符串 `s` 可由 `s[0: len(p) - next[size - 1]]` 构成的子串重复构成,返回 `True`。否则返回 `False`。 +- 如果最长相等的前后缀是不重叠的,那我们可将重叠部分视为长度为 `0` 的空串,则剩余的部分其实就是去除后缀部分的剩余部分,上述结论依旧成立。  + +### 思路 1:代码 + +```python +class Solution: + def generateNext(self, p: str): + m = len(p) + next = [0 for _ in range(m)] + + left = 0 + for right in range(1, m): + while left > 0 and p[left] != p[right]: + left = next[left - 1] + if p[left] == p[right]: + left += 1 + next[right] = left + + return next + + def repeatedSubstringPattern(self, s: str) -> bool: + size = len(s) + if size == 0: + return False + next = self.generateNext(s) + if next[size - 1] != 0 and size % (size - next[size - 1]) == 0: + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m)$,其中模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + diff --git a/docs/solutions/0400-0499/serialize-and-deserialize-n-ary-tree.md b/docs/solutions/0400-0499/serialize-and-deserialize-n-ary-tree.md new file mode 100644 index 00000000..bb22265a --- /dev/null +++ b/docs/solutions/0400-0499/serialize-and-deserialize-n-ary-tree.md @@ -0,0 +1,64 @@ +# [0428. 序列化和反序列化 N 叉树](https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、字符串 +- 难度:困难 + +## 题目链接 + +- [0428. 序列化和反序列化 N 叉树 - 力扣](https://leetcode.cn/problems/serialize-and-deserialize-n-ary-tree/) + +## 题目大意 + +要求:设计一个序列化和反序列化 N 叉树的算法。序列化 / 反序列化算法的算法实现没有限制。你只需要保证 N 叉树可以被序列化为一个字符串并且该字符串可以被反序列化成原树结构即可。 + +- 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构。 +- N 叉树是指每个节点都有不超过 N 个孩子节点的有根树。 + +## 解题思路 + +- 序列化:通过深度优先搜索的方式,递归遍历节点,以 `root.val`、`len(root.children)`、`root.children` 的顺序生成序列化结果,并用 `-` 链接,返回结果字符串。 +- 反序列化:先将字符串按 `-` 分割成数组。然后按照 `root.val`、`len(root.children)`、`root.children` 的顺序解码,并建立对应节点。最后返回根节点。 + + + +## 代码 + +```python +class Codec: + def serialize(self, root: 'Node') -> str: + """Encodes a tree to a single string. + + :type root: Node + :rtype: str + """ + if not root: + return 'None' + + data = str(root.val) + '-' + str(len(root.children)) + for child in root.children: + data += '-' + self.serialize(child) + return data + + + def deserialize(self, data: str) -> 'Node': + """Decodes your encoded data to tree. + + :type data: str + :rtype: Node + """ + datalist = data.split('-') + return self.dfs(datalist) + + def dfs(self, datalist): + val = datalist.pop(0) + if val == 'None': + return None + root = Node(int(val)) + root.children = [] + + size = int(datalist.pop(0)) + for _ in range(size): + root.children.append(self.dfs(datalist)) + return root +``` + diff --git a/docs/solutions/0400-0499/sliding-window-median.md b/docs/solutions/0400-0499/sliding-window-median.md new file mode 100644 index 00000000..245048f6 --- /dev/null +++ b/docs/solutions/0400-0499/sliding-window-median.md @@ -0,0 +1,143 @@ +# [0480. 滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) + +- 标签:数组、哈希表、滑动窗口、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0480. 滑动窗口中位数 - 力扣](https://leetcode.cn/problems/sliding-window-median/) + +## 题目大意 + +**描述**:给定一个数组 $nums$,有一个长度为 $k$ 的窗口从最左端滑动到最右端。窗口中有 $k$ 个数,每次窗口向右移动 $1$ 位。 + +**要求**:找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。 + +**说明**: + +- **中位数**:有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。 +- 例如: + - $[2,3,4]$,中位数是 $3$ + - $[2,3]$,中位数是 $(2 + 3) / 2 = 2.5$。 +- 你可以假设 $k$ 始终有效,即:$k$ 始终小于等于输入的非空数组的元素个数。 +- 与真实值误差在 $10 ^ {-5}$ 以内的答案将被视作正确答案。 + +**示例**: + +- 示例 1: + +```python +给出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3。 + +窗口位置 中位数 +--------------- ----- +[1 3 -1] -3 5 3 6 7 1 + 1 [3 -1 -3] 5 3 6 7 -1 + 1 3 [-1 -3 5] 3 6 7 -1 + 1 3 -1 [-3 5 3] 6 7 3 + 1 3 -1 -3 [5 3 6] 7 5 + 1 3 -1 -3 5 [3 6 7] 6 + 因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。 +``` + +## 解题思路 + +### 思路 1:小顶堆 + 大顶堆 + +题目要求动态维护长度为 $k$ 的窗口中元素的中位数。如果对窗口元素进行排序,时间复杂度一般是 $O(k \times \log k)$。如果对每个区间都进行排序,那时间复杂度就更大了,肯定会超时。 + +我们需要借助一个内部有序的数据结构,来降低取窗口中位数的时间复杂度。Python 可以借助 `heapq` 构建大顶堆和小顶堆。通过 $k$ 的奇偶性和堆顶元素来获取中位数。 + +接下来还要考虑几个问题:初始化问题、取中位数问题、窗口滑动中元素的添加删除操作。接下来一一解决。 + +初始化问题: + +我们将所有大于中位数的元素放到 $heap\underline{\hspace{0.5em}}max$(小顶堆)中,并且元素个数向上取整。然后再将所有小于等于中位数的元素放到 $heap\underline{\hspace{0.5em}}min$(大顶堆)中,并且元素个数向下取整。这样当 $k$ 为奇数时,$heap\underline{\hspace{0.5em}}max$ 比 $heap\underline{\hspace{0.5em}}min$ 多一个元素,中位数就是 $heap\underline{\hspace{0.5em}}max$ 堆顶元素。当 $k$ 为偶数时,$heap\underline{\hspace{0.5em}}max$ 和 $heap\underline{\hspace{0.5em}}min$ 中的元素个数相同,中位数就是 $heap\underline{\hspace{0.5em}}min$ 堆顶元素和 $heap\underline{\hspace{0.5em}}max$ 堆顶元素的平均数。这个过程操作如下: + +- 先将数组中前 $k$ 个元素放到 $heap\underline{\hspace{0.5em}}max$ 中。 +- 再从 $heap\underline{\hspace{0.5em}}max$ 中取出 $k // 2$ 个堆顶元素放到 $heap\underline{\hspace{0.5em}}min$ 中。 + +取中位数问题(上边提到过): + +- 当 $k$ 为奇数时,中位数就是 $heap\underline{\hspace{0.5em}}max$ 堆顶元素。当 $k$ 为偶数时,中位数就是 $heap\underline{\hspace{0.5em}}max$ 堆顶元素和 $heap\underline{\hspace{0.5em}}min$ 堆顶元素的平均数。 + +窗口滑动过程中元素的添加和删除问题: + +- 删除:每次滑动将窗口左侧元素删除。由于 `heapq` 没有提供删除中间特定元素相对应的方法。所以我们使用「延迟删除」的方式先把待删除的元素标记上,等到待删除的元素出现在堆顶时,再将其移除。我们使用 $removes$ (哈希表)来记录待删除元素个数。 + - 将窗口左侧元素删除的操作为:`removes[nums[left]] += 1`。 +- 添加:每次滑动在窗口右侧添加元素。需要根据上一步删除的结果来判断需要添加到哪一个堆上。我们用 $banlance$ 记录 $heap\underline{\hspace{0.5em}}max$ 和 $heap\underline{\hspace{0.5em}}min$ 元素个数的差值。 + - 如果窗口左边界 $nums[left]$小于等于 $heap\underline{\hspace{0.5em}}max$ 堆顶元素 ,则说明上一步删除的元素在 $heap\underline{\hspace{0.5em}}min$ 上,则让 `banlance -= 1`。 + - 如果窗口左边界 $nums[left]$ 大于 $heap\underline{\hspace{0.5em}}max$ 堆顶元素,则说明上一步删除的元素在 $heap\underline{\hspace{0.5em}}max$ 上,则上 `banlance += 1`。 + - 如果窗口右边界 $nums[right]$ 小于等于 $heap\underline{\hspace{0.5em}}max$ 堆顶元素,则说明待添加元素需要添加到 $heap\underline{\hspace{0.5em}}min$ 上,则让 `banlance += 1`。 + - 如果窗口右边界 $nums[right]$ 大于 $heap\underline{\hspace{0.5em}}max$ 堆顶元素,则说明待添加元素需要添加到 $heap\underline{\hspace{0.5em}}max$ 上,则让 `banlance -= 1`。 +- 经过上述操作,$banlance$ 的取值为 $0$、$-2$、$2$ 中的一种。需要经过调整使得 $banlance == 0$。 + - 如果 $banlance == 0$,已经平衡,不需要再做操作。 + - 如果 $banlance == -2$,则说明 $heap\underline{\hspace{0.5em}}min$ 比 $heap\underline{\hspace{0.5em}}max$ 的元素多了两个。则从 $heap\underline{\hspace{0.5em}}min$ 中取出堆顶元素添加到 $heap\underline{\hspace{0.5em}}max$ 中。 + - 如果 $banlance == 2$,则说明 $heap\underline{\hspace{0.5em}}max$ 比 $heap\underline{\hspace{0.5em}}min$ 的元素多了两个。则从 $heap\underline{\hspace{0.5em}}max$ 中取出堆顶元素添加到 $heap\underline{\hspace{0.5em}}min$ 中。 +- 调整完之后,分别检查 $heap\underline{\hspace{0.5em}}max$ 和 $heap\underline{\hspace{0.5em}}min$ 的堆顶元素。 + - 如果 $heap\underline{\hspace{0.5em}}max$ 堆顶元素恰好为待删除元素,即 $removes[-heap\underline{\hspace{0.5em}}max[0]] > 0$,则弹出 $heap\underline{\hspace{0.5em}}max$ 堆顶元素。 + - 如果 $heap\underline{\hspace{0.5em}}min$ 堆顶元素恰好为待删除元素,即 $removes[heap\underline{\hspace{0.5em}}min[0]] > 0$,则弹出 $heap\underline{\hspace{0.5em}}min$ 堆顶元素。 +- 最后取中位数放入答案数组中,然后继续滑动窗口。 + +### 思路 1:代码 + +```python +import collections +import heapq + +class Solution: + def median(self, heap_max, heap_min, k): + if k % 2 == 1: + return -heap_max[0] + else: + return (-heap_max[0] + heap_min[0]) / 2 + + def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]: + heap_max, heap_min = [], [] + removes = collections.Counter() + + for i in range(k): + heapq.heappush(heap_max, -nums[i]) + for i in range(k // 2): + heapq.heappush(heap_min, -heapq.heappop(heap_max)) + + res = [self.median(heap_max, heap_min, k)] + + for i in range(k, len(nums)): + banlance = 0 + left, right = i - k, i + removes[nums[left]] += 1 + if heap_max and nums[left] <= -heap_max[0]: + banlance -= 1 + else: + banlance += 1 + + if heap_max and nums[right] <= -heap_max[0]: + heapq.heappush(heap_max, -nums[i]) + banlance += 1 + else: + banlance -= 1 + heapq.heappush(heap_min, nums[i]) + + if banlance == -2: + heapq.heappush(heap_max, -heapq.heappop(heap_min)) + if banlance == 2: + heapq.heappush(heap_min, -heapq.heappop(heap_max)) + + while heap_max and removes[-heap_max[0]] > 0: + removes[-heapq.heappop(heap_max)] -= 1 + while heap_min and removes[heap_min[0]] > 0: + removes[heapq.heappop(heap_min)] -= 1 + res.append(self.median(heap_max, heap_min, k)) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[《风 险 对 冲》:双堆对顶,大堆小堆同时维护,44ms - 滑动窗口中位数 - 力扣](https://leetcode.cn/problems/sliding-window-median/solution/feng-xian-dui-chong-shuang-dui-dui-ding-hq1dt/) diff --git a/docs/solutions/0400-0499/sort-characters-by-frequency.md b/docs/solutions/0400-0499/sort-characters-by-frequency.md new file mode 100644 index 00000000..e3b6b1ac --- /dev/null +++ b/docs/solutions/0400-0499/sort-characters-by-frequency.md @@ -0,0 +1,83 @@ +# [0451. 根据字符出现频率排序](https://leetcode.cn/problems/sort-characters-by-frequency/) + +- 标签:哈希表、字符串、桶排序、计数、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0451. 根据字符出现频率排序 - 力扣](https://leetcode.cn/problems/sort-characters-by-frequency/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:将字符串 `s` 里的字符按照出现的频率降序排列。如果有多个答案,返回其中任何一个。 + +**说明**: + +- $1 \le s.length \le 5 * 10^5$。 +- `s` 由大小写英文字母和数字组成。 + +**示例**: + +- 示例 1: + +```python +输入: s = "tree" +输出: "eert" +解释: 'e'出现两次,'r'和't'都只出现一次。 +因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。 +``` + +- 示例 2: + +```python +输入: s = "cccaaa" +输出: "cccaaa" +解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。 +注意"cacaca"是不正确的,因为相同的字母必须放在一起。 +``` + +## 解题思路 + +### 思路 1:优先队列 + +1. 使用哈希表 `s_dict` 统计字符频率。 +2. 然后遍历哈希表 `s_dict`,将字符以及字符频数存入优先队列中。 +3. 将优先队列中频数最高的元素依次加入答案数组中。 +4. 最后拼接答案数组为字符串,将其返回。 + +### 思路 1:代码 + +```python +import heapq + +class Solution: + def frequencySort(self, s: str) -> str: + # 统计元素频数 + s_dict = dict() + for ch in s: + if ch in s_dict: + s_dict[ch] += 1 + else: + s_dict[ch] = 1 + + priority_queue = [] + for ch in s_dict: + heapq.heappush(priority_queue, (-s_dict[ch], ch)) + + res = [] + while priority_queue: + ch = heapq.heappop(priority_queue)[-1] + times = s_dict[ch] + while times: + res.append(ch) + times -= 1 + return ''.join(res) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + k \times log_2k)$。其中 $n$ 为字符串 $s$ 的长度,$k$ 是字符串中不同字符的个数。 +- **空间复杂度**:$O(n + k)$。 + diff --git a/docs/solutions/0400-0499/split-array-largest-sum.md b/docs/solutions/0400-0499/split-array-largest-sum.md new file mode 100644 index 00000000..58e024ab --- /dev/null +++ b/docs/solutions/0400-0499/split-array-largest-sum.md @@ -0,0 +1,87 @@ +# [0410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) + +- 标签:贪心、数组、二分查找、动态规划、前缀和 +- 难度:困难 + +## 题目链接 + +- [0410. 分割数组的最大值 - 力扣](https://leetcode.cn/problems/split-array-largest-sum/) + +## 题目大意 + +**描述**:给定一个非负整数数组 $nums$ 和一个整数 $k$,将数组分成 $m$ 个非空的连续子数组。 + +**要求**:使 $m$ 个子数组各自和的最大值最小,并求出子数组各自和的最大值。 + +**说明**: + +- $1 \le nums.length \le 1000$。 +- $0 \le nums[i] \le 10^6$。 +- $1 \le k \le min(50, nums.length)$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [7,2,5,10,8], k = 2 +输出:18 +解释: +一共有四种方法将 nums 分割为 2 个子数组。 +其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。 +因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3,4,5], k = 2 +输出:9 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +先来理解清楚题意。题目的目的是使得 $m$ 个连续子数组各自和的最大值最小。意思是将数组按顺序分成 $m$ 个子数组,然后计算每个子数组的和,然后找出 $m$ 个和中的最大值,要求使这个最大值尽可能小。最后输出这个尽可能小的和最大值。 + +可以用二分查找来找这个子数组和的最大值,我们用 $ans$ 来表示这个值。$ans$ 最小为数组 $nums$ 所有元素的最大值,最大为数组 $nums$ 所有元素的和。即 $ans$ 范围是 $[max(nums), sum(nums)]$。 + +所以就确定了二分查找的两个指针位置。$left$ 指向 $max(nums)$,$right$ 指向 $sum(nums)$。然后取中间值 $mid$,计算当子数组和的最大值为 mid 时,所需要分割的子数组最少个数。 + +- 如果需要分割的子数组最少个数大于 $m$ 个,则说明子数组和的最大值取小了,不满足条件,应该继续调大,将 $left$ 右移,从右区间继续查找。 +- 如果需要分割的子数组最少个数小于或等于 $m$ 个,则说明子数组和的最大值满足条件,并且还可以继续调小,将 $right$ 左移,从左区间继续查找,看是否有更小的数组和满足条件。 +- 最终,返回符合条件的最小值即可。 + +### 思路 1:代码 + +```python +class Solution: + def splitArray(self, nums: List[int], m: int) -> int: + def get_count(x): + total = 0 + count = 1 + for num in nums: + if total + num > x: + count += 1 + total = num + else: + total += num + return count + + left = max(nums) + right = sum(nums) + while left < right: + mid = left + (right - left) // 2 + if get_count(mid) > m: + left = mid + 1 + else: + right = mid + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log (\sum nums))$,其中 $n$ 为数组中的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0400-0499/string-compression.md b/docs/solutions/0400-0499/string-compression.md new file mode 100644 index 00000000..cdf3a4d9 --- /dev/null +++ b/docs/solutions/0400-0499/string-compression.md @@ -0,0 +1,100 @@ +# [0443. 压缩字符串](https://leetcode.cn/problems/string-compression/) + +- 标签:双指针、字符串 +- 难度:中等 + +## 题目链接 + +- [0443. 压缩字符串 - 力扣](https://leetcode.cn/problems/string-compression/) + +## 题目大意 + +**描述**:给定一个字符数组 $chars$。请使用下述算法压缩: + +从一个空字符串 $s$ 开始。对于 $chars$ 中的每组连续重复字符: + +- 如果这一组长度为 $1$,则将字符追加到 $s$ 中。 +- 如果这一组长度超过 $1$,则需要向 $s$ 追加字符,后跟这一组的长度。 + +压缩后得到的字符串 $s$ 不应该直接返回 ,需要转储到字符数组 $chars$ 中。需要注意的是,如果组长度为 $10$ 或 $10$ 以上,则在 $chars$ 数组中会被拆分为多个字符。 + +**要求**:在修改完输入数组后,返回该数组的新长度。 + +**说明**: + +- $1 \le chars.length \le 2000$。 +- $chars[i]$ 可以是小写英文字母、大写英文字母、数字或符号。 +- 必须设计并实现一个只使用常量额外空间的算法来解决此问题。 + +**示例**: + +- 示例 1: + +```python +输入:chars = ["a","a","b","b","c","c","c"] +输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"] +解释:"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。 +``` + +- 示例 2: + +```python +输入:chars = ["a"] +输出:返回 1 ,输入数组的前 1 个字符应该是:["a"] +解释:唯一的组是“a”,它保持未压缩,因为它是一个字符。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +题目要求原地修改字符串数组。我们可以使用快慢指针来解决原地修改问题,具体解决方法如下: + +- 定义两个快慢指针 $slow$,$fast$。其中 $slow$ 指向压缩后的当前字符位置,$fast$ 指向压缩前的当前字符位置。 +- 记录下当前待压缩字符的起始位置 $fast\underline{\hspace{0.5em}}start = start$,然后过滤掉连续相同的字符。 +- 将待压缩字符的起始位置的字符存入压缩后的当前字符位置,即 $chars[slow] = chars[fast\underline{\hspace{0.5em}}start]$,并向右移动压缩后的当前字符位置,即 $slow += 1$。 +- 判断一下待压缩字符的数目是否大于 $1$: + - 如果数量为 $1$,则不用记录该数量。 + - 如果数量大于 $1$(即 $fast - fast\underline{\hspace{0.5em}}start > 0$),则我们需要将对应数量存入压缩后的当前字符位置。这时候还需要判断一下数量是否大于等于 $10$。 + - 如果数量大于等于 $10$,则需要先将数字从个位到高位转为字符,存入压缩后的当前字符位置(此时数字为反,比如原数字是 $321$,则此时存入后为 $123$)。因为数字为反,所以我们需要将对应位置上的子字符串进行反转。 + - 如果数量小于 $10$,则直接将数字存入压缩后的当前字符位置,无需取反。 +- 判断完之后向右移动压缩前的当前字符位置 $fast$,然后继续压缩字符串,直到全部压缩完,则返回压缩后的当前字符位置 $slow$ 即为答案。 + +### 思路 1:代码 + +```python +class Solution: + + def compress(self, chars: List[str]) -> int: + def reverse(left, right): + while left < right: + chars[left], chars[right] = chars[right], chars[left] + left += 1 + right -= 1 + + slow, fast = 0, 0 + while fast < len(chars): + fast_start = fast + while fast + 1 < len(chars) and chars[fast + 1] == chars[fast]: + fast += 1 + + chars[slow] = chars[fast_start] + slow += 1 + + if fast - fast_start > 0: + cnt = fast - fast_start + 1 + slow_start = slow + while cnt != 0: + chars[slow] = str(cnt % 10) + slow += 1 + cnt = cnt // 10 + reverse(slow_start, slow - 1) + + fast += 1 + return slow +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0400-0499/sum-of-left-leaves.md b/docs/solutions/0400-0499/sum-of-left-leaves.md new file mode 100644 index 00000000..12c2b962 --- /dev/null +++ b/docs/solutions/0400-0499/sum-of-left-leaves.md @@ -0,0 +1,34 @@ +# [0404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0404. 左叶子之和 - 力扣](https://leetcode.cn/problems/sum-of-left-leaves/) + +## 题目大意 + +给定一个二叉树,计算所有左叶子之和。 + +## 解题思路 + +深度优先搜索递归遍历二叉树,若当前节点不为空,且左孩子节点不为空,且左孩子节点的左右孩子节点都为空,则该节点的左孩子节点为左叶子节点。将其值累加起来,即为答案。 + +## 代码 + +```python +class Solution: + def sumOfLeftLeaves(self, root: TreeNode) -> int: + self.ans = 0 + def dfs(node): + if not node: + return None + if node.left and not node.left.left and not node.left.right: + self.ans += node.left.val + dfs(node.left) + dfs(node.right) + dfs(root) + return self.ans +``` + diff --git a/docs/solutions/0400-0499/target-sum.md b/docs/solutions/0400-0499/target-sum.md new file mode 100644 index 00000000..51b3a352 --- /dev/null +++ b/docs/solutions/0400-0499/target-sum.md @@ -0,0 +1,182 @@ +# [0494. 目标和](https://leetcode.cn/problems/target-sum/) + +- 标签:数组、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [0494. 目标和 - 力扣](https://leetcode.cn/problems/target-sum/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $target$。数组长度不超过 $20$。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 + +**要求**:返回通过上述方法构造的、运算结果等于 $target$ 的不同表达式数目。 + +**说明**: + +- $1 \le nums.length \le 20$。 +- $0 \le nums[i] \le 1000$。 +- $0 \le sum(nums[i]) \le 1000$。 +- $-1000 \le target \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1,1,1], target = 3 +输出:5 +解释:一共有 5 种方法让最终目标和为 3。 +-1 + 1 + 1 + 1 + 1 = 3 ++1 - 1 + 1 + 1 + 1 = 3 ++1 + 1 - 1 + 1 + 1 = 3 ++1 + 1 + 1 - 1 + 1 = 3 ++1 + 1 + 1 + 1 - 1 = 3 +``` + +- 示例 2: + +```python +输入:nums = [1], target = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:深度优先搜索(超时) + +使用深度优先搜索对每位数字进行 `+` 或者 `-`,具体步骤如下: + +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 到达最后一个位置 $size$: + 1. 如果和 $cur\underline{\hspace{0.5em}}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{\hspace{0.5em}}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum - nums[i]$ 的方案数。 +5. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum + nums[i]$ 的方案数。 +6. 将 4 ~ 5 两个方案数加起来就是当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 的方案数,返回该方案数。 +7. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + size = len(nums) + + def dfs(i, cur_sum): + if i == size: + if cur_sum == target: + return 1 + else: + return 0 + ans = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) + return ans + + return dfs(0, 0) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 + +### 思路 2:记忆化搜索 + +在思路 1 中我们单独使用深度优先搜索对每位数字进行 `+` 或者 `-` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。 + +这里我们使用哈希表 $$table$$ 记录遍历过的位置 $i$ 及所得到的的当前和 $cur\underline{\hspace{0.5em}}sum$ 下的方案数,来避免重复搜索。具体步骤如下: + +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 遍历完所有位置: + 1. 如果和 $cur\underline{\hspace{0.5em}}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{\hspace{0.5em}}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 如果当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 之前记录过(即使用 $table$ 记录过对应方案数),则返回该方案数。 +5. 如果当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 之前没有记录过,则: + 1. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum - nums[i]$ 的方案数。 + 2. 递归搜索 $i + 1$ 位置,和为 $cur\underline{\hspace{0.5em}}sum + nums[i]$ 的方案数。 + 3. 将上述两个方案数加起来就是当前位置 $i$、和为 $cur\underline{\hspace{0.5em}}sum$ 的方案数,将其记录到哈希表 $table$ 中,并返回该方案数。 +6. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 + +### 思路 2:代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + size = len(nums) + table = dict() + + def dfs(i, cur_sum): + if i == size: + if cur_sum == target: + return 1 + else: + return 0 + + if (i, cur_sum) in table: + return table[(i, cur_sum)] + + cnt = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i]) + table[(i, cur_sum)] = cnt + return cnt + + return dfs(0, 0) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。递归调用的栈空间深度不超过 $n$。 + +### 思路 3:动态规划 + +假设数组中所有元素和为 $sum$,数组中所有符号为 `+` 的元素为 $sum\underline{\hspace{0.5em}}x$,符号为 `-` 的元素和为 $sum\underline{\hspace{0.5em}}y$。则 $target = sum\underline{\hspace{0.5em}}x - sum\underline{\hspace{0.5em}}y$。 + +而 $sum\underline{\hspace{0.5em}}x + sum\underline{\hspace{0.5em}}y = sum$。根据两个式子可以求出 $2 \times sum\underline{\hspace{0.5em}}x = target + sum$,即 $sum\underline{\hspace{0.5em}}x = (target + sum) / 2$。 + +那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 $(target + sum) / 2$。这就变为了「0-1 背包问题」中求装满背包的方案数问题。 + +###### 1. 定义状态 + +定义状态 $dp[i]$ 表示为:填满容量为 $i$ 的背包,有 $dp[i]$ 种方法。 + +###### 2. 状态转移方程 + +填满容量为 $i$ 的背包的方法数来源于: + +1. 不使用当前 $num$:只使用之前元素填满容量为 $i$ 的背包的方法数。 +2. 使用当前 $num$:填满容量 $i - num$ 的包的方法数,再填入 $num$ 的方法数。 + +则动态规划的状态转移方程为:$dp[i] = dp[i] + dp[i - num]$。 + +###### 3. 初始化 + +初始状态下,默认填满容量为 $0$ 的背包有 $1$ 种办法(什么也不装)。即 $dp[i] = 1$。 + +###### 4. 最终结果 + +根据状态定义,最后输出 $dp[sise]$(即填满容量为 $size$ 的背包,有 $dp[size]$ 种方法)即可,其中 $size$ 为数组 $nums$ 的长度。 + +### 思路 3:代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + sum_nums = sum(nums) + if abs(target) > abs(sum_nums) or (target + sum_nums) % 2 == 1: + return 0 + size = (target + sum_nums) // 2 + dp = [0 for _ in range(size + 1)] + dp[0] = 1 + for num in nums: + for i in range(size, num - 1, -1): + dp[i] = dp[i] + dp[i - num] + return dp[size] +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md b/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md new file mode 100644 index 00000000..2eb5e95b --- /dev/null +++ b/docs/solutions/0400-0499/unique-substrings-in-wraparound-string.md @@ -0,0 +1,47 @@ +# [0467. 环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0467. 环绕字符串中唯一的子字符串 - 力扣](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) + +## 题目大意 + +把字符串 `s` 看作是 `abcdefghijklmnopqrstuvwxyz` 的无限环绕字符串,所以 `s` 看起来是这样的:`...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....`。 + +给定一个字符串 `p`。 + +要求:你需要的是找出 `s` 中有多少个唯一的 `p` 的非空子串,尤其是当你的输入是字符串 `p` ,你需要输出字符串 `s` 中 `p` 的不同的非空子串的数目。 + +注意: `p` 仅由小写的英文字母组成,`p` 的大小可能超过 `10000`。 + +## 解题思路 + +字符串 `s` 是个 `a` ~ `z` 无限循环的字符串,题目要求计算字符串 `s` 和字符串 `p` 中有多少个相等的非空子串。发现以该字符结尾的连续子串的长度,就等于以该字符结尾的相等子串的个数。所以我们可以按以下步骤求解: + +- 记录以每个字符结尾的字符串最长长度。 +- 将其累加起来就是最终答案。 + +## 代码 + +```python +class Solution: + def findSubstringInWraproundString(self, p: str) -> int: + dp = collections.defaultdict(int) + dp[p[0]] = 1 + max_len = 1 + for i in range(1, len(p)): + if (ord(p[i]) - ord(p[i - 1])) % 26 == 1: + max_len += 1 + else: + max_len = 1 + dp[p[i]] = max(dp[p[i]], max_len) + + ans = 0 + for key, value in dp.items(): + ans += value + return ans +``` + diff --git a/docs/solutions/0400-0499/validate-ip-address.md b/docs/solutions/0400-0499/validate-ip-address.md new file mode 100644 index 00000000..ffb49758 --- /dev/null +++ b/docs/solutions/0400-0499/validate-ip-address.md @@ -0,0 +1,112 @@ +# [0468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) + +- 标签:字符串 +- 难度:中等 + +## 题目链接 + +- [0468. 验证IP地址 - 力扣](https://leetcode.cn/problems/validate-ip-address/) + +## 题目大意 + +**描述**:给定一个字符串 `queryIP`。 + +**要求**:如果是有效的 IPv4 地址,返回 `"IPv4"`;如果是有效的 IPv6 地址,返回 `"IPv6"`;如果不是上述类型的 IP 地址,返回 `"Neither"`。 + +**说明**: + +- **有效的 IPv4 地址**:格式为 `"x1.x2.x3.x4"` 形式的 IP 地址。 其中: + - $0 \le xi \le 255$。 + - $xi$ 不能包含前导零。 + +- 例如: `"192.168.1.1"` 、 `"192.168.1.0"` 为有效 IPv4 地址,`"192.168.01.1"` 为无效 IPv4 地址,`"192.168.1.00"` 、 `"192.168@1.1"` 为无效 IPv4 地址。 +- **有效的 IPv6 地址**: 格式为`"x1:x2:x3:x4:x5:x6:x7:x8"` 的 IP 地址,其中: + - $1 \le xi.length \le 4$。 + - $xi$ 是一个十六进制字符串,可以包含数字、小写英文字母(`'a'` 到 `'f'`)和大写英文字母(`'A'` 到 `'F'`)。 + - 在 $xi$ 中允许前导零。 +- 例如:`"2001:0db8:85a3:0000:0000:8a2e:0370:7334"` 和 `"2001:db8:85a3:0:0:8A2E:0370:7334"` 是有效的 IPv6 地址,而 `"2001:0db8:85a3::8A2E:037j:7334"` 和 `"02001:0db8:85a3:0000:0000:8a2e:0370:7334"` 是无效的 IPv6 地址。 +- `queryIP` 仅由英文字母,数字,字符 `'.'` 和 `':'` 组成。 + +**示例**: + +- 示例 1: + +```python +输入:queryIP = "172.16.254.1" +输出:"IPv4" +解释:有效的 IPv4 地址,返回 "IPv4" +``` + +- 示例 2: + +```python +输入:queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334" +输出:"IPv6" +解释:有效的 IPv6 地址,返回 "IPv6" +``` + +## 解题思路 + +### 思路 1:模拟 + +根据题意以及有效的 IPV4 地址规则、有效的 IPv6 地址规则,我们可以分两步来做:第一步,验证是否为有效的 IPV4 地址。第二步,验证是否为有效的 IPv6 地址。 + +#### 1. 验证是否为有效的 IPv4 地址 + +1. 将字符串按照 `'.'` 进行分割,将不同分段存入数组 `path` 中。 +2. 如果分段数组 `path` 长度等于 $4$,则说明该字符串为 IPv4 地址,接下里验证是否为有效的 IPv4 地址。 +3. 遍历分段数组 `path`,去验证每个分段 `sub`。 + 1. 如果当前分段 `sub` 为空,或者不是纯数字,则返回 `"Neither"`。 + 2. 如果当前分段 `sub` 有前导 $0$,并且长度不为 $1$,则返回 `"Neither"`。 + 3. 如果当前分段 `sub` 对应的值不在 $0 \sim 255$ 范围内,则返回 `"Neither"`。 +4. 遍历完分段数组 `path`,扔未发现问题,则该字符串为有效的 IPv4 地址,返回 `IPv4`。 + +#### 2. 验证是否为有效的 IPv6 地址 + +1. 将字符串按照 `':'` 进行分割,将不同分段存入数组 `path` 中。 +2. 如果分段数组 `path` 长度等于 $8$,则说明该字符串为 IPv6 地址,接下里验证是否为有效的 IPv6 地址。 +3. 定义一个代表十六进制不同字符的字符串 `valid = "0123456789abcdefABCDEF"`,用于验证分段的每一位是否为 $16$ 进制数。 +4. 遍历分段数组 `path`,去验证每个分段 `sub`。 + 1. 如果当前分段 `sub` 为空,则返回 `"Neither"`。 + 2. 如果当前分段 `sub` 长度超过 $4$,则返回 `"Neither"`。 + 3. 如果当前分段 `sub` 对应的每一位的值不在 `valid` 内,则返回 `"Neither"`。 +5. 遍历完分段数组 `path`,扔未发现问题,则该字符串为有效的 IPv6 地址,返回 `IPv6`。 + +如果通过上面两步验证,该字符串既不是有效的 IPv4 地址,也不是有效的 IPv6 地址,则返回 `"Neither"`。 + +### 思路 1:代码 + +```python +class Solution: + def validIPAddress(self, queryIP: str) -> str: + path = queryIP.split('.') + if len(path) == 4: + for sub in path: + if not sub or not sub.isdecimal(): + return "Neither" + if sub[0] == '0' and len(sub) != 1: + return "Neither" + if int(sub) > 255: + return "Neither" + return "IPv4" + + path = queryIP.split(':') + if len(path) == 8: + valid = "0123456789abcdefABCDEF" + for sub in path: + if not sub: + return "Neither" + if len(sub) > 4: + return "Neither" + for digit in sub: + if digit not in valid: + return "Neither" + return "IPv6" + + return "Neither" +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 `queryIP` 的长度。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0400-0499/word-squares.md b/docs/solutions/0400-0499/word-squares.md new file mode 100644 index 00000000..540ad354 --- /dev/null +++ b/docs/solutions/0400-0499/word-squares.md @@ -0,0 +1,117 @@ +# [0425. 单词方块](https://leetcode.cn/problems/word-squares/) + +- 标签:字典树、数组、字符串、回溯 +- 难度:困难 + +## 题目链接 + +- [0425. 单词方块 - 力扣](https://leetcode.cn/problems/word-squares/) + +## 题目大意 + +给定一个单词集合 `words`(没有重复)。 + +要求:找出其中所有的单词方块 。 + +- 单词方块:指从第 `k` 行和第 `k` 列 `(0 ≤ k < max(行数, 列数))` 来看都是相同的字符串。 + +例如,单词序列 ["ball","area","lead","lady"] 形成了一个单词方块,因为每个单词从水平方向看和从竖直方向看都是相同的。 + +``` +b a l l +a r e a +l e a d +l a d y +``` + +## 解题思路 + +根据单词方块的第一个单词,可以推出下一个单词的前缀。 + +比如第一个单词是 `ball`,那么单词方块的长度是 `4 * 4`,则下一个单词(第二个单词)的前缀为 `a`。这样我们就又找到了一个以 `a` 为前缀且长度为 `4` 的单词,即 `area`,此时就变成了 `[ball, area]`。 + +那么下一个单词(第三个单词)的前缀为 `le`。这样我们就又找到了一个以 `le` 为前缀且长度为 `4` 的单词,即 `lead`。此时就变成了 `[ball, area, lead]`。 + +以此类推,就可以得到整个单词方块。 + +并且我们可以使用字典树(前缀树)来存储单词,并且通过回溯得到所有的解。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str): + """ + Returns if the word is in the trie. + """ + cur = self + res = [] + for ch in word: + if ch not in cur.children: + return res + cur = cur.children[ch] + cur.dfs(word, res) + return res + + def dfs(self, word, res): + cur = self + if cur and cur.isEnd: + res.append(word) + return + for ch in cur.children: + node = cur.children[ch] + node.dfs(word + ch, res) + + +class Solution: + + def backtrace(self, index, size, path, res, trie_tree): + if index == size: + res.append(path[:]) + return + next_prefix = "" # 下一行的前缀 + for i in range(index): + next_prefix += path[i][index] + + next_words_with_prefix = trie_tree.search(next_prefix) + for word in next_words_with_prefix: + path.append(word) + self.backtrace(index + 1, size, path, res, trie_tree) + path.pop(-1) + + + def wordSquares(self, words: List[str]) -> List[List[str]]: + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + size = len(words[0]) + res = [] + path = [] + for word in words: + path.append(word) + self.backtrace(1, size, path, res, trie_tree) + path.pop(-1) + return res +``` + diff --git a/docs/solutions/0500-0599/01-matrix.md b/docs/solutions/0500-0599/01-matrix.md new file mode 100644 index 00000000..10602880 --- /dev/null +++ b/docs/solutions/0500-0599/01-matrix.md @@ -0,0 +1,104 @@ +# [0542. 01 矩阵](https://leetcode.cn/problems/01-matrix/) + +- 标签:广度优先搜索、数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [0542. 01 矩阵 - 力扣](https://leetcode.cn/problems/01-matrix/) + +## 题目大意 + +**描述**:给定一个 $m * n$ 大小的、由 `0` 和 `1` 组成的矩阵 $mat$。 + +**要求**:输出一个大小相同的矩阵 $res$,其中 $res[i][j]$ 表示对应位置元素(即 $mat[i][j]$)到最近的 $0$ 的距离。 + +**说明**: + +- 两个相邻元素间的距离为 $1$。 +- $m == mat.length$。 +- $n == mat[i].length$。 +- $1 \le m, n \le 10^4$。 +- $1 \le m * n \le 10^4$。 +- $mat[i][j] === 0$ 或者 $mat[i][j] == 1$。 +- $mat$ 中至少有一个 $0$。 + +**示例**: + +- 示例 1: + +![](https://pic.leetcode-cn.com/1626667201-NCWmuP-image.png) + +```python +输入:mat = [[0,0,0],[0,1,0],[0,0,0]] +输出:[[0,0,0],[0,1,0],[0,0,0]] +``` + +- 示例 2: + +![](https://pic.leetcode-cn.com/1626667205-xFxIeK-image.png) + +```python +输入:mat = [[0,0,0],[0,1,0],[1,1,1]] +输出:[[0,0,0],[0,1,0],[1,2,1]] +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +题目要求的是每个 `1` 到 `0`的最短曼哈顿距离。 + +比较暴力的做法是,从每个 `1` 开始进行广度优先搜索,每一步累积距离,当搜索到第一个 `0`,就是离这个 `1` 最近的 `0`,我们更新对应 `1` 位置上的答案距离。然后从下一个 `1` 开始进行广度优先搜索。 + +这样做每次进行广度优先搜索的时间复杂度为 $O(m \times n)$。对于 $m \times n$ 个节点来说,每个节点可能都要进行一次广度优先搜索,总的时间复杂度为 $O(m^2 \times n^2)$。时间复杂度太高了。 + +我们可以换个角度:求每个 `0` 到 `1` 的最短曼哈顿距离(和求每个 `1` 到 `0` 是等价的)。 + +我们将所有值为 `0` 的元素位置保存到队列中,然后对所有值为 `0` 的元素开始进行广度优先搜索,每搜一步距离加 `1`,当每次搜索到 `1` 时,就可以得到 `0` 到这个 `1` 的最短距离,也就是当前离这个 `1` 最近的 `0` 的距离。 + +这样对于所有节点来说,总共需要进行一次广度优先搜索就可以了,时间复杂度为 $O(m \times n)$。 + +具体步骤如下: + +1. 使用一个集合变量 `visited` 存储所有值为 `0` 的元素坐标。使用队列变量 `queue` 存储所有值为 `0` 的元素坐标。使用二维数组 `res` 存储对应位置元素(即 $mat[i][j]$)到最近的 $0$ 的距离。 +2. 我们从所有为如果队列 `queue` 不为空,则从队列中依次取出值为 `0` 的元素坐标,遍历其上、下、左、右位置。 +3. 如果相邻区域未被访问过(说明遇到了值为 `1` 的元素),则更新相邻位置的距离值,并把相邻位置坐标加入队列 `queue` 和访问集合 `visited` 中。 +4. 继续执行 2 ~ 3 步,直到队列为空时,返回 `res`。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: + rows, cols = len(mat), len(mat[0]) + res = [[0 for _ in range(cols)] for _ in range(rows)] + visited = set() + + for i in range(rows): + for j in range(cols): + if mat[i][j] == 0: + visited.add((i, j)) + + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + queue = collections.deque(visited) + + while queue: + i, j = queue.popleft() + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + if 0 <= new_i < rows and 0 <= new_j < cols and (new_i, new_j) not in visited: + res[new_i][new_j] = res[i][j] + 1 + queue.append((new_i, new_j)) + visited.add((new_i, new_j)) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0500-0599/array-partition.md b/docs/solutions/0500-0599/array-partition.md new file mode 100644 index 00000000..de77920e --- /dev/null +++ b/docs/solutions/0500-0599/array-partition.md @@ -0,0 +1,111 @@ +# [0561. 数组拆分](https://leetcode.cn/problems/array-partition/) + +- 标签:贪心、数组、计数排序、排序 +- 难度:简单 + +## 题目链接 + +- [0561. 数组拆分 - 力扣](https://leetcode.cn/problems/array-partition/) + +## 题目大意 + +**描述**:给定一个长度为 $2 \times n$ 的整数数组 $nums$。 + +**要求**:将数组中的数拆分成 $n$ 对,每对数求最小值,求 $n$ 对数最小值的最大总和是多少。 + +**说明**: + +- $1 \le n \le 10^4$。 +- $nums.length == 2 * n$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,4,3,2] +输出:4 +解释:所有可能的分法(忽略元素顺序)为: +1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3 +2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3 +3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4 +所以最大总和为 4 +``` +- 示例 2: + +```python +输入:nums = [6,2,6,5,1,2] +输出:9 +解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9 +``` + +## 解题思路 + +### 思路 1:计数排序 + +因为 $nums[i]$ 的范围为 $[-10^4, 10^4]$,范围不是很大,所以我们可以使用计数排序算法先将数组 $nums$ 进行排序。 + +要想每对数最小值的总和最大,就得使每对数的最小值尽可能大。只有让较大的数与较大的数一起组合,较小的数与较小的数一起结合,才能才能使总和最大。所以,排序完之后将相邻两个元素的最小值进行相加,即得到结果。 + +### 思路 1:代码 + +```python +class Solution: + def countingSort(self, nums: [int]) -> [int]: + # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min + nums_min, nums_max = min(nums), max(nums) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 + size = nums_max - nums_min + 1 + counts = [0 for _ in range(size)] + + # 统计值为 num 的元素出现的次数 + for num in nums: + counts[num - nums_min] += 1 + + # 生成累积计数数组 + for i in range(1, size): + counts[i] += counts[i - 1] + + # 反向填充目标数组 + res = [0 for _ in range(len(nums))] + for i in range(len(nums) - 1, -1, -1): + num = nums[i] + # 根据累积计数数组,将 num 放在数组对应位置 + res[counts[num - nums_min] - 1] = num + # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 + counts[nums[i] - nums_min] -= 1 + + return res + + def arrayPairSum(self, nums: List[int]) -> int: + nums = self.countingSort(nums) + return sum(nums[::2]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + k)$,其中 $k$ 代表数组 $nums$ 的值域。 +- **空间复杂度**:$O(k)$。 + +### 思路 2:排序 + +要想每对数最小值的总和最大,就得使每对数的最小值尽可能大。只有让较大的数与较大的数一起组合,较小的数与较小的数一起结合,才能才能使总和最大。 + +1. 对 $nums$ 进行排序。 +2. 将相邻两个元素的最小值进行相加,即得到结果。 + +### 思路 1:代码 + +```python +class Solution: + def arrayPairSum(self, nums: List[int]) -> int: + nums.sort() + return sum(nums[::2]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0500-0599/base-7.md b/docs/solutions/0500-0599/base-7.md new file mode 100644 index 00000000..c4a3bf24 --- /dev/null +++ b/docs/solutions/0500-0599/base-7.md @@ -0,0 +1,63 @@ +# [0504. 七进制数](https://leetcode.cn/problems/base-7/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [0504. 七进制数 - 力扣](https://leetcode.cn/problems/base-7/) + +## 题目大意 + +**描述**:给定一个整数 $num$。 + +**要求**:将其转换为 $7$ 进制数,并以字符串形式输出。 + +**说明**: + +- $-10^7 \le num \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入: num = 100 +输出: "202" +``` + +- 示例 2: + +```python +输入: num = -7 +输出: "-10" +``` + +## 解题思路 + +### 思路 1:模拟 + +1. $num$ 不断对 $7$ 取余整除。 +2. 然后将取到的余数进行拼接成字符串即可。 + +### 思路 1:代码 + +```python +class Solution: + def convertToBase7(self, num: int) -> str: + if num == 0: + return "0" + if num < 0: + return "-" + self.convertToBase7(-num) + ans = "" + while num: + ans = str(num % 7) + ans + num //= 7 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log |n|)$。 +- **空间复杂度**:$O(\log |n|)$。 + diff --git a/docs/solutions/0500-0599/beautiful-arrangement.md b/docs/solutions/0500-0599/beautiful-arrangement.md new file mode 100644 index 00000000..eac572be --- /dev/null +++ b/docs/solutions/0500-0599/beautiful-arrangement.md @@ -0,0 +1,220 @@ +# [0526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) + +- 标签:位运算、数组、动态规划、回溯、状态压缩 +- 难度:中等 + +## 题目链接 + +- [0526. 优美的排列 - 力扣](https://leetcode.cn/problems/beautiful-arrangement/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回可以构造的「优美的排列」的数量。 + +**说明**: + +- **优美的排列**:假设有 $1 \sim n$ 的 $n$ 个整数。如果用这些整数构造一个数组 $perm$(下标从 $1$ 开始),使得数组第 $i$ 位元素 $perm[i]$ 满足下面两个条件之一,则该数组就是一个「优美的排列」: + - $perm[i]$ 能够被 $i$ 整除; + - $i$ 能够被 $perm[i]$ 整除。 + +- $1 \le n \le 15$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:2 +解释: +第 1 个优美的排列是 [1,2]: + - perm[1] = 1 能被 i = 1 整除 + - perm[2] = 2 能被 i = 2 整除 +第 2 个优美的排列是 [2,1]: + - perm[1] = 2 能被 i = 1 整除 + - i = 2 能被 perm[2] = 1 整除 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:回溯算法 + +这道题可以看做是「[0046. 全排列](https://leetcode.cn/problems/permutations/)」的升级版。 + +1. 通过回溯算法我们可以将数组的所有排列情况列举出来。 +2. 因为只有满足第 $i$ 位元素能被 $i$ 整除,或者满足 $i$ 能整除第 $i$ 位元素的条件下才符合要求,所以我们可以进行剪枝操作,不再考虑不满足要求的情况。 +3. 最后回溯完输出方案数。 + +### 思路 1:代码 + +```python +class Solution: + def countArrangement(self, n: int) -> int: + ans = 0 + visited = set() + + def backtracking(index): + nonlocal ans + if index == n + 1: + ans += 1 + return + + for i in range(1, n + 1): + if i in visited: + continue + if i % index == 0 or index % i == 0: + visited.add(i) + backtracking(index + 1) + visited.remove(i) + + backtracking(1) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n!)$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(n)$,递归栈空间大小为 $O(n)$。 + +### 思路 2:状态压缩 DP + +因为 $n$ 最大只有 $15$,所以我们可以考虑使用「状态压缩」。 + +「状态压缩」指的是使用一个 $n$ 位的二进制数来表示排列中数的选取情况。 + +举个例子: + +1. $n = 4, state = (1001)_2$,表示选择了数字 $1, 4$,剩余数字 $2$ 和 $3$ 未被选择。 +2. $n = 6, state = (011010)_2$,表示选择了数字 $2, 4, 5$,剩余数字 $1, 3, 6$ 未被选择。 + +这样我们就可以使用 $n$ 位的二进制数 $state$ 来表示当前排列中数的选取情况。 + +如果我们需要检查值为 $k$ 的数字是否被选择时,可以通过判断 $(state \text{ >} \text{> } (k - 1)) \text{ \& } 1$ 是否为 $1$ 来确定。 + +如果为 $1$,则表示值为 $k$ 的数字被选择了,如果为 $0$,则表示值为 $k$ 的数字没有被选择。 + +###### 1. 划分阶段 + +按照排列的数字个数、数字集合的选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][state]$ 表示为:考虑前 $i$ 个数,且当数字集合的选择情况为 $state$ 时的方案数。 + +###### 3. 状态转移方程 + +假设 $dp[i][state]$ 中第 $i$ 个位置所选数字为 $k$,则:$state$ 中第 $k$ 位为 $1$,且 $k \mod i == 0$ 或者 $i \mod k == 0$。 + +那么 $dp[i][state]$ 肯定是由考虑前 $i - 1$ 个位置,且 $state$ 第 $k$ 位为 $0$ 的状态而来,即:$dp[i - 1][state \& (\neg(1 \text{ <}\text{< } (k - 1)))]$。 + +所以状态转移方程为:$dp[i][state] = \sum_{k = 1}^n dp[i - 1][state \text{ \& } (\neg(1 \text{ <} \text{< } (k - 1)))]$。 + +###### 4. 初始条件 + +- 不考虑任何数($i = 0, state = 0$)的情况下,方案数为 $1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][state]$ 表示为:考虑前 $i$ 个数,且当数字集合的选择情况为 $state$ 时的方案数。所以最终结果为 $dp[i][states - 1]$,其中 $states = 1 \text{ <} \text{< } n$。 + +### 思路 2:代码 + +```python +class Solution: + def countArrangement(self, n: int) -> int: + states = 1 << n + dp = [[0 for _ in range(states)] for _ in range(n + 1)] + dp[0][0] = 1 + + for i in range(1, n + 1): # 枚举第 i 个位置 + for state in range(states): # 枚举所有状态 + one_num = bin(state).count("1") # 计算当前状态中选择了多少个数字(即统计 1 的个数) + if one_num != i: # 只有 i 与选择数字个数相同时才能计算 + continue + for k in range(1, n + 1): # 枚举第 i 个位置(最后 1 位)上所选的数字 + if state >> (k - 1) & 1 == 0: # 只有 state 第 k 个位置上为 1 才表示选了该数字 + continue + if k % i == 0 or i % k == 0: # 只有满足整除关系才符合要求 + # dp[i][state] 由前 i - 1 个位置,且 state 第 k 位为 0 的状态而来 + dp[i][state] += dp[i - 1][state & (~(1 << (k - 1)))] + + return dp[i][states - 1] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2 \times 2^n)$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(n \times 2^n)$。 + +### 思路 3:状态压缩 DP + 优化 + +通过二维的「状态压缩 DP」可以看出,当我们在考虑第 $i$ 个位置时,其选择数字个数也应该为 $i$。 + +而我们可以根据 $state$ 中 $1$ 的个数来判断当前选择的数字个数,这样我们就可以减少用于枚举第 $i$ 个位置的循环,改用统计 $state$ 中 $1$ 的个数来判断前选择的数字个数或者说当前正在考虑的元素位置。 + +而这样,我们还可以进一步优化状态的定义,将二维的状态优化为一维的状态。具体做法如下: + +###### 1. 划分阶段 + +按照数字集合的选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[state]$ 表示为:当数字集合的选择情况为 $state$ 时的方案数。 + +###### 3. 状态转移方程 + +对于状态 $state$,先统计出 $state$ 中选择的数字个数(即统计二进制中 $1$ 的个数)$one\underline{\hspace{0.5em}}num$。 + +则 $dp[state]$ 表示选择了前 $one\underline{\hspace{0.5em}}num$ 个数字,且选择情况为 $state$ 时的方案数。 + +$dp[state]$ 的状态肯定是由前 $one\underline{\hspace{0.5em}}num - 1$ 个数字,且 $state$ 第 $k$ 位为 $0$ 的状态而来对应状态转移而来,即:$dp[state \oplus (1 << (k - 1))]$。 + +所以状态转移方程为:$dp[state] = \sum_{k = 1}^n dp[state \oplus (1 << (k - 1))]$ + +###### 4. 初始条件 + +- 不考虑任何数的情况下,方案数为 $1$,即:$dp[0] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当数字集合选择状态为 $state$ 时的方案数。所以最终结果为 $dp[states - 1]$,其中 $states = 1 << n$。 + +### 思路 3:代码 + +```python +class Solution: + def countArrangement(self, n: int) -> int: + states = 1 << n + dp = [0 for _ in range(states)] + dp[0] = 1 + + for state in range(states): # 枚举所有状态 + one_num = bin(state).count("1") # 计算当前状态中选择了多少个数字(即统计 1 的个数) + for k in range(1, n + 1): # 枚举最后 1 位上所选的数字 + if state >> (k - 1) & 1 == 0: # 只有 state 第 k 个位置上为 1 才表示选了该数字 + continue + if one_num % k == 0 or k % one_num == 0: # 只有满足整除关系才符合要求 + # dp[state] 由前 one_num - 1 个位置,且 state 第 k 位为 0 的状态而来 + dp[state] += dp[state ^ (1 << (k - 1))] + + return dp[states - 1] +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(2^n)$。 + +## 参考资料 + +- 【题解】[【宫水三叶】详解两种状态压缩 DP 思路 - 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/solution/gong-shui-san-xie-xiang-jie-liang-chong-vgsia/) diff --git a/docs/solutions/0500-0599/coin-change-ii.md b/docs/solutions/0500-0599/coin-change-ii.md new file mode 100644 index 00000000..e8a667f5 --- /dev/null +++ b/docs/solutions/0500-0599/coin-change-ii.md @@ -0,0 +1,93 @@ +# [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0518. 零钱兑换 II - 力扣](https://leetcode.cn/problems/coin-change-ii/) + +## 题目大意 + +**描述**:给定一个整数数组 $coins$ 表示不同面额的硬币,另给一个整数 $amount$ 表示总金额。 + +**要求**:计算并返回可以凑成总金额的硬币方案数。如果无法凑出总金额,则返回 $0$。 + +**说明**: + +- 每一种面额的硬币枚数为无限个。 +- $1 \le coins.length \le 300$。 +- $1 \le coins[i] \le 5000$。 +- $coins$ 中的所有值互不相同。 +- $0 \le amount \le 5000$。 + +**示例**: + +- 示例 1: + +```python +输入:amount = 5, coins = [1, 2, 5] +输出:4 +解释:有四种方式可以凑成总金额: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 +``` + +- 示例 2: + +```python +输入:amount = 3, coins = [2] +输出:0 +解释:只用面额 2 的硬币不能凑成总金额 3。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问凑成总金额为 $amount$ 的背包,一共有多少种方案? + +这就变成了完全背包问题。「[322. 零钱兑换](https://leetcode.cn/problems/coin-change/)」中计算的是凑成总金额的最少硬币个数,而这道题计算的是凑成总金额的方案数。 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 + +###### 3. 状态转移方程 + +凑成总金额为 $i$ 的方案数 = 「不使用当前 $coin$,只使用之前硬币凑成金额 $i$ 的方案数」+「使用当前 $coin$ 凑成金额 $i - coin$ 的方案数」。即状态转移方程为:$dp[i] = dp[i] + dp[i - coin]$。 + +###### 4. 初始条件 + +- 凑成总金额为 $0$ 的方案数为 $1$,即 $dp[0] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 所以最终结果为 $dp[amount]$。 + +### 思路 1:代码 + +```python +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + + dp = [0 for _ in range(amount + 1)] + dp[0] = 1 + for coin in coins: + for i in range(coin, amount + 1): + dp[i] += dp[i - coin] + + return dp[amount] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times amount)$,其中 $n$ 为数组 $coins$ 的元素个数,$amount$ 为总金额。 +- **空间复杂度**:$O(amount)$。 + diff --git a/docs/solutions/0500-0599/contiguous-array.md b/docs/solutions/0500-0599/contiguous-array.md new file mode 100644 index 00000000..b50acff3 --- /dev/null +++ b/docs/solutions/0500-0599/contiguous-array.md @@ -0,0 +1,68 @@ +# [0525. 连续数组](https://leetcode.cn/problems/contiguous-array/) + +- 标签:数组、哈希表、前缀和 +- 难度:中等 + +## 题目链接 + +- [0525. 连续数组 - 力扣](https://leetcode.cn/problems/contiguous-array/) + +## 题目大意 + +给定一个二进制数组 `nums`。 + +要求:找到含有相同数量 `0` 和 `1` 的最长连续子数组,并返回该子数组的长度。 + +## 解题思路 + +「`0` 和 `1` 数量相同」等价于「`1` 的数量减去 `0` 的数量等于 `0`」。 + +我们可以使用一个变量 `pre_diff` 来记录下前 `i` 个数中,`1` 的数量比 `0` 的数量多多少个。我们把这个 `pre_diff`叫做「`1` 和 `0` 数量差」,也可以理解为变种的前缀和。 + +然后我们再用一个哈希表 `pre_dic` 来记录「`1` 和 `0` 数量差」第一次出现的下标。 + +那么,如果我们在遍历的时候,发现 `pre_diff` 相同的数量差已经在之前出现过了,则说明:这两段之间相减的 `1` 和 `0` 数量差为 `0`。 + +什么意思呢? + +比如说:`j < i`,前 `j` 个数中第一次出现 `pre_diff == 2` ,然后前 `i` 个数中个第二次又出现了 `pre_diff == 2`。那么这两段形成的子数组 `nums[j + 1: i]` 中 `1` 比 `0` 多 `0` 个,则 `0` 和 `1` 数量相同的子数组长度为 `i - j`。 + +而第二次之所以又出现 `pre_diff == 2` ,是因为前半段子数组 `nums[0: j]` 贡献了相同的差值。 + +接下来还有一个小问题,如何计算「`1` 和 `0` 数量差」? + +我们可以把数组中的 `1` 记为贡献 `+1`,`0` 记为贡献 `-1`。然后使用一个变量 `count`,只要出现 `1` 就让 `count` 加上 `1`,意思是又多出了 `1` 个 `1`。只要出现 `0`,将让 `count` 减去 `1`,意思是 `0` 和之前累积的 `1` 个 `1` 相互抵消掉了。这样遍历完数组,也就计算出了对应的「`1` 和 `0` 数量差」。 + +整个思路的具体做法如下: + +- 创建一个哈希表,键值对关系为「`1` 和 `0` 的数量差:最早出现的下标 `i`」。 +- 使用变量 `pre_diff` 来计算「`1` 和 `0` 数量差」,使用变量 `count` 来记录 `0` 和 `1` 数量相同的连续子数组的最长长度,然后遍历整个数组。 +- 如果 `nums[i] == 1`,则让 `pre_diff += 1`;如果 `nums[i] == 0`,则让 `pre_diff -= 1`。 +- 如果在哈希表中发现了相同的 `pre_diff`,则计算相应的子数组长度,与 `count` 进行比较并更新 `count` 值。 +- 如果在哈希表中没有发现相同的 `pre_diff`,则在哈希表中记录下第一次出现 `pre_diff` 的下标 `i`。 +- 最后遍历完输出 `count`。 + +> 注意:初始化哈希表为:`pre_dic = {0: -1}`,意思为空数组时,默认「`1` 和 `0` 数量差」为 `0`,且第一次出现的下标为 `-1`。 +> +> 之所以这样做,是因为在遍历过程中可能会直接出现 `pre_diff == 0` 的情况,这种情况下说明 `nums[0: i]` 中 `0` 和 `1` 数量相同,如果像上边这样初始化后,就可以直接计算出此时子数组长度为 `i - (-1) = i + 1`。 + +## 代码 + +```python +class Solution: + def findMaxLength(self, nums: List[int]) -> int: + pre_dic = {0: -1} + count = 0 + pre_sum = 0 + for i in range(len(nums)): + if nums[i]: + pre_sum += 1 + else: + pre_sum -= 1 + if pre_sum in pre_dic: + count = max(count, i - pre_dic[pre_sum]) + else: + pre_dic[pre_sum] = i + return count +``` + diff --git a/docs/solutions/0500-0599/convert-bst-to-greater-tree.md b/docs/solutions/0500-0599/convert-bst-to-greater-tree.md new file mode 100644 index 00000000..21aa8ca1 --- /dev/null +++ b/docs/solutions/0500-0599/convert-bst-to-greater-tree.md @@ -0,0 +1,48 @@ +# [0538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0538. 把二叉搜索树转换为累加树 - 力扣](https://leetcode.cn/problems/convert-bst-to-greater-tree/) + +## 题目大意 + +给定一棵二叉搜索树(BST)的根节点,且二叉搜索树的节点值各不相同。要求将其转化为「累加树」,使其每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和。 + +二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +## 解题思路 + +题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 + +题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 + +二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 + +当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 + +## 代码 + +```python +class Solution: + pre = 0 + def createBinaryTree(self, root: TreeNode): + if not root: + return + self.createBinaryTree(root.right) + root.val += self.pre + self.pre = root.val + self.createBinaryTree(root.left) + + def convertBST(self, root: TreeNode) -> TreeNode: + self.pre = 0 + self.createBinaryTree(root) + return root +``` + diff --git a/docs/solutions/0500-0599/delete-operation-for-two-strings.md b/docs/solutions/0500-0599/delete-operation-for-two-strings.md new file mode 100644 index 00000000..34ec8724 --- /dev/null +++ b/docs/solutions/0500-0599/delete-operation-for-two-strings.md @@ -0,0 +1,58 @@ +# [0583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0583. 两个字符串的删除操作 - 力扣](https://leetcode.cn/problems/delete-operation-for-two-strings/) + +## 题目大意 + +给定两个单词 `word1` 和 `word2`,找到使得 `word1` 和 `word2` 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 + +## 解题思路 + +动态规划求解。 + +先定义状态 `dp[i][j]` 为以 `i - 1` 为结尾的字符串 `word1` 和以 `j - 1` 字结尾的字符串 `word2` 想要达到相等,所需要删除元素的最少次数。 + +然后确定状态转移方程。 + +- 如果 `word1[i - 1] == word2[j - 1]`,`dp[i][j]` 取源于以 `i - 2` 结尾结尾的字符串 `word1` 和以 `j - 1` 结尾的字符串 `word2`,即 `dp[i][j] = dp[i - 1][j - 1]`。 +- 如果 `word1[i - 1] != word2[j - 1]`,`dp[i][j]` 取源于以下三种情况中的最小情况: + - 删除 `word1[i - 1]`,最少操作次数为:`dp[i - 1][j] + 1`。 + - 删除 `word2[j - 1]`,最少操作次数为:`dp[i][j - 1] + 1`。 + - 同时删除 `word1[i - 1]`、`word2[j - 1]`,最少操作次数为 `dp[i - 1][j - 1] + 2`。 + +然后确定一下边界条件。 + +- 当 `word1` 为空字符串,以 `j - 1` 结尾的字符串 `word2` 要删除 `j` 个字符才能和 `word1` 相同,即 `dp[0][j] = j`。 +- 当 `word2` 为空字符串,以 `i - 1` 结尾的字符串 `word1` 要删除 `i` 个字符才能和 `word2` 相同,即 `dp[i][0] = i`。 + +最后递推求解,最终输出 `dp[size1][size2]` 为答案。 + +## 代码 + +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + size1 = len(word1) + size2 = len(word2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + + for i in range(size1 + 1): + dp[i][0] = i + for j in range(size2 + 1): + dp[0][j] = j + + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if word1[i - 1] == word2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + else: + dp[i][j] = min(dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1) + + return dp[size1][size2] +``` + diff --git a/docs/solutions/0500-0599/diameter-of-binary-tree.md b/docs/solutions/0500-0599/diameter-of-binary-tree.md new file mode 100644 index 00000000..a4c42776 --- /dev/null +++ b/docs/solutions/0500-0599/diameter-of-binary-tree.md @@ -0,0 +1,97 @@ +# [0543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0543. 二叉树的直径 - 力扣](https://leetcode.cn/problems/diameter-of-binary-tree/) + +## 题目大意 + +**描述**:给一个二叉树的根节点 $root$。 + +**要求**:计算该二叉树的直径长度。 + +**说明**: + +- **二叉树的直径长度**:二叉树中任意两个节点路径长度中的最大值。 +- 两节点之间的路径长度是以它们之间边的数目表示。 +- 这条路径可能穿过也可能不穿过根节点。 + +**示例**: + +- 示例 1: + +```python +给定二叉树: + 1 + / \ + 2 3 + / \ + 4 5 +输出:3 +解释:该二叉树的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +这道题重点是理解直径长度的定义。「二叉树的直径长度」的定义为:二叉树中任意两个节点路径长度中的最大值。并且这条路径可能穿过也可能不穿过根节点。 + +对于根为 $root$ 的二叉树来说,其直径长度并不简单等于「左子树高度」加上「右子树高度」。 + +根据路径是否穿过根节点,我们可以将二叉树分为两种: + +1. 直径长度所对应的路径穿过根节点。 +2. 直径长度所对应的路径不穿过根节点。 + +我们来看下图中的两个例子。 + +![](https://qcdn.itcharge.cn/images/20230427111005.png) + +如图所示,左侧这棵二叉树就是一棵常见的平衡二叉树,其直径长度所对应的路径是穿过根节点的($D\rightarrow B \rightarrow A \rightarrow C$)。这种情况下:$\text{二叉树的直径} = \text{左子树高度} + \text{右子树高度}$。 + +而右侧这棵特殊的二叉树,其直径长度所对应的路径是没有穿过根节点的($F \rightarrow D \rightarrow B \rightarrow E \rightarrow G$)。这种情况下:$\text{二叉树的直径} = \text{所有子树中最大直径长度}$。 + +也就是说根为 $root$ 的二叉树的直径长度可能来自于 $\text{左子树高度} + \text{右子树高度}$,也可能来自于 $\text{子树中的最大直径}$,即 $\text{二叉树的直径} = max(\text{左子树高度} + \text{右子树高度}, \quad \text{所有子树中最大直径长度})$。 + +那么现在问题就变成为如何求「子树的高度」和「子树中的最大直径」。 + +1. 子树的高度:我们可以利用深度优先搜索方法,递归遍历左右子树,并分别返回左右子树的高度。 +2. 子树中的最大直径:我们可以在递归求解子树高度的时候维护一个 $ans$ 变量,用于记录所有 $\text{左子树高度} + \text{右子树高度}$ 中的最大值。 + +最终 $ans$ 就是我们所求的该二叉树的最大直径,将其返回即可。 + +### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def __init__(self): + self.ans = 0 + + def dfs(self, node): + if not node: + return 0 + left_height = self.dfs(node.left) # 左子树高度 + right_height = self.dfs(node.right) # 右子树高度 + self.ans = max(self.ans, left_height + right_height) # 维护所有路径中的最大直径 + return max(left_height, right_height) + 1 # 返回该节点的高度 = 左右子树最大高度 + 1 + + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + self.dfs(root) + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。 + diff --git a/docs/solutions/0500-0599/distribute-candies.md b/docs/solutions/0500-0599/distribute-candies.md new file mode 100644 index 00000000..ab5acd42 --- /dev/null +++ b/docs/solutions/0500-0599/distribute-candies.md @@ -0,0 +1,32 @@ +# [0575. 分糖果](https://leetcode.cn/problems/distribute-candies/) + +- 标签:数组、哈希表 +- 难度:简单 + +## 题目链接 + +- [0575. 分糖果 - 力扣](https://leetcode.cn/problems/distribute-candies/) + +## 题目大意 + +给定一个偶数长度为 `n` 的数组,其中不同的数字代表不同种类的糖果,每一个数字代表一个糖果。 + +要求:将这些糖果按种类平均分为一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。 + +## 解题思路 + +`n` 个糖果分为两个人,每个人最多只能得到 `n // 2` 个糖果。假设糖果种数为 `m`。则如果糖果种类数大于糖果总数的一半,即 `m > n // 2`,则返回糖果数量的一半就好,也就说糖果总数一半的糖果都可以是不同种类的糖果。妹妹能获得最多 `n // 2` 种糖果。而如果让给种类数小于等于糖果总数的一半,即 `m <= n // 2`,则返回种类数,也就是说妹妹可以最多获得 `m` 种糖果。 + +综合这两种情况,其最终结果就是 `ans = min(m, n // 2)`。 + +计算糖果种类可以用 set 集合来做。 + +## 代码 + +```python +class Solution: + def distributeCandies(self, candyType: List[int]) -> int: + candy_set = set(candyType) + return min(len(candyType) // 2, len(candy_set)) +``` + diff --git a/docs/solutions/0500-0599/fibonacci-number.md b/docs/solutions/0500-0599/fibonacci-number.md new file mode 100644 index 00000000..bd7e4e67 --- /dev/null +++ b/docs/solutions/0500-0599/fibonacci-number.md @@ -0,0 +1,115 @@ +# [0509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) + +- 标签:递归、记忆化搜索、数学、动态规划 +- 难度:简单 + +## 题目链接 + +- [0509. 斐波那契数 - 力扣](https://leetcode.cn/problems/fibonacci-number/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算第 $n$ 个斐波那契数。 + +**说明**: + +- 斐波那契数列的定义如下: + - $f(0) = 0, f(1) = 1$。 + - $f(n) = f(n - 1) + f(n - 2)$,其中 $n > 1$。 +- $0 \le n \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:1 +解释:F(2) = F(1) + F(0) = 1 + 0 = 1 +``` + +- 示例 2: + +```python +输入:n = 3 +输出:2 +解释:F(3) = F(2) + F(1) = 1 + 1 = 2 +``` + +## 解题思路 + +### 思路 1:递归算法 + +根据我们的递推三步走策略,写出对应的递归代码。 + +1. 写出递推公式:$f(n) = f(n - 1) + f(n - 2)$。 +2. 明确终止条件:$f(0) = 0, f(1) = 1$。 +3. 翻译为递归代码: + 1. 定义递归函数:`fib(self, n)` 表示输入参数为问题的规模 $n$,返回结果为第 $n$ 个斐波那契数。 + 2. 书写递归主体:`return self.fib(n - 1) + self.fib(n - 2)`。 + 3. 明确递归终止条件: + 1. `if n == 0: return 0` + 2. `if n == 1: return 1` + +### 思路 1:代码 + +```python +class Solution: + def fib(self, n: int) -> int: + if n == 0: + return 0 + if n == 1: + return 1 + return self.fib(n - 1) + self.fib(n - 2) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O((\frac{1 + \sqrt{5}}{2})^n)$。具体证明方法参考 [递归求斐波那契数列的时间复杂度,不要被网上的答案误导了 - 知乎](https://zhuanlan.zhihu.com/p/256344121)。 +- **空间复杂度**:$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。 + +### 思路 2:动态规划算法 + +###### 1. 划分阶段 + +我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 为:第 $i$ 个斐波那契数。 + +###### 3. 状态转移方程 + +根据题目中所给的斐波那契数列的定义 $f(n) = f(n - 1) + f(n - 2)$,则直接得出状态转移方程为 $dp[i] = dp[i - 1] + dp[i - 2]$。 + +###### 4. 初始条件 + +根据题目中所给的初始条件 $f(0) = 0, f(1) = 1$ 确定动态规划的初始条件,即 $dp[0] = 0, dp[1] = 1$。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 $dp[n]$,即第 $n$ 个斐波那契数为 $dp[n]$。 + +### 思路 2:代码 + +```python +class Solution: + def fib(self, n: int) -> int: + if n <= 1: + return n + + dp = [0 for _ in range(n + 1)] + dp[0] = 0 + dp[1] = 1 + for i in range(2, n + 1): + dp[i] = dp[i - 2] + dp[i - 1] + + return dp[n] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。因为 $dp[i]$ 的状态只依赖于 $dp[i - 1]$ 和 $dp[i - 2]$,所以可以使用 $3$ 个变量来分别表示 $dp[i]$、$dp[i - 1]$、$dp[i - 2]$,从而将空间复杂度优化到 $O(1)$。 diff --git a/docs/solutions/0500-0599/find-bottom-left-tree-value.md b/docs/solutions/0500-0599/find-bottom-left-tree-value.md new file mode 100644 index 00000000..d255834e --- /dev/null +++ b/docs/solutions/0500-0599/find-bottom-left-tree-value.md @@ -0,0 +1,65 @@ +# [0513. 找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0513. 找树左下角的值 - 力扣](https://leetcode.cn/problems/find-bottom-left-tree-value/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:找出该二叉树 「最底层」的「最左边」节点的值。 + +**说明**: + +- 假设二叉树中至少有一个节点。 +- 二叉树的节点个数的范围是 $[1,10^4]$。 +- $-2^{31} \le Node.val \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:[1,2,3,4,null,5,6,null,null,7] +输出:7 +``` + +![](https://assets.leetcode.com/uploads/2020/12/14/tree2.jpg) + +## 解题思路 + +### 思路 1:层序遍历 + +这个问题可以拆分为两个问题: + +1. 如何找到「最底层」。 +2. 在「最底层」如何找到最左边的节点。 + +第一个问题,我们可以通过层序遍历直接确定最底层节点。而第二个问题可以通过改变层序遍历的左右节点访问顺序从而找到「最底层」的「最左边节点」。具体方法如下: + +1. 对二叉树进行层序遍历。每层元素先访问右节点,再访问左节点。 +2. 当遍历到最后一个元素时,此时最后一个元素就是「最底层」的「最左边」节点,即左下角的节点,将该节点的值返回即可。 + +### 思路 1:层序遍历代码 + +```python +import collections +class Solution: + def findBottomLeftValue(self, root: TreeNode) -> int: + if not root: + return -1 + queue = collections.deque() + queue.append(root) + while queue: + cur = queue.popleft() + if cur.right: + queue.append(cur.right) + if cur.left: + queue.append(cur.left) + return cur.val +``` + diff --git a/docs/solutions/0500-0599/find-largest-value-in-each-tree-row.md b/docs/solutions/0500-0599/find-largest-value-in-each-tree-row.md new file mode 100644 index 00000000..990f2ae2 --- /dev/null +++ b/docs/solutions/0500-0599/find-largest-value-in-each-tree-row.md @@ -0,0 +1,25 @@ +# [0515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0515. 在每个树行中找最大值 - 力扣](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:找出二叉树中每一层的最大值。 + +## 解题思路 + +利用队列进行层序遍历,并记录下每一层的最大值,将其存入答案数组中。 + +## 代码 + +```python + +``` + diff --git a/docs/solutions/0500-0599/find-mode-in-binary-search-tree.md b/docs/solutions/0500-0599/find-mode-in-binary-search-tree.md new file mode 100644 index 00000000..b4d59cc3 --- /dev/null +++ b/docs/solutions/0500-0599/find-mode-in-binary-search-tree.md @@ -0,0 +1,80 @@ +# [0501. 二叉搜索树中的众数](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [0501. 二叉搜索树中的众数 - 力扣](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) + +## 题目大意 + +给定一个有相同值的二叉搜索树(BST),要求找出 BST 中所有众数(出现频率最高的元素)。 + +二叉搜索树定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +## 解题思路 + +中序递归遍历二叉搜索树所得到的结果是一个有序数组,所以问题就变为了如何统计有序数组的众数。 + +定义几个变量。`count` 用来统计当前元素值对应的节点个数,`max_count` 用来元素出现次数最多的次数。数组 `res` 用来存储所有众数结果(因为众数可能不止一个)。 + +因为中序递归遍历二叉树,比较的元素肯定是相邻节点,所以需要再使用一个变量 `pre` 来指向前一节点。下面就开始愉快的递归了。 + +- 如果当前节点为空,直接返回。 +- 递归遍历左子树。 +- 比较当前节点和前一节点: + - 如果前一节点为空,则当前元素频率赋值为 1。 + - 如果前一节点值与当前节点值相同,则当前元素频率 + 1。 + - 如果前一节点值与当前节点值不同,则重新计算当前元素频率,将当前元素频率赋值为 1。 +- 判断当前元素频率和最高频率关系: + - 如果当前元素频率和最高频率值相等,则将对应元素值加入 res 数组。 + - 如果当前元素频率大于最高频率值,则更新最高频率值,并清空原 res 数组,将当前元素加入 res 数组。 +- 递归遍历右子树。 + +最终得到的 res 数组即为所求的众数。 + +## 代码 + +```python +class Solution: + res = [] + count = 0 + max_count = 0 + pre = None + def search(self, cur: TreeNode): + if not cur: + return + self.search(cur.left) + if not self.pre: + self.count = 1 + elif self.pre.val == cur.val: + self.count += 1 + else: + self.count = 1 + + self.pre = cur + + if self.count == self.max_count: + self.res.append(cur.val) + elif self.count > self.max_count: + self.max_count = self.count + self.res.clear() + self.res.append(cur.val) + + self.search(cur.right) + return + + def findMode(self, root: TreeNode) -> List[int]: + self.count = 0 + self.max_count = 0 + self.res.clear() + self.pre = None + self.search(root) + return self.res +``` + diff --git a/docs/solutions/0500-0599/index.md b/docs/solutions/0500-0599/index.md new file mode 100644 index 00000000..04bdf8a7 --- /dev/null +++ b/docs/solutions/0500-0599/index.md @@ -0,0 +1,30 @@ +## 本章内容 + +- [0501. 二叉搜索树中的众数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-mode-in-binary-search-tree.md) +- [0503. 下一个更大元素 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/next-greater-element-ii.md) +- [0504. 七进制数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/base-7.md) +- [0506. 相对名次](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/relative-ranks.md) +- [0509. 斐波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/fibonacci-number.md) +- [0513. 找树左下角的值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-bottom-left-tree-value.md) +- [0515. 在每个树行中找最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/find-largest-value-in-each-tree-row.md) +- [0516. 最长回文子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/longest-palindromic-subsequence.md) +- [0518. 零钱兑换 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/coin-change-ii.md) +- [0525. 连续数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/contiguous-array.md) +- [0526. 优美的排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/beautiful-arrangement.md) +- [0530. 二叉搜索树的最小绝对差](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-absolute-difference-in-bst.md) +- [0538. 把二叉搜索树转换为累加树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/convert-bst-to-greater-tree.md) +- [0539. 最小时间差](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-time-difference.md) +- [0542. 01 矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/01-matrix.md) +- [0543. 二叉树的直径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/diameter-of-binary-tree.md) +- [0546. 移除盒子](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/remove-boxes.md) +- [0547. 省份数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/number-of-provinces.md) +- [0557. 反转字符串中的单词 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md) +- [0560. 和为 K 的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/subarray-sum-equals-k.md) +- [0561. 数组拆分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/array-partition.md) +- [0567. 字符串的排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/permutation-in-string.md) +- [0575. 分糖果](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/distribute-candies.md) +- [0576. 出界的路径数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/out-of-boundary-paths.md) +- [0583. 两个字符串的删除操作](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/delete-operation-for-two-strings.md) +- [0589. N 叉树的前序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md) +- [0590. N 叉树的后序遍历](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md) +- [0599. 两个列表的最小索引总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md) diff --git a/docs/solutions/0500-0599/longest-palindromic-subsequence.md b/docs/solutions/0500-0599/longest-palindromic-subsequence.md new file mode 100644 index 00000000..7249c736 --- /dev/null +++ b/docs/solutions/0500-0599/longest-palindromic-subsequence.md @@ -0,0 +1,99 @@ +# [0516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0516. 最长回文子序列 - 力扣](https://leetcode.cn/problems/longest-palindromic-subsequence/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中最长的回文子序列,并返回该序列的长度。 + +**说明**: + +- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 +- $1 \le s.length \le 1000$。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "bbbab" +输出:4 +解释:一个可能的最长回文子序列为 "bbbb"。 +``` + +- 示例 2: + +```python +输入:s = "cbbd" +输出:2 +解释:一个可能的最长回文子序列为 "bb"。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 + +###### 3. 状态转移方程 + +我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: + +1. 如果 $s[i] = s[j]$,则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + $2$,即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 +2. 如果 $s[i] \ne s[j]$,则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: + 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 + 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 + +则状态转移方程为: + +$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ + +###### 4. 初始条件 + +- 单个字符的最长回文序列是 $1$,即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1]$,所以我们应该按照从下到上、从左到右的顺序进行遍历。 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def longestPalindromeSubseq(self, s: str) -> int: + size = len(s) + dp = [[0 for _ in range(size)] for _ in range(size)] + for i in range(size): + dp[i][i] = 1 + + for i in range(size - 1, -1, -1): + for j in range(i + 1, size): + if s[i] == s[j]: + dp[i][j] = dp[i + 1][j - 1] + 2 + else: + dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) + + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0500-0599/minimum-absolute-difference-in-bst.md b/docs/solutions/0500-0599/minimum-absolute-difference-in-bst.md new file mode 100644 index 00000000..89abd0de --- /dev/null +++ b/docs/solutions/0500-0599/minimum-absolute-difference-in-bst.md @@ -0,0 +1,93 @@ +# [0530. 二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 +- 难度: + +## 题目链接 + +- [0530. 二叉搜索树的最小绝对差 - 力扣](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) + +## 题目大意 + +**描述**:给定一个二叉搜索树的根节点 $root$。 + +**要求**:返回树中任意两不同节点值之间的最小差值。 + +**说明**: + +- **差值**:是一个正数,其数值等于两值之差的绝对值。 +- 树中节点的数目范围是 $[2, 10^4]$。 +- $0 \le Node.val \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/05/bst1.jpg) + +```python +输入:root = [4,2,6,1,3] +输出:1 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/05/bst2.jpg) + +```python +输入:root = [1,0,48,null,null,12,49] +输出:1 +``` + +## 解题思路 + +### 思路 1:中序遍历 + +先来看二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +题目要求二叉搜索树上任意两节点的差的绝对值的最小值。 + +二叉树的中序遍历顺序是:左 -> 根 -> 右,二叉搜索树的中序遍历最终得到就是一个升序数组。而升序数组中绝对值差的最小值就是比较相邻两节点差值的绝对值,找出其中最小值。 + +那么我们就可以先对二叉搜索树进行中序遍历,并保存中序遍历的结果。然后再比较相邻节点差值的最小值,从而找出最小值。 + +### 思路 1:代码 + +```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def inorder(root): + if not root: + return + inorder(root.left) + res.append(root.val) + inorder(root.right) + + inorder(root) + return res + + def getMinimumDifference(self, root: Optional[TreeNode]) -> int: + inorder = self.inorderTraversal(root) + ans = float('inf') + for i in range(1, len(inorder)): + ans = min(ans, abs(inorder[i - 1] - inorder[i])) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉搜索树中的节点数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md b/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md new file mode 100644 index 00000000..e7bae488 --- /dev/null +++ b/docs/solutions/0500-0599/minimum-index-sum-of-two-lists.md @@ -0,0 +1,45 @@ +# [0599. 两个列表的最小索引总和](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) + +- 标签:数组、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0599. 两个列表的最小索引总和 - 力扣](https://leetcode.cn/problems/minimum-index-sum-of-two-lists/) + +## 题目大意 + +Andy 和 Doris 都有一个表示最喜欢餐厅的列表 list1、list2,每个餐厅的名字用字符串表示。 + +找出他们共同喜爱的餐厅,要求两个餐厅在列表中的索引和最小,如果答案不唯一,则输出所有答案。 + +## 解题思路 + +遍历 list1,建立一个哈希表 list1_dict,以 list1[i] : i 键值对的方式,将 list1 的下标存储起来。 + +然后遍历 list2,判断 list2[i] 是否在哈希表中,如果在,则根据 i + list1_dict[i] 和 min_sum 的比较,判断是否需要更新最小索引和。如果 i + list1_dict[i] < min_sum,则更新最小索引和,并清空答案数据,添加新的答案。如果 i + list1_dict[i] == min_sum,则更新最小索引和,并添加答案。 + +## 代码 + +```python +class Solution: + def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]: + list1_dict = dict() + len1 = len(list1) + len2 = len(list2) + for i in range(len1): + list1_dict[list1[i]] = i + + min_sum = len1 + len2 + res = [] + for i in range(len2): + if list2[i] in list1_dict: + sum = i + list1_dict[list2[i]] + if sum < min_sum: + res = [list2[i]] + min_sum = sum + elif sum == min_sum: + res.append(list2[i]) + return res +``` + diff --git a/docs/solutions/0500-0599/minimum-time-difference.md b/docs/solutions/0500-0599/minimum-time-difference.md new file mode 100644 index 00000000..a1b851cd --- /dev/null +++ b/docs/solutions/0500-0599/minimum-time-difference.md @@ -0,0 +1,42 @@ +# [0539. 最小时间差](https://leetcode.cn/problems/minimum-time-difference/) + +- 标签:数组、数学、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [0539. 最小时间差 - 力扣](https://leetcode.cn/problems/minimum-time-difference/) + +## 题目大意 + +给定一个 24 小时制形式(小时:分钟 "HH:MM")的时间列表 `timePoints`。 + +要求:找出列表中任意两个时间的最小时间差并以分钟数表示。 + +## 解题思路 + +- 遍历时间列表 `timePoints`,将每个时间转换为以分钟计算的整数形式,比如时间 `14:20`,将其转换为 `14 * 60 + 20 = 860`,存放到新的时间列表 `times` 中。 +- 为了处理最早时间、最晚时间之间的时间间隔,我们将 `times` 中最小时间添加到列表末尾一起进行排序。 +- 然后将新的时间列表 `times` 按照升序排列。 +- 遍历排好序的事件列表 `times` ,找出相邻两个时间的最小间隔值即可。 + +## 代码 + +```python +class Solution: + def changeTime(self, timePoint: str): + hours, minutes = timePoint.split(':') + return int(hours) * 60 + int(minutes) + + def findMinDifference(self, timePoints: List[str]) -> int: + if not timePoints or len(timePoints) > 24 * 60: + return 0 + + times = sorted(self.changeTime(time) for time in timePoints) + times.append(times[0] + 24 * 60) + res = times[-1] + for i in range(1, len(times)): + res = min(res, times[i] - times[i - 1]) + return res +``` + diff --git a/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md b/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md new file mode 100644 index 00000000..b8707f01 --- /dev/null +++ b/docs/solutions/0500-0599/n-ary-tree-postorder-traversal.md @@ -0,0 +1,51 @@ +# [0590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) + +- 标签:栈、树、深度优先搜索 +- 难度:简单 + +## 题目链接 + +- [0590. N 叉树的后序遍历 - 力扣](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) + +## 题目大意 + +给定一个 N 叉树的根节点 `root`。 + +要求:返回其节点值的后序遍历。 + +## 解题思路 + +N 叉树的后序遍历顺序为:子节点顺序递归遍历 -> 根节点。 + +一个取巧的方法是先按照:根节点 -> 子节点逆序递归遍历 的顺序将遍历顺序存储到答案数组。 + +然后再将其进行翻转就变为了后序遍历顺序。具体操作如下: + +- 用栈保存根节点 `root`。然后遍历栈。 +- 循环判断栈是否为空。 +- 如果栈不为空,取出栈顶节点,将节点值加入答案数组。 +- 顺序遍历栈顶节点的子节点,将其依次放入栈中(顺序遍历保证取出顺序为逆序)。 +- 然后继续第 2 ~ 4 步,直到栈为空。 + +最后将答案数组逆序返回。 + +## 代码 + +```python +class Solution: + def postorder(self, root: 'Node') -> List[int]: + res = [] + stack = [] + if not root: + return res + + stack.append(root) + while stack: + node = stack.pop() + res.append(node.val) + for child in node.children: + stack.append(child) + + return res[::-1] +``` + diff --git a/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md b/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md new file mode 100644 index 00000000..5b0bd2ce --- /dev/null +++ b/docs/solutions/0500-0599/n-ary-tree-preorder-traversal.md @@ -0,0 +1,48 @@ +# [0589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) + +- 标签:栈、树、深度优先搜索 +- 难度:简单 + +## 题目链接 + +- [0589. N 叉树的前序遍历 - 力扣](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) + +## 题目大意 + +给定一棵 N 叉树的根节点 `root`。 + +要求:返回其节点值的前序遍历。 + +进阶:使用迭代法完成。 + +## 解题思路 + +递归法很好写。迭代法需要借助于栈。 + +- 用栈保存根节点 `root`。然后遍历栈。 +- 循环判断栈是否为空。 +- 如果栈不为空,取出栈顶节点,将节点值加入答案数组。 +- 逆序遍历栈顶节点的子节点,将其依次放入栈中(逆序保证取出顺序为正)。 +- 然后继续第 2 ~ 4 步,直到栈为空。 + +最后输出答案数组。 + +## 代码 + +```python +class Solution: + def preorder(self, root: 'Node') -> List[int]: + res = [] + stack = [] + if not root: + return res + stack.append(root) + while stack: + node = stack.pop() + res.append(node.val) + for i in range(len(node.children) - 1, -1, -1): + if node.children[i]: + stack.append(node.children[i]) + return res +``` + diff --git a/docs/solutions/0500-0599/next-greater-element-ii.md b/docs/solutions/0500-0599/next-greater-element-ii.md new file mode 100644 index 00000000..0c1f1a05 --- /dev/null +++ b/docs/solutions/0500-0599/next-greater-element-ii.md @@ -0,0 +1,49 @@ +# [0503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) + +- 标签:栈、数组、单调栈 +- 难度:中等 + +## 题目链接 + +- [0503. 下一个更大元素 II - 力扣](https://leetcode.cn/problems/next-greater-element-ii/) + +## 题目大意 + +给定一个循环数组 `nums`(最后一个元素的下一个元素是数组的第一个元素)。 + +要求:输出每个元素的下一个更大元素。如果不存在,则输出 `-1`。 + +- 数字 `x` 的下一个更大的元素:按数组遍历顺序,这个数字之后的第一个比它更大的数。这意味着你应该循环地搜索它的下一个更大的数。 + +## 解题思路 + +第一种思路是根据题意直接暴力求解。遍历 `nums` 中的每一个元素。对于 `nums` 的每一个元素 `nums[i]`,查找 `nums[i]` 右边第一个比 `nums1[i]` 大的元素。这种解法的时间复杂度是 $O(n^2)$。 + +第二种思路是使用单调递增栈。遍历数组 `nums`,构造单调递增栈,求出 `nums` 中每个元素右侧下一个更大的元素。然后将其存储到答案数组中。这种解法的时间复杂度是 $O(n)$。 + +而循环数组的求解方法可以将 `nums` 复制一份到末尾,生成长度为 `len(nums) * 2` 的数组,或者通过取模运算将下标映射到 `0` ~ `len(nums) * 2 - 1` 之间。 + +具体做法如下: + +- 使用数组 `res` 存放答案,初始值都赋值为 `-1`。使用变量 `stack` 表示单调递增栈。 +- 遍历数组 `nums`,对于当前元素: + - 如果当前元素值小于栈顶元素,则说明当前元素「下一个更大元素」与栈顶元素的「下一个更大元素」相同。应该直接让当前元素的下标入栈。 + - 如果当前元素值大于栈顶元素,则说明当前元素是之前元素的「下一个更大元素」,则不断将栈顶元素出栈。直到当前元素值小于栈顶元素值。 + - 出栈时,出栈元素的「下一个更大元素」是当前元素。则将当前元素值存入到答案数组 `res` 中出栈元素所对应的位置中。 +- 最终输出答案数组 `res`。 + +## 代码 + +```python +size = len(nums) + res = [-1 for _ in range(size)] + stack = [] + for i in range(size * 2): + while stack and nums[i % size] > nums[stack[-1]]: + index = stack.pop() + res[index] = nums[i % size] + stack.append(i % size) + + return res +``` + diff --git a/docs/solutions/0500-0599/number-of-provinces.md b/docs/solutions/0500-0599/number-of-provinces.md new file mode 100644 index 00000000..e8623664 --- /dev/null +++ b/docs/solutions/0500-0599/number-of-provinces.md @@ -0,0 +1,98 @@ +# [0547. 省份数量](https://leetcode.cn/problems/number-of-provinces/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0547. 省份数量 - 力扣](https://leetcode.cn/problems/number-of-provinces/) + +## 题目大意 + +**描述**:有 `n` 个城市,其中一些彼此相连,另一些没有相连。如果城市 `a` 与城市 `b` 直接相连,且城市 `b` 与城市 `c` 直接相连,那么城市 `a` 与城市 `c` 间接相连。 + +「省份」是由一组直接或间接链接的城市组成,组内不含有其他没有相连的城市。 + +现在给定一个 `n * n` 的矩阵 `isConnected` 表示城市的链接关系。其中 `isConnected[i][j] = 1` 表示第 `i` 个城市和第 `j` 个城市直接相连,`isConnected[i][j] = 0` 表示第 `i` 个城市和第 `j` 个城市没有相连。 + +**要求**:根据给定的城市关系,返回「省份」的数量。 + +**说明**: + +- $1 \le n \le 200$。 +- $n == isConnected.length$。 +- $n == isConnected[i].length$。 +- $isConnected[i][j]$ 为 $1$ 或 $0$。 +- $isConnected[i][i] == 1$。 +- $isConnected[i][j] == isConnected[j][i]$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/12/24/graph1.jpg) + +```python +输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] +输出:2 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/12/24/graph2.jpg) + +```python +输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] +输出:3 +``` + +## 解题思路 + +### 思路 1:并查集 + +1. 遍历矩阵 `isConnected`。如果 `isConnected[i][j] == 1`,将 `i` 节点和 `j` 节点相连。 +2. 然后判断每个城市节点的根节点,然后统计不重复的根节点有多少个,即为「省份」的数量。 + +### 思路 1:代码 + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + + def find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.find(x) == self.find(y) + +class Solution: + def findCircleNum(self, isConnected: List[List[int]]) -> int: + size = len(isConnected) + union_find = UnionFind(size) + for i in range(size): + for j in range(i + 1, size): + if isConnected[i][j] == 1: + union_find.union(i, j) + + res = set() + for i in range(size): + res.add(union_find.find(i)) + return len(res) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times \alpha(n))$。其中 $n$ 是城市的数量,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0500-0599/out-of-boundary-paths.md b/docs/solutions/0500-0599/out-of-boundary-paths.md new file mode 100644 index 00000000..bb6b83ca --- /dev/null +++ b/docs/solutions/0500-0599/out-of-boundary-paths.md @@ -0,0 +1,140 @@ +# [0576. 出界的路径数](https://leetcode.cn/problems/out-of-boundary-paths/) + +- 标签:动态规划 +- 难度:中等 + +## 题目链接 + +- [0576. 出界的路径数 - 力扣](https://leetcode.cn/problems/out-of-boundary-paths/) + +## 题目大意 + +**描述**:有一个大小为 $m \times n$ 的网络和一个球。球的起始位置为 $(startRow, startColumn)$。你可以将球移到在四个方向上相邻的单元格内(可以穿过网格边界到达网格之外)。最多可以移动 $maxMove$ 次球。 + +现在给定五个整数 $m$、$n$、$maxMove$、$startRow$ 以及 $startColumn$。 + +**要求**:找出并返回可以将球移出边界的路径数量。因为答案可能非常大,返回对 $10^9 + 7$ 取余后的结果。 + +**说明**: + +- $1 \le m, n \le 50$。 +- $0 \le maxMove \le 50$。 +- $0 \le startRow < m$。 +- $0 \le startColumn < n$。 + +**示例**: + +- 示例 1: + +```python +输入:m = 2, n = 2, maxMove = 2, startRow = 0, startColumn = 0 +输出:6 +``` + +![](https://assets.leetcode.com/uploads/2021/04/28/out_of_boundary_paths_1.png) + +## 解题思路 + +### 思路 1:记忆化搜索 + +1. 问题的状态定义为:从位置 $(i, j)$ 出发,最多使用 $moveCount$ 步,可以将球移出边界的路径数量。 +2. 定义一个 $m \times n \times (maxMove + 1)$ 的三维数组 $memo$ 用于记录已经计算过的路径数量。 +3. 定义递归函数 $dfs(i, j, moveCount)$ 用于计算路径数量。 + 1. 如果 $(i, j)$ 已经出界,则说明找到了一条路径,返回方案数为 $1$。 + 2. 如果没有移动次数了,则返回方案数为 $0$。 + 3. 定义方案数 $ans$,遍历四个方向,递归计算四个方向的方案数,累积到 $ans$ 中,并进行取余。 + 4. 返回方案数 $ans$。 +4. 调用递归函数 $dfs(startRow, startColumn, maxMove)$,并将其返回值作为答案进行返回。 + +### 思路 1:代码 + +```python +class Solution: + def findPaths(self, m: int, n: int, maxMove: int, startRow: int, startColumn: int) -> int: + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + mod = 10 ** 9 + 7 + + memo = [[[-1 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] + + def dfs(i, j, moveCount): + if i < 0 or i >= m or j < 0 or j >= n: + return 1 + + if moveCount == 0: + return 0 + + if memo[i][j][moveCount] != -1: + return memo[i][j][moveCount] + + ans = 0 + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + ans += dfs(new_i, new_j, moveCount - 1) + ans %= mod + + memo[i][j][moveCount] = ans + return ans + + return dfs(startRow, startColumn, maxMove) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n \times maxMove)$。 +- **空间复杂度**:$O(m \times n \times maxMove)$。 + +### 思路 2:动态规划 + +我们需要统计从 $(startRow, startColumn)$ 位置出发,最多移动 $maxMove$ 次能够穿过边界的所有路径数量。则我们可以根据位置和移动步数来划分阶段和定义状态。 + +###### 1. 划分阶段 + +按照位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j][k]$ 表示为:从位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量。 + +###### 3. 状态转移方程 + +因为球可以在上下左右四个方向上进行移动,所以对于位置 $(i, j)$,最多移动 $k$ 次最终穿过边界的所有路径数量取决于周围四个方向上最多经过 $k - 1$ 次穿过对应位置上的所有路径数量和。 + +即:$dp[i][j][k] = dp[i - 1][j][k - 1] + dp[i + 1][j][k - 1] + dp[i][j - 1][k - 1] + dp[i][j + 1][k - 1]$。 + +###### 4. 初始条件 + +如果位置 $[i, j]$ 已经处于边缘,只差一步就穿过边界。则此时位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量取决于有相邻多少个方向是边界。也可以通过对上面 $(i - 1, j)$、$(i + 1, j)$、$(i, j - 1)$、$(i, j + 1)$ 是否已经穿过边界进行判断(每一个方向穿过一次,就累积一次),来计算路径数目。然后将其作为初始条件。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j][k]$ 表示为:从位置 $(i, j)$ 最多移动 $k$ 次最终穿过边界的所有路径数量。则最终答案为 $dp[startRow][startColumn][maxMove]$。 + +### 思路 2:动态规划代码 + +```python +class Solution: + def findPaths(self, m: int, n: int, maxMove: int, startRow: int, startColumn: int) -> int: + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + mod = 10 ** 9 + 7 + + dp = [[[0 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] + for i in r + for k in range(1, maxMove + 1): + for i in range(m): + for j in range(n): + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + if 0 <= new_i < m and 0 <= new_j < n: + dp[i][j][k] = (dp[i][j][k] + dp[new_i][new_j][k - 1]) % mod + else: + dp[i][j][k] = (dp[i][j][k] + 1) % mod + + return dp[startRow][startColumn][maxMove] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(m \times n \times maxMove)$。三重循环遍历的时间复杂度为 $O(m \times n \times maxMove)$。 +- **空间复杂度**:$O(m \times n \times maxMove)$。使用了三维数组保存状态,所以总体空间复杂度为 $O(m \times n \times maxMove)$。 diff --git a/docs/solutions/0500-0599/permutation-in-string.md b/docs/solutions/0500-0599/permutation-in-string.md new file mode 100644 index 00000000..bf8a5e05 --- /dev/null +++ b/docs/solutions/0500-0599/permutation-in-string.md @@ -0,0 +1,85 @@ +# [0567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) + +- 标签:哈希表、双指针、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0567. 字符串的排列 - 力扣](https://leetcode.cn/problems/permutation-in-string/) + +## 题目大意 + +**描述**:给定两个字符串 $s1$ 和 $s2$ 。 + +**要求**:判断 $s2$ 是否包含 $s1$ 的排列。如果包含,返回 $True$;否则,返回 $False$。 + +**说明**: + +- $1 \le s1.length, s2.length \le 10^4$。 +- $s1$ 和 $s2$ 仅包含小写字母。 + +**示例**: + +- 示例 1: + +```python +输入:s1 = "ab" s2 = "eidbaooo" +输出:true +解释:s2 包含 s1 的排列之一 ("ba"). +``` + +- 示例 2: + +```python +输入:s1= "ab" s2 = "eidboaoo" +输出:False +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +题目要求判断 $s2$ 是否包含 $s1$ 的排列,则 $s2$ 的子串长度等于 $s1$ 的长度。我们可以维护一个长度为字符串 $s1$ 长度的固定长度的滑动窗口。 + +先统计出字符串 $s1$ 中各个字符的数量,我们用 $s1\underline{\hspace{0.5em}}count$ 来表示。这个过程可以用字典、数组来实现,也可以直接用 `collections.Counter()` 实现。再统计 $s2$ 对应窗口内的字符数量 $window\underline{\hspace{0.5em}}count$,然后不断向右滑动,然后进行比较。如果对应字符数量相同,则返回 $True$,否则继续滑动。直到末尾时,返回 $False$。整个解题步骤具体如下: + +1. $s1\underline{\hspace{0.5em}}count$ 用来统计 $s1$ 中各个字符数量。$window\underline{\hspace{0.5em}}count$ 用来维护窗口中 $s2$ 对应子串的各个字符数量。$window\underline{\hspace{0.5em}}size$ 表示固定窗口的长度,值为 $len(s1)$。 +2. 先统计出 $s1$ 中各个字符数量。 +3. $left$ 、$right$ 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +4. 向右移动 $right$,先将 $len(s1)$ 个元素填入窗口中。 +5. 当窗口元素个数为 $window\underline{\hspace{0.5em}}size$ 时,即:$right - left + 1 \ge window\underline{\hspace{0.5em}}size$ 时,判断窗口内各个字符数量 $window\underline{\hspace{0.5em}}count$ 是否等于 $s1 $ 中各个字符数量 $s1\underline{\hspace{0.5em}}count$。 + 1. 如果等于,直接返回 $True$。 + 2. 如果不等于,则向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $window\underline{\hspace{0.5em}}size$。 +6. 重复 $4 \sim 5$ 步,直到 $right$ 到达数组末尾。返回 $False$。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def checkInclusion(self, s1: str, s2: str) -> bool: + left, right = 0, 0 + s1_count = collections.Counter(s1) + window_count = collections.Counter() + window_size = len(s1) + + while right < len(s2): + window_count[s2[right]] += 1 + + if right - left + 1 >= window_size: + if window_count == s1_count: + return True + window_count[s2[left]] -= 1 + if window_count[s2[left]] == 0: + del window_count[s2[left]] + left += 1 + right += 1 + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m + |\sum|)$,其中 $n$、$m$ 分别是字符串 $s1$、$s2$ 的长度,$\sum$ 是字符集,本题中 $|\sum| = 26$。 +- **空间复杂度**:$O(|\sum|)$。 + diff --git a/docs/solutions/0500-0599/relative-ranks.md b/docs/solutions/0500-0599/relative-ranks.md new file mode 100644 index 00000000..9206e6ae --- /dev/null +++ b/docs/solutions/0500-0599/relative-ranks.md @@ -0,0 +1,90 @@ +# [0506. 相对名次](https://leetcode.cn/problems/relative-ranks/) + +- 标签:数组、排序、堆(优先队列) +- 难度:简单 + +## 题目链接 + +- [0506. 相对名次 - 力扣](https://leetcode.cn/problems/relative-ranks/) + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的数组 $score$。其中 $score[i]$ 表示第 $i$ 名运动员在比赛中的成绩。所有成绩互不相同。 + +**要求**:找出他们的相对名次,并授予前三名对应的奖牌。前三名运动员将会被分别授予「金牌(`"Gold Medal"`)」,「银牌(`"Silver Medal"`)」和「铜牌(`"Bronze Medal"`)」。 + +**说明**: + +- $n == score.length$。 +- $1 \le n \le 10^4$。 +- $0 \le score[i] \le 10^6$。 +- $score$ 中的所有值互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:score = [5,4,3,2,1] +输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"] +解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。 +``` + +- 示例 2: + +```python +输入:score = [10,3,8,9,4] +输出:["Gold Medal","5","Bronze Medal","Silver Medal","4"] +解释:名次为 [1st, 5th, 3rd, 2nd, 4th] 。 +``` + +## 解题思路 + +### 思路 1:排序 + +1. 先对数组 $score$ 进行排序。 +2. 再将对应前三个位置上的元素替换成对应的字符串:`"Gold Medal"`, `"Silver Medal"`, `"Bronze Medal"`。 + +### 思路 1:代码 + +```python +class Solution: + def shellSort(self, arr): + size = len(arr) + gap = size // 2 + + while gap > 0: + for i in range(gap, size): + temp = arr[i] + j = i + while j >= gap and arr[j - gap] < temp: + arr[j] = arr[j - gap] + j -= gap + arr[j] = temp + gap = gap // 2 + return arr + + def findRelativeRanks(self, score: List[int]) -> List[str]: + nums = score.copy() + nums = self.shellSort(nums) + score_map = dict() + for i in range(len(nums)): + score_map[nums[i]] = i + 1 + + res = [] + for i in range(len(score)): + if score[i] == nums[0]: + res.append("Gold Medal") + elif score[i] == nums[1]: + res.append("Silver Medal") + elif score[i] == nums[2]: + res.append("Bronze Medal") + else: + res.append(str(score_map[score[i]])) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。因为采用了时间复杂度为 $O(n \times \log n)$ 的希尔排序。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0500-0599/remove-boxes.md b/docs/solutions/0500-0599/remove-boxes.md new file mode 100644 index 00000000..5464b36f --- /dev/null +++ b/docs/solutions/0500-0599/remove-boxes.md @@ -0,0 +1,110 @@ +# [0546. 移除盒子](https://leetcode.cn/problems/remove-boxes/) + +- 标签:记忆化搜索、数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0546. 移除盒子 - 力扣](https://leetcode.cn/problems/remove-boxes/) + +## 题目大意 + +**描述**:给定一个代表不同颜色盒子的正数数组 $boxes$,盒子的颜色由不同正数组成,其中 $boxes[i]$ 表示第 $i$ 个盒子的颜色。 + +我们将经过若干轮操作去去掉盒子,直到所有盒子都去掉为止。每一轮我们可以移除具有相同颜色的连续 $k$ 个盒子($k \ge 1$),这样一轮之后,我们将获得 $k \times k$ 个积分。 + +**要求**:返回我们能获得的最大积分和。 + +**说明**: + +- $1 \le boxes.length \le 100$。 +- $1 \le boxes[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:boxes = [1,3,2,2,2,3,4,3,1] +输出:23 +解释: +[1, 3, 2, 2, 2, 3, 4, 3, 1] +----> [1, 3, 3, 4, 3, 1] (3*3=9 分) +----> [1, 3, 3, 3, 1] (1*1=1 分) +----> [1, 1] (3*3=9 分) +----> [] (2*2=4 分) +``` + +- 示例 2: + +```python +输入:boxes = [1,1,1] +输出:9 +``` + +## 解题思路 + +### 思路 1:动态规划 + +对于每个盒子, + +如果使用二维状态 $dp[i][j]$ 表示为:移除区间 $[i, j]$ 之间的盒子,所能够得到的最大积分和。但实际上,移除区间 $[i, j]$ 之间盒子,所能得到的最大积分和,并不只依赖于子区间,也依赖于之前移除其他区间对当前区间的影响。比如当前区间的某个值和其他区间的相同值连起来可以获得更高的额分数。 + +因此,我们需要再二维状态的基础上,增加更多维数的状态。 + +对于当前区间 $[i, j]$,我们需要凑一些尽可能长的同色盒子一起消除,从而获得更高的分数。我们不妨每次都选择消除区间 $[i, j]$ 中最后一个盒子 $boxes[j]$,并且记录 $boxes[j]$ 之后与 $boxes[j]$ 颜色相同的盒子数量。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j][k]$ 表示为:移除区间 $[i, j]$ 之间的盒子,并且区间右侧有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分和。 + +###### 3. 状态转移方程 + +- 当区间长度为 $1$ 时,当前区间只有一个盒子,区间末尾有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分为 $(k + 1) \times (k + 1)$。 +- 当区间长度大于 $1$ 时,对于区间末尾的 $k$ 个与 $boxes[j]$ 颜色相同的盒子,有两种处理方式: + - 将末尾的盒子移除,所能够得到的最大积分为:移除末尾盒子之前能够获得的最大积分和,再加上本轮移除末尾盒子能够获得的积分和,即:$dp[i][j - 1][0] + (k + 1) \times (k + 1)$。 + - 在区间中找到一个位置 $t$,使得第 $t$ 个盒子与第 $j$ 个盒子颜色相同,先将区间 $[t + 1, j - 1]$ 的盒子消除,然后继续凑同色盒子,即:$dp[t + 1][j - 1][0] + dp[i][t][k + 1]$。 + +###### 4. 初始条件 + +- 区间长度为 $1$ 时,当前区间只有一个盒子,区间末尾有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分为 $(k + 1) \times (k + 1)$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j][k]$ 表示为:移除区间 $[i, j]$ 之间的盒子,并且区间右侧有 $k$ 个与 $boxes[j]$ 颜色相同的盒子,所能够得到的最大积分和。所以最终结果为 $dp[0][size - 1][0]$。 + +### 思路 1:代码 + +```python +class Solution: + def removeBoxes(self, boxes: List[int]) -> int: + size = len(boxes) + + dp = [[[0 for _ in range(size)] for _ in range(size)] for _ in range(size)] + for l in range(1, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + + for k in range(size - j): + if l == 1: + dp[i][j][k] = max(dp[i][j][k], (k + 1) * (k + 1)) + else: + dp[i][j][k] = max(dp[i][j][k], dp[i][j - 1][0] + (k + 1) * (k + 1)) + for t in range(i, j): + if boxes[t] == boxes[j]: + dp[i][j][k] = max(dp[i][j][k], dp[t + 1][j - 1][0] + dp[i][t][k + 1]) + + return dp[0][size - 1][0] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^4)$,其中 $n$ 为数组 $boxes$ 的元素个数。 +- **空间复杂度**:$O(n^3)$。 + diff --git a/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md b/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md new file mode 100644 index 00000000..0785a3c4 --- /dev/null +++ b/docs/solutions/0500-0599/reverse-words-in-a-string-iii.md @@ -0,0 +1,61 @@ +# [0557. 反转字符串中的单词 III](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0557. 反转字符串中的单词 III - 力扣](https://leetcode.cn/problems/reverse-words-in-a-string-iii/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:将字符串中每个单词的字符顺序进行反装,同时仍保留空格和单词的初始顺序。 + +**说明**: + +- $1 \le s.length \le 5 * 10^4$。 +- `s` 包含可打印的 ASCII 字符。 +- `s` 不包含任何开头或结尾空格。 +- `s` 里至少有一个词。 +- `s` 中的所有单词都用一个空格隔开。 + +**示例**: + +- 示例 1: + +```python +输入:s = "Let's take LeetCode contest" +输出:"s'teL ekat edoCteeL tsetnoc" +``` + +- 示例 2: + +```python +输入: s = "God Ding" +输出:"doG gniD" +``` + +## 解题思路 + +### 思路 1:使用额外空间 + +因为 Python 的字符串是不可变的,所以在原字符串空间上进行切换顺序操作肯定是不可行的了。但我们可以利用切片方法。 + +1. 将字符串按空格进行分割,分割成一个个的单词。 +2. 再将每个单词进行反转。 +3. 最后将每个单词连接起来。 + +### 思路 1:代码 + +```python +class Solution: + def reverseWords(self, s: str) -> str: + return " ".join(word[::-1] for word in s.split(" ")) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0500-0599/subarray-sum-equals-k.md b/docs/solutions/0500-0599/subarray-sum-equals-k.md new file mode 100644 index 00000000..76b25a8f --- /dev/null +++ b/docs/solutions/0500-0599/subarray-sum-equals-k.md @@ -0,0 +1,121 @@ +# [0560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) + +- 标签:数组、哈希表、前缀和 +- 难度:中等 + +## 题目链接 + +- [0560. 和为 K 的子数组 - 力扣](https://leetcode.cn/problems/subarray-sum-equals-k/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 + +**要求**:找到该数组中和为 $k$ 的连续子数组的个数。 + +**说明**: + +- $1 \le nums.length \le 2 \times 10^4$。 +- $-1000 \le nums[i] \le 1000$。 + $-10^7 \le k \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1], k = 2 +输出:2 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3], k = 3 +输出:2 +``` + +## 解题思路 + +### 思路 1:枚举算法(超时) + +先考虑暴力做法,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: + +```python +for i in range(len(nums)): + for j in range(i + 1): + sum = countSum(i, j) +``` + +这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 + +对于以 $i$ 开头,以 $j$ 结尾($i \le j$)的子数组 $nums[i]…nums[j]$ 来说,我们可以通过顺序遍历 $j$,逆序遍历 $i$ 的方式(或者前缀和的方式),从而在 $O(n^2)$ 的时间复杂度内计算出子数组的和,同时使用变量 $cnt$ 统计出和为 $k$ 的子数组个数。 + +但这样提交上去超时了。 + +### 思路 1:代码 + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt = 0 + for j in range(len(nums)): + sum = 0 + for i in range(j, -1, -1): + sum += nums[i] + if sum == k: + cnt += 1 + + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:前缀和 + 哈希表 + +先用一重循环遍历数组,计算出数组 $nums$ 中前 $j$ 个元素的和(前缀和),保存到一维数组 $pre\underline{\hspace{0.5em}}sum$ 中,那么对于任意 $nums[i]…nums[j]$ 的子数组的和为 $pre\underline{\hspace{0.5em}}sum[j] - pre\underline{\hspace{0.5em}}sum[i - 1]$。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 + +但是还是超时了。。 + +由于我们只关心和为 $k$ 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 + +$pre\underline{\hspace{0.5em}}sum[i]$ 的定义是前 $i$ 个元素和,则 $pre\underline{\hspace{0.5em}}sum[i]$ 可以由 $pre\underline{\hspace{0.5em}}sum[i - 1]$ 递推而来,即:$pre\underline{\hspace{0.5em}}sum[i] = pre\underline{\hspace{0.5em}}sum[i - 1] + num[i]$。 $[i..j]$ 子数组和为 $k$ 可以转换为:$pre\underline{\hspace{0.5em}}sum[j] - pre\underline{\hspace{0.5em}}sum[i - 1] == k$。 + +综合一下,可得:$pre\underline{\hspace{0.5em}}sum[i - 1] == pre\underline{\hspace{0.5em}}sum[j] - k $。 + +所以,当我们考虑以 $j$ 结尾和为 $k$ 的连续子数组个数时,只需要统计有多少个前缀和为 $pre\underline{\hspace{0.5em}}sum[j] - k$ (即 $pre\underline{\hspace{0.5em}}sum[i - 1]$)的个数即可。具体做法如下: + +- 使用 $pre\underline{\hspace{0.5em}}sum$ 变量记录前缀和(代表 $pre\underline{\hspace{0.5em}}sum[j]$)。 +- 使用哈希表 $pre\underline{\hspace{0.5em}}dic$ 记录 $pre\underline{\hspace{0.5em}}sum[j]$ 出现的次数。键值对为 $pre\underline{\hspace{0.5em}}sum[j] : pre\underline{\hspace{0.5em}}sum\underline{\hspace{0.5em}}count$。 +- 从左到右遍历数组,计算当前前缀和 $pre\underline{\hspace{0.5em}}sum$。 +- 如果 $pre\underline{\hspace{0.5em}}sum - k$ 在哈希表中,则答案个数累加上 $pre\underline{\hspace{0.5em}}dic[pre\underline{\hspace{0.5em}}sum - k]$。 +- 如果 $pre\underline{\hspace{0.5em}}sum$ 在哈希表中,则前缀和个数累加 $1$,即 $pre\underline{\hspace{0.5em}}dic[pre\underline{\hspace{0.5em}}sum] += 1$。 +- 最后输出答案个数。 + +### 思路 2:代码 + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + pre_dic = {0: 1} + pre_sum = 0 + count = 0 + for num in nums: + pre_sum += num + if pre_sum - k in pre_dic: + count += pre_dic[pre_sum - k] + if pre_sum in pre_dic: + pre_dic[pre_sum] += 1 + else: + pre_dic[pre_sum] = 1 + return count +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0600-0699/2-keys-keyboard.md b/docs/solutions/0600-0699/2-keys-keyboard.md new file mode 100644 index 00000000..b8fd3ea0 --- /dev/null +++ b/docs/solutions/0600-0699/2-keys-keyboard.md @@ -0,0 +1,97 @@ +# [0650. 两个键的键盘](https://leetcode.cn/problems/2-keys-keyboard/) + +- 标签:数学、动态规划 +- 难度:中等 + +## 题目链接 + +- [0650. 两个键的键盘 - 力扣](https://leetcode.cn/problems/2-keys-keyboard/) + +## 题目大意 + +**描述**:最初记事本上只有一个字符 `'A'`。你每次可以对这个记事本进行两种操作: + +- **Copy All(复制全部)**:复制这个记事本中的所有字符(不允许仅复制部分字符)。 +- **Paste(粘贴)**:粘贴上一次复制的字符。 + +现在,给定一个数字 $n$,需要使用最少的操作次数,在记事本上输出恰好 $n$ 个 `'A'` 。 + +**要求**:返回能够打印出 $n$ 个 `'A'` 的最少操作次数。 + +**说明**: + +- $1 \le n \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:3 +输出:3 +解释 +最初, 只有一个字符 'A'。 +第 1 步, 使用 Copy All 操作。 +第 2 步, 使用 Paste 操作来获得 'AA'。 +第 3 步, 使用 Paste 操作来获得 'AAA'。 +``` + +- 示例 2: + +```python +输入:n = 1 +输出:0 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照字符 `'A'` 的个数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 + +###### 3. 状态转移方程 + +1. 对于 $i$ 个字符 `'A'`,如果 $i$ 可以被一个小于 $i$ 的整数 $j$ 除尽($j$ 是 $i$ 的因子),则说明 $j$ 个字符 `'A'` 可以通过「复制」+「粘贴」总共 $\frac{i}{j}$ 次得到 $i$ 个字符 `'A'`。 +2. 而得到 $j$ 个字符 `'A'`,最少需要的操作数可以通过 $dp[j]$ 获取。 + +则我们可以枚举 $i$ 的因子,从中找到在满足 $j$ 能够整除 $i$ 的条件下,最小的 $dp[j] + \frac{i}{j}$,即为 $dp[i]$,即 $dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j})$。 + +由于 $j$ 能够整除 $i$,则 $j$ 与 $\frac{i}{j}$ 都是 $i$ 的因子,两者中必有一个因子是小于等于 $\sqrt{i}$ 的,所以在枚举 $i$ 的因子时,我们只需要枚举区间 $[1, \sqrt{i}]$ 即可。 + +综上所述,状态转移方程为:$dp[i] = min_{j | i}(dp[i], dp[j] + \frac{i}{j}, dp[\frac{i}{j}] + j)$。 + +###### 4. 初始条件 + +- 当 $i$ 为 $1$ 时,最少需要的操作数为 $0$。所以 $dp[1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:通过「复制」和「粘贴」操作,得到 $i$ 个字符 `'A'`,最少需要的操作数。 所以最终结果为 $dp[n]$。 + +### 思路 1:动态规划代码 + +```python +import math + +class Solution: + def minSteps(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + for i in range(2, n + 1): + dp[i] = float('inf') + for j in range(1, int(math.sqrt(n)) + 1): + if i % j == 0: + dp[i] = min(dp[i], dp[j] + i // j, dp[i // j] + j) + + return dp[n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \sqrt{n})$。外层循环遍历的时间复杂度是 $O(n)$,内层循环遍历的时间复杂度是 $O(\sqrt{n})$,所以总体时间复杂度为 $O(n \sqrt{n})$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 diff --git a/docs/solutions/0600-0699/add-bold-tag-in-string.md b/docs/solutions/0600-0699/add-bold-tag-in-string.md new file mode 100644 index 00000000..f894e629 --- /dev/null +++ b/docs/solutions/0600-0699/add-bold-tag-in-string.md @@ -0,0 +1,90 @@ +# [0616. 给字符串添加加粗标签](https://leetcode.cn/problems/add-bold-tag-in-string/) + +- 标签:字典树、数组、哈希表、字符串、字符串匹配 +- 难度:中等 + +## 题目链接 + +- [0616. 给字符串添加加粗标签 - 力扣](https://leetcode.cn/problems/add-bold-tag-in-string/) + +## 题目大意 + +给定一个字符串 `s` 和一个字符串列表 `words`。 + +要求:如果 `s` 的子串在字符串列表 `words` 中出现过,则在该子串前后添加加粗闭合标签 `` 和 ``。如果两个子串有重叠部分,则将它们一起用一对闭合标签包围起来。同理,如果两个子字符串连续被加粗,那么你也需要把它们合起来用一对加粗标签包围。最后返回添加加粗标签后的字符串 `s`。 + +## 解题思路 + +构建字典树,将字符串列表 `words` 中所有字符串添加到字典树中。 + +然后遍历字符串 `s`,从每一个位置开始查询字典树。在第一个符合要求的单词前面添加 ``。在连续符合要求的单词中的最后一个单词后面添加 ``。 + +最后返回添加加粗标签后的字符串 `s`。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + + +class Solution: + def addBoldTag(self, s: str, words: List[str]) -> str: + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + size = len(s) + bold_left, bold_right = -1, -1 + ans = "" + for i in range(size): + cur = trie_tree + if s[i] in cur.children: + bold_left = i + while bold_left < size and s[bold_left] in cur.children: + cur = cur.children[s[bold_left]] + bold_left += 1 + if cur.isEnd: + if bold_right == -1: + ans += "" + bold_right = max(bold_left, bold_right) + if i == bold_right: + ans += "" + bold_right = -1 + ans += s[i] + if bold_right >= 0: + ans += "" + return ans +``` + diff --git a/docs/solutions/0600-0699/decode-ways-ii.md b/docs/solutions/0600-0699/decode-ways-ii.md new file mode 100644 index 00000000..e2ec6085 --- /dev/null +++ b/docs/solutions/0600-0699/decode-ways-ii.md @@ -0,0 +1,140 @@ +# [0639. 解码方法 II](https://leetcode.cn/problems/decode-ways-ii/) + +- 标签:字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0639. 解码方法 II - 力扣](https://leetcode.cn/problems/decode-ways-ii/) + +## 题目大意 + +**描述**:给定一个包含数字和字符 `'*'` 的字符串 $s$。该字符串已经按照下面的映射关系进行了编码: + +- `A` 映射为 $1$。 +- `B` 映射为 $2$。 +- ... +- `Z` 映射为 $26$。 + +除了上述映射方法,字符串 $s$ 中可能包含字符 `'*'`,可以表示 $1$ ~ $9$ 的任一数字(不包括 $0$)。例如字符串 `"1*"` 可以表示为 `"11"`、`"12"`、…、`"18"`、`"19"` 中的任何一个编码。 + +基于上述映射的方法,现在对字符串 `s` 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: + +- `"AAJF"`,将消息分组为 $(1 1 10 6)$。 +- `"KJF"`,将消息分组为 $(11 10 6)$。 + +**要求**:计算出共有多少种可能的解码方案。 + +**说明**: + +- $1 \le s.length \le 100$。 +- $s$ 只包含数字,并且可能包含前导零。 +- 题目数据保证答案肯定是一个 $32$ 位的整数。 + +```python +输入:s = "*" +输出:9 +解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。因此,"*" 总共有 9 种解码方法。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +这道题是「[91. 解码方法 - 力扣](https://leetcode.cn/problems/decode-ways/)」的升级版,其思路是相似的,只不过本题的状态转移方程的条件和公式不太容易想全。 + +###### 1. 划分阶段 + +按照字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。 + +###### 3. 状态转移方程 + +$dp[i]$ 的来源有两种情况: + +1. 使用了一个字符,对 $s[i]$ 进行翻译: + 1. 如果 `s[i] == '*'`,则 `s[i]` 可以视作区间 `[1, 9]` 上的任意一个数字,可以被翻译为 `A` ~ `I`。此时当前位置上的方案数为 `9`,即 `dp[i] = dp[i - 1] * 9`。 + 2. 如果 `s[i] == '0'`,则无法被翻译,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 1] * 0`。 + 3. 如果是其他情况(即 `s[i]` 是区间 `[1, 9]` 上某一个数字),可以被翻译为 `A` ~ `I` 对应位置上的某个字母。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 1] * 1`。 + +2. 使用了两个字符,对 `s[i - 1]` 和 `s[i]` 进行翻译: + 1. 如果 `s[i - 1] == '*'` 并且 `s[i] == '*'`,则 `s[i]` 可以视作区间 `[11, 19]` 或者 `[21, 26]` 上的任意一个数字。此时当前位置上的方案数为 `15`,即 `dp[i] = dp[i - 2] * 15`。 + 2. 如果 `s[i - 1] == '*'` 并且 `s[i] != '*'`,则: + 1. 如果 `s[i]` 在区间 `[1, 6]` 内,`s[i - 1]` 可以选择 `1` 或 `2`。此时当前位置上的方案数为 `2`,即 `dp[i] = dp[i - 2] * 2`。 + 2. 如果 `s[i]` 不在区间 `[1, 6]` 内,`s[i - 1]` 只能选择 `1`。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 + + 3. 如果 `s[i - 1] == '1'` 并且 `s[i] == '*'`,`s[i]` 可以视作区间 `[1, 9]` 上任意一个数字。此时当前位置上的方案数为 `9`,即 `dp[i] = dp[i - 2] * 9`。 + 4. 如果 `s[i - 1] == '1'` 并且 `s[i] != '*'`,`s[i]` 可以视作区间 `[1, 9]` 上的某一个数字。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 + 5. 如果 `s[i - 1] == '2'` 并且 `s[i] == '*'`,`s[i]` 可以视作区间 `[1, 6]` 上任意一个数字。此时当前位置上的方案数为 `6`,即 `dp[i] = dp[i - 2] * 6`。 + 6. 如果 `s[i - 1] == '2'` 并且 `s[i] != '*'`,则: + 1. 如果 `s[i]` 在区间 `[1, 6]` 内,此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 2] * 1`。 + 2. 如果 `s[i]` 不在区间 `[1, 6]` 内,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 2] * 0`。 + + 7. 其他情况下(即 `s[i - 1]` 在区间 `[3, 9]` 内),则无法被翻译,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 2] * 0`。 + + +在进行转移的时候,需要将使用一个字符的翻译方案数与使用两个字符的翻译方案数进行相加。同时还要注意对 $10^9 + 7$ 的取余。 + +这里我们可以单独写两个方法 `,分别来表示「单个字符 `s[i]` 的翻译方案数」和「两个字符 `s[i - 1]` 和 `s[i]` 的翻译方案数」,这样代码逻辑会更加清晰。 + +###### 4. 初始条件 + +- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 `dp[0] = 1`。 +- 字符串只有一个字符时,单个字符 `s[i]` 的翻译方案数为转移条件的第一种求法,即`dp[1] = self.parse1(s[0])`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。则最终结果为 `dp[size]`,`size` 为字符串长度。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def parse1(self, ch): + if ch == '*': + return 9 + if ch == '0': + return 0 + return 1 + + def parse2(self, ch1, ch2): + if ch1 == '*' and ch2 == '*': + return 15 + if ch1 == '*' and ch2 != '*': + return 2 if ch2 <= '6' else 1 + + if ch1 == '1' and ch2 == '*': + return 9 + if ch1 == '1' and ch2 != '*': + return 1 + + if ch1 == '2' and ch2 == '*': + return 6 + if ch1 == '2' and ch2 != '*': + return 1 if ch2 <= '6' else 0 + + return 0 + + def numDecodings(self, s: str) -> int: + mod = 10 ** 9 + 7 + size = len(s) + + dp = [0 for _ in range(size + 1)] + dp[0] = 1 + dp[1] = self.parse1(s[0]) + + for i in range(2, size + 1): + dp[i] += dp[i - 1] * self.parse1(s[i - 1]) + dp[i] += dp[i - 2] * self.parse2(s[i - 2], s[i - 1]) + dp[i] %= mod + + return dp[size] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/design-circular-queue.md b/docs/solutions/0600-0699/design-circular-queue.md new file mode 100644 index 00000000..d60dd119 --- /dev/null +++ b/docs/solutions/0600-0699/design-circular-queue.md @@ -0,0 +1,118 @@ +# [0622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) + +- 标签:设计、队列、数组、链表 +- 难度:中等 + +## 题目链接 + +- [0622. 设计循环队列 - 力扣](https://leetcode.cn/problems/design-circular-queue/) + +## 题目大意 + +**要求**:设计实现一个循环队列,支持以下操作: + +- `MyCircularQueue(k)`: 构造器,设置队列长度为 `k`。 +- `Front`: 从队首获取元素。如果队列为空,返回 `-1`。 +- `Rear`: 获取队尾元素。如果队列为空,返回 `-1`。 +- `enQueue(value)`: 向循环队列插入一个元素。如果成功插入则返回真。 +- `deQueue()`: 从循环队列中删除一个元素。如果成功删除则返回真。 +- `isEmpty()`: 检查循环队列是否为空。 +- `isFull()`: 检查循环队列是否已满。 + +**说明**: + +- 所有的值都在 `0` 至 `1000` 的范围内。 +- 操作数将在 `1` 至 `1000` 的范围内。 +- 请不要使用内置的队列库。 + +**示例**: + +- 示例 1: + +```python +MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3 +circularQueue.enQueue(1);  // 返回 true +circularQueue.enQueue(2);  // 返回 true +circularQueue.enQueue(3);  // 返回 true +circularQueue.enQueue(4);  // 返回 false,队列已满 +circularQueue.Rear();  // 返回 3 +circularQueue.isFull();  // 返回 true +circularQueue.deQueue();  // 返回 true +circularQueue.enQueue(4);  // 返回 true +circularQueue.Rear();  // 返回 4 +``` + +## 解题思路 + +这道题可以使用数组,也可以使用链表来实现循环队列。 + +### 思路 1:使用数组模拟 + +建立一个容量为 `k + 1` 的数组 `queue`。并保存队头指针 `front`、队尾指针 `rear`,队列容量 `capacity` 为 `k + 1`(这里之所以用了 `k + 1` 的容量,是为了判断空和满,需要空出一个)。 + +然后实现循环队列的各个接口: + +1. `MyCircularQueue(k)`: + 1. 将数组 `queue` 初始化大小为 `k + 1` 的数组。 + 2. `front`、`rear` 初始化为 `0`。 +2. `Front`: + 1. 先检测队列是否为空。如果队列为空,返回 `-1`。 + 2. 如果不为空,则返回队头元素。 +3. `Rear`: + 1. 先检测队列是否为空。如果队列为空,返回 `-1`。 + 2. 如果不为空,则返回队尾元素。 +4. `enQueue(value)`: + 1. 如果队列已满,则无法插入,返回 `False`。 + 2. 如果队列未满,则将队尾指针 `rear` 向右循环移动一位,并进行插入操作。然后返回 `True`。 +5. `deQueue()`: + 1. 如果队列为空,则无法删除,返回 `False`。 + 2. 如果队列不空,则将队头指针 `front` 指向元素赋值为 `None`,并将 `front` 向右循环移动一位。然后返回 `True`。 +6. `isEmpty()`: 如果 `rear` 等于 `front`,则说明队列为空,返回 `True`。否则,队列不为空,返回 `False`。 +7. `isFull()`: 如果 `(rear + 1) % capacity` 等于 `front`,则说明队列已满,返回 `True`。否则,队列未满,返回 `False`。 + +### 思路 1:代码 + +```python +class MyCircularQueue: + + def __init__(self, k: int): + self.capacity = k + 1 + self.queue = [0 for _ in range(k + 1)] + self.front = 0 + self.rear = 0 + + def enQueue(self, value: int) -> bool: + if self.isFull(): + return False + self.rear = (self.rear + 1) % self.capacity + self.queue[self.rear] = value + return True + + def deQueue(self) -> bool: + if self.isEmpty(): + return False + self.front = (self.front + 1) % self.capacity + return True + + def Front(self) -> int: + if self.isEmpty(): + return -1 + return self.queue[(self.front + 1) % self.capacity] + + def Rear(self) -> int: + if self.isEmpty(): + return -1 + return self.queue[self.rear] + + def isEmpty(self) -> bool: + return self.front == self.rear + + def isFull(self) -> bool: + return (self.rear + 1) % self.capacity == self.front +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。初始化和每项操作的时间复杂度均为 $O(1)$。 +- **空间复杂度**:$O(k)$。其中 $k$ 为给定队列的元素数目。 + diff --git a/docs/solutions/0600-0699/design-search-autocomplete-system.md b/docs/solutions/0600-0699/design-search-autocomplete-system.md new file mode 100644 index 00000000..9fb80fa1 --- /dev/null +++ b/docs/solutions/0600-0699/design-search-autocomplete-system.md @@ -0,0 +1,129 @@ +# [0642. 设计搜索自动补全系统](https://leetcode.cn/problems/design-search-autocomplete-system/) + +- 标签:设计、字典树、字符串、数据流 +- 难度:困难 + +## 题目链接 + +- [0642. 设计搜索自动补全系统 - 力扣](https://leetcode.cn/problems/design-search-autocomplete-system/) + +## 题目大意 + +要求:设计一个搜索自动补全系统。用户会输入一条语句(最少包含一个字母,以特殊字符 `#` 结尾)。除 `#` 以外用户输入的每个字符,返回历史中热度前三并以当前输入部分为前缀的句子。下面是详细规则: + +- 一条句子的热度定义为历史上用户输入这个句子的总次数。 +- 返回前三的句子需要按照热度从高到低排序(第一个是最热门的)。如果有多条热度相同的句子,请按照 ASCII 码的顺序输出(ASCII 码越小排名越前)。 +- 如果满足条件的句子个数少于 3,将它们全部输出。 +- 如果输入了特殊字符,意味着句子结束了,请返回一个空集合。 + +你的工作是实现以下功能: + +- 构造函数: `AutocompleteSystem(String[] sentences, int[] times):` + - 输入历史数据。 `sentences` 是之前输入过的所有句子,`times` 是每条句子输入的次数,你的系统需要记录这些历史信息。 + +- 输入函数(用户输入一条新的句子,下面的函数会提供用户输入的下一个字符):`List input(char c):` + - 其中 `c` 是用户输入的下一个字符。字符只会是小写英文字母(`a` 到 `z` ),空格(` `)和特殊字符(`#`)。输出历史热度前三的具有相同前缀的句子。 + +## 解题思路 + +使用字典树来保存输入过的所有句子 `sentences`,并且在字典树中维护每条句子的输入次数 `times`。 + +构造函数中: + +- 将所有句子及对应输入次数插入到字典树中。 + +输入函数中: + +- 使用 `path` 变量保存当前输入句子的前缀。 +- 如果遇到 `#`,则将当前句子插入到字典树中。 +- 如果遇到其他字符,用 `path` 保存当前字符 `c`。并在字典树中搜索以 `path` 为前缀的节点的所有分支,将每个分支对应的单词 `path` 和它们出现的次数 `times` 存入数组中。然后借助 `heapq` 进行堆排序,根据出现次数和 ASCII 码大小排序,找出 `times` 最多的前三个单词。 + +## 代码 + +```python +import heapq + +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.times = 0 + + + def insert(self, word: str, times=1) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.times += times + + + def search(self, word: str): + """ + Returns if the word is in the trie. + """ + cur = self + + for ch in word: + if ch not in cur.children: + return [] + cur = cur.children[ch] + + res = [] + path = [word] + cur.dfs(res, path) + return res + + + def dfs(self, res, path): + cur = self + if cur.isEnd: + res.append((-cur.times, ''.join(path))) + for ch in cur.children: + node = cur.children[ch] + path.append(ch) + node.dfs(res, path) + path.pop() + + +class AutocompleteSystem: + + def __init__(self, sentences: List[str], times: List[int]): + self.path = '' + self.exists = True + self.trie_tree = Trie() + for i in range(len(sentences)): + self.trie_tree.insert(sentences[i], times[i]) + + + def input(self, c: str) -> List[str]: + if c == '#': + self.trie_tree.insert(self.path, 1) + self.path = '' + self.exists = True + return [] + else: + self.path += c + if not self.exists: + return [] + words = self.trie_tree.search(self.path) + if words: + heapq.heapify(words) + res = [] + while words and len(res) < 3: + res.append(heapq.heappop(words)[1]) + return res + else: + self.exists = False + return [] +``` + diff --git a/docs/solutions/0600-0699/employee-importance.md b/docs/solutions/0600-0699/employee-importance.md new file mode 100644 index 00000000..a88db54d --- /dev/null +++ b/docs/solutions/0600-0699/employee-importance.md @@ -0,0 +1,37 @@ +# [0690. 员工的重要性](https://leetcode.cn/problems/employee-importance/) + +- 标签:深度优先搜索、广度优先搜索、哈希表 +- 难度:中等 + +## 题目链接 + +- [0690. 员工的重要性 - 力扣](https://leetcode.cn/problems/employee-importance/) + +## 题目大意 + +给定一个公司的所有员工信息。其中每个员工信息包含:该员工 id,该员工重要度,以及该员工的所有下属 id。 + +再给定一个员工 id,要求返回该员工和他所有下属的重要度之和。 + +## 解题思路 + +利用哈希表,以「员工 id: 员工数据结构」的形式将员工信息存入哈希表中。然后深度优先搜索该员工以及下属员工。在搜索的同时,计算重要度之和,最终返回结果即可。 + +## 代码 + +```python +class Solution: + def getImportance(self, employees: List['Employee'], id: int) -> int: + employee_dict = dict() + for employee in employees: + employee_dict[employee.id] = employee + + def dfs(index: int) -> int: + total = employee_dict[index].importance + for sub_index in employee_dict[index].subordinates: + total += dfs(sub_index) + return total + + return dfs(id) +``` + diff --git a/docs/solutions/0600-0699/find-duplicate-subtrees.md b/docs/solutions/0600-0699/find-duplicate-subtrees.md new file mode 100644 index 00000000..c98c8be5 --- /dev/null +++ b/docs/solutions/0600-0699/find-duplicate-subtrees.md @@ -0,0 +1,41 @@ +# [0652. 寻找重复的子树](https://leetcode.cn/problems/find-duplicate-subtrees/) + +- 标签:树、深度优先搜索、哈希表、二叉树 +- 难度:中等 + +## 题目链接 + +- [0652. 寻找重复的子树 - 力扣](https://leetcode.cn/problems/find-duplicate-subtrees/) + +## 题目大意 + +给定一个二叉树,返回所有重复的子树。对于重复的子树,只需返回其中任意一棵的根节点。 + +## 解题思路 + +对二叉树进行先序遍历,对遍历的所有的子树进行序列化处理,将序列化处理后的字符串作为哈希表的键,记录每棵子树出现的次数。 + +当出现第二次时,则说明该子树是重复的子树,将其加入答案数组。最后返回答案数组即可。 + +## 代码 + +```python +class Solution: + def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]: + tree_dict = dict() + res = [] + def preorder(node): + if not node: + return '#' + sub_tree = str(node.val) + ',' + preorder(node.left) + ',' + preorder(node.right) + if sub_tree in tree_dict: + tree_dict[sub_tree] += 1 + else: + tree_dict[sub_tree] = 1 + if tree_dict[sub_tree] == 2: + res.append(node) + return sub_tree + preorder(root) + return res +``` + diff --git a/docs/solutions/0600-0699/find-k-closest-elements.md b/docs/solutions/0600-0699/find-k-closest-elements.md new file mode 100644 index 00000000..9ef4125b --- /dev/null +++ b/docs/solutions/0600-0699/find-k-closest-elements.md @@ -0,0 +1,83 @@ +# [0658. 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) + +- 标签:数组、双指针、二分查找、排序、滑动窗口、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0658. 找到 K 个最接近的元素 - 力扣](https://leetcode.cn/problems/find-k-closest-elements/) + +## 题目大意 + +**描述**:给定一个有序数组 $arr$,以及两个整数 $k$、$x$。 + +**要求**:从数组中找到最靠近 $x$(两数之差最小)的 $k$ 个数。返回包含这 $k$ 个数的有序数组。 + +**说明**: + +- 整数 $a$ 比整数 $b$ 更接近 $x$ 需要满足: + - $|a - x| < |b - x|$ 或者 + - $|a - x| == |b - x|$ 且 $a < b$。 + +- $1 \le k \le arr.length$。 +- $1 \le arr.length \le 10^4$。 +- $arr$ 按升序排列。 +- $-10^4 \le arr[i], x \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [1,2,3,4,5], k = 4, x = 3 +输出:[1,2,3,4] +``` + +- 示例 2: + +```python +输入:arr = [1,2,3,4,5], k = 4, x = -1 +输出:[1,2,3,4] +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +数组的区间为 $[0, n-1]$,查找的子区间长度为 $k$。我们可以通过查找子区间左端点位置,从而确定子区间。 + +查找子区间左端点可以通过二分查找来降低复杂度。 + +因为子区间为 $k$,所以左端点最多取到 $n - k$ 的位置。 + +设定两个指针 $left$,$right$。$left$ 指向 $0$,$right$ 指向 $n - k$。 + +每次取 $left$ 和 $right$ 中间位置,判断 $x$ 与左右边界的差值。$x$ 与左边的差值为 $x - arr[mid]$,$x$ 与右边界的差值为 $arr[mid + k] - x$。 + +- 如果 $x$ 与左边界的差值大于 $x$ 与右边界的差值,即 $x - arr[mid] > arr[mid + k] - x$,将 $left$ 右移,$left = mid + 1$,从右侧继续查找。 +- 如果 $x$ 与左边界的差值小于等于 $x$ 与右边界的差值, 即 $x - arr[mid] \le arr[mid + k] - x$,则将 $right$ 向左侧靠拢,$right = mid$,从左侧继续查找。 + +最后返回 $arr[left, left + k]$ 即可。 + +### 思路 1:代码 + +```python +class Solution: + def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: + n = len(arr) + left = 0 + right = n - k + while left < right: + mid = left + (right - left) // 2 + if x - arr[mid] > arr[mid + k] - x: + left = mid + 1 + else: + right = mid + return arr[left: left + k] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log (n - k) + k)$,其中 $n$ 为数组中的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0600-0699/implement-magic-dictionary.md b/docs/solutions/0600-0699/implement-magic-dictionary.md new file mode 100644 index 00000000..c60bc8a8 --- /dev/null +++ b/docs/solutions/0600-0699/implement-magic-dictionary.md @@ -0,0 +1,126 @@ +# [0676. 实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0676. 实现一个魔法字典 - 力扣](https://leetcode.cn/problems/implement-magic-dictionary/) + +## 题目大意 + +**要求**:设计一个使用单词表进行初始化的数据结构。单词表中的单词互不相同。如果给出一个单词,要求判定能否将该单词中的一个字母替换成另一个字母,是的所形成的新单词已经在够构建的单词表中。 + +实现 MagicDictionary 类: + +- `MagicDictionary()` 初始化对象。 +- `void buildDict(String[] dictionary)` 使用字符串数组 `dictionary` 设定该数据结构,`dictionary` 中的字符串互不相同。 +- `bool search(String searchWord)` 给定一个字符串 `searchWord`,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 `True`;否则,返回 `False`。 + +**说明**: + +- $1 \le dictionary.length \le 100$。 +- $1 \le dictionary[i].length \le 100$。 +- `dictionary[i]` 仅由小写英文字母组成。 +- `dictionary` 中的所有字符串互不相同。 +- $1 \le searchWord.length \le 100$。 +- `searchWord` 仅由小写英文字母组成。 +- `buildDict` 仅在 `search` 之前调用一次。 +- 最多调用 $100$ 次 `search`。 + +**示例**: + +- 示例 1: + +```python +输入 +["MagicDictionary", "buildDict", "search", "search", "search", "search"] +[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]] +输出 +[null, null, false, true, false, false] + +解释 +MagicDictionary magicDictionary = new MagicDictionary(); +magicDictionary.buildDict(["hello", "leetcode"]); +magicDictionary.search("hello"); // 返回 False +magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True +magicDictionary.search("hell"); // 返回 False +magicDictionary.search("leetcoded"); // 返回 False +``` + +## 解题思路 + +### 思路 1:字典树 + +1. 构造一棵字典树。 +2. `buildDict` 方法中将所有单词存入字典树中。 +3. `search` 方法中替换 `searchWord` 每一个位置上的字符,然后在字典树中查询。 + +### 思路 1:代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + + +class MagicDictionary: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.trie_tree = Trie() + + + def buildDict(self, dictionary: List[str]) -> None: + for word in dictionary: + self.trie_tree.insert(word) + + + def search(self, searchWord: str) -> bool: + size = len(searchWord) + for i in range(size): + for j in range(26): + new_ch = chr(ord('a') + j) + if searchWord[i] != new_ch: + new_word = searchWord[:i] + new_ch + searchWord[i + 1:] + if self.trie_tree.search(new_word): + return True + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:初始化操作是 $O(1)$。构建操作是 $O(|dictionary|)$,搜索操作是 $O(|searchWord| \times |\sum|)$。其中 $|dictionary|$ 是字符串数组 `dictionary` 中的字符个数,$|searchWord|$ 是查询操作中字符串的长度,$|\sum|$ 是字符集的大小。 +- **空间复杂度**:$O(|dicitonary|)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/index.md b/docs/solutions/0600-0699/index.md new file mode 100644 index 00000000..28cf22ed --- /dev/null +++ b/docs/solutions/0600-0699/index.md @@ -0,0 +1,38 @@ +## 本章内容 + +- [0600. 不含连续1的非负整数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md) +- [0611. 有效三角形的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-triangle-number.md) +- [0616. 给字符串添加加粗标签](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/add-bold-tag-in-string.md) +- [0617. 合并二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/merge-two-binary-trees.md) +- [0621. 任务调度器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/task-scheduler.md) +- [0622. 设计循环队列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-circular-queue.md) +- [0633. 平方数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/sum-of-square-numbers.md) +- [0639. 解码方法 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/decode-ways-ii.md) +- [0642. 设计搜索自动补全系统](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/design-search-autocomplete-system.md) +- [0643. 子数组最大平均数 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-average-subarray-i.md) +- [0647. 回文子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/palindromic-substrings.md) +- [0648. 单词替换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/replace-words.md) +- [0650. 两个键的键盘](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/2-keys-keyboard.md) +- [0652. 寻找重复的子树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-duplicate-subtrees.md) +- [0653. 两数之和 IV - 输入二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/two-sum-iv-input-is-a-bst.md) +- [0654. 最大二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-binary-tree.md) +- [0658. 找到 K 个最接近的元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/find-k-closest-elements.md) +- [0662. 二叉树最大宽度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/maximum-width-of-binary-tree.md) +- [0664. 奇怪的打印机](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/strange-printer.md) +- [0665. 非递减数列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/non-decreasing-array.md) +- [0669. 修剪二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/trim-a-binary-search-tree.md) +- [0673. 最长递增子序列的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md) +- [0674. 最长连续递增序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md) +- [0676. 实现一个魔法字典](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/implement-magic-dictionary.md) +- [0677. 键值映射](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/map-sum-pairs.md) +- [0678. 有效的括号字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-parenthesis-string.md) +- [0680. 验证回文串 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/valid-palindrome-ii.md) +- [0683. K 个关闭的灯泡](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/k-empty-slots.md) +- [0684. 冗余连接](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/redundant-connection.md) +- [0686. 重复叠加字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/repeated-string-match.md) +- [0687. 最长同值路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/longest-univalue-path.md) +- [0688. 骑士在棋盘上的概率](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/knight-probability-in-chessboard.md) +- [0690. 员工的重要性](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/employee-importance.md) +- [0691. 贴纸拼词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/stickers-to-spell-word.md) +- [0695. 岛屿的最大面积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/max-area-of-island.md) +- [0698. 划分为k个相等的子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md) diff --git a/docs/solutions/0600-0699/k-empty-slots.md b/docs/solutions/0600-0699/k-empty-slots.md new file mode 100644 index 00000000..553312ae --- /dev/null +++ b/docs/solutions/0600-0699/k-empty-slots.md @@ -0,0 +1,96 @@ +# [0683. K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) + +- 标签:树状数组、数组、有序集合、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [0683. K 个关闭的灯泡 - 力扣](https://leetcode.cn/problems/k-empty-slots/) + +## 题目大意 + +**描述**:$n$ 个灯泡排成一行,编号从 $1$ 到 $n$。最初,所有灯泡都关闭。每天只打开一个灯泡,直到 $n$ 天后所有灯泡都打开。 + +给定一个长度为 $n$ 的灯泡数组 $blubs$,其中 `bulls[i] = x` 意味着在第 $i + 1$ 天,我们会把在位置 $x$ 的灯泡打开,其中 $i$ 从 $0$ 开始,$x$ 从 $1$ 开始。 + +再给定一个整数 $k$。 + +**要求**:输出在第几天恰好有两个打开的灯泡,使得它们中间正好有 $k$ 个灯泡且这些灯泡全部是关闭的 。如果不存在这种情况,则返回 $-1$。如果有多天都出现这种情况,请返回最小的天数 。 + +**说明**: + +- $n == bulbs.length$。 +- $1 \le n \le 2 \times 10^4$。 +- $1 \le bulbs[i] \le n$。 +- $bulbs$ 是一个由从 $1$ 到 $n$ 的数字构成的排列。 +- $0 \le k \le 2 \times 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入: +bulbs = [1,3,2],k = 1 +输出:2 +解释: +第一天 bulbs[0] = 1,打开第一个灯泡 [1,0,0] +第二天 bulbs[1] = 3,打开第三个灯泡 [1,0,1] +第三天 bulbs[2] = 2,打开第二个灯泡 [1,1,1] +返回2,因为在第二天,两个打开的灯泡之间恰好有一个关闭的灯泡。 +``` + +- 示例 2: + +```python +输入:bulbs = [1,2,3],k = 1 +输出:-1 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +$blubs[i]$ 记录的是第 $i + 1$ 天开灯的位置。我们将其转换一下,使用另一个数组 $days$ 来存储每个灯泡的开灯时间,其中 $days[i]$ 表示第 $i$ 个位置上的灯泡的开灯时间。 + +- 使用 $ans$ 记录最小满足条件的天数。维护一个窗口 $left$、$right$。其中 `right = left + k + 1`。使得区间 $(left, right)$ 中所有灯泡(总共为 $k$ 个)开灯时间都晚于 $days[left]$ 和 $days[right]$。 +- 对于区间 $[left, right]$,$left < i < right$: + - 如果出现 $days[i] < days[left]$ 或者 $days[i] < days[right]$,说明不符合要求。将 $left$、$right$ 移动到 $[i, i + k + 1]$,继续进行判断。 + - 如果对于 $left < i < right$ 中所有的 $i$,都满足 $days[i] \ge days[left]$ 并且 $days[i] \ge days[right]$,说明此时满足要求。将当前答案与 $days[left]$ 和 $days[right]$ 中的较大值作比较。如果比当前答案更小,则更新答案。同时将窗口向右移动 $k $位。继续检测新的不相交间隔 $[right, right + k + 1]$。 + - 注意:之所以检测新的不相交间隔,是因为如果检测的是相交间隔,原来的 $right$ 位置元素仍在区间中,肯定会出现 $days[right] < days[right_new]$,不满足要求。所以此时相交的区间可以直接跳过,直接检测不相交的间隔。 +- 直到 $right \ge len(days)$ 时跳出循环,判断是否有符合要求的答案,并返回答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def kEmptySlots(self, bulbs: List[int], k: int) -> int: + size = len(bulbs) + days = [0 for _ in range(size)] + for i in range(size): + days[bulbs[i] - 1] = i + 1 + + left, right = 0, k + 1 + ans = float('inf') + while right < size: + check_flag = True + for i in range(left + 1, right): + if days[i] < days[left] or days[i] < days[right]: + left, right = i, i + k + 1 + check_flag = False + break + if check_flag: + ans = min(ans, max(days[left], days[right])) + left, right = right, right + k + 1 + + if ans != float('inf'): + return ans + else: + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $bulbs$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0600-0699/knight-probability-in-chessboard.md b/docs/solutions/0600-0699/knight-probability-in-chessboard.md new file mode 100644 index 00000000..739744f0 --- /dev/null +++ b/docs/solutions/0600-0699/knight-probability-in-chessboard.md @@ -0,0 +1,96 @@ +# [0688. 骑士在棋盘上的概率](https://leetcode.cn/problems/knight-probability-in-chessboard/) + +- 标签:动态规划 +- 难度:中等 + +## 题目链接 + +- [0688. 骑士在棋盘上的概率 - 力扣](https://leetcode.cn/problems/knight-probability-in-chessboard/) + +## 题目大意 + +**描述**:在一个 `n * n` 的国际象棋棋盘上,一个骑士从单元格 `(row, column)` 开始,尝试进行 `k` 次 移动。行和列是从 `0` 开始的,左上角的单元格是 `(0, 0)`,右下角的单元格是 `(n - 1, n - 1)`。 + +象棋骑士有 `8` 种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。 + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/knight.png) + +每次骑士要移动时,它都会随机从 `8` 种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。骑士继续移动,直到它走了 `k` 步或离开了棋盘。 + +现在给定代表棋盘大小的整数 `n`、代表骑士移动次数的整数 `k`,以及代表骑士初始位置的坐标 `row` 和 `column`。 + +**要求**:返回骑士在棋盘停止移动后仍留在棋盘上的概率。 + +**说明**: + +- $1 \le n \le 25$。 +- $0 \le k \le 100$。 +- $0 \le row, column \le n$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 3, k = 2, row = 0, column = 0 +输出:0.0625 +解释:有两步(到(1,2),(2,1))可以让骑士留在棋盘上。在每一个位置上,也有两种移动可以让骑士留在棋盘上。骑士留在棋盘上的总概率是 0.0625。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照骑士所在位置和所走步数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j][p]` 表示为:从位置 `(i, j)` 出发,移动不超过 `p` 步的情况下,最后仍留在棋盘内的概率。 + +###### 3. 状态转移方程 + +根据象棋骑士的 `8` 种可能的走法,`dp[i][j][p]` 的来源有八个方向(超出棋盘的无需再考虑): + +- 假设下一步的落点为 `(new_i, new_j)`。从当前步选择 `8` 个方向其中之一作为下一步方向的概率为 $\frac{1}{8}$。 +- 而每个方向上落点仍在棋盘内的概率为 `dp[new_i][new_j][p - 1]`。所以从 `(i, j)` 走到 `(new_i, new_j)` 的可能性为 $dp[new_i][new_j] \times \frac{1}{8}$。 + +最终 $dp[i][j][p]$ 来源为 `8` 个方向上落点的概率之和,即:$dp[i][j][p] = \sum{ dp[new_i][new_j] \times \frac{1}{8} }$。 + +###### 4. 初始条件 + +- 从位置 `(i, j)` 出发,移动不超过 `0` 步的情况下,最后仍留在棋盘内的概率为 `1`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j][p]` 表示为:从位置 `(i, j)` 出发,移动不超过 `p` 步的情况下,最后仍留在棋盘内的概率。则最终结果为 `dp[row][column][k]`。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def knightProbability(self, n: int, k: int, row: int, column: int) -> float: + dp = [[[0 for _ in range(k + 1)] for _ in range(n)] for _ in range(n)] + for i in range(n): + for j in range(n): + dp[i][j][0] = 1 + + directions = {(-1, -2), (-1, 2), (1, -2), (1, 2), (-2, -1), (-2, 1), (2, -1), (2, 1)} + + for p in range(1, k + 1): + for i in range(n): + for j in range(n): + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + if 0 <= new_i < n and 0 <= new_j < n: + dp[i][j][p] += dp[new_i][new_j][p - 1] / 8 + + return dp[row][column][k] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 * k)$。外三层循环的时间复杂度为 $O(n^2 * k)$,内层关于 `directions` 的循环每次执行 `8` 次,可以看做是常数级时间复杂度。 +- **空间复杂度**:$O(n^2 * k)$。用到了三维数组保存状态。 diff --git a/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md b/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md new file mode 100644 index 00000000..8701a725 --- /dev/null +++ b/docs/solutions/0600-0699/longest-continuous-increasing-subsequence.md @@ -0,0 +1,127 @@ +# [0674. 最长连续递增序列](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [0674. 最长连续递增序列 - 力扣](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) + +## 题目大意 + +**描述**:给定一个未经排序的数组 $nums$。 + +**要求**:找到最长且连续递增的子序列,并返回该序列的长度。 + +**说明**: + +- **连续递增的子序列**:可以由两个下标 $l$ 和 $r$($l < r$)确定,如果对于每个 $l \le i < r$,都有 $nums[i] < nums[i + 1] $,那么子序列 $[nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]$ 就是连续递增子序列。 +- $1 \le nums.length \le 10^4$。 +- $-10^9 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,5,4,7] +输出:3 +解释:最长连续递增序列是 [1,3,5], 长度为 3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 +``` + +- 示例 2: + +```python +输入:nums = [2,2,2,2,2] +输出:1 +解释:最长连续递增序列是 [2], 长度为 1。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 定义状态 + +定义状态 $dp[i]$ 表示为:以 $nums[i]$ 结尾的最长且连续递增的子序列长度。 + +###### 2. 状态转移方程 + +因为求解的是连续子序列,所以只需要考察相邻元素的状态转移方程。 + +如果一个较小的数右侧相邻元素为一个较大的数,则会形成一个更长的递增子序列。 + +对于相邻的数组元素 $nums[i - 1]$ 和 $nums[i]$ 来说: + +- 如果 $nums[i - 1] < nums[i]$,则 $nums[i]$ 可以接在 $nums[i - 1]$ 后面,此时以 $nums[i]$ 结尾的最长递增子序列长度会在「以 $nums[i - 1]$ 结尾的最长递增子序列长度」的基础上加 $1$,即 $dp[i] = dp[i - 1] + 1$。 + +- 如果 $nums[i - 1] >= nums[i]$,则 $nums[i]$ 不可以接在 $nums[i - 1]$ 后面,可以直接跳过。 + +综上,我们的状态转移方程为:$dp[i] = dp[i - 1] + 1$,$nums[i - 1] < nums[i]$。 + +###### 3. 初始条件 + +默认状态下,把数组中的每个元素都作为长度为 $1$ 的最长且连续递增的子序列长度。即 $dp[i] = 1$。 + +###### 4. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:以 $nums[i]$ 结尾的最长且连续递增的子序列长度。则为了计算出最大值,则需要再遍历一遍 $dp$ 数组,求出最大值即为最终结果。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def findLengthOfLCIS(self, nums: List[int]) -> int: + size = len(nums) + dp = [1 for _ in range(size)] + + for i in range(1, size): + if nums[i - 1] < nums[i]: + dp[i] = dp[i - 1] + 1 + + return max(dp) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$,最后求最大值的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 + +### 思路 2:滑动窗口(不定长度) + +1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内为连续递增序列。使用 $window\underline{\hspace{0.5em}}len$ 存储当前窗口大小,使用 $max\underline{\hspace{0.5em}}len$ 维护最大窗口长度。 +2. 一开始,$left$、$right$ 都指向 $0$。 +3. 将最右侧元素 $nums[right]$ 加入当前连续递增序列中,即当前窗口长度加 $1$(`window_len += 1`)。 +4. 判断当前元素 $nums[right]$ 是否满足连续递增序列。 +5. 如果 $right > 0$ 并且 $nums[right - 1] \ge nums[right]$ ,说明不满足连续递增序列,则将 $left$ 移动到窗口最右侧,重置当前窗口长度为 $1$(`window_len = 1`)。 +6. 记录当前连续递增序列的长度,并更新最长连续递增序列的长度。 +7. 继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +8. 输出最长连续递增序列的长度 $max\underline{\hspace{0.5em}}len$。 + +### 思路 2:代码 + +```python +class Solution: + def findLengthOfLCIS(self, nums: List[int]) -> int: + size = len(nums) + left, right = 0, 0 + window_len = 0 + max_len = 0 + + while right < size: + window_len += 1 + + if right > 0 and nums[right - 1] >= nums[right]: + left = right + window_len = 1 + + max_len = max(max_len, window_len) + right += 1 + + return max_len +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/longest-univalue-path.md b/docs/solutions/0600-0699/longest-univalue-path.md new file mode 100644 index 00000000..3e7c8eb0 --- /dev/null +++ b/docs/solutions/0600-0699/longest-univalue-path.md @@ -0,0 +1,119 @@ +# [0687. 最长同值路径](https://leetcode.cn/problems/longest-univalue-path/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0687. 最长同值路径 - 力扣](https://leetcode.cn/problems/longest-univalue-path/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 $root$。 + +**要求**:返回二叉树中最长的路径的长度,该路径中每个节点具有相同值。 这条路径可以经过也可以不经过根节点。 + +**说明**: + +- 树的节点数的范围是 $[0, 10^4]$。 +- $-1000 \le Node.val \le 1000$。 +- 树的深度将不超过 $1000$。 +- 两个节点之间的路径长度:由它们之间的边数表示。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/10/13/ex1.jpg) + +```python +输入:root = [5,4,5,1,1,5] +输出:2 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/10/13/ex2.jpg) + +```python +输入:root = [1,4,5,4,4,5] +输出:2 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +这道题如果先不考虑「路径中每个节点具有相同值」这个条件,那么这道题就是在求「二叉树的直径长度(最长路径的长度)」。 + +「二叉树的直径长度」的定义为:二叉树中任意两个节点路径长度中的最大值。并且这条路径可能穿过也可能不穿过根节点。 + +对于根为 $root$ 的二叉树来说,其直径长度并不简单等于「左子树高度」加上「右子树高度」。 + +根据路径是否穿过根节点,我们可以将二叉树分为两种: + +1. 直径长度所对应的路径穿过根节点,这种情况下:$\text{二叉树的直径} = \text{左子树高度} + \text{右子树高度}$。 +2. 直径长度所对应的路径不穿过根节点,这种情况下:$\text{二叉树的直径} = \text{所有子树中最大直径长度}$。 + +也就是说根为 $root$ 的二叉树的直径长度可能来自于 $\text{左子树高度} + \text{右子树高度}$,也可能来自于 $\text{子树中的最大直径}$,即 $\text{二叉树的直径} = max(\text{左子树高度} + \text{右子树高度}, \quad \text{所有子树中最大直径长度})$。 + +那么现在问题就变成为如何求「子树的高度」和「子树中的最大直径」。 + +1. 子树的高度:我们可以利用深度优先搜索方法,递归遍历左右子树,并分别返回左右子树的高度。 +2. 子树中的最大直径:我们可以在递归求解子树高度的时候维护一个 $ans$ 变量,用于记录所有 $\text{左子树高度} + \text{右子树高度}$ 中的最大值。 + +最终 $ans$ 就是我们所求的该二叉树的最大直径。 + +接下来我们再来加上「路径中每个节点具有相同值」这个限制条件。 + +1. 「左子树高度」应变为「左子树最长同值路径长度」。 +2. 「右子树高度」应变为「右子树最长同值路径长度」。 +3. 题目变为求「二叉树的最长同值路径长度」,式子为:$\text{二叉树的最长同值路径长度} = max(\text{左子树最长同值路径长度} + \text{右子树最长同值路径长度}, \quad \text{所有子树中最长同值路径长度})$。 + +在递归遍历的时候,我们还需要当前节点与左右子节点的值的相同情况,来维护更新「包含当前节点的最长同值路径长度」。 + +1. 在递归遍历左子树时,如果当前节点与左子树的值相同,则:$\text{包含当前节点向左的最长同值路径长度} = \text{左子树最长同值路径长度} + 1$,否则为 $0$。 +2. 在递归遍历左子树时,如果当前节点与左子树的值相同,则:$\text{包含当前节点向右的最长同值路径长度} = \text{右子树最长同值路径长度} + 1$,否则为 $0$。 + +则:$\text{包含当前节点向左的最长同值路径长度} = max(\text{包含当前节点向左的最长同值路径长度}, \quad \text{包含当前节点向右的最长同值路径长度})$。 + +### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def __init__(self): + self.ans = 0 + + def dfs(self, node): + if not node: + return 0 + + left_len = self.dfs(node.left) # 左子树高度 + right_len = self.dfs(node.right) # 右子树高度 + if node.left and node.left.val == node.val: + left_len += 1 + else: + left_len = 0 + if node.right and node.right.val == node.val: + right_len += 1 + else: + right_len = 0 + self.ans = max(self.ans, left_len + right_len) + return max(left_len, right_len) + + def longestUnivaluePath(self, root: Optional[TreeNode]) -> int: + self.dfs(root) + + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点个数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0600-0699/map-sum-pairs.md b/docs/solutions/0600-0699/map-sum-pairs.md new file mode 100644 index 00000000..3663c065 --- /dev/null +++ b/docs/solutions/0600-0699/map-sum-pairs.md @@ -0,0 +1,123 @@ +# [0677. 键值映射](https://leetcode.cn/problems/map-sum-pairs/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0677. 键值映射 - 力扣](https://leetcode.cn/problems/map-sum-pairs/) + +## 题目大意 + +**要求**:实现一个 MapSum 类,支持两个方法,`insert` 和 `sum`: + +- `MapSum()` 初始化 MapSum 对象。 +- `void insert(String key, int val)` 插入 `key-val` 键值对,字符串表示键 `key`,整数表示值 `val`。如果键 `key` 已经存在,那么原来的键值对将被替代成新的键值对。 +- `int sum(string prefix)` 返回所有以该前缀 `prefix` 开头的键 `key` 的值的总和。 + +**说明**: + +- $1 \le key.length, prefix.length \le 50$。 +- `key` 和 `prefix` 仅由小写英文字母组成。 +- $1 \le val \le 1000$。 +- 最多调用 $50$ 次 `insert` 和 `sum`。 + +**示例**: + +- 示例 1: + +```python +输入: +["MapSum", "insert", "sum", "insert", "sum"] +[[], ["apple", 3], ["ap"], ["app", 2], ["ap"]] +输出: +[null, null, 3, null, 5] + +解释: +MapSum mapSum = new MapSum(); +mapSum.insert("apple", 3); +mapSum.sum("ap"); // 返回 3 (apple = 3) +mapSum.insert("app", 2); +mapSum.sum("ap"); // 返回 5 (apple + app = 3 + 2 = 5) +``` + +## 解题思路 + +### 思路 1:字典树 + +可以构造前缀树(字典树)解题。 + +- 初始化时,构建一棵前缀树(字典树),并增加 `val` 变量。 + +- 调用插入方法时,用字典树存储 `key`,并在对应字母节点存储对应的 `val`。 +- 在调用查询总和方法时,先查找该前缀 `prefix` 对应的前缀树节点,从该节点开始,递归遍历该节点的子节点,并累积子节点的 `val`,进行求和,并返回求和累加结果。 + +### 思路 1:代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.value = 0 + + + def insert(self, word: str, value: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.value = value + + + def search(self, word: str) -> int: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return 0 + cur = cur.children[ch] + return self.dfs(cur) + + def dfs(self, root) -> int: + if not root: + return 0 + res = root.value + for node in root.children.values(): + res += self.dfs(node) + return res + + + +class MapSum: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.trie_tree = Trie() + + + def insert(self, key: str, val: int) -> None: + self.trie_tree.insert(key, val) + + + def sum(self, prefix: str) -> int: + return self.trie_tree.search(prefix) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:`insert` 操作的时间复杂度为 $O(|key|)$。其中 $|key|$ 是每次插入字符串 `key` 的长度。`sum` 操作的时间复杂度是 $O(|prefix|)$,其中 $O(| prefix |)$ 是查询字符串 `prefix` 的长度。 +- **空间复杂度**:$O(|T| \times m)$。其中 $|T|$ 表示字符串 `key` 的最大长度,$m$ 表示 `key - val` 的键值数目。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/max-area-of-island.md b/docs/solutions/0600-0699/max-area-of-island.md new file mode 100644 index 00000000..1c67bcaa --- /dev/null +++ b/docs/solutions/0600-0699/max-area-of-island.md @@ -0,0 +1,128 @@ +# [0695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0695. 岛屿的最大面积 - 力扣](https://leetcode.cn/problems/max-area-of-island/) + +## 题目大意 + +**描述**:给定一个只包含 $0$、$1$ 元素的二维数组,$1$ 代表岛屿,$0$ 代表水。一座岛的面积就是上下左右相邻的 $1$ 所组成的连通块的数目。 + +**要求**:计算出最大的岛屿面积。 + +**说明**: + +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 50$。 +- $grid[i][j]$ 为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg) + +```python +输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] +输出:6 +解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。 +``` + +- 示例 2: + +```python +输入:grid = [[0,0,0,0,0,0,0,0]] +输出:0 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +1. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: + 1. 将该位置上的值置为 $0$(防止二次重复计算)。 + 2. 递归搜索该位置上下左右四个位置,并统计搜到值为 $1$ 的元素个数。 + 3. 返回值为 $1$ 的元素个数(即为该岛的面积)。 +2. 维护并更新最大的岛面积。 +3. 返回最大的到面积。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, grid, i, j): + n = len(grid) + m = len(grid[0]) + if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == 0: + return 0 + ans = 1 + grid[i][j] = 0 + ans += self.dfs(grid, i + 1, j) + ans += self.dfs(grid, i, j + 1) + ans += self.dfs(grid, i - 1, j) + ans += self.dfs(grid, i, j - 1) + return ans + + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + ans = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == 1: + ans = max(ans, self.dfs(grid, i, j)) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(n \times m)$。 + +### 思路 2:广度优先搜索 + +1. 使用 $ans$ 记录最大岛屿面积。 +2. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素: + 1. 将该元素置为 $0$。并使用队列 $queue$ 存储该节点位置。使用 $temp\underline{\hspace{0.5em}}ans$ 记录当前岛屿面积。 + 2. 然后从队列 $queue$ 中取出第一个节点位置 $(i, j)$。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 $0$(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`。 + 3. 不断重复上一步骤,直到队列 $queue$ 为空。 + 4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`。 +3. 将 $ans$ 作为答案返回。 + +### 思路 2:代码 + +```python +import collections + +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + rows, cols = len(grid), len(grid[0]) + ans = 0 + for i in range(rows): + for j in range(cols): + if grid[i][j] == 1: + grid[i][j] = 0 + temp_ans = 1 + q = collections.deque([(i, j)]) + while q: + i, j = q.popleft() + for direct in directs: + new_i = i + direct[0] + new_j = j + direct[1] + if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols or grid[new_i][new_j] == 0: + continue + grid[new_i][new_j] = 0 + q.append((new_i, new_j)) + temp_ans += 1 + + ans = max(ans, temp_ans) + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(n \times m)$。 diff --git a/docs/solutions/0600-0699/maximum-average-subarray-i.md b/docs/solutions/0600-0699/maximum-average-subarray-i.md new file mode 100644 index 00000000..bcae8639 --- /dev/null +++ b/docs/solutions/0600-0699/maximum-average-subarray-i.md @@ -0,0 +1,80 @@ +# [0643. 子数组最大平均数 I](https://leetcode.cn/problems/maximum-average-subarray-i/) + +- 标签:数组、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [0643. 子数组最大平均数 I - 力扣](https://leetcode.cn/problems/maximum-average-subarray-i/) + +## 题目大意 + +**描述**:给定一个由 $n$ 个元素组成的整数数组 $nums$ 和一个整数 $k$。 + +**要求**:找出平均数最大且长度为 $k$ 的连续子数组,并输出该最大平均数。 + +**说明**: + +- 任何误差小于 $10^{-5}$ 的答案都将被视为正确答案。 +- $n == nums.length$。 +- $1 \le k \le n \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,12,-5,-6,50,3], k = 4 +输出:12.75 +解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75 +``` + +- 示例 2: + +```python +输入:nums = [5], k = 1 +输出:5.00000 +``` + +## 解题思路 + +### 思路 1:滑动窗口(固定长度) + +这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 $k$。具体做法如下: + +1. $ans$ 用来维护子数组最大平均数,初始值为负无穷,即 `float('-inf')`。$window\underline{\hspace{0.5em}}total$ 用来维护窗口中元素的和。 +2. $left$ 、$right$ 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +3. 向右移动 $right$,先将 $k$ 个元素填入窗口中。 +4. 当窗口元素个数为 $k$ 时,即:$right - left + 1 >= k$ 时,计算窗口内的元素和平均值,并维护子数组最大平均数。 +5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 +6. 重复 $4 \sim 5$ 步,直到 $right$ 到达数组末尾。 +7. 最后输出答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def findMaxAverage(self, nums: List[int], k: int) -> float: + left = 0 + right = 0 + window_total = 0 + ans = float('-inf') + while right < len(nums): + window_total += nums[right] + + if right - left + 1 >= k: + ans = max(window_total / k, ans) + window_total -= nums[left] + left += 1 + + # 向右侧增大窗口 + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为数组 $nums$ 的元素个数。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/maximum-binary-tree.md b/docs/solutions/0600-0699/maximum-binary-tree.md new file mode 100644 index 00000000..77ba36f5 --- /dev/null +++ b/docs/solutions/0600-0699/maximum-binary-tree.md @@ -0,0 +1,49 @@ +# [0654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) + +- 标签:栈、树、数组、分治、二叉树、单调栈 +- 难度:中等 + +## 题目链接 + +- [0654. 最大二叉树 - 力扣](https://leetcode.cn/problems/maximum-binary-tree/) + +## 题目大意 + +给定一个不含重复元素的整数数组 `nums`。一个以此数组构建的最大二叉树定义如下: + +- 二叉树的根是数组中的最大元素。 +- 左子树是通过数组中最大值左边部分构造出的最大二叉树。 +- 右子树是通过数组中最大值右边部分构造出的最大二叉树。 + +要求通过给定的数组构建最大二叉树,并且输出这个树的根节点。 + +## 解题思路 + +根据题意可知,数组中最大元素位置为根节点,最大元素位置左右部分可分别作为左右子树。则我们可以通过递归的方式构建最大二叉树。 + +- 定义 left、right 分别表示当前数组的左右边界位置,定义 `max_value_index` 为当前数组中最大值位置。 +- 遍历当前数组,找到最大值位置 `max_value_index`,并建立根节点 `root`,将数组 `nums` 分为 `[left, max_value_index]` 和 `[max_value_index, right]` 两部分,并分别递归建树。 +- 将其赋值给 `root` 的左右子节点,最后返回 root 节点。 + +## 代码 + +```python +class Solution: + def createBinaryTree(self, nums: List[int], left: int, right: int) -> TreeNode: + if left >= right: + return None + max_value_index = left + for i in range(left + 1, right): + if nums[i] > nums[max_value_index]: + max_value_index = i + + root = TreeNode(nums[max_value_index]) + root.left = self.createBinaryTree(nums, left, max_value_index) + root.right = self.createBinaryTree(nums, max_value_index + 1, right) + + return root + + def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: + return self.createBinaryTree(nums, 0, len(nums)) +``` + diff --git a/docs/solutions/0600-0699/maximum-width-of-binary-tree.md b/docs/solutions/0600-0699/maximum-width-of-binary-tree.md new file mode 100644 index 00000000..ecb9ad59 --- /dev/null +++ b/docs/solutions/0600-0699/maximum-width-of-binary-tree.md @@ -0,0 +1,85 @@ +# [0662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0662. 二叉树最大宽度 - 力扣](https://leetcode.cn/problems/maximum-width-of-binary-tree/) + +## 题目大意 + +**描述**:给你一棵二叉树的根节点 `root`。 + +**要求**:返回树的最大宽度。 + +**说明**: + +- **每一层的宽度**:为该层最左和最右的非空节点(即两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 `null` 节点,这些 `null` 节点也计入长度。 +- **树的最大宽度**:是所有层中最大的宽度。 +- 题目数据保证答案将会在 32 位带符号整数范围内。 +- 树中节点的数目范围是 $[1, 3000]$。 +- $-100 \le Node.val \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/05/03/width1-tree.jpg) + +```python +输入:root = [1,3,2,5,3,null,9] +输出:4 +解释:最大宽度出现在树的第 3 层,宽度为 4 (5,3,null,9)。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2022/03/14/maximum-width-of-binary-tree-v3.jpg) + +```python +输入:root = [1,3,2,5,null,null,9,6,null,7] +输出:7 +解释:最大宽度出现在树的第 4 层,宽度为 7 (6,null,null,null,null,null,7) 。 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +最直观的做法是,求出每一层的宽度,然后求出所有层高度的最大值。 + +在计算每一层宽度时,根据题意,两端点之间的 `null` 节点也计入长度,所以我们可以对包括 `null` 节点在内的该二叉树的所有节点进行编号。 + +也就是满二叉树的编号规则:如果当前节点的编号为 $i$,则左子节点编号记为 $i \times 2 + 1$,则右子节点编号为 $i \times 2 + 2$。 + +接下来我们使用广度优先搜索方法遍历每一层的节点,在向队列中添加节点时,将该节点与该节点对应的编号一同存入队列中。 + +这样在计算每一层节点的宽度时,我们可以通过队列中队尾节点的编号与队头节点的编号,快速计算出当前层的宽度。并计算出所有层宽度的最大值。 + +### 思路 1:代码 + +```python +class Solution: + def widthOfBinaryTree(self, root: Optional[TreeNode]) -> int: + if not root: + return False + + queue = collections.deque([[root, 0]]) + ans = 0 + while queue: + ans = max(ans, queue[-1][1] - queue[0][1] + 1) + size = len(queue) + for _ in range(size): + cur, index = queue.popleft() + if cur.left: + queue.append([cur.left, index * 2 + 1]) + if cur.right: + queue.append([cur.right, index * 2 + 2]) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0600-0699/merge-two-binary-trees.md b/docs/solutions/0600-0699/merge-two-binary-trees.md new file mode 100644 index 00000000..9f601a00 --- /dev/null +++ b/docs/solutions/0600-0699/merge-two-binary-trees.md @@ -0,0 +1,39 @@ +# [0617. 合并二叉树](https://leetcode.cn/problems/merge-two-binary-trees/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0617. 合并二叉树 - 力扣](https://leetcode.cn/problems/merge-two-binary-trees/) + +## 题目大意 + +给定两个二叉树,将两个二叉树合并成一个新的二叉树。合并规则如下: + +- 如果两个二叉树对应节点重叠,则将两个节点的值相加并作为新的二叉树节点。 +- 如果两个二叉树对应节点其中一个为空,另一个不为空,则将不为空的节点左心新的二叉树节点。 + +最终返回新的二叉树的根节点。 + +## 解题思路 + +利用前序遍历二叉树,并按照规则递归建立二叉树。将其对应节点值相加或者取其中不为空的节点做为新节点。 + +## 代码 + +```python +class Solution: + def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: + if not root1: + return root2 + if not root2: + return root1 + + merged = TreeNode(root1.val + root2.val) + merged.left = self.mergeTrees(root1.left, root2.left) + merged.right = self.mergeTrees(root1.right, root2.right) + return merged + +``` + diff --git a/docs/solutions/0600-0699/non-decreasing-array.md b/docs/solutions/0600-0699/non-decreasing-array.md new file mode 100644 index 00000000..be989df8 --- /dev/null +++ b/docs/solutions/0600-0699/non-decreasing-array.md @@ -0,0 +1,45 @@ +# [0665. 非递减数列](https://leetcode.cn/problems/non-decreasing-array/) + +- 标签:数组 +- 难度:中等 + +## 题目链接 + +- [0665. 非递减数列 - 力扣](https://leetcode.cn/problems/non-decreasing-array/) + +## 题目大意 + +给定一个整数数组 nums,问能否在最多改变 1 个元素的条件下,使数组变为非递减序列。若能,返回 True,不能则返回 False。 + +## 解题思路 + +循环遍历数组,寻找 nums[i] > nums[i+1] 的情况,一旦这种情况出现超过 2 次,则不可能最多改变 1 个元素,直接返回 False。 + +遇到 nums[i] > nums[i+1] 的情况,应该手动调节某位置上元素使数组有序。此时,有两种选择: + +- 将 nums[i] 调低,与 nums[i-1] 持平 +- 将 nums[i+1] 调高,与 nums[i] 持平 + +若选择第一种调节方式,如果调节前 nums[i-1] > nums[i+1],那么调节完 nums[i] 之后,nums[i-1] 还是比 nums[i+1] 大,不可取。 + +所以应选择第二种调节方式,如果调节前 nums[i-1] > nums[i+1],那么调节完 nums[i+1] 之后 nums[i-1] < nums[i] <= nums[i+1],满足非递减要求。 + +最终如果最多调整过一次,且 nums[i] > nums[i+1] 的情况也最多出现过一次,则返回 True。 + +## 代码 + +```python +class Solution: + def checkPossibility(self, nums: List[int]) -> bool: + count = 0 + for i in range(len(nums)-1): + if nums[i] > nums[i+1]: + count += 1 + if count > 1: + return False + if i > 0 and nums[i-1] > nums[i+1]: + nums[i+1] = nums[i] + + return True +``` + diff --git a/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md b/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md new file mode 100644 index 00000000..4a45a162 --- /dev/null +++ b/docs/solutions/0600-0699/non-negative-integers-without-consecutive-ones.md @@ -0,0 +1,108 @@ +# [0600. 不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) + +- 标签:动态规划 +- 难度:困难 + +## 题目链接 + +- [0600. 不含连续1的非负整数 - 力扣](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) + +## 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:统计在 $[0, n]$ 范围的非负整数中,有多少个整数的二进制表示中不存在连续的 $1$。 + +**说明**: + +- $1 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入: n = 5 +输出: 5 +解释: +下面列出范围在 [0, 5] 的非负整数与其对应的二进制表示: +0 : 0 +1 : 1 +2 : 10 +3 : 11 +4 : 100 +5 : 101 +其中,只有整数 3 违反规则(有两个连续的 1 ),其他 5 个满足规则。 +``` + +- 示例 2: + +```python +输入: n = 1 +输出: 2 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, pre, isLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。其中: + +1. $pos$ 表示当前枚举的数位位置。 +2. $pre$ 表示前一位是否为 $1$,用于过滤连续 $1$ 的不合法方案。 +3. $isLimit$ 表示前一位数位是否等于上界,用于限制本次搜索的数位范围。 + +接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, False, True)` 开始递归。 `dfs(0, False, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 开始时前一位不为 $1$。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,当前为合法方案,此时:直接返回方案数 $1$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 因为不需要考虑前导 $0$,所以当前所能选择的最小数字 $minX$ 为 $0$。 +5. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 +6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 +7. 如果前一位为 $1$ 并且当前为 $d$ 也为 $1$,则说明当前方案出现了连续的 $1$,则跳过。 +8. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, d == 1, isLimit and d == maxX)`。 + 1. `d == 1` 表示下一位 $pos - 1$ 的前一位 $pos$ 是否为 $1$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 +9. 最后的方案数为 `dfs(0, False, True)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def findIntegers(self, n: int) -> int: + # 将 n 的二进制转换为字符串 s + s = str(bin(n))[2:] + + @cache + # pos: 第 pos 个数位 + # pre: 第 pos - 1 位是否为 1 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + def dfs(pos, pre, isLimit): + if pos == len(s): + return 1 + + ans = 0 + # 不需要考虑前导 0,则最小可选择数字为 0 + minX = 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 1。 + maxX = int(s[pos]) if isLimit else 1 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + if pre and d == 1: + continue + ans += dfs(pos + 1, d == 1, isLimit and d == maxX) + + return ans + + return dfs(0, False, True) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 diff --git a/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md b/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md new file mode 100644 index 00000000..6035fc70 --- /dev/null +++ b/docs/solutions/0600-0699/number-of-longest-increasing-subsequence.md @@ -0,0 +1,225 @@ +# [0673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) + +- 标签:树状数组、线段树、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0673. 最长递增子序列的个数 - 力扣](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) + +## 题目大意 + +**描述**:给定一个未排序的整数数组 `nums`。 + +**要求**:返回最长递增子序列的个数。 + +**说明**: + +- 子数列必须是严格递增的。 +- $1 \le nums.length \le 2000$。 +- $-10^6 \le nums[i] \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:[1,3,5,4,7] +输出:2 +解释:有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +可以先做题目 [0300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)。 + +动态规划的状态 `dp[i]` 表示为:以第 `i` 个数字结尾的前 `i` 个元素中最长严格递增子序列的长度。 + +两重循环遍历前 `i` 个数字,对于 $0 \le j \le i$: + +- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 `i` 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。 +- 当 `nums[j] ≥ nums[i]` 时,可以直接跳过。 + +则状态转移方程为:`dp[i] = max(dp[i], dp[j] + 1)`,`0 ≤ j ≤ i`,`nums[j] < nums[i]`。 + +最后再遍历一遍 dp 数组,求出最大值即为最长递增子序列的长度。 + +现在求最长递增子序列的个数。则需要在求解的过程中维护一个 `count` 数组,用来保存以 `nums[i]` 结尾的最长递增子序列的个数。 + +对于 $0 \le j \le i$: + +- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 > dp[i]` 时,说明第一次找到 `dp[j] + 1`长度且以`nums[i]`结尾的最长递增子序列,则以 `nums[i]` 结尾的最长递增子序列的组合数就等于以 `nums[j]` 结尾的组合数,即 `count[i] = count[j]`。 +- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 == dp[i]` 时,说明以 `nums[i]` 结尾且长度为 `dp[j] + 1` 的递增序列已找到过一次了,则以 `nums[i]` 结尾的最长递增子序列的组合数要加上以 `nums[j]` 结尾的组合数,即 `count[i] += count[j]`。 + +- 然后根据遍历 dp 数组得到的最长递增子序列的长度 max_length,然后再一次遍历 dp 数组,将所有 `dp[i] == max_length` 情况下的组合数 `coun[i]` 累加起来,即为最长递增序列的个数。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def findNumberOfLIS(self, nums: List[int]) -> int: + size = len(nums) + dp = [1 for _ in range(size)] + count = [1 for _ in range(size)] + for i in range(size): + for j in range(i): + if nums[j] < nums[i]: + if dp[j] + 1 > dp[i]: + dp[i] = dp[j] + 1 + count[i] = count[j] + elif dp[j] + 1 == dp[i]: + count[i] += count[j] + + max_length = max(dp) + res = 0 + for i in range(size): + if dp[i] == max_length: + res += count[i] + return res +``` + +### 思路 2:线段树 + +题目中 `nums` 的长度 为 $[1, 2000]$,值域为 $[-10^6, 10^6]$。 + +值域范围不是特别大,我们可以直接用线段树保存整个值域区间。但因为数组的长度只有 `2000`,所以算法效率更高的做法是先对数组进行离散化处理。把数组中的元素按照大小依次映射到 `[0, len(nums) - 1]` 这个区间。 + +1. 构建一棵长度为 `len(nums)` 的线段树,其中每个线段树的节点保存一个二元组。这个二元组 `val = [length, count]` 用来表示:以当前节点为结尾的子序列所能达到的最长递增子序列长度 `length` 和最长递增子序列对应的数量 `count`。 +2. 顺序遍历数组 `nums`。对于当前元素 `nums[i]`: +3. 查找 `[0, nums[i - 1]]` 离散化后对应区间节点的二元组,也就是查找以区间 `[0, nums[i - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量,即 `val = [length, count]`。 + - 如果所能达到的最长递增子序列长度为 `0`,则加入 `nums[i]` 之后最长递增子序列长度变为 `1`,且数量也变为 `1`。 + - 如果所能达到的最长递增子序列长度不为 `0`,则加入 `nums[i]` 之后最长递增子序列长度 +1,但数量不变。 +4. 根据上述计算的 `val` 值更新 `nums[i]` 对应节点的 `val` 值。 +5. 然后继续向后遍历,重复进行第 `3` ~ `4` 步操作。 +6. 最后查询以区间 `[0, nums[len(nums) - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量。返回对应的数量即为答案。 + +### 思路 2:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=[0, 1]): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, size): + self.size = size + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.__update_point(i, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = [0, 0] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + + self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值 + + # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == i and right == i: + self.tree[index].val = self.merge(self.tree[index].val, val) + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值 + + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return [0, 0] + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = [0, 0] + res_right = [0, 0] + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + # 返回合并结果 + return self.merge(res_left, res_right) + + # 向上合并实现方法 + def merge(self, val1, val2): + val = [0, 0] + if val1[0] == val2[0]: # 递增子序列长度一致,则合并后最长递增子序列个数为之前两者之和 + val = [val1[0], val1[1] + val2[1]] + elif val1[0] < val2[0]: # 如果递增子序列长度不一致,则合并后最长递增子序列个数取较长一方的个数 + val = [val2[0], val2[1]] + else: + val = [val1[0], val1[1]] + return val + +class Solution: + def findNumberOfLIS(self, nums: List[int]) -> int: + + # 离散化处理 + num_dict = dict() + nums_sort = sorted(nums) + for i in range(len(nums_sort)): + num_dict[nums_sort[i]] = i + + # 构造线段树 + self.STree = SegmentTree(len(nums_sort)) + + for num in nums: + index = num_dict[num] + # 查询 [0, nums[index - 1]] 区间上以 nums[index - 1] 结尾的子序列所能达到的最长递增子序列长度和对应数量 + val = self.STree.query_interval(0, index - 1) + # 如果当前最长递增子序列长度为 0,则加入 num 之后最长递增子序列长度为 1,且数量为 1 + # 如果当前最长递增子序列长度不为 0,则加入 num 之后最长递增子序列长度 +1,但数量不变 + if val[0] == 0: + val = [1, 1] + else: + val = [val[0] + 1, val[1]] + self.STree.update_point(index, val) + return self.STree.query_interval(0, len(nums_sort) - 1)[1] +``` + diff --git a/docs/solutions/0600-0699/palindromic-substrings.md b/docs/solutions/0600-0699/palindromic-substrings.md new file mode 100644 index 00000000..933aa444 --- /dev/null +++ b/docs/solutions/0600-0699/palindromic-substrings.md @@ -0,0 +1,57 @@ +# [0647. 回文子串](https://leetcode.cn/problems/palindromic-substrings/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0647. 回文子串 - 力扣](https://leetcode.cn/problems/palindromic-substrings/) + +## 题目大意 + +给定一个字符串 `s`,计算 `s` 中有多少个回文子串。 + +## 解题思路 + +动态规划求解。 + +先定义状态 `dp[i][j]` 表示为区间 `[i, j]` 的子串是否为回文子串,如果是,则 `dp[i][j] = True`,如果不是,则 `dp[i][j] = False`。 + +接下来确定状态转移共识: + +如果 `s[i] == s[j]`,分为以下几种情况: + +- `i == j`,单字符肯定是回文子串,`dp[i][j] == True`。 +- `j - i == 1`,比如 `aa` 肯定也是回文子串,`dp[i][j] = True`。 +- 如果 `j - i > 1`,则需要看 `[i + 1, j - 1]` 区间是不是回文子串,`dp[i][j] = dp[i + 1][j - 1]`。 + +如果 `s[i] != s[j]`,那肯定不是回文子串,`dp[i][j] = False`。 + +下一步确定遍历方向。 + +由于 `dp[i][j]` 依赖于 `dp[i + 1][j - 1]`,所以我们可以从左下角向右上角遍历。 + +同时,在递推过程中记录下 `dp[i][j] == True` 的个数,即为最后结果。 + +## 代码 + +```python +class Solution: + def countSubstrings(self, s: str) -> int: + size = len(s) + dp = [[False for _ in range(size)] for _ in range(size)] + res = 0 + for i in range(size - 1, -1, -1): + for j in range(i, size): + if s[i] == s[j]: + if j - i <= 1: + dp[i][j] = True + else: + dp[i][j] = dp[i + 1][j - 1] + else: + dp[i][j] = False + if dp[i][j]: + res += 1 + return res +``` + diff --git a/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md b/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md new file mode 100644 index 00000000..a58e36f0 --- /dev/null +++ b/docs/solutions/0600-0699/partition-to-k-equal-sum-subsets.md @@ -0,0 +1,136 @@ +# [0698. 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) + +- 标签:位运算、记忆化搜索、数组、动态规划、回溯、状态压缩 +- 难度:中等 + +## 题目链接 + +- [0698. 划分为k个相等的子集 - 力扣](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个正整数 $k$。 + +**要求**:找出是否有可能把这个数组分成 $k$ 个非空子集,其总和都相等。 + +**说明**: + +- $1 \le k \le len(nums) \le 16$。 +- $0 < nums[i] < 10000$。 +- 每个元素的频率在 $[1, 4]$ 范围内。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 +输出: True +说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。 +``` + +- 示例 2: + +```python +输入: nums = [1,2,3,4], k = 3 +输出: False +``` + +## 解题思路 + +### 思路 1:状态压缩 DP + +根据题目要求,我们可以将几种明显不符合要求的情况过滤掉,比如:元素个数小于 $k$、元素总和不是 $k$ 的倍数、数组 $nums$ 中最大元素超过 $k$ 等分的目标和这几种情况。 + +然后再来考虑一般情况下,如何判断是否符合要求。 + +因为题目给定数组 $nums$ 的长度最多为 $16$,所以我们可以使用一个长度为 $16$ 位的二进制数来表示数组子集的选择状态。我们可以定义 $dp[state]$ 表示为当前选择状态下,是否可行。如果 $dp[state] == True$,表示可行;如果 $dp[state] == False$,则表示不可行。 + +接下来使用动态规划方法,进行求解。具体步骤如下: + +###### 1. 划分阶段 + +按照数组元素选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[state]$ 表示为:当数组元素选择情况为 $state$ 时,是否存在一种方案,使得方案中的数字必定能分割成 $p(0 \le p \le k)$ 组恰好数字和等于目标和 $target$ 的集合和至多 $1$ 组数字和小于目标和 $target$ 的集合。 + +###### 3. 状态转移方程 + +对于当前状态 $state$,如果: + +1. 当数组元素选择情况为 $state$ 时可行,即 $dp[state] == True$; +2. 第 $i$ 位数字没有被使用; +3. 加上第 $i$ 位元素后的状态为 $next\underline{\hspace{0.5em}}state$; +4. 加上第 $i$ 位元素后没有超出目标和。 + +则:$dp[next\underline{\hspace{0.5em}}state] = True$。 + +###### 4. 初始条件 + +- 当不选择任何元素时,可按照题目要求 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当数组元素选择情况为 $state$ 时,是否存在一种方案,使得方案中的数字必定能分割成 $p(0 \le p \le k)$ 组恰好数字和等于目标和 $target$ 的集合和至多 $1$ 组数字和小于目标和 $target$ 的集合。 + +所以当 $state == 1 << n - 1$ 时,状态就变为了:当数组元素都选上的情况下,是否存在一种方案,使得方案中的数字必定能分割成 $k$ 组恰好数字和等于目标和 $target$ 的集合。 + +这里之所以是 $k$ 组恰好数字和等于目标和 $target$ 的集合,是因为一开我们就限定了 $total \mod k == 0$ 这个条件,所以只能是 $k$ 组恰好数字和等于目标和 $target$ 的集合。 + +所以最终结果为 $dp[states - 1]$,其中 $states = 1 << n$。 + +### 思路 1:代码 + +```python +class Solution: + def canPartitionKSubsets(self, nums: List[int], k: int) -> bool: + size = len(nums) + if size < k: # 元素个数小于 k + return False + + total = sum(nums) + if total % k != 0: # 元素总和不是 k 的倍数 + return False + + target = total // k + if nums[-1] > target: # 最大元素超过 k 等分的目标和 + return False + + nums.sort() + states = 1 << size # 子集选择状态总数 + cur_sum = [0 for _ in range(states)] + dp = [False for _ in range(states)] + dp[0] = True + + for state in range(states): + if not dp[state]: # 基于 dp[state] == True 前提下进行转移 + continue + for i in range(size): + if state & (1 << i) != 0: # 当前数字已被使用 + continue + + if cur_sum[state] % target + nums[i] > target: + break # 如果加入当前数字超出目标和,则后续不用继续遍历 + + next_state = state | (1 << i) # 加入当前数字 + if dp[next_state]: # 如果新状态能划分,则跳过继续 + continue + + cur_sum[next_state] = cur_sum[state] + nums[i] # 更新新状态下子集和 + dp[next_state] = True # 更新新状态 + if dp[states - 1]: # 找到一个符合要求的划分方案,提前返回 + return True + + return dp[states - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(2^n)$。 + +## 参考资料 + +- 【题解】[状态压缩的定义理解 - 划分为k个相等的子集](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/solution/zhuang-tai-ya-suo-de-ding-yi-li-jie-by-c-fo1b/) diff --git a/docs/solutions/0600-0699/redundant-connection.md b/docs/solutions/0600-0699/redundant-connection.md new file mode 100644 index 00000000..9de1644a --- /dev/null +++ b/docs/solutions/0600-0699/redundant-connection.md @@ -0,0 +1,96 @@ +# [0684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0684. 冗余连接 - 力扣](https://leetcode.cn/problems/redundant-connection/) + +## 题目大意 + +**描述**:一个 `n` 个节点的树(节点值为 `1~n`)添加一条边后就形成了图,添加的这条边不属于树中已经存在的边。图的信息记录存储与长度为 `n` 的二维数组 `edges`,`edges[i] = [ai, bi]` 表示图中在 `ai` 和 `bi` 之间存在一条边。 + +现在给定代表边信息的二维数组 `edges`。 + +**要求**:找到一条可以山区的边,使得删除后的剩余部分是一个有着 `n` 个节点的树。如果有多个答案,则返回数组 `edges` 中最后出现的边。 + +**说明**: + +- $n == edges.length$。 +- $3 \le n \le 1000$。 +- $edges[i].length == 2$。 +- $1 \le ai < bi \le edges.length$。 +- $ai ≠ bi$。 +- $edges$ 中无重复元素。 +- 给定的图是连通的。 + +**示例**: + +- 示例 1: + +![img](https://pic.leetcode-cn.com/1626676174-hOEVUL-image.png) + +```python +输入: edges = [[1,2], [1,3], [2,3]] +输出: [2,3] +``` + +- 示例 2: + +![img](https://pic.leetcode-cn.com/1626676179-kGxcmu-image.png) + +```python +输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]] +输出: [1,4] +``` + +## 解题思路 + +### 思路 1:并查集 + +树可以看做是无环的图,这道题就是要找出那条添加边之后成环的边。可以考虑用并查集来做。 + +1. 从前向后遍历每一条边。 +2. 如果边的两个节点不在同一个集合,就加入到一个集合(链接到同一个根节点)。 +3. 如果边的节点已经出现在同一个集合里,说明边的两个节点已经连在一起了,再加入这条边一定会出现环,则这条边就是所求答案。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + self.parent[root_x] = root_y + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: + size = len(edges) + union_find = UnionFind(size + 1) + + for edge in edges: + if union_find.is_connected(edge[0], edge[1]): + return edge + union_find.union(edge[0], edge[1]) + + return None +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是图中的节点个数,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/repeated-string-match.md b/docs/solutions/0600-0699/repeated-string-match.md new file mode 100644 index 00000000..c4fcd906 --- /dev/null +++ b/docs/solutions/0600-0699/repeated-string-match.md @@ -0,0 +1,117 @@ +# [0686. 重复叠加字符串匹配](https://leetcode.cn/problems/repeated-string-match/) + +- 标签:字符串、字符串匹配 +- 难度:中等 + +## 题目链接 + +- [0686. 重复叠加字符串匹配 - 力扣](https://leetcode.cn/problems/repeated-string-match/) + +## 题目大意 + +**描述**:给定两个字符串 `a` 和 `b`。 + +**要求**:寻找重复叠加字符串 `a` 的最小次数,使得字符串 `b` 成为叠加后的字符串 `a` 的子串,如果不存在则返回 `-1`。 + +**说明**: + +- 字符串 `"abc"` 重复叠加 `0` 次是 `""`,重复叠加 `1` 次是 `"abc"`,重复叠加 `2` 次是 `"abcabc"`。 +- $1 \le a.length \le 10^4$。 +- $1 \le b.length \le 10^4$。 +- `a` 和 `b` 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:a = "abcd", b = "cdabcdab" +输出:3 +解释:a 重复叠加三遍后为 "abcdabcdabcd", 此时 b 是其子串。 +``` + +- 示例 2: + +```python +输入:a = "a", b = "aa" +输出:2 +``` + +## 解题思路 + +### 思路 1:KMP 算法 + +假设字符串 `a` 的长度为 `n`,`b` 的长度为 `m`。 + +把 `b` 看做是模式串,把字符串 `a` 叠加后的字符串看做是文本串,这道题就变成了单模式串匹配问题。 + +我们可以模拟叠加字符串 `a` 后进行单模式串匹配问题。模拟叠加字符串可以通过在遍历字符串匹配时对字符串 `a` 的长度 `n` 取余来实现。 + +那么问题关键点就变为了如何高效的进行单模式串匹配,以及字符串循环匹配的退出条件是什么。 + +**单模式串匹配问题**:可以用 KMP 算法来做。 + +**循环匹配退出条件问题**:假设我们用 `i` 遍历 `a` 叠加后字符串,用 `j` 遍历字符串 `b`。如果字符串 `b` 是 `a` 叠加后字符串的子串,那么 `b` 有两种可能: + +1. `b` 直接是原字符串 `a` 的子串:这种情况下,最多遍历到 `len(a)`。 +2. `b` 是 `a` 叠加后的字符串的子串: + 1. 最多遍历到 `len(a) + len(b)`,可以写为 `while i < len(a) + len(b):`,当 `i == len(a) + len(b)` 时跳出循环。 + 2. 也可以写为 `while i - j < len(a):`,这种写法中 `i - j ` 表示的是字符匹配开始的位置,如果匹配到 `len(a)` 时(即 `i - j == len(a)` 时)最开始位置的字符仍没有匹配,那么 `b` 也不可能是 `a` 叠加后的字符串的子串了,此时跳出循环。 + +最后我们需要计算一下重复叠加字符串 `a` 的最小次数。假设 `index` 使我们求出的匹配位置。 + +1. 如果 `index == -1`,则说明 `b` 不可能是 `a` 叠加后的字符串的子串,返回 `False`。 +2. 如果 `len(a) - index >= len(b)`,则说明匹配位置未超过字符串 `a` 的长度,叠加 `1` 次(字符串 `a` 本身)就可以匹配。 +3. 如果 `len(a) - index < len(b)`,则说明需要叠加才能匹配。此时最小叠加次数为 $\lfloor \frac{index + len(b) - 1}{len(a)} \rfloor + 1$。其中 `index` 代笔匹配开始前的字符串长度,加上 `len(b)` 后就是匹配到字符串 `b` 结束时最少需要的字符数,再 `-1` 是为了向下取整。 除以 `len(a)` 表示至少需要几个 `a`, 因为是向下取整,所以最后要加上 `1`。写成代码就是:`(index + len(b) - 1) // len(a) + 1`。 + +### 思路 1:代码 + +```python +class Solution: + # KMP 匹配算法,T 为文本串,p 为模式串 + def kmp(self, T: str, p: str) -> int: + n, m = len(T), len(p) + + next = self.generateNext(p) + + i, j = 0, 0 + while i - j < n: + while j > 0 and T[i % n] != p[j]: + j = next[j - 1] + if T[i % n] == p[j]: + j += 1 + if j == m: + return i - m + 1 + i += 1 + return -1 + + def generateNext(self, p: str): + m = len(p) + next = [0 for _ in range(m)] + + left = 0 + for right in range(1, m): + while left > 0 and p[left] != p[right]: + left = next[left - 1] + if p[left] == p[right]: + left += 1 + next[right] = left + + return next + + def repeatedStringMatch(self, a: str, b: str) -> int: + len_a = len(a) + len_b = len(b) + index = self.kmp(a, b) + if index == -1: + return -1 + if len_a - index >= len_b: + return 1 + return (index + len(b) - 1) // len(a) + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中文本串 $a$ 的长度为 $n$,模式串 $b$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + diff --git a/docs/solutions/0600-0699/replace-words.md b/docs/solutions/0600-0699/replace-words.md new file mode 100644 index 00000000..d7eda529 --- /dev/null +++ b/docs/solutions/0600-0699/replace-words.md @@ -0,0 +1,110 @@ +# [0648. 单词替换](https://leetcode.cn/problems/replace-words/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0648. 单词替换 - 力扣](https://leetcode.cn/problems/replace-words/) + +## 题目大意 + +**描述**:给定一个由许多词根组成的字典列表 `dictionary`,以及一个句子字符串 `sentence`。 + +**要求**:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。 + +**说明**: + +- $1 \le dictionary.length \le 1000$。 +- $1 \le dictionary[i].length \le 100$。 +- `dictionary[i]` 仅由小写字母组成。 +- $1 \le sentence.length \le 10^6$。 +- `sentence` 仅由小写字母和空格组成。 +- `sentence` 中单词的总量在范围 $[1, 1000]$ 内。 +- `sentence` 中每个单词的长度在范围 $[1, 1000]$ 内。 +- `sentence` 中单词之间由一个空格隔开。 +- `sentence` 没有前导或尾随空格。 + +**示例**: + +- 示例 1: + +```python +输入:dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" +输出:"the cat was rat by the bat" +``` + +- 示例 2: + +```python +输入:dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" +输出:"a a b c" +``` + +## 解题思路 + +### 思路 1:字典树 + +1. 构造一棵字典树。 +2. 将所有的词根存入到前缀树(字典树)中。 +3. 然后在树上查找每个单词的最短词根。 + +### 思路 1:代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> str: + """ + Returns if the word is in the trie. + """ + cur = self + index = 0 + for ch in word: + if ch not in cur.children: + return word + cur = cur.children[ch] + index += 1 + if cur.isEnd: + break + return word[:index] + + +class Solution: + def replaceWords(self, dictionary: List[str], sentence: str) -> str: + trie_tree = Trie() + for word in dictionary: + trie_tree.insert(word) + + words = sentence.split(" ") + size = len(words) + for i in range(size): + word = words[i] + words[i] = trie_tree.search(word) + return ' '.join(words) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(|dictionary| + |sentence|)$。其中 $|dictionary|$ 是字符串数组 `dictionary` 中的字符总数,$|sentence|$ 是字符串 `sentence` 的字符总数。 +- **空间复杂度**:$O(|dictionary| + |sentence|)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/stickers-to-spell-word.md b/docs/solutions/0600-0699/stickers-to-spell-word.md new file mode 100644 index 00000000..3bbd6858 --- /dev/null +++ b/docs/solutions/0600-0699/stickers-to-spell-word.md @@ -0,0 +1,103 @@ +# [0691. 贴纸拼词](https://leetcode.cn/problems/stickers-to-spell-word/) + +- 标签:位运算、数组、字符串、动态规划、回溯、状态压缩 +- 难度:困难 + +## 题目链接 + +- [0691. 贴纸拼词 - 力扣](https://leetcode.cn/problems/stickers-to-spell-word/) + +## 题目大意 + +**描述**:给定一个字符串数组 $stickers$ 表示不同的贴纸,其中 $stickers[i]$ 表示第 $i$ 张贴纸上的小写英文单词。再给定一个字符串 $target$。为了拼出给定字符串 $target$,我们需要从贴纸中切割单个字母并重新排列它们。贴纸的数量是无限的,可以重复多次使用。 + +**要求**:返回需要拼出 $target$ 的最小贴纸数量。如果任务不可能,则返回 $-1$。 + +**说明**: + +- 在所有的测试用例中,所有的单词都是从 $1000$ 个最常见的美国英语单词中随机选择的,并且 $target$ 被选择为两个随机单词的连接。 +- $n == stickers.length$。 +- $1 \le n \le 50$。 +- $1 \le stickers[i].length \le 10$。 +- $1 \le target.length \le 15$。 +- $stickers[i]$ 和 $target$ 由小写英文单词组成。 + +**示例**: + +- 示例 1: + +```python +输入:stickers = ["with","example","science"], target = "thehat" +输出:3 +解释: +我们可以使用 2 个 "with" 贴纸,和 1 个 "example" 贴纸。 +把贴纸上的字母剪下来并重新排列后,就可以形成目标 “thehat“ 了。 +此外,这是形成目标字符串所需的最小贴纸数量。 +``` + +- 示例 2: + +```python +输入:stickers = ["notice","possible"], target = "basicbasic" +输出:-1 +解释:我们不能通过剪切给定贴纸的字母来形成目标“basicbasic”。 +``` + +## 解题思路 + +### 思路 1:状态压缩 DP + 广度优先搜索 + +根据题意,$target$ 的长度最大为 $15$,所以我们可以使用一个长度最多为 $15$ 位的二进制数 $state$ 来表示 $target$ 的某个子序列,如果 $state$ 第 $i$ 位二进制值为 $1$,则说明 $target$ 的第 $i$ 个字母被选中。 + +然后我们从初始状态 $state = 0$(没有选中 $target$ 中的任何字母)开始进行广度优先搜索遍历。 + +在广度优先搜索过程中,对于当前状态 $cur\underline{\hspace{0.5em}}state$,我们遍历所有贴纸的所有字母,如果当前字母可以拼到 $target$ 中的某个位置上,则更新状态 $next\underline{\hspace{0.5em}}state$ 为「选中 $target$ 中对应位置上的字母」。 + +为了得到最小最小贴纸数量,我们可以使用动态规划的方法,定义 $dp[state]$ 表示为到达 $state$ 状态需要的最小贴纸数量。 + +那么在广度优先搜索中,在更新状态时,同时进行状态转移,即 $dp[next\underline{\hspace{0.5em}}state] = dp[cur\underline{\hspace{0.5em}}state] + 1$。 + +> 注意:在进行状态转移时,要跳过 $dp[next\underline{\hspace{0.5em}}state]$ 已经有值的情况。 + +这样在到达状态 $1 \text{ <}\text{< } len(target) - 1$ 时,所得到的 $dp[1 \text{ <}\text{< } len(target) - 1]$ 即为答案。 + +如果最终到达不了 $dp[1 \text{ <}\text{< } len(target) - 1]$,则说明无法完成任务,返回 $-1$。 + +### 思路 1:代码 + +```python +class Solution: + def minStickers(self, stickers: List[str], target: str) -> int: + size = len(target) + states = 1 << size + dp = [0 for _ in range(states)] + + queue = collections.deque([0]) + + while queue: + cur_state = queue.popleft() + for sticker in stickers: + next_state = cur_state + cnts = [0 for _ in range(26)] + for ch in sticker: + cnts[ord(ch) - ord('a')] += 1 + for i in range(size): + if cnts[ord(target[i]) - ord('a')] and next_state & (1 << i) == 0: + next_state |= (1 << i) + cnts[ord(target[i]) - ord('a')] -= 1 + + if dp[next_state] or next_state == 0: + continue + + queue.append(next_state) + dp[next_state] = dp[cur_state] + 1 + if next_state == states - 1: + return dp[next_state] + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n \times \sum_{i = 0}^{m - 1} len(stickers[i]) \times n$,其中 $n$ 为 $target$ 的长度,$m$ 为 $stickers$ 的元素个数。 +- **空间复杂度**:$O(2^n)$。 + diff --git a/docs/solutions/0600-0699/strange-printer.md b/docs/solutions/0600-0699/strange-printer.md new file mode 100644 index 00000000..1d9ba214 --- /dev/null +++ b/docs/solutions/0600-0699/strange-printer.md @@ -0,0 +1,103 @@ +# [0664. 奇怪的打印机](https://leetcode.cn/problems/strange-printer/) + +- 标签:字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [0664. 奇怪的打印机 - 力扣](https://leetcode.cn/problems/strange-printer/) + +## 题目大意 + +**描述**:有一台奇怪的打印机,有以下两个功能: + +1. 打印机每次只能打印由同一个字符组成的序列,比如:`"aaaa"`、`"bbb"`。 +2. 每次可以从起始位置到结束的任意为止打印新字符,并且会覆盖掉原有字符。 + +现在给定一个字符串 $s$。 + +**要求**:计算这个打印机打印出字符串 $s$ 需要的最少打印次数。 + +**说明**: + +- $1 \le s.length \le 100$。 +- $s$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "aaabbb" +输出:2 +解释:首先打印 "aaa" 然后打印 "bbb"。 +``` + +- 示例 2: + +```python +输入:s = "aba" +输出:2 +解释:首先打印 "aaa" 然后在第二个位置打印 "b" 覆盖掉原来的字符 'a'。 +``` + +## 解题思路 + +对于字符串 $s$,我们可以先考虑区间 $[i, j]$ 上的子字符串需要的最少打印次数。 + +1. 如果区间 $[i, j]$ 内只有 $1$ 种字符,则最少打印次数为 $1$,即:$dp[i][i] = 1$。 +2. 如果区间 $[i, j]$ 内首尾字符相同,即 $s[i] == s[j]$,则我们在打印 $s[i]$ 的同时我们可以顺便打印 $s[j]$,这样我们可以忽略 $s[j]$,只考虑剩下区间 $[i, j - 1]$ 的打印情况,即:$dp[i][j] = dp[i][j - 1]$。 +3. 如果区间 $[i, j]$ 上首尾字符不同,即 $s[i] \ne s[j]$,则枚举分割点 $k$,将区间 $[i, j]$ 分为区间 $[i, k]$ 与区间 $[k + 1, j]$,使得 $dp[i][k] + dp[k + 1][j]$ 的值最小即为 $dp[i][j]$。 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:打印第 $i$ 个字符到第 $j$ 个字符需要的最少打印次数。 + +###### 3. 状态转移方程 + +1. 如果 $s[i] == s[j]$,则我们在打印 $s[i]$ 的同时我们可以顺便打印 $s[j]$,这样我们可以忽略 $s[j]$,只考虑剩下区间 $[i, j - 1]$ 的打印情况,即:$dp[i][j] = dp[i][j - 1]$。 +2. 如果 $s[i] \ne s[j]$,则枚举分割点 $k$,将区间 $[i, j]$ 分为区间 $[i, k]$ 与区间 $[k + 1, j]$,使得 $dp[i][k] + dp[k + 1][j]$ 的值最小即为 $dp[i][j]$,即:$dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])$。 + +###### 4. 初始条件 + +- 初始时,打印单个字符的最少打印次数为 $1$,即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:打印第 $i$ 个字符到第 $j$ 个字符需要的最少打印次数。 所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def strangePrinter(self, s: str) -> int: + size = len(s) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for i in range(size): + dp[i][i] = 1 + + for l in range(2, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + if s[i] == s[j]: + dp[i][j] = dp[i][j - 1] + else: + for k in range(i, j): + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]) + + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0600-0699/sum-of-square-numbers.md b/docs/solutions/0600-0699/sum-of-square-numbers.md new file mode 100644 index 00000000..2795460d --- /dev/null +++ b/docs/solutions/0600-0699/sum-of-square-numbers.md @@ -0,0 +1,41 @@ +# [0633. 平方数之和](https://leetcode.cn/problems/sum-of-square-numbers/) + +- 标签:数学、双指针、二分查找 +- 难度:中等 + +## 题目链接 + +- [0633. 平方数之和 - 力扣](https://leetcode.cn/problems/sum-of-square-numbers/) + +## 题目大意 + +给定一个非负整数 c,判断是否存在两个整数 a 和 b,使得 $a^2 + b^2 = c$,如果存在则返回 True,不存在返回 False。 + +## 解题思路 + +最直接的办法就是枚举 a、b 所有可能。这样遍历下来的时间复杂度为 $O(c^2)$。但是没必要进行二重遍历。可以只遍历 a,然后去判断 $\sqrt{c - b^2}$ 是否为整数,并且 a 只需遍历到 $\sqrt{c}$ 即可,时间复杂度为 $O(\sqrt{c})$。 + +另一种方法是双指针。定义两个指针 left,right 分别指向 0 和 $\sqrt{c}$。判断 $left^2 + right^2$ 与 c 之间的关系。 + +- 如果 $a^2 + b^2 == c$,则返回 True。 +- 如果 $a^2 + b^2 < c$,则将 a 值加一,继续查找。 +- 如果 $a^2 + b^2 > c$,则将 b 值减一,继续查找。 +- 当 $a == b$ 时,结束查找。如果此时仍没有找到满足 $a^2 + b^2 == c$ 的 a、b 值,则返回 False。 + +## 代码 + +```python +class Solution: + def judgeSquareSum(self, c: int) -> bool: + a, b = 0, int(c ** 0.5) + while a <= b: + sum = a*a + b*b + if sum == c: + return True + elif sum < c: + a += 1 + else: + b -= 1 + return False +``` + diff --git a/docs/solutions/0600-0699/task-scheduler.md b/docs/solutions/0600-0699/task-scheduler.md new file mode 100644 index 00000000..a4ae26f1 --- /dev/null +++ b/docs/solutions/0600-0699/task-scheduler.md @@ -0,0 +1,54 @@ +# [0621. 任务调度器](https://leetcode.cn/problems/task-scheduler/) + +- 标签:贪心、数组、哈希表、计数、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0621. 任务调度器 - 力扣](https://leetcode.cn/problems/task-scheduler/) + +## 题目大意 + +给定一个字符数组 tasks 表示 CPU 需要执行的任务列表。tasks 中每个字母表示一种不同种类的任务。任务可以按任意顺序执行,并且每个任务执行时间为 1 个单位时间。在任何一个单位时间,CPU 可以完成一个任务,或者也可以处于待命状态。 + +但是两个相同种类的任务之间需要 n 个单位时间的冷却时间,所以不能在连续的 n 个单位时间内执行相同的任务。 + +要求计算出完成 tasks 中所有任务所需要的「最短时间」。 + +## 解题思路 + +因为相同种类的任务之间最少需要 n 个单位时间间隔,所以为了最短时间,应该优先考虑任务出现此次最多的任务。 + +先找出出现次数最多的任务,然后中间间隔的单位来安排别的任务,或者处于待命状态。 + +然后将第二出现次数最多的任务,按照 n 个时间间隔安排起来。如果第二出现次数最多的任务跟第一出现次数最多的任务出现次数相同,则最短时间就会加一。 + +最后我们会发现:最短时间跟出现次数最多的任务正相关。 + +假设出现次数最多的任务为 "A"。与 "A" 出现次数相同的任务数为 count。则: + +- `最短时间 = (A 出现次数 - 1)* (n + 1)+ count`。 + +最后还应该比较一下总的任务个数跟计算出的最短时间答案。如果最短时间比总的任务个数还少,说明间隔中放不下所有的任务,会有任务「溢出」。则应该将多余任务插入间隔中,则答案应为总的任务个数。 + +## 代码 + +```python +class Solution: + def leastInterval(self, tasks: List[str], n: int) -> int: + # 记录每个任务出现的次数 + tasks_counts = [0 for _ in range(26)] + for i in range(len(tasks)): + num = ord(tasks[i]) - ord('A') + tasks_counts[num] += 1 + max_task_count = max(tasks_counts) + # 统计多少个出现最多次的任务 + count = 0 + for task_count in tasks_counts: + if task_count == max_task_count: + count += 1 + + # 如果结果比任务数量少,则返回总任务数 + return max((max_task_count - 1) * (n + 1) + count, len(tasks)) +``` + diff --git a/docs/solutions/0600-0699/trim-a-binary-search-tree.md b/docs/solutions/0600-0699/trim-a-binary-search-tree.md new file mode 100644 index 00000000..07869f25 --- /dev/null +++ b/docs/solutions/0600-0699/trim-a-binary-search-tree.md @@ -0,0 +1,43 @@ +# [0669. 修剪二叉搜索树](https://leetcode.cn/problems/trim-a-binary-search-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0669. 修剪二叉搜索树 - 力扣](https://leetcode.cn/problems/trim-a-binary-search-tree/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root`,同时给定最小边界 `low` 和最大边界 `high`。通过修建二叉搜索树,使得所有节点值都在 `[low, high]` 中。修剪树不应该改变保留在树中的元素的相对结构(即如果没有移除节点,则该节点的父节点关系、子节点关系都应当保留)。 + +现在要求返回修建过后的二叉树的根节点。 + +## 解题思路 + +递归修剪,函数返回值为修剪之后的树。 + +- 如果当前根节点为空,则直接返回 None。 +- 如果当前根节点的值小于 `low`,则该节点左子树全部都小于最小边界,则删除左子树,然后递归遍历右子树,在右子树中寻找符合条件的节点。 +- 如果当前根节点的值大于 `hight`,则该节点右子树全部都大于最大边界,则删除右子树,然后递归遍历左子树,在左子树中寻找符合条件的节点。 +- 如果在最小边界和最大边界的区间内,则分别从左右子树寻找符合条件的节点作为根的左右子树。 + +## 代码 + +```python +class Solution: + def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode: + if not root: + return None + if root.val < low: + right = self.trimBST(root.right, low, high) + return right + if root.val > high: + left = self.trimBST(root.left, low, high) + return left + + root.left = self.trimBST(root.left, low, high) + root.right = self.trimBST(root.right, low, high) + return root +``` + diff --git a/docs/solutions/0600-0699/two-sum-iv-input-is-a-bst.md b/docs/solutions/0600-0699/two-sum-iv-input-is-a-bst.md new file mode 100644 index 00000000..5045f6aa --- /dev/null +++ b/docs/solutions/0600-0699/two-sum-iv-input-is-a-bst.md @@ -0,0 +1,45 @@ +# [0653. 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 +- 难度:简单 + +## 题目链接 + +- [0653. 两数之和 IV - 输入二叉搜索树 - 力扣](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/) + +## 题目大意 + +给定一个二叉搜索树的根节点 `root` 和一个整数 `k`。 + +要求:判断该二叉搜索树是否存在两个节点值的和等于 `k`。如果存在,则返回 `True`,不存在则返回 `False`。 + +## 解题思路 + +二叉搜索树中序遍历的结果是从小到大排序,所以我们可以先对二叉搜索树进行中序遍历,将中序遍历结果存储到列表中。再使用左右指针查找节点值和为 `k` 的两个节点。 + +## 代码 + +```python +class Solution: + def inOrder(self, root, nums): + if not root: + return + self.inOrder(root.left, nums) + nums.append(root.val) + self.inOrder(root.right, nums) + + def findTarget(self, root: TreeNode, k: int) -> bool: + nums = [] + self.inOrder(root, nums) + left, right = 0, len(nums) - 1 + while left < right: + sum = nums[left] + nums[right] + if sum == k: + return True + elif sum < k: + left += 1 + else: + right -= 1 + return False +``` + diff --git a/docs/solutions/0600-0699/valid-palindrome-ii.md b/docs/solutions/0600-0699/valid-palindrome-ii.md new file mode 100644 index 00000000..f79d471d --- /dev/null +++ b/docs/solutions/0600-0699/valid-palindrome-ii.md @@ -0,0 +1,54 @@ +# [0680. 验证回文串 II](https://leetcode.cn/problems/valid-palindrome-ii/) + +- 标签:贪心、双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0680. 验证回文串 II - 力扣](https://leetcode.cn/problems/valid-palindrome-ii/) + +## 题目大意 + +给定一个非空字符串 `s`。 + +要求:判断如果最多从字符串中删除一个字符能否得到一个回文字符串。 + +## 解题思路 + +题目要求在最多删除一个字符的情况下是否能得到一个回文字符串。最直接的思路是遍历各个字符,判断将该字符删除之后,剩余字符串是否是回文串。但是这种思路的时间复杂度是 $O(n^2)$,解答的话会超时。 + +我们可以通过双指针 + 贪心算法来减少时间复杂度。具体做法如下: + +- 使用两个指针变量 `left`、`right` 分别指向字符串的开始和结束位置。 + +- 判断 `s[left]` 是否等于 `s[right]`。 + - 如果等于,则 `left` 右移、`right`左移。 + - 如果不等于,则判断 `s[left: right - 1]` 或 `s[left + 1, right]` 是为回文串。 + - 如果是则返回 `True`。 + - 如果不是则返回 `False`,然后继续判断。 +- 如果 `right >= left`,则说明字符串 `s` 本身就是回文串,返回 `True`。 + +## 代码 + +```python +class Solution: + def checkPalindrome(self, s: str, left: int, right: int): + i, j = left, right + while i < j: + if s[i] != s[j]: + return False + i += 1 + j -= 1 + return True + + def validPalindrome(self, s: str) -> bool: + left, right = 0, len(s) - 1 + while left < right: + if s[left] == s[right]: + left += 1 + right -= 1 + else: + return self.checkPalindrome(s, left + 1, right) or self.checkPalindrome(s, left, right - 1) + return True +``` + diff --git a/docs/solutions/0600-0699/valid-parenthesis-string.md b/docs/solutions/0600-0699/valid-parenthesis-string.md new file mode 100644 index 00000000..2298cec7 --- /dev/null +++ b/docs/solutions/0600-0699/valid-parenthesis-string.md @@ -0,0 +1,149 @@ +# [0678. 有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) + +- 标签:栈、贪心、字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [0678. 有效的括号字符串 - 力扣](https://leetcode.cn/problems/valid-parenthesis-string/) + +## 题目大意 + +**描述**:给定一个只包含三种字符的字符串:`(` ,`)` 和 `*`。有效的括号字符串具有如下规则: + +1. 任何左括号 `(` 必须有相应的右括号 `)`。 +2. 任何右括号 `)` 必须有相应的左括号 `(`。 +3. 左括号 `(` 必须在对应的右括号之前 `)`。 +4. `*` 可以被视为单个右括号 `)`,或单个左括号 `(`,或一个空字符串。 +5. 一个空字符串也被视为有效字符串。 + +**要求**:验证这个字符串是否为有效字符串。如果是,则返回 `True`;否则,则返回 `False`。 + +**说明**: + +- 字符串大小将在 `[1, 100]` 范围内。 + +**示例**: + +- 示例 1: + +```python +输入:"(*)" +输出:True +``` + +## 解题思路 + +### 思路 1:动态规划(时间复杂度为 $O(n^3)$) + +###### 1. 划分阶段 + +按照子串的起始位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:从下标 `i` 到下标 `j` 的子串是否为有效的括号字符串,其中 ($0 \le i < j < size$,$size$ 为字符串长度)。如果是则 `dp[i][j] = True`,否则,`dp[i][j] = False`。 + +###### 3. 状态转移方程 + +长度大于 `2` 时,我们需要根据 `s[i]` 和 `s[j]` 的情况,以及子串中间的有效字符串情况来判断 `dp[i][j]`。 + +- 如果 `s[i]`、`s[j]` 分别表示左括号和右括号,或者为 `'*'`(此时 `s[i]`、`s[j]` 可以分别看做是左括号、右括号)。则如果 `dp[i + 1][j - 1] == True` 时,`dp[i][j] = True`。 +- 如果可以将从下标 `i` 到下标 `j` 的子串从中间分开为两个有效字符串,则 `dp[i][j] = True`。即如果存在 $i \le k < j$,使得 `dp[i][k] == True` 并且 `dp[k + 1][j] == True`,则 `dp[i][j] = True`。 + +###### 4. 初始条件 + +- 当子串的长度为 `1`,并且该字符串为 `'*'` 时,子串可看做是空字符串,此时子串是有效的括号字符串。 +- 当子串的长度为 `2` 时,如果两个字符可以分别看做是左括号和右括号,子串可以看做是 `"()"`,此时子串是有效的括号字符串。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j]` 表示为:从下标 `i` 到下标 `j` 的子串是否为有效的括号字符串。则最终结果为 `dp[0][size - 1]`。 + +### 思路 1:动态规划(时间复杂度为 $O(n^3)$)代码 + +```python +class Solution: + def checkValidString(self, s: str) -> bool: + size = len(s) + dp = [[False for _ in range(size)] for _ in range(size)] + + for i in range(size): + if s[i] == '*': + dp[i][i] = True + + for i in range(1, size): + if (s[i - 1] == '(' or s[i - 1] == '*') and (s[i] == ')' or s[i] == '*'): + dp[i - 1][i] = True + + for i in range(size - 3, -1, -1): + for j in range(i + 2, size): + if (s[i] == '(' or s[i] == '*') and (s[j] == ')' or s[j] == '*'): + dp[i][j] = dp[i + 1][j - 1] + for k in range(i, j): + if dp[i][j]: + break + dp[i][j] = dp[i][k] and dp[k + 1][j] + + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$。三重循环遍历的时间复杂度是 $O(n^3)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 + +### 思路 2:动态规划(时间复杂度为 $O(n^2)$) + +###### 1. 划分阶段 + +按照字符串的结束位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:前 `i` 个字符能否通过补齐 `j` 个右括号成为有效的括号字符串。 + +###### 3. 状态转移方程 + +1. 如果 `s[i] == '('`,则如果前 `i - 1` 个字符通过补齐 `j - 1` 个右括号成为有效的括号字符串,则前 `i` 个字符就能通过补齐 `j` 个右括号成为有效的括号字符串(比前 `i - 1` 个字符需要多补一个右括号)。也就是说,如果 `s[i] == '('` 并且 `dp[i - 1][j - 1] == True`,则 `dp[i][j] = True`。 +2. 如果 `s[i] == ')'`,则如果前 `i - 1` 个字符通过补齐 `j + 1` 个右括号成为有效的括号字符串,则前 `i` 个字符就能通过补齐 `j` 个右括号成为有效的括号字符串(比前 `i - 1` 个字符需要少补一个右括号)。也就是说,如果 `s[i] == ')'` 并且 `dp[i - 1][j + 1] == True`,则 `dp[i][j] = True`。 +3. 如果 `s[i] == '*'`,而 `'*'` 可以表示空字符串、左括号或者右括号,则 `dp[i][j]` 取决于这三种情况,只要有一种情况为 `True`,则 `dp[i][j] = True`。也就是说,如果 `s[i] == '*'`,则 `dp[i][j] = dp[i - 1][j] or dp[i - 1][j - 1]`。 + +###### 4. 初始条件 + +- `0` 个字符可以通过补齐 `0` 个右括号成为有效的括号字符串(空字符串),即 `dp[0][0] = 0`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j]` 表示为:前 `i` 个字符能否通过补齐 `j` 个右括号成为有效的括号字符串。。则最终结果为 `dp[size][0]`。 + +### 思路 2:动态规划(时间复杂度为 $O(n^2)$)代码 + +```python +class Solution: + def checkValidString(self, s: str) -> bool: + size = len(s) + dp = [[False for _ in range(size + 1)] for _ in range(size + 1)] + dp[0][0] = True + for i in range(1, size + 1): + for j in range(i + 1): + if s[i - 1] == '(': + if j > 0: + dp[i][j] = dp[i - 1][j - 1] + elif s[i - 1] == ')': + if j < i: + dp[i][j] = dp[i - 1][j + 1] + else: + dp[i][j] = dp[i - 1][j] + if j > 0: + dp[i][j] = dp[i][j] or dp[i - 1][j - 1] + if j < i: + dp[i][j] = dp[i][j] or dp[i - 1][j + 1] + + return dp[size][0] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 \ No newline at end of file diff --git a/docs/solutions/0600-0699/valid-triangle-number.md b/docs/solutions/0600-0699/valid-triangle-number.md new file mode 100644 index 00000000..84a29e84 --- /dev/null +++ b/docs/solutions/0600-0699/valid-triangle-number.md @@ -0,0 +1,83 @@ +# [0611. 有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/) + +- 标签:贪心、数组、双指针、二分查找、排序 +- 难度:中等 + +## 题目链接 + +- [0611. 有效三角形的个数 - 力扣](https://leetcode.cn/problems/valid-triangle-number/) + +## 题目大意 + +**描述**:给定一个包含非负整数的数组 $nums$,其中 $nums[i]$ 表示第 $i$ 条边的边长。 + +**要求**:统计数组中可以组成三角形三条边的三元组个数。 + +**说明**: + +- $1 \le nums.length \le 1000$。 +- $0 \le nums[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [2,2,3,4] +输出: 3 +解释:有效的组合是: +2,3,4 (使用第一个 2) +2,3,4 (使用第二个 2) +2,2,3 +``` + +- 示例 2: + +```python +输入: nums = [4,2,3,4] +输出: 4 +``` + +## 解题思路 + +### 思路 1:对撞指针 + +构成三角形的条件为:任意两边和大于第三边,或者任意两边差小于第三边。只要满足这两个条件之一就可以构成三角形。以任意两边和大于第三边为例,如果用 $a$、$b$、$c$ 来表示的话,应该同时满足 $a + b > c$、$a + c > b$、$b + c > a$。如果我们将三条边升序排序,假设 $a \le b \le c$,则如果满足 $a + b > c$,则 $a + c > b$ 和 $b + c > a$ 一定成立。 + +所以我们可以先对 $nums$ 进行排序。然后固定最大边 $i$,利用对撞指针 $left$、$right$ 查找较小的两条边。然后判断是否构成三角形并统计三元组个数。 + +为了避免重复计算和漏解,要严格保证三条边的序号关系为:$left < right < i$。具体做法如下: + +- 对数组从小到大排序,使用 $ans$ 记录三元组个数。 +- 从 $i = 2$ 开始遍历数组的每一条边,$i$ 作为最大边。 +- 使用双指针 $left$、$right$。$left$ 指向 $0$,$right$ 指向 $i - 1$。 + - 如果 $nums[left] + nums[right] \le nums[i]$,说明第一条边太短了,可以增加第一条边长度,所以将 $left$ 右移,即 `left += 1`。 + - 如果 $nums[left] + nums[right] > nums[i]$,说明可以构成三角形,并且第二条边固定为 $right$ 边的话,第一条边可以在 $[left, right - 1]$ 中任意选择。所以三元组个数要加上 $right - left$。即 `ans += (right - left)`。 +- 直到 $left == right$ 跳出循环,输出三元组个数 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def triangleNumber(self, nums: List[int]) -> int: + nums.sort() + size = len(nums) + ans = 0 + + for i in range(2, size): + left = 0 + right = i - 1 + while left < right: + if nums[left] + nums[right] <= nums[i]: + left += 1 + else: + ans += (right - left) + right -= 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组中的元素个数。 +- **空间复杂度**:$O(\log n)$,排序需要 $\log n$ 的栈空间。 + diff --git a/docs/solutions/0700-0799/all-paths-from-source-to-target.md b/docs/solutions/0700-0799/all-paths-from-source-to-target.md new file mode 100644 index 00000000..b9727801 --- /dev/null +++ b/docs/solutions/0700-0799/all-paths-from-source-to-target.md @@ -0,0 +1,50 @@ +# [0797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) + +- 标签:深度优先搜索、广度优先搜索、图、回溯 +- 难度:中等 + +## 题目链接 + +- [0797. 所有可能的路径 - 力扣](https://leetcode.cn/problems/all-paths-from-source-to-target/) + +## 题目大意 + +给定一个有 `n` 个节点的有向无环图(DAG),用二维数组 `graph` 表示。 + +要求:找出所有从节点 `0` 到节点 `n - 1` 的路径并输出(不要求按特定顺序)。 + +二维数组 `graph` 的第 `i` 个数组 `graph[i]` 中的单元都表示有向图中 `i` 号节点所能到达的下一个节点,如果为空就是没有下一个结点了。 + +## 解题思路 + +从第 `0` 个节点开始进行深度优先搜索遍历。在遍历的同时,通过回溯来寻找所有路径。具体做法如下: + +- 使用 `ans` 数组存放所有答案路径,使用 `path` 数组记录当前路径。 +- 从第 `0` 个节点开始进行深度优先搜索遍历。 + - 如果当前开始节点 `start` 等于目标节点 `target`。则将当前路径 `path` 添加到答案数组 `ans` 中,并返回。 + - 然后遍历当前节点 `start` 所能达到的下一个节点。 + - 将下一个节点加入到当前路径中。 + - 从该节点出发进行深度优先搜索遍历。 + - 然后将下一个节点从当前路径中移出,进行回退操作。 +- 最后返回答案数组 `ans`。 + +## 代码 + +```python +class Solution: + def dfs(self, graph, start, target, path, ans): + if start == target: + ans.append(path[:]) + return + for end in graph[start]: + path.append(end) + self.dfs(graph, end, target, path, ans) + path.remove(end) + + def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: + path = [0] + ans = [] + self.dfs(graph, 0, len(graph) - 1, path, ans) + return ans +``` + diff --git a/docs/solutions/0700-0799/asteroid-collision.md b/docs/solutions/0700-0799/asteroid-collision.md new file mode 100644 index 00000000..647d0583 --- /dev/null +++ b/docs/solutions/0700-0799/asteroid-collision.md @@ -0,0 +1,51 @@ +# [0735. 小行星碰撞](https://leetcode.cn/problems/asteroid-collision/) + +- 标签:栈、数组 +- 难度:中等 + +## 题目链接 + +- [0735. 小行星碰撞 - 力扣](https://leetcode.cn/problems/asteroid-collision/) + +## 题目大意 + +给定一个整数数组 `asteroids`,表示在同一行的小行星。 + +数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。小行星按照下面的规则发生碰撞。 + +- 碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 + +要求:找出碰撞后剩下的所有小行星,将答案存入数组并返回。 + +## 解题思路 + +用栈模拟小行星碰撞,具体步骤如下: + +- 遍历数组 `asteroids`。 +- 如果栈为空或者当前元素 `asteroid` 为正数,将其压入栈。 +- 如果当前栈不为空并且当前元素 `asteroid` 为负数: + - 与栈中元素发生碰撞,判断当前元素和栈顶元素的大小和方向,如果栈顶元素为正数,并且当前元素的绝对值大于栈顶元素,则将栈顶元素弹出,并继续与栈中元素发生碰撞。 + - 碰撞完之后,如果栈为空并且栈顶元素为负数,则将当前元素 `asteroid` 压入栈,表示碰撞完剩下了 `asteroid`。 + - 如果栈顶元素恰好与当前元素值大小相等、方向相反,则弹出栈顶元素,表示碰撞完两者都爆炸了。 +- 最后返回栈作为答案。 + +## 代码 + +```python +class Solution: + def asteroidCollision(self, asteroids: List[int]) -> List[int]: + stack = [] + for asteroid in asteroids: + if not stack or asteroid > 0: + stack.append(asteroid) + else: + while stack and 0 < stack[-1] < -asteroid: + stack.pop() + if not stack or stack[-1] < 0: + stack.append(asteroid) + elif stack[-1] == -asteroid: + stack.pop() + + return stack +``` + diff --git a/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md b/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md new file mode 100644 index 00000000..3e14f1c0 --- /dev/null +++ b/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md @@ -0,0 +1,41 @@ +# [0714. 买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [0714. 买卖股票的最佳时机含手续费 - 力扣](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) + +## 题目大意 + +给定一个整数数组 `prices`,其中第 `i` 个元素代表了第 `i` 天的股票价格 ;整数 `fee` 代表了交易股票的手续费用。 + +你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + +最后要求返回获得利润的最大值。 + +## 解题思路 + +这道题的解题思路和「[0122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)」类似,同样可以买卖多次。122 题是在跌入谷底的时候买入,在涨到波峰的时候卖出,这道题多了手续费,则在判断波峰波谷的时候还要考虑手续费。贪心策略如下: + +- 当股票价格小于当前最低股价时,更新最低股价,不卖出。 +- 当股票价格大于最小价格 + 手续费时,累积股票利润(实质上暂未卖出,等到波峰卖出),同时最低股价减去手续费,以免重复计算。 + +## 代码 + +```python +class Solution: + def maxProfit(self, prices: List[int], fee: int) -> int: + res = 0 + min_price = prices[0] + + for i in range(1, len(prices)): + if prices[i] < min_price: + min_price = prices[i] + elif prices[i] > min_price + fee: + res += prices[i] - min_price - fee + min_price = prices[i] - fee + return res +``` + diff --git a/docs/solutions/0700-0799/binary-search.md b/docs/solutions/0700-0799/binary-search.md new file mode 100644 index 00000000..e8bc6f9a --- /dev/null +++ b/docs/solutions/0700-0799/binary-search.md @@ -0,0 +1,80 @@ +# [0704. 二分查找](https://leetcode.cn/problems/binary-search/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [0704. 二分查找 - 力扣](https://leetcode.cn/problems/binary-search/) + +## 题目大意 + +**描述**:给定一个升序的数组 $nums$,和一个目标值 $target$。 + +**要求**:返回 $target$ 在数组中的位置,如果找不到,则返回 -1。 + +**说明**: + +- 你可以假设 $nums$ 中的所有元素是不重复的。 +- $n$ 将在 $[1, 10000]$之间。 +- $nums$ 的每个元素都将在 $[-9999, 9999]$之间。 + +**示例**: + +- 示例 1: + +```python +输入: nums = [-1,0,3,5,9,12], target = 9 +输出: 4 +解释: 9 出现在 nums 中并且下标为 4 +``` + +- 示例 2: + +```python +输入: nums = [-1,0,3,5,9,12], target = 2 +输出: -1 +解释: 2 不存在 nums 中因此返回 -1 +``` + +## 解题思路 + +### 思路 1:二分查找 + +设定左右节点为数组两端,即 `left = 0`,`right = len(nums) - 1`,代表待查找区间为 $[left, right]$(左闭右闭)。 + +取两个节点中心位置 $mid$,先比较中心位置值 $nums[mid]$ 与目标值 $target$ 的大小。 + +- 如果 $target == nums[mid]$,则返回中心位置。 +- 如果 $target > nums[mid]$,则将左节点设置为 $mid + 1$,然后继续在右区间 $[mid + 1, right]$ 搜索。 +- 如果中心位置值 $target < nums[mid]$,则将右节点设置为 $mid - 1$,然后继续在左区间 $[left, mid - 1]$ 搜索。 + +### 思路 1:代码 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + # 在区间 [left, right] 内查找 target + while left <= right: + # 取区间中间节点 + mid = (left + right) // 2 + # 如果找到目标值,则直接返回中心位置 + if nums[mid] == target: + return mid + # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索 + elif nums[mid] < target: + left = mid + 1 + # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索 + else: + right = mid - 1 + # 未搜索到元素,返回 -1 + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/bold-words-in-string.md b/docs/solutions/0700-0799/bold-words-in-string.md new file mode 100644 index 00000000..2d7c5c9e --- /dev/null +++ b/docs/solutions/0700-0799/bold-words-in-string.md @@ -0,0 +1,89 @@ +# [0758. 字符串中的加粗单词](https://leetcode.cn/problems/bold-words-in-string/) + +- 标签:字典树、数组、哈希表、字符串、字符串匹配 +- 难度:中等 + +## 题目链接 + +- [0758. 字符串中的加粗单词 - 力扣](https://leetcode.cn/problems/bold-words-in-string/) + +## 题目大意 + +给定一个关键词集合 `words` 和一个字符串 `s`。 + +要求:在所有 `s` 中出现的关键词前后位置上添加加粗闭合标签 `` 和 ``。如果两个子串有重叠部分,则将它们一起用一对闭合标签包围起来。同理,如果两个子字符串连续被加粗,那么你也需要把它们合起来用一对加粗标签包围。最后返回添加加粗标签后的字符串 `s`。 + +## 解题思路 + +构建字典树,将字符串列表 `words` 中所有字符串添加到字典树中。 + +然后遍历字符串 `s`,从每一个位置开始查询字典树。在第一个符合要求的单词前面添加 ``。在连续符合要求的单词中的最后一个单词后面添加 ``。 + +最后返回添加加粗标签后的字符串 `s`。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def boldWords(self, words: List[str], s: str) -> str: + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + size = len(s) + bold_left, bold_right = -1, -1 + ans = "" + for i in range(size): + cur = trie_tree + if s[i] in cur.children: + bold_left = i + while bold_left < size and s[bold_left] in cur.children: + cur = cur.children[s[bold_left]] + bold_left += 1 + if cur.isEnd: + if bold_right == -1: + ans += "" + bold_right = max(bold_left, bold_right) + if i == bold_right: + ans += "" + bold_right = -1 + ans += s[i] + if bold_right >= 0: + ans += "" + return ans +``` + diff --git a/docs/solutions/0700-0799/couples-holding-hands.md b/docs/solutions/0700-0799/couples-holding-hands.md new file mode 100644 index 00000000..c95c2279 --- /dev/null +++ b/docs/solutions/0700-0799/couples-holding-hands.md @@ -0,0 +1,101 @@ +# [0765. 情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) + +- 标签:贪心、深度优先搜索、广度优先搜索、并查集、图 +- 难度:困难 + +## 题目链接 + +- [0765. 情侣牵手 - 力扣](https://leetcode.cn/problems/couples-holding-hands/) + +## 题目大意 + +**描述**:$n$ 对情侣坐在连续排列的 $2 \times n$ 个座位上,想要牵对方的手。人和座位用 $0 \sim 2 \times n - 1$ 的整数表示。情侣按顺序编号,第一对是 $(0, 1)$,第二对是 $(2, 3)$,以此类推,最后一对是 $(2 \times n - 2, 2 \times n - 1)$。 + +给定代表情侣初始座位的数组 `row`,`row[i]` 表示第 `i` 个座位上的人的编号。 + +**要求**:计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。每一次交换可以选择任意两人,让他们互换座位。 + +**说明**: + +- $2 \times n == row.length$。 +- $2 \le n \le 30$。 +- $n$ 是偶数。 +- $0 \le row[i] < 2 \times n$。 +- $row$ 中所有元素均无重复。 + +**示例**: + +- 示例 1: + +```python +输入: row = [0,2,1,3] +输出: 1 +解释: 只需要交换row[1]和row[2]的位置即可。 +``` + +- 示例 2: + +```python +输入: row = [3,2,0,1] +输出: 0 +解释: 无需交换座位,所有的情侣都已经可以手牵手了。 +``` + +## 解题思路 + +### 思路 1:并查集 + +先观察一下可以直接牵手的情侣特点: + +- 编号一定相邻。 +- 编号为一个奇数一个偶数。 +- 偶数 + 1 = 奇数。 + +将每对情侣的编号 `(0, 1) (2, 3) (4, 5) ...` 除以 `2` 可以得到 `(0, 0) (1, 1) (2, 2) ...`,这样相同编号就代表是一对情侣。 + +1. 按照 `2` 个一组的顺序,遍历一下所有编号。 + 1. 如果相邻的两人编号除以 `2` 相同,则两人是情侣,将其合并到一个集合中。 + 2. 如果相邻的两人编号不同,则将其合并到同一个集合中,而这两个人分别都有各自的对象,所以在后续遍历中两个人各自的对象和他们同组上的另一个人一定都会并到统一集合中,最终形成一个闭环。比如 `(0, 1) (1, 3) (2, 0) (3, 2)`。假设闭环对数为 `k`,最少需要交换 `k - 1` 次才能让情侣牵手。 +2. 假设 `n` 对情侣中有 `m` 个闭环,则 `至少交换次数 = (n1 - 1) + (n2 - 1) + ... + (nn - 1) = n - m`。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return False + self.parent[root_x] = root_y + return True + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def minSwapsCouples(self, row: List[int]) -> int: + size = len(row) + n = size // 2 + count = n + union_find = UnionFind(n) + for i in range(0, size, 2): + if union_find.union(row[i] // 2, row[i + 1] // 2): + count -= 1 + return n - count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是数组 $row$ 长度,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/daily-temperatures.md b/docs/solutions/0700-0799/daily-temperatures.md new file mode 100644 index 00000000..17fb2307 --- /dev/null +++ b/docs/solutions/0700-0799/daily-temperatures.md @@ -0,0 +1,74 @@ +# [0739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) + +- 标签:栈、数组、单调栈 +- 难度:中等 + +## 题目链接 + +- [0739. 每日温度 - 力扣](https://leetcode.cn/problems/daily-temperatures/) + +## 题目大意 + +**描述**:给定一个列表 `temperatures`,`temperatures[i]` 表示第 `i` 天的气温。 + +**要求**:输出一个列表,列表上每个位置代表「如果要观测到更高的气温,至少需要等待的天数」。如果之后的气温不再升高,则用 `0` 来代替。 + +**说明**: + +- $1 \le temperatures.length \le 10^5$。 +- $30 \le temperatures[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入: temperatures = [73,74,75,71,69,72,76,73] +输出: [1,1,4,2,1,1,0,0] +``` + +- 示例 2: + +```python +输入: temperatures = [30,40,50,60] +输出: [1,1,1,0] +``` + +## 解题思路 + +题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置右侧找到第一个比当前元素更大的元素。求「该元素」与「右侧第一个比当前元素更大的元素」之间的距离,将所有距离保存为数组返回结果。 + +最简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 + +更好的方式使用「单调递增栈」,栈中保存元素的下标。 + +### 思路 1:单调栈 + +1. 首先,将答案数组 `ans` 全部赋值为 0。然后遍历数组每个位置元素。 +2. 如果栈为空,则将当前元素的下标入栈。 +3. 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 +4. 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组 `ans` 中保存起来,判断栈顶元素。 +5. 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 +6. 最后输出答案数组 `ans`。 + +### 思路 1:代码 + +```python +class Solution: + def dailyTemperatures(self, T: List[int]) -> List[int]: + n = len(T) + stack = [] + ans = [0 for _ in range(n)] + for i in range(n): + while stack and T[i] > T[stack[-1]]: + index = stack.pop() + ans[index] = (i-index) + stack.append(i) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0700-0799/design-hashmap.md b/docs/solutions/0700-0799/design-hashmap.md new file mode 100644 index 00000000..db33021c --- /dev/null +++ b/docs/solutions/0700-0799/design-hashmap.md @@ -0,0 +1,108 @@ +# [0706. 设计哈希映射](https://leetcode.cn/problems/design-hashmap/) + +- 标签:设计、数组、哈希表、链表、哈希函数 +- 难度:简单 + +## 题目链接 + +- [0706. 设计哈希映射 - 力扣](https://leetcode.cn/problems/design-hashmap/) + +## 题目大意 + +**要求**:不使用任何内建的哈希表库设计一个哈希映射(`HashMap`)。 + +需要满足以下操作: + +- `MyHashMap()` 用空映射初始化对象。 +- `void put(int key, int value) 向 HashMap` 插入一个键值对 `(key, value)` 。如果 `key` 已经存在于映射中,则更新其对应的值 `value`。 +- `int get(int key)` 返回特定的 `key` 所映射的 `value`;如果映射中不包含 `key` 的映射,返回 `-1`。 +- `void remove(key)` 如果映射中存在 key 的映射,则移除 `key` 和它所对应的 `value` 。 + +**说明**: + +- $0 \le key, value \le 10^6$。 +- 最多调用 $10^4$ 次 `put`、`get` 和 `remove` 方法。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] +[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] +输出: +[null, null, null, 1, -1, null, 1, null, -1] + +解释: +MyHashMap myHashMap = new MyHashMap(); +myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] +myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] +myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] +myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] +myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) +myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] +myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] +myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] +``` + +## 解题思路 + +### 思路 1:链地址法 + +和 [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) 类似。这里我们使用「链地址法」来解决哈希冲突。即利用「数组 + 链表」的方式实现哈希集合。 + +1. 定义哈希表长度 `buckets` 为 `1003`。 +2. 定义一个一维长度为 `buckets` 的二维数组 `table`。其中第一维度用于计算哈希函数,为关键字 `key` 分桶。第二个维度用于存放 `key` 和对应的 `value`。第二维度的数组会根据 `key` 值动态增长,用数组模拟真正的链表。 +3. 定义一个 `hash(key)` 的方法,将 `key` 转换为对应的地址 `hash_key`。 +4. 进行 `put` 操作时,根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 + 1. 如果找到与 `key` 值相同的元素,则更改该元素对应的 `value` 值。 + 2. 如果没找到与 `key` 值相同的元素,则在第二维数组 `table[hask_key]` 中增加元素,元素为 `(key, value)` 组成的元组。 + +5. 进行 `get` 操作跟 `put` 操作差不多。根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 + 1. 如果找到与 `key` 值相同的元素,则返回该元素对应的 `value`。 + 2. 如果没找到与 `key` 值相同的元素,则返回 `-1`。 + +### 思路 1:代码 + +```python +class MyHashMap: + + def __init__(self): + self.buckets = 1003 + self.table = [[] for _ in range(self.buckets)] + + + def hash(self, key): + return key % self.buckets + + + def put(self, key: int, value: int) -> None: + hash_key = self.hash(key) + for item in self.table[hash_key]: + if key == item[0]: + item[1] = value + return + self.table[hash_key].append([key, value]) + + + def get(self, key: int) -> int: + hash_key = self.hash(key) + for item in self.table[hash_key]: + if key == item[0]: + return item[1] + return -1 + + + def remove(self, key: int) -> None: + hash_key = self.hash(key) + for i, item in enumerate(self.table[hash_key]): + if key == item[0]: + self.table[hash_key].pop(i) + return +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\frac{n}{b})$。其中 $n$ 为哈希表中元素数量,$b$ 为链表的数量。 +- **空间复杂度**:$O(n + b)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/design-hashset.md b/docs/solutions/0700-0799/design-hashset.md new file mode 100644 index 00000000..e595fc21 --- /dev/null +++ b/docs/solutions/0700-0799/design-hashset.md @@ -0,0 +1,93 @@ +# [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) + +- 标签:设计、数组、哈希表、链表、哈希函数 +- 难度:简单 + +## 题目链接 + +- [0705. 设计哈希集合 - 力扣](https://leetcode.cn/problems/design-hashset/) + +## 题目大意 + +**要求**:不使用内建的哈希表库,自行实现一个哈希集合(HashSet)。 + +需要满足以下操作: + +- `void add(key)` 向哈希集合中插入值 $key$。 +- `bool contains(key)` 返回哈希集合中是否存在这个值 $key$。 +- `void remove(key)` 将给定值 $key$ 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 + +**说明**: + +- $0 \le key \le 10^6$。 +- 最多调用 $10^4$ 次 `add`、`remove` 和 `contains`。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyHashSet", "add", "add", "contains", "contains", "add", "contains", "remove", "contains"] +[[], [1], [2], [1], [3], [2], [2], [2], [2]] +输出: +[null, null, null, true, false, null, true, null, false] + +解释: +MyHashSet myHashSet = new MyHashSet(); +myHashSet.add(1); // set = [1] +myHashSet.add(2); // set = [1, 2] +myHashSet.contains(1); // 返回 True +myHashSet.contains(3); // 返回 False ,(未找到) +myHashSet.add(2); // set = [1, 2] +myHashSet.contains(2); // 返回 True +myHashSet.remove(2); // set = [1] +myHashSet.contains(2); // 返回 False ,(已移除) +``` + +## 解题思路 + +### 思路 1:数组 + 链表 + +定义一个一维长度为 $buckets$ 的二维数组 $table$。 + +第一维度用于计算哈希函数,为 $key$ 进行分桶。第二个维度用于寻找 $key$ 存放的具体位置。第二维度的数组会根据 $key$ 值动态增长,模拟真正的链表。 + +### 思路 1:代码 + +```python +class MyHashSet: + + def __init__(self): + self.buckets = 1003 + self.table = [[] for _ in range(self.buckets)] + + + def hash(self, key): + return key % self.buckets + + + def add(self, key: int) -> None: + hash_key = self.hash(key) + if key in self.table[hash_key]: + return + self.table[hash_key].append(key) + + + def remove(self, key: int) -> None: + hash_key = self.hash(key) + if key not in self.table[hash_key]: + return + self.table[hash_key].remove(key) + + + def contains(self, key: int) -> bool: + hash_key = self.hash(key) + return key in self.table[hash_key] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\frac{n}{m})$,其中 $n$ 为哈希表中的元素数量,$b$ 为 $table$ 的元素个数,也就是链表的数量。 +- **空间复杂度**:$O(n + m)$。 + diff --git a/docs/solutions/0700-0799/design-linked-list.md b/docs/solutions/0700-0799/design-linked-list.md new file mode 100644 index 00000000..25c688e6 --- /dev/null +++ b/docs/solutions/0700-0799/design-linked-list.md @@ -0,0 +1,249 @@ +# [0707. 设计链表](https://leetcode.cn/problems/design-linked-list/) + +- 标签:设计、链表 +- 难度:中等 + +## 题目链接 + +- [0707. 设计链表 - 力扣](https://leetcode.cn/problems/design-linked-list/) + +## 题目大意 + +**要求**:设计实现一个链表,需要支持以下操作: + +- `get(index)`:获取链表中第 `index` 个节点的值。如果索引无效,则返回 `-1`。 +- `addAtHead(val)`:在链表的第一个元素之前添加一个值为 `val` 的节点。插入后,新节点将成为链表的第一个节点。 +- `addAtTail(val)`:将值为 `val` 的节点追加到链表的最后一个元素。 +- `addAtIndex(index, val)`:在链表中的第 `index` 个节点之前添加值为 `val` 的节点。如果 `index` 等于链表的长度,则该节点将附加到链表的末尾。如果 `index` 大于链表长度,则不会插入节点。如果 `index` 小于 `0`,则在头部插入节点。 +- `deleteAtIndex(index)`:如果索引 `index` 有效,则删除链表中的第 `index` 个节点。 + +**说明**: + +- 所有`val`值都在 $[1, 1000]$ 之内。 +- 操作次数将在 $[1, 1000]$ 之内。 +- 请不要使用内置的 `LinkedList` 库。 + +**示例**: + +- 示例 1: + +```python +MyLinkedList linkedList = new MyLinkedList(); +linkedList.addAtHead(1); +linkedList.addAtTail(3); +linkedList.addAtIndex(1,2); // 链表变为 1 -> 2 -> 3 +linkedList.get(1); // 返回 2 +linkedList.deleteAtIndex(1); // 现在链表是 1-> 3 +linkedList.get(1); // 返回 3 +``` + +## 解题思路 + +### 思路 1:单链表 + +新建一个带有 `val` 值 和 `next` 指针的链表节点类, 然后按照要求对节点进行操作。 + +### 思路 1:代码 + +```python +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + + +class MyLinkedList: + def __init__(self): + """ + Initialize your data structure here. + """ + self.size = 0 + self.head = ListNode(0) + + + def get(self, index: int) -> int: + """ + Get the value of the index-th node in the linked list. If the index is invalid, return -1. + """ + if index < 0 or index >= self.size: + return -1 + + curr = self.head + for _ in range(index + 1): + curr = curr.next + return curr.val + + + def addAtHead(self, val: int) -> None: + """ + Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. + """ + self.addAtIndex(0, val) + + + def addAtTail(self, val: int) -> None: + """ + Append a node of value val to the last element of the linked list. + """ + self.addAtIndex(self.size, val) + + + def addAtIndex(self, index: int, val: int) -> None: + """ + Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. + """ + if index > self.size: + return + + if index < 0: + index = 0 + + self.size += 1 + pre = self.head + for _ in range(index): + pre = pre.next + + add_node = ListNode(val) + add_node.next = pre.next + pre.next = add_node + + + def deleteAtIndex(self, index: int) -> None: + """ + Delete the index-th node in the linked list, if the index is valid. + """ + if index < 0 or index >= self.size: + return + + self.size -= 1 + pre = self.head + for _ in range(index): + pre = pre.next + + pre.next = pre.next.next +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**: + - `addAtHead(val)`:$O(1)$。 + - `get(index)`、`addAtTail(val)`、`del eteAtIndex(index)`:$O(k)$。$k$ 指的是元素的索引。 + - `addAtIndex(index, val)`:$O(n)$。$n$ 指的是链表的元素个数。 + +- **空间复杂度**:$O(1)$。 + +### 思路 2:双链表 + +新建一个带有 `val` 值和 `next` 指针、`prev` 指针的链表节点类,然后按照要求对节点进行操作。 + +### 思路 2:代码 + +```python +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + self.prev = None + +class MyLinkedList: + def __init__(self): + """ + Initialize your data structure here. + """ + self.size = 0 + self.head = ListNode(0) + self.tail = ListNode(0) + self.head.next = self.tail + self.tail.prev = self.head + + + def get(self, index: int) -> int: + """ + Get the value of the index-th node in the linked list. If the index is invalid, return -1. + """ + if index < 0 or index >= self.size: + return -1 + + if index + 1 < self.size - index: + curr = self.head + for _ in range(index + 1): + curr = curr.next + else: + curr = self.tail + for _ in range(self.size - index): + curr = curr.prev + return curr.val + + + def addAtHead(self, val: int) -> None: + """ + Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. + """ + self.addAtIndex(0, val) + + + def addAtTail(self, val: int) -> None: + """ + Append a node of value val to the last element of the linked list. + """ + self.addAtIndex(self.size, val) + + + def addAtIndex(self, index: int, val: int) -> None: + """ + Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. + """ + if index > self.size: + return + + if index < 0: + index = 0 + + if index < self.size - index: + prev = self.head + for _ in range(index): + prev = prev.next + next = prev.next + else: + next = self.tail + for _ in range(self.size - index): + next = next.prev + prev = next.prev + + self.size += 1 + add_node = ListNode(val) + add_node.prev = prev + add_node.next = next + prev.next = add_node + next.prev = add_node + + def deleteAtIndex(self, index: int) -> None: + """ + Delete the index-th node in the linked list, if the index is valid. + """ + if index < 0 or index >= self.size: + return + + if index < self.size - index: + prev = self.head + for _ in range(index): + prev = prev.next + next = prev.next.next + else: + next = self.tail + for _ in range(self.size - index - 1): + next = next.prev + prev = next.prev.prev + + self.size -= 1 + prev.next = next + next.prev = prev +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**: + - `addAtHead(val)`、`addAtTail(val)`:$O(1)$。 + - `get(index)`、`addAtIndex(index, val)`、`del eteAtIndex(index)`:$O(min(k, n - k))$。$n$ 指的是链表的元素个数,$k$ 指的是元素的索引。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md b/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md new file mode 100644 index 00000000..91286861 --- /dev/null +++ b/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md @@ -0,0 +1,88 @@ +# [0719. 找出第 K 小的距离对](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) + +- 标签:数组、双指针、二分查找、排序 +- 难度:困难 + +## 题目链接 + +- [0719. 找出第 K 小的距离对 - 力扣](https://leetcode.cn/problems/find-k-th-smallest-pair-distance/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$,对于数组中不同的数 $nums[i]$、$nums[j]$ 之间的距离定义为 $nums[i]$ 和 $nums[j]$ 的绝对差值,即 $dist(nums[i], nums[j]) = abs(nums[i] - nums[j])$。 + +**要求**:求所有数对之间第 $k$ 个最小距离。 + +**说明**: + +- $n == nums.length$ +- $2 \le n \le 10^4$。 +- $0 \le nums[i] \le 10^6$。 +- $1 \le k \le n \times (n - 1) / 2$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,3,1], k = 1 +输出:0 +解释:数对和对应的距离如下: +(1,3) -> 2 +(1,1) -> 0 +(3,1) -> 2 +距离第 1 小的数对是 (1,1) ,距离为 0。 +``` + +- 示例 2: + +```python +输入:nums = [1,1,1], k = 2 +输出:0 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +一般来说 topK 问题都可以用堆排序来解决。但是这道题使用堆排序超时了。所以需要换其他方法。 + +先来考虑第 $k$ 个最小距离的范围。这个范围一定在 $[0, max(nums) - min(nums)]$ 之间。 + +我们可以对 $nums$ 先进行排序,然后得到最小距离为 $0$,最大距离为 $nums[-1] - nums[0]$。我们可以在这个区间上进行二分,对于二分的位置 $mid$,统计距离小于等于 $mid$ 的距离对数,并根据它和 $k$ 的关系调整区间上下界。 + +统计对数可以使用双指针来计算出所有小于等于 $mid$ 的距离对数目。 + +1. 维护两个指针 $left$、$right$。$left$、$right$ 都指向数组开头位置。 +2. 然后不断移动 $right$,计算 $nums[right]$ 和 $nums[left]$ 之间的距离。 +3. 如果大于 $mid$,则 $left$ 向右移动,直到距离小于等于 $mid$ 时,统计当前距离对数为 $right - left$。 +4. 最终将这些符合要求的距离对数累加,就得到了所有小于等于 $mid$ 的距离对数目。 + +### 思路 1:代码 + +```python +class Solution: + def smallestDistancePair(self, nums: List[int], k: int) -> int: + def get_count(dist): + left, count = 0, 0 + for right in range(1, len(nums)): + while nums[right] - nums[left] > dist: + left += 1 + count += (right - left) + return count + + nums.sort() + left, right = 0, nums[-1] - nums[0] + while left < right: + mid = left + (right - left) // 2 + if get_count(mid) >= k: + right = mid + else: + left = mid + 1 + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $nums$ 中的元素个数。 +- **空间复杂度**:$O(\log n)$,排序算法所用到的空间复杂度为 $O(\log n)$。 diff --git a/docs/solutions/0700-0799/find-pivot-index.md b/docs/solutions/0700-0799/find-pivot-index.md new file mode 100644 index 00000000..7c334a83 --- /dev/null +++ b/docs/solutions/0700-0799/find-pivot-index.md @@ -0,0 +1,69 @@ +# [0724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) + +- 标签:数组、前缀和 +- 难度:简单 + +## 题目链接 + +- [0724. 寻找数组的中心下标 - 力扣](https://leetcode.cn/problems/find-pivot-index/) + +## 题目大意 + +**描述**:给定一个数组 $nums$。 + +**要求**:找到「左侧元素和」与「右侧元素和相等」的位置,若找不到,则返回 $-1$。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $-1000 \le nums[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1, 7, 3, 6, 5, 6] +输出:3 +解释: +中心下标是 3 。 +左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11, +右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11,二者相等。 +``` + +- 示例 2: + +```python +输入:nums = [1, 2, 3] +输出:-1 +解释: +数组中不存在满足此条件的中心下标。 +``` + +## 解题思路 + +### 思路 1:两次遍历 + +两次遍历,第一次遍历先求出数组全部元素和。第二次遍历找到左侧元素和恰好为全部元素和一半的位置。 + +### 思路 1:代码 + +```python +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + sum = 0 + for i in range(len(nums)): + sum += nums[i] + curr_sum = 0 + for i in range(len(nums)): + if curr_sum * 2 + nums[i] == sum: + return i + curr_sum += nums[i] + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。两次遍历的时间复杂度为 $O(2 \times n)$ ,$O(2 \times n) == O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md b/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md new file mode 100644 index 00000000..bbb86703 --- /dev/null +++ b/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md @@ -0,0 +1,71 @@ +# [0744. 寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [0744. 寻找比目标字母大的最小字母 - 力扣](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) + +## 题目大意 + +**描述**:给你一个字符数组 $letters$,该数组按非递减顺序排序,以及一个字符 $target$。$letters$ 里至少有两个不同的字符。 + +**要求**:找出 $letters$ 中大于 $target$ 的最小的字符。如果不存在这样的字符,则返回 $letters$ 的第一个字符。 + +**说明**: + +- $2 \le letters.length \le 10^4$。 +- $letters[i]$$ 是一个小写字母。 +- $letters$ 按非递减顺序排序。 +- $letters$ 最少包含两个不同的字母。 +- $target$ 是一个小写字母。 + +**示例**: + +- 示例 1: + +```python +输入: letters = ["c", "f", "j"],target = "a" +输出: "c" +解释:letters 中字典上比 'a' 大的最小字符是 'c'。 +``` + +- 示例 2: + +```python +输入: letters = ["c","f","j"], target = "c" +输出: "f" +解释:letters 中字典顺序上大于 'c' 的最小字符是 'f'。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +利用二分查找,找到比 $target$ 大的字母。注意 $target$ 可能大于 $letters$ 的所有字符,此时应返回 $letters$ 的第一个字母。 + +我们可以假定 $target$ 的取值范围为 $[0, len(letters)]$。当 $target$ 取到 $len(letters)$ 时,说明 $target$ 大于 $letters$ 的所有字符,对 $len(letters)$ 取余即可得到 $letters[0]$。 + +### 思路 1:代码 + +```python +class Solution: + def nextGreatestLetter(self, letters: List[str], target: str) -> str: + n = len(letters) + left = 0 + right = n + while left < right: + mid = left + (right - left) // 2 + if letters[mid] <= target: + left = mid + 1 + else: + right = mid + return letters[left % n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 为字符数组 $letters$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/flood-fill.md b/docs/solutions/0700-0799/flood-fill.md new file mode 100644 index 00000000..7b01f948 --- /dev/null +++ b/docs/solutions/0700-0799/flood-fill.md @@ -0,0 +1,44 @@ +# [0733. 图像渲染](https://leetcode.cn/problems/flood-fill/) + +- 标签:深度优先搜索、广度优先搜索、数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [0733. 图像渲染 - 力扣](https://leetcode.cn/problems/flood-fill/) + +## 题目大意 + +给定一个二维数组 image 表示图画,数组的每个元素值表示该位置的像素值大小。再给定一个坐标 (sr, sc) 表示图像渲染开始的位置。然后再给定一个新的颜色值 newColor。现在要求:将坐标 (sr, sc) 以及 (sr, sc) 相连的上下左右区域上与 (sr, sc) 原始颜色相同的区域染色为 newColor。返回染色后的二维数组。 + + + +## 解题思路 + +从起点开始,对上下左右四个方向进行广度优先搜索。每次搜索到一个位置时,如果该位置上的像素值与初始位置像素值相同,则更新该位置像素值,并将该位置加入队列中。最后将二维数组返回。 + +- 注意:如果起点位置初始颜色和新颜色值 newColor 相同,则不需要染色,直接返回原数组即可。 + +## 代码 + +```python +import collections + +class Solution: + def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: + if newColor == image[sr][sc]: + return image + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + queue = collections.deque([(sr, sc)]) + oriColor = image[sr][sc] + while queue: + point = queue.popleft() + image[point[0]][point[1]] = newColor + for direction in directions: + new_i = point[0] + direction[0] + new_j = point[1] + direction[1] + if 0 <= new_i < len(image) and 0 <= new_j < len(image[0]) and image[new_i][new_j] == oriColor: + queue.append((new_i, new_j)) + return image +``` + diff --git a/docs/solutions/0700-0799/index.md b/docs/solutions/0700-0799/index.md new file mode 100644 index 00000000..9697d8e8 --- /dev/null +++ b/docs/solutions/0700-0799/index.md @@ -0,0 +1,44 @@ +## 本章内容 + +- [0700. 二叉搜索树中的搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-binary-search-tree.md) +- [0701. 二叉搜索树中的插入操作](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md) +- [0702. 搜索长度未知的有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md) +- [0703. 数据流中的第 K 大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md) +- [0704. 二分查找](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/binary-search.md) +- [0705. 设计哈希集合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashset.md) +- [0706. 设计哈希映射](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-hashmap.md) +- [0707. 设计链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/design-linked-list.md) +- [0708. 循环有序列表的插入](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/insert-into-a-sorted-circular-linked-list.md) +- [0709. 转换成小写字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/to-lower-case.md) +- [0713. 乘积小于 K 的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/subarray-product-less-than-k.md) +- [0714. 买卖股票的最佳时机含手续费](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/best-time-to-buy-and-sell-stock-with-transaction-fee.md) +- [0715. Range 模块](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/range-module.md) +- [0718. 最长重复子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md) +- [0719. 找出第 K 小的数对距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-k-th-smallest-pair-distance.md) +- [0720. 词典中最长的单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/longest-word-in-dictionary.md) +- [0724. 寻找数组的中心下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-pivot-index.md) +- [0727. 最小窗口子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-window-subsequence.md) +- [0729. 我的日程安排表 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-i.md) +- [0731. 我的日程安排表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-ii.md) +- [0732. 我的日程安排表 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/my-calendar-iii.md) +- [0733. 图像渲染](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/flood-fill.md) +- [0735. 小行星碰撞](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/asteroid-collision.md) +- [0738. 单调递增的数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/monotone-increasing-digits.md) +- [0739. 每日温度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/daily-temperatures.md) +- [0744. 寻找比目标字母大的最小字母](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/find-smallest-letter-greater-than-target.md) +- [0746. 使用最小花费爬楼梯](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/min-cost-climbing-stairs.md) +- [0752. 打开转盘锁](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/open-the-lock.md) +- [0758. 字符串中的加粗单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/bold-words-in-string.md) +- [0763. 划分字母区间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/partition-labels.md) +- [0765. 情侣牵手](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/couples-holding-hands.md) +- [0766. 托普利茨矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/toeplitz-matrix.md) +- [0771. 宝石与石头](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/jewels-and-stones.md) +- [0778. 水位上升的泳池中游泳](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/swim-in-rising-water.md) +- [0779. 第K个语法符号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/k-th-symbol-in-grammar.md) +- [0783. 二叉搜索树节点最小距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/minimum-distance-between-bst-nodes.md) +- [0784. 字母大小写全排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/letter-case-permutation.md) +- [0785. 判断二分图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/is-graph-bipartite.md) +- [0788. 旋转数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotated-digits.md) +- [0795. 区间子数组个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md) +- [0796. 旋转字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/rotate-string.md) +- [0797. 所有可能的路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/all-paths-from-source-to-target.md) diff --git a/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md b/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md new file mode 100644 index 00000000..53b4b33d --- /dev/null +++ b/docs/solutions/0700-0799/insert-into-a-binary-search-tree.md @@ -0,0 +1,89 @@ +# [0701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) + +- 标签:树、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [0701. 二叉搜索树中的插入操作 - 力扣](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) + +## 题目大意 + +**描述**:给定一个二叉搜索树的根节点和要插入树中的值 `val`。 + +**要求**:将 `val` 插入到二叉搜索树中,返回新的二叉搜索树的根节点。 + +**说明**: + +- 树中的节点数将在 $[0, 10^4]$ 的范围内。 +- $-10^8 \le Node.val \le 10^8$ +- 所有值 `Node.val` 是独一无二的。 +- $-10^8 \le val \le 10^8$。 +- **保证** $val$ 在原始 BST 中不存在。 + +**示例**: + +- 示例 1: + +```python +输入:root = [4,2,7,1,3], val = 5 +输出:[4,2,7,1,3,5] +解释:另一个满足题目要求可以通过的树是: +``` + +- 示例 2: + +```python +输入:root = [40,20,60,10,30,50,70], val = 25 +输出:[40,20,60,10,30,50,70,null,null,25] +``` + +## 解题思路 + +### 思路 1:递归 + +已知搜索二叉树的性质: + +- 左子树上任意节点值均小于根节点,即 `root.left.val < root.val`。 +- 右子树上任意节点值均大于根节点,即 `root.left.val > root.val`。 + +那么根据 `val` 和当前节点的大小关系,则可以确定将 `val` 插入到当前节点的哪个子树上。具体步骤如下: + +1. 从根节点 `root` 开始向下递归遍历。根据 `val` 值和当前子树节点 `cur` 的大小关系: + 1. 如果 `val < cur.val`,则应在当前节点的左子树继续遍历判断。 + 1. 如果左子树为空,则新建节点,赋值为 `val`。链接到该子树的父节点上。并停止遍历。 + 2. 如果左子树不为空,则继续向左子树移动。 + 2. 如果 `val >= cur.val`,则应在当前节点的右子树继续遍历判断。 + 1. 如果右子树为空,则新建节点,赋值为 `val`。链接到该子树的父节点上。并停止遍历。 + 2. 如果右子树不为空,则继续向左子树移动。 +2. 遍历完返回根节点 `root`。 + +### 思路 1:代码 + +```python +class Solution: + def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: + if not root: + return TreeNode(val) + + cur = root + while cur: + if val < cur.val: + if not cur.left: + cur.left = TreeNode(val) + break + else: + cur = cur.left + else: + if not cur.right: + cur.right = TreeNode(val) + break + else: + cur = cur.right + return root +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/insert-into-a-sorted-circular-linked-list.md b/docs/solutions/0700-0799/insert-into-a-sorted-circular-linked-list.md new file mode 100644 index 00000000..6898843b --- /dev/null +++ b/docs/solutions/0700-0799/insert-into-a-sorted-circular-linked-list.md @@ -0,0 +1,53 @@ +# [0708. 循环有序列表的插入](https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/) + +- 标签:链表 +- 难度:中等 + +## 题目链接 + +- [0708. 循环有序列表的插入 - 力扣](https://leetcode.cn/problems/insert-into-a-sorted-circular-linked-list/) + +## 题目大意 + +给定循环升序链表中的一个节点 `head` 和一个整数 `insertVal`。 + +要求:将整数 `insertVal` 插入循环升序链表中,并且满足链表仍为循环升序链表。最终返回原先给定的节点。 + +## 解题思路 + +- 先判断所给节点 `head` 是否为空,为空直接创建一个值为 `insertVal` 的新节点,并指向自己,返回即可。 + +- 如果 `head` 不为空,把 `head` 赋值给 `node` ,方便最后返回原节点 `head`。 +- 然后遍历 `node`,判断插入值 `insertVal` 与 `node.val` 和 `node.next.val` 的关系,找到插入位置,具体判断如下: + - 如果新节点值在两个节点值中间, 即 `node.val <= insertVal <= node.next.val`。则说明新节点值在最大值最小值中间,应将新节点插入到当前位置,则应将 `insertVal` 插入到这个位置。 + - 如果新节点值比当前节点值和当前节点下一节点值都大,并且当前节点值比当前节点值的下一节点值大,即 `node.next.val < node.val <= insertVal`,则说明 `insertVal` 比链表最大值都大,应插入最大值后边。 + - 如果新节点值比当前节点值和当前节点下一节点值都小,并且当前节点值比当前节点值的下一节点值大,即 `insertVal < node.next.val < node.val`,则说明 `insertVal` 比链表中最小值都小,应插入最小值前边。 +- 找到插入位置后,跳出循环,在插入位置插入值为 `insertVal` 的新节点。 + +## 代码 + +```python +class Solution: + def insert(self, head: 'Node', insertVal: int) -> 'Node': + if not head: + node = Node(insertVal) + node.next = node + return node + + node = head + while node.next != head: + if node.val <= insertVal <= node.next.val: + break + elif node.next.val < node.val <= insertVal: + break + elif insertVal < node.next.val < node.val: + break + else: + node = node.next + + insert_node = Node(insertVal) + insert_node.next = node.next + node.next = insert_node + return head +``` + diff --git a/docs/solutions/0700-0799/is-graph-bipartite.md b/docs/solutions/0700-0799/is-graph-bipartite.md new file mode 100644 index 00000000..a0159928 --- /dev/null +++ b/docs/solutions/0700-0799/is-graph-bipartite.md @@ -0,0 +1,57 @@ +# [0785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0785. 判断二分图 - 力扣](https://leetcode.cn/problems/is-graph-bipartite/) + +## 题目大意 + +给定一个代表 n 个节点的无向图的二维数组 `graph`,其中 `graph[u]` 是一个节点数组,由节点 `u` 的邻接节点组成。对于 `graph[u]` 中的每个 `v`,都存在一条位于节点 `u` 和节点 `v` 之间的无向边。 + +该无向图具有以下属性: + +- 不存在自环(`graph[u]` 不包含 `u`)。 +- 不存在平行边(`graph[u]` 不包含重复值)。 +- 如果 `v` 在 `graph[u]` 内,那么 `u` 也应该在 `graph[v]` 内(该图是无向图)。 +- 这个图可能不是连通图,也就是说两个节点 `u` 和 `v` 之间可能不存在一条连通彼此的路径。 + +要求:判断该图是否是二分图,如果是二分图,则返回 `True`;否则返回 `False`。 + +- 二分图:如果能将一个图的节点集合分割成两个独立的子集 `A` 和 `B`,并使图中的每一条边的两个节点一个来自 `A` 集合,一个来自 `B` 集合,就将这个图称为 二分图 。 + +## 解题思路 + +对于图中的任意节点 `u` 和 `v`,如果 `u` 和 `v` 之间有一条无向边,那么 `u` 和 `v` 必然属于不同的集合。 + +我们可以通过在深度优先搜索中对邻接点染色标记的方式,来识别该图是否是二分图。具体做法如下: + +- 找到一个没有染色的节点 `u`,将其染成红色。 +- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 +- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 +- 如果所有节点都顺利染上色,则说明该图为二分图,返回 `True`。否则,如果在途中不能顺利染色,则返回 `False`。 + +## 代码 + +```python +class Solution: + def dfs(self, graph, colors, i, color): + colors[i] = color + for j in graph[i]: + if colors[j] == colors[i]: + return False + if colors[j] == 0 and not self.dfs(graph, colors, j, -color): + return False + return True + + def isBipartite(self, graph: List[List[int]]) -> bool: + size = len(graph) + colors = [0 for _ in range(size)] + for i in range(size): + if colors[i] == 0 and not self.dfs(graph, colors, i, 1): + return False + return True +``` + diff --git a/docs/solutions/0700-0799/jewels-and-stones.md b/docs/solutions/0700-0799/jewels-and-stones.md new file mode 100644 index 00000000..d9f62a6d --- /dev/null +++ b/docs/solutions/0700-0799/jewels-and-stones.md @@ -0,0 +1,69 @@ +# [0771. 宝石与石头](https://leetcode.cn/problems/jewels-and-stones/) + +- 标签:哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0771. 宝石与石头 - 力扣](https://leetcode.cn/problems/jewels-and-stones/) + +## 题目大意 + +**描述**:给定一个字符串 $jewels$ 代表石头中宝石的类型,再给定一个字符串 $stones$ 代表你拥有的石头。$stones$ 中每个字符代表了一种你拥有的石头的类型。 + +**要求**:计算出拥有的石头中有多少是宝石。 + +**说明**: + +- 字母区分大小写,因此 $a$ 和 $A$ 是不同类型的石头。 +- $1 \le jewels.length, stones.length \le 50$。 +- $jewels$ 和 $stones$ 仅由英文字母组成。 +- $jewels$ 中的所有字符都是唯一的。 + +**示例**: + +- 示例 1: + +```python +输入:jewels = "aA", stones = "aAAbbbb" +输出:3 +``` + +- 示例 2: + +```python +输入:jewels = "z", stones = "ZZ" +输出:0 +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 用 $count$ 来维护石头中的宝石个数。 +2. 先使用哈希表或者集合存储宝石。 +3. 再遍历数组 $stones$,并统计每块石头是否在哈希表中或集合中。 + 1. 如果当前石头在哈希表或集合中,则令 $count$ 加 $1$。 + 2. 如果当前石头不在哈希表或集合中,则不统计。 +4. 最后返回 $count$。 + +### 思路 1:代码 + +```python +class Solution: + def numJewelsInStones(self, jewels: str, stones: str) -> int: + jewel_dict = dict() + for jewel in jewels: + jewel_dict[jewel] = 1 + count = 0 + for stone in stones: + if stone in jewel_dict: + count += 1 + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$,其中 $m$ 是字符串 $jewels$ 的长度,$n$ 是 $stones$ 的长度。 +- **空间复杂度**:$O(m)$,其中 $m$ 是字符串 $jewels$ 的长度。 + diff --git a/docs/solutions/0700-0799/k-th-symbol-in-grammar.md b/docs/solutions/0700-0799/k-th-symbol-in-grammar.md new file mode 100644 index 00000000..5c46d11a --- /dev/null +++ b/docs/solutions/0700-0799/k-th-symbol-in-grammar.md @@ -0,0 +1,78 @@ +# [0779. 第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) + +- 标签:位运算、递归、数学 +- 难度:中等 + +## 题目链接 + +- [0779. 第K个语法符号 - 力扣](https://leetcode.cn/problems/k-th-symbol-in-grammar/) + +## 题目大意 + +**描述**:给定两个整数 $n$ 和 $k$​。我们可以按照下面的规则来生成字符串: + +- 第一行写上一个 $0$。 +- 从第二行开始,每一行将上一行的 $0$ 替换成 $01$,$1$ 替换为 $10$。 + +**要求**:输出第 $n$ 行字符串中的第 $k$ 个字符。 + +**说明**: + +- $1 \le n \le 30$。 +- $1 \le k \le 2^{n - 1}$。 + +**示例**: + +- 示例 1: + +```python +输入: n = 2, k = 1 +输出: 0 +解释: +第一行: 0 +第二行: 01 +``` + +- 示例 2: + +```python +输入: n = 4, k = 4 +输出: 0 +解释: +第一行:0 +第二行:01 +第三行:0110 +第四行:01101001 +``` + +## 解题思路 + +### 思路 1:递归算法 + 找规律 + +每一行都是由上一行生成的。我们可以将多行写到一起找下规律。 + +可以发现:第 $k$ 个数字是由上一位对应位置上的数字生成的。 + +- $k$ 在奇数位时,由上一行 $(k + 1) / 2$ 位置的值生成。且与上一行 $(k + 1) / 2$ 位置的值相同; +- $k$ 在偶数位时,由上一行 $k / 2$ 位置的值生成。且与上一行 $k / 2$ 位置的值相反。 + +接下来就是递归求解即可。 + +### 思路 1:代码 + +```python +class Solution: + def kthGrammar(self, n: int, k: int) -> int: + if n == 0: + return 0 + if k % 2 == 1: + return self.kthGrammar(n - 1, (k + 1) // 2) + else: + return abs(self.kthGrammar(n - 1, k // 2) - 1) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md b/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md new file mode 100644 index 00000000..f9d9e16e --- /dev/null +++ b/docs/solutions/0700-0799/kth-largest-element-in-a-stream.md @@ -0,0 +1,85 @@ +# [0703. 数据流中的第 K 大元素](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) + +- 标签:树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) +- 难度:简单 + +## 题目链接 + +- [0703. 数据流中的第 K 大元素 - 力扣](https://leetcode.cn/problems/kth-largest-element-in-a-stream/) + +## 题目大意 + +**要求**:设计一个 KthLargest 类,用于找到数据流中第 $k$ 大元素。 + +实现 KthLargest 类: + +- `KthLargest(int k, int[] nums)`:使用整数 $k$ 和整数流 $nums$ 初始化对象。 +- `int add(int val)`:将 $val$ 插入数据流 $nums$ 后,返回当前数据流中第 $k$ 大的元素。 + +**说明**: + +- $1 \le k \le 10^4$。 +- $0 \le nums.length \le 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $-10^4 \le val \le 10^4$。 +- 最多调用 `add` 方法 $10^4$ 次。 +- 题目数据保证,在查找第 $k$ 大元素时,数组中至少有 $k$ 个元素。 + +**示例**: + +- 示例 1: + +```python +输入: +["KthLargest", "add", "add", "add", "add", "add"] +[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]] +输出: +[null, 4, 5, 5, 8, 8] + +解释: +KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]); +kthLargest.add(3); // return 4 +kthLargest.add(5); // return 5 +kthLargest.add(10); // return 5 +kthLargest.add(9); // return 8 +kthLargest.add(4); // return 8 +``` + +## 解题思路 + +### 思路 1:堆 + +1. 建立大小为 $k$ 的大顶堆,堆中元素保证不超过 $k$ 个。 +2. 每次 `add` 操作时,将新元素压入堆中,如果堆中元素超出了 $k$ 个,则将堆中最小元素(堆顶)移除。 + +- 此时堆中最小元素(堆顶)就是整个数据流中的第 $k$ 大元素。 + +### 思路 1:代码 + +```python +import heapq + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + self.min_heap = [] + self.k = k + for num in nums: + heapq.heappush(self.min_heap, num) + if len(self.min_heap) > k: + heapq.heappop(self.min_heap) + + def add(self, val: int) -> int: + heapq.heappush(self.min_heap, val) + if len(self.min_heap) > self.k: + heapq.heappop(self.min_heap) + return self.min_heap[0] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**: + - 初始化时间复杂度:$O(n \times \log k)$,其中 $n$ 为 $nums$ 初始化时的元素个数。 + - 单次插入时间复杂度:$O(\log k)$。 +- **空间复杂度**:$O(k)$。 + diff --git a/docs/solutions/0700-0799/letter-case-permutation.md b/docs/solutions/0700-0799/letter-case-permutation.md new file mode 100644 index 00000000..e00a92e9 --- /dev/null +++ b/docs/solutions/0700-0799/letter-case-permutation.md @@ -0,0 +1,73 @@ +# [0784. 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation/) + +- 标签:位运算、字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [0784. 字母大小写全排列 - 力扣](https://leetcode.cn/problems/letter-case-permutation/) + +## 题目大意 + +**描述**:给定一个字符串 $s$,通过将字符串 $s$ 中的每个字母转变大小写,我们可以获得一个新的字符串。 + +**要求**:返回所有可能得到的字符串集合。 + +**说明**: + +- 答案可以以任意顺序返回输出。 +- $1 \le s.length \le 12$。 +- $s$ 由小写英文字母、大写英文字母和数字组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "a1b2" +输出:["a1b2", "a1B2", "A1b2", "A1B2"] +``` + +- 示例 2: + +```python +输入: s = "3z4" +输出: ["3z4","3Z4"] +``` + +## 解题思路 + +### 思路 1:回溯算法 + +- $i$ 代表当前要处理的字符在字符串 $s$ 中的下标,$path$ 表示当前路径,$ans$ 表示答案数组。 +- 如果处理到 $i == len(s)$ 时,将当前路径存入答案数组中返回,否则进行递归处理。 + - 不修改当前字符,直接递归处理第 $i + 1$ 个字符。 + - 如果当前字符是小写字符,则变为大写字符之后,递归处理第 $i + 1$ 个字符。 + - 如果当前字符是大写字符,则变为小写字符之后,递归处理第 $i + 1$ 个字符。 + +### 思路 1:代码 + +```python +class Solution: + def dfs(self, s, path, i, ans): + if i == len(s): + ans.append(path) + return + + self.dfs(s, path + s[i], i + 1, ans) + if ord('a') <= ord(s[i]) <= ord('z'): + self.dfs(s, path + s[i].upper(), i + 1, ans) + elif ord('A') <= ord(s[i]) <= ord('Z'): + self.dfs(s, path + s[i].lower(), i + 1, ans) + + def letterCasePermutation(self, s: str) -> List[str]: + ans, path = [], "" + self.dfs(s, path, 0, ans) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$n \times 2^n$,其中 $n$ 为字符串的长度。 +- **空间复杂度**:$O(1)$,除返回值外不需要额外的空间。 + diff --git a/docs/solutions/0700-0799/longest-word-in-dictionary.md b/docs/solutions/0700-0799/longest-word-in-dictionary.md new file mode 100644 index 00000000..0ca7f5f5 --- /dev/null +++ b/docs/solutions/0700-0799/longest-word-in-dictionary.md @@ -0,0 +1,75 @@ +# [0720. 词典中最长的单词](https://leetcode.cn/problems/longest-word-in-dictionary/) + +- 标签:字典树、数组、哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [0720. 词典中最长的单词 - 力扣](https://leetcode.cn/problems/longest-word-in-dictionary/) + +## 题目大意 + +给出一个字符串数组 `words` 组成的一本英语词典。 + +要求:从中找出最长的一个单词,该单词是由 `words` 词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。 + +## 解题思路 + +使用字典树存储每一个单词。再在字典树中查找每一个单词,查找的时候判断是否有以当前单词为前缀的单词。如果有,则该单词可以由前缀构成的单词逐步添加字母获得。此时,如果该单词比答案单词更长,则维护更新答案单词。 + +最后输出答案单词。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children or not cur.children[ch].isEnd: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def longestWord(self, words: List[str]) -> str: + + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + ans = "" + for word in words: + if trie_tree.search(word): + if len(word) > len(ans): + ans = word + elif len(word) == len(ans) and word < ans: + ans = word + return ans +``` + diff --git a/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md b/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md new file mode 100644 index 00000000..d212720e --- /dev/null +++ b/docs/solutions/0700-0799/maximum-length-of-repeated-subarray.md @@ -0,0 +1,192 @@ +# [0718. 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) + +- 标签:数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 +- 难度:中等 + +## 题目链接 + +- [0718. 最长重复子数组 - 力扣](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) + +## 题目大意 + +**描述**:给定两个整数数组 $nums1$、$nums2$。 + +**要求**:计算两个数组中公共的、长度最长的子数组长度。 + +**说明**: + +- $1 \le nums1.length, nums2.length \le 1000$。 +- $0 \le nums1[i], nums2[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] +输出:3 +解释:长度最长的公共子数组是 [3,2,1] 。 +``` + +- 示例 2: + +```python +输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] +输出:5 +``` + +## 解题思路 + +### 思路 1:暴力(超时) + +1. 枚举数组 $nums1$ 和 $nums2$ 的子数组开始位置 $i$、$j$。 +2. 如果遇到相同项,即 $nums1[i] == nums2[j]$,则以 $nums1[i]$、$nums2[j]$ 为前缀,同时向后遍历,计算当前的公共子数组长度 $subLen$ 最长为多少。 +3. 直到遇到超出数组范围或者 $nums1[i + subLen] == nums2[j + subLen]$ 情况时,停止遍历,并更新答案。 +4. 继续执行 $1 \sim 3$ 步,直到遍历完,输出答案。 + +### 思路 1:代码 + +```python +class Solution: + def findLength(self, nums1: List[int], nums2: List[int]) -> int: + size1, size2 = len(nums1), len(nums2) + ans = 0 + for i in range(size1): + for j in range(size2): + if nums1[i] == nums2[j]: + subLen = 1 + while i + subLen < size1 and j + subLen < size2 and nums1[i + subLen] == nums2[j + subLen]: + subLen += 1 + ans = max(ans, subLen) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m \times min(n, m))$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:滑动窗口 + +暴力方法中,因为子数组在两个数组中的位置不同,所以会导致子数组之间会进行多次比较。 + +我们可以将两个数组分别看做是两把直尺。然后将数组 $nums1$ 固定, 让 $nums2$ 的尾部与 $nums1$ 的头部对齐,如下所示。 + +```python +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] +``` + +然后逐渐向右移动直尺 $nums2$,比较 $nums1$ 与 $nums2$ 重叠部分中的公共子数组的长度,直到直尺 $nums2$ 的头部移动到 $nums1$ 的尾部。 + +```python +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] + +nums1 = [1, 2, 3, 2, 1] +nums2 = [3, 2, 1, 4, 7] +``` + +在这个过程中求得的 $nums1$ 与 $nums2$ 重叠部分中的最大的公共子数组的长度就是 $nums1$ 与 $nums2$ 数组中公共的、长度最长的子数组长度。 + +### 思路 2:代码 + +```python +class Solution: + def findMaxLength(self, nums1, nums2, i, j): + size1, size2 = len(nums1), len(nums2) + max_len = 0 + cur_len = 0 + while i < size1 and j < size2: + if nums1[i] == nums2[j]: + cur_len += 1 + max_len = max(max_len, cur_len) + else: + cur_len = 0 + i += 1 + j += 1 + return max_len + + def findLength(self, nums1: List[int], nums2: List[int]) -> int: + size1, size2 = len(nums1), len(nums2) + res = 0 + for i in range(size1): + res = max(res, self.findMaxLength(nums1, nums2, i, 0)) + + for i in range(size2): + res = max(res, self.findMaxLength(nums1, nums2, 0, i)) + + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n + m) \times min(n, m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 +- **空间复杂度**:$O(1)$。 + +### 思路 3:动态规划 + +###### 1. 划分阶段 + +按照子数组结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。 + +###### 3. 状态转移方程 + +1. 如果 $nums1[i - 1] = nums2[j - 1]$,则当前元素可以构成公共子数组,此时 $dp[i][j] = dp[i - 1][j - 1] + 1$。 +2. 如果 $nums1[i - 1] \ne nums2[j - 1]$,则当前元素不能构成公共子数组,此时 $dp[i][j] = 0$。 + +###### 4. 初始条件 + +- 当 $i = 0$ 时,$nums1[0]...nums1[i - 1]$ 表示的是空数组,空数组与 $nums2[0]...nums2[j - 1]$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 +- 当 $j = 0$ 时,$nums2[0]...nums2[j - 1]$ 表示的是空数组,空数组与 $nums1[0]...nums1[i - 1]$ 的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 + +###### 5. 最终结果 + +- 根据状态定义, $dp[i][j]$ 为:「以 $nums1$ 中前 $i$ 个元素为子数组($nums1[0]...nums2[i - 1]$)」和「以 $nums2$ 中前 $j$ 个元素为子数组($nums2[0]...nums2[j - 1]$)」的最长公共子数组长度。在遍历过程中,我们可以使用 $res$ 记录下所有 $dp[i][j]$ 中最大值即为答案。 + +### 思路 3:代码 + +```python +class Solution: + def findLength(self, nums1: List[int], nums2: List[int]) -> int: + size1 = len(nums1) + size2 = len(nums2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + res = 0 + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if nums1[i - 1] == nums2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + if dp[i][j] > res: + res = dp[i][j] + + return res +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times m)$。其中 $n$ 是数组 $nums1$ 的长度,$m$ 是数组 $nums2$ 的长度。 +- **空间复杂度**:$O(n \times m)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/min-cost-climbing-stairs.md b/docs/solutions/0700-0799/min-cost-climbing-stairs.md new file mode 100644 index 00000000..15509f74 --- /dev/null +++ b/docs/solutions/0700-0799/min-cost-climbing-stairs.md @@ -0,0 +1,39 @@ +# [0746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) + +- 标签:数组、动态规划 +- 难度:简单 + +## 题目链接 + +- [0746. 使用最小花费爬楼梯 - 力扣](https://leetcode.cn/problems/min-cost-climbing-stairs/) + +## 题目大意 + +给定一个数组 `cost` 代表一段楼梯,`cost[i]` 代表爬上第 `i` 阶楼梯醒酒药花费的体力值(下标从 `0` 开始)。 + +每爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 + +要求:找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 `0` 或 `1` 的元素作为初始阶梯。 + +## 解题思路 + +使用动态规划方法。 + +状态 `dp[i]` 表示为:到达第 `i` 个台阶所花费的最少体⼒。 + +则状态转移方程为: `dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]`。 + +表示为:到达第 `i` 个台阶所花费的最少体⼒ = 到达第 `i - 1` 个台阶所花费的最小体力 与 到达第 `i - 2` 个台阶所花费的最小体力中的最小值 + 到达第 `i` 个台阶所需要花费的体力值。 + +## 代码 + +```python +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + size = len(cost) + dp = [0 for _ in range(size + 1)] + for i in range(2, size+1): + dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]) + return dp[size] +``` + diff --git a/docs/solutions/0700-0799/minimum-distance-between-bst-nodes.md b/docs/solutions/0700-0799/minimum-distance-between-bst-nodes.md new file mode 100644 index 00000000..e9b10bfa --- /dev/null +++ b/docs/solutions/0700-0799/minimum-distance-between-bst-nodes.md @@ -0,0 +1,93 @@ +# [0783. 二叉搜索树节点最小距离](https://leetcode.cn/problems/minimum-distance-between-bst-nodes/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [0783. 二叉搜索树节点最小距离 - 力扣](https://leetcode.cn/problems/minimum-distance-between-bst-nodes/) + +## 题目大意 + +**描述**:给定一个二叉搜索树的根节点 $root$。 + +**要求**:返回树中任意两不同节点值之间的最小差值。 + +**说明**: + +- **差值**:是一个正数,其数值等于两值之差的绝对值。 +- 树中节点的数目范围是 $[2, 100]$。 +- $0 \le Node.val \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/05/bst1.jpg) + +```python +输入:root = [4,2,6,1,3] +输出:1 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/05/bst2.jpg) + +```python +输入:root = [1,0,48,null,null,12,49] +输出:1 +``` + +## 解题思路 + +### 思路 1:中序遍历 + +先来看二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +题目要求二叉搜索树上任意两节点的差的绝对值的最小值。 + +二叉树的中序遍历顺序是:左 -> 根 -> 右,二叉搜索树的中序遍历最终得到就是一个升序数组。而升序数组中绝对值差的最小值就是比较相邻两节点差值的绝对值,找出其中最小值。 + +那么我们就可以先对二叉搜索树进行中序遍历,并保存中序遍历的结果。然后再比较相邻节点差值的最小值,从而找出最小值。 + +### 思路 1:代码 + +```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def inorder(root): + if not root: + return + inorder(root.left) + res.append(root.val) + inorder(root.right) + + inorder(root) + return res + + def minDiffInBST(self, root: Optional[TreeNode]) -> int: + inorder = self.inorderTraversal(root) + ans = float('inf') + for i in range(1, len(inorder)): + ans = min(ans, abs(inorder[i - 1] - inorder[i])) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉搜索树中的节点数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0700-0799/minimum-window-subsequence.md b/docs/solutions/0700-0799/minimum-window-subsequence.md new file mode 100644 index 00000000..26107fa7 --- /dev/null +++ b/docs/solutions/0700-0799/minimum-window-subsequence.md @@ -0,0 +1,72 @@ +# [0727. 最小窗口子序列](https://leetcode.cn/problems/minimum-window-subsequence/) + +- 标签:字符串、动态规划、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [0727. 最小窗口子序列 - 力扣](https://leetcode.cn/problems/minimum-window-subsequence/) + +## 题目大意 + +给定字符串 `s1` 和 `s2`。 + +要求:找出 `s1` 中最短的(连续)子串 `w`,使得 `s2` 是 `w` 的子序列 。如果 `s1` 中没有窗口可以包含 `s2` 中的所有字符,返回空字符串 `""`。如果有不止一个最短长度的窗口,返回开始位置最靠左的那个。 + +## 解题思路 + +这道题跟「[76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)」有点类似。但这道题中字符的相对顺序需要保持一致。求解的思路如下: + +- 向右扩大窗口,匹配字符,直到匹配完 `s2` 的最后一个字符。 +- 当满足条件时,缩小窗口,并更新最小窗口的起始位置和最短长度。 +- 缩小窗口到不满足条件为止。 + +这道题的难点在于第二步中如何缩小窗口。当匹配到一个子序列时,可以采用逆向匹配的方式,从 `s2` 的最后一位字符匹配到 `s2` 的第一位字符。找到符合要求的最大下标,即是窗口的左边界。 + +整个算法的解题步骤如下: + +- 使用两个指针 `left`、`right` 代表窗口的边界,一开始都指向 `0` 。`min_len` 用来记录最小子序列的长度。`i`、`j` 作为索引,用于遍历字符串 `s1` 和 `s2`,一开始都为 `0`。 +- 遍历字符串 `s1` 的每一个字符,如果 `s1[i] == s2[j]`,则说明 `s2` 中第 `j` 个字符匹配了,向右移动 `j`,即 `j += 1`,然后继续匹配。 +- 如果 `j == len(s2)`,则说明 `s2` 中所有字符都匹配了。 + - 此时确定了窗口的右边界 `right = i`,并令 `j` 指向 `s2` 最后一个字符位置。 + - 从右至左逆向匹配字符串,找到窗口的左边界。 + - 判断当前窗口长度和窗口的最短长度,并更新最小窗口的起始位置和最短长度。 + - 令 `j = 0`,重新继续匹配 `s2`。 +- 向右移动 `i`,继续匹配。 +- 遍历完输出窗口的最短长度(需要判断是否有解)。 + +## 代码 + +```python +class Solution: + def minWindow(self, s1: str, s2: str) -> str: + i, j = 0, 0 + min_len = float('inf') + left, right = 0, 0 + while i < len(s1): + if s1[i] == s2[j]: + j += 1 + # 完成了匹配 + if j == len(s2): + right = i + j -= 1 + while j >= 0: + if s1[i] == s2[j]: + j -= 1 + i -= 1 + i += 1 + if right - i + 1 < min_len: + left = i + min_len = right - left + 1 + j = 0 + i += 1 + if min_len != float('inf'): + return s1[left: left + min_len] + return "" +``` + +## 参考资料 + +- 【题解】[c++ 简单好理解的 滑动窗口解法 和 动态规划解法 - 最小窗口子序列 - 力扣](https://leetcode.cn/problems/minimum-window-subsequence/solution/c-jian-dan-hao-li-jie-de-hua-dong-chuang-wguk/) +- 【题解】[727. 最小窗口子序列 C++ 滑动窗口 - 最小窗口子序列 - 力扣](https://leetcode.cn/problems/minimum-window-subsequence/solution/727-zui-xiao-chuang-kou-zi-xu-lie-c-hua-dong-chuan/) + diff --git a/docs/solutions/0700-0799/monotone-increasing-digits.md b/docs/solutions/0700-0799/monotone-increasing-digits.md new file mode 100644 index 00000000..c6cac012 --- /dev/null +++ b/docs/solutions/0700-0799/monotone-increasing-digits.md @@ -0,0 +1,38 @@ +# [0738. 单调递增的数字](https://leetcode.cn/problems/monotone-increasing-digits/) + +- 标签:贪心、数学 +- 难度:中等 + +## 题目链接 + +- [0738. 单调递增的数字 - 力扣](https://leetcode.cn/problems/monotone-increasing-digits/) + +## 题目大意 + +给定一个非负整数 n,找出小于等于 n 的最大整数,同时该整数需要满足其各个位数上的数字是单调递增的。 + +## 解题思路 + +为了方便操作,我们先将整数 n 转为 list 数组,即 n_list。 + +题目要求这个整数尽可能的大,那么这个数从高位开始,就应该尽可能的保持不变。那么我们需要从高位到低位,找到第一个满足 `n_list[i - 1] > n_list[i]` 的位置,然后把 `n_list[i] - 1`,再把剩下的低位都变为 9。 + +## 代码 + +```python +class Solution: + def monotoneIncreasingDigits(self, n: int) -> int: + n_list = list(str(n)) + size = len(n_list) + start_i = size + for i in range(size - 1, 0, -1): + if n_list[i - 1] > n_list[i]: + start_i = i + n_list[i - 1] = chr(ord(n_list[i - 1]) - 1) + + for i in range(start_i, size, 1): + n_list[i] = '9' + res = int(''.join(n_list)) + return res +``` + diff --git a/docs/solutions/0700-0799/my-calendar-i.md b/docs/solutions/0700-0799/my-calendar-i.md new file mode 100644 index 00000000..a8fcd512 --- /dev/null +++ b/docs/solutions/0700-0799/my-calendar-i.md @@ -0,0 +1,195 @@ +# [0729. 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/) + +- 标签:设计、线段树、二分查找、有序集合 +- 难度:中等 + +## 题目链接 + +- [0729. 我的日程安排表 I - 力扣](https://leetcode.cn/problems/my-calendar-i/) + +## 题目大意 + +**要求**:实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的日程安排不会造成重复预订 ,则可以存储这个新的日程安排。 + +日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 + +`MyCalendar` 类: + +- `MyCalendar()` 初始化日历对象。 +- `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 `True` 。否则,返回 `False` 并且不要将该日程安排添加到日历中。 + +**说明**: + +- 重复预订:当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订 。 +- $0 \le start < end \le 10^9$ +- 每个测试用例,调用 `book` 方法的次数最多不超过 `1000` 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyCalendar", "book", "book", "book"] +[[], [10, 20], [15, 25], [20, 30]] + +输出: +[null, true, false, true] + +解释: +MyCalendar myCalendar = new MyCalendar(); +myCalendar.book(10, 20); // return True +myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。 +myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。 +``` + +## 解题思路 + +### 思路 1:线段树 + +这道题可以使用线段树来做。 + +因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 + +- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 + +- 在 `book` 方法中,从线段树中查询 `[start, end - 1]` 区间上保存的日程区间个数。 + - 如果日程区间个数大于等于 `1`,则说明该日程添加到日历中会导致重复预订,则直接返回 `False`。 + - 如果日程区间个数小于 `1`,则说明该日程添加到日历中不会导致重复预定,则在线段树中将区间 `[start, end - 1]` 的日程区间个数 + 1,然后返回 `True`。 + +### 思路 1:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, function): + self.tree = SegTreeNode(0, int(1e9)) + self.function = function # function 是一个函数,左右区间的聚合方法 + + # 单点更新,将 nums[i] 更改为 val + def update_point(self, i, val): + self.__update_point(i, val, self.tree) + + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询,查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self, length): + nums = [0 for _ in range(length)] + for i in range(length): + nums[i] = self.query_interval(i, i) + return nums + + + # 以下为内部实现方法 + + # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] + def __update_point(self, i, val, node): + if node.left == node.right: + node.val = val # 叶子节点,节点值修改为 val + return + + if i <= node.mid: # 在左子树中更新节点值 + self.__update_point(i, val, node.leftNode) + else: # 在右子树中更新节点值 + self.__update_point(i, val, node.rightNode) + self.__pushup(node) # 向上更新节点的区间值 + + # 区间更新 + def __update_interval(self, q_left, q_right, val, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if node.lazy_tag is not None: + node.lazy_tag += val # 将当前节点的延迟标记增加 val + else: + node.lazy_tag = val # 将当前节点的延迟标记增加 val + node.val += val # 当前节点所在区间每个元素值增加 val + return + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, node.rightNode) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.leftNode and node.rightNode: + node.val = self.function(node.leftNode.val, node.rightNode.val) + + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + + lazy_tag = node.lazy_tag + if node.lazy_tag is None: + return + + if node.leftNode.lazy_tag is not None: + node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 + node.leftNode.val += lazy_tag # 左子节点每个元素值增加 lazy_tag + + if node.rightNode.lazy_tag is not None: + node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 + node.rightNode.val += lazy_tag # 右子节点每个元素值增加 lazy_tag + + node.lazy_tag = None # 更新当前节点的懒惰标记 + +class MyCalendar: + + def __init__(self): + self.STree = SegmentTree(lambda x, y: max(x, y)) + + + def book(self, start: int, end: int) -> bool: + if self.STree.query_interval(start, end - 1) >= 1: + return False + self.STree.update_interval(start, end - 1, 1) + return True +``` diff --git a/docs/solutions/0700-0799/my-calendar-ii.md b/docs/solutions/0700-0799/my-calendar-ii.md new file mode 100644 index 00000000..287d3354 --- /dev/null +++ b/docs/solutions/0700-0799/my-calendar-ii.md @@ -0,0 +1,194 @@ +# [731. 我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/) + +- 标签:设计、线段树、二分查找、有序集合 +- 难度:中等 + +## 题目链接 + +- [731. 我的日程安排表 II - 力扣](https://leetcode.cn/problems/my-calendar-ii/) + +## 题目大意 + +**要求**:实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。 + +日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 + +`MyCalendar` 类: + +- `MyCalendar()` 初始化日历对象。 +- `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 `True` 。否则,返回 `False` 并且不要将该日程安排添加到日历中。 + +**说明**: + +- 三重预定:当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订 。 +- $0 \le start < end \le 10^9$。 +- 每个测试用例,调用 `book` 方法的次数最多不超过 `1000` 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["MyCalendar", "book", "book", "book"] +[[], [10, 20], [15, 25], [20, 30]] +输出: +[null, true, false, true] + +解释: +MyCalendar myCalendar = new MyCalendar(); +myCalendar.book(10, 20); // return True +myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。 +myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。 +``` + +## 解题思路 + +### 思路 1:线段树 + +这道题可以使用线段树来做。 + +因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 + +- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 + +- 在 `book` 方法中,从线段树中查询 `[start, end - 1]` 区间上保存的日程区间个数。 + - 如果日程区间个数大于等于 `2`,则说明该日程添加到日历中会导致三重预订,则直接返回 `False`。 + - 如果日程区间个数小于 `2`,则说明该日程添加到日历中不会导致三重预订,则在线段树中将区间 `[start, end - 1]` 的日程区间个数 + 1,然后返回 `True`。 + +### 思路 1:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, function): + self.tree = SegTreeNode(0, int(1e9)) + self.function = function # function 是一个函数,左右区间的聚合方法 + + # 单点更新,将 nums[i] 更改为 val + def update_point(self, i, val): + self.__update_point(i, val, self.tree) + + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询,查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self, length): + nums = [0 for _ in range(length)] + for i in range(length): + nums[i] = self.query_interval(i, i) + return nums + + + # 以下为内部实现方法 + + # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] + def __update_point(self, i, val, node): + if node.left == node.right: + node.val = val # 叶子节点,节点值修改为 val + return + + if i <= node.mid: # 在左子树中更新节点值 + self.__update_point(i, val, node.leftNode) + else: # 在右子树中更新节点值 + self.__update_point(i, val, node.rightNode) + self.__pushup(node) # 向上更新节点的区间值 + + # 区间更新 + def __update_interval(self, q_left, q_right, val, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if node.lazy_tag is not None: + node.lazy_tag += val # 将当前节点的延迟标记增加 val + else: + node.lazy_tag = val # 将当前节点的延迟标记增加 val + node.val += val # 当前节点所在区间增加 val + return + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, node.rightNode) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.leftNode and node.rightNode: + node.val = self.function(node.leftNode.val, node.rightNode.val) + + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + + lazy_tag = node.lazy_tag + if node.lazy_tag is None: + return + + if node.leftNode.lazy_tag is not None: + node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 + node.leftNode.val += lazy_tag # 左子节点区间增加 lazy_tag + + if node.rightNode.lazy_tag is not None: + node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 + node.rightNode.val += lazy_tag # 右子节点区间增加 lazy_tag + + node.lazy_tag = None # 更新当前节点的懒惰标记 + +class MyCalendarTwo: + + def __init__(self): + self.STree = SegmentTree(lambda x, y: max(x, y)) + + + def book(self, start: int, end: int) -> bool: + if self.STree.query_interval(start, end - 1) >= 2: + return False + self.STree.update_interval(start, end - 1, 1) + return True +``` \ No newline at end of file diff --git a/docs/solutions/0700-0799/my-calendar-iii.md b/docs/solutions/0700-0799/my-calendar-iii.md new file mode 100644 index 00000000..6fd5c6e5 --- /dev/null +++ b/docs/solutions/0700-0799/my-calendar-iii.md @@ -0,0 +1,203 @@ +# [0732. 我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/) + +- 标签:设计、线段树、二分查找、有序集合 +- 难度:困难 + +## 题目链接 + +- [0732. 我的日程安排表 III - 力扣](https://leetcode.cn/problems/my-calendar-iii/) + +## 题目大意 + +**要求**:实现一个 `MyCalendarThree` 类来存放你的日程安排,你可以一直添加新的日程安排。 + +日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 + +`MyCalendarThree` 类: + +- `MyCalendarThree()` 初始化对象。 +- `int book(int start, int end)` 返回一个整数 `k`,表示日历中存在的 `k` 次预订的最大值。 + +**说明**: + +- `k` 次预定:当 `k` 个日程安排有一些时间上的交叉时(例如 `k` 个日程安排都在同一时间内),就会产生 `k` 次预订。 +- $0 \le start < end \le 10^9$ +- 每个测试用例,调用 `book` 函数最多不超过 `400` 次。 + +**示例**: + +- 示例 1: + +```python +输入 +["MyCalendarThree", "book", "book", "book", "book", "book", "book"] +[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]] +输出 +[null, 1, 1, 2, 3, 3, 3] + +解释 +MyCalendarThree myCalendarThree = new MyCalendarThree(); +myCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。 +myCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。 +myCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。 +myCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。 +myCalendarThree.book(5, 10); // 返回 3 +myCalendarThree.book(25, 55); // 返回 3 +``` + +## 解题思路 + +### 思路 1:线段树 + +这道题可以使用线段树来做。 + +因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 + +- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 + +- 在 `book` 方法中,在线段树中更新 `[start, end - 1]` 的交叉日程区间个数,即令其区间值整体加 `1`。 + +- 然后从线段树中查询区间 $[0, 10^9]$ 上保存的交叉日程区间个数,并返回。 + + +### 思路 1:代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, function): + self.tree = SegTreeNode(0, int(1e9)) + self.function = function # function 是一个函数,左右区间的聚合方法 + + # 单点更新,将 nums[i] 更改为 val + def update_point(self, i, val): + self.__update_point(i, val, self.tree) + + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询,查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self, length): + nums = [0 for _ in range(length)] + for i in range(length): + nums[i] = self.query_interval(i, i) + return nums + + + # 以下为内部实现方法 + + # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] + def __update_point(self, i, val, node): + if node.left == node.right: + node.val = val # 叶子节点,节点值修改为 val + return + + if i <= node.mid: # 在左子树中更新节点值 + self.__update_point(i, val, node.leftNode) + else: # 在右子树中更新节点值 + self.__update_point(i, val, node.rightNode) + self.__pushup(node) # 向上更新节点的区间值 + + # 区间更新 + def __update_interval(self, q_left, q_right, val, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if node.lazy_tag is not None: + node.lazy_tag += val # 将当前节点的延迟标记增加 val + else: + node.lazy_tag = val # 将当前节点的延迟标记增加 val + node.val += val # 当前节点所在区间增加 val + return + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, node.rightNode) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.leftNode and node.rightNode: + node.val = self.function(node.leftNode.val, node.rightNode.val) + + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + + lazy_tag = node.lazy_tag + if node.lazy_tag is None: + return + + if node.leftNode.lazy_tag is not None: + node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 + node.leftNode.val += lazy_tag # 左子节点区间增加 lazy_tag + + if node.rightNode.lazy_tag is not None: + node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 + node.rightNode.val += lazy_tag # 右子节点区间增加 lazy_tag + + node.lazy_tag = None # 更新当前节点的懒惰标记 + + +class MyCalendarThree: + + def __init__(self): + self.STree = SegmentTree(lambda x, y: max(x, y)) + + + def book(self, start: int, end: int) -> int: + self.STree.update_interval(start, end - 1, 1) + return self.STree.query_interval(0, int(1e9)) + + + +# Your MyCalendarThree object will be instantiated and called as such: +# obj = MyCalendarThree() +# param_1 = obj.book(start,end) +``` diff --git a/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md b/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md new file mode 100644 index 00000000..5b84a58d --- /dev/null +++ b/docs/solutions/0700-0799/number-of-subarrays-with-bounded-maximum.md @@ -0,0 +1,42 @@ +# [0795. 区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) + +- 标签:数组、双指针 +- 难度:中等 + +## 题目链接 + +- [0795. 区间子数组个数 - 力扣](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) + +## 题目大意 + +给定一个元素都是正整数的数组`A` ,正整数 `L` 以及 `R` (`L <= R`)。 + +求连续、非空且其中最大元素满足大于等于`L` 小于等于`R`的子数组个数。 + +## 解题思路 + +最大元素满足大于等于`L` 小于等于`R`的子数组个数 = 最大元素小于等于 `R` 的子数组个数 - 最大元素小于 `L` 的子数组个数。 + +其中「最大元素小于 `L` 的子数组个数」也可以转变为「最大元素小于等于 `L - 1` 的子数组个数」。那么现在的问题就变为了如何计算最大元素小于等于 `k` 的子数组个数。 + +我们使用 `count` 记录 小于等于 `k` 的连续元素数量,遍历一遍数组,如果遇到 `nums[i] <= k` 时,`count` 累加,表示在此位置上结束的有效子数组数量为 `count + 1`。如果遇到 `nums[i] > k` 时,`count` 重新开始计算。每次遍历完将有效子数组数量累加到答案中。 + +## 代码 + +```python +class Solution: + def numSubarrayMaxK(self, nums, k): + ans = 0 + count = 0 + for i in range(len(nums)): + if nums[i] <= k: + count += 1 + else: + count = 0 + ans += count + return ans + + def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int: + return self.numSubarrayMaxK(nums, right) - self.numSubarrayMaxK(nums, left - 1) +``` + diff --git a/docs/solutions/0700-0799/open-the-lock.md b/docs/solutions/0700-0799/open-the-lock.md new file mode 100644 index 00000000..67421a98 --- /dev/null +++ b/docs/solutions/0700-0799/open-the-lock.md @@ -0,0 +1,115 @@ +# [0752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) + +- 标签:广度优先搜索、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0752. 打开转盘锁 - 力扣](https://leetcode.cn/problems/open-the-lock/) + +## 题目大意 + +**描述**:有一把带有四个数字的密码锁,每个位置上有 `0` ~ `9` 共 `10` 个数字。每次只能将其中一个位置上的数字转动一下。可以向上转,也可以向下转。比如:`1 -> 2`、`2 -> 1`。 + +密码锁的初始数字为:`0000`。现在给定一组表示死亡数字的字符串数组 `deadends`,和一个带有四位数字的目标字符串 `target`。 + +如果密码锁转动到 `deadends` 中任一字符串状态,则锁就会永久锁定,无法再次旋转。 + +**要求**:给出使得锁的状态由 `0000` 转动到 `target` 的最小的选择次数。如果无论如何不能解锁,返回 `-1` 。 + +**说明**: + +- $1 \le deadends.length \le 500$ + $deadends[i].length == 4$ + $target.length == 4$ + $target$ 不在 $deadends$ 之中 + $target$ 和 $deadends[i]$ 仅由若干位数字组成。 + +**示例**: + +- 示例 1: + +```python +输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202" +输出:6 +解释: +可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。 +注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的, +因为当拨动到 "0102" 时这个锁就会被锁定。 +``` + +- 示例 2: + +```python +输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888" +输出:-1 +解释:无法旋转到目标数字且不被锁定。 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +1. 定义 `visited` 为标记访问节点的 set 集合变量,`queue` 为存放节点的队列。 +2. 将`0000` 状态标记为访问,并将其加入队列 `queue`。 +3. 将当前队列中的所有状态依次出队,判断这些状态是否为死亡字符串。 + 1. 如果为死亡字符串,则跳过该状态,否则继续执行。 + 2. 如果为目标字符串,则返回当前路径长度,否则继续执行。 + +4. 枚举当前状态所有位置所能到达的所有状态(通过向上或者向下旋转),并判断是否访问过该状态。 +5. 如果之前出现过该状态,则继续执行,否则将其存入队列,并标记访问。 +6. 遍历完步骤 3 中当前队列中的所有状态,令路径长度加 `1`,继续执行 3 ~ 5 步,直到队列为空。 +7. 如果队列为空,也未能到达目标状态,则返回 `-1`。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def openLock(self, deadends: List[str], target: str) -> int: + queue = collections.deque(['0000']) + visited = set(['0000']) + deadset = set(deadends) + level = 0 + while queue: + size = len(queue) + for _ in range(size): + cur = queue.popleft() + if cur in deadset: + continue + if cur == target: + return level + for i in range(len(cur)): + up = self.upward_adjust(cur, i) + if up not in visited: + queue.append(up) + visited.add(up) + down = self.downward_adjust(cur, i) + if down not in visited: + queue.append(down) + visited.add(down) + level += 1 + return -1 + + def upward_adjust(self, s, i): + s_list = list(s) + if s_list[i] == '9': + s_list[i] = '0' + else: + s_list[i] = chr(ord(s_list[i]) + 1) + return "".join(s_list) + + def downward_adjust(self, s, i): + s_list = list(s) + if s_list[i] == '0': + s_list[i] = '9' + else: + s_list[i] = chr(ord(s_list[i]) - 1) + return "".join(s_list) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(10^d \times d^2 + m \times d)$。其中 $d$ 是数字的位数,$m$ 是数组 $deadends$ 的长度。 +- **空间复杂度**:$O(10^D \times d + m)$。 diff --git a/docs/solutions/0700-0799/partition-labels.md b/docs/solutions/0700-0799/partition-labels.md new file mode 100644 index 00000000..ad819aa1 --- /dev/null +++ b/docs/solutions/0700-0799/partition-labels.md @@ -0,0 +1,46 @@ +# [0763. 划分字母区间](https://leetcode.cn/problems/partition-labels/) + +- 标签:贪心、哈希表、双指针、字符串 +- 难度:中等 + +## 题目链接 + +- [0763. 划分字母区间 - 力扣](https://leetcode.cn/problems/partition-labels/) + +## 题目大意 + +给定一个由小写字母组成的字符串 `s`。要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 + +要求:返回一个表示每个字符串片段的长度的列表。 + +## 解题思路 + +因为同一字母最多出现在一个片段中,则同一字母第一次出现的下标位置和最后一次出现的下标位置肯定在同一个片段中。 + +我们先遍历一遍字符串,用哈希表 letter_map 存储下每一个字母最后一次出现的下标位置。 + +为了得到尽可能的片段,我们使用贪心的思想: + +- 从头开始遍历字符串,遍历同时维护当前片段的开始位置 start 和结束位置 end。 +- 对于字符串中的每个字符 `s[i]`,得到当前字母的最后一次出现的下标位置 `letter_map[s[i]]`,则当前片段的结束位置一定不会早于 `letter_map[s[i]]`,所以更新 end 值为 `end = max(end, letter_map[s[i]])`。 +- 当访问到 `i == end` 时,当前片段访问结束,当前片段的下标范围为 `[start, end]`,长度为 `end - start + 1`,将其长度加入答案数组,并更新 start 值为 `i + 1`,继续遍历。 +- 最终返回答案数组。 + +## 代码 + +```python +class Solution: + def partitionLabels(self, s: str) -> List[int]: + letter_map = dict() + for i in range(len(s)): + letter_map[s[i]] = i + res = [] + start, end = 0, 0 + for i in range(len(s)): + end = max(end, letter_map[s[i]]) + if i == end: + res.append(end - start + 1) + start = i + 1 + return res +``` + diff --git a/docs/solutions/0700-0799/range-module.md b/docs/solutions/0700-0799/range-module.md new file mode 100644 index 00000000..f79c8690 --- /dev/null +++ b/docs/solutions/0700-0799/range-module.md @@ -0,0 +1,137 @@ +# [0715. Range 模块](https://leetcode.cn/problems/range-module/) + +- 标签:设计、线段树、有序集合 +- 难度:困难 + +## 题目链接 + +- [0715. Range 模块 - 力扣](https://leetcode.cn/problems/range-module/) + +## 题目大意 + +**描述**:`Range` 模块是跟踪数字范围的模块。 + +**要求**: + +- 设计一个数据结构来跟踪查询半开区间 `[left, right)` 内的数字是否被跟踪。 +- 实现 `RangeModule` 类: + - `RangeModule()` 初始化数据结构的对象。 + - `void addRange(int left, int right)` 添加半开区间 `[left, right)`,跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 `[left, right)` 中尚未跟踪的任何数字到该区间中。 + - `boolean queryRange(int left, int right)` 只有在当前正在跟踪区间 `[left, right)` 中的每一个实数时,才返回 `True` ,否则返回 `False`。 + - `void removeRange(int left, int right)` 停止跟踪半开区间 `[left, right)` 中当前正在跟踪的每个实数。 + +**说明**: + +- $1 \le left < right \le 10^9$。 + +**示例**: + +- 示例 1: + +``` +rangeModule = RangeModule() -> null +rangeModule.addRange(10, 20) -> null +rangeModule.removeRange(14, 16) -> null +rangeModule.queryRange(10, 14) -> True +rangeModule.queryRange(13, 15) -> False +rangeModule.queryRange(16, 17) -> True +``` + +## 解题思路 + +### 思路 1:线段树 + +这道题可以使用线段树来做,但是效率比较差。 + +区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。题目要求的是半开区间 `[left, right)` ,而线段树中常用的是闭合区间。但是我们可以将半开区间 `[left, right)` 转为 `[left, right - 1]` 的闭合空间。 + +这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 + +## 代码 + +### 思路 1 代码: + +```python +# 线段树的节点类 +class TreeNode: + def __init__(self, left, right, val=False, lazy_tag=None, letNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = (left + right) >> 1 + self.leftNode = letNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 + + +class RangeModule: + + def __init__(self): + self.tree = TreeNode(0, int(1e9)) + + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.leftNode and node.rightNode: + node.val = node.leftNode.val and node.rightNode.val + else: + node.val = False + + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if not node.leftNode: + node.leftNode = TreeNode(node.left, node.mid) + if not node.rightNode: + node.rightNode = TreeNode(node.mid + 1, node.right) + if node.lazy_tag is not None: + node.leftNode.lazy_tag = node.lazy_tag # 更新左子节点懒惰标记 + node.leftNode.val = node.lazy_tag # 左子节点每个元素值增加 lazy_tag + + node.rightNode.lazy_tag = node.lazy_tag # 更新右子节点懒惰标记 + node.rightNode.val = node.lazy_tag # 右子节点每个元素值增加 lazy_tag + + node.lazy_tag = None # 更新当前节点的懒惰标记 + + # 区间更新 + def __update_interval(self, q_left, q_right, val, node): + if q_left <= node.left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + node.lazy_tag = val # 将当前节点的延迟标记增加 val + node.val = val # 当前节点所在区间每个元素值增加 val + return + + self.__pushdown(node) + + if q_left <= node.mid: + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if q_left <= node.left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + + # 需要向下更新节点所在区间的左右子节点的值和懒惰标记 + self.__pushdown(node) + + if q_right <= node.mid: + return self.__query_interval(q_left, q_right, node.leftNode) + if q_left > node.mid: + return self.__query_interval(q_left, q_right, node.rightNode) + + return self.__query_interval(q_left, q_right, node.leftNode) and self.__query_interval(q_left, q_right, node.rightNode) # 返回左右子树元素值的聚合计算结果 + + + def addRange(self, left: int, right: int) -> None: + self.__update_interval(left, right - 1, True, self.tree) + + + def queryRange(self, left: int, right: int) -> bool: + return self.__query_interval(left, right - 1, self.tree) + + + def removeRange(self, left: int, right: int) -> None: + self.__update_interval(left, right - 1, False, self.tree) +``` + diff --git a/docs/solutions/0700-0799/rotate-string.md b/docs/solutions/0700-0799/rotate-string.md new file mode 100644 index 00000000..360398cf --- /dev/null +++ b/docs/solutions/0700-0799/rotate-string.md @@ -0,0 +1,96 @@ +# [0796. 旋转字符串](https://leetcode.cn/problems/rotate-string/) + +- 标签:字符串、字符串匹配 +- 难度:简单 + +## 题目链接 + +- [0796. 旋转字符串 - 力扣](https://leetcode.cn/problems/rotate-string/) + +## 题目大意 + +**描述**:给定两个字符串 `s` 和 `goal`。 + +**要求**:如果 `s` 在若干次旋转之后,能变为 `goal`,则返回 `True`,否则返回 `False`。 + +**说明**: + +- `s` 的旋转操作:将 `s` 最左侧的字符移动到最右边。 + - 比如:`s = "abcde"`,在旋转一次之后结果就是 `s = "bcdea"`。 +- $1 \le s.length, goal.length \le 100$。 +- `s` 和 `goal` 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入: s = "abcde", goal = "cdeab" +输出: true +``` + +- 示例 2: + +```python +输入: s = "abcde", goal = "abced" +输出: false +``` + +## 解题思路 + +### 思路 1:KMP 算法 + +其实将两个字符串 `s` 拼接在一起,就包含了所有从 `s` 进行旋转后的字符串。那么我们只需要判断一下 `goal` 是否为 `s + s` 的子串即可。可以用 KMP 算法来做。 + +1. 先排除掉几种不可能的情况,比如 `s` 为空串的情况,`goal` 为空串的情况,`len(s) != len(goal)` 的情况。 +2. 然后使用 KMP 算法计算出 `goal` 在 `s + s` 中的下标位置 `index`(`s + s` 可用取余运算模拟)。 +3. 如果 `index == -1`,则说明 `s` 在若干次旋转之后,不能能变为 `goal`,则返回 `False`。 +4. 如果 `index != -1`,则说明 `s` 在若干次旋转之后,能变为 `goal`,则返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def kmp(self, T: str, p: str) -> int: + n, m = len(T), len(p) + + next = self.generateNext(p) + + i, j = 0, 0 + while i - j < n: + while j > 0 and T[i % n] != p[j]: + j = next[j - 1] + if T[i % n] == p[j]: + j += 1 + if j == m: + return i - m + 1 + i += 1 + return -1 + + def generateNext(self, p: str): + m = len(p) + next = [0 for _ in range(m)] + + left = 0 + for right in range(1, m): + while left > 0 and p[left] != p[right]: + left = next[left - 1] + if p[left] == p[right]: + left += 1 + next[right] = left + + return next + + def rotateString(self, s: str, goal: str) -> bool: + if not s or not goal or len(s) != len(goal): + return False + index = self.kmp(s, goal) + if index == -1: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中文本串 $s$ 的长度为 $n$,模式串 $goal$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 diff --git a/docs/solutions/0700-0799/rotated-digits.md b/docs/solutions/0700-0799/rotated-digits.md new file mode 100644 index 00000000..893273ff --- /dev/null +++ b/docs/solutions/0700-0799/rotated-digits.md @@ -0,0 +1,138 @@ +# [0788. 旋转数字](https://leetcode.cn/problems/rotated-digits/) + +- 标签:数学、动态规划 +- 难度:中等 + +## 题目链接 + +- [0788. 旋转数字 - 力扣](https://leetcode.cn/problems/rotated-digits/) + +## 题目大意 + +**描述**:给定搞一个正整数 $n$。 + +**要求**:计算从 $1$ 到 $n$ 中有多少个数 $x$ 是好数。 + +**说明**: + +- **好数**:如果一个数 $x$ 的每位数字逐个被旋转 180 度之后,我们仍可以得到一个有效的,且和 $x$ 不同的数,则成该数为好数。 +- 如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。$0$、$1$ 和 $8$ 被旋转后仍然是它们自己;$2$ 和 $5$ 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,$2$ 和 $5$ 互为镜像);$6$ 和 $9$ 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。 +- $n$ 的取值范围是 $[1, 10000]$。 + +**示例**: + +- 示例 1: + +```python +输入: 10 +输出: 4 +解释: +在 [1, 10] 中有四个好数: 2, 5, 6, 9。 +注意 1 和 10 不是好数, 因为他们在旋转之后不变。 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +根据题目描述,一个数满足:数中没有出现 $3$、$4$、$7$,并且至少出现一次 $2$、$5$、$6$ 或 $9$,就是好数。 + +因此,我们可以枚举 $[1, n]$ 中的每一个正整数 $x$,并判断该正整数 $x$ 的数位中是否满足没有出现 $3$、$4$、$7$,并且至少一次出现了 $2$、$5$、$6$ 或 $9$,如果满足,则该正整数 $x$ 位好数,否则不是好数。 + +最后统计好数的方案个数并将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def rotatedDigits(self, n: int) -> int: + check = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] + ans = 0 + for i in range(1, n + 1): + flag = False + num = i + while num: + digit = num % 10 + num //= 10 + if check[digit] == 1: + flag = True + elif check[digit] == -1: + flag = False + break + if flag: + ans += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(\log n)$。 + +### 思路 2:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, hasDiff, isLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。其中: + +1. $pos$ 表示当前枚举的数位位置。 +2. $hasDiff$ 表示当前是否用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字。 +3. $isLimit$ 表示前一位数位是否等于上界,用于限制本次搜索的数位范围。 + +接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, False, True)` 开始递归。 `dfs(0, False, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $hasDiff == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $hasDiff == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 因为不需要考虑前导 $0$,所以当前所能选择的最小数字 $minX$ 为 $0$。 +5. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 +6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 +7. 如果当前数位与之前数位没有出现 $3$、$4$、$7$,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, hasDiff or check[d], isLimit and d == maxX)`。 + 1. `hasDiff or check[d]` 表示当前是否用到 $2$、$5$、$6$ 或 $9$ 中任何一个数字或者没有用到 $3$、$4$、$7$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 +8. 最后的方案数为 `dfs(0, False, True)`,将其返回即可。 + +### 思路 2:代码 + +```python +class Solution: + def rotatedDigits(self, n: int) -> int: + check = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] + + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # hasDiff: 之前选过的数字是否包含 2,5,6,9 中至少一个。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + def dfs(pos, hasDiff, isLimit): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(hasDiff) + + ans = 0 + # 不需要考虑前导 0,则最小可选择数字为 0 + minX = 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + # d 不在选择的数字集合中,即之前没有选择过 d + if check[d] != -1: + ans += dfs(pos + 1, hasDiff or check[d], isLimit and d == maxX) + return ans + + return dfs(0, False, True) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0700-0799/search-in-a-binary-search-tree.md b/docs/solutions/0700-0799/search-in-a-binary-search-tree.md new file mode 100644 index 00000000..16c1a678 --- /dev/null +++ b/docs/solutions/0700-0799/search-in-a-binary-search-tree.md @@ -0,0 +1,69 @@ +# [0700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) + +- 标签:树、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [0700. 二叉搜索树中的搜索 - 力扣](https://leetcode.cn/problems/search-in-a-binary-search-tree/) + +## 题目大意 + +**描述**:给定一个二叉搜索树和一个值 `val`。 + +**要求**:在二叉搜索树中查找节点值等于 `val` 的节点,并返回该节点。 + +**说明**: + +- 数中节点数在 $[1, 5000]$ 范围内。 +- $1 \le Node.val \le 10^7$。 +- `root` 是二叉搜索树。 +- $1 \le val \le 10^7$。 + +**示例**: + +- 示例 1: + +![img](https://assets.leetcode.com/uploads/2021/01/12/tree1.jpg) + +```python +输入:root = [4,2,7,1,3], val = 2 +输出:[2,1,3] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/01/12/tree2.jpg) + +```python +输入:root = [4,2,7,1,3], val = 5 +输出:[] +``` + +## 解题思路 + +### 思路 1:递归 + +1. 从根节点 `root` 开始向下递归遍历。 + 1. 如果 `val` 等于当前节点的值,即 `val == root.val`,则返回 `root`; + 2. 如果 `val` 小于当前节点的值 ,即 `val < root.val`,则递归遍历左子树,继续查找; + 3. 如果 `val` 大于当前节点的值 ,即 `val > root.val`,则递归遍历右子树,继续查找。 +2. 如果遍历到最后也没有找到,则返回空节点。 + +### 思路 1:代码 + +```python +class Solution: + def searchBST(self, root: TreeNode, val: int) -> TreeNode: + if not root or val == root.val: + return root + if val < root.val: + return self.searchBST(root.left, val) + else: + return self.searchBST(root.right, val) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉搜索树的节点数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md b/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md new file mode 100644 index 00000000..ce894519 --- /dev/null +++ b/docs/solutions/0700-0799/search-in-a-sorted-array-of-unknown-size.md @@ -0,0 +1,81 @@ +# [0702. 搜索长度未知的有序数组](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) + +- 标签:数组、二分查找、交互 +- 难度:中等 + +## 题目链接 + +- [0702. 搜索长度未知的有序数组 - 力扣](https://leetcode.cn/problems/search-in-a-sorted-array-of-unknown-size/) + +## 题目大意 + +**描述**:给定一个升序数组 $secret$,但是数组的大小是未知的。我们无法直接访问数组,智能通过 `ArrayReader` 接口去访问他。我们可以通过接口 `reader.get(k)`: + +1. 如果数组访问未越界,则返回数组 $secret$ 中第 $k$ 个下标位置的元素值。 +2. 如果数组访问越界,则接口返回 $2^{31} - 1$。 + +现在再给定一个数字 $target$。 + +**要求**:从 $secret$ 中找出 $secret[k] == target$ 的下标位置 $k$,如果 $secret$ 中不存在 $target$,则返回 $-1$。 + +**说明**: + +- $1 \le secret.length \le 10^4$。 +- $-10^4 \le secret[i], target \le 10^4$。 +- $secret$ 严格递增。 + +**示例**: + +- 示例 1: + +```python +输入: secret = [-1,0,3,5,9,12], target = 9 +输出: 4 +解释: 9 存在在 nums 中,下标为 4 +``` + +- 示例 2: + +```python +输入: secret = [-1,0,3,5,9,12], target = 2 +输出: -1 +解释: 2 不在数组中所以返回 -1 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +这道题的关键点在于找到数组的大小,以便确定查找的右边界位置。右边界可以通过倍增的方式快速查找。在查找右边界的同时,也能将左边界的范围进一步缩小。等确定了左右边界,就可以使用二分查找算法快速查找 $target$。 + +### 思路 1:代码 + +```python +class Solution: + def binarySearch(self, reader, left, right, target): + while left < right: + mid = left + (right - left) // 2 + if target > reader.get(mid): + left = mid + 1 + else: + right = mid + if reader.get(left) == target: + return left + else: + return -1 + + def search(self, reader, target): + left = 0 + right = 1 + while reader.get(right) < target: + left = right + right <<= 1 + + return self.binarySearch(reader, left, right, target) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$,其中 $n$ 为数组长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/subarray-product-less-than-k.md b/docs/solutions/0700-0799/subarray-product-less-than-k.md new file mode 100644 index 00000000..459ffe21 --- /dev/null +++ b/docs/solutions/0700-0799/subarray-product-less-than-k.md @@ -0,0 +1,82 @@ +# [0713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) + +- 标签:数组、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0713. 乘积小于 K 的子数组 - 力扣](https://leetcode.cn/problems/subarray-product-less-than-k/) + +## 题目大意 + +**描述**:给定一个正整数数组 $nums$ 和整数 $k$。 + +**要求**:找出该数组内乘积小于 $k$ 的连续的子数组的个数。 + +**说明**: + +- $1 \le nums.length \le 3 * 10^4$。 +- $1 \le nums[i] \le 1000$。 +- $0 \le k \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [10,5,2,6], k = 100 +输出:8 +解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3], k = 0 +输出:0 +``` + +## 解题思路 + +### 思路 1:滑动窗口(不定长度) + +1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 $window\underline{\hspace{0.5em}}product$ 都小于 $k$。使用 $window\underline{\hspace{0.5em}}product$ 记录窗口中的乘积值,使用 $count$ 记录符合要求的子数组个数。 +2. 一开始,$left$、$right$ 都指向 $0$。 +3. 向右移动 $right$,将最右侧元素加入当前子数组乘积 $window\underline{\hspace{0.5em}}product$ 中。 +4. 如果 $window\underline{\hspace{0.5em}}product \ge k$,则不断右移 $left$,缩小滑动窗口长度,并更新当前乘积值 $window\underline{\hspace{0.5em}}product$ 直到 $window\underline{\hspace{0.5em}}product < k$。 +5. 记录累积答案个数加 $1$,继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +6. 输出累积答案个数。 + +### 思路 1:代码 + +```python +class Solution: + def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int: + if k <= 1: + return 0 + + size = len(nums) + left = 0 + right = 0 + window_product = 1 + + count = 0 + + while right < size: + window_product *= nums[right] + + while window_product >= k: + window_product /= nums[left] + left += 1 + + count += (right - left + 1) + right += 1 + + return count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0700-0799/swim-in-rising-water.md b/docs/solutions/0700-0799/swim-in-rising-water.md new file mode 100644 index 00000000..e7cc1d1a --- /dev/null +++ b/docs/solutions/0700-0799/swim-in-rising-water.md @@ -0,0 +1,129 @@ +# [0778. 水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0778. 水位上升的泳池中游泳 - 力扣](https://leetcode.cn/problems/swim-in-rising-water/) + +## 题目大意 + +**描述**:给定一个 $n \times n$ 大小的二维数组 $grid$,每一个方格的值 $grid[i][j]$ 表示为位置 $(i, j)$ 的高度。 + +现在要从左上角 $(0, 0)$ 位置出发,经过方格的一些点,到达右下角 $(n - 1, n - 1)$ 位置上。其中所经过路径的花费为这条路径上所有位置的最大高度。 + +**要求**:计算从 $(0, 0)$ 位置到 $(n - 1, n - 1)$ 的最优路径的花费。 + +**说明**: + +- **最优路径**:路径上最大高度最小的那条路径。 +- $n == grid.length$。 +- $n == grid[i].length$。 +- $1 \le n \le 50$。 +- $0 \le grid[i][j] < n2$。 +- $grid[i][j]$ 中每个值均无重复。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/06/29/swim1-grid.jpg) + +```python +输入: grid = [[0,2],[1,3]] +输出: 3 +解释: +时间为 0 时,你位于坐标方格的位置为 (0, 0)。 +此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。 +等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/06/29/swim2-grid-1.jpg) + +```python +输入: grid = [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]] +输出: 16 +解释: 最终的路线用加粗进行了标记。 +我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的。 +``` + +## 解题思路 + +### 思路 1:并查集 + +将整个网络抽象为一个无向图,每个点与相邻的点(上下左右)之间都存在一条无向边,边的权重为两个点之间的最大高度。 + +我们要找到左上角到右下角的最优路径,可以遍历所有的点,将所有的边存储到数组中,每条边的存储格式为 $[x, y, h]$,意思是编号 $x$ 的点和编号为 $y$ 的点之间的权重为 $h$。 + +然后按照权重从小到大的顺序,对所有边进行排序。 + +再按照权重大小遍历所有边,将其依次加入并查集中。并且每次都需要判断 $(0, 0)$ 点和 $(n - 1, n - 1)$ 点是否连通。 + +如果连通,则该边的权重即为答案。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def swimInWater(self, grid: List[List[int]]) -> int: + row_size = len(grid) + col_size = len(grid[0]) + size = row_size * col_size + edges = [] + for row in range(row_size): + for col in range(col_size): + if row < row_size - 1: + x = row * col_size + col + y = (row + 1) * col_size + col + h = max(grid[row][col], grid[row + 1][col]) + edges.append([x, y, h]) + if col < col_size - 1: + x = row * col_size + col + y = row * col_size + col + 1 + h = max(grid[row][col], grid[row][col + 1]) + edges.append([x, y, h]) + + edges.sort(key=lambda x: x[2]) + + union_find = UnionFind(size) + + for edge in edges: + x, y, h = edge[0], edge[1], edge[2] + union_find.union(x, y) + if union_find.is_connected(0, size - 1): + return h + return 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n \times \alpha(m \times n))$,其中 $\alpha$ 是反 Ackerman 函数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0700-0799/to-lower-case.md b/docs/solutions/0700-0799/to-lower-case.md new file mode 100644 index 00000000..6e86e7cc --- /dev/null +++ b/docs/solutions/0700-0799/to-lower-case.md @@ -0,0 +1,86 @@ +# [0709. 转换成小写字母](https://leetcode.cn/problems/to-lower-case/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [0709. 转换成小写字母 - 力扣](https://leetcode.cn/problems/to-lower-case/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。 + +**说明**: + +- $1 \le s.length \le 100$。 +- $s$ 由 ASCII 字符集中的可打印字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "Hello" +输出:"hello" +``` + +- 示例 2: + +```python +输入:s = "LOVELY" +输出:"lovely" +``` + +## 解题思路 + +### 思路 1:直接模拟 + +- 大写字母 $A \sim Z$ 的 ASCII 码范围为 $[65, 90]$。 +- 小写字母 $a \sim z$ 的 ASCII 码范围为 $[97, 122]$。 + +将大写字母的 ASCII 码加 $32$,就得到了对应的小写字母,则解决步骤如下: + +1. 使用一个字符串变量 $ans$ 存储最终答案字符串。 +2. 遍历字符串 $s$,对于当前字符 $ch$: + 1. 如果 $ch$ 的 ASCII 码范围在 $[65, 90]$,则说明 $ch$ 为大写字母。将 $ch$ 的 ASCII 码增加 $32$,再转换为对应的字符,存入字符串 $ans$ 的末尾。 + 2. 如果 $ch$ 的 ASCII 码范围不在 $[65, 90]$,则说明 $ch$ 为小写字母。直接将 $ch$ 存入字符串 $ans$ 的末尾。 +3. 遍历完字符串 $s$,返回答案字符串 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def toLowerCase(self, s: str) -> str: + ans = "" + for ch in s: + if ord('A') <= ord(ch) <= ord('Z'): + ans += chr(ord(ch) + 32) + else: + ans += ch + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 + +### 思路 2:使用 API + +Python 语言中自带大写字母转小写字母的 API:`lower()`,用 API 转换完成之后,直接返回新的字符串。 + +### 思路 2:代码 + +```python +class Solution: + def toLowerCase(self, s: str) -> str: + return s.lower() +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0700-0799/toeplitz-matrix.md b/docs/solutions/0700-0799/toeplitz-matrix.md new file mode 100644 index 00000000..0f1fba49 --- /dev/null +++ b/docs/solutions/0700-0799/toeplitz-matrix.md @@ -0,0 +1,73 @@ +# [0766. 托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) + +- 标签:数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [0766. 托普利茨矩阵 - 力扣](https://leetcode.cn/problems/toeplitz-matrix/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的矩阵 $matrix$。 + +**要求**:如果 $matrix$ 是托普利茨矩阵,则返回 `True`;否则返回 `False`。 + +**说明**: + +- **托普利茨矩阵**:矩阵上每一条由左上到右下的对角线上的元素都相同。 +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 20$。 +- $0 \le matrix[i][j] \le 99$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/04/ex1.jpg) + +```python +输入:matrix = [[1,2,3,4],[5,1,2,3],[9,5,1,2]] +输出:true +解释: +在上述矩阵中, 其对角线为: +"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。 +各条对角线上的所有元素均相同, 因此答案是 True。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/04/ex2.jpg) + +```python +输入:matrix = [[1,2],[2,2]] +输出:false +解释: +对角线 "[1, 2]" 上的元素不同。 +``` + +## 解题思路 + +### 思路 1:简单模拟 + +1. 两层循环遍历矩阵,依次判断矩阵当前位置 $(i, j)$ 上的值 $matrix[i][j]$ 与其左上角位置 $(i - 1, j - 1)$ 位置上的值 $matrix[i - 1][j - 1]$ 是否相等。 +2. 如果不相等,则返回 `False`。 +3. 遍历完,则返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool: + for i in range(1, len(matrix)): + for j in range(1, len(matrix[0])): + if matrix[i][j] != matrix[i - 1][j - 1]: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别是矩阵 $matrix$ 的行数、列数。 +- **空间复杂度**:$O(m \times n)$。 diff --git a/docs/solutions/0800-0899/backspace-string-compare.md b/docs/solutions/0800-0899/backspace-string-compare.md new file mode 100644 index 00000000..b1cd1e02 --- /dev/null +++ b/docs/solutions/0800-0899/backspace-string-compare.md @@ -0,0 +1,140 @@ +# [0844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) + +- 标签:栈、双指针、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [0844. 比较含退格的字符串 - 力扣](https://leetcode.cn/problems/backspace-string-compare/) + +## 题目大意 + +**描述**:给定 $s$ 和 $t$ 两个字符串。字符串中的 `#` 代表退格字符。 + +**要求**:当它们分别被输入到空白的文本编辑器后,判断二者是否相等。如果相等,返回 $True$;否则,返回 $False$。 + +**说明**: + +- 如果对空文本输入退格字符,文本继续为空。 +- $1 \le s.length, t.length \le 200$。 +- $s$ 和 $t$ 只含有小写字母以及字符 `#`。 + +**示例**: + +- 示例 1: + +```python +输入:s = "ab#c", t = "ad#c" +输出:true +解释:s 和 t 都会变成 "ac"。 +``` + +- 示例 2: + +```python +输入:s = "ab##", t = "c#d#" +输出:true +解释:s 和 t 都会变成 ""。 +``` + +## 解题思路 + +这道题的第一个思路是用栈,第二个思路是使用分离双指针。 + +### 思路 1:栈 + +- 定义一个构建方法,用来将含有退格字符串构建为删除退格的字符串。构建方法如下。 + - 使用一个栈存放删除退格的字符串。 + - 遍历字符串,如果遇到的字符不是 `#`,则将其插入到栈中。 + - 如果遇到的字符是 `#`,且当前栈不为空,则将当前栈顶元素弹出。 +- 分别使用构建方法处理字符串 $s$ 和 $t$,如果处理完的字符串 $s$ 和 $t$ 相等,则返回 $True$,否则返回 $False$。 + +### 思路 1:代码 + +```python +class Solution: + def build(self, s: str): + stack = [] + for ch in s: + if ch != '#': + stack.append(ch) + elif stack: + stack.pop() + return stack + + def backspaceCompare(self, s: str, t: str) -> bool: + return self.build(s) == self.build(t) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 和 $m$ 分别为字符串 $s$、$t$ 的长度。 +- **空间复杂度**:$O(n + m)$。 + +### 思路 2:分离双指针 + +由于 `#` 会消除左侧字符,而不会影响右侧字符,所以我们选择从字符串尾端遍历 $s$、$t$ 字符串。具体做法如下: + +- 使用分离双指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$。$left\underline{\hspace{0.5em}}1$ 指向字符串 $s$ 末尾,$left\underline{\hspace{0.5em}}2$ 指向字符串 $t$ 末尾。使用 $sign\underline{\hspace{0.5em}}1$、$sign\underline{\hspace{0.5em}}2$ 标记字符串 $s$、$t$ 中当前退格字符个数。 +- 从后到前遍历字符串 $s$、$t$。 + - 先来循环处理字符串 $s$ 尾端 `#` 的影响,具体如下: + - 如果当前字符是 `#`,则更新 $s$ 当前退格字符个数,即 `sign_1 += 1`。同时将 $left\underline{\hspace{0.5em}}1$ 左移。 + - 如果 $s$ 当前退格字符个数大于 $0$,则退格数减一,即 `sign_1 -= 1`。同时将 $left\underline{\hspace{0.5em}}1$ 左移。 + - 如果 $s$ 当前为普通字符,则跳出循环。 + - 同理再来处理字符串 $t$ 尾端 `#` 的影响,具体如下: + - 如果当前字符是 `#`,则更新 $t$ 当前退格字符个数,即 `sign_2 += 1`。同时将 $left\underline{\hspace{0.5em}}2$ 左移。 + - 如果 $t$ 当前退格字符个数大于 $0$,则退格数减一,即 `sign_2 -= 1`。同时将 $left\underline{\hspace{0.5em}}2$ 左移。 + - 如果 $t$ 当前为普通字符,则跳出循环。 + - 处理完,如果两个字符串为空,则说明匹配,直接返回 $True$。 + - 再先排除长度不匹配的情况,直接返回 $False$。 + - 最后判断 $s[left\underline{\hspace{0.5em}}1]$ 是否等于 $s[left\underline{\hspace{0.5em}}2]$。不等于则直接返回 $False$,等于则令 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$ 左移,继续遍历。 +- 遍历完没有出现不匹配的情况,则返回 $True$。 + +### 思路 2:代码 + +```python +class Solution: + def backspaceCompare(self, s: str, t: str) -> bool: + left_1, left_2 = len(s) - 1, len(t) - 1 + sign_1, sign_2 = 0, 0 + while left_1 >= 0 or left_2 >= 0: + while left_1 >= 0: + if s[left_1] == '#': + sign_1 += 1 + left_1 -= 1 + elif sign_1 > 0: + sign_1 -= 1 + left_1 -= 1 + else: + break + + while left_2 >= 0: + if t[left_2] == '#': + sign_2 += 1 + left_2 -= 1 + elif sign_2 > 0: + sign_2 -= 1 + left_2 -= 1 + else: + break + + if left_1 < 0 and left_2 < 0: + return True + if left_1 >= 0 and left_2 < 0: + return False + if left_1 < 0 and left_2 >= 0: + return False + if s[left_1] != t[left_2]: + return False + + left_1 -= 1 + left_2 -= 1 + + return True +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 和 $m$ 分别为字符串 $s$、$t$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/binary-gap.md b/docs/solutions/0800-0899/binary-gap.md new file mode 100644 index 00000000..defbc8ba --- /dev/null +++ b/docs/solutions/0800-0899/binary-gap.md @@ -0,0 +1,72 @@ +# [0868. 二进制间距](https://leetcode.cn/problems/binary-gap/) + +- 标签:位运算 +- 难度:简单 + +## 题目链接 + +- [0868. 二进制间距 - 力扣](https://leetcode.cn/problems/binary-gap/) + +## 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:找到并返回 $n$ 的二进制表示中两个相邻 $1$ 之间的最长距离。如果不存在两个相邻的 $1$,返回 $0$。 + +**说明**: + +- $1 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 22 +输出:2 +解释:22 的二进制是 "10110"。 +在 22 的二进制表示中,有三个 1,组成两对相邻的 1。 +第一对相邻的 1 中,两个 1 之间的距离为 2。 +第二对相邻的 1 中,两个 1 之间的距离为 1。 +答案取两个距离之中最大的,也就是 2。 +``` + +- 示例 2: + +```python +输入:n = 8 +输出:0 +解释:8 的二进制是 "1000"。 +在 8 的二进制表示中没有相邻的两个 1,所以返回 0。 +``` + +## 解题思路 + +### 思路 1:遍历 + +1. 将正整数 $n$ 转为二进制字符串形式 $bin\underline{\hspace{0.5em}}n$。 +2. 使用变量 $pre$ 记录二进制字符串中上一个 $1$ 的位置,使用变量 $ans$ 存储两个相邻 $1$ 之间的最长距离。 +3. 遍历二进制字符串形式 $bin\underline{\hspace{0.5em}}n$ 的每一位,遇到 $1$ 时判断并更新两个相邻 $1$ 之间的最长距离。 +4. 遍历完返回两个相邻 $1$ 之间的最长距离,即 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def binaryGap(self, n: int) -> int: + bin_n = bin(n) + pre, ans = 2, 0 + + for i in range(2, len(bin_n)): + if bin_n[i] == '1': + ans = max(ans, i - pre) + pre = i + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/binary-tree-pruning.md b/docs/solutions/0800-0899/binary-tree-pruning.md new file mode 100644 index 00000000..745e0f1b --- /dev/null +++ b/docs/solutions/0800-0899/binary-tree-pruning.md @@ -0,0 +1,47 @@ +# [0814. 二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0814. 二叉树剪枝 - 力扣](https://leetcode.cn/problems/binary-tree-pruning/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`,树的每个节点值要么是 `0`,要么是 `1`。 + +要求:剪除该二叉树中所有节点值为 `0` 的子树。 + +- 节点 `node` 的子树为: `node` 本身,以及所有 `node` 的后代。 + +## 解题思路 + +定义辅助方法 `containsOnlyZero(root)` 递归判断以 `root` 为根的子树中是否只包含 `0`。如果子树中只包含 `0`,则返回 `True`。如果子树中含有 `1`,则返回 `False`。当 `root` 为空时,也返回 `True`。 + +然后递归遍历二叉树,判断当前节点 `root` 是否只包含 `0`。如果只包含 `0`,则将其置空,返回 `None`。否则递归遍历左右子树,并设置对应的左右指针。 + +最后返回根节点 `root`。 + +## 代码 + +```python +class Solution: + def containsOnlyZero(self, root: TreeNode): + if not root: + return True + if root.val == 1: + return False + return self.containsOnlyZero(root.left) and self.containsOnlyZero(root.right) + + def pruneTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + if not root: + return root + if self.containsOnlyZero(root): + return None + + root.left = self.pruneTree(root.left) + root.right = self.pruneTree(root.right) + return root +``` + diff --git a/docs/solutions/0800-0899/boats-to-save-people.md b/docs/solutions/0800-0899/boats-to-save-people.md new file mode 100644 index 00000000..1a40fb8e --- /dev/null +++ b/docs/solutions/0800-0899/boats-to-save-people.md @@ -0,0 +1,81 @@ +# [0881. 救生艇](https://leetcode.cn/problems/boats-to-save-people/) + +- 标签:贪心、数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [0881. 救生艇 - 力扣](https://leetcode.cn/problems/boats-to-save-people/) + +## 题目大意 + +**描述**:给定一个整数数组 `people` 代表每个人的体重,其中第 `i` 个人的体重为 `people[i]`。再给定一个整数 `limit`,代表每艘船可以承载的最大重量。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 `limit`。 + +**要求**:返回载到每一个人所需的最小船数(保证每个人都能被船载)。 + +**说明**: + +- $1 \le people.length \le 5 \times 10^4$。 +- $1 \le people[i] \le limit \le 3 \times 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:people = [1,2], limit = 3 +输出:1 +解释:1 艘船载 (1, 2) +``` + +- 示例 2: + +```python +输入:people = [3,2,2,1], limit = 3 +输出:3 +解释:3 艘船分别载 (1, 2), (2) 和 (3) +``` + +## 解题思路 + +### 思路 1:贪心算法 + 双指针 + +暴力枚举的时间复杂度为 $O(n^2)$。使用双指针可以减少循环内的时间复杂度。 + +我们可以利用贪心算法的思想,让最重的和最轻的人一起走。这样一只船就可以尽可能的带上两个人。 + +具体做法如下: + +1. 先对数组进行升序排序,使用 `ans` 记录所需最小船数。 +2. 使用两个指针 `left`、`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 +3. 判断 `people[left]` 和 `people[right]` 加一起是否超重。 + 1. 如果 `people[left] + people[right] > limit`,则让重的人上船,船数量 + 1,令 `right` 左移,继续判断。 + 2. 如果 `people[left] + people[right] <= limit`,则两个人都上船,船数量 + 1,并令 `left` 右移,`right` 左移,继续判断。 +4. 如果 `lefft == right`,则让最后一个人上船,船数量 + 1。并返回答案。 + +### 思路 1:代码 + +```python +class Solution: + def numRescueBoats(self, people: List[int], limit: int) -> int: + people.sort() + size = len(people) + left, right = 0, size - 1 + ans = 0 + while left < right: + if people[left] + people[right] > limit: + right -= 1 + else: + left += 1 + right -= 1 + ans += 1 + if left == right: + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是数组 `people` 的长度。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0800-0899/bricks-falling-when-hit.md b/docs/solutions/0800-0899/bricks-falling-when-hit.md new file mode 100644 index 00000000..8070f4b6 --- /dev/null +++ b/docs/solutions/0800-0899/bricks-falling-when-hit.md @@ -0,0 +1,183 @@ +# [0803. 打砖块](https://leetcode.cn/problems/bricks-falling-when-hit/) + +- 标签:并查集、数组、矩阵 +- 难度:困难 + +## 题目链接 + +- [0803. 打砖块 - 力扣](https://leetcode.cn/problems/bricks-falling-when-hit/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的二元网格,其中 $1$ 表示砖块,$0$ 表示空白。砖块稳定(不会掉落)的前提是: + +- 一块砖直接连接到网格的顶部。 +- 或者至少有一块相邻(4 个方向之一)砖块稳定不会掉落时。 + +再给定一个数组 $hits$,这是需要依次消除砖块的位置。每当消除 $hits[i] = (row_i, col_i)$ 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。 + +**要求**:返回一个数组 $result$,其中 $result[i]$ 表示第 $i$ 次消除操作对应掉落的砖块数目。 + +**说明**: + +- 消除可能指向是没有砖块的空白位置,如果发生这种情况,则没有砖块掉落。 +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 200$。 +- $grid[i][j]$ 为 $0$ 或 $1$。 +- $1 \le hits.length \le 4 \times 10^4$。 +- $hits[i].length == 2$。 +- $0 \le xi \le m - 1$。 +- $0 \le yi \le n - 1$。 +- 所有 $(xi, yi)$ 互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:grid = [[1,0,0,0],[1,1,1,0]], hits = [[1,0]] +输出:[2] +解释:网格开始为: +[[1,0,0,0], + [1,1,1,0]] +消除 (1,0) 处加粗的砖块,得到网格: +[[1,0,0,0] + [0,1,1,0]] +两个加粗的砖不再稳定,因为它们不再与顶部相连,也不再与另一个稳定的砖相邻,因此它们将掉落。得到网格: +[[1,0,0,0], + [0,0,0,0]] +因此,结果为 [2]。 +``` + +- 示例 2: + +```python +输入:grid = [[1,0,0,0],[1,1,0,0]], hits = [[1,1],[1,0]] +输出:[0,0] +解释:网格开始为: +[[1,0,0,0], + [1,1,0,0]] +消除 (1,1) 处加粗的砖块,得到网格: +[[1,0,0,0], + [1,0,0,0]] +剩下的砖都很稳定,所以不会掉落。网格保持不变: +[[1,0,0,0], + [1,0,0,0]] +接下来消除 (1,0) 处加粗的砖块,得到网格: +[[1,0,0,0], + [0,0,0,0]] +剩下的砖块仍然是稳定的,所以不会有砖块掉落。 +因此,结果为 [0,0]。 +``` + +## 解题思路 + +### 思路 1:并查集 + +一个很直观的想法: + +- 将所有砖块放入一个集合中。 +- 根据 $hits$ 数组的顺序,每敲掉一块砖。则将这块砖与相邻(4 个方向)的砖块断开集合。 +- 然后判断哪些砖块会掉落,从集合中删除会掉落的砖块,并统计掉落砖块的数量。 + - **掉落砖块的数目 = 击碎砖块之前与屋顶相连的砖块数目 - 击碎砖块之后与屋顶相连的砖块数目 - 1**。 + +涉及集合问题,很容易想到用并查集来做。但是并查集主要用于合并查找集合,不适合断开集合。我们可以反向思考问题: + +- 先将 $hits$ 中的所有位置上的砖块敲掉。 +- 将剩下的砖块建立并查集。 +- 逆序填回被敲掉的砖块,并与相邻(4 个方向)的砖块合并。这样问题就变为了 **补上砖块会新增多少个砖块粘到屋顶**。 + +整个算法步骤具体如下: + +1. 先将二维数组 $grid$ 复制一份到二维数组 $copy\underline{\hspace{0.5em}}gird$ 上。这是因为遍历 $hits$ 元素时需要判断原网格是空白还是被打碎的砖块。 +2. 在 $copy\underline{\hspace{0.5em}}grid$ 中将 $hits$ 中打碎的砖块赋值为 $0$。 +3. 建立并查集,将房顶上的砖块合并到一个集合中。 +4. 逆序遍历 $hits$,将 $hits$ 中的砖块补到 $copy\underline{\hspace{0.5em}}grid$ 中,并计算每一步中有多少个砖块粘到屋顶上(与屋顶砖块在一个集合中),并存入答案数组对应位置。 +5. 最后输出答案数组。 + +### 思路 1:代码 + +```python +class UnionFind: + def __init__(self, n): + self.parent = [i for i in range(n)] + self.size = [1 for _ in range(n)] + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return False + self.parent[root_x] = root_y + self.size[root_y] += self.size[root_x] + return True + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + + def get_size(self, x): + root_x = self.find(x) + return self.size[root_x] + +class Solution: + def hitBricks(self, grid: List[List[int]], hits: List[List[int]]) -> List[int]: + directions = {(0, 1), (1, 0), (-1, 0), (0, -1)} + rows, cols = len(grid), len(grid[0]) + + def is_area(x, y): + return 0 <= x < rows and 0 <= y < cols + + def get_index(x, y): + return x * cols + y + + copy_grid = [[grid[i][j] for j in range(cols)] for i in range(rows)] + + for hit in hits: + copy_grid[hit[0]][hit[1]] = 0 + + union_find = UnionFind(rows * cols + 1) + + for j in range(cols): + if copy_grid[0][j] == 1: + union_find.union(j, rows * cols) + + for i in range(1, rows): + for j in range(cols): + if copy_grid[i][j] == 1: + if copy_grid[i - 1][j] == 1: + union_find.union(get_index(i - 1, j), get_index(i, j)) + if j > 0 and copy_grid[i][j - 1] == 1: + union_find.union(get_index(i, j - 1), get_index(i, j)) + + size_hits = len(hits) + res = [0 for _ in range(size_hits)] + for i in range(size_hits - 1, -1, -1): + x, y = hits[i][0], hits[i][1] + if grid[x][y] == 0: + continue + origin = union_find.get_size(rows * cols) + if x == 0: + union_find.union(y, rows * cols) + for direction in directions: + new_x = x + direction[0] + new_y = y + direction[1] + if is_area(new_x, new_y) and copy_grid[new_x][new_y] == 1: + union_find.union(get_index(x, y), get_index(new_x, new_y)) + curr = union_find.get_size(rows * cols) + res[i] = max(0, curr - origin - 1) + copy_grid[x][y] = 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n \times \alpha(m \times n))$,其中 $\alpha$ 是反 Ackerman 函数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md b/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md new file mode 100644 index 00000000..dc5d2b0b --- /dev/null +++ b/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md @@ -0,0 +1,87 @@ +# [0889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) + +- 标签:树、数组、哈希表、分治、二叉树 +- 难度:中等 + +## 题目链接 + +- [0889. 根据前序和后序遍历构造二叉树 - 力扣](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) + +## 题目大意 + +**描述**:给定一棵无重复值二叉树的前序遍历结果 `preorder` 和后序遍历结果 `postorder`。 + +**要求**:构造出该二叉树并返回其根节点。如果存在多个答案,则可以返回其中任意一个。 + +**说明**: + +- $1 \le preorder.length \le 30$。 +- $1 \le preorder[i] \le preorder.length$。 +- `preorder` 中所有值都不同。 +- `postorder.length == preorder.length`。 +- $1 \le postorder[i] \le postorder.length$。 +- `postorder` 中所有值都不同。 +- 保证 `preorder` 和 `postorder` 是同一棵二叉树的前序遍历和后序遍历。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/07/24/lc-prepost.jpg) + +```python +输入:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1] +输出:[1,2,3,4,5,6,7] +``` + +- 示例 2: + +```python +输入: preorder = [1], postorder = [1] +输出: [1] +``` + +## 解题思路 + +### 思路 1:递归 + +如果已知二叉树的前序遍历序列和后序遍历序列,是不能唯一地确定一棵二叉树的。这是因为没有中序遍历序列无法确定左右部分,也就无法进行子序列的分割。 + +只有二叉树中每个节点度为 `2` 或者 `0` 的时候,已知前序遍历序列和后序遍历序列,才能唯一地确定一颗二叉树,如果二叉树中存在度为 `1` 的节点时是无法唯一地确定一棵二叉树的,这是因为我们无法判断该节点是左子树还是右子树。 + +而这道题说明了,如果存在多个答案,则可以返回其中任意一个。 + +我们可以默认指定前序遍历序列的第 `2` 个值为左子树的根节点,由此递归划分左右子序列。具体操作步骤如下: + +1. 从前序遍历序列中可知当前根节点的位置在 `preorder[0]`。 + +2. 前序遍历序列的第 `2` 个值为左子树的根节点,即 `preorder[1]`。通过在后序遍历中查找上一步根节点对应的位置 `postorder[k]`(该节点右侧为右子树序列),从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 + +3. 从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。 + +4. 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +### 思路 1:代码 + +```python +class Solution: + def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode: + def createTree(preorder, postorder, n): + if n == 0: + return None + node = TreeNode(preorder[0]) + if n == 1: + return node + k = 0 + while postorder[k] != preorder[1]: + k += 1 + node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1) + node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2) + return node + return createTree(preorder, postorder, len(preorder)) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n^2)$。 \ No newline at end of file diff --git a/docs/solutions/0800-0899/find-eventual-safe-states.md b/docs/solutions/0800-0899/find-eventual-safe-states.md new file mode 100644 index 00000000..9fc09562 --- /dev/null +++ b/docs/solutions/0800-0899/find-eventual-safe-states.md @@ -0,0 +1,103 @@ +# [0802. 找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [0802. 找到最终的安全状态 - 力扣](https://leetcode.cn/problems/find-eventual-safe-states/) + +## 题目大意 + +**描述**:给定一个有向图 $graph$,其中 $graph[i]$ 是与节点 $i$ 相邻的节点列表,意味着从节点 $i$ 到节点 $graph[i]$ 中的每个节点都有一条有向边。 + +**要求**:找出图中所有的安全节点,将其存入数组作为答案返回,答案数组中的元素应当按升序排列。 + +**说明**: + +- **终端节点**:如果一个节点没有连出的有向边,则它是终端节点。或者说,如果没有出边,则节点为终端节点。 +- **安全节点**:如果从该节点开始的所有可能路径都通向终端节点,则该节点为安全节点。 +- $n == graph.length$。 +- $1 \le n \le 10^4$。 +- $0 \le graph[i].length \le n$。 +- $0 \le graph[i][j] \le n - 1$。 +- $graph[i]$ 按严格递增顺序排列。 +- 图中可能包含自环。 +- 图中边的数目在范围 $[1, 4 \times 10^4]$ 内。 + +**示例**: + +- 示例 1: + +![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/03/17/picture1.png) + +```python +输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]] +输出:[2,4,5,6] +解释:示意图如上。 +节点 5 和节点 6 是终端节点,因为它们都没有出边。 +从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6。 +``` + +- 示例 2: + +```python +输入:graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]] +输出:[4] +解释: +只有节点 4 是终端节点,从节点 4 开始的所有路径都通向节点 4。 +``` + +## 解题思路 + +### 思路 1:拓扑排序 + +1. 根据题意可知,安全节点所对应的终点,一定是出度为 $0$ 的节点。而安全节点一定能在有限步内到达终点,则说明安全节点一定不在「环」内。 +2. 我们可以利用拓扑排序来判断顶点是否在环中。 +3. 为了找出安全节点,可以采取逆序建图的方式,将所有边进行反向。这样出度为 $0$ 的终点就变为了入度为 $0$ 的点。 +4. 然后通过拓扑排序不断移除入度为 $0$ 的点之后,如果不在「环」中的点,最后入度一定为 $0$,这些点也就是安全节点。而在「环」中的点,最后入度一定不为 $0$。 +5. 最后将所有安全的起始节点存入数组作为答案返回。 + +### 思路 1:代码 + +```python +class Solution: + # 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点) + def topologicalSortingKahn(self, graph: dict): + indegrees = {u: 0 for u in graph} # indegrees 用于记录所有节点入度 + for u in graph: + for v in graph[u]: + indegrees[v] += 1 # 统计所有节点入度 + + # 将入度为 0 的顶点存入集合 S 中 + S = collections.deque([u for u in indegrees if indegrees[u] == 0]) + + while S: + u = S.pop() # 从集合中选择一个没有前驱的顶点 0 + for v in graph[u]: # 遍历顶点 u 的邻接顶点 v + indegrees[v] -= 1 # 删除从顶点 u 出发的有向边 + if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0 + S.append(v) # 将其放入集合 S 中 + + res = [] + for u in indegrees: + if indegrees[u] == 0: + res.append(u) + + return res + + def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]: + graph_dict = {u: [] for u in range(len(graph))} + + for u in range(len(graph)): + for v in graph[u]: + graph_dict[v].append(u) # 逆序建图 + + return self.topologicalSortingKahn(graph_dict) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 是图中节点数目,$m$ 是图中边数目。 +- **空间复杂度**:$O(n + m)$。 + diff --git a/docs/solutions/0800-0899/flipping-an-image.md b/docs/solutions/0800-0899/flipping-an-image.md new file mode 100644 index 00000000..033d4854 --- /dev/null +++ b/docs/solutions/0800-0899/flipping-an-image.md @@ -0,0 +1,32 @@ +# [0832. 翻转图像](https://leetcode.cn/problems/flipping-an-image/) + +- 标签:数组、双指针、矩阵、模拟 +- 难度:简单 + +## 题目链接 + +- [0832. 翻转图像 - 力扣](https://leetcode.cn/problems/flipping-an-image/) + +## 题目大意 + +给定一个二进制矩阵 `A` 代表图像,先将矩阵进行水平翻转,再进行翻转(将 0 变为 1,1 变为 0)。 + +## 解题思路 + +两重 for 循环,第二层 for 循环遍历到一半即可。对于 `image[i][j]`、`image[i][n-1-j]` 先水平翻转操作,再进行翻转。 + +## 代码 + +```python +class Solution: + def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]: + n = len(image) + for i in range(n): + for j in range((n+1)//2): + image[i][j], image[i][n-1-j] = image[i][n-1-j], image[i][j] + image[i][j] = 0 if image[i][j] == 1 else 1 + if j != n-1-j: + image[i][n-1-j] = 0 if image[i][n-1-j] == 1 else 1 + return image +``` + diff --git a/docs/solutions/0800-0899/goat-latin.md b/docs/solutions/0800-0899/goat-latin.md new file mode 100644 index 00000000..ed4d44dd --- /dev/null +++ b/docs/solutions/0800-0899/goat-latin.md @@ -0,0 +1,82 @@ +# [0824. 山羊拉丁文](https://leetcode.cn/problems/goat-latin/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [0824. 山羊拉丁文 - 力扣](https://leetcode.cn/problems/goat-latin/) + +## 题目大意 + +**描述**:给定一个由若干单词组成的句子 $sentence$,单词之间由空格分隔。每个单词仅由大写和小写字母组成。 + +**要求**:将句子转换为「山羊拉丁文(Goat Latin)」,并返回将 $sentence$ 转换为山羊拉丁文后的句子。 + +**说明**: + +- 山羊拉丁文的规则如下: + - 如果单词以元音开头(`a`,`e`,`i`,`o`,`u`),在单词后添加 `"ma"`。 + - 例如,单词 `"apple"` 变为 `"applema"`。 + + - 如果单词以辅音字母开头(即,非元音字母),移除第一个字符并将它放到末尾,之后再添加 `"ma"`。 + - 例如,单词 `"goat"` 变为 `"oatgma"`。 + + - 根据单词在句子中的索引,在单词最后添加与索引相同数量的字母 `a`,索引从 $1$ 开始。 + - 例如,在第一个单词后添加 `"a"` ,在第二个单词后添加 `"aa"`,以此类推。 + +- $1 \le sentence.length \le 150$。 +- $sentence$ 由英文字母和空格组成。 +- $sentence$ 不含前导或尾随空格。 +- $sentence$ 中的所有单词由单个空格分隔。 + +**示例**: + +- 示例 1: + +```python +输入:sentence = "I speak Goat Latin" +输出:"Imaa peaksmaaa oatGmaaaa atinLmaaaaa" +``` + +- 示例 2: + +```python +输入:sentence = "The quick brown fox jumped over the lazy dog" +输出:"heTmaa uickqmaaa rownbmaaaa oxfmaaaaa umpedjmaaaaaa overmaaaaaaa hetmaaaaaaaa azylmaaaaaaaaa ogdmaaaaaaaaaa" +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 使用集合 $vowels$ 存储元音字符,然后将 $sentence$ 按照空格分隔成单词数组 $words$。 +2. 遍历单词数组 $words$,对于当前单词 $word$,根据山羊拉丁文的规则,将其转为山羊拉丁文的单词,并存入答案数组 $res$ 中。 +3. 遍历完之后将答案数组拼接为字符串并返回。 + +### 思路 1:代码 + +```python +class Solution: + def toGoatLatin(self, sentence: str) -> str: + vowels = set(['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']) + words = sentence.split(' ') + res = [] + for i in range(len(words)): + word = words[i] + ans = "" + if word[0] in vowels: + ans += word + "ma" + else: + ans += word[1:] + word[0] + "ma" + ans += 'a' * (i + 1) + res.append(ans) + + return " ".join(res) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/hand-of-straights.md b/docs/solutions/0800-0899/hand-of-straights.md new file mode 100644 index 00000000..355c94ba --- /dev/null +++ b/docs/solutions/0800-0899/hand-of-straights.md @@ -0,0 +1,60 @@ +# [0846. 一手顺子](https://leetcode.cn/problems/hand-of-straights/) + +- 标签:贪心、数组、哈希表、排序 +- 难度:中等 + +## 题目链接 + +- [0846. 一手顺子 - 力扣](https://leetcode.cn/problems/hand-of-straights/) + +## 题目大意 + +**描述**:`Alice` 手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌都是顺子(即由连续的牌构成),并且每一组的牌数都是 `groupSize`。现在给定一个整数数组 `hand`,其中 `hand[i]` 是表示第 `i` 张牌的数值,和一个整数 `groupSize`。 + +**要求**:如果 `Alice` 能将这些牌重新排列成若干组、并且每组都是 `goupSize` 张牌的顺子,则返回 `True`;否则,返回 `False`。 + +**说明**: + +- $1 \le hand.length \le 10^4$。 +- $0 \le hand[i] \le 10^9$。 +- $1 \le groupSize \le hand.length$。 + +**示例**: + +- 示例 1: + +```python +输入:hand = [1,2,3,6,2,3,4,7,8], groupSize = 3 +输出:True +解释:Alice 手中的牌可以被重新排列为 [1,2,3],[2,3,4],[6,7,8]。 +``` + +## 解题思路 + +### 思路 1:哈希表 + 排序 + +1. 使用哈希表存储每个数出现的次数。 +2. 将哈希表中每个键从小到大排序。 +3. 从哈希表中最小的数开始,以它作为当前顺子的开头,然后依次判断顺子里的数是否在哈希表中,如果在的话,则将哈希表中对应数的数量减 `1`。不在的话,说明无法满足题目要求,直接返回 `False`。 +4. 重复执行 2 ~ 3 步,直到哈希表为空。最后返回 `True`。 + +### 思路 1:哈希表 + 排序代码 + +```python +class Solution: + def isPossibleDivide(self, nums: List[int], k: int) -> bool: + hand_map = collections.defaultdict(int) + for i in range(len(nums)): + hand_map[nums[i]] += 1 + for key in sorted(hand_map.keys()): + value = hand_map[key] + if value == 0: + continue + count = 0 + for i in range(k): + hand_map[key + count] -= value + if hand_map[key + count] < 0: + return False + count += 1 + return True +``` diff --git a/docs/solutions/0800-0899/increasing-order-search-tree.md b/docs/solutions/0800-0899/increasing-order-search-tree.md new file mode 100644 index 00000000..75f44679 --- /dev/null +++ b/docs/solutions/0800-0899/increasing-order-search-tree.md @@ -0,0 +1,56 @@ +# [0897. 递增顺序搜索树](https://leetcode.cn/problems/increasing-order-search-tree/) + +- 标签:栈、树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [0897. 递增顺序搜索树 - 力扣](https://leetcode.cn/problems/increasing-order-search-tree/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root`。 + +要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 + +## 解题思路 + +可以分为两步: + +1. 中序遍历二叉搜索树,将节点先存储到列表中。 +2. 将列表中的节点构造成一棵递增顺序搜索树。 + +中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 + +构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 + +最后返回 `head.right` 即可。 + +## 代码 + +```python +class Solution: + def inOrder(self, root, res): + if not root: + return + self.inOrder(root.left, res) + res.append(root) + self.inOrder(root.right, res) + + def increasingBST(self, root: TreeNode) -> TreeNode: + res = [] + self.inOrder(root, res) + + if not res: + return + head = TreeNode(-1) + cur = head + for node in res: + node.left = node.right = None + cur.right = node + cur = cur.right + return head.right +``` + + + diff --git a/docs/solutions/0800-0899/index.md b/docs/solutions/0800-0899/index.md new file mode 100644 index 00000000..21ad6d7e --- /dev/null +++ b/docs/solutions/0800-0899/index.md @@ -0,0 +1,43 @@ +## 本章内容 + +- [0800. 相似 RGB 颜色](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/similar-rgb-color.md) +- [0801. 使序列递增的最小交换次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md) +- [0802. 找到最终的安全状态](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/find-eventual-safe-states.md) +- [0803. 打砖块](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/bricks-falling-when-hit.md) +- [0804. 唯一摩尔斯密码词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/unique-morse-code-words.md) +- [0806. 写字符串需要的行数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/number-of-lines-to-write-string.md) +- [0811. 子域名访问计数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/subdomain-visit-count.md) +- [0814. 二叉树剪枝](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/binary-tree-pruning.md) +- [0819. 最常见的单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/most-common-word.md) +- [0820. 单词的压缩编码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/short-encoding-of-words.md) +- [0821. 字符的最短距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-distance-to-a-character.md) +- [0824. 山羊拉丁文](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/goat-latin.md) +- [0830. 较大分组的位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/positions-of-large-groups.md) +- [0832. 翻转图像](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/flipping-an-image.md) +- [0834. 树中距离之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/sum-of-distances-in-tree.md) +- [0836. 矩形重叠](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/rectangle-overlap.md) +- [0841. 钥匙和房间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/keys-and-rooms.md) +- [0844. 比较含退格的字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/backspace-string-compare.md) +- [0845. 数组中的最长山脉](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/longest-mountain-in-array.md) +- [0846. 一手顺子](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/hand-of-straights.md) +- [0847. 访问所有节点的最短路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md) +- [0850. 矩形面积 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/rectangle-area-ii.md) +- [0851. 喧闹和富有](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/loud-and-rich.md) +- [0852. 山脉数组的峰顶索引](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md) +- [0860. 柠檬水找零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/lemonade-change.md) +- [0861. 翻转矩阵后的得分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/score-after-flipping-matrix.md) +- [0862. 和至少为 K 的最短子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md) +- [0867. 转置矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/transpose-matrix.md) +- [0868. 二进制间距](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/binary-gap.md) +- [0872. 叶子相似的树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/leaf-similar-trees.md) +- [0873. 最长的斐波那契子序列的长度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md) +- [0875. 爱吃香蕉的珂珂](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/koko-eating-bananas.md) +- [0876. 链表的中间结点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/middle-of-the-linked-list.md) +- [0877. 石子游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/stone-game.md) +- [0881. 救生艇](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/boats-to-save-people.md) +- [0884. 两句话中的不常见单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/uncommon-words-from-two-sentences.md) +- [0886. 可能的二分法](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/possible-bipartition.md) +- [0887. 鸡蛋掉落](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/super-egg-drop.md) +- [0889. 根据前序和后序遍历构造二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/construct-binary-tree-from-preorder-and-postorder-traversal.md) +- [0892. 三维形体的表面积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/surface-area-of-3d-shapes.md) +- [0897. 递增顺序搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/increasing-order-search-tree.md) diff --git a/docs/solutions/0800-0899/keys-and-rooms.md b/docs/solutions/0800-0899/keys-and-rooms.md new file mode 100644 index 00000000..04e1dd0c --- /dev/null +++ b/docs/solutions/0800-0899/keys-and-rooms.md @@ -0,0 +1,86 @@ +# [0841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) + +- 标签:深度优先搜索、广度优先搜索、图 +- 难度:中等 + +## 题目链接 + +- [0841. 钥匙和房间 - 力扣](https://leetcode.cn/problems/keys-and-rooms/) + +## 题目大意 + +**描述**:有 `n` 个房间,编号为 `0` ~ `n - 1`,每个房间都有若干把钥匙,每把钥匙上都有一个编号,可以开启对应房间号的门。最初,除了 `0` 号房间外其他房间的门都是锁着的。 + +现在给定一个二维数组 `rooms`,`rooms[i][j]` 表示第 `i` 个房间的第 `j` 把钥匙所能开启的房间号。 + +**要求**:判断是否能开启所有房间的门。如果能开启,则返回 `True`。否则返回 `False`。 + +**说明**: + +- $n == rooms.length$。 +- $2 \le n \le 1000$。 +- $0 \le rooms[i].length \le 1000$。 +- $1 \le sum(rooms[i].length) \le 3000$。 +- $0 \le rooms[i][j] < n$。 +- 所有 $rooms[i]$ 的值互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:rooms = [[1],[2],[3],[]] +输出:True +解释: +我们从 0 号房间开始,拿到钥匙 1。 +之后我们去 1 号房间,拿到钥匙 2。 +然后我们去 2 号房间,拿到钥匙 3。 +最后我们去了 3 号房间。 +由于我们能够进入每个房间,我们返回 true。 +``` + +- 示例 2: + +```python +输入:rooms = [[1,3],[3,0,1],[2],[0]] +输出:False +解释:我们不能进入 2 号房间。 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +当 `x` 号房间有 `y` 号房间的钥匙时,就可以认为我们可以通过 `x` 号房间去往 `y` 号房间。现在把 `n` 个房间看做是拥有 `n` 个节点的图,则上述关系可以看做是 `x` 与 `y` 点之间有一条有向边。 + +那么问题就变为了给定一张有向图,从 `0` 节点开始出发,问是否能到达所有的节点。 + +我们可以使用深度优先搜索的方式来解决这道题,具体做法如下: + +1. 使用 set 集合变量 `visited` 来统计遍历到的节点个数。 +2. 从 `0` 节点开始,使用深度优先搜索的方式遍历整个图。 +3. 将当前节点 `x` 加入到集合 `visited` 中,遍历当前节点的邻接点。 + 1. 如果邻接点不再集合 `visited` 中,则继续递归遍历。 +4. 最后深度优先搜索完毕,判断一下遍历到的节点个数是否等于图的节点个数(即集合 `visited` 中的元素个数是否等于节点个数)。 + 1. 如果等于,则返回 `True` + 2. 如果不等于,则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def canVisitAllRooms(self, rooms: List[List[int]]) -> bool: + def dfs(x): + visited.add(x) + for key in rooms[x]: + if key not in visited: + dfs(key) + visited = set() + dfs(0) + return len(visited) == len(rooms) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 是房间的数量,$m$ 是所有房间中的钥匙数量的总数。 +- **空间复杂度**:$O(n)$,递归调用的栈空间深度不超过 $n$。 \ No newline at end of file diff --git a/docs/solutions/0800-0899/koko-eating-bananas.md b/docs/solutions/0800-0899/koko-eating-bananas.md new file mode 100644 index 00000000..80191aa6 --- /dev/null +++ b/docs/solutions/0800-0899/koko-eating-bananas.md @@ -0,0 +1,80 @@ +# [0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0875. 爱吃香蕉的珂珂 - 力扣](https://leetcode.cn/problems/koko-eating-bananas/) + +## 题目大意 + +**描述**:给定一个数组 $piles$ 代表 $n$ 堆香蕉。其中 $piles[i]$ 表示第 $i$ 堆香蕉的个数。再给定一个整数 $h$ ,表示最多可以在 $h$ 小时内吃完所有香蕉。珂珂决定以速度每小时 $k$(未知)根的速度吃香蕉。每一个小时,她讲选择其中一堆香蕉,从中吃掉 $k$ 根。如果这堆香蕉少于 $k$ 根,珂珂将在这一小时吃掉这堆的所有香蕉,并且这一小时不会再吃其他堆的香蕉。 + +**要求**:返回珂珂可以在 $h$ 小时内吃掉所有香蕉的最小速度 $k$($k$ 为整数)。 + +**说明**: + +- $1 \le piles.length \le 10^4$。 +- $piles.length \le h \le 10^9$。 +- $1 \le piles[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:piles = [3,6,7,11], h = 8 +输出:4 +``` + +- 示例 2: + +```python +输入:piles = [30,11,23,4,20], h = 5 +输出:30 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +先来看 $k$ 的取值范围,因为 $k$ 是整数,且速度肯定不能为 $0$ 吧,为 $0$ 的话就永远吃不完了。所以$k$ 的最小值可以取 $1$。$k$ 的最大值根香蕉中最大堆的香蕉个数有关,因为 $1$ 个小时内只能选择一堆吃,不能再吃其他堆的香蕉,则 $k$ 的最大值取香蕉堆的最大值即可。即 $k$ 的最大值为 $max(piles)$。 + +我们的目标是求出 $h$ 小时内吃掉所有香蕉的最小速度 $k$。现在有了区间「$[1, max(piles)]$」,有了目标「最小速度 $k$」。接下来使用二分查找算法来查找「最小速度 $k$」。至于计算 $h$ 小时内能否以 $k$ 的速度吃完香蕉,我们可以再写一个方法 $canEat$ 用于判断。如果能吃完就返回 $True$,不能吃完则返回 $False$。下面说一下算法的具体步骤。 + +- 使用两个指针 $left$、$right$。令 $left$ 指向 $1$,$right$ 指向 $max(piles)$。代表待查找区间为 $[left, right]$ + +- 取两个节点中心位置 $mid$,判断是否能在 $h$ 小时内以 $k$ 的速度吃完香蕉。 + - 如果不能吃完,则将区间 $[left, mid]$ 排除掉,继续在区间 $[mid + 1, right]$ 中查找。 + - 如果能吃完,说明 $k$ 还可以继续减小,则继续在区间 $[left, mid]$ 中查找。 +- 当 $left == right$ 时跳出循环,返回 $left$。 + +### 思路 1:代码 + +```python +class Solution: + def canEat(self, piles, hour, speed): + time = 0 + for pile in piles: + time += (pile + speed - 1) // speed + return time <= hour + + def minEatingSpeed(self, piles: List[int], h: int) -> int: + left, right = 1, max(piles) + + while left < right: + mid = left + (right - left) // 2 + if not self.canEat(piles, h, mid): + left = mid + 1 + else: + right = mid + + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log max(piles))$,$n$ 表示数组 $piles$ 中的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/leaf-similar-trees.md b/docs/solutions/0800-0899/leaf-similar-trees.md new file mode 100644 index 00000000..ff0e1a72 --- /dev/null +++ b/docs/solutions/0800-0899/leaf-similar-trees.md @@ -0,0 +1,39 @@ +# [0872. 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0872. 叶子相似的树 - 力扣](https://leetcode.cn/problems/leaf-similar-trees/) + +## 题目大意 + +将一棵二叉树树上所有的叶子,按照从左到右的顺序排列起来就形成了一个「叶值序列」。如果两棵二叉树的叶值序列是相同的,我们就认为它们是叶相似的。 + +现在给定两棵二叉树的根节点 `root1`、`root2`。如果两棵二叉是叶相似的,则返回 `True`,否则返回 `False`。 + +## 解题思路 + +分别 DFS 遍历两棵树,得到对应的叶值序列,判断两个叶值序列是否相等。 + +## 代码 + +```python +class Solution: + def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool: + def dfs(node: TreeNode, res: List[int]): + if not node: + return + if not node.left and not node.right: + res.append(node.val) + dfs(node.left, res) + dfs(node.right, res) + + res1 = [] + dfs(root1, res1) + res2 = [] + dfs(root2, res2) + return res1 == res2 +``` + diff --git a/docs/solutions/0800-0899/lemonade-change.md b/docs/solutions/0800-0899/lemonade-change.md new file mode 100644 index 00000000..b6584e64 --- /dev/null +++ b/docs/solutions/0800-0899/lemonade-change.md @@ -0,0 +1,94 @@ +# [0860. 柠檬水找零](https://leetcode.cn/problems/lemonade-change/) + +- 标签:贪心、数组 +- 难度:简单 + +## 题目链接 + +- [0860. 柠檬水找零 - 力扣](https://leetcode.cn/problems/lemonade-change/) + +## 题目大意 + +**描述**:一杯柠檬水的售价是 $5$ 美元。现在有 $n$ 个顾客排队购买柠檬水,每人只能购买一杯。顾客支付的钱面额有 $5$ 美元、$10$ 美元、$20$ 美元。必须给每个顾客正确找零(就是每位顾客需要向你支付 $5$ 美元,多出的钱要找还回顾客)。 + +现在给定 $n$ 个顾客支付的钱币面额数组 `bills`。 + +**要求**:如果能给每位顾客正确找零,则返回 `True`,否则返回 `False`。 + +**说明**: + +- 一开始的时候手头没有任何零钱。 +- $1 \le bills.length \le 10^5$。 +- `bills[i]` 不是 $5$ 就是 $10$ 或是 $20$。 + +**示例**: + +- 示例 1: + +```python +输入:bills = [5,5,5,10,20] +输出:True +解释: +前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 +第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 +第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 +由于所有客户都得到了正确的找零,所以我们输出 True。 +``` + +- 示例 2: + +```python +输入:bills = [5,5,10,10,20] +输出:False +解释: +前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 +对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 +对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 +由于不是每位顾客都得到了正确的找零,所以答案是 False。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +由于顾客只能给我们 $5$、$10$、$20$ 三种面额的钞票,且一开始我们手头没有任何钞票,所以我们手中所能拥有的钞票面额只能是 $5$、$10$、$20$。因此可以采取下面的策略: + +1. 如果顾客支付 $5$ 美元,直接收下。 +2. 如果顾客支付 $10$ 美元,如果我们手头有 $5$ 美元面额的钞票,则找给顾客,否则无法正确找零,返回 `False`。 +3. 如果顾客支付 $20$ 美元,如果我们手头有 $1$ 张 $10$ 美元和 $1$ 张 $5$ 美元的钞票,或者有 $3$ 张 $5$ 美元的钞票,则可以找给顾客。如果两种组合方式同时存在,倾向于第 $1$ 种方式找零,因为使用 $5$ 美元的场景比使用 $10$ 美元的场景多,要尽可能的保留 $5$ 美元的钞票。如果这两种组合方式都不通知,则无法正确找零,返回 `False`。 + +所以,我们可以使用两个变量 `five` 和 `ten` 来维护手中 $5$ 美元、$10$ 美团的钞票数量, 然后遍历一遍根据上述条件分别判断即可。 + +### 思路 1:代码 + +```python +class Solution: + def lemonadeChange(self, bills: List[int]) -> bool: + five, ten, twenty = 0, 0, 0 + for bill in bills: + if bill == 5: + five += 1 + if bill == 10: + if five <= 0: + return False + ten += 1 + five -= 1 + if bill == 20: + if five > 0 and ten > 0: + five -= 1 + ten -= 1 + twenty += 1 + elif five >= 3: + five -= 3 + twenty += 1 + else: + return False + + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是数组 `bill` 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md b/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md new file mode 100644 index 00000000..97aef39c --- /dev/null +++ b/docs/solutions/0800-0899/length-of-longest-fibonacci-subsequence.md @@ -0,0 +1,200 @@ +# [0873. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) + +- 标签:数组、哈希表、动态规划 +- 难度:中等 + +## 题目链接 + +- [0873. 最长的斐波那契子序列的长度 - 力扣](https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/) + +## 题目大意 + +**描述**:给定一个严格递增的正整数数组 $arr$。 + +**要求**:从数组 $arr$ 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 0。 + +**说明**: + +- **斐波那契式序列**:如果序列 $X_1, X_2, ..., X_n$ 满足: + + - $n \ge 3$; + - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 + + 则称该序列为斐波那契式序列。 + +- **斐波那契式子序列**:从序列 $A$ 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:$A = [3, 4, 5, 6, 7, 8]$。则 $[3, 5, 8]$ 是 $A$ 的一个斐波那契式子序列。 + +- $3 \le arr.length \le 1000$。 + +- $1 \le arr[i] < arr[i + 1] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入: arr = [1,2,3,4,5,6,7,8] +输出: 5 +解释: 最长的斐波那契式子序列为 [1,2,3,5,8]。 +``` + +- 示例 2: + +```python +输入: arr = [1,3,7,11,12,14,18] +输出: 3 +解释: 最长的斐波那契式子序列有 [1,11,12]、[3,11,14] 以及 [7,11,18]。 +``` + +## 解题思路 + +### 思路 1: 暴力枚举(超时) + +假设 $arr[i]$、$arr[j]$、$arr[k]$ 是序列 $arr$ 中的 $3$ 个元素,且满足关系:$arr[i] + arr[j] == arr[k]$,则 $arr[i]$、$arr[j]$、$arr[k]$ 就构成了 $arr$ 的一个斐波那契式子序列。 + +通过 $arr[i]$、$arr[j]$,我们可以确定下一个斐波那契式子序列元素的值为 $arr[i] + arr[j]$。 + +因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 $arr[i]$、$arr[j]$,则可以顺着 $arr$ 序列,从第 $j + 1$ 的元素开始,查找值为 $arr[i] + arr[j]$ 的元素 。找到 $arr[i] + arr[j]$ 之后,然后再顺着查找子序列的下一个元素。 + +简单来说,就是确定了 $arr[i]$、$arr[j]$,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 $arr[i]$、$arr[j]$,统计不同的斐波那契式子序列的长度。 + +最后将这些长度进行比较,其中最长的长度就是答案。 + +### 思路 1:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + k = j + 1 + while k < size: + if arr[temp_i] + arr[temp_j] == arr[k]: + temp_ans += 1 + temp_i = temp_j + temp_j = k + k += 1 + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:哈希表 + +对于 $arr[i]$、$arr[j]$,要查找的元素 $arr[i] + arr[j]$ 是否在 $arr$ 中,我们可以预先建立一个反向的哈希表。键值对关系为 $value : idx$,这样就能在 $O(1)$ 的时间复杂度通过 $arr[i] + arr[j]$ 的值查找到对应的 $arr[k]$,而不用像原先一样线性查找 $arr[k]$ 了。 + +### 思路 2:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + idx_map = dict() + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + while arr[temp_i] + arr[temp_j] in idx_map: + temp_ans += 1 + k = idx_map[arr[temp_i] + arr[temp_j]] + temp_i = temp_j + temp_j = k + + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 3:动态规划 + 哈希表 + +###### 1. 划分阶段 + +按照斐波那契式子序列相邻两项的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。 + +###### 3. 状态转移方程 + +以 $arr[j]$、$arr[k]$ 结尾的斐波那契式子序列的最大长度 = 满足 $arr[i] + arr[j] = arr[k]$ 条件下,以 $arr[i]$、$arr[j]$ 结尾的斐波那契式子序列的最大长度加 $1$。即状态转移方程为:$dp[j][k] = max_{(A[i] + A[j] = A[k], i < j < k)}(dp[i][j] + 1)$。 + +###### 4. 初始条件 + +默认状态下,数组中任意相邻两项元素都可以作为长度为 $2$ 的斐波那契式子序列,即 $dp[i][j] = 2$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:以 $arr[i]$、$arr[j]$ 为结尾的斐波那契式子序列的最大长度。那为了计算出最大的最长递增子序列长度,则需要在进行状态转移时,求出最大值 $ans$ 即为最终结果。 + +因为题目定义中,斐波那契式中 $n \ge 3$,所以只有当 $ans \ge 3$ 时,返回 $ans$。如果 $ans < 3$,则返回 $0$。 + +> **注意**:在进行状态转移的同时,我们应和「思路 2:哈希表」一样采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 + +### 思路 3:代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + + dp = [[0 for _ in range(size)] for _ in range(size)] + ans = 0 + + # 初始化 dp + for i in range(size): + for j in range(i + 1, size): + dp[i][j] = 2 + + idx_map = {} + # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + if arr[i] + arr[j] in idx_map: + # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 + k = idx_map[arr[i] + arr[j]] + + dp[j][k] = max(dp[j][k], dp[i][j] + 1) + ans = max(ans, dp[j][k]) + + if ans >= 3: + return ans + return 0 +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组 $arr$ 的元素个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0800-0899/longest-mountain-in-array.md b/docs/solutions/0800-0899/longest-mountain-in-array.md new file mode 100644 index 00000000..72b21a62 --- /dev/null +++ b/docs/solutions/0800-0899/longest-mountain-in-array.md @@ -0,0 +1,80 @@ +# [0845. 数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) + +- 标签:数组、双指针、动态规划、枚举 +- 难度:中等 + +## 题目链接 + +- [0845. 数组中的最长山脉 - 力扣](https://leetcode.cn/problems/longest-mountain-in-array/) + +## 题目大意 + +**描述**:给定一个整数数组 $arr$。 + +**要求**:返回最长山脉子数组的长度。如果不存在山脉子数组,返回 $0$。 + +**说明**: + +- **山脉数组**:符合下列属性的数组 $arr$ 称为山脉数组。 + - $arr.length \ge 3$。 + - 存在下标 $i(0 < i < arr.length - 1)$ 满足: + - $arr[0] < arr[1] < … < arr[i]$ + - $arr[i] > arr[i + 1] > … > arr[arr.length - 1]$ + +- $1 \le arr.length \le 10^4$。 +- $0 \le arr[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [2,1,4,7,3,2,5] +输出:5 +解释:最长的山脉子数组是 [1,4,7,3,2],长度为 5。 +``` + +- 示例 2: + +```python +输入:arr = [2,2,2] +输出:0 +解释:不存在山脉子数组。 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +1. 使用变量 $ans$ 保存最长山脉长度。 +2. 遍历数组,假定当前节点为山峰。 +3. 使用双指针 $left$、$right$ 分别向左、向右查找山脉的长度。 +4. 如果当前山脉的长度比最长山脉长度更长,则更新最长山脉长度。 +5. 最后输出 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def longestMountain(self, arr: List[int]) -> int: + size = len(arr) + res = 0 + for i in range(1, size - 1): + if arr[i] > arr[i - 1] and arr[i] > arr[i + 1]: + left = i - 1 + right = i + 1 + + while left > 0 and arr[left - 1] < arr[left]: + left -= 1 + while right < size - 1 and arr[right + 1] < arr[right]: + right += 1 + if right - left + 1 > res: + res = right - left + 1 + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $arr$ 中的元素数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/loud-and-rich.md b/docs/solutions/0800-0899/loud-and-rich.md new file mode 100644 index 00000000..eddcf7fd --- /dev/null +++ b/docs/solutions/0800-0899/loud-and-rich.md @@ -0,0 +1,91 @@ +# [0851. 喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) + +- 标签:深度优先搜索、图、拓扑排序、数组 +- 难度:中等 + +## 题目链接 + +- [0851. 喧闹和富有 - 力扣](https://leetcode.cn/problems/loud-and-rich/) + +## 题目大意 + +**描述**:有一组 `n` 个人作为实验对象,从 `0` 到 `n - 1` 编号,其中每个人都有不同数目的钱,以及不同程度的安静值 `quietness`。 + +现在给定一个数组 `richer`,其中 `richer[i] = [ai, bi]` 表示第 `ai` 个人比第 `bi` 个人更有钱。另给你一个整数数组 `quiet`,其中 `quiet[i]` 是第 `i` 个人的安静值。数组 `richer` 中所给出的数据逻辑自洽(也就是说,在第 `ai` 个人比第 `bi` 个人更有钱的同时,不会出现第 `bi` 个人比第 `ai` 个人更有钱的情况 )。 + +**要求**:返回一个长度为 `n` 的整数数组 `answer` 作为答案,其中 `answer[i]` 表示在所有比第 `i` 个人更有钱或者和他一样有钱的人中,安静值最小的那个人的编号。 + +**说明**: + +- $n == quiet.length$ +- $1 \le n \le 500$。 +- $0 \le quiet[i] \le n$。 +- $quiet$ 的所有值互不相同。 +- $0 \le richer.length \le n * (n - 1) / 2$。 +- $0 \le ai, bi < n$。 +- $ai != bi$。 +- $richer$ 中的所有数对 互不相同。 +- 对 $richer$ 的观察在逻辑上是一致的。 + +**示例**: + +- 示例 1: + +```python +输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0] +输出:[5,5,2,5,4,5,6,7] + +解释: +answer[0] = 5, +person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。 +唯一较为安静(有较低的安静值 quiet[x])的人是 person 7, +但是目前还不清楚他是否比 person 0 更有钱。 +answer[7] = 7, +在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3,4,5,6 以及 7), +最安静(有较低安静值 quiet[x])的人是 person 7。 +其他的答案也可以用类似的推理来解释。 +``` + +## 解题思路 + +### 思路 1:拓扑排序 + +对于第 `i` 个人,我们要求解的是比第 `i` 个人更有钱或者和他一样有钱的人中,安静值最小的那个人的编号。 + +我们可以建立一张有向无环图,由富人指向穷人。这样,对于任意一点来说(比如 `x`),通过有向边链接的点(比如 `y`),拥有的钱都没有 `x` 多。则我们可以根据 `answer[x]` 去更新所有 `x` 能连接到的点的 `answer` 值。 + +我们可以先将数组 `answer` 元素初始化为当前元素编号。然后对建立的有向无环图进行拓扑排序,按照拓扑排序的顺序去更新 `x` 能连接到的点的 `answer` 值。 + +### 思路 1:拓扑排序代码 + +```python +import collections + +class Solution: + def loudAndRich(self, richer: List[List[int]], quiet: List[int]) -> List[int]: + + size = len(quiet) + indegrees = [0 for _ in range(size)] + edges = collections.defaultdict(list) + + for x, y in richer: + edges[x].append(y) + indegrees[y] += 1 + + res = [i for i in range(size)] + queue = collections.deque([]) + for i in range(size): + if not indegrees[i]: + queue.append(i) + + while queue: + x = queue.popleft() + size -= 1 + for y in edges[x]: + if quiet[res[x]] < quiet[res[y]]: + res[y] = res[x] + indegrees[y] -= 1 + if not indegrees[y]: + queue.append(y) + return res +``` diff --git a/docs/solutions/0800-0899/middle-of-the-linked-list.md b/docs/solutions/0800-0899/middle-of-the-linked-list.md new file mode 100644 index 00000000..3ad07f33 --- /dev/null +++ b/docs/solutions/0800-0899/middle-of-the-linked-list.md @@ -0,0 +1,94 @@ +# [0876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) + +- 标签:链表、双指针 +- 难度:简单 + +## 题目链接 + +- [0876. 链表的中间结点 - 力扣](https://leetcode.cn/problems/middle-of-the-linked-list/) + +## 题目大意 + +**描述**:给定一个单链表的头节点 `head`。 + +**要求**:返回链表的中间节点。如果有两个中间节点,则返回第二个中间节点。 + +**说明**: + +- 给定链表的结点数介于 `1` 和 `100` 之间。 + +**示例**: + +- 示例 1: + +```python +输入:[1,2,3,4,5] +输出:此列表中的结点 3 (序列化形式:[3,4,5]) +解释:返回的结点值为 3 。 +注意,我们返回了一个 ListNode 类型的对象 ans,这样: +ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. +``` + +- 示例 2: + +```python +输入:[1,2,3,4,5,6] +输出:此列表中的结点 4 (序列化形式:[4,5,6]) +解释:由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 +``` + +## 解题思路 + +### 思路 1:单指针 + +先遍历一遍链表,统计一下节点个数为 `n`,再遍历到 `n / 2` 的位置,返回中间节点。 + +### 思路 1:代码 + +```python +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + n = 0 + curr = head + while curr: + n += 1 + curr = curr.next + k = 0 + curr = head + while k < n // 2: + k += 1 + curr = curr.next + return curr +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:快慢指针 + +使用步长不一致的快慢指针进行一次遍历找到链表的中间节点。具体做法如下: + +1. 使用两个指针 `slow`、`fast`。`slow`、`fast` 都指向链表的头节点。 +2. 在循环体中将快、慢指针同时向右移动。其中慢指针每次移动 `1` 步,即 `slow = slow.next`。快指针每次移动 `2` 步,即 `fast = fast.next.next`。 +3. 等到快指针移动到链表尾部(即 `fast == Node`)时跳出循环体,此时 `slow` 指向链表中间位置。 +4. 返回 `slow` 指针。 + +### 思路 2:代码 + +```python +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + fast = head + slow = head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + return slow +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md b/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md new file mode 100644 index 00000000..4e651790 --- /dev/null +++ b/docs/solutions/0800-0899/minimum-swaps-to-make-sequences-increasing.md @@ -0,0 +1,82 @@ +# [0801. 使序列递增的最小交换次数](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [0801. 使序列递增的最小交换次数 - 力扣](https://leetcode.cn/problems/minimum-swaps-to-make-sequences-increasing/) + +## 题目大意 + +给定两个长度相等的整形数组 A 和 B。可以交换两个数组相同位置上的元素,比如 A[i] 与 B[i] 交换,可以交换多个位置,但要保证交换之后保证数组 A和数组 B 是严格递增的。 + +要求:返回使得数组 A和数组 B 保持严格递增状态的最小交换次数。假设给定的输入一定有效。 + +## 解题思路 + +可以用动态规划来做。 + +对于两个数组每一个位置上的元素 A[i] 和 B[i] 来说,只有两种情况:换或者不换。 + +动态规划的状态 `dp[i][j]` 表示为:第 i 个位置元素,不交换(j = 0)、交换(j = 1)状态时的最小交换次数。 + +如果数组元素个数只有一个,则: + +- `dp[0][0] = 0` ,第 0 个元素不做交换,交换次数为 0。 +- `dp[0][1] = 1`,第 0 个元素做交换,交换次数为 1。 + +如果有 2 个元素,为了保证两个数组中的相邻元素都为递增元素,则第 2 个元素交换与否与第 1 个元素有关。同理如果有多个元素,那么第 i 个元素交换与否,只与第 i - 1 个元素有关。现在来考虑第 i 个元素与第 i - 1 的元素的情况。 + +先按原本数组当前是否满足递增关系来划分,可以划分为: + +- 原本数组都满足递增关系,即 `A[i - 1] < A[i]` 并且 `B[i - 1] < B[i]`。 +- 不满足上述递增关系的情况,即 `A[i - 1] >= A[i]` 或者 `B[i - 1] >= B[i]`。 + +可以看出,不满足递增关系的情况下是肯定要交换的。只需要考虑交换第 i 位元素,还是第 i - 1 位元素。 + +- `dp[i][0] = dp[i - 1][1]`,第 i 位若不交换,则第 i - 1 位必须交换。 +- `dp[i][1] = dp[i - 1][0] + 1`,第 i 位交换,则第 i - 1 位不能交换。 + +下面再来考虑原本数组都满足递增关系的情况。考虑两个数组间相邻元素的关系。 + +- `A[i - 1] < B[i]` 并且 `B[i - 1] < A[i]`。 +- `A[i - 1] >= B[i]` 或者 `B[i - 1] >= A[i]`。 + +如果是 `A[i - 1] < B[i]` 并且 `B[i - 1] < A[i]` 情况下,第 i 位交换,与第 i - 1 位交换与否无关,则 `dp[i][j]` 只需取 `dp[i-1][j]` 上较小结果进行计算即可,即: + +- `dp[i][0] = min(dp[i-1][0], dp[i-1][1])` +- `dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + 1` + +如果是 `A[i - 1] >= B[i]` 或者 `B[i - 1] >= A[i]` 情况下,则如果第 i 位交换,则第 i - 1 位必须跟着交换。如果第 i 位不交换,则第 i - 1 为也不能交换,即: + +- `dp[i][0] = dp[i - 1][0]`,如果第 i 位不交换,则第 i - 1 位也不交换。 +- `dp[i][1] = dp[i - 1][1] + 1`,如果第 i 位交换,则第 i - 1 位也必须交换。 + +这样就考虑了所有的情况,最终返回最后一个元素,(交换、不交换)状态下的最小值即可。 + +## 代码 + +```python +class Solution: + def minSwap(self, nums1: List[int], nums2: List[int]) -> int: + size = len(nums1) + dp = [[0 for _ in range(size)] for _ in range(size)] + dp[0][1] = 1 + for i in range(1, size): + if nums1[i - 1] < nums1[i] and nums2[i - 1] < nums2[i]: + if nums1[i - 1] < nums2[i] and nums2[i - 1] < nums1[i]: + # 第 i 位交换,与第 i - 1 位交换与否无关 + dp[i][0] = min(dp[i-1][0], dp[i-1][1]) + dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + 1 + else: + # 如果第 i 位不交换,则第 i - 1 位也不交换 + # 如果第 i 位交换,则第 i - 1 位也必须交换 + dp[i][0] = dp[i - 1][0] + dp[i][1] = dp[i - 1][1] + 1 + else: + dp[i][0] = dp[i - 1][1] # 如果第 i 位若不交换,则第 i - 1 位必须交换 + dp[i][1] = dp[i - 1][0] + 1 # 如果第 i 位交换,则第 i - 1 位不能交换 + return min(dp[size - 1][0], dp[size - 1][1]) +``` + diff --git a/docs/solutions/0800-0899/most-common-word.md b/docs/solutions/0800-0899/most-common-word.md new file mode 100644 index 00000000..0cf6940f --- /dev/null +++ b/docs/solutions/0800-0899/most-common-word.md @@ -0,0 +1,95 @@ +# [0819. 最常见的单词](https://leetcode.cn/problems/most-common-word/) + +- 标签:哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [0819. 最常见的单词 - 力扣](https://leetcode.cn/problems/most-common-word/) + +## 题目大意 + +**描述**:给定一个字符串 $paragraph$ 表示段落,再给定搞一个禁用单词列表 $banned$。 + +**要求**:返回出现次数最多,同时不在禁用列表中的单词。 + +**说明**: + +- 题目保证至少有一个词不在禁用列表中,而且答案唯一。 +- 禁用列表 $banned$ 中的单词用小写字母表示,不含标点符号。 +- 段落 $paragraph$ 只包含字母、空格和下列标点符号`!?',;.` +- 段落中的单词不区分大小写。 +- $1 \le \text{段落长度} \le 1000$。 +- $0 \le \text{禁用单词个数} \le 100$。 +- $1 \le \text{禁用单词长度} \le 10$。 +- 答案是唯一的,且都是小写字母(即使在 $paragraph$ 里是大写的,即使是一些特定的名词,答案都是小写的)。 +- 不存在没有连字符或者带有连字符的单词。 +- 单词里只包含字母,不会出现省略号或者其他标点符号。 + +**示例**: + +- 示例 1: + +```python +输入: +paragraph = "Bob hit a ball, the hit BALL flew far after it was hit." +banned = ["hit"] +输出: "ball" +解释: +"hit" 出现了3次,但它是一个禁用的单词。 +"ball" 出现了2次 (同时没有其他单词出现2次),所以它是段落里出现次数最多的,且不在禁用列表中的单词。 +注意,所有这些单词在段落里不区分大小写,标点符号需要忽略(即使是紧挨着单词也忽略, 比如 "ball,"), +"hit"不是最终的答案,虽然它出现次数更多,但它在禁用单词列表中。 +``` + +- 示例 2: + +```python +输入: +paragraph = "a." +banned = [] +输出:"a" +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 将禁用词列表转为集合 $banned\underline{\hspace{0.5em}}set$。 +2. 遍历段落 $paragraph$,获取段落中的所有单词。 +3. 判断当前单词是否在禁用词集合中,如果不在禁用词集合中,则使用哈希表对该单词进行计数。 +4. 遍历完,找出哈希表中频率最大的单词,将该单词作为答案进行返回。 + +### 思路 1:代码 + +```python +class Solution: + def mostCommonWord(self, paragraph: str, banned: List[str]) -> str: + banned_set = set(banned) + cnts = Counter() + + word = "" + for ch in paragraph: + if ch.isalpha(): + word += ch.lower() + else: + if word and word not in banned_set: + cnts[word] += 1 + word = "" + if word and word not in banned_set: + cnts[word] += 1 + + max_cnt, ans = 0, "" + for word, cnt in cnts.items(): + if cnt > max_cnt: + max_cnt = cnt + ans = word + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 为段落 $paragraph$ 的长度,$m$ 是禁用词 $banned$ 的长度。 +- **空间复杂度**:$O(n + m)$。 + diff --git a/docs/solutions/0800-0899/number-of-lines-to-write-string.md b/docs/solutions/0800-0899/number-of-lines-to-write-string.md new file mode 100644 index 00000000..13ec0704 --- /dev/null +++ b/docs/solutions/0800-0899/number-of-lines-to-write-string.md @@ -0,0 +1,81 @@ +# [0806. 写字符串需要的行数](https://leetcode.cn/problems/number-of-lines-to-write-string/) + +- 标签:数组、字符串 +- 难度:简单 + +## 题目链接 + +- [0806. 写字符串需要的行数 - 力扣](https://leetcode.cn/problems/number-of-lines-to-write-string/) + +## 题目大意 + +**描述**:给定一个数组 $widths$,其中 $words[0]$ 代表 `'a'` 需要的单位,$words[1]$ 代表 `'b'` 需要的单位,…,$words[25]$ 代表 `'z'` 需要的单位。再给定一个字符串 $s$,现在需要将字符串 $s$ 从左到右写到每一行上,每一行的最大宽度为 $100$ 个单位,如果在写某个字符的时候使改行超过了 $100$ 个单位,那么我们应该将这个字母写到下一行。 + +**要求**:计算出能放下 $s$ 的最少行数,以及最后一行使用的宽度单位。 + +**说明**: + +- 字符串 $s$ 的长度在 $[1, 1000]$ 的范围。 +- $s$ 只包含小写字母。 +- $widths$ 是长度为 $26$ 的数组。 +- $widths[i]$ 值的范围在 $[2, 10]$。 + +**示例**: + +- 示例 1: + +```python +输入: +widths = [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10] +S = "abcdefghijklmnopqrstuvwxyz" +输出: [3, 60] +解释: +所有的字符拥有相同的占用单位10。所以书写所有的26个字母, +我们需要2个整行和占用60个单位的一行。 +``` + +- 示例 2: + +```python +输入: +widths = [4,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10] +S = "bbbcccdddaaa" +输出: [2, 4] +解释: +除去字母'a'所有的字符都是相同的单位10,并且字符串 "bbbcccdddaa" 将会覆盖 9 * 10 + 2 * 4 = 98 个单位. +最后一个字母 'a' 将会被写到第二行,因为第一行只剩下2个单位了。 +所以,这个答案是2行,第二行有4个单位宽度。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 使用变量 $line\underline{\hspace{0.5em}}cnt$ 记录行数,使用变量 $last\underline{\hspace{0.5em}}cnt$ 记录最后一行使用的单位数。 +2. 遍历字符串,如果当前最后一行使用的单位数 + 当前字符需要的单位超过了 $100$,则: + 1. 另起一行填充字符。(即行数加 $1$,最后一行使用的单位数为当前字符宽度)。 +3. 如果当前最后一行使用的单位数 + 当前字符需要的单位没有超过 $100$,则: + 1. 在当前行填充字符。(即最后一行使用的单位数累加上当前字符宽度)。 + +### 思路 1:代码 + +```python +class Solution: + def numberOfLines(self, widths: List[int], s: str) -> List[int]: + line_cnt, last_cnt = 1, 0 + for ch in s: + width = widths[ord(ch) - ord('a')] + if last_cnt + width > 100: + line_cnt += 1 + last_cnt = width + else: + last_cnt += width + + return [line_cnt, last_cnt] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md b/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md new file mode 100644 index 00000000..f14c7750 --- /dev/null +++ b/docs/solutions/0800-0899/peak-index-in-a-mountain-array.md @@ -0,0 +1,73 @@ +# [0852. 山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [0852. 山脉数组的峰顶索引 - 力扣](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) + +## 题目大意 + +**描述**:给定由整数组成的山脉数组 $arr$。 + +**要求**:返回任何满足 $arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[len(arr) - 1] $ 的下标 $i$。 + +**说明**: + +- **山脉数组**:满足以下属性的数组: + 1. $len(arr) \ge 3$; + 2. 存在 $i$($0 < i < len(arr) - 1$),使得: + 1. $arr[0] < arr[1] < ... arr[i-1] < arr[i]$; + 2. $arr[i] > arr[i+1] > ... > arr[len(arr) - 1]$。 +- $3 <= arr.length <= 105$ +- $0 <= arr[i] <= 106$ +- 题目数据保证 $arr$ 是一个山脉数组 + +**示例**: + +- 示例 1: + +```python +输入:arr = [0,1,0] +输出:1 +``` + +- 示例 2: + +```python +输入:arr = [0,2,1,0] +输出:1 +``` + +## 解题思路 + +### 思路 1:二分查找 + +1. 使用两个指针 $left$、$right$ 。$left$ 指向数组第一个元素,$right$ 指向数组最后一个元素。 +2. 取区间中间节点 $mid$,并比较 $nums[mid]$ 和 $nums[mid + 1]$ 的值大小。 + 1. 如果 $nums[mid]< nums[mid + 1]$,则右侧存在峰值,令 `left = mid + 1`。 + 2. 如果 $nums[mid] \ge nums[mid + 1]$,则左侧存在峰值,令 `right = mid`。 +3. 最后,当 $left == right$ 时,跳出循环,返回 $left$。 + +### 思路 1:代码 + +```python +class Solution: + def peakIndexInMountainArray(self, arr: List[int]) -> int: + left = 0 + right = len(arr) - 1 + while left < right: + mid = left + (right - left) // 2 + if arr[mid] < arr[mid + 1]: + left = mid + 1 + else: + right = mid + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/positions-of-large-groups.md b/docs/solutions/0800-0899/positions-of-large-groups.md new file mode 100644 index 00000000..55077faa --- /dev/null +++ b/docs/solutions/0800-0899/positions-of-large-groups.md @@ -0,0 +1,74 @@ +# [0830. 较大分组的位置](https://leetcode.cn/problems/positions-of-large-groups/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [0830. 较大分组的位置 - 力扣](https://leetcode.cn/problems/positions-of-large-groups/) + +## 题目大意 + +**描述**:给定由小写字母构成的字符串 $s$。字符串 $s$ 包含一些连续的相同字符所构成的分组。 + +**要求**:找到每一个较大分组的区间,按起始位置下标递增顺序排序后,返回结果。 + +**说明**: + +- **较大分组**:我们称所有包含大于或等于三个连续字符的分组为较大分组。 + +**示例**: + +- 示例 1: + +```python +输入:s = "abbxxxxzzy" +输出:[[3,6]] +解释:"xxxx" 是一个起始于 3 且终止于 6 的较大分组。 +``` + +- 示例 2: + +```python +输入:s = "abc" +输出:[] +解释:"a","b" 和 "c" 均不是符合要求的较大分组。 +``` + +## 解题思路 + +### 思路 1:简单模拟 + +遍历字符串 $s$,统计出所有大于等于 $3$ 个连续字符的子字符串的开始位置与结束位置。具体步骤如下: + +1. 令 $cnt = 1$,然后从下标 $1$ 位置开始遍历字符串 $s$。 + 1. 如果 $s[i - 1] == s[i]$,则令 $cnt$ 加 $1$。 + 2. 如果 $s[i - 1] \ne s[i]$,说明出现了不同字符,则判断之前连续字符个数 $cnt$ 是否大于等于 $3$。 + 3. 如果 $cnt \ge 3$,则将对应包含 $cnt$ 个连续字符的子字符串的开始位置与结束位置存入答案数组中。 + 4. 令 $cnt = 1$,重新开始记录连续字符个数。 +2. 遍历完字符串 $s$,输出答案数组。 + +### 思路 1:代码 + +```python +class Solution: + def largeGroupPositions(self, s: str) -> List[List[int]]: + res = [] + cnt = 1 + size = len(s) + for i in range(1, size): + if s[i] == s[i - 1]: + cnt += 1 + else: + if cnt >= 3: + res.append([i - cnt, i - 1]) + cnt = 1 + if cnt >= 3: + res.append([size - cnt, size - 1]) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0800-0899/possible-bipartition.md b/docs/solutions/0800-0899/possible-bipartition.md new file mode 100644 index 00000000..0eeef269 --- /dev/null +++ b/docs/solutions/0800-0899/possible-bipartition.md @@ -0,0 +1,53 @@ +# [0886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0886. 可能的二分法 - 力扣](https://leetcode.cn/problems/possible-bipartition/) + +## 题目大意 + +把 n 个人(编号为 1, 2, ... , n)分为任意大小的两组。每个人都可能不喜欢其他人,那么他们不应该属于同一组。 + +给定表示不喜欢关系的数组 `dislikes`,其中 `dislikes[i] = [a, b]` 表示 `a` 和 `b` 互相不喜欢,不允许将编号 `a` 和 `b` 的人归入同一组。 + +要求:如果可以以这种方式将所有人分为两组,则返回 `True`;如果不能则返回 `False`。 + +## 解题思路 + +先构建图,对于 `dislikes[i] = [a, b]`,在节点 `a` 和 `b` 之间建立一条无向边,然后判断该图是否为二分图。具体做法如下: + +- 找到一个没有染色的节点 `u`,将其染成红色。 +- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 +- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 +- 如果所有节点都顺利染上色,则说明该图为二分图,可以将所有人分为两组,返回 `True`。否则,如果在途中不能顺利染色,不能将所有人分为两组,则返回 `False`。 + +## 代码 + +```python +class Solution: + def dfs(self, graph, colors, i, color): + colors[i] = color + for j in graph[i]: + if colors[j] == colors[i]: + return False + if colors[j] == 0 and not self.dfs(graph, colors, j, -color): + return False + return True + + def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool: + graph = [[] for _ in range(n + 1)] + colors = [0 for _ in range(n + 1)] + + for x, y in dislikes: + graph[x].append(y) + graph[y].append(x) + + for i in range(1, n + 1): + if colors[i] == 0 and not self.dfs(graph, colors, i, 1): + return False + return True +``` + diff --git a/docs/solutions/0800-0899/rectangle-area-ii.md b/docs/solutions/0800-0899/rectangle-area-ii.md new file mode 100644 index 00000000..db5273ee --- /dev/null +++ b/docs/solutions/0800-0899/rectangle-area-ii.md @@ -0,0 +1,166 @@ +# [0850. 矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) + +- 标签:线段树、数组、有序集合、扫描线 +- 难度:困难 + +## 题目链接 + +- [0850. 矩形面积 II - 力扣](https://leetcode.cn/problems/rectangle-area-ii/) + +## 题目大意 + +**描述**:给定一个二维矩形列表 `rectangles`,其中 `rectangle[i] = [x1, y1, x2, y2]` 表示第 `i` 个矩形,`(x1, y1)` 是第 `i` 个矩形左下角的坐标,`(x2, y2)` 是第 `i` 个矩形右上角的坐标。。 + +**要求**:计算 `rectangles` 中所有矩形所覆盖的总面积,并返回总面积。 + +**说明**: + +- 任何被两个或多个矩形覆盖的区域应只计算一次 。 +- 因为答案可能太大,返回 $10^9 + 7$ 的模。 +- $1 \le rectangles.length \le 200$。 +- $rectanges[i].length = 4$。 +- $0 \le x_1, y_1, x_2, y_2 \le 10^9$。 +- 矩形叠加覆盖后的总面积不会超越 $2^63 - 1$,这意味着可以用一个 $64$ 位有符号整数来保存面积结果。 + +**示例**: + +- 示例 1: + +![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/06/06/rectangle_area_ii_pic.png) + +```python +输入:rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]] +输出:6 +解释:如图所示,三个矩形覆盖了总面积为6的区域。 +从 (1,1) 到 (2,2),绿色矩形和红色矩形重叠。 +从 (1,0) 到 (2,3),三个矩形都重叠。 +``` + +## 解题思路 + +### 思路 1:扫描线 + 动态开点线段树 + + + +### 思路 1:扫描线 + 动态开点线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, cnt=0, height=0, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.cnt = cnt # 节点值(区间值) + self.height = height # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self): + self.tree = SegTreeNode(0, int(1e9)) + + # 区间更新接口:将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + + # 以下为内部实现方法 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, node): + + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + node.cnt += val # 当前节点所在区间每个元素值改为 val + self.__pushup(node) + return + + + self.__pushdown(node) + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.height # 直接返回节点值 + + self.__pushdown(node) + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, node.mid, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(node.mid + 1, q_right, node.rightNode) + + + return res_left + res_right # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新 node 节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.cnt > 0: + node.height = node.right - node.left + 1 + else: + if node.leftNode and node.rightNode: + node.height = node.leftNode.height + node.rightNode.height + else: + node.height = 0 + + # 向下更新实现方法:更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + +class Solution: + def rectangleArea(self, rectangles) -> int: + # lines 存储每个矩阵的上下两条边 + lines = [] + + for rectangle in rectangles: + x1, y1, x2, y2 = rectangle + lines.append([x1, y1 + 1, y2, 1]) + lines.append([x2, y1 + 1, y2, -1]) + + lines.sort(key=lambda line: line[0]) + + # 建立线段树 + self.STree = SegmentTree() + + ans = 0 + mod = 10 ** 9 + 7 + prev_x = lines[0][0] + for i in range(len(lines)): + x, y1, y2, val = lines[i] + height = self.STree.query_interval(0, int(1e9)) + ans += height * (x - prev_x) + ans %= mod + self.STree.update_interval(y1, y2, val) + prev_x = x + + return ans +``` + +## 参考资料 + +- 【文章】[【hdu1542】线段树求矩形面积并 - 拦路雨偏似雪花](https://www.cnblogs.com/KonjakJuruo/p/6024266.html) diff --git a/docs/solutions/0800-0899/rectangle-overlap.md b/docs/solutions/0800-0899/rectangle-overlap.md new file mode 100644 index 00000000..073328a6 --- /dev/null +++ b/docs/solutions/0800-0899/rectangle-overlap.md @@ -0,0 +1,33 @@ +# [0836. 矩形重叠](https://leetcode.cn/problems/rectangle-overlap/) + +- 标签:几何、数学 +- 难度:简单 + +## 题目链接 + +- [0836. 矩形重叠 - 力扣](https://leetcode.cn/problems/rectangle-overlap/) + +## 题目大意 + +给定两个矩形的左下角、右上角坐标:[x1, y1, x2, y2]。[x1, y1] 表示左下角坐标,[x2, y2] 表示右上角坐标。如果两个矩形相交面积大于 0,则称两矩形重叠。 + +要求:根据给定的矩形 rec1 和 rec2 的左下角、右上角坐标,如果重叠,则返回 True,否则返回 False。 + +## 解题思路 + +如果两个矩形重叠,则两个矩形的水平边投影到 x 轴上的线段会有交集,同理竖直边投影到 y 轴上的线段也会有交集。因此我们可以把问题看做是:判断两条线段是否有交集。 + +矩形 rec1 和 rec2 水平边投影到 x 轴上的线段为 `(rec1[0], rec1[2])` 和 `(rec2[0], rec2[2])`。如果两条线段有交集,则 `min(rec1[2], rec2[2]) > max(rec1[0], rec2[0])`。 + +矩形 rec1 和 rec2 竖直边投影到 y 轴上的线段为 `(rec1[1], rec1[3])` 和 `(rec2[1], rec2[3])`。如果两条线段有交集,则 `min(rec1[3], rec2[3]) > max(rec1[1], rec2[1])`。 + +判断是否满足上述条件,若满足则说明两个矩形重叠,返回 True,若不满足则返回 False。 + +## 代码 + +```python +class Solution: + def isRectangleOverlap(self, rec1: List[int], rec2: List[int]) -> bool: + return min(rec1[2], rec2[2]) > max(rec1[0], rec2[0]) and min(rec1[3], rec2[3]) > max(rec1[1], rec2[1]) +``` + diff --git a/docs/solutions/0800-0899/score-after-flipping-matrix.md b/docs/solutions/0800-0899/score-after-flipping-matrix.md new file mode 100644 index 00000000..18f2f707 --- /dev/null +++ b/docs/solutions/0800-0899/score-after-flipping-matrix.md @@ -0,0 +1,78 @@ +# [0861. 翻转矩阵后的得分](https://leetcode.cn/problems/score-after-flipping-matrix/) + +- 标签:贪心、位运算、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [0861. 翻转矩阵后的得分 - 力扣](https://leetcode.cn/problems/score-after-flipping-matrix/) + +## 题目大意 + +**描述**:给定一个二维矩阵 `A`,其中每个元素的值为 `0` 或 `1`。 + +我们可以选择任一行或列,并转换该行或列中的每一个值:将所有 `0` 都更改为 `1`,将所有 `1` 都更改为 `0`。 + +在做出任意次数的移动后,将该矩阵的每一行都按照二进制数来解释,矩阵的得分就是这些数字的总和。 + +**要求**:返回尽可能高的分数。 + +**说明**: + +- $1 \le A.length \le 20$。 +- $1 \le A[0].length \le 20$。 +- `A[i][j]` 值为 `0` 或 `1`。 + +**示例**: + +- 示例 1: + +```python +输入:[[0,0,1,1],[1,0,1,0],[1,1,0,0]] +输出:39 +解释: +转换为 [[1,1,1,1],[1,0,0,1],[1,1,1,1]] +0b1111 + 0b1001 + 0b1111 = 15 + 9 + 15 = 39 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +对于一个二进制数来说,应该优先保证高位(靠前的列)尽可能的大,也就是保证高位尽可能值为 `1`。 + +- 我们先来看矩阵的第一列数,只要第一列的某一行为 `0`,则将这一行的值进行翻转。这样就保证了最高位一定为 `1`。 +- 接下来,我们再来关注除了第一列的其他列,这里因为有最高位限制,所以我们不能随意再将某一行的值进行翻转,只能选择某一列进行翻转。 +- 为了保证当前位上有尽可能多的 `1`。我们可以用两个变量 `one_cnt`、`zeo_cnt` 来记录当前列上 `1` 的个数和 `0` 的个数。如果 `0` 的个数多于 `1` 的个数,那么我们就将当前列进行翻转。从而保证当前位上有尽可能多的 `1`。 +- 当所有列都遍历完成后,我们会得到加和最大的情况。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def matrixScore(self, grid: List[List[int]]) -> int: + zero_cnt, one_cnt = 0, 0 + res = 0 + rows, cols = len(grid), len(grid[0]) + + for col in range(cols): + for row in range(rows): + if col == 0 and grid[row][col] == 0: + for j in range(cols): + grid[row][j] = 1 - grid[row][j] + else: + if grid[row][col] == 1: + one_cnt += 1 + else: + zero_cnt += 1 + if zero_cnt > one_cnt: + for row in range(rows): + grid[row][col] = 1 - grid[row][col] + + for row in range(rows): + if grid[row][col] == 1: + res += pow(2, cols - col - 1) + zero_cnt = 0 + one_cnt = 0 + return res +``` diff --git a/docs/solutions/0800-0899/short-encoding-of-words.md b/docs/solutions/0800-0899/short-encoding-of-words.md new file mode 100644 index 00000000..fc720c22 --- /dev/null +++ b/docs/solutions/0800-0899/short-encoding-of-words.md @@ -0,0 +1,78 @@ +# [0820. 单词的压缩编码](https://leetcode.cn/problems/short-encoding-of-words/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [0820. 单词的压缩编码 - 力扣](https://leetcode.cn/problems/short-encoding-of-words/) + +## 题目大意 + +给定一个单词数组 `words`。要求对 `words` 进行编码成一个助记字符串,用来帮助记忆。`words` 中拥有相同字符后缀的单词可以合并成一个单词,比如`time` 和 `me` 可以合并成 `time`。同时每个不能再合并的单词末尾以 `#` 为结束符,将所有合并后的单词排列起来就是一个助记字符串。 + +要求:返回对 `words` 进行编码的最小助记字符串 `s` 的长度。 + +## 解题思路 + +构建一个字典树。然后对字符串长度进行从小到大排序。 + +再依次将去重后的所有单词插入到字典树中。如果出现比当前单词更长的单词,则将短单词的结尾置为 `False`,意为替换掉短单词。 + +然后再依次在字典树中查询所有单词,「单词长度 + 1」就是当前不能在合并的单词,累加起来就是答案。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = False + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def minimumLengthEncoding(self, words: List[str]) -> int: + trie_tree = Trie() + words = list(set(words)) + words.sort(key=lambda i: len(i)) + + ans = 0 + for word in words: + trie_tree.insert(word[::-1]) + + for word in words: + if trie_tree.search(word[::-1]): + ans += len(word) + 1 + + return ans +``` + diff --git a/docs/solutions/0800-0899/shortest-distance-to-a-character.md b/docs/solutions/0800-0899/shortest-distance-to-a-character.md new file mode 100644 index 00000000..4b93d813 --- /dev/null +++ b/docs/solutions/0800-0899/shortest-distance-to-a-character.md @@ -0,0 +1,82 @@ +# [0821. 字符的最短距离](https://leetcode.cn/problems/shortest-distance-to-a-character/) + +- 标签:数组、双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0821. 字符的最短距离 - 力扣](https://leetcode.cn/problems/shortest-distance-to-a-character/) + +## 题目大意 + +**描述**:给定一个字符串 $s$ 和一个字符 $c$,并且 $c$ 是字符串 $s$ 中出现过的字符。 + +**要求**:返回一个长度与字符串 $s$ 想通的整数数组 $answer$,其中 $answer[i]$ 是字符串 $s$ 中从下标 $i$ 到离下标 $i$ 最近的字符 $c$ 的距离。 + +**说明**: + +- 两个下标 $i$ 和 $j$ 之间的 **距离** 为 $abs(i - j)$ ,其中 $abs$ 是绝对值函数。 +- $1 \le s.length \le 10^4$。 +- $s[i]$ 和 $c$ 均为小写英文字母 +- 题目数据保证 $c$ 在 $s$ 中至少出现一次。 + +**示例**: + +- 示例 1: + +```python +输入:s = "loveleetcode", c = "e" +输出:[3,2,1,0,1,0,0,1,2,2,1,0] +解释:字符 'e' 出现在下标 3、5、6 和 11 处(下标从 0 开始计数)。 +距下标 0 最近的 'e' 出现在下标 3,所以距离为 abs(0 - 3) = 3。 +距下标 1 最近的 'e' 出现在下标 3,所以距离为 abs(1 - 3) = 2。 +对于下标 4,出现在下标 3 和下标 5 处的 'e' 都离它最近,但距离是一样的 abs(4 - 3) == abs(4 - 5) = 1。 +距下标 8 最近的 'e' 出现在下标 6,所以距离为 abs(8 - 6) = 2。 +``` + +- 示例 2: + +```python +输入:s = "aaab", c = "b" +输出:[3,2,1,0] +``` + +## 解题思路 + +### 思路 1:两次遍历 + +第一次从左到右遍历,记录每个 $i$ 左边最近的 $c$ 的位置,并将其距离记录到 $answer[i]$ 中。 + +第二次从右到左遍历,记录每个 $i$ 右侧最近的 $c$ 的位置,并将其与第一次遍历左侧最近的 $c$ 的位置相比较,并将较小的距离记录到 $answer[i]$ 中。 + +### 思路 1:代码 + +```python +class Solution: + def shortestToChar(self, s: str, c: str) -> List[int]: + size = len(s) + ans = [size + 1 for _ in range(size)] + + pos = -1 + for i in range(size): + if s[i] == c: + pos = i + if pos != -1: + ans[i] = i - pos + + pos = -1 + for i in range(size - 1, -1, -1): + if s[i] == c: + pos = i + if pos != -1: + ans[i] = min(ans[i], pos - i) + + return ans + +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md b/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md new file mode 100644 index 00000000..e98c00f3 --- /dev/null +++ b/docs/solutions/0800-0899/shortest-path-visiting-all-nodes.md @@ -0,0 +1,111 @@ +# [0847. 访问所有节点的最短路径](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) + +- 标签:位运算、广度优先搜索、图、动态规划、状态压缩 +- 难度:困难 + +## 题目链接 + +- [0847. 访问所有节点的最短路径 - 力扣](https://leetcode.cn/problems/shortest-path-visiting-all-nodes/) + +## 题目大意 + +**描述**:存在一个由 $n$ 个节点组成的无向连通图,图中节点编号为 $0 \sim n - 1$。现在给定一个数组 $graph$ 表示这个图。其中,$graph[i]$ 是一个列表,由所有与节点 $i$ 直接相连的节点组成。 + +**要求**:返回能够访问所有节点的最短路径长度。可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。 + +**说明**: + +- $n == graph.length$。 +- $1 \le n \le 12$。 +- $0 \le graph[i].length < n$。 +- $graph[i]$ 不包含 $i$。 +- 如果 $graph[a]$ 包含 $b$,那么 $graph[b]$ 也包含 $a$。 +- 输入的图总是连通图。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/05/12/shortest1-graph.jpg) + +```python +输入:graph = [[1,2,3],[0],[0],[0]] +输出:4 +解释:一种可能的路径为 [1,0,2,0,3] +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/05/12/shortest2-graph.jpg) + +```python +输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]] +输出:4 +解释:一种可能的路径为 [0,1,4,2,3] +``` + +## 解题思路 + +### 思路 1:状态压缩 + 广度优先搜索 + + 题目需要求解的是「能够访问所有节点的最短路径长度」,并且每个节点都可以作为起始点。 + +如果对于一个特定的起点,我们可以将该起点放入队列中,然后对其进行广度优先搜索,并使用访问数组 $visited$ 标记访问过的节点,直到所有节点都已经访问过时,返回路径长度即为「从某点开始出发,所能够访问所有节点的最短路径长度」。 + +而本题中,每个节点都可以作为起始点,则我们可以直接将所有节点放入队列中,然后对所有节点进行广度优先搜索。 + +因为本题中节点数目 $n$ 的范围为 $[1, 12]$,所以我们可以采用「状态压缩」的方式,标记节点的访问情况。每个点的初始状态可以表示为 `(u, 1 << u)`。当状态 $state == 1 \text{ <}\text{< } n - 1$ 时,表示所有节点都已经访问过了,此时返回其对应路径长度即为「能够访问所有节点的最短路径长度」。 + +为了方便在广度优先搜索的同事,记录当前的「路径长度」以及「节点的访问情况」。我们可以使用一个三元组 $(u, state, dist)$ 来表示当前节点情况,其中: + +- $u$:表示当前节点编号。 +- $state$:一个 $n$ 位的二进制数,表示 $n$ 个节点的访问情况。$state$ 第 $i$ 位为 $0$ 时表示未访问过,$state$ 第 $i$ 位为 $1$ 时表示访问过。 +- $dist$ 表示当前的「路径长度」。 + +同时为了避免重复搜索同一个节点 $u$ 以及相同节点的访问情况,我们可以使用集合记录 $(u, state)$ 是否已经被搜索过。 + +整个算法步骤如下: + +1. 将所有节点的 `(节点编号, 起始状态, 路径长度)` 作为三元组存入队列,并使用集合 $visited$ 记录所有节点的访问情况。 +2. 对所有点开始进行广度优先搜索: + 1. 从队列中弹出队头节点。 + 2. 判断节点的当前状态,如果所有节点都已经访问过,则返回答案。 + 3. 如果没有全访问过,则遍历当前节点的邻接节点。 + 4. 将邻接节点的访问状态标记为访问过。 + 5. 如果节点即当前路径没有访问过,则加入队列继续遍历,并标记为访问过。 +3. 重复进行第 $2$ 步,直到队列为空。 + +### 思路 1:代码 + +```python +import collections + + +class Solution: + def shortestPathLength(self, graph: List[List[int]]) -> int: + size = len(graph) + + queue = collections.deque([]) + visited = set() + for u in range(size): + queue.append((u, 1 << u, 0)) # 将 (节点编号, 起始状态, 路径长度) 存入队列 + visited.add((u, 1 << u)) # 标记所有节点的节点编号,以及当前状态 + + while queue: # 对所有点开始进行广度优先搜索 + u, state, dist = queue.popleft() # 弹出队头节点 + if state == (1 << size) - 1: # 所有节点都访问完,返回答案 + return dist + for v in graph[u]: # 遍历邻接节点 + next_state = state | (1 << v) # 标记邻接节点的访问状态 + if (v, next_state) not in visited: # 如果节点即当前路径没有访问过,则加入队列继续遍历,并标记为访问过 + queue.append((v, next_state, dist + 1)) + visited.add((v, next_state)) + + return 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times 2^n)$,其中 $n$ 为图的节点数量。 +- **空间复杂度**:$O(n \times 2^n)$。 + diff --git a/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md b/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md new file mode 100644 index 00000000..300f2347 --- /dev/null +++ b/docs/solutions/0800-0899/shortest-subarray-with-sum-at-least-k.md @@ -0,0 +1,107 @@ +# [0862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) + +- 标签:队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [0862. 和至少为 K 的最短子数组 - 力扣](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 + +**要求**:找出 $nums$ 中和至少为 $k$ 的最短非空子数组,并返回该子数组的长度。如果不存在这样的子数组,返回 $-1$。 + +**说明**: + +- **子数组**:数组中连续的一部分。 +- $1 \le nums.length \le 10^5$。 +- $-10^5 \le nums[i] \le 10^5$。 +- $1 \le k \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1], k = 1 +输出:1 +``` + +- 示例 2: + +```python +输入:nums = [1,2], k = 4 +输出:-1 +``` + +## 解题思路 + +### 思路 1:前缀和 + 单调队列 + +题目要求得到满足和至少为 $k$ 的子数组的最短长度。 + +先来考虑暴力做法。如果使用两重循环分别遍历子数组的开始和结束位置,则可以直接求出所有满足条件的子数组,以及对应长度。但是这种做法的时间复杂度为 $O(n^2)$。我们需要对其进行优化。 + +#### 1. 前缀和优化 + +首先对于子数组和,我们可以使用「前缀和」的方式,方便快速的得到某个子数组的和。 + +对于区间 $[left, right]$,通过 $pre\underline{\hspace{0.5em}}sum[right + 1] - prefix\underline{\hspace{0.5em}}cnts[left]$ 即可快速求解出区间 $[left, right]$ 的子数组和。 + +此时问题就转变为:是否能找到满足 $i > j$ 且 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j] \ge k$ 两个条件的子数组 $[j, i)$?如果能找到,则找出 $i - j$ 差值最小的作为答案。 + +#### 2. 单调队列优化 + +对于区间 $[j, i)$ 来说,我们应该尽可能的减少不成立的区间枚举。 + +1. 对于某个区间 $[j, i)$ 来说,如果 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j] \ge k$,那么大于 $i$ 的索引值就不用再进行枚举了,不可能比 $i - j$ 的差值更优了。此时我们应该尽可能的向右移动 $j$,从而使得 $i - j$ 更小。 +2. 对于某个区间 $[j, i)$ 来说,如果 $pre\underline{\hspace{0.5em}}sum[j] \ge pre\underline{\hspace{0.5em}}sum[i]$,对于任何大于等于 $i$ 的索引值 $r$ 来说,$pre\underline{\hspace{0.5em}}sum[r] - pre\underline{\hspace{0.5em}}sum[i]$ 一定比 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j]$ 更小且长度更小,此时 $pre\underline{\hspace{0.5em}}sum[j]$ 可以直接忽略掉。 + +因此,我们可以使用单调队列来维护单调递增的前缀数组 $pre\underline{\hspace{0.5em}}sum$。其中存放了下标 $x:x_0, x_1, …$,满足 $pre\underline{\hspace{0.5em}}sum[x_0] < pre\underline{\hspace{0.5em}}sum[x_1] < …$ 单调递增。 + +1. 使用一重循环遍历位置 $i$,将当前位置 $i$ 存入倒掉队列中。 +2. 对于每一个位置 $i$,如果单调队列不为空,则可以判断其之前存入在单调队列中的 $pre\underline{\hspace{0.5em}}sum[j]$ 值,如果 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j] \ge k$,则更新答案,并将 $j$ 从队头位置弹出。直到不再满足 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j] \ge k$ 时为止(即 $pre\underline{\hspace{0.5em}}sum[i] - pre\underline{\hspace{0.5em}}sum[j] < k$)。 +3. 如果队尾 $pre\underline{\hspace{0.5em}}sum[j] \ge pre\underline{\hspace{0.5em}}sum[i]$,那么说明以后无论如何都不会再考虑 $pre\underline{\hspace{0.5em}}sum[j]$ 了,则将其从队尾弹出。 +4. 最后遍历完返回答案。 + +### 思路 1:代码 + +```Python +class Solution: + def shortestSubarray(self, nums: List[int], k: int) -> int: + size = len(nums) + + # 优化 1 + pre_sum = [0 for _ in range(size + 1)] + for i in range(size): + pre_sum[i + 1] = pre_sum[i] + nums[i] + + ans = float('inf') + queue = collections.deque() + + for i in range(size + 1): + # 优化 2 + while queue and pre_sum[i] - pre_sum[queue[0]] >= k: + ans = min(ans, i - queue.popleft()) + while queue and pre_sum[queue[-1]] >= pre_sum[i]: + queue.pop() + queue.append(i) + + if ans == float('inf'): + return -1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。 + +## 参考资料 + +- 【题解】[862. 和至少为 K 的最短子数组 - 力扣](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/solutions/1925036/liang-zhang-tu-miao-dong-dan-diao-dui-li-9fvh/) +- 【题解】[Leetcode 862:和至少为 K 的最短子数组 - 掘金](https://juejin.cn/post/7076316608460750856) +- 【题解】[LeetCode 862. 和至少为 K 的最短子数组 - AcWing](https://www.acwing.com/solution/leetcode/content/612/) +- 【题解】[0862. Shortest Subarray With Sum at Least K | LeetCode Cookbook](https://books.halfrost.com/leetcode/ChapterFour/0800~0899/0862.Shortest-Subarray-with-Sum-at-Least-K/) diff --git a/docs/solutions/0800-0899/similar-rgb-color.md b/docs/solutions/0800-0899/similar-rgb-color.md new file mode 100644 index 00000000..5dac196a --- /dev/null +++ b/docs/solutions/0800-0899/similar-rgb-color.md @@ -0,0 +1,66 @@ +# [0800. 相似 RGB 颜色](https://leetcode.cn/problems/similar-rgb-color/) + +- 标签:数学、字符串、枚举 +- 难度:简单 + +## 题目链接 + +- [0800. 相似 RGB 颜色 - 力扣](https://leetcode.cn/problems/similar-rgb-color/) + +## 题目大意 + +**描述**:RGB 颜色 `"#AABBCC"` 可以简写成 `"#ABC"` 。例如,`"#1155cc"` 可以简写为 `"#15c"`。现在给定一个按 `"#ABCDEF"` 形式定义的字符串 `color` 表示 RGB 颜色。 + +**要求**:返回一个与 `color` 相似度最大并且可以简写的颜色。 + +**说明**: + +- 两个颜色 `"#ABCDEF"` 和 `"#UVWXYZ"` 的相似度计算公式为:$-(AB - UV)^2 - (CD - WX)^2 - (EF - YZ)^2$。 + +**示例**: + +- 示例 1: + +```python +输入 color = "#09f166" +输出 "#11ee66" +解释: 因为相似度计算得出 -(0x09 - 0x11)^2 -(0xf1 - 0xee)^2 - (0x66 - 0x66)^2 = -64 -9 -0 = -73,这是所有可以简写的颜色中与 color 最相似的颜色 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +所有可以简写的颜色范围是 `"#000"` ~ `"#fff"`,共 $16^3 = 4096$ 种颜色。因此,我们可以枚举这些可以简写的颜色,并计算出其与 $color$的相似度,从而找出与 $color$ 最相似的颜色。具体做法如下: + +- 将 $color$ 转换为十六进制数,即 `hex_color = int(color[1:], 16)`。 +- 三重循环遍历 $R$、$G$、$B$ 三个通道颜色,每一重循环范围为 $0 \sim 15$。 +- 计算出每一种可以简写的颜色对应的十六进制,即 $17 \times R \times (1 << 16) + 17 \times G \times (1 << 8) + 17 \times B$,$17$ 是 $0x11 = 16 + 1 = 17$,$(1 << 16)$ 为 $R$ 左移的位数,$17 \times R \times (1 << 16)$ 就表示 $R$ 通道上对应的十六进制数。$(1 << 8)$ 为 $G$ 左移的位数,$17 \times G \times (1 << 8)$ 就表示 $G$ 通道上对应的十六进制数。$17 \times B$ 就表示 $B$ 通道上对应的十六进制数。 +- 然后我们根据 $color$ 的十六进制数,与每一个可以简写的颜色对应的十六进制数,计算出相似度,并找出大相似对应的颜色。将其转换为字符串,并输出。 + +### 思路 1:枚举算法代码 + +```python +class Solution: + def similar(self, hex1, hex2): + r1, g1, b1 = hex1 >> 16, (hex1 >> 8) % 256, hex1 % 256 + r2, g2, b2 = hex2 >> 16, (hex2 >> 8) % 256, hex2 % 256 + return - (r1 - r2) ** 2 - (g1 - g2) ** 2 - (b1 - b2) ** 2 + + def similarRGB(self, color: str) -> str: + ans = 0 + hex_color = int(color[1:], 16) + for r in range(16): + for g in range(16): + for b in range(16): + hex_cur = 17 * r * (1 << 16) + 17 * g * (1 << 8) + 17 * b + if self.similar(hex_color, hex_cur) > self.similar(hex_color, ans): + ans = hex_cur + + return "#{:06x}".format(ans) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(16^3)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/0800-0899/stone-game.md b/docs/solutions/0800-0899/stone-game.md new file mode 100644 index 00000000..eccb3b22 --- /dev/null +++ b/docs/solutions/0800-0899/stone-game.md @@ -0,0 +1,34 @@ +# [0877. 石子游戏](https://leetcode.cn/problems/stone-game/) + +- 标签:数组、数学、动态规划、博弈 +- 难度:中等 + +## 题目链接 + +- [0877. 石子游戏 - 力扣](https://leetcode.cn/problems/stone-game/) + +## 题目大意 + +亚历克斯和李在玩石子游戏。总共有偶数堆石子,每堆都有正整数颗石子 `piles[i]`,总共的石子数为奇数 。每回合,玩家从开始位置或者结束位置取走一整堆石子。直到没有石子堆为止结束游戏,最终手中石子颗数多的玩家获胜。假设亚历克斯和李每回合都能发挥出最佳水平,并且亚历克斯先开始。 + +给定代表每个位置石子颗数的数组 `piles`。 + +要求:判断亚历克斯是否能赢得比赛。如果亚历克斯赢得比赛,则返回 `True`。如果李赢得比赛返回 `False`。 + +## 解题思路 + +能取的次数是偶数个,总数是奇数个。 + +- 如果亚历克斯开始取了开始偶数位置 `0`,那么李只能取奇数位置 `1` 或者末尾位置 `len(piles) - 1`。然后亚历克斯可以j接着取偶数位。 +- 或者亚历克斯开始取了最后奇数位置 `len(piles) - 1`,那么李只能取偶数位置 `0` 或 `len(piles) - 2`。然后亚历克斯可以接着取奇数位。 +- 这样亚历克斯只要一开始计算好奇数位置上的石子总数多,还是偶数位置上的石子总数多,然后就可以选择一开始取奇数位置还是偶数位置。所以最后肯定会赢 +- 游戏一开始,其实就没李啥事了。。。 + +## 代码 + +```python +class Solution: + def stoneGame(self, piles: List[int]) -> bool: + return True +``` + diff --git a/docs/solutions/0800-0899/subdomain-visit-count.md b/docs/solutions/0800-0899/subdomain-visit-count.md new file mode 100644 index 00000000..bae9c3be --- /dev/null +++ b/docs/solutions/0800-0899/subdomain-visit-count.md @@ -0,0 +1,66 @@ +# [0811. 子域名访问计数](https://leetcode.cn/problems/subdomain-visit-count/) + +- 标签:数组、哈希表、字符串、计数 +- 难度:中等 + +## 题目链接 + +- [0811. 子域名访问计数 - 力扣](https://leetcode.cn/problems/subdomain-visit-count/) + +## 题目大意 + +**描述**:网站域名是由多个子域名构成的。 + +- 例如 `"discuss.leetcode.com"` 的顶级域名为 `"com"`,二级域名为 `"leetcode.com"`,三级域名为 `"discuss.leetcode.com"`。 + +当访问 `"discuss.leetcode.com"` 时,也会隐式访问其父域名 `"leetcode.com"` 以及 `"com"`。 + +计算机配对域名的格式为 `"rep d1.d2.d3"` 或 `"rep d1.d2"`。其中 `rep` 表示访问域名的次数,`d1.d2.d3` 或 `d1.d2` 为域名本身。 + +- 例如:`"9001 discuss.leetcode.com"` 就是一个 计数配对域名 ,表示 `discuss.leetcode.com` 被访问了 `9001` 次。 + +现在给定一个由计算机配对域名组成的数组 `cpdomains`。 + +**要求**:解析每一个计算机配对域名,计算出所有域名的访问次数,并以数组形式返回。可以按任意顺序返回答案。 + +## 解题思路 + +这道题求解的是不同层级的域名的次数汇总,很容易想到使用哈希表。我们可以使用哈希表来统计不同层级的域名访问次数。具体做如下: + +1. 如果数组 `cpdomains` 为空,直接返回空数组。 +2. 使用哈希表 `times_dict` 存储不同层级的域名访问次数。 +3. 遍历数组 `cpdomains`。对于每一个计算机配对域名 `cpdomain`: + 1. 先将计算机配对域名的访问次数 `times` 和域名 `domain` 进行分割。 + 2. 然后将域名转为子域名数组 `domain_list`,逆序拼接不同等级的子域名 `sub_domain`。 + 3. 如果子域名 `sub_domain` 没有出现在哈希表 `times_dict` 中,则在哈希表中存入 `sub_domain` 和访问次数 `times` 的键值对。 + 4. 如果子域名 `sub_domain` 曾经出现在哈希表 `times_dict` 中,则在哈希表对应位置加上 `times`。 +4. 遍历完之后,遍历哈希表 `times_dict`,将所有域名和访问次数拼接为字符串,存入答案数组中。 +5. 最后返回答案数组。 + +## 代码 + +```python +class Solution: + def subdomainVisits(self, cpdomains: List[str]) -> List[str]: + if not cpdomains: + return [] + + times_dict = dict() + for cpdomain in cpdomains: + tiems, domain = cpdomain.split() + tiems = int(tiems) + + domain_list = domain.split('.') + for i in range(len(domain_list) - 1, -1, -1): + sub_domain = '.'.join(domain_list[i:]) + if sub_domain not in times_dict: + times_dict[sub_domain] = tiems + else: + times_dict[sub_domain] += tiems + + res = [] + for key in times_dict.keys(): + res.append(str(times_dict[key]) + ' ' + key) + return res +``` + diff --git a/docs/solutions/0800-0899/sum-of-distances-in-tree.md b/docs/solutions/0800-0899/sum-of-distances-in-tree.md new file mode 100644 index 00000000..5e58e00a --- /dev/null +++ b/docs/solutions/0800-0899/sum-of-distances-in-tree.md @@ -0,0 +1,112 @@ +# [0834. 树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) + +- 标签:树、深度优先搜索、图、动态规划 +- 难度:困难 + +## 题目链接 + +- [0834. 树中距离之和 - 力扣](https://leetcode.cn/problems/sum-of-distances-in-tree/) + +## 题目大意 + +**描述**:给定一个无向、连通的树。树中有 $n$ 个标记为 $0 \sim n - 1$ 的节点以及 $n - 1$ 条边 。 + +给定整数 $n$ 和数组 $edges$,其中 $edges[i] = [ai, bi]$ 表示树中的节点 $ai$ 和 $bi$ 之间有一条边。 + +**要求**:返回长度为 $n$ 的数组 $answer$,其中 $answer[i]$ 是树中第 $i$ 个节点与所有其他节点之间的距离之和。 + +**说明**: + +- $1 \le n \le 3 \times 10^4$。 +- $edges.length == n - 1$。 +- $edges[i].length == 2$。 +- $0 \le ai, bi < n$。 +- $ai \ne bi$。 +- 给定的输入保证为有效的树。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/07/23/lc-sumdist1.jpg) + +```python +输入: n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]] +输出: [8,12,6,10,10,10] +解释: 树如图所示。 +我们可以计算出 dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5) +也就是 1 + 1 + 2 + 2 + 2 = 8。 因此,answer[0] = 8,以此类推。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/07/23/lc-sumdist3.jpg) + +```python +输入: n = 2, edges = [[1,0]] +输出: [1,1] +``` + +## 解题思路 + +### 思路 1:树形 DP + 二次遍历换根法 + +最容易想到的做法是:枚举 $n$ 个节点,以每个节点为根节点进行树形 DP。 + +对于节点 $u$,定义 $dp[u]$ 为:以节点 $u$ 为根节点的树,它的所有子节点到它的距离之和。 + +然后进行一轮深度优先搜索,在搜索的过程中得到以节点 $v$ 为根节点的树,节点 $v$ 与所有其他子节点之间的距离之和 $dp[v]$。还能得到子树的节点个数 $sizes[v]$。 + +对于节点 $v$ 来说,其对 $dp[u]$ 的贡献为:节点 $v$ 与所有其他子节点之间的距离之和,再加上需要经过 $u \rightarrow v$ 这条边的节点个数,即 $dp[v] + sizes[v]$。 + +可得到状态转移方程为:$dp[u] = \sum_{v \in graph[u]}(dp[v] + sizes[v])$。 + +这样,对于 $n$ 个节点来说,需要进行 $n$ 次树形 DP,这种做法的时间复杂度为 $O(n^2)$,而 $n$ 的范围为 $[1, 3 \times 10^4]$,这样做会导致超时,因此需要进行优化。 + +我们可以使用「二次遍历换根法」进行优化,从而在 $O(n)$ 的时间复杂度内解决这道题。 + +以编号为 $0$ 的节点为根节点,进行两次深度优先搜索。 + +1. 第一次遍历:从编号为 $0$ 的根节点开始,自底向上地计算出节点 $0$ 到其他的距离之和,记录在 $ans[0]$ 中。并且统计出以子节点为根节点的子树节点个数 $sizes[v]$。 +2. 第二次遍历:从编号为 $0$ 的根节点开始,自顶向下地枚举每个点,计算出将每个点作为新的根节点时,其他节点到根节点的距离之和。如果当前节点为 $v$,其父节点为 $u$,则自顶向下计算出 $ans[u]$ 之后,我们将根节点从 $u$ 换为节点 $v$,子树上的点到新根节点的距离比原来都小了 $1$,非子树上剩下所有点到新根节点的距离比原来都大了 $1$。则可以据此计算出节点 $v$ 与其他节点的距离和为:$ans[v] = ans[u] + n - 2 \times sizes[u]$。 + +### 思路 1:代码 + +```python +class Solution: + def sumOfDistancesInTree(self, n: int, edges: List[List[int]]) -> List[int]: + graph = [[] for _ in range(n)] + + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + + ans = [0 for _ in range(n)] + + sizes = [1 for _ in range(n)] + def dfs(u, fa, depth): + ans[0] += depth + for v in graph[u]: + if v == fa: + continue + dfs(v, u, depth + 1) + sizes[u] += sizes[v] + + def reroot(u, fa): + for v in graph[u]: + if v == fa: + continue + ans[v] = ans[u] + n - 2 * size[v] + reroot(v, u) + + dfs(0, -1, 0) + reroot(0, -1) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树的节点个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0800-0899/super-egg-drop.md b/docs/solutions/0800-0899/super-egg-drop.md new file mode 100644 index 00000000..e1145faa --- /dev/null +++ b/docs/solutions/0800-0899/super-egg-drop.md @@ -0,0 +1,266 @@ +# [0887. 鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/) + +- 标签:数学、二分查找、动态规划 +- 难度:困难 + +## 题目链接 + +- [0887. 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/) + +## 题目大意 + +**描述**:给定一个整数 `k` 和整数 `n`,分别代表 `k` 枚鸡蛋和可以使用的一栋从第 `1` 层到第 `n` 层楼的建筑。 + +已知存在楼层 `f`,满足 `0 <= f <= n`,任何从高于 `f` 的楼层落下的鸡蛋都会碎,从 `f` 楼层或比它低的楼层落下的鸡蛋都不会碎。 + +每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 `x` 扔下(满足 `1 <= x <= n`),如果鸡蛋碎了,就不能再次使用它。如果鸡蛋没碎,则可以再次使用。 + +**要求**:计算并返回要确定 `f` 确切值的最小操作次数是多少。 + +**说明**: + +- $1 \le k \le 100$。 +- $1 \le n \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:k = 1, n = 2 +输入:2 +解释:鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0。否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1。如果它没碎,那么肯定能得出 f = 2。因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 +``` + +## 解题思路 + +这道题目的题意不是很容易理解,我们先把题目简化一下,忽略一些限制条件,理解简单情况下的题意。然后再一步步增加限制条件,从而弄明白这道题目的意思,以及思考清楚这道题的解题思路。 + +我们先忽略 `k` 个鸡蛋这个条件,假设有无限个鸡蛋。 + +现在有 `1` ~ `n` 一共 `n` 层楼。已知存在楼层 `f`,低于等于 `f` 层的楼层扔下去的鸡蛋都不会碎,高于 `f` 的楼层扔下去的鸡蛋都会碎。 + +当然这个楼层 `f` 的确切值题目没有给出,需要我们一次次去测试鸡蛋最高会在哪一层不会摔碎。 + +在每次操作中,我们可以选定一个楼层,将鸡蛋扔下去: + +- 如果鸡蛋没摔碎,则可以继续选择其他楼层进行测试。 +- 如果鸡蛋摔碎了,则该鸡蛋无法继续测试。 + +现在题目要求:**已知有 `n` 层楼,无限个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来?** + +最简单且直观的想法: + +1. 从第 `1` 楼开始扔鸡蛋。`1` 楼不碎,再去 `2` 楼扔。 +2. `2` 楼还不碎,就去 `3` 楼扔。 +3. …… +4. 直到鸡蛋碎了,也就找到了鸡蛋不会摔碎的最高层 `f`。 + +用这种方法,最坏情况下,鸡蛋在第 `n` 层也没摔碎。这种情况下我们总共试了 `n` 次才确定鸡蛋不会摔碎的最高楼层 `f`。 + +下面再来说一下比 `n` 次要少的情况。 + +如果我们可以通过二分查找的方法,先从 `1` ~ `n` 层的中间层开始扔鸡蛋。 + +- 如果鸡蛋碎了,则从第 `1` 层到中间层这个区间中去扔鸡蛋。 +- 如果鸡蛋没碎,则从中间层到第 `n` 层这个区间中去扔鸡蛋。 + +每次扔鸡蛋都从区间的中间层去扔,这样每次都能排除当前区间一半的答案,从而最终确定鸡蛋不会摔碎的最高楼层 `f`。 + +通过这种二分查找的方法,可以优化到 $\log n$ 次就能确定鸡蛋不会摔碎的最高楼层 `f`。 + +因为 $\log n \le n$,所以通过二分查找的方式,「至少」比线性查找的次数要少。 + +同样,我们还可以通过三分查找、五分查找等等方式减少次数。 + +这是在不限制鸡蛋个数的情况下,现在我们来限制一下鸡蛋个数为 `k`。 + +现在题目要求:**已知有 `n` 层楼,`k` 个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来?** + +如果鸡蛋足够多(大于等于 $\log_2 n$ 个),可以通过二分查找的方法来测试。如果鸡蛋不够多,可能二分查找过程中,鸡蛋就用没了,则不能通过二分查找的方法来测试。 + +那么这时候为了找出 `f` ,我们应该如何求出最少的扔鸡蛋次数? + +### 思路 1:动态规划(超时) + +可以这样考虑。题目限定了 `n` 层楼,`k` 个鸡蛋。 + +如果我们尝试在 `1` ~ `n` 层中的任意一层 `x` 扔鸡蛋: + +1. 如果鸡蛋没碎,则说明 `1` ~ `x` 层都不用再考虑了,我们需要用 `k` 个鸡蛋去考虑剩下的 `n - x` 层,问题就从 `(n, k)` 转变为了 `(n - x, k)`。 +2. 如果鸡蛋碎了,则说明 `x + 1` ~ `n` 层都不用再考虑了,我们需要去剩下的 `k - 1` 个鸡蛋考虑剩下的 `x - 1` 层,问题就从 `(n, k)` 转变为了 `(x - 1, k - 1)`。 + +这样一来,我们就可以根据上述关系使用动态规划方法来解决这道题目了。具体步骤如下: + +###### 1. 划分阶段 + +按照楼层数量、剩余鸡蛋个数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:一共有 `i` 层楼,`j` 个鸡蛋的条件下,为了找出 `f` ,最坏情况下的最少扔鸡蛋次数。 + +###### 3. 状态转移方程 + +根据之前的描述,`dp[i][j]` 有两个来源,其状态转移方程为: + +$dp[i][j] = min_{1 \le x \le n} (max(dp[i - x][j], dp[x - 1][j - 1])) + 1$ + +###### 4. 初始条件 + +给定鸡蛋 `k` 的取值范围为 `[1, 100]`,`f` 值取值范围为 `[0, n]`,初始化时,可以考虑将所有值设置为当前拥有的楼层数。 + +- 当鸡蛋数为 `1` 时,`dp[i][1] = i`。这是如果唯一的蛋碎了,则无法测试了。只能从低到高,一步步进行测试,最终最少测试数为当前拥有的楼层数(如果刚开始初始化时已经将所有值设置为当前拥有的楼层数,其实这一步可省略)。 +- 当楼层为 `1` 时,在 `1` 层扔鸡蛋,`dp[1][j] = 1`。这是因为: + - 如果在 `1` 层扔鸡蛋碎了,则 `f < 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 + - 如果在 `1` 层扔鸡蛋没碎,则 `f >= 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j]` 表示为:一共有 `i` 层楼,`j` 个鸡蛋的条件下,为了找出 `f` ,最坏情况下的最少扔鸡蛋次数。则最终结果为 `dp[n][k]`。 + +### 思路 1:代码 + +```python +class Solution: + def superEggDrop(self, k: int, n: int) -> int: + dp = [[0 for _ in range(k + 1)] for i in range(n + 1)] + + for i in range(1, n + 1): + for j in range(1, k + 1): + dp[i][j] = i + + # for i in range(1, n + 1): + # dp[i][1] = i + + for j in range(1, k + 1): + dp[1][j] = 1 + + for i in range(2, n + 1): + for j in range(2, k + 1): + for x in range(1, i + 1): + dp[i][j] = min(dp[i][j], max(dp[i - x][j], dp[x - 1][j - 1]) + 1) + + return dp[n][k] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times k)$。三重循环的时间复杂度为 $O(n^2 \times k)$。 +- **空间复杂度**:$O(n \times k)$。 + +### 思路 2:动态规划优化 + +上一步中时间复杂度为 $O(n^2 \times k)$。根据 $n$ 的规模,提交上去不出意外的超时了。 + +我们可以观察一下上面的状态转移方程:$dp[i][j] = min_{1 \le x \le n} (max(dp[i - x][j], dp[x - 1][j - 1])) + 1$ 。 + +这里最外两层循环的 `i`、`j` 分别为状态的阶段,可以先将 `i`、`j` 看作固定值。最里层循环的 `x` 代表选择的任意一层 `x` ,值从 `1` 遍历到 `i`。 + +此时我们把 `dp[i - x][j]` 和 `dp[x - 1][j - 1]` 分别单独来看。可以看出: + +- 对于 `dp[i - x][j]`:当 `x` 增加时,`i - x` 的值减少,`dp[i - x][j]` 的值跟着减小。自变量 `x` 与函数 `dp[i - x][j]` 是一条单调非递增函数。 +- 对于 `dp[x - 1][j - 1]`:当 `x` 增加时, `x - 1` 的值增加,`dp[x - 1][j - 1]` 的值跟着增加。自变量 `x` 与函数 `dp[x - 1][j - 1]` 是一条单调非递减函数。 + +两条函数的交点处就是两个函数较大值的最小值位置。即 `dp[i][j]` 所取位置。而这个位置可以通过二分查找满足 `dp[x - 1][j - 1] >= dp[i - x][j]` 最大的那个 `x`。这样时间复杂度就从 $O(n^2 \times k)$ 优化到了 $O(n \log n \times k)$。 + +### 思路 2:代码 + +```python +class Solution: + def superEggDrop(self, k: int, n: int) -> int: + dp = [[0 for _ in range(k + 1)] for i in range(n + 1)] + + for i in range(1, n + 1): + for j in range(1, k + 1): + dp[i][j] = i + + # for i in range(1, n + 1): + # dp[i][1] = i + + for j in range(1, k + 1): + dp[1][j] = 1 + + for i in range(2, n + 1): + for j in range(2, k + 1): + left, right = 1, i + while left < right: + mid = left + (right - left) // 2 + if dp[mid - 1][j - 1] < dp[i - mid][j]: + left = mid + 1 + else: + right = mid + dp[i][j] = max(dp[left - 1][j - 1], dp[i - left][j]) + 1 + + return dp[n][k] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \log n \times k)$。两重循环的时间复杂度为 $O(n \times k)$,二分查找的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(n \times k)$。 + +### 思路 3:动态规划 + 逆向思维 + +再看一下我们现在的题目要求:已知有 `n` 层楼,`k` 个鸡蛋,求出至少需要扔几次鸡蛋,才能保证无论 `f` 是多少层,都能将 `f` 找出来? + +我们可以逆向转换一下思维,将题目转变为:**已知有 `k` 个鸡蛋,最多扔 `x` 次鸡蛋(碎没碎都算 `1` 次),求最多可以检测的多少层?** + +我们把未知条件「扔鸡蛋的次数」变为了已知条件,将「检测的楼层个数」变为了未知条件。 + +这样如果求出来的「检测的楼层个数」大于等于 `n`,则说明 `1` ~ `n` 层楼都考虑全了,`f` 值也就明确了。我们只需要从符合条件的情况中,找出「扔鸡蛋次数」最少的次数即可。 + +动态规划的具体步骤如下: + +###### 1. 划分阶段 + +按照鸡蛋个数、扔鸡蛋的次数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:一共有 `i` 个鸡蛋,最多扔 `j` 次鸡蛋(碎没碎都算 `1` 次)的条件下,最多可以检测的楼层个数。 + +###### 3. 状态转移方程 + +我们现在有 `i` 个鸡蛋,`j` 次扔鸡蛋的机会,现在尝试在 `1` ~ `n` 层中的任意一层 `x` 扔鸡蛋: + +1. 如果鸡蛋没碎,剩下 `i` 个鸡蛋,还有 `j - 1` 次扔鸡蛋的机会,最多可以检测 `dp[i][j - 1]` 层楼层。 +2. 如果鸡蛋碎了,剩下 `i - 1` 个鸡蛋,还有 `j - 1` 次扔鸡蛋的机会,最多可以检测 `dp[i - 1][j - 1]` 层楼层。 +3. 再加上我们扔鸡蛋的第 `x` 层,`i` 个鸡蛋,`j` 次扔鸡蛋的机会最多可以检测 `dp[i][j - 1] + dp[i - 1][j - 1] + 1` 层。 + +则状态转移方程为:$dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1$。 + +###### 4. 初始条件 + +- 当鸡蛋数为 `1` 时,只有 `1` 次扔鸡蛋的机会时,最多可以检测 `1` 层,即 `dp[1][1] = 1`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][j]` 表示为:一共有 `i` 个鸡蛋,最多扔 `j` 次鸡蛋(碎没碎都算 `1` 次)的条件下,最多可以检测的楼层个数。则我们需要从满足 `i == k` 并且 `dp[i][j] >= n`(即 `k` 个鸡蛋,`j` 次扔鸡蛋,一共检测出 `n` 层楼)的情况中,找出最小的 ` j`,将其返回。 + +### 思路 3:代码 + +```python +class Solution: + def superEggDrop(self, k: int, n: int) -> int: + dp = [[0 for _ in range(n + 1)] for i in range(k + 1)] + dp[1][1] = 1 + + for i in range(1, k + 1): + for j in range(1, n + 1): + dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1 + if i == k and dp[i][j] >= n: + return j + return n +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times k)$。两重循环的时间复杂度为 $O(n \times k)$。 +- **空间复杂度**:$O(n \times k)$。 + +## 参考资料 + +- 【题解】[题目理解 + 基本解法 + 进阶解法 - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/ji-ben-dong-tai-gui-hua-jie-fa-by-labuladong/) +- 【题解】[动态规划(只解释官方题解方法一)(Java) - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang/) +- 【题解】[动态规划 & 记忆化搜索 2000ms -> 32ms 的过程 - 鸡蛋掉落 - 力扣](https://leetcode.cn/problems/super-egg-drop/solution/python-dong-tai-gui-hua-ji-yi-hua-sou-su-hnj9/) diff --git a/docs/solutions/0800-0899/surface-area-of-3d-shapes.md b/docs/solutions/0800-0899/surface-area-of-3d-shapes.md new file mode 100644 index 00000000..f2b18e2b --- /dev/null +++ b/docs/solutions/0800-0899/surface-area-of-3d-shapes.md @@ -0,0 +1,88 @@ +# [0892. 三维形体的表面积](https://leetcode.cn/problems/surface-area-of-3d-shapes/) + +- 标签:几何、数组、数学、矩阵 +- 难度:简单 + +## 题目链接 + +- [0892. 三维形体的表面积 - 力扣](https://leetcode.cn/problems/surface-area-of-3d-shapes/) + +## 题目大意 + +**描述**:给定一个 $n \times n$ 的网格 $grid$,上面放置着一些 $1 \times 1 \times 1$ 的正方体。每个值 $v = grid[i][j]$ 表示 $v$ 个正方体叠放在对应单元格 $(i, j)$ 上。 + +放置好正方体后,任何直接相邻的正方体都会互相粘在一起,形成一些不规则的三维形体。 + +**要求**:返回最终这些形体的总面积。 + +**说明**: + +- 每个形体的底面也需要计入表面积中。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/01/08/tmp-grid2.jpg) + +```python +输入:grid = [[1,2],[3,4]] +输出:34 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/01/08/tmp-grid4.jpg) + +```python +输入:grid = [[1,1,1],[1,0,1],[1,1,1]] +输出:32 +``` + +## 解题思路 + +### 思路 1:模拟 + +使用二重循环遍历所有的正方体,计算每一个正方体所贡献的表面积,将其累积起来即为答案。 + +而每一个正方体所贡献的表面积,可以通过枚举当前正方体前后左右相邻四个方向上的正方体的个数,从而通过判断计算得出。 + +- 如果当前位置 $(row, col)$ 存在正方体,则正方体在上下位置上起码贡献了 $2$ 的表面积。 +- 如果当前位置 $(row, col)$ 的相邻位置 $(new\underline{\hspace{0.5em}}row, new\underline{\hspace{0.5em}}col)$ 上不存在正方体,说明当前正方体在该方向为最外侧,则 $(row, col)$ 位置所贡献的表面积为当前位置上的正方体个数,即 $grid[row][col]$。 +- 如果当前位置 $(row, col)$ 的相邻位置 $(new\underline{\hspace{0.5em}}row, new\underline{\hspace{0.5em}}col)$ 上存在正方体: + - 如果 $grid[row][col] > grid[new\underline{\hspace{0.5em}}row][new\underline{\hspace{0.5em}}col]$,说明 $grid[row][col]$ 在该方向上底面一部分被 $grid[new\underline{\hspace{0.5em}}row][new\underline{\hspace{0.5em}}col]$ 遮盖了,则 $(row, col)$ 位置所贡献的表面积为 $grid[row][col] - grid[new_row][new_col]$。 + - 如果 $grid[row][col] \le grid[new\underline{\hspace{0.5em}}row][new\underline{\hspace{0.5em}}col]$,说明 $grid[row][col]$ 在该方向上完全被 $grid[new\underline{\hspace{0.5em}}row][new\underline{\hspace{0.5em}}col]$ 遮盖了,则 $(row, col)$ 位置所贡献的表面积为 $0$。 + +### 思路 1:代码 + +```Python +class Solution: + def surfaceArea(self, grid: List[List[int]]) -> int: + directions = [(-1, 0), (0, 1), (1, 0), (0, -1)] + size = len(grid) + + ans = 0 + for row in range(size): + for col in range(size): + if grid[row][col]: + # 底部、顶部贡献表面积 + ans += 2 + for direction in directions: + new_row = row + direction[0] + new_col = col + direction[1] + if 0 <= new_row < size and 0 <= new_col < size: + if grid[row][col] > grid[new_row][new_col]: + add = grid[row][col] - grid[new_row][new_col] + else: + add = 0 + else: + add = grid[row][col] + ans += add + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为二位数组 $grid$ 的行数或列数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0800-0899/transpose-matrix.md b/docs/solutions/0800-0899/transpose-matrix.md new file mode 100644 index 00000000..cfeac41c --- /dev/null +++ b/docs/solutions/0800-0899/transpose-matrix.md @@ -0,0 +1,31 @@ +# [0867. 转置矩阵](https://leetcode.cn/problems/transpose-matrix/) + +- 标签:数组、矩阵、模拟 +- 难度:简单 + +## 题目链接 + +- [0867. 转置矩阵 - 力扣](https://leetcode.cn/problems/transpose-matrix/) + +## 题目大意 + +给定一个二维数组 matrix。返回 matrix 的转置矩阵。 + +## 解题思路 + +直接模拟求解即可。先求出 matrix 的规模。若 matrix 是 m * n 的矩阵。则创建一个 n * m 大小的矩阵 transposed。根据转置的规则对 transposed 的每个元素进行赋值。最终返回 transposed。 + +## 代码 + +```python +class Solution: + def transpose(self, matrix: List[List[int]]) -> List[List[int]]: + m = len(matrix) + n = len(matrix[0]) + transposed = [[0 for _ in range(m)] for _ in range(n)] + for i in range(m): + for j in range(n): + transposed[j][i] = matrix[i][j] + return transposed +``` + diff --git a/docs/solutions/0800-0899/uncommon-words-from-two-sentences.md b/docs/solutions/0800-0899/uncommon-words-from-two-sentences.md new file mode 100644 index 00000000..af057dcd --- /dev/null +++ b/docs/solutions/0800-0899/uncommon-words-from-two-sentences.md @@ -0,0 +1,83 @@ +# [0884. 两句话中的不常见单词](https://leetcode.cn/problems/uncommon-words-from-two-sentences/) + +- 标签:哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0884. 两句话中的不常见单词 - 力扣](https://leetcode.cn/problems/uncommon-words-from-two-sentences/) + +## 题目大意 + +**描述**:给定两个字符串 $s1$ 和 $s2$ ,分别表示两个句子。 + +**要求**:返回所有不常用单词的列表。返回列表中单词可以按任意顺序组织。 + +**说明**: + +- **句子**:是一串由空格分隔的单词。 +- **单词**:仅由小写字母组成的子字符串。 +- **不常见单词**:如果某个单词在其中一个句子中恰好出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的。 +- $1 \le s1.length, s2.length \le 200$。 +- $s1$ 和 $s2$ 由小写英文字母和空格组成。 +- $s1$ 和 $s2$ 都不含前导或尾随空格。 +- $s1$ 和 $s2$ 中的所有单词间均由单个空格分隔。 + +**示例**: + +- 示例 1: + +```python +输入:s1 = "this apple is sweet", s2 = "this apple is sour" +输出:["sweet","sour"] +``` + +- 示例 2: + +```python +输入:s1 = "apple apple", s2 = "banana" +输出:["banana"] +``` + +## 解题思路 + +### 思路 1:哈希表 + +题目要求找出在其中一个句子中恰好出现一次,在另一个句子中却没有出现的单词,其实就是找出在两个句子中只出现过一次的单词,我们可以用哈希表统计两个句子中每个单词的出现频次,然后将出现频次为 $1$ 的单词就是不常见单词,将其加入答案数组即可。 + +具体步骤如下: + +1. 遍历字符串 $s1$、$s2$,使用哈希表 $table$ 统计字符串 $s1$、$s2$ 各个单词的出现频次。 +2. 遍历哈希表,找出出现频次为 $1$ 的单词,将其加入答案数组 $res$ 中。 +3. 遍历完返回答案数组 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def uncommonFromSentences(self, s1: str, s2: str) -> List[str]: + table = dict() + for word in s1.split(' '): + if word not in table: + table[word] = 1 + else: + table[word] += 1 + + for word in s2.split(' '): + if word not in table: + table[word] = 1 + else: + table[word] += 1 + + res = [] + for word in table: + if table[word] == 1: + res.append(word) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$,其中 $m$、$n$ 分别为字符串 $s1$、$s2$ 的长度。 +- **空间复杂度**:$O(m + n)$。 diff --git a/docs/solutions/0800-0899/unique-morse-code-words.md b/docs/solutions/0800-0899/unique-morse-code-words.md new file mode 100644 index 00000000..f4d10cc9 --- /dev/null +++ b/docs/solutions/0800-0899/unique-morse-code-words.md @@ -0,0 +1,87 @@ +# [0804. 唯一摩尔斯密码词](https://leetcode.cn/problems/unique-morse-code-words/) + +- 标签:数组、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0804. 唯一摩尔斯密码词 - 力扣](https://leetcode.cn/problems/unique-morse-code-words/) + +## 题目大意 + +**描述**:国际摩尔斯密码定义一种标准编码方式,将每个字母对应于一个由一系列点和短线组成的字符串, 比如: + +- `'a'` 对应 `".-"`, +- `'b'` 对应 `"-..."`, +- `'c'` 对应 `"-.-."` ,以此类推。 + +为了方便,所有 $26$ 个英文字母的摩尔斯密码表如下: + +`[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."]` + +给定一个字符串数组 $words$,每个单词可以写成每个字母对应摩尔斯密码的组合。 + +- 例如,`"cab"` 可以写成 `"-.-..--..."` ,(即 `"-.-."` + `".-"` + `"-..."` 字符串的结合)。我们将这样一个连接过程称作单词翻译。 + +**要求**:对 $words$ 中所有单词进行单词翻译,返回不同单词翻译的数量。 + +**说明**: + +- $1 \le words.length \le 100$。 +- $1 \le words[i].length \le 12$。 +- $words[i]$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入: words = ["gin", "zen", "gig", "msg"] +输出: 2 +解释: +各单词翻译如下: +"gin" -> "--...-." +"zen" -> "--...-." +"gig" -> "--...--." +"msg" -> "--...--." + +共有 2 种不同翻译, "--...-." 和 "--...--.". +``` + +- 示例 2: + +```python +输入:words = ["a"] +输出:1 +``` + +## 解题思路 + +### 思路 1:模拟 + 哈希表 + +1. 根据题目要求,将所有单词都转换为对应摩斯密码。 +2. 使用哈希表存储所有转换后的摩斯密码。 +3. 返回哈希表中不同的摩斯密码个数(脊哈希表的长度)作为答案。 + +### 思路 1:代码 + +```Python +class Solution: + def uniqueMorseRepresentations(self, words: List[str]) -> int: + table = [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."] + word_set = set() + + for word in words: + word_mose = "" + for ch in word: + word_mose += table[ord(ch) - ord('a')] + word_set.add(word_mose) + + return len(word_set) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(s)$,其中 $s$ 为数组 $words$ 中所有单词的长度之和。 +- **空间复杂度**:$O(s)$。 + diff --git a/docs/solutions/0900-0999/available-captures-for-rook.md b/docs/solutions/0900-0999/available-captures-for-rook.md new file mode 100644 index 00000000..465dc0ba --- /dev/null +++ b/docs/solutions/0900-0999/available-captures-for-rook.md @@ -0,0 +1,93 @@ +# [0999. 可以被一步捕获的棋子数](https://leetcode.cn/problems/available-captures-for-rook/) + +- 标签:数组、矩阵、模拟 +- 难度:简单 + +## 题目链接 + +- [0999. 可以被一步捕获的棋子数 - 力扣](https://leetcode.cn/problems/available-captures-for-rook/) + +## 题目大意 + +**描述**:在一个 $8 \times 8$ 的棋盘上,有一个白色的车(Rook),用字符 `'R'` 表示。棋盘上还可能存在空方块,白色的象(Bishop)以及黑色的卒(pawn),分别用字符 `'.'`,`'B'` 和 `'p'` 表示。不难看出,大写字符表示的是白棋,小写字符表示的是黑棋。 + +**要求**:你现在可以控制车移动一次,请你统计有多少敌方的卒处于你的捕获范围内(即,可以被一步捕获的棋子数)。 + +**说明**: + +- 车按国际象棋中的规则移动。东,西,南,北四个基本方向任选其一,然后一直向选定的方向移动,直到满足下列四个条件之一: + - 棋手选择主动停下来。 + - 棋子因到达棋盘的边缘而停下。 + - 棋子移动到某一方格来捕获位于该方格上敌方(黑色)的卒,停在该方格内。 + - 车不能进入/越过已经放有其他友方棋子(白色的象)的方格,停在友方棋子前。 + +- $board.length == board[i].length == 8$ +- $board[i][j]$ 可以是 `'R'`,`'.'`,`'B'` 或 `'p'`。 +- 只有一个格子上存在 $board[i][j] == 'R'$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/1253_example_1_improved.PNG) + +```python +输入:[[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".","R",".",".",".","p"],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]] +输出:3 +解释:在本例中,车能够捕获所有的卒。 +``` + +- 示例 2: + +![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/1253_example_2_improved.PNG) + +```python +输入:[[".",".",".",".",".",".",".","."],[".","p","p","p","p","p",".","."],[".","p","p","B","p","p",".","."],[".","p","B","R","B","p",".","."],[".","p","p","B","p","p",".","."],[".","p","p","p","p","p",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]] +输出:0 +解释:象阻止了车捕获任何卒。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 双重循环遍历确定白色车的位置 $(pos\underline{\hspace{0.5em}}i,poss\underline{\hspace{0.5em}}j)$。 +2. 让车向上、下、左、右四个方向进行移动,直到超出边界 / 碰到白色象 / 碰到卒为止。使用计数器 $cnt$ 记录捕获的卒的数量。 +3. 返回答案 $cnt$。 + +### 思路 1:代码 + +```Python +class Solution: + def numRookCaptures(self, board: List[List[str]]) -> int: + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + pos_i, pos_j = -1, -1 + for i in range(len(board)): + if pos_i != -1 and pos_j != -1: + break + for j in range(len(board[i])): + if board[i][j] == 'R': + pos_i, pos_j = i, j + break + + cnt = 0 + for direction in directions: + setp = 0 + while True: + new_i = pos_i + setp * direction[0] + new_j = pos_j + setp * direction[1] + if new_i < 0 or new_i >= 8 or new_j < 0 or new_j >= 8 or board[new_i][new_j] == 'B': + break + if board[new_i][new_j] == 'p': + cnt += 1 + break + setp += 1 + + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为棋盘的边长。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0900-0999/beautiful-array.md b/docs/solutions/0900-0999/beautiful-array.md new file mode 100644 index 00000000..2740e683 --- /dev/null +++ b/docs/solutions/0900-0999/beautiful-array.md @@ -0,0 +1,86 @@ +# [0932. 漂亮数组](https://leetcode.cn/problems/beautiful-array/) + +- 标签:数组、数学、分治 +- 难度:中等 + +## 题目链接 + +- [0932. 漂亮数组 - 力扣](https://leetcode.cn/problems/beautiful-array/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回长度为 $n$ 的任一漂亮数组。 + +**说明**: + +- **漂亮数组**(长度为 $n$ 的数组 $nums$ 满足下述条件): + - $nums$ 是由范围 $[1, n]$ 的整数组成的一个排列。 + - 对于每个 $0 \le i < j < n$,均不存在下标 $k$($i < k < j$)使得 $2 \times nums[k] == nums[i] + nums[j]$。 +- $1 \le n \le 1000$。 +- 本题保证对于给定的 $n$ 至少存在一个有效答案。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:[2,1,4,3] +``` + +- 示例 2: + +```python +输入:n = 5 +输出:[3,1,2,5,4] +``` + +## 解题思路 + +### 思路 1:分治算法 + +根据题目要求,我们可以得到以下信息: + +1. 题目要求 $2 \times nums[k] == nums[i] + nums[j], (0 \le i < k < j < n)$ 不能成立,可知:等式左侧必为偶数,只要右侧和为奇数则等式不成立。 +2. 已知:奇数 + 偶数 = 奇数,则令 $nums[i]$ 和 $nums[j]$ 其中一个为奇数,另一个为偶数,即可保证 $nums[i] + nums[j]$ 一定为奇数。这里我们不妨令 $nums[i]$ 为奇数,令 $nums[j]$ 为偶数。 +3. 如果数组 $nums$ 是漂亮数组,那么对数组 $nums$ 的每一位元素乘以一个常数或者加上一个常数之后,$nums$ 仍是漂亮数组。 + - 即如果 $[a_1, a_2, ..., a_n]$ 是一个漂亮数组,那么 $[k \times a_1 + b, k \times a_2 + b, ..., k \times a_n + b]$ 也是漂亮数组。 + +那么,我们可以按照下面的规则构建长度为 $n$ 的漂亮数组。 + +1. 当 $n = 1$ 时,返回 $[1]$。此时数组 $nums$ 中仅有 $1$ 个元素,并且满足漂亮数组的条件。 +2. 当 $n > 1$ 时,我们将 $nums$ 分解为左右两个部分:`left_nums`、`right_nums`。如果左右两个部分满足: + 1. 数组 `left_nums` 中元素全为奇数(可以通过 `nums[i] * 2 - 1` 将 `left_nums` 中元素全部映射为奇数)。 + 2. 数组 `right_nums` 中元素全为偶数(可以通过 `nums[i] * 2` 将 `right_nums` 中元素全部映射为偶数)。 + 3. `left_nums` 和 `right_nums` 都是漂亮数组。 +3. 那么 `left_nums + right_nums` 构成的数组一定也是漂亮数组,即 $nums$ 为漂亮数组,将 $nums$ 返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def beautifulArray(self, n: int) -> List[int]: + if n == 1: + return [1] + + nums = [0 for _ in range(n)] + left_cnt = (n + 1) // 2 + right_cnt = n - left_cnt + left_nums = self.beautifulArray(left_cnt) + right_nums = self.beautifulArray(right_cnt) + + for i in range(left_cnt): + nums[i] = 2 * left_nums[i] - 1 + + for i in range(right_cnt): + nums[left_cnt + i] = 2 * right_nums[i] + + return nums +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n \times \log n)$。 diff --git a/docs/solutions/0900-0999/binary-tree-cameras.md b/docs/solutions/0900-0999/binary-tree-cameras.md new file mode 100644 index 00000000..b5f7ea84 --- /dev/null +++ b/docs/solutions/0900-0999/binary-tree-cameras.md @@ -0,0 +1,89 @@ +# [0968. 监控二叉树](https://leetcode.cn/problems/binary-tree-cameras/) + +- 标签:树、深度优先搜索、动态规划、二叉树 +- 难度:困难 + +## 题目链接 + +- [0968. 监控二叉树 - 力扣](https://leetcode.cn/problems/binary-tree-cameras/) + +## 题目大意 + +给定一个二叉树,需要在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父节点、自身及其直接子节点。 + +计算监控树的所有节点所需的最小摄像头数量。 + +- 示例 1: + + + +![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/bst_cameras_01.png) + +``` +输入:[0,0,null,0,0] +输出:1 +解释:如图所示,一台摄像头足以监控所有节点。 +``` + +- 示例 2: + +![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/29/bst_cameras_02.png) + +``` +输入:[0,0,null,0,null,0,null,null,0] +输出:2 +解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。 +``` + +## 解题思路 + +根据题意可知,一个摄像头的有效范围为 3 层:父节点、自身及其直接子节点。而约是下层的节点就越多,所以摄像头应该优先满足下层节点。可以使用后序遍历的方式遍历二叉树的节点,这样就可以优先遍历叶子节点。 + +对于每个节点,利用贪心思想,可以确定三种状态: + +- 第一种状态:该节点无覆盖 +- 第二种状态:该节点已经装上了摄像头 +- 第三种状态:该节点已经覆盖 + +为了让摄像头数量最少,我们要尽量让叶⼦节点的⽗节点安装摄像头,这样才能摄像头的数量最少。对此我们应当分析当前节点和左右两侧子节点的覆盖情况。 + +先来考虑空节点,空节点应该算作已经覆盖状态。 + +再来考虑左右两侧子覆盖情况: + +- 如果左节点或者右节点都无覆盖,则当前节点需要装上摄像头,答案 res 需要 + 1。 +- 如果左节点已经覆盖或者右节点已经装上了摄像头,则当前节点已经覆盖。 +- 如果左节点右节点都已经覆盖,则当前节点无覆盖。 + +根据以上条件就可以写出对应的后序遍历代码。 + +## 代码 + +```python +class Solution: + res = 0 + def traversal(self, cur: TreeNode) -> int: + if not cur: + return 3 + + left = self.traversal(cur.left) + right = self.traversal(cur.right) + + if left == 1 or right == 1: + self.res += 1 + return 2 + + if left == 2 or right == 2: + return 3 + + if left == 3 and right == 3: + return 1 + return -1 + + def minCameraCover(self, root: TreeNode) -> int: + self.res = 0 + if self.traversal(root) == 1: + self.res += 1 + return self.res +``` + diff --git a/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md b/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md new file mode 100644 index 00000000..501174d1 --- /dev/null +++ b/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md @@ -0,0 +1,81 @@ +# [0958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [0958. 二叉树的完全性检验 - 力扣](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) + +## 题目大意 + +**描述**:给定一个二叉树的根节点 `root`。 + +**要求**:判断该二叉树是否是一个完全二叉树。 + +**说明**: + +- **完全二叉树**: +- 树的结点数在范围 $[1, 100]$ 内。 +- $1 \le Node.val \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-1.png) + +```python +输入:root = [1,2,3,4,5,6] +输出:true +解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-2.png) + +```python +输入:root = [1,2,3,4,5,null,7] +输出:false +解释:值为 7 的结点没有尽可能靠向左侧。 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +对于一个完全二叉树,按照「层序遍历」的顺序进行广度优先搜索,在遇到第一个空节点之后,整个完全二叉树的遍历就已结束了。不应该在后续遍历过程中再次出现非空节点。 + +如果在遍历过程中在遇到第一个空节点之后,又出现了非空节点,则该二叉树不是完全二叉树。 + +利用这一点,我们可以在广度优先搜索的过程中,维护一个布尔变量 `is_empty` 用于标记是否遇见了空节点。 + +### 思路 1:代码 + +```python +class Solution: + def isCompleteTree(self, root: Optional[TreeNode]) -> bool: + if not root: + return False + + queue = collections.deque([root]) + is_empty = False + while queue: + size = len(queue) + for _ in range(size): + cur = queue.popleft() + if not cur: + is_empty = True + else: + if is_empty: + return False + queue.append(cur.left) + queue.append(cur.right) + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树的节点数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/0900-0999/complete-binary-tree-inserter.md b/docs/solutions/0900-0999/complete-binary-tree-inserter.md new file mode 100644 index 00000000..3605503b --- /dev/null +++ b/docs/solutions/0900-0999/complete-binary-tree-inserter.md @@ -0,0 +1,56 @@ +# [0919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) + +- 标签:树、广度优先搜索、设计、二叉树 +- 难度:中等 + +## 题目链接 + +- [0919. 完全二叉树插入器 - 力扣](https://leetcode.cn/problems/complete-binary-tree-inserter/) + +## 题目大意 + +要求:设计一个用完全二叉树初始化的数据结构 `CBTInserter`,并支持以下几种操作: + +- `CBTInserter(TreeNode root)` 使用根节点为 `root` 的给定树初始化该数据结构; +- `CBTInserter.insert(int v)` 向树中插入一个新节点,节点类型为 `TreeNode`,值为 `v`。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值; +- `CBTInserter.get_root()` 返回树的根节点。 + +## 解题思路 + +使用数组标记完全二叉树中节点的序号,初始化数组为 `[None]`。完全二叉树中节点的序号从 `1` 开始,对于序号为 `k` 的节点,其左子节点序号为 `2k`,右子节点的序号为 `2k + 1`,其父节点的序号为 `k // 2`。 + +然后在初始化和插入节点的同时,按顺序向数组中插入节点。 + +## 代码 + +```python +class CBTInserter: + + def __init__(self, root: TreeNode): + self.queue = [root] + self.nodelist = [None] + + while self.queue: + node = self.queue.pop(0) + self.nodelist.append(node) + if node.left: + self.queue.append(node.left) + if node.right: + self.queue.append(node.right) + + + def insert(self, v: int) -> int: + self.nodelist.append(TreeNode(v)) + index = len(self.nodelist) - 1 + father = self.nodelist[index // 2] + if index % 2 == 0: + father.left = self.nodelist[-1] + else: + father.right = self.nodelist[-1] + return father.val + + + def get_root(self) -> TreeNode: + return self.nodelist[1] +``` + diff --git a/docs/solutions/0900-0999/cousins-in-binary-tree.md b/docs/solutions/0900-0999/cousins-in-binary-tree.md new file mode 100644 index 00000000..011a47b3 --- /dev/null +++ b/docs/solutions/0900-0999/cousins-in-binary-tree.md @@ -0,0 +1,43 @@ +# [0993. 二叉树的堂兄弟节点](https://leetcode.cn/problems/cousins-in-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [0993. 二叉树的堂兄弟节点 - 力扣](https://leetcode.cn/problems/cousins-in-binary-tree/) + +## 题目大意 + +给定一个二叉树,和两个值 x,y。从二叉树中找出 x 和 y 对应的节点 node_x,node_y。如果两个节点是堂兄弟节点,则返回 True,否则返回 False。 + +- 堂兄弟节点:两个节点的深度相同,父节点不同。 + +## 解题思路 + +广度优先搜索或者深度优先搜索都可。以深度优先搜索为例,递归遍历查找节点值为 x,y 的两个节点。在递归的同时,需要传入递归函数当前节点的深度和父节点信息。若找到对应的节点,则保存两节点对应深度和父节点信息。最后判断两个节点是否是深度相同,父节点不同。如果是,则返回 True,不是则返回 False。 + +## 代码 + +```python +class Solution: + def isCousins(self, root: TreeNode, x: int, y: int) -> bool: + depths = [0, 0] + parents = [None, None] + + def dfs(node, depth, parent): + if not node: + return + if node.val == x: + depths[0] = depth + parents[0] = parent + elif node.val == y: + depths[1] = depth + parents[1] = parent + dfs(node.left, depth+1, node) + dfs(node.right, depth+1, node) + + dfs(root, 0, None) + return depths[0] == depths[1] and parents[0] != parents[1] +``` + diff --git a/docs/solutions/0900-0999/fruit-into-baskets.md b/docs/solutions/0900-0999/fruit-into-baskets.md new file mode 100644 index 00000000..95f2e405 --- /dev/null +++ b/docs/solutions/0900-0999/fruit-into-baskets.md @@ -0,0 +1,59 @@ +# [0904. 水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) + +- 标签:数组、哈希表、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0904. 水果成篮 - 力扣](https://leetcode.cn/problems/fruit-into-baskets/) + +## 题目大意 + +给定一个数组 `fruits`。其中 `fruits[i]` 表示第 `i` 棵树会产生 `fruits[i]` 型水果。 + +你可以从你选择的任何树开始,然后重复执行以下步骤: + +- 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。 +- 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。 +- 请注意,在选择一棵树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。 + +你有 `2` 个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。 + +要求:返回你能收集的水果树的最大总量。 + +## 解题思路 + +只有 `2` 个篮子,要求在连续子数组中装最多 `2` 种不同水果。可以理解为维护一个水果种类数为 `2` 的滑动数组,求窗口中最大的水果树数目。具体做法如下: + +- 用滑动窗口 `window` 来维护不同种类水果树数目。`window` 为哈希表类型。`ans` 用来维护能收集的水果树的最大总量。设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中水果种类数不超过 `2` 种。 +- 一开始,`left`、`right` 都指向 `0`。 +- 将最右侧数组元素 `fruits[right]` 加入当前窗口 `window` 中,该水果树数目 +1。 +- 如果该窗口中该水果树种类多于 `2` 种,即 `len(window) > 2`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应水果树的个数,直到 `len(window) <= 2`。 +- 维护更新能收集的水果树的最大总量。然后右移 `right`,直到 `right >= len(fruits)` 结束。 +- 输出能收集的水果树的最大总量。 + +## 代码 + +```python +class Solution: + def totalFruit(self, fruits: List[int]) -> int: + window = dict() + window_size = 2 + ans = 0 + left, right = 0, 0 + while right < len(fruits): + if fruits[right] in window: + window[fruits[right]] += 1 + else: + window[fruits[right]] = 1 + + while len(window) > window_size: + window[fruits[left]] -= 1 + if window[fruits[left]] == 0: + del window[fruits[left]] + left += 1 + ans = max(ans, right - left + 1) + right += 1 + return ans +``` + diff --git a/docs/solutions/0900-0999/index.md b/docs/solutions/0900-0999/index.md new file mode 100644 index 00000000..3904cd2a --- /dev/null +++ b/docs/solutions/0900-0999/index.md @@ -0,0 +1,33 @@ +## 本章内容 + +- [0900. RLE 迭代器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/rle-iterator.md) +- [0901. 股票价格跨度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/online-stock-span.md) +- [0902. 最大为 N 的数字组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md) +- [0904. 水果成篮](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/fruit-into-baskets.md) +- [0908. 最小差值 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/smallest-range-i.md) +- [0912. 排序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/sort-an-array.md) +- [0918. 环形子数组的最大和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/maximum-sum-circular-subarray.md) +- [0919. 完全二叉树插入器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/complete-binary-tree-inserter.md) +- [0921. 使括号有效的最少添加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md) +- [0925. 长按键入](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/long-pressed-name.md) +- [0932. 漂亮数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/beautiful-array.md) +- [0933. 最近的请求次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/number-of-recent-calls.md) +- [0935. 骑士拨号器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/knight-dialer.md) +- [0938. 二叉搜索树的范围和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/range-sum-of-bst.md) +- [0946. 验证栈序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/validate-stack-sequences.md) +- [0947. 移除最多的同行或同列石头](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md) +- [0953. 验证外星语词典](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/verifying-an-alien-dictionary.md) +- [0958. 二叉树的完全性检验](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/check-completeness-of-a-binary-tree.md) +- [0959. 由斜杠划分区域](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/regions-cut-by-slashes.md) +- [0968. 监控二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/binary-tree-cameras.md) +- [0973. 最接近原点的 K 个点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/k-closest-points-to-origin.md) +- [0974. 和可被 K 整除的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/subarray-sums-divisible-by-k.md) +- [0976. 三角形的最大周长](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/largest-perimeter-triangle.md) +- [0977. 有序数组的平方](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/squares-of-a-sorted-array.md) +- [0978. 最长湍流子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/longest-turbulent-subarray.md) +- [0982. 按位与为零的三元组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md) +- [0990. 等式方程的可满足性](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/satisfiability-of-equality-equations.md) +- [0992. K 个不同整数的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/subarrays-with-k-different-integers.md) +- [0993. 二叉树的堂兄弟节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/cousins-in-binary-tree.md) +- [0995. K 连续位的最小翻转次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md) +- [0999. 可以被一步捕获的棋子数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/available-captures-for-rook.md) diff --git a/docs/solutions/0900-0999/k-closest-points-to-origin.md b/docs/solutions/0900-0999/k-closest-points-to-origin.md new file mode 100644 index 00000000..ae462195 --- /dev/null +++ b/docs/solutions/0900-0999/k-closest-points-to-origin.md @@ -0,0 +1,114 @@ +# [0973. 最接近原点的 K 个点](https://leetcode.cn/problems/k-closest-points-to-origin/) + +- 标签:几何、数组、数学、分治、快速选择、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [0973. 最接近原点的 K 个点 - 力扣](https://leetcode.cn/problems/k-closest-points-to-origin/) + +## 题目大意 + +给定一个由由平面上的点组成的列表 `points`,再给定一个整数 `K`。 + +要求:从中找出 `K` 个距离原点` (0, 0)` 最近的点。(这里,平面上两点之间的距离是欧几里德距离。)可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。 + +## 解题思路 + +1. 使用二叉堆构建优先队列,优先级为距离原点的距离。此时堆顶元素即为距离原点最近的元素。 +2. 将堆顶元素加入到答案数组中,进行出队操作。时间复杂度 $O(log{n})$。 + - 出队操作:交换堆顶元素与末尾元素,将末尾元素已移出堆。继续调整大顶堆。 +3. 不断重复第 2 步,直到 `K` 次结束。 + +## 代码 + +```python +class Heapq: + def compare(self, a, b): + dist_a = a[0] * a[0] + a[1] * a[1] + dist_b = b[0] * b[0] + b[1] * b[1] + if dist_a < dist_b: + return -1 + elif dist_a == dist_b: + return 0 + else: + return 1 + # 堆调整方法:调整为小顶堆 + def heapAdjust(self, nums: [int], index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子结点 + max_index = index + if self.compare(nums[left], nums[max_index]) == -1: + max_index = left + if right <= end and self.compare(nums[right], nums[max_index]) == -1: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 将数组构建为二叉堆 + def heapify(self, nums: [int]): + size = len(nums) + # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + # 调用调整堆函数 + self.heapAdjust(nums, i, size - 1) + + # 入队操作 + def heappush(self, nums: list, value): + nums.append(value) + size = len(nums) + i = size - 1 + # 寻找插入位置 + while (i - 1) // 2 >= 0: + cur_root = (i - 1) // 2 + # value 大于当前根节点,则插入到当前位置 + if self.compare(nums[cur_root], value) == -1: + break + # 继续向上查找 + nums[i] = nums[cur_root] + i = cur_root + # 找到插入位置或者到达根位置,将其插入 + nums[i] = value + + # 出队操作 + def heappop(self, nums: list) -> int: + size = len(nums) + nums[0], nums[-1] = nums[-1], nums[0] + # 得到最小值(堆顶元素)然后调整堆 + top = nums.pop() + if size > 0: + self.heapAdjust(nums, 0, size - 2) + + return top + + # 升序堆排序 + def heapSort(self, nums: [int]): + self.heapify(nums) + size = len(nums) + for i in range(size): + nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] + self.heapAdjust(nums, 0, size - i - 2) + return nums + +class Solution: + def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]: + heap = Heapq() + queue = [] + for point in points: + heap.heappush(queue, point) + + res = [] + for i in range(k): + res.append(heap.heappop(queue)) + + return res +``` + diff --git a/docs/solutions/0900-0999/knight-dialer.md b/docs/solutions/0900-0999/knight-dialer.md new file mode 100644 index 00000000..69d6f2db --- /dev/null +++ b/docs/solutions/0900-0999/knight-dialer.md @@ -0,0 +1,112 @@ +# [0935. 骑士拨号器](https://leetcode.cn/problems/knight-dialer/) + +- 标签:动态规划 +- 难度:中等 + +## 题目链接 + +- [0935. 骑士拨号器 - 力扣](https://leetcode.cn/problems/knight-dialer/) + +## 题目大意 + +**描述**:象棋骑士可以垂直移动两个方格,水平移动一个方格,或者水平移动两个方格,垂直移动一个方格(两者都形成一个 $L$ 的形状),如下图所示。 + +![](https://assets.leetcode.com/uploads/2020/08/18/chess.jpg) + +现在我们有一个象棋其实和一个电话垫,如下图所示,骑士只能站在一个数字单元格上($0 \sim 9$)。 + +![](https://assets.leetcode.com/uploads/2020/08/18/phone.jpg) + +现在给定一个整数 $n$。 + +**要求**:返回我们可以拨多少个长度为 $n$ 的不同电话号码。因为答案可能很大,所以最终答案需要对 $10^9 + 7$ 进行取模。 + +**说明**: + +- 可以将骑士放在任何数字单元格上,然后执行 $n - 1$ 次移动来获得长度为 $n$ 的电话号码。 +- $1 \le n \le 5000$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 1 +输出:10 +解释:我们需要拨一个长度为1的数字,所以把骑士放在10个单元格中的任何一个数字单元格上都能满足条件。 +``` + +- 示例 2: + +```python +输入:n = 2 +输出:20 +解释:我们可以拨打的所有有效号码为[04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94] +``` + +## 解题思路 + +### 思路 1:动态规划 + +根据象棋骑士的跳跃规则,以及电话键盘的样式,我们可以预先处理一下象棋骑士当前位置与下一步能跳跃到的位置关系,将其存入哈希表中,方便查询。 + +接下来我们可以用动态规划的方式,计算出跳跃 $n - 1$ 次总共能得到多少个长度为 $n$ 的不同电话号码。 + +###### 1. 划分阶段 + +按照步数、所处数字位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][v]$ 表示为:第 $i$ 步到达键位 $u$ 总共能到的长度为 $i + 1$ 的不同电话号码个数。 + +###### 3. 状态转移方程 + +第 $i$ 步到达键位 $v$ 所能得到的不同电话号码个数,取决于 $i - 1$ 步中所有能到达 $v$ 的键位 $u$ 的不同电话号码个数总和。 + +呢状态转移方程为:$dp[i][v] = \sum dp[i - 1][u]$(可以从 $u$ 跳到 $v$)。 + +###### 4. 初始条件 + +- 第 $0$ 步(位于开始位置)所能得到的电话号码个数为 $1$,因为开始时可以将骑士放在任何数字单元格上,所以所有的 $dp[0][v] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][v]$ 表示为:第 $i$ 步到达键位 $u$ 总共能到的长度为 $i + 1$ 的不同电话号码个数。 所以最终结果为第 $n - 1$ 行所有的 $dp[n - 1][v]$ 的总和。 + +### 思路 1:代码 + +```python +class Solution: + def knightDialer(self, n: int) -> int: + graph = { + 0: [4, 6], + 1: [6, 8], + 2: [7, 9], + 3: [4, 8], + 4: [0, 3, 9], + 5: [], + 6: [0, 1, 7], + 7: [2, 6], + 8: [1, 3], + 9: [2, 4] + } + + MOD = 10 ** 9 + 7 + dp = [[0 for _ in range(10)] for _ in range(n)] + for v in range(10): + dp[0][v] = 1 + + for i in range(1, n): + for u in range(10): + for v in graph[u]: + dp[i][v] = (dp[i][v] + dp[i - 1][u]) % MOD + + return sum(dp[n - 1]) % MOD +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 10)$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(n \times 10)$。 + diff --git a/docs/solutions/0900-0999/largest-perimeter-triangle.md b/docs/solutions/0900-0999/largest-perimeter-triangle.md new file mode 100644 index 00000000..a4bbf0aa --- /dev/null +++ b/docs/solutions/0900-0999/largest-perimeter-triangle.md @@ -0,0 +1,52 @@ +# [0976. 三角形的最大周长](https://leetcode.cn/problems/largest-perimeter-triangle/) + +- 标签:贪心、数组、数学、排序 +- 难度:简单 + +## 题目链接 + +- [0976. 三角形的最大周长 - 力扣](https://leetcode.cn/problems/largest-perimeter-triangle/) + +## 题目大意 + +**描述**:给定一些由正数(代表长度)组成的数组 `nums`。 + +**要求**:返回由其中 `3` 个长度组成的、面积不为 `0` 的三角形的最大周长。如果不能形成任何面积不为 `0` 的三角形,则返回 `0`。 + +**说明**: + +- $3 \le nums.length \le 10^4$。 +- $1 \le nums[i] \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,1,2] +输出:5 +解释:长度为 2, 1, 2 的边组成的三角形周长为 5,为最大周长 +``` + +## 解题思路 + +### 思路 1: + +要想三角形的周长最大,则每一条边都要尽可能的长,并且还要满足三角形的边长条件,即 `a + b > c`,其中 `a`、`b`、`c` 分别是三角形的 `3` 条边长。 + +所以,我们可以先对所有边长进行排序。然后倒序枚举最长边 `nums[i]`,判断前两个边长相加是否大于最长边,即 `nums[i - 2] + nums[i - 1] > nums[i]`。如果满足,则返回 `3` 条边长的和,否则的话继续枚举最长边。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def largestPerimeter(self, nums: List[int]) -> int: + nums.sort() + for i in range(len(nums) - 1, 1, -1): + if nums[i - 2] + nums[i - 1] > nums[i]: + return nums[i - 2] + nums[i - 1] + nums[i] + return 0 +``` + diff --git a/docs/solutions/0900-0999/long-pressed-name.md b/docs/solutions/0900-0999/long-pressed-name.md new file mode 100644 index 00000000..27f8eaf0 --- /dev/null +++ b/docs/solutions/0900-0999/long-pressed-name.md @@ -0,0 +1,84 @@ +# [0925. 长按键入](https://leetcode.cn/problems/long-pressed-name/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [0925. 长按键入 - 力扣](https://leetcode.cn/problems/long-pressed-name/) + +## 题目大意 + +**描述**:你的朋友正在使用键盘输入他的名字 $name$。偶尔,在键入字符时,按键可能会被长按,而字符可能被输入 $1$ 次或多次。 + +现在给定代表名字的字符串 $name$,以及实际输入的字符串 $typed$。 + +**要求**:检查键盘输入的字符 $typed$。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),就返回 `True`。否则返回 `False`。 + +**说明**: + +- $1 \le name.length, typed.length \le 1000$。 +- $name$ 和 $typed$ 的字符都是小写字母。 + +**示例**: + +- 示例 1: + +```python +输入:name = "alex", typed = "aaleex" +输出:true +解释:'alex' 中的 'a' 和 'e' 被长按。 +``` + +- 示例 2: + +```python +输入:name = "saeed", typed = "ssaaedd" +输出:false +解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。 +``` + +## 解题思路 + +### 思路 1:分离双指针 + +这道题目的意思是在 $typed$ 里边匹配 $name$,同时要考虑字符重复问题,以及不匹配的情况。可以使用分离双指针来做。具体做法如下: + +1. 使用两个指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$,$left\underline{\hspace{0.5em}}1$ 指向字符串 $name$ 开始位置,$left\underline{\hspace{0.5em}}2$ 指向字符串 $type$ 开始位置。 +2. 如果 $name[left\underline{\hspace{0.5em}}1] == name[left\underline{\hspace{0.5em}}2]$,则将 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$ 同时右移。 +3. 如果 $nmae[left\underline{\hspace{0.5em}}1] \ne name[left\underline{\hspace{0.5em}}2]$,则: + 1. 如果 $typed[left\underline{\hspace{0.5em}}2]$ 和前一个位置元素 $typed[left\underline{\hspace{0.5em}}2 - 1]$ 相等,则说明出现了重复元素,将 $left\underline{\hspace{0.5em}}2$ 右移,过滤重复元素。 + 2. 如果 $typed[left\underline{\hspace{0.5em}}2]$ 和前一个位置元素 $typed[left\underline{\hspace{0.5em}}2 - 1]$ 不等,则说明出现了多余元素,不匹配。直接返回 `False` 即可。 + +4. 当 $left\underline{\hspace{0.5em}}1 == len(name)$ 或者 $left\underline{\hspace{0.5em}}2 == len(typed)$ 时跳出循环。然后过滤掉 $typed$ 末尾的重复元素。 +5. 最后判断,如果 $left\underline{\hspace{0.5em}}1 == len(name)$ 并且 $left\underline{\hspace{0.5em}}2 == len(typed)$,则说明匹配,返回 `True`,否则返回 `False`。 + +### 思路 1:代码 + +```python +class Solution: + def isLongPressedName(self, name: str, typed: str) -> bool: + left_1, left_2 = 0, 0 + + while left_1 < len(name) and left_2 < len(typed): + if name[left_1] == typed[left_2]: + left_1 += 1 + left_2 += 1 + elif left_2 > 0 and typed[left_2 - 1] == typed[left_2]: + left_2 += 1 + else: + return False + while 0 < left_2 < len(typed) and typed[left_2] == typed[left_2 - 1]: + left_2 += 1 + + if left_1 == len(name) and left_2 == len(typed): + return True + else: + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$。其中 $n$、$m$ 分别为字符串 $name$、$typed$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0900-0999/longest-turbulent-subarray.md b/docs/solutions/0900-0999/longest-turbulent-subarray.md new file mode 100644 index 00000000..ee68e168 --- /dev/null +++ b/docs/solutions/0900-0999/longest-turbulent-subarray.md @@ -0,0 +1,80 @@ +# [0978. 最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/) + +- 标签:数组、动态规划、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [0978. 最长湍流子数组 - 力扣](https://leetcode.cn/problems/longest-turbulent-subarray/) + +## 题目大意 + +**描述**:给定一个数组 $arr$。当 $arr$ 的子数组 $arr[i]$,$arr[i + 1]$,$...$, $arr[j]$ 满足下列条件时,我们称其为湍流子数组: + +- 如果 $i \le k < j$,当 $k$ 为奇数时, $arr[k] > arr[k + 1]$,且当 $k$ 为偶数时,$arr[k] < arr[k + 1]$; +- 或如果 $i \le k < j$,当 $k$ 为偶数时,$arr[k] > arr[k + 1]$ ,且当 $k$ 为奇数时,$arr[k] < arr[k + 1]$。 +- 也就是说,如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组。 + +**要求**:返回给定数组 $arr$ 的最大湍流子数组的长度。 + +**说明**: + +- $1 \le arr.length \le 4 \times 10^4$。 +- $0 \le arr[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [9,4,2,10,7,8,8,1,9] +输出:5 +解释:arr[1] > arr[2] < arr[3] > arr[4] < arr[5] +``` + +- 示例 2: + +```python +输入:arr = [4,8,12,16] +输出:2 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +湍流子数组实际上像波浪一样,比如 $arr[i - 2] > arr[i - 1] < arr[i] > arr[i + 1] < arr[i + 2]$。所以我们可以使用双指针的做法。具体做法如下: + +- 使用两个指针 $left$、$right$。$left$ 指向湍流子数组的左端,$right$ 指向湍流子数组的右端。 +- 如果 $arr[right - 1] == arr[right]$,则更新 `left = right`,重新开始计算最长湍流子数组大小。 +- 如果 $arr[right - 2] < arr[right - 1] < arr[right]$,此时为递增数组,则 $left$ 从 $right - 1$ 开始重新计算最长湍流子数组大小。 +- 如果 $arr[right - 2] > arr[right - 1] > arr[right]$,此时为递减数组,则 $left$ 从 $right - 1$ 开始重新计算最长湍流子数组大小。 +- 其他情况(即 $arr[right - 2] < arr[right - 1] > arr[right]$ 或 $arr[right - 2] > arr[right - 1] < arr[right]$)时,不用更新 $left$值。 +- 更新最大湍流子数组的长度,并向右移动 $right$。直到 $right \ge len(arr)$ 时,返回答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def maxTurbulenceSize(self, arr: List[int]) -> int: + left, right = 0, 1 + ans = 1 + + while right < len(arr): + if arr[right - 1] == arr[right]: + left = right + elif right != 1 and arr[right - 2] < arr[right - 1] and arr[right - 1] < arr[right]: + left = right - 1 + elif right != 1 and arr[right - 2] > arr[right - 1] and arr[right - 1] > arr[right]: + left = right - 1 + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $arr$ 中的元素数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0900-0999/maximum-sum-circular-subarray.md b/docs/solutions/0900-0999/maximum-sum-circular-subarray.md new file mode 100644 index 00000000..9409a3f2 --- /dev/null +++ b/docs/solutions/0900-0999/maximum-sum-circular-subarray.md @@ -0,0 +1,50 @@ +# [0918. 环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/) + +- 标签:队列、数组、分治、动态规划、单调队列 +- 难度:中等 + +## 题目链接 + +- [0918. 环形子数组的最大和 - 力扣](https://leetcode.cn/problems/maximum-sum-circular-subarray/) + +## 题目大意 + +给定一个环形整数数组 nums,数组 nums 的尾部和头部是相连状态。求环形数组 nums 的非空子数组的最大和(子数组中每个位置元素最多出现一次)。 + +## 解题思路 + +构成环形整数数组 nums 的非空子数组的最大和的子数组有两种情况: + +- 最大和的子数组为一个子区间:$nums[i] + nums[i+1] + nums[i+2] + ... + num[j]$。 +- 最大和的子数组为首尾的两个子区间:$(nums[0] + nums[1] + ... + nums[i]) + (nums[j] + nums[j+1] + ... + num[N-1])$。 + +第一种情况其实就是无环情况下的整数数组的非空子数组最大和问题,跟「[53. 最大子序和](https://leetcode.cn/problems/maximum-subarray/)」问题是一致的,我们假设求解结果为 `max_num`。 + +下来来思考第二种情况,第二种情况下,要使首尾两个子区间的和尽可能的大,则中间的子区间的和应该尽可能的小。 + +使得中间子区间的和尽可能小的问题,可以转变为求解:整数数组 nums 的非空子数组最小和问题。求解思路跟上边是相似的,只不过最大变为了最小。我们假设求解结果为 `min_num`。 + +而首尾两个区间和尽可能大的结果为数组 nums 的和减去中间最小子数组和,即 `sum(nums) - min_num`。 + + 最终的结果就是比较 `sum(nums) - min_num` 和 `max_num`的大小,返回较大值即可。 + +## 代码 + +```python +class Solution: + def maxSubarraySumCircular(self, nums: List[int]) -> int: + size = len(nums) + + dp_max, dp_min = nums[0], nums[0] + max_num, min_num = nums[0], nums[0] + for i in range(1, size): + dp_max = max(dp_max + nums[i], nums[i]) + dp_min = min(dp_min + nums[i], nums[i]) + max_num = max(dp_max, max_num) + min_num = min(dp_min, min_num) + sum_num = sum(nums) + if max_num < 0: + return max_num + return max(sum_num - min_num, max_num) +``` + diff --git a/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md b/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md new file mode 100644 index 00000000..63037724 --- /dev/null +++ b/docs/solutions/0900-0999/minimum-add-to-make-parentheses-valid.md @@ -0,0 +1,69 @@ +# [0921. 使括号有效的最少添加](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) + +- 标签:栈、贪心、字符串 +- 难度:中等 + +## 题目链接 + +- [0921. 使括号有效的最少添加 - 力扣](https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid/) + +## 题目大意 + +**描述**:给定一个括号字符串 `s`,可以在字符串的任何位置插入一个括号。 + +**要求**:返回为使结果字符串 `s` 有效而必须添加的最少括号数。 + +**说明**: + +- $1 \le s.length \le 1000$。 +- `s` 只包含 `'('` 和 `')'` 字符。 + +只有满足下面几点之一,括号字符串才是有效的: + +- 它是一个空字符串,或者 +- 它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者 +- 它可以被写作 (A),其中 A 是有效字符串。 + +例如,如果 `s = "()))"`,你可以插入一个开始括号为 `"(()))"` 或结束括号为 `"())))"`。 + +**示例**: + +- 示例 1: + +```python +输入:s = "())" +输出:1 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +为了最终添加的最少括号数,我们应该尽可能将当前能够匹配的括号先进行配对。则剩余的未完成配对的括号数量就是答案。 + +我们使用变量 `left_cnt` 来记录当前左括号的数量。使用 `res` 来记录添加的最少括号数量。 + +- 遍历字符串,判断当前字符。 +- 如果当前字符为左括号 `(`,则令 `left_cnt` 加 `1`。 +- 如果当前字符为右括号 `)`,则令 `left_cnt` 减 `1`。如果 `left_cnt` 减到 `-1`,说明当前有右括号不能完成匹配,则答案数量 `res` 加 `1`,并令 `left_cnt` 重新赋值为 `0`。 +- 遍历完之后,令 `res` 加上剩余不匹配的 `left_cnt` 数量。 +- 最后输出 `res`。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def minAddToMakeValid(self, s: str) -> int: + res = 0 + left_cnt = 0 + for ch in s: + if ch == '(': + left_cnt += 1 + elif ch == ')': + left_cnt -= 1 + if left_cnt == -1: + left_cnt = 0 + res += 1 + res += left_cnt + return res +``` diff --git a/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md b/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md new file mode 100644 index 00000000..fa49023c --- /dev/null +++ b/docs/solutions/0900-0999/minimum-number-of-k-consecutive-bit-flips.md @@ -0,0 +1,98 @@ +# [0995. K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) + +- 标签:位运算、队列、数组、前缀和、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [0995. K 连续位的最小翻转次数 - 力扣](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) + +## 题目大意 + +**描述**:给定一个仅包含 $0$ 和 $1$ 的数组 $nums$,再给定一个整数 $k$。进行一次 $k$ 位翻转包括选择一个长度为 $k$ 的(连续)子数组,同时将子数组中的每个 $0$ 更改为 $1$,而每个 $1$ 更改为 $0$。 + +**要求**:返回所需的 $k$ 位翻转的最小次数,以便数组没有值为 $0$ 的元素。如果不可能,返回 $-1$。 + +**说明**: + +- **子数组**:数组的连续部分。 +- $1 <= nums.length <= 105$。 +- $1 <= k <= nums.length$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [0,1,0], K = 1 +输出:2 +解释:先翻转 A[0],然后翻转 A[2]。 +``` + +- 示例 2: + +```python +输入:nums = [0,0,0,1,0,1,1,0], K = 3 +输出:3 +解释: +翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0] +翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0] +翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1] +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +每次需要翻转的起始位置肯定是遇到第一个元素为 $0$ 的位置开始反转,如果能够使得整个数组不存在 $0$,即返回 $ans$ 作为反转次数。 + +同时我们还可以发现: + +- 如果某个元素反转次数为奇数次,元素会由 $0 \rightarrow 1$,$1 \rightarrow 0$。 +- 如果某个元素反转次数为偶数次,元素不会发生变化。 + +每个第 $i$ 位置上的元素只会被前面 $[i - k + 1, i - 1]$ 的元素影响。所以我们只需要知道前面 $k - 1$ 个元素翻转次数的奇偶性就可以了。 + +同时如果我们知道了前面 $k - 1$ 个元素的翻转次数就可以直接修改 $nums[i]$ 了。 + +我们使用 $flip\underline{\hspace{0.5em}}count$ 记录第 $i$ 个元素之前 $k - 1$ 个位置总共被反转了多少次,或者 $flip\underline{\hspace{0.5em}}count$ 是大小为 $k - 1$ 的滑动窗口。 + +- 如果前面第 $k - 1$ 个元素翻转了奇数次,则如果 $nums[i] == 1$,则 $nums[i]$ 也被翻转成了 $0$,需要再翻转 $1$ 次。 +- 如果前面第 $k - 1$ 个元素翻转了偶数次,则如果 $nums[i] == 0$,则 $nums[i]$ 也被翻转成为了 $0$,需要再翻转 $1$ 次。 + +这两句写成判断语句可以写为:`if (flip_count + nums[i]) % 2 == 0:`。 + +因为 $0 <= nums[i] <= 1$,所以我们可以用 $0$ 和 $1$ 以外的数,比如 $2$ 来标记第 $i$ 个元素发生了翻转,即 `nums[i] = 2`。这样在遍历到第 $i$ 个元素时,如果有 $nums[i - k] == 2$,则说明 $nums[i - k]$ 发生了翻转。同时根据 $flip\underline{\hspace{0.5em}}count$ 和 $nums[i]$ 来判断第 $i$ 位是否需要进行翻转。 + +整个算法的具体步骤如下: + +- 使用 $res$ 记录最小翻转次数。使用 $flip\underline{\hspace{0.5em}}count$ 记录窗口内前 $k - 1 $ 位元素的翻转次数。 +- 遍历数组 $nums$,对于第 $i$ 位元素: + - 如果 $i - k >= 0$,并且 $nums[i - k] == 2$,需要缩小窗口,将翻转次数减一。(此时窗口范围为 $[i - k + 1, i - 1]$)。 + - 如果 $(flip\underline{\hspace{0.5em}}count + nums[i]) \mod 2 == 0$,则说明 $nums[i]$ 还需要再翻转一次,将 $nums[i]$ 标记为 $2$,同时更新窗口内翻转次数 $flip\underline{\hspace{0.5em}}count$ 和答案最小翻转次数 $ans$。 +- 遍历完之后,返回 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def minKBitFlips(self, nums: List[int], k: int) -> int: + ans = 0 + flip_count = 0 + for i in range(len(nums)): + if i - k >= 0 and nums[i - k] == 2: + flip_count -= 1 + if (flip_count + nums[i]) % 2 == 0: + if i + k > len(nums): + return -1 + nums[i] = 2 + flip_count += 1 + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md b/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md new file mode 100644 index 00000000..4b874a4b --- /dev/null +++ b/docs/solutions/0900-0999/most-stones-removed-with-same-row-or-column.md @@ -0,0 +1,124 @@ +# [0947. 移除最多的同行或同列石头](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) + +- 标签:深度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0947. 移除最多的同行或同列石头 - 力扣](https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/) + +## 题目大意 + +**描述**:二维平面中有 $n$ 块石头,每块石头都在整数坐标点上,且每个坐标点上最多只能有一块石头。如果一块石头的同行或者同列上有其他石头存在,那么就可以移除这块石头。 + +给你一个长度为 $n$ 的数组 $stones$ ,其中 $stones[i] = [xi, yi]$ 表示第 $i$ 块石头的位置。 + +**要求**:返回可以移除的石子的最大数量。 + +**说明**: + +- $1 \le stones.length \le 1000$。 +- $0 \le xi, yi \le 10^4$。 +- 不会有两块石头放在同一个坐标点上。 + +**示例**: + +- 示例 1: + +```python +输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]] +输出:5 +解释:一种移除 5 块石头的方法如下所示: +1. 移除石头 [2,2] ,因为它和 [2,1] 同行。 +2. 移除石头 [2,1] ,因为它和 [0,1] 同列。 +3. 移除石头 [1,2] ,因为它和 [1,0] 同行。 +4. 移除石头 [1,0] ,因为它和 [0,0] 同列。 +5. 移除石头 [0,1] ,因为它和 [0,0] 同行。 +石头 [0,0] 不能移除,因为它没有与另一块石头同行/列。 +``` + +- 示例 2: + +```python +输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]] +输出:3 +解释:一种移除 3 块石头的方法如下所示: +1. 移除石头 [2,2] ,因为它和 [2,0] 同行。 +2. 移除石头 [2,0] ,因为它和 [0,0] 同列。 +3. 移除石头 [0,2] ,因为它和 [0,0] 同行。 +石头 [0,0] 和 [1,1] 不能移除,因为它们没有与另一块石头同行/列。 +``` + +## 解题思路 + +### 思路 1:并查集 + +题目「求最多可以移走的石头数目」也可以换一种思路:「求最少留下的石头数目」。 + +- 如果两个石头 $A$、$B$ 处于同一行或者同一列,我们就可以删除石头 $A$ 或 $B$,最少留下 $1$ 个石头。 +- 如果三个石头 $A$、$B$、$C$,其中 $A$、$B$ 处于同一行,$B$、$C$ 处于同一列,则我们可以先删除石头 $A$,再删除石头 $C$,最少留下 $1$ 个石头。 +- 如果有 $n$ 个石头,其中每个石头都有一个同行或者同列的石头,则我们可以将 $n - 1$ 个石头都删除,最少留下 $1$ 个石头。 + +通过上面的分析,我们可以利用并查集,将同行、同列的石头都加入到一个集合中。这样「最少可以留下的石头」就是并查集中集合的个数。 + +则答案为:**最多可以移走的石头数目 = 所有石头个数 - 最少可以留下的石头(并查集的集合个数)**。 + +因为石子坐标是二维的,在使用并查集的时候要区分横纵坐标,因为 $0 <= xi, yi <= 10^4$,可以取 $n = 10010$,将纵坐标映射到 $[n, n + 10000]$ 的范围内,这样就可以得到所有节点的标号。 + +最后计算集合个数,可以使用 set 集合去重,然后统计数量。 + +整体步骤如下: + +1. 定义一个 $10010 \times 2$ 大小的并查集。 +2. 遍历每块石头的横纵坐标: + 1. 将纵坐标映射到 $[10010, 10010 + 10000]$ 的范围内。 + 2. 然后将当前石头的横纵坐标相连接(加入到并查集中)。 +3. 建立一个 set 集合,查找每块石头横坐标所在集合对应的并查集编号,将编号加入到 set 集合中。 +4. 最后,返回「所有石头个数 - 并查集集合个数」即为答案。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def removeStones(self, stones: List[List[int]]) -> int: + size = len(stones) + n = 10010 + union_find = UnionFind(n * 2) + for i in range(size): + union_find.union(stones[i][0], stones[i][1] + n) + + stones_set = set() + for i in range(size): + stones_set.add(union_find.find(stones[i][0])) + + return size - len(stones_set) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \alpha(n))$。其中 $n$ 是石子个数。$\alpha$ 是反 Ackerman 函数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/0900-0999/number-of-recent-calls.md b/docs/solutions/0900-0999/number-of-recent-calls.md new file mode 100644 index 00000000..cfb235d0 --- /dev/null +++ b/docs/solutions/0900-0999/number-of-recent-calls.md @@ -0,0 +1,38 @@ +# [0933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) + +- 标签:设计、队列、数据流 +- 难度:简单 + +## 题目链接 + +- [0933. 最近的请求次数 - 力扣](https://leetcode.cn/problems/number-of-recent-calls/) + +## 题目大意 + +要求:实现一个用来计算特定时间范围内的最近请求的 `RecentCounter` 类: + +- `RecentCounter()` 初始化计数器,请求数为 0 。 +- `int ping(int t)` 在时间 `t` 时添加一个新请求,其中 `t` 表示以毫秒为单位的某个时间,并返回在 `[t-3000, t]` 内发生的请求数。 + +## 解题思路 + +使用一个队列,用于存储 `[t - 3000, t]` 范围内的请求。 + +获取请求数时,将队首所有小于 `t - 3000` 时间的请求将其从队列中移除,然后返回队列的长度即可。 + +## 代码 + +```python +class RecentCounter: + + def __init__(self): + self.queue = [] + + + def ping(self, t: int) -> int: + self.queue.append(t) + while self.queue[0] < t - 3000: + self.queue.pop(0) + return len(self.queue) +``` + diff --git a/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md b/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md new file mode 100644 index 00000000..484d5653 --- /dev/null +++ b/docs/solutions/0900-0999/numbers-at-most-n-given-digit-set.md @@ -0,0 +1,117 @@ +# [0902. 最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) + +- 标签:数组、数学、字符串、二分查找、动态规划 +- 难度:困难 + +## 题目链接 + +- [0902. 最大为 N 的数字组合 - 力扣](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) + +## 题目大意 + +**描述**:给定一个按非递减序列排列的数字数组 $digits$。我们可以使用任意次数的 $digits[i]$ 来写数字。例如,如果 `digits = ["1", "3", "5"]`,我们可以写数字,如 `"13"`, `"551"`, 和 `"1351315"`。 + +**要求**:返回可以生成的小于等于给定整数 $n$ 的正整数个数。 + +**说明**: + +- $1 \le digits.length \le 9$。 +- $digits[i].length == 1$。 +- $digits[i]$ 是从 `'1'` 到 `'9'` 的数。 +- $digits$ 中的所有值都不同。 +- $digits$ 按非递减顺序排列。 +- $1 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:digits = ["1","3","5","7"], n = 100 +输出:20 +解释: +可写出的 20 个数字是: +1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77。 +``` + +- 示例 2: + +```python +输入:digits = ["1","4","9"], n = 1000000000 +输出:29523 +解释: +我们可以写 3 个一位数字,9 个两位数字,27 个三位数字, +81 个四位数字,243 个五位数字,729 个六位数字, +2187 个七位数字,6561 个八位数字和 19683 个九位数字。 +总共,可以使用D中的数字写出 29523 个整数。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +数位 DP 模板的应用。因为这道题目中可以使用任意次数的 $digits[i]$,所以不需要用状态压缩的方式来表示数字集合。 + +这道题的具体步骤如下: + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, True, False)` 开始递归。 `dfs(0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 开始时受到数字 $n$ 对应最高位数位的约束。 + 3. 开始时没有填写数字。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 + 2. 然后枚举 $digits$ 数组中所有能够填入的数字 $d$。 + 3. 如果 $d$ 超过了所能选择的最大数字 $maxX$ 则直接跳出循环。 + 4. 如果 $d$ 是合法数字,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, isLimit and d == maxX, True)`。 + 1. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 + 2. $isNum == True$ 表示 $pos$ 位选择了数字。 +6. 最后的方案数为 `dfs(0, True, False)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def atMostNGivenDigitSet(self, digits: List[str], n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, False, False) + + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = s[pos] if isLimit else '9' + + # 枚举可选择的数字 + for d in digits: + if d > maxX: + break + ans += dfs(pos + 1, isLimit and d == maxX, True) + + return ans + + return dfs(0, True, False) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times \log n)$,其中 $m$ 是数组 $digits$ 的长度,$\log n$ 是 $n$ 转为字符串之后的位数长度。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0900-0999/online-stock-span.md b/docs/solutions/0900-0999/online-stock-span.md new file mode 100644 index 00000000..b0fc36d8 --- /dev/null +++ b/docs/solutions/0900-0999/online-stock-span.md @@ -0,0 +1,51 @@ +# [0901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) + +- 标签:栈、设计、数据流、单调栈 +- 难度:中等 + +## 题目链接 + +- [0901. 股票价格跨度 - 力扣](https://leetcode.cn/problems/online-stock-span/) + +## 题目大意 + +要求:编写一个 `StockSpanner` 类,用于收集某些股票的每日报价,并返回该股票当日价格的跨度。 + +- 今天股票价格的跨度:股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。 + +例如:如果未来 7 天股票的价格是 `[100, 80, 60, 70, 60, 75, 85]`,那么股票跨度将是 `[1, 1, 1, 2, 1, 4, 6]`。 + +## 解题思路 + +「求解小于或等于今天价格的最大连续日」等价于「求出左侧第一个比当前股票价格大的股票,并计算距离」。求出左侧第一个比当前股票价格大的股票我们可以使用「单调递减栈」来做。具体步骤如下: + +- 初始化方法:初始化一个空栈,即 `self.stack = []` + +- 求解今天股票价格的跨度: + + - 初始化跨度 `span` 为 `1`。 + - 如果今日股票价格 `price` 大于等于栈顶元素 `self.stack[-1][0]`,则: + - 将其弹出,即 `top = self.stack.pop()`。 + - 跨度累加上弹出栈顶元素的跨度,即 `span += top[1]`。 + - 继续判断,直到遇到一个今日股票价格 `price` 小于栈顶元素的元素位置,再将 `[price, span]` 压入栈中。 + - 如果今日股票价格 `price` 小于栈顶元素 `self.stack[-1][0]`,则直接将 `[price, span]` 压入栈中。 + + - 最后输出今天股票价格的跨度 `span`。 + +## 代码 + +```python +class StockSpanner: + + def __init__(self): + self.stack = [] + + def next(self, price: int) -> int: + span = 1 + while self.stack and price >= self.stack[-1][0]: + top = self.stack.pop() + span += top[1] + self.stack.append([price, span]) + return span +``` + diff --git a/docs/solutions/0900-0999/range-sum-of-bst.md b/docs/solutions/0900-0999/range-sum-of-bst.md new file mode 100644 index 00000000..1e517fd4 --- /dev/null +++ b/docs/solutions/0900-0999/range-sum-of-bst.md @@ -0,0 +1,42 @@ +# [0938. 二叉搜索树的范围和](https://leetcode.cn/problems/range-sum-of-bst/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [0938. 二叉搜索树的范围和 - 力扣](https://leetcode.cn/problems/range-sum-of-bst/) + +## 题目大意 + +给定一个二叉搜索树,和一个范围 [low, high]。求范围 [low, high] 之间所有节点的值的和。 + +## 解题思路 + +二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +这道题求解 [low, high] 之间所有节点的值的和,需要递归求解。 + +- 当前节点为 None 时返回 0; +- 当前节点值 val > high 时,则返回左子树之和; +- 当前节点值 val < low 时,则返回右子树之和; +- 当前节点 val <= high,且 val >= low 时,则返回当前节点值 + 左子树之和 + 右子树之和。 + +## 代码 + +```python +class Solution: + def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int: + if not root: + return 0 + if root.val > high: + return self.rangeSumBST(root.left, low, high) + if root.val < low: + return self.rangeSumBST(root.right, low, high) + return root.val + self.rangeSumBST(root.left, low, high) + self.rangeSumBST(root.right, low, high) +``` + diff --git a/docs/solutions/0900-0999/regions-cut-by-slashes.md b/docs/solutions/0900-0999/regions-cut-by-slashes.md new file mode 100644 index 00000000..97403730 --- /dev/null +++ b/docs/solutions/0900-0999/regions-cut-by-slashes.md @@ -0,0 +1,132 @@ +# [0959. 由斜杠划分区域](https://leetcode.cn/problems/regions-cut-by-slashes/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [0959. 由斜杠划分区域 - 力扣](https://leetcode.cn/problems/regions-cut-by-slashes/) + +## 题目大意 + +**描述**:在由 $1 \times 1$ 方格组成的 $n \times n$ 网格 $grid$ 中,每个 $1 \times 1$ 方块由 `'/'`、`'\'` 或 `' '` 构成。这些字符会将方块划分为一些共边的区域。 + +现在给定代表网格的二维数组 $grid$。 + +**要求**:返回区域的数目。 + +**说明**: + +- 反斜杠字符是转义的,因此 `'\'` 用 `'\\'` 表示。 +- $n == grid.length == grid[i].length$。 +- $1 \le n \le 30$。 +- $grid[i][j]$ 是 `'/'`、`'\'` 或 `' '`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2018/12/15/1.png) + +```python +输入:grid = [" /","/ "] +输出:2 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2018/12/15/4.png) + +```python +输入:grid = ["/\\","\\/"] +输出:5 +解释:回想一下,因为 \ 字符是转义的,所以 "/\\" 表示 /\,而 "\\/" 表示 \/。 +``` + +## 解题思路 + +### 思路 1:并查集 + +我们把一个 $1 \times 1$ 的单元格分割成逻辑上的 $4$ 个部分,则 `' '`、`'/'`、`'\'` 可以将 $1 \times 1$ 的方格分割为以下三种形态: + +![](http://qcdn.itcharge.cn/images/20210827142447.png) + +在进行遍历的时候,需要将联通的部分进行合并,并统计出联通的块数。这就需要用到了并查集。 + +遍历二维数组 $gird$,然后在「单元格内」和「单元格间」进行合并。 + +现在我们为单元格的每个小三角部分按顺时针方向都编上编号,起始位置为左边。然后单元格间的编号按照从左到右,从上到下的位置进行编号,如下图所示: + +![](http://qcdn.itcharge.cn/images/20210827143836.png) + +假设当前单元格的起始位置为 $index$,则合并策略如下: + +- 如果是单元格内: + - 如果是空格:合并 $index$、$index + 1$、$index + 2$、$index + 3$。 + - 如果是 `'/'`:合并 $index$ 和 $index + 1$,合并 $index + 2$ 和 $index + 3$。 + - 如果是 `'\'`:合并 $index$ 和 $index + 3$,合并 $index + 1$ 和 $index + 2$。 +- 如果是单元格间,则向下向右进行合并: + - 向下:合并 $index + 3$ 和 $index + 4 * size + 1 $。 + - 向右:合并 $index + 2$ 和 $index + 4$。 + +最后合并完成之后,统计并查集中连通分量个数即为答案。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def regionsBySlashes(self, grid: List[str]) -> int: + size = len(grid) + m = 4 * size * size + union_find = UnionFind(m) + for i in range(size): + for j in range(size): + index = 4 * (i * size + j) + ch = grid[i][j] + if ch == '/': + union_find.union(index, index + 1) + union_find.union(index + 2, index + 3) + elif ch == '\\': + union_find.union(index, index + 3) + union_find.union(index + 1, index + 2) + else: + union_find.union(index, index + 1) + union_find.union(index + 1, index + 2) + union_find.union(index + 2, index + 3) + if j + 1 < size: + union_find.union(index + 2, index + 4) + if i + 1 < size: + union_find.union(index + 3, index + 4 * size + 1) + + return union_find.count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times \alpha(n^2))$,其中 $\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/0900-0999/rle-iterator.md b/docs/solutions/0900-0999/rle-iterator.md new file mode 100644 index 00000000..5b0aa72f --- /dev/null +++ b/docs/solutions/0900-0999/rle-iterator.md @@ -0,0 +1,92 @@ +# [0900. RLE 迭代器](https://leetcode.cn/problems/rle-iterator/) + +- 标签:设计、数组、计数、迭代器 +- 难度:中等 + +## 题目链接 + +- [0900. RLE 迭代器 - 力扣](https://leetcode.cn/problems/rle-iterator/) + +## 题目大意 + +**描述**:我们可以使用游程编码(即 RLE)来编码一个整数序列。在偶数长度 $encoding$ ( 从 $0$ 开始 )的游程编码数组中,对于所有偶数 $i$,$encoding[i]$ 告诉我们非负整数 $encoding[i + 1]$ 在序列中重复的次数。 + +- 例如,序列 $arr = [8,8,8,5,5]$ 可以被编码为 $encoding =[3,8,2,5]$。$encoding =[3,8,0,9,2,5]$ 和 $encoding =[2,8,1,8,2,5]$ 也是 $arr$ 有效的 RLE。 + +给定一个游程长度的编码数组 $encoding$。 + +**要求**:设计一个迭代器来遍历它。 + +实现 `RLEIterator` 类: + +- `RLEIterator(int[] encoded)` 用编码后的数组初始化对象。 +- `int next(int n)` 以这种方式耗尽后 $n$ 个元素并返回最后一个耗尽的元素。如果没有剩余的元素要耗尽,则返回 $-1$。 + +**说明**: + +- $2 \le encoding.length \le 1000$。 +- $encoding.length$ 为偶。 +- $0 \le encoding[i] \le 10^9$。 +- $1 \le n \le 10^9$。 +- 每个测试用例调用 `next` 不高于 $1000$ 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["RLEIterator","next","next","next","next"] +[[[3,8,0,9,2,5]],[2],[1],[1],[2]] +输出: +[null,8,8,5,-1] +解释: +RLEIterator rLEIterator = new RLEIterator([3, 8, 0, 9, 2, 5]); // 这映射到序列 [8,8,8,5,5]。 +rLEIterator.next(2); // 耗去序列的 2 个项,返回 8。现在剩下的序列是 [8, 5, 5]。 +rLEIterator.next(1); // 耗去序列的 1 个项,返回 8。现在剩下的序列是 [5, 5]。 +rLEIterator.next(1); // 耗去序列的 1 个项,返回 5。现在剩下的序列是 [5]。 +rLEIterator.next(2); // 耗去序列的 2 个项,返回 -1。 这是由于第一个被耗去的项是 5, +但第二个项并不存在。由于最后一个要耗去的项不存在,我们返回 -1。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 初始化时: + 1. 保存数组 $encoding$ 作为成员变量。 + 2. 保存当前位置 $index$,表示当前迭代器指向元素 $encoding[index + 1]$。初始化赋值为 $0$。 + 3. 保存当前指向元素 $encoding[index + 1]$ 已经被删除的元素个数 $d\underline{\hspace{0.5em}}cnt$。初始化赋值为 $0$。 +2. 调用 `next(n)` 时: + 1. 对于当前元素,先判断当前位置是否超出 $encoding$ 范围,超过则直接返回 $-1$。 + 2. 如果未超过,再判断当前元素剩余个数 $encoding[index] - d\underline{\hspace{0.5em}}cnt$ 是否小于 $n$ 个。 + 1. 如果小于 $n$ 个,则删除当前元素剩余所有个数,并指向下一位置继续删除剩余元素。 + 2. 如果等于大于等于 $n$ 个,则令当前指向元素 $encoding[index + 1]$ 已经被删除的元素个数 $d\underline{\hspace{0.5em}}cnt$ 加上 $n$。 + +### 思路 1:代码 + +```Python +class RLEIterator: + + def __init__(self, encoding: List[int]): + self.encoding = encoding + self.index = 0 + self.d_cnt = 0 + + def next(self, n: int) -> int: + while self.index < len(self.encoding): + if self.d_cnt + n > self.encoding[self.index]: + n -= self.encoding[self.index] - self.d_cnt + self.d_cnt = 0 + self.index += 2 + else: + self.d_cnt += n + return self.encoding[self.index + 1] + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 为数组 $encoding$ 的长度,$m$ 是调用 `next(n)` 的次数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0900-0999/satisfiability-of-equality-equations.md b/docs/solutions/0900-0999/satisfiability-of-equality-equations.md new file mode 100644 index 00000000..53e50e3b --- /dev/null +++ b/docs/solutions/0900-0999/satisfiability-of-equality-equations.md @@ -0,0 +1,91 @@ +# [0990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) + +- 标签:并查集、图、数组、字符串 +- 难度:中等 + +## 题目链接 + +- [0990. 等式方程的可满足性 - 力扣](https://leetcode.cn/problems/satisfiability-of-equality-equations/) + +## 题目大意 + +**描述**:给定一个由字符串方程组成的数组 `equations`,每个字符串方程 `equations[i]` 的长度为 `4`,有以下两种形式组成:`a==b` 或 `a!=b`。`a` 和 `b` 是小写字母,表示单字母变量名。 + +**要求**:判断所有的字符串方程是否能同时满足,如果能同时满足,返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le equations.length \le 500$。 +- $equations[i].length == 4$。 +- $equations[i][0]$ 和 $equations[i][3]$ 是小写字母。 +- $equations[i][1]$ 要么是 `'='`,要么是 `'!'`。 +- `equations[i][2]` 是 `'='`。 + +**示例**: + +- 示例 1: + +```python +输入:["a==b","b!=a"] +输出:False +解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。 +``` + +## 解题思路 + +### 思路 1:并查集 + +字符串方程只有 `==` 或者 `!=`,可以考虑将相等的遍历划分到相同集合中,然后再遍历所有不等式方程,看方程的两个变量是否在之前划分的相同集合中,如果在则说明不满足。 + +这就需要用到并查集,具体操作如下: + +- 遍历所有等式方程,将等式两边的单字母变量顶点进行合并。 +- 遍历所有不等式方程,检查不等式两边的单字母遍历是不是在一个连通分量中,如果在则返回 `False`,否则继续扫描。如果所有不等式检查都没有矛盾,则返回 `True`。 + +### 思路 1:并查集代码 + +```python +class UnionFind: + def __init__(self, n): # 初始化 + self.fa = [i for i in range(n)] # 每个元素的集合编号初始化为数组 fa 的下标索引 + + def __find(self, x): # 查找元素根节点的集合编号内部实现方法 + while self.fa[x] != x: # 递归查找元素的父节点,直到根节点 + self.fa[x] = self.fa[self.fa[x]] # 隔代压缩优化 + x = self.fa[x] + return x # 返回元素根节点的集合编号 + + def union(self, x, y): # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点 + root_x = self.__find(x) + root_y = self.__find(y) + if root_x == root_y: # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合 + return False + + self.fa[root_x] = root_y # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点 + return True + + def is_connected(self, x, y): # 查询操作:判断 x 和 y 是否同属于一个集合 + return self.__find(x) == self.__find(y) + +class Solution: + def equationsPossible(self, equations: List[str]) -> bool: + union_find = UnionFind(26) + for eqation in equations: + if eqation[1] == "=": + index1 = ord(eqation[0]) - 97 + index2 = ord(eqation[3]) - 97 + union_find.union(index1, index2) + + for eqation in equations: + if eqation[1] == "!": + index1 = ord(eqation[0]) - 97 + index2 = ord(eqation[3]) - 97 + if union_find.is_connected(index1, index2): + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + C \times \log C)$。其中 $n$ 是方程组 $equations$ 中的等式数量。$C$ 是字母变量的数量。本题中变量都是小写字母,即 $C \le 26$。 +- **空间复杂度**:$O(C)$。 \ No newline at end of file diff --git a/docs/solutions/0900-0999/smallest-range-i.md b/docs/solutions/0900-0999/smallest-range-i.md new file mode 100644 index 00000000..95852a7c --- /dev/null +++ b/docs/solutions/0900-0999/smallest-range-i.md @@ -0,0 +1,58 @@ +# [0908. 最小差值 I](https://leetcode.cn/problems/smallest-range-i/) + +- 标签:数组、数学 +- 难度:简单 + +## 题目链接 + +- [0908. 最小差值 I - 力扣](https://leetcode.cn/problems/smallest-range-i/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums`,和一个整数 `k`。给数组中的每个元素 `nums[i]` 都加上一个任意数字 `x` (`-k <= x <= k`),从而得到一个新数组 `result`。 + +**要求**:返回数组 `result` 的最大值和最小值之间可能存在的最小差值。 + +**说明**: + +- $1 \le nums.length \le 10^4$。 +- $0 \le nums[i] \le 10^4$。 +- $0 \le k \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1], k = 0 +输出:0 +解释:分数是 max(nums) - min(nums) = 1 - 1 = 0。 +``` + +- 示例 2: + +```python +输入:nums = [0,10], k = 2 +输出:6 +解释:将 nums 改为 [2,8]。分数是 max(nums) - min(nums) = 8 - 2 = 6。 +``` + +## 解题思路 + +### 思路 1:数学 + +`nums` 中的每个元素可以波动 `[-k, k]`。最小的差值就是「最大值减去 `k`」和「最小值加上 `k`」之间的差值。而如果差值小于 `0`,则说明每个数字都可以波动成相等的数字,此时直接返回 `0` 即可。 + +### 思路 1:代码 + +```python +class Solution: + def smallestRangeI(self, nums: List[int], k: int) -> int: + return max(0, max(nums) - min(nums) - 2*k) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/0900-0999/sort-an-array.md b/docs/solutions/0900-0999/sort-an-array.md new file mode 100644 index 00000000..c8840726 --- /dev/null +++ b/docs/solutions/0900-0999/sort-an-array.md @@ -0,0 +1,598 @@ +# [0912. 排序数组](https://leetcode.cn/problems/sort-an-array/) + +- 标签:数组、分治、桶排序、计数排序、基数排序、排序、堆(优先队列)、归并排序 +- 难度:中等 + +## 题目链接 + +- [0912. 排序数组 - 力扣](https://leetcode.cn/problems/sort-an-array/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:将该数组升序排列。 + +**说明**: + +- $1 \le nums.length \le 5 * 10^4$。 +- $-5 * 10^4 \le nums[i] \le 5 * 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [5,2,3,1] +输出:[1,2,3,5] +``` + +- 示例 2: + +```python +输入:nums = [5,1,1,2,0,0] +输出:[0,0,1,1,2,5] +``` + +## 解题思路 + +这道题是一道用来复习排序算法,测试算法时间复杂度的好题。我试过了十种排序算法。得到了如下结论: + +- 超时算法(时间复杂度为 $O(n^2)$):冒泡排序、选择排序、插入排序。 +- 通过算法(时间复杂度为 $O(n \times \log n)$):希尔排序、归并排序、快速排序、堆排序。 +- 通过算法(时间复杂度为 $O(n)$):计数排序、桶排序。 +- 解答错误算法(普通基数排序只适合非负数):基数排序。 + +### 思路 1:冒泡排序(超时) + +> **冒泡排序(Bubble Sort)基本思想**:经过多次迭代,通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 + +假设数组的元素个数为 $n$ 个,则冒泡排序的算法步骤如下: + +1. 第 $1$ 趟「冒泡」:对前 $n$ 个元素执行「冒泡」,从而使第 $1$ 个值最大的元素放置在正确位置上。 + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换。 + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,如果前者大于后者,则两者交换位置,否则不交换。 + 3. 依次类推,直到第 $n - 1$ 个元素与第 $n$ 个元素比较(或交换)为止。 + 4. 经过第 $1$ 趟排序,使得 $n$ 个元素中第 $i$ 个值最大元素被安置在第 $n$ 个位置上。 +2. 第 $2$ 趟「冒泡」:对前 $n - 1$ 个元素执行「冒泡」,从而使第 $2$ 个值最大的元素放置在正确位置上。 + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换。 + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,若前者大于后者,则两者交换位置,否则不交换。 + 3. 依次类推,直到第 $n - 2$ 个元素与第 $n - 1$ 个元素比较(或交换)为止。 + 4. 经过第 $2$ 趟排序,使得数组中第 $2$ 个值最大元素被安置在第 $n$ 个位置上。 +3. 依次类推,重复上述「冒泡」过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 + +### 思路 1:代码 + +```python +class Solution: + def bubbleSort(self, nums: [int]) -> [int]: + # 第 i 趟「冒泡」 + for i in range(len(nums) - 1): + flag = False # 是否发生交换的标志位 + # 对数组未排序区间 [0, n - i - 1] 的元素执行「冒泡」 + for j in range(len(nums) - i - 1): + # 相邻两个元素进行比较,如果前者大于后者,则交换位置 + if nums[j] > nums[j + 1]: + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True + if not flag: # 此趟遍历未交换任何元素,直接跳出 + break + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.bubbleSort(nums) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:选择排序(超时) + +>**选择排序(Selection Sort)基本思想**:将数组分为两个区间,左侧为已排序区间,右侧为未排序区间。每趟从未排序区间中选择一个值最小的元素,放到已排序区间的末尾,从而将该元素划分到已排序区间。 + +假设数组的元素个数为 $n$ 个,则选择排序的算法步骤如下: + +1. 初始状态下,无已排序区间,未排序区间为 $[0, n - 1]$。 +2. 第 $1$ 趟选择: + 1. 遍历未排序区间 $[0, n - 1]$,使用变量 $min\underline{\hspace{0.5em}}i$ 记录区间中值最小的元素位置。 + 2. 将 $min\underline{\hspace{0.5em}}i$ 与下标为 $0$ 处的元素交换位置。如果下标为 $0$ 处元素就是值最小的元素位置,则不用交换。 + 3. 此时,$[0, 0]$ 为已排序区间,$[1, n - 1]$(总共 $n - 1$ 个元素)为未排序区间。 +3. 第 $2$ 趟选择: + 1. 遍历未排序区间 $[1, n - 1]$,使用变量 $min\underline{\hspace{0.5em}}i$ 记录区间中值最小的元素位置。 + 2. 将 $min\underline{\hspace{0.5em}}i$ 与下标为 $1$ 处的元素交换位置。如果下标为 $1$ 处元素就是值最小的元素位置,则不用交换。 + 3. 此时,$[0, 1]$ 为已排序区间,$[2, n - 1]$(总共 $n - 2$ 个元素)为未排序区间。 +4. 依次类推,对剩余未排序区间重复上述选择过程,直到所有元素都划分到已排序区间,排序结束。 + +### 思路 2:代码 + +```python +class Solution: + def selectionSort(self, nums: [int]) -> [int]: + for i in range(len(nums) - 1): + # 记录未排序区间中最小值的位置 + min_i = i + for j in range(i + 1, len(nums)): + if nums[j] < nums[min_i]: + min_i = j + # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 + if i != min_i: + nums[i], nums[min_i] = nums[min_i], nums[i] + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.selectionSort(nums) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 3:插入排序(超时) + +>**插入排序(Insertion Sort)基本思想**:将数组分为两个区间,左侧为有序区间,右侧为无序区间。每趟从无序区间取出一个元素,然后将其插入到有序区间的适当位置。 + +假设数组的元素个数为 $n$ 个,则插入排序的算法步骤如下: + +1. 初始状态下,有序区间为 $[0, 0]$,无序区间为 $[1, n - 1]$。 +2. 第 $1$ 趟插入: + 1. 取出无序区间 $[1, n - 1]$ 中的第 $1$ 个元素,即 $nums[1]$。 + 2. 从右到左遍历有序区间中的元素,将比 $nums[1]$ 小的元素向后移动 $1$ 位。 + 3. 如果遇到大于或等于 $nums[1]$ 的元素时,说明找到了插入位置,将 $nums[1]$ 插入到该位置。 + 4. 插入元素后有序区间变为 $[0, 1]$,无序区间变为 $[2, n - 1]$。 +3. 第 $2$ 趟插入: + 1. 取出无序区间 $[2, n - 1]$ 中的第 $1$ 个元素,即 $nums[2]$。 + 2. 从右到左遍历有序区间中的元素,将比 $nums[2]$ 小的元素向后移动 $1$ 位。 + 3. 如果遇到大于或等于 $nums[2]$ 的元素时,说明找到了插入位置,将 $nums[2]$ 插入到该位置。 + 4. 插入元素后有序区间变为 $[0, 2]$,无序区间变为 $[3, n - 1]$。 +4. 依次类推,对剩余无序区间中的元素重复上述插入过程,直到所有元素都插入到有序区间中,排序结束。 + +### 思路 3:代码 + +```python +class Solution: + def insertionSort(self, nums: [int]) -> [int]: + # 遍历无序区间 + for i in range(1, len(nums)): + temp = nums[i] + j = i + # 从右至左遍历有序区间 + while j > 0 and nums[j - 1] > temp: + # 将有序区间中插入位置右侧的所有元素依次右移一位 + nums[j] = nums[j - 1] + j -= 1 + # 将该元素插入到适当位置 + nums[j] = temp + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.insertionSort(nums) +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 4:希尔排序(通过) + +> **希尔排序(Shell Sort)基本思想**:将整个数组切按照一定的间隔取值划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 $1$,对整个数组进行插入排序。 + +假设数组的元素个数为 $n$ 个,则希尔排序的算法步骤如下: + +1. 确定一个元素间隔数 $gap$。 +2. 将参加排序的数组按此间隔数从第 $1$ 个元素开始一次分成若干个子数组,即分别将所有位置相隔为 $gap$ 的元素视为一个子数组。 +3. 在各个子数组中采用某种排序算法(例如插入排序算法)进行排序。 +4. 减少间隔数,并重新将整个数组按新的间隔数分成若干个子数组,再分别对各个子数组进行排序。 +5. 依次类推,直到间隔数 $gap$ 值为 $1$,最后进行一次排序,排序结束。 + +### 思路 4:代码 + +```python +class Solution: + def shellSort(self, nums: [int]) -> [int]: + size = len(nums) + gap = size // 2 + # 按照 gap 分组 + while gap > 0: + # 对每组元素进行插入排序 + for i in range(gap, size): + # temp 为每组中无序数组第 1 个元素 + temp = nums[i] + j = i + # 从右至左遍历每组中的有序数组元素 + while j >= gap and nums[j - gap] > temp: + # 将每组有序数组中插入位置右侧的元素依次在组中右移一位 + nums[j] = nums[j - gap] + j -= gap + # 将该元素插入到适当位置 + nums[j] = temp + # 缩小 gap 间隔 + gap = gap // 2 + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.shellSort(nums) +``` + +### 思路 4:复杂度分析 + +- **时间复杂度**:介于 $O(n \times \log n)$ 与 $O(n^2)$ 之间。 +- **空间复杂度**:$O(1)$。 + +### 思路 5:归并排序(通过) + +> **归并排序(Merge Sort)基本思想**:采用经典的分治策略,先递归地将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。 + +假设数组的元素个数为 $n$ 个,则归并排序的算法步骤如下: + +1. **分解过程**:先递归地将当前数组平均分成两半,直到子数组长度为 $1$。 + 1. 找到数组中心位置 $mid$,从中心位置将数组分成左右两个子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$。 + 2. 对左右两个子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$ 分别进行递归分解。 + 3. 最终将数组分解为 $n$ 个长度均为 $1$ 的有序子数组。 +2. **归并过程**:从长度为 $1$ 的有序子数组开始,依次将有序数组两两合并,直到合并成一个长度为 $n$ 的有序数组。 + 1. 使用数组变量 $nums$ 存放合并后的有序数组。 + 2. 使用两个指针 $left\underline{\hspace{0.5em}}i$、$right\underline{\hspace{0.5em}}i$ 分别指向两个有序子数组 $left\underline{\hspace{0.5em}}nums$、$right\underline{\hspace{0.5em}}nums$ 的开始位置。 + 3. 比较两个指针指向的元素,将两个有序子数组中较小元素依次存入到结果数组 $nums$ 中,并将指针移动到下一位置。 + 4. 重复步骤 $3$,直到某一指针到达子数组末尾。 + 5. 将另一个子数组中的剩余元素存入到结果数组 $nums$ 中。 + 6. 返回合并后的有序数组 $nums$。 + +### 思路 5:代码 + +```python +class Solution: + # 合并过程 + def merge(self, left_nums: [int], right_nums: [int]): + nums = [] + left_i, right_i = 0, 0 + while left_i < len(left_nums) and right_i < len(right_nums): + # 将两个有序子数组中较小元素依次插入到结果数组中 + if left_nums[left_i] < right_nums[right_i]: + nums.append(left_nums[left_i]) + left_i += 1 + else: + nums.append(right_nums[right_i]) + right_i += 1 + + # 如果左子数组有剩余元素,则将其插入到结果数组中 + while left_i < len(left_nums): + nums.append(left_nums[left_i]) + left_i += 1 + + # 如果右子数组有剩余元素,则将其插入到结果数组中 + while right_i < len(right_nums): + nums.append(right_nums[right_i]) + right_i += 1 + + # 返回合并后的结果数组 + return nums + + # 分解过程 + def mergeSort(self, nums: [int]) -> [int]: + # 数组元素个数小于等于 1 时,直接返回原数组 + if len(nums) <= 1: + return nums + + mid = len(nums) // 2 # 将数组从中间位置分为左右两个数组 + left_nums = self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序 + right_nums = self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序 + return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上,进行两两合并 + + def sortArray(self, nums: [int]) -> [int]: + return self.mergeSort(nums) +``` + +### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 6:快速排序(通过) + +> **快速排序(Quick Sort)基本思想**:采用经典的分治策略,选择数组中某个元素作为基准数,通过一趟排序将数组分为独立的两个子数组,一个子数组中所有元素值都比基准数小,另一个子数组中所有元素值都比基准数大。然后再按照同样的方式递归的对两个子数组分别进行快速排序,以达到整个数组有序。 + +假设数组的元素个数为 $n$ 个,则快速排序的算法步骤如下: + +1. **哨兵划分**:选取一个基准数,将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。 + 1. 从当前数组中找到一个基准数 $pivot$(这里以当前数组第 $1$ 个元素作为基准数,即 $pivot = nums[low]$)。 + 2. 使用指针 $i$ 指向数组开始位置,指针 $j$ 指向数组末尾位置。 + 3. 从右向左移动指针 $j$,找到第 $1$ 个小于基准值的元素。 + 4. 从左向右移动指针 $i$,找到第 $1$ 个大于基准数的元素。 + 5. 交换指针 $i$、指针 $j$ 指向的两个元素位置。 + 6. 重复第 $3 \sim 5$ 步,直到指针 $i$ 和指针 $j$ 相遇时停止,最后将基准数放到两个子数组交界的位置上。 +2. **递归分解**:完成哨兵划分之后,对划分好的左右子数组分别进行递归排序。 + 1. 按照基准数的位置将数组拆分为左右两个子数组。 + 2. 对每个子数组分别重复「哨兵划分」和「递归分解」,直到各个子数组只有 $1$ 个元素,排序结束。 + +### 思路 6:代码 + +```python +import random + +class Solution: + # 随机哨兵划分:从 nums[low: high + 1] 中随机挑选一个基准数,并进行移位排序 + def randomPartition(self, nums: [int], low: int, high: int) -> int: + # 随机挑选一个基准数 + i = random.randint(low, high) + # 将基准数与最低位互换 + nums[i], nums[low] = nums[low], nums[i] + # 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + return self.partition(nums, low, high) + + # 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上 + def partition(self, nums: [int], low: int, high: int) -> int: + # 以第 1 位元素为基准数 + pivot = nums[low] + + i, j = low, high + while i < j: + # 从右向左找到第 1 个小于基准数的元素 + while i < j and nums[j] >= pivot: + j -= 1 + # 从左向右找到第 1 个大于基准数的元素 + while i < j and nums[i] <= pivot: + i += 1 + # 交换元素 + nums[i], nums[j] = nums[j], nums[i] + + # 将基准数放到正确位置上 + nums[j], nums[low] = nums[low], nums[j] + return j + + def quickSort(self, nums: [int], low: int, high: int) -> [int]: + if low < high: + # 按照基准数的位置,将数组划分为左右两个子数组 + pivot_i = self.partition(nums, low, high) + # 对左右两个子数组分别进行递归快速排序 + self.quickSort(nums, low, pivot_i - 1) + self.quickSort(nums, pivot_i + 1, high) + + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.quickSort(nums, 0, len(nums) - 1) +``` + +### 思路 6:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 7:堆排序(通过) + +> **堆排序(Heap sort)基本思想**:借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 + +假设数组的元素个数为 $n$ 个,则堆排序的算法步骤如下: + +1. **构建初始大顶堆**: + 1. 定义一个数组实现的堆结构,将原始数组的元素依次存入堆结构的数组中(初始顺序不变)。 + 2. 从数组的中间位置开始,从右至左,依次通过「下移调整」将数组转换为一个大顶堆。 + +2. **交换元素,调整堆**: + 1. 交换堆顶元素(第 $1$ 个元素)与末尾(最后 $1$ 个元素)的位置,交换完成后,堆的长度减 $1$。 + 2. 交换元素之后,由于堆顶元素发生了改变,需要从根节点开始,对当前堆进行「下移调整」,使其保持堆的特性。 + +3. **重复交换和调整堆**: + 1. 重复第 $2$ 步,直到堆的大小为 $1$ 时,此时大顶堆的数组已经完全有序。 + +### 思路 7:代码 + +```python +class Solution: + # 调整为大顶堆 + def heapify(self, arr, index, end): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if arr[left] > arr[max_index]: + max_index = left + if right <= end and arr[right] > arr[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + arr[index], arr[max_index] = arr[max_index], arr[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(self, arr): + size = len(arr) + # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + self.heapify(arr, i, size - 1) + return arr + + # 升序堆排序,思路如下: + # 1. 先建立大顶堆 + # 2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值 + # 3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值 + # 4. 以此类推,直到最后一个元素交换之后完毕。 + def maxHeapSort(self, arr): + self.buildMaxHeap(arr) + size = len(arr) + for i in range(size): + arr[0], arr[size-i-1] = arr[size-i-1], arr[0] + self.heapify(arr, 0, size-i-2) + return arr + + def sortArray(self, nums: List[int]) -> List[int]: + return self.maxHeapSort(nums) +``` + +### 思路 7:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 8:计数排序(通过) + +> **计数排序(Counting Sort)基本思想**:通过统计数组中每个元素在数组中出现的次数,根据这些统计信息将数组元素有序的放置到正确位置,从而达到排序的目的。 + +假设数组的元素个数为 $n$ 个,则计数排序的算法步骤如下: + +1. **计算排序范围**:遍历数组,找出待排序序列中最大值元素 $nums\underline{\hspace{0.5em}}max$ 和最小值元素 $nums\underline{\hspace{0.5em}}min$,计算出排序范围为 $nums\underline{\hspace{0.5em}}max - nums\underline{\hspace{0.5em}}min + 1$。 +2. **定义计数数组**:定义一个大小为排序范围的计数数组 $counts$,用于统计每个元素的出现次数。其中: + 1. 数组的索引值 $num - nums\underline{\hspace{0.5em}}min$ 表示元素的值为 $num$。 + 2. 数组的值 $counts[num - nums\underline{\hspace{0.5em}}min]$ 表示元素 $num$ 的出现次数。 + +3. **对数组元素进行计数统计**:遍历待排序数组 $nums$,对每个元素在计数数组中进行计数,即将待排序数组中「每个元素值减去最小值」作为索引,将「对计数数组中的值」加 $1$,即令 $counts[num - nums\underline{\hspace{0.5em}}min]$ 加 $1$。 +4. **生成累积计数数组**:从 $counts$ 中的第 $1$ 个元素开始,每一项累家前一项和。此时 $counts[num - nums\underline{\hspace{0.5em}}min]$ 表示值为 $num$ 的元素在排序数组中最后一次出现的位置。 +5. **逆序填充目标数组**:逆序遍历数组 $nums$,将每个元素 $num$ 填入正确位置。 + 6. 将其填充到结果数组 $res$ 的索引 $counts[num - nums\underline{\hspace{0.5em}}min]$ 处。 + 7. 放入后,令累积计数数组中对应索引减 $1$,从而得到下个元素 $num$ 的放置位置。 + +### 思路 8:代码 + +```python +class Solution: + def countingSort(self, nums: [int]) -> [int]: + # 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min + nums_min, nums_max = min(nums), max(nums) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 + size = nums_max - nums_min + 1 + counts = [0 for _ in range(size)] + + # 统计值为 num 的元素出现的次数 + for num in nums: + counts[num - nums_min] += 1 + + # 生成累积计数数组 + for i in range(1, size): + counts[i] += counts[i - 1] + + # 反向填充目标数组 + res = [0 for _ in range(len(nums))] + for i in range(len(nums) - 1, -1, -1): + num = nums[i] + # 根据累积计数数组,将 num 放在数组对应位置 + res[counts[num - nums_min] - 1] = num + # 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置 + counts[nums[i] - nums_min] -= 1 + + return res + + def sortArray(self, nums: [int]) -> [int]: + return self.countingSort(nums) +``` + +### 思路 8:复杂度分析 + +- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序序列的值域。 +- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。 + +### 思路 9:桶排序(通过) + +> **桶排序(Bucket Sort)基本思想**:将待排序数组中的元素分散到若干个「桶」中,然后对每个桶中的元素再进行单独排序。 + +假设数组的元素个数为 $n$ 个,则桶排序的算法步骤如下: + +1. **确定桶的数量**:根据待排序数组的值域范围,将数组划分为 $k$ 个桶,每个桶可以看做是一个范围区间。 +2. **分配元素**:遍历待排序数组元素,将每个元素根据大小分配到对应的桶中。 +3. **对每个桶进行排序**:对每个非空桶内的元素单独排序(使用插入排序、归并排序、快排排序等算法)。 +4. **合并桶内元素**:将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。 + +### 思路 9:代码 + +```python +class Solution: + def insertionSort(self, nums: [int]) -> [int]: + # 遍历无序区间 + for i in range(1, len(nums)): + temp = nums[i] + j = i + # 从右至左遍历有序区间 + while j > 0 and nums[j - 1] > temp: + # 将有序区间中插入位置右侧的元素依次右移一位 + nums[j] = nums[j - 1] + j -= 1 + # 将该元素插入到适当位置 + nums[j] = temp + + return nums + + def bucketSort(self, nums: [int], bucket_size=5) -> [int]: + # 计算待排序序列中最大值元素 nums_max、最小值元素 nums_min + nums_min, nums_max = min(nums), max(nums) + # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 + bucket_count = (nums_max - nums_min) // bucket_size + 1 + # 定义桶数组 buckets + buckets = [[] for _ in range(bucket_count)] + + # 遍历待排序数组元素,将每个元素根据大小分配到对应的桶中 + for num in nums: + buckets[(num - nums_min) // bucket_size].append(num) + + # 对每个非空桶内的元素单独排序,排序之后,按照区间顺序依次合并到 res 数组中 + res = [] + for bucket in buckets: + self.insertionSort(bucket) + res.extend(bucket) + + # 返回结果数组 + return res + + def sortArray(self, nums: [int]) -> [int]: + return self.bucketSort(nums) +``` + +### 思路 9:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 + +### 思路 10:基数排序(提交解答错误,普通基数排序只适合非负数) + +> **基数排序(Radix Sort)基本思想**:将整数按位数切割成不同的数字,然后从低位开始,依次到高位,逐位进行排序,从而达到排序的目的。 + +我们以最低位优先法为例,讲解一下基数排序的算法步骤。 + +1. **确定排序的最大位数**:遍历数组元素,获取数组最大值元素,并取得对应位数。 +2. **从最低位(个位)开始,到最高位为止,逐位对每一位进行排序**: + 1. 定义一个长度为 $10$ 的桶数组 $buckets$,每个桶分别代表 $0 \sim 9$ 中的 $1$ 个数字。 + 2. 按照每个元素当前位上的数字,将元素放入对应数字的桶中。 + 3. 清空原始数组,然后按照桶的顺序依次取出对应元素,重新加入到原始数组中。 + +### 思路 10:代码 + +```python +class Solution: + def radixSort(self, nums: [int]) -> [int]: + # 桶的大小为所有元素的最大位数 + size = len(str(max(nums))) + + # 从最低位(个位)开始,逐位遍历每一位 + for i in range(size): + # 定义长度为 10 的桶数组 buckets,每个桶分别代表 0 ~ 9 中的 1 个数字。 + buckets = [[] for _ in range(10)] + # 遍历数组元素,按照每个元素当前位上的数字,将元素放入对应数字的桶中。 + for num in nums: + buckets[num // (10 ** i) % 10].append(num) + # 清空原始数组 + nums.clear() + # 按照桶的顺序依次取出对应元素,重新加入到原始数组中。 + for bucket in buckets: + for num in bucket: + nums.append(num) + + # 完成排序,返回结果数组 + return nums + + def sortArray(self, nums: [int]) -> [int]: + return self.radixSort(nums) +``` + +### 思路 10:复杂度分析 + +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 + diff --git a/docs/solutions/0900-0999/squares-of-a-sorted-array.md b/docs/solutions/0900-0999/squares-of-a-sorted-array.md new file mode 100644 index 00000000..3de0a2da --- /dev/null +++ b/docs/solutions/0900-0999/squares-of-a-sorted-array.md @@ -0,0 +1,139 @@ +# [0977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) + +- 标签:数组、双指针、排序 +- 难度:简单 + +## 题目链接 + +- [0977. 有序数组的平方 - 力扣](https://leetcode.cn/problems/squares-of-a-sorted-array/) + +## 题目大意 + +**描述**:给定一个按「非递减顺序」排序的整数数组 $nums$。 + +**要求**:返回「每个数字的平方」组成的新数组,要求也按「非递减顺序」排序。 + +**说明**: + +- 要求使用时间复杂度为 $O(n)$ 的算法解决本问题。 +- $1 \le nums.length \le 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- $nums$ 已按非递减顺序排序。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [-4,-1,0,3,10] +输出:[0,1,9,16,100] +解释:平方后,数组变为 [16,1,0,9,100] +排序后,数组变为 [0,1,9,16,100] +``` + +- 示例 2: + +```python +输入:nums = [-7,-3,2,3,11] +输出:[4,9,9,49,121] +``` + +## 解题思路 + +### 思路 1:对撞指针 + +原数组是按「非递减顺序」排序的,可能会存在负数元素。但是无论是否存在负数,数字的平方最大值一定在原数组的两端。题目要求返回的新数组也要按照「非递减顺序」排序。那么,我们可以利用双指针,从两端向中间移动,然后不断将数的平方最大值填入数组。具体做法如下: + +- 使用两个指针 $left$、$right$。$left$ 指向数组第一个元素位置,$right$ 指向数组最后一个元素位置。再定义 $index = len(nums) - 1$ 作为答案数组填入顺序的索引值。$res$ 作为答案数组。 + +- 比较 $nums[left]$ 与 $nums[right]$ 的绝对值大小。大的就是平方最大的的那个数。 + + - 如果 $abs(nums[right])$ 更大,则将其填入答案数组对应位置,并令 `right -= 1`。 + + - 如果 $abs(nums[left])$ 更大,则将其填入答案数组对应位置,并令 `left += 1`。 + + - 令 $index -= 1$。 + +- 直到 $left == right$,最后将 $nums[left]$ 填入答案数组对应位置。 + +返回答案数组 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def sortedSquares(self, nums: List[int]) -> List[int]: + size = len(nums) + left, right = 0, size - 1 + index = size - 1 + res = [0 for _ in range(size)] + + while left < right: + if abs(nums[left]) < abs(nums[right]): + res[index] = nums[right] * nums[right] + right -= 1 + else: + res[index] = nums[left] * nums[left] + left += 1 + index -= 1 + res[index] = nums[left] * nums[left] + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 中的元素数量。 +- **空间复杂度**:$O(1)$,不考虑最终返回值的空间占用。 + +### 思路 2:排序算法 + +可以通过各种排序算法来对平方后的数组进行排序。以快速排序为例,具体步骤如下: + +1. 遍历数组,将数组中各个元素变为平方项。 +2. 从数组中找到一个基准数。 +3. 然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧,从而把数组拆分为左右两个部分。 +4. 再对左右两个部分分别重复第 2、3 步,直到各个部分只有一个数,则排序结束。 + +### 思路 2:代码 + +```python +import random + +class Solution: + def randomPartition(self, arr: [int], low: int, high: int): + i = random.randint(low, high) + arr[i], arr[high] = arr[high], arr[i] + return self.partition(arr, low, high) + + def partition(self, arr: [int], low: int, high: int): + i = low - 1 + pivot = arr[high] + + for j in range(low, high): + if arr[j] <= pivot: + i += 1 + arr[i], arr[j] = arr[j], arr[i] + arr[i + 1], arr[high] = arr[high], arr[i + 1] + return i + 1 + + def quickSort(self, arr, low, high): + if low < high: + pi = self.randomPartition(arr, low, high) + self.quickSort(arr, low, pi - 1) + self.quickSort(arr, pi + 1, high) + + return arr + + def sortedSquares(self, nums: List[int]) -> List[int]: + for i in range(len(nums)): + nums[i] = nums[i] * nums[i] + + return self.quickSort(nums, 0, len(nums) - 1) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \log n)$,其中 $n$ 为数组 $nums$ 中的元素数量。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/0900-0999/subarray-sums-divisible-by-k.md b/docs/solutions/0900-0999/subarray-sums-divisible-by-k.md new file mode 100644 index 00000000..09aaae52 --- /dev/null +++ b/docs/solutions/0900-0999/subarray-sums-divisible-by-k.md @@ -0,0 +1,62 @@ +# [974. 和可被 K 整除的子数组](https://leetcode.cn/problems/subarray-sums-divisible-by-k/) + +- 标签:数组、哈希表、前缀和 +- 难度:中等 + +## 题目链接 + +- [974. 和可被 K 整除的子数组 - 力扣](https://leetcode.cn/problems/subarray-sums-divisible-by-k/) + +## 题目大意 + +给定一个整数数组 `nums` 和一个整数 `k`。 + +要求:返回其中元素之和可被 `k` 整除的(连续、非空)子数组的数目。 + +## 解题思路 + +先考虑暴力计算子数组和,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: + +```python +for i in range(len(nums)): + for j in range(i + 1): + sum = countSum(i, j) +``` + +这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 + +先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 + +由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 + +`pre_sum[i]` 的定义是前 `i` 个元素和,则 `[j..i]` 子数组和可以被 `k` 整除可以转换为:`(pre_sum[i] - pre_sum[j - 1])% k == 0`。再转换一下:`pre_sum[i] % k == pre_sum[j - 1] % k`。 + +所以,我们只需要统计满足 `pre_sum[i] % k == pre_sum[j - 1] % k` 条件的组合个数。具体做法如下: + +使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。使用哈希表 `pre_dic` 记录 `pre_sum[i] % k` 出现的次数。键值对为 `pre_sum[i] : count`。 + +- 从左到右遍历数组,计算当前前缀和并对 `k` 取余,即 `pre_sum = (pre_sum + nums[i]) % k`。 + - 如果 `pre_sum` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum]`。同时 `pre_sum` 个数累加 1,即 `pre_dic[pre_sum] += 1`。 + - 如果 `pre_sum` 不在哈希表中,则 `pre_sum` 个数记为 1,即 `pre_dic[pre_sum] += 1`。 +- 最后输出答案个数。 + +## 代码 + +```python +class Solution: + def subarraysDivByK(self, nums: List[int], k: int) -> int: + pre_sum = 0 + ans = 0 + nums_dict = {0: 1} + for i in range(len(nums)): + pre_sum = (pre_sum + nums[i]) % k + if pre_sum < 0: + pre_sum += k + if pre_sum in nums_dict: + ans += nums_dict[pre_sum] + nums_dict[pre_sum] += 1 + else: + nums_dict[pre_sum] = 1 + return ans +``` + diff --git a/docs/solutions/0900-0999/subarrays-with-k-different-integers.md b/docs/solutions/0900-0999/subarrays-with-k-different-integers.md new file mode 100644 index 00000000..925cd2c3 --- /dev/null +++ b/docs/solutions/0900-0999/subarrays-with-k-different-integers.md @@ -0,0 +1,64 @@ +# [0992. K 个不同整数的子数组](https://leetcode.cn/problems/subarrays-with-k-different-integers/) + +- 标签:数组、哈希表、计数、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [0992. K 个不同整数的子数组 - 力扣](https://leetcode.cn/problems/subarrays-with-k-different-integers/) + +## 题目大意 + +给定一个正整数数组 `nums`,再给定一个整数 `k`。如果 `nums` 的某个子数组中不同整数的个数恰好为 `k`,则称 `nums` 的这个连续、不一定不同的子数组为「好子数组」。 + +- 例如,`[1, 2, 3, 1, 2]` 中有 3 个不同的整数:`1`,`2` 以及 `3`。 + +要求:返回 `nums` 中好子数组的数目。 + +## 解题思路 + +这道题转换一下思路会更简单。 + +恰好包含 `k` 个不同整数的连续子数组数量 = 包含小于等于 `k` 个不同整数的连续子数组数量 - 包含小于等于 `k - 1` 个不同整数的连续子数组数量 + +可以专门写一个方法计算包含小于等于 `k` 个不同整数的连续子数组数量。 + +计算包含小于等于 `k` 个不同整数的连续子数组数量的方法具体步骤如下: + +用滑动窗口 `windows` 来记录不同的整数个数,`windows` 为哈希表类型。 + +设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内不超过 `k` 个不同整数。 + +- 一开始,`left`、`right` 都指向 `0`。 +- 将最右侧整数 `nums[right]` 加入当前窗口 `windows` 中,记录该整数个数。 +- 如果该窗口中该整数的个数多于 `k` 个,即 `len(windows) > k`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应整数的个数,直到 `len(windows) <= k`。 +- 维护更新包含小于等于 `k` 个不同整数的连续子数组数量。每次累加数量为 `right - left + 1`,表示以 `nums[right]` 为结尾的小于等于 `k` 个不同整数的连续子数组数量。 +- 然后右移 `right`,直到 `right >= len(nums)` 结束。 +- 返回包含小于等于 `k` 个不同整数的连续子数组数量。 + +## 代码 + +```python +class Solution: + def subarraysMostKDistinct(self, nums, k): + windows = dict() + left, right = 0, 0 + ans = 0 + while right < len(nums): + if nums[right] in windows: + windows[nums[right]] += 1 + else: + windows[nums[right]] = 1 + while len(windows) > k: + windows[nums[left]] -= 1 + if windows[nums[left]] == 0: + del windows[nums[left]] + left += 1 + ans += right - left + 1 + right += 1 + return ans + + def subarraysWithKDistinct(self, nums: List[int], k: int) -> int: + return self.subarraysMostKDistinct(nums, k) - self.subarraysMostKDistinct(nums, k - 1) +``` + diff --git a/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md b/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md new file mode 100644 index 00000000..73e79791 --- /dev/null +++ b/docs/solutions/0900-0999/triples-with-bitwise-and-equal-to-zero.md @@ -0,0 +1,157 @@ +# [0982. 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) + +- 标签:位运算、数组、哈希表 +- 难度:困难 + +## 题目链接 + +- [0982. 按位与为零的三元组 - 力扣](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:返回其中「按位与三元组」的数目。 + +**说明**: + +- **按位与三元组**:由下标 $(i, j, k)$ 组成的三元组,并满足下述全部条件: + - $0 \le i < nums.length$。 + - $0 \le j < nums.length$。 + - $0 \le k < nums.length$。 + - $nums[i] \text{ \& } nums[j] \text{ \& } nums[k] == 0$ ,其中 $\text{ \& }$ 表示按位与运算符。 + +- $1 \le nums.length \le 1000$。 +- $0 \le nums[i] < 2^{16}$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,1,3] +输出:12 +解释:可以选出如下 i, j, k 三元组: +(i=0, j=0, k=1) : 2 & 2 & 1 +(i=0, j=1, k=0) : 2 & 1 & 2 +(i=0, j=1, k=1) : 2 & 1 & 1 +(i=0, j=1, k=2) : 2 & 1 & 3 +(i=0, j=2, k=1) : 2 & 3 & 1 +(i=1, j=0, k=0) : 1 & 2 & 2 +(i=1, j=0, k=1) : 1 & 2 & 1 +(i=1, j=0, k=2) : 1 & 2 & 3 +(i=1, j=1, k=0) : 1 & 1 & 2 +(i=1, j=2, k=0) : 1 & 3 & 2 +(i=2, j=0, k=1) : 3 & 2 & 1 +(i=2, j=1, k=0) : 3 & 1 & 2 +``` + +- 示例 2: + +```python +输入:nums = [0,0,0] +输出:27 +``` + +## 解题思路 + +### 思路 1:枚举 + +最直接的方法是使用三重循环直接枚举 $(i, j, k)$,然后再判断 $nums[i] \text{ \& } nums[j] \text{ \& } nums[k]$ 是否为 $0$。但是这样做的时间复杂度为 $O(n^3)$。 + +从题目中可以看出 $nums[i]$ 的值域范围为 $[0, 2^{16}]$,而 $2^{16} = 65536$。所以我们可以按照下面步骤优化时间复杂度: + +1. 先使用两重循环枚举 $(i, j)$,计算出 $nums[i] \text{ \& } nums[j]$ 的值,将其存入一个大小为 $2^{16}$ 的数组或者哈希表 $cnts$ 中,并记录每个 $nums[i] \text{ \& } nums[j]$ 值出现的次数。 +2. 然后遍历该数组或哈希表,再使用一重循环遍历 $k$,找出所有满足 $nums[k] \text{ \& } x == 0$ 的 $x$,并将其对应数量 $cnts[x]$ 累积到答案 $ans$ 中。 +3. 最后返回答案 $ans$ 即可。 + +### 思路 1:代码 + +```python +class Solution: + def countTriplets(self, nums: List[int]) -> int: + states = 1 << 16 + cnts = [0 for _ in range(states)] + + for num_x in nums: + for num_y in nums: + cnts[num_x & num_y] += 1 + + ans = 0 + for num in nums: + for x in range(states): + if num & x == 0: + ans += cnts[x] + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 + 2^{16} \times n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(2^{16})$。 + +### 思路 2:枚举 + 优化 + +第一步跟思路 1 一样,我们先使用两重循环枚举 $(i, j)$,计算出 $nums[i] \text{ \& } nums[j]$ 的值,将其存入一个大小为 $2^{16}$ 的数组或者哈希表 $cnts$ 中,并记录每个 $nums[i] \text{ \& } nums[j]$ 值出现的次数。 + +接下来我们对思路 1 中的第二步进行优化,在思路 1 中,我们是通过枚举数组或哈希表的方式得到 $x$ 的,这里我们换一种方法。 + +使用一重循环遍历 $k$,对于 $nums[k]$,我们先计算出 $nums[k]$ 的补集,即将 $nums[k]$ 与 $2^{16} - 1$(二进制中 $16$ 个 $1$)进行按位异或操作,得到 $nums[k]$ 的补集 $com$。如果 $nums[k] \text{ \& } x == 0$,则 $x$ 一定是 $com$ 的子集。 + +换句话说,$x$ 中 $1$ 的位置一定与 $nums[k]$ 中 $1$ 的位置不同,如果 $nums[k]$ 中第 $m$ 位为 $1$,则 $x$ 中第 $m$ 位一定为 $0$。 + +接下来我们通过下面的方式来枚举子集: + +1. 定义子集为 $sub$,初始时赋值为 $com$,即:$sub = com$。 +2. 令 $sub$ 减 $1$,然后与 $com$ 做按位与操作,得到下一个子集,即:$sub = (sub - 1) \text{ \& } com$。 +3. 不断重复第 $2$ 步,直到 $sub$ 为空集时为止。 + +这种方法能枚举子集的原理是:$sub$ 减 $1$ 会将最低位的 $1$ 改为 $0$,而比这个 $1$ 更低位的 $0$ 都改为了 $1$。此时再与 $com$ 做按位与操作,就会过保留原本高位上的 $1$,滤掉当前最低位的 $1$,并且保留比这个 $1$ 更低位上的原有的 $1$,也就得到嘞下一个子集。 + +举个例子,比如补集 $com$ 为 $(00010110)_2$: + +1. 初始 $sub = (00010110)_2$。 +2. 令其减 $1$ 后为 $(00010101)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010100)_2$,即:$(00010101)_2 \text{ \& } (00010110)_2$)。 +3. 令其减 $1$ 后为 $(00010011)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010010)_2$,即: $(00010011)_2 \text{ \& } (00010110)_2$。 +4. 令其减 $1$ 后为 $(00010001)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00010000)_2$,即:$(00010001)_2 \text{ \& } (00010110)_2$。 +5. 令其减 $1$ 后为 $(00001111)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000110)_2$,即:$(00001111)_2 \text{ \& } (00010110)_2$。 +6. 令其减 $1$ 后为 $(00000101)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000100)_2$,即:$(00000101)_2 \text{ \& } (00010110)_2$。 +7. 令其减 $1$ 后为 $(00000011)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000010)_2$,即:$(00000011)_2 \text{ \& } (00010110)_2$。 +8. 令其减 $1$ 后为 $(00000001)_2$,然后与 $com$ 做按位与操作,得到下一个子集 $sub = (00000000)_2$,即:$(00000001)_2 \text{ \& } (00010110)_2$。 +9. $sub$ 变为了空集。 + +### 思路 2:代码 + +```python +class Solution: + def countTriplets(self, nums: List[int]) -> int: + states = 1 << 16 + cnts = [0 for _ in range(states)] + + for num_x in nums: + for num_y in nums: + cnts[num_x & num_y] += 1 + + ans = 0 + for num in nums: + com = num ^ 0xffff # com: num 的补集 + sub = com # sub: 子集 + while True: + ans += cnts[sub] + if sub == 0: + break + sub = (sub - 1) & com + + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2 + 2^{16} \times n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(2^{16})$。 + +## 参考资料 + +- 【题解】[按位与为零的三元组 - 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/solution/an-wei-yu-wei-ling-de-san-yuan-zu-by-lee-gjud/) +- 【题解】[有技巧的枚举 + 常数优化(Python/Java/C++/Go) - 按位与为零的三元组](https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero/solution/you-ji-qiao-de-mei-ju-chang-shu-you-hua-daxit/) diff --git a/docs/solutions/0900-0999/validate-stack-sequences.md b/docs/solutions/0900-0999/validate-stack-sequences.md new file mode 100644 index 00000000..b30210b3 --- /dev/null +++ b/docs/solutions/0900-0999/validate-stack-sequences.md @@ -0,0 +1,70 @@ +# [0946. 验证栈序列](https://leetcode.cn/problems/validate-stack-sequences/) + +- 标签:栈、数组、模拟 +- 难度:中等 + +## 题目链接 + +- [0946. 验证栈序列 - 力扣](https://leetcode.cn/problems/validate-stack-sequences/) + +## 题目大意 + +**描述**:给定两个整数序列 `pushed` 和 `popped`,每个序列中的值都不重复。 + +**要求**:如果第一个序列为空栈的压入顺序,而第二个序列 `popped` 为该栈的压出序列,则返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le pushed.length \le 1000$。 +- $0 \le pushed[i] \le 1000$。 +- $pushed$ 的所有元素互不相同。 +- $popped.length == pushed.length$。 +- $popped$ 是 $pushed$ 的一个排列。 + +**示例**: + +- 示例 1: + +```python +输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1] +输出:true +解释:我们可以按以下顺序执行: +push(1), push(2), push(3), push(4), pop() -> 4, +push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1 +``` + +- 示例 2: + +```python +输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2] +输出:false +解释:1 不能在 2 之前弹出。 +``` + +## 解题思路 + +### 思路 1:栈 + +借助一个栈来模拟压入、压出的操作。检测最后是否能模拟成功。 + +### 思路 1:代码 + +```python +class Solution: + def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: + stack = [] + index = 0 + for item in pushed: + stack.append(item) + while (stack and stack[-1] == popped[index]): + stack.pop() + index += 1 + + return len(stack) == 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/0900-0999/verifying-an-alien-dictionary.md b/docs/solutions/0900-0999/verifying-an-alien-dictionary.md new file mode 100644 index 00000000..95633bc0 --- /dev/null +++ b/docs/solutions/0900-0999/verifying-an-alien-dictionary.md @@ -0,0 +1,50 @@ +# [0953. 验证外星语词典](https://leetcode.cn/problems/verifying-an-alien-dictionary/) + +- 标签:数组、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [0953. 验证外星语词典 - 力扣](https://leetcode.cn/problems/verifying-an-alien-dictionary/) + +## 题目大意 + +给定一组用外星语书写的单词字符串数组 `words`,以及表示外星字母表的顺序的字符串 `order` 。 + +要求:判断 `words` 中的单词是否都是按照 `order` 来排序的。如果是,则返回 `True`,否则返回 `False`。 + +## 解题思路 + +如果所有单词是按照 `order` 的规则升序排列,则所有单词都符合规则。而判断所有单词是升序排列,只需要两两比较相邻的单词即可。所以我们可以先用哈希表存储所有字母的顺序,然后对所有相邻单词进行两两比较,如果最终是升序排列,则符合要求。具体步骤如下: + +- 使用哈希表 `order_map` 存储字母的顺序。 +- 遍历单词数组 `words`,比较相邻单词 `word1` 和 `word2` 中所有字母在 `order_map` 中的下标,看是否满足 `word1 <= word2`。 +- 如果全部满足,则返回 `True`。如果有不满足的情况,则直接返回 `False`。 + +## 代码 + +```python +class Solution: + def isAlienSorted(self, words: List[str], order: str) -> bool: + order_map = dict() + for i in range(len(order)): + order_map[order[i]] = i + for i in range(len(words) - 1): + word1 = words[i] + word2 = words[i + 1] + + flag = True + + for j in range(min(len(word1), len(word2))): + if word1[j] != word2[j]: + if order_map[word1[j]] > order_map[word2[j]]: + return False + else: + flag = False + break + + if flag and len(word1) > len(word2): + return False + return True +``` + diff --git a/docs/solutions/1000-1099/best-sightseeing-pair.md b/docs/solutions/1000-1099/best-sightseeing-pair.md new file mode 100644 index 00000000..6dad2bd4 --- /dev/null +++ b/docs/solutions/1000-1099/best-sightseeing-pair.md @@ -0,0 +1,31 @@ +# [1014. 最佳观光组合](https://leetcode.cn/problems/best-sightseeing-pair/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1014. 最佳观光组合 - 力扣](https://leetcode.cn/problems/best-sightseeing-pair/) + +## 题目大意 + +给你一个正整数数组 `values`,其中 `values[i]` 表示第 `i` 个观光景点的评分,并且两个景点 `i` 和 `j` 之间的距离 为 `j - i`。一对景点(`i < j`)组成的观光组合的得分为 `values[i] + values[j] + i - j`,也就是景点的评分之和减去它们两者之间的距离。 + +要求:返回一对观光景点能取得的最高分。 + +## 解题思路 + +求解的是 `ans = max(values[i] + values[j] + i - j)`。对于当前第 `j` 个位置上的元素来说,`values[j] - j` 的值是固定的,求解 `ans` 就是在求解 `values[i] + i` 的最大值。我们使用一个变量 `max_score` 来存储当前第 `j` 个位置元素之前 `values[i] + i` 的最大值。然后遍历数组,求出每一个元素位置之前 `values[i] + i` 的最大值,并找出其中最大的 `ans`。 + +## 代码 + +```python +class Solution: + def maxScoreSightseeingPair(self, values: List[int]) -> int: + ans = 0 + max_score = values[0] + for i in range(1, len(values)): + ans = max(ans, max_score + values[i] - i) + max_score = max(max_score, values[i] + i) + return ans +``` diff --git a/docs/solutions/1000-1099/binary-search-tree-to-greater-sum-tree.md b/docs/solutions/1000-1099/binary-search-tree-to-greater-sum-tree.md new file mode 100644 index 00000000..c0c84de2 --- /dev/null +++ b/docs/solutions/1000-1099/binary-search-tree-to-greater-sum-tree.md @@ -0,0 +1,51 @@ +# [1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [1038. 从二叉搜索树到更大和树 - 力扣](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) + +## 题目大意 + +给定一棵二叉搜索树(BST)的根节点,且二叉搜索树的节点值各不相同。 + +要求:将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 + +二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +## 解题思路 + +题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 + +题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 + +二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 + +当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 + +## 代码 + +```python +class Solution: + pre = 0 + + def createBinaryTree(self, root: TreeNode): + if not root: + return + self.createBinaryTree(root.right) + root.val += self.pre + self.pre = root.val + self.createBinaryTree(root.left) + + def bstToGst(self, root: TreeNode) -> TreeNode: + self.pre = 0 + self.createBinaryTree(root) + return root +``` + diff --git a/docs/solutions/1000-1099/camelcase-matching.md b/docs/solutions/1000-1099/camelcase-matching.md new file mode 100644 index 00000000..63136441 --- /dev/null +++ b/docs/solutions/1000-1099/camelcase-matching.md @@ -0,0 +1,114 @@ +# [1023. 驼峰式匹配](https://leetcode.cn/problems/camelcase-matching/) + +- 标签:字典树、双指针、字符串、字符串匹配 +- 难度:中等 + +## 题目链接 + +- [1023. 驼峰式匹配 - 力扣](https://leetcode.cn/problems/camelcase-matching/) + +## 题目大意 + +**描述**:给定待查询列表 `queries`,和模式串 `pattern`。如果我们可以将小写字母(0 个或多个)插入模式串 `pattern` 中间(任意位置)得到待查询项 `queries[i]`,那么待查询项与给定模式串匹配。如果匹配,则对应答案为 `True`,否则为 `False`。 + +**要求**:将匹配结果存入由布尔值组成的答案列表中,并返回。 + +**说明**: + +- $1 \le queries.length \le 100$。 +- $1 \le queries[i].length \le 100$。 +- $1 \le pattern.length \le 100$。 +- 所有字符串都仅由大写和小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FB" +输出:[true,false,true,true,false] +示例: +"FooBar" 可以这样生成:"F" + "oo" + "B" + "ar"。 +"FootBall" 可以这样生成:"F" + "oot" + "B" + "all". +"FrameBuffer" 可以这样生成:"F" + "rame" + "B" + "uffer". +``` + +- 示例 2: + +```python +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBa" +输出:[true,false,true,false,false] +解释: +"FooBar" 可以这样生成:"Fo" + "o" + "Ba" + "r". +"FootBall" 可以这样生成:"Fo" + "ot" + "Ba" + "ll". +``` + +## 解题思路 + +### 思路 1:字典树 + +构建一棵字典树,将 `pattern` 存入字典树中。 + +1. 对于 `queries[i]` 中的每个字符串。逐个字符与 `pattern` 进行匹配。 + 1. 如果遇见小写字母,直接跳过。 + 2. 如果遇见大写字母,但是不能匹配,返回 `False`。 + 3. 如果遇见大写字母,且可以匹配,继续查找。 + 4. 如果到达末尾仍然匹配,则返回 `True`。 +2. 最后将所有结果存入答案数组中返回。 + +### 思路 1:代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ord(ch) > 96: + if ch not in cur.children: + continue + else: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def camelMatch(self, queries: List[str], pattern: str) -> List[bool]: + trie_tree = Trie() + trie_tree.insert(pattern) + res = [] + for query in queries: + res.append(trie_tree.search(query)) + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times |T| + |pattern|)$。其中 $n$ 是待查询项的数目,$|T|$ 是最长的待查询项的字符串长度,$|pattern|$ 是字符串 `pattern` 的长度。 +- **空间复杂度**:$O(|pattern|)$。 + diff --git a/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md b/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md new file mode 100644 index 00000000..dca6f114 --- /dev/null +++ b/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md @@ -0,0 +1,89 @@ +# [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [1011. 在 D 天内送达包裹的能力 - 力扣](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) + +## 题目大意 + +**描述**:传送带上的包裹必须在 $D$ 天内从一个港口运送到另一个港口。给定所有包裹的重量数组 $weights$,货物必须按照给定的顺序装运。且每天船上装载的重量不会超过船的最大运载重量。 + +**要求**:求能在 $D$ 天内将所有包裹送达的船的最低运载量。 + +**说明**: + +- $1 \le days \le weights.length \le 5 * 10^4$。 +- $1 \le weights[i] \le 500$。 + +**示例**: + +- 示例 1: + +```python +输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5 +输出:15 +解释: +船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示: +第 1 天:1, 2, 3, 4, 5 +第 2 天:6, 7 +第 3 天:8 +第 4 天:9 +第 5 天:10 +请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 +``` + +- 示例 2: + +```python +输入:weights = [3,2,2,4,1,4], days = 3 +输出:6 +解释: +船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示: +第 1 天:3, 2 +第 2 天:2, 4 +第 3 天:1, 4 +``` + +## 解题思路 + +### 思路 1:二分查找 + +船最小的运载能力,最少也要等于或大于最重的那件包裹,即 $max(weights)$。最多的话,可以一次性将所有包裹运完,即 $sum(weights)$。船的运载能力介于 $[max(weights), sum(weights)]$ 之间。 + +我们现在要做的就是从这个区间内,找到满足可以在 $D$ 天内运送完所有包裹的最小载重量。 + +可以通过二分查找的方式,找到满足要求的最小载重量。 + +### 思路 1:代码 + +```python +class Solution: + def shipWithinDays(self, weights: List[int], D: int) -> int: + left = max(weights) + right = sum(weights) + + while left < right: + mid = (left + right) >> 1 + days = 1 + cur = 0 + for weight in weights: + if cur + weight > mid: + days += 1 + cur = 0 + cur += weight + + if days <= D: + right = mid + else: + left = mid + 1 + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。二分查找算法的时间复杂度为 $O(\log n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + diff --git a/docs/solutions/1000-1099/coloring-a-border.md b/docs/solutions/1000-1099/coloring-a-border.md new file mode 100644 index 00000000..659dc26a --- /dev/null +++ b/docs/solutions/1000-1099/coloring-a-border.md @@ -0,0 +1,68 @@ +# [1034. 边界着色](https://leetcode.cn/problems/coloring-a-border/) + +- 标签:深度优先搜索、广度优先搜索、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1034. 边界着色 - 力扣](https://leetcode.cn/problems/coloring-a-border/) + +## 题目大意 + +给定一个二维整数矩阵 `grid`,其中 `grid[i][j]` 表示矩阵第 `i` 行、第 `j` 列上网格块的颜色值。再给定一个起始位置 `(row, col)`,以及一个目标颜色 `color`。 + +要求:对起始位置 `(row, col)` 所在的连通分量边界填充颜色为 `color`。并返回最终的二维整数矩阵 `grid`。 + +- 连通分量:当两个相邻(上下左右四个方向上)网格块的颜色值相同时,它们属于同一连通分量。 +- 连通分量边界:当前连通分量最外圈的所有网格块,这些网格块与连通分量的颜色相同,与其他周围网格块颜色不同。边界上的网格块也是连通分量边界。 + +## 解题思路 + +深度优先搜索。使用二维数组 `visited` 标记访问过的节点。遍历上、下、左、右四个方向上的点。如果下一个点位置越界,或者当前位置与下一个点位置颜色不一样,则对该节点进行染色。 + +在遍历的过程中注意使用 `visited` 标记访问过的节点,以免重复遍历。 + +## 代码 + +```python +class Solution: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(self, grid, i, j, origin_color, color, visited): + rows, cols = len(grid), len(grid[0]) + + for direct in self.directs: + new_i = i + direct[0] + new_j = j + direct[1] + + # 下一个位置越界,则当前点在边界,对其进行着色 + if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: + grid[i][j] = color + continue + + # 如果访问过,则跳过 + if visited[new_i][new_j]: + continue + + # 如果下一个位置颜色与当前颜色相同,则继续搜索 + if grid[new_i][new_j] == origin_color: + visited[new_i][new_j] = True + self.dfs(grid, new_i, new_j, origin_color, color, visited) + # 下一个位置颜色与当前颜色不同,则当前位置为连通区域边界,对其进行着色 + else: + grid[i][j] = color + + + def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]: + if not grid: + return grid + + rows, cols = len(grid), len(grid[0]) + visited = [[False for _ in range(cols)] for _ in range(rows)] + visited[row][col] = True + + self.dfs(grid, row, col, grid[row][col], color, visited) + + return grid +``` + diff --git a/docs/solutions/1000-1099/complement-of-base-10-integer.md b/docs/solutions/1000-1099/complement-of-base-10-integer.md new file mode 100644 index 00000000..b9ce9786 --- /dev/null +++ b/docs/solutions/1000-1099/complement-of-base-10-integer.md @@ -0,0 +1,74 @@ +# [1009. 十进制整数的反码](https://leetcode.cn/problems/complement-of-base-10-integer/) + +- 标签:位运算 +- 难度:简单 + +## 题目链接 + +- [1009. 十进制整数的反码 - 力扣](https://leetcode.cn/problems/complement-of-base-10-integer/) + +## 题目大意 + +**描述**:给定一个十进制数 $n$。 + +**要求**:返回其二进制表示的反码对应的十进制整数。 + +**说明**: + +- $0 \le N < 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:5 +输出:2 +解释:5 的二进制表示为 "101",其二进制反码为 "010",也就是十进制中的 2 。 +``` + +- 示例 2: + +```python +输入:7 +输出:0 +解释:7 的二进制表示为 "111",其二进制反码为 "000",也就是十进制中的 0 。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 将十进制数 $n$ 转为二进制 $binary$。 +2. 遍历二进制 $binary$ 的每一个数位 $digit$。 + 1. 如果 $digit$ 为 $0$,则将其转为 $1$,存入答案 $res$ 中。 + 2. 如果 $digit$ 为 $1$,则将其转为 $0$,存入答案 $res$ 中。 +3. 返回答案 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def bitwiseComplement(self, n: int) -> int: + binary = "" + while n: + binary += str(n % 2) + n //= 2 + if binary == "": + binary = "0" + else: + binary = binary[::-1] + res = 0 + for digit in binary: + if digit == '0': + res = res * 2 + 1 + else: + res = res * 2 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(len(n))$,其中 $len(n)$ 为 $n$ 对应二进制的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal.md b/docs/solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal.md new file mode 100644 index 00000000..bf0958c6 --- /dev/null +++ b/docs/solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal.md @@ -0,0 +1,47 @@ +# [1008. 前序遍历构造二叉搜索树](https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/) + +- 标签:栈、树、二叉搜索树、数组、二叉树、单调栈 +- 难度:中等 + +## 题目链接 + +- [1008. 前序遍历构造二叉搜索树 - 力扣](https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/) + +## 题目大意 + +给定一棵二叉搜索树的前序遍历结果 `preorder`。 + +要求:返回与给定前序遍历 `preorder` 相匹配的二叉搜索树的根节点。题目保证,对于给定的测试用例,总能找到满足要求的二叉搜索树。 + +## 解题思路 + +二叉搜索树的中序遍历是升序序列。而题目又给了我们二叉搜索树的前序遍历,那么通过对前序遍历结果的排序,我们也可以得到二叉搜索树的中序遍历结果。这样就能根据二叉树的前序、中序遍历序列构造二叉树了。就变成了了「[0105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)」题。 + +此外,我们还有另一种方法求解。前序遍历的顺序是:根 -> 左 -> 右。并且在二叉搜索树中,左子树的值小于根节点,右子树的值大于根节点。 + +根据以上性质,我们可以递归地构造二叉搜索树。 + +首先,以前序遍历的开始位置元素构造为根节点。从开始位置的下一个位置开始,找到序列中第一个大于等于根节点值的位置 `mid`。该位置左侧的值都小于根节点,右侧的值都大于等于根节点。以此位置为中心,递归的构造左子树和右子树。 + +最后再将根节点进行返回。 + +## 代码 + +```python +class Solution: + def buildTree(self, preorder, start, end): + if start == end: + return None + root = preorder[start] + mid = start + 1 + while mid < end and preorder[mid] < root: + mid += 1 + node = TreeNode(root) + node.left = self.buildTree(preorder, start + 1, mid) + node.right = self.buildTree(preorder, mid, end) + return node + + def bstFromPreorder(self, preorder: List[int]) -> TreeNode: + return self.buildTree(preorder, 0, len(preorder)) +``` + diff --git a/docs/solutions/1000-1099/divisor-game.md b/docs/solutions/1000-1099/divisor-game.md new file mode 100644 index 00000000..d51fbe5b --- /dev/null +++ b/docs/solutions/1000-1099/divisor-game.md @@ -0,0 +1,37 @@ +# [1025. 除数博弈](https://leetcode.cn/problems/divisor-game/) + +- 标签:脑筋急转弯、数学、动态规划、博弈 +- 难度:简单 + +## 题目链接 + +- [1025. 除数博弈 - 力扣](https://leetcode.cn/problems/divisor-game/) + +## 题目大意 + +爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。最初,黑板上有一个数字 `n`。在每个玩家的回合,玩家需要执行以下操作: + +- 选出任一 `x`,满足 `0 < x < n` 且 `n % x == 0`。 +- 用 `n - x` 替换黑板上的数字 `n` 。 +- 如果玩家无法执行这些操作,就会输掉游戏。 + +只有在爱丽丝在游戏中取得胜利时才返回 `True`,否则返回 `False`。假设两个玩家都以最佳状态参与游戏。 + +## 解题思路 + +- 如果 `n` 为奇数,则 `n` 的约数必然都是奇数;如果 `n` 为偶数,则 `n` 的约数可能为奇数也可能为偶数。 +- 无论 `n` 为奇数还是偶数,都可以选择 `1` 作为约数。 +- 无论 `n` 初始为多大的数,游戏到最终只能到 `n == 2` 结束,只要谁先到 `n == 2`,谁就赢得胜利。 +- 当初始 `n` 为偶数时,爱丽丝只要一直选 `1`,那么鲍勃必然会一直面临 `n` 为奇数的情况,这样最后爱丽丝肯定能先到 `n == 2`,稳赢。 +- 当初始 `n` 为奇数时,因为奇数的约数只能是奇数,奇数 - 奇数 必然是偶数,所以给鲍勃的数一定是偶数,鲍勃只需一直选 `1` 就会稳赢,此时爱丽丝稳输。 + +所以,当 `n` 为偶数时,爱丽丝稳赢。当 `n` 为奇数时,爱丽丝稳输。 + +## 代码 + +```python +class Solution: + def divisorGame(self, n: int) -> bool: + return n & 1 == 0 +``` + diff --git a/docs/solutions/1000-1099/duplicate-zeros.md b/docs/solutions/1000-1099/duplicate-zeros.md new file mode 100644 index 00000000..e835cd7c --- /dev/null +++ b/docs/solutions/1000-1099/duplicate-zeros.md @@ -0,0 +1,90 @@ +# [1089. 复写零](https://leetcode.cn/problems/duplicate-zeros/) + +- 标签:数组、双指针 +- 难度:简单 + +## 题目链接 + +- [1089. 复写零 - 力扣](https://leetcode.cn/problems/duplicate-zeros/) + +## 题目大意 + +**描述**:给定搞一个长度固定的整数数组 $arr$。 + +**要求**:键改改数组中出现的每一个 $0$ 都复写一遍,并将其余的元素向右平移。 + +**说明**: + +- 注意:不要在超过该数组长度的位置写上元素。请对输入的数组就地进行上述修改,不要从函数返回任何东西。 +- $1 \le arr.length \le 10^4$。 +- $0 \le arr[i] \le 9$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [1,0,2,3,0,4,5,0] +输出:[1,0,0,2,3,0,0,4] +解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4] +``` + +- 示例 2: + +```python +输入:arr = [1,2,3] +输出:[1,2,3] +解释:调用函数后,输入的数组将被修改为:[1,2,3] +``` + +## 解题思路 + +### 思路 1:两次遍历 + 快慢指针 + +因为数组中出现的 $0$ 需要复写为 $00$,占用空间从一个单位变成两个单位空间,那么右侧必定会有一部分元素丢失。我们可以先遍历一遍数组,找出复写后需要保留的有效数字部分与需要丢失部分的分界点。则从分界点开始,分界点右侧的元素都可以丢失。 + +我们再次逆序遍历数组, + +1. 使用两个指针 $slow$、$fast$,$slow$ 表示当前有效字符位置,$fast$ 表示当前遍历字符位置。一开始 $slow$ 和 $fast$ 都指向数组开始位置。 +2. 正序扫描数组: + 1. 如果遇到 $arr[slow] == 0$,则让 $fast$ 指针多走一步。 + 2. 然后 $fast$、$slow$ 各自向右移动 $1$ 位,直到 $fast$ 指针移动到数组末尾。此时 $slow$ 左侧数字 $arr[0]... arr[slow - 1]$ 为需要保留的有效数字部分, $arr[slow]...arr[fast - 1]$ 为需要丢失部分。 +3. 令 $slow$、$fast$ 分别左移 $1$ 位,此时 $slow$ 指向最后一个有效数字,$fast$ 指向丢失部分的最后一个数字。此时 $fast$ 可能等于 $size - 1$,也可能等于 $size$(比如输入 $[0, 0, 0]$)。 +4. 逆序遍历数组: + 1. 将 $slow$ 位置元素移动到 $fast$ 位置。 + 2. 如果遇到 $arr[slow] == 0$,则令 $fast$ 减 $1$,然后再复制 $1$ 个 $0$ 到 $fast$ 位置。 + 3. 令 $slow$、$fast$ 分别左移 $1$ 位。 + +### 思路 1:代码 + +```python +class Solution: + def duplicateZeros(self, arr: List[int]) -> None: + """ + Do not return anything, modify arr in-place instead. + """ + size = len(arr) + slow, fast = 0, 0 + while fast < size: + if arr[slow] == 0: + fast += 1 + slow += 1 + fast += 1 + + slow -= 1 # slow 指向最后一个有效数字 + fast -= 1 # fast 指向丢失部分的最后一个数字(可能在减 1 之后为 size,比如输入 [0, 0, 0]) + + while slow >= 0: + if fast < size: # 防止 fast 越界 + arr[fast] = arr[slow] # 将 slow 位置元素移动到 fast 位置 + if arr[slow] == 0 and fast >= 0: # 遇见 0 则复制 0 到 fast - 1 位置 + fast -= 1 + arr[fast] = arr[slow] + fast -= 1 + slow -= 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $arr$ 中的元素个数。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1000-1099/find-common-characters.md b/docs/solutions/1000-1099/find-common-characters.md new file mode 100644 index 00000000..a8be55cb --- /dev/null +++ b/docs/solutions/1000-1099/find-common-characters.md @@ -0,0 +1,77 @@ +# [1002. 查找共用字符](https://leetcode.cn/problems/find-common-characters/) + +- 标签:数组、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [1002. 查找共用字符 - 力扣](https://leetcode.cn/problems/find-common-characters/) + +## 题目大意 + +**描述**:给定一个字符串数组 $words$。 + +**要求**:找出所有在 $words$ 的每个字符串中都出现的公用字符(包括重复字符),并以数组形式返回。可以按照任意顺序返回答案。 + +**说明**: + +- $1 \le words.length \le 100$。 +- $1 \le words[i].length \le 100$。 +- $words[i]$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:words = ["bella","label","roller"] +输出:["e","l","l"] +``` + +- 示例 2: + +```python +输入:words = ["cool","lock","cook"] +输出:["c","o"] +``` + +## 解题思路 + +### 思路 1:哈希表 + +如果某个字符 $ch$ 在所有字符串中都出现了 $k$ 次以上,则最终答案中需要包含 $k$ 个 $ch$。因此,我们可以使用哈希表 $minfreq[ch]$ 记录字符 $ch$ 在所有字符串中出现的最小次数。具体步骤如下: + +1. 定义长度为 $26$ 的哈希表 $minfreq$,初始化所有字符出现次数为无穷大,$minfreq[ch] = float('inf')$。 +2. 遍历字符串数组中的所有字符串 $word$,对于字符串 $word$: + 1. 记录 $word$ 中所有字符串的出现次数 $freq[ch]$。 + 2. 取 $freq[ch]$ 与 $minfreq[ch]$ 中的较小值更新 $minfreq[ch]$。 +3. 遍历完之后,再次遍历 $26$ 个字符,将所有最小出现次数大于零的字符按照出现次数存入答案数组中。 +4. 最后将答案数组返回。 + +### 思路 1:代码 + +```python +class Solution: + def commonChars(self, words: List[str]) -> List[str]: + minfreq = [float('inf') for _ in range(26)] + for word in words: + freq = [0 for _ in range(26)] + for ch in word: + freq[ord(ch) - ord('a')] += 1 + for i in range(26): + minfreq[i] = min(minfreq[i], freq[i]) + + res = [] + for i in range(26): + while minfreq[i]: + res.append(chr(i + ord('a'))) + minfreq[i] -= 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times (|\sum| + m))$,其中 $n$ 为字符串数组 $words$ 的长度,$m$ 为每个字符串的平均长度,$|\sum|$ 为字符集。 +- **空间复杂度**:$O(|\sum|)$。 + diff --git a/docs/solutions/1000-1099/find-in-mountain-array.md b/docs/solutions/1000-1099/find-in-mountain-array.md new file mode 100644 index 00000000..0ab7b19b --- /dev/null +++ b/docs/solutions/1000-1099/find-in-mountain-array.md @@ -0,0 +1,117 @@ +# [1095. 山脉数组中查找目标值](https://leetcode.cn/problems/find-in-mountain-array/) + +- 标签:数组、二分查找、交互 +- 难度:困难 + +## 题目链接 + +- [1095. 山脉数组中查找目标值 - 力扣](https://leetcode.cn/problems/find-in-mountain-array/) + +## 题目大意 + +**描述**:给定一个山脉数组 $mountainArr$。 + +**要求**:返回能够使得 `mountainArr.get(index)` 等于 $target$ 最小的下标 $index$ 值。如果不存在这样的下标 $index$,就请返回 $-1$。 + +**说明**: + +- 山脉数组:满足以下属性的数组: + + - $len(arr) \ge 3$; + - 存在 $i$($0 < i < len(arr) - 1$),使得: + - $arr[0] < arr[1] < ... arr[i-1] < arr[i]$; + - $arr[i] > arr[i+1] > ... > arr[len(arr) - 1]$。 +- 不能直接访问该山脉数组,必须通过 `MountainArray` 接口来获取数据: + + - `MountainArray.get(index)`:会返回数组中索引为 $k$ 的元素(下标从 $0$ 开始)。 + + - `MountainArray.length()`:会返回该数组的长度。 +- 对 `MountainArray.get` 发起超过 $100$ 次调用的提交将被视为错误答案。 +- $3 \le mountain_arr.length() \le 10000$。 +- $0 \le target \le 10^9$。 +- $0 \le mountain_arr.get(index) \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:array = [1,2,3,4,5,3,1], target = 3 +输出:2 +解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。 +``` + +- 示例 2: + +```python +输入:array = [0,1,2,4,2,1], target = 3 +输出:-1 +解释:3 在数组中没有出现,返回 -1。 +``` + +## 解题思路 + +### 思路 1:二分查找 + +因为题目要求不能对 `MountainArray.get` 发起超过 $100$ 次调用。所以遍历数组进行查找是不可行的。 + +根据山脉数组的性质,我们可以把山脉数组分为两部分:「前半部分的升序数组」和「后半部分的降序数组」。在有序数组中查找目标值可以使用二分查找来减少查找次数。 + +而山脉的峰顶元素索引也可以通过二分查找来做。所以这道题我们可以分为三步: + +1. 通过二分查找找到山脉数组的峰顶元素索引。 +2. 通过二分查找在前半部分的升序数组中查找目标元素。 +3. 通过二分查找在后半部分的降序数组中查找目标元素。 + +最后,通过对查找结果的判断来输出最终答案。 + +### 思路 1:代码 + +```python +#class MountainArray: +# def get(self, index: int) -> int: +# def length(self) -> int: + +class Solution: + def binarySearchPeak(self, mountain_arr) -> int: + left, right = 0, mountain_arr.length() - 1 + while left < right: + mid = left + (right - left) // 2 + if mountain_arr.get(mid) < mountain_arr.get(mid + 1): + left = mid + 1 + else: + right = mid + return left + + def binarySearchAscending(self, mountain_arr, left, right, target): + while left < right: + mid = left + (right - left) // 2 + if mountain_arr.get(mid) < target: + left = mid + 1 + else: + right = mid + return left if mountain_arr.get(left) == target else -1 + + def binarySearchDescending(self, mountain_arr, left, right, target): + while left < right: + mid = left + (right - left) // 2 + if mountain_arr.get(mid) > target: + left = mid + 1 + else: + right = mid + return left if mountain_arr.get(left) == target else -1 + + def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int: + size = mountain_arr.length() + peek_i = self.binarySearchPeak(mountain_arr) + + res_left = self.binarySearchAscending(mountain_arr, 0, peek_i, target) + res_right = self.binarySearchDescending(mountain_arr, peek_i + 1, size - 1, target) + + return res_left if res_left != -1 else res_right +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1000-1099/grumpy-bookstore-owner.md b/docs/solutions/1000-1099/grumpy-bookstore-owner.md new file mode 100644 index 00000000..529c7352 --- /dev/null +++ b/docs/solutions/1000-1099/grumpy-bookstore-owner.md @@ -0,0 +1,92 @@ +# [1052. 爱生气的书店老板](https://leetcode.cn/problems/grumpy-bookstore-owner/) + +- 标签:数组、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1052. 爱生气的书店老板 - 力扣](https://leetcode.cn/problems/grumpy-bookstore-owner/) + +## 题目大意 + +**描述**:书店老板有一家店打算试营业 $len(customers)$ 分钟。每一分钟都有一些顾客 $customers[i]$ 会进入书店,这些顾客会在这一分钟结束后离开。 + +在某些时候,书店老板会生气。如果书店老板在第 $i$ 分钟生气,则 `grumpy[i] = 1`,如果第 $i$ 分钟不生气,则 `grumpy[i] = 0`。当书店老板生气时,这一分钟的顾客会不满意。当书店老板不生气时,这一分钟的顾客是满意的。 + +假设老板知道一个秘密技巧,能保证自己连续 $minutes$ 分钟不生气,但只能使用一次。 + +现在给定代表每分钟进入书店的顾客数量的数组 $customes$,和代表老板生气状态的数组 $grumpy$,以及老板保证连续不生气的分钟数 $minutes$。 + +**要求**:计算出试营业下来,最多有多少客户能够感到满意。 + +**说明**: + +- $n == customers.length == grumpy.length$。 +- $1 \le minutes \le n \le 2 \times 10^4$。 +- $0 \le customers[i] \le 1000$。 +- $grumpy[i] == 0 \text{ or } 1$。 + +**示例**: + +- 示例 1: + +```python +输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3 +输出:16 +解释:书店老板在最后 3 分钟保持冷静。 +感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16. +``` + +- 示例 2: + +```python +输入:customers = [1], grumpy = [0], minutes = 1 +输出:1 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +固定长度的滑动窗口题目。我们可以维护一个窗口大小为 $minutes$ 的滑动窗口。使用 $window_count$ 记录当前窗口内生气的顾客人数。然后滑动求出窗口中最大顾客数,然后累加上老板未生气时的顾客数,就是答案。具体做法如下: + +1. $ans$ 用来维护答案数目。$window\underline{\hspace{0.5em}}count$ 用来维护窗口中生气的顾客人数。 +2. $left$ 、$right$ 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +3. 如果书店老板生气,则将这一分钟的顾客数量加入到 $window\underline{\hspace{0.5em}}count$ 中,然后向右移动 $right$。 +4. 当窗口元素个数大于 $minutes$ 时,即:$right - left + 1 > count$ 时,如果最左侧边界老板处于生气状态,则向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为小于 $minutes$。 +5. 重复 $3 \sim 4$ 步,直到 $right$ 到达数组末尾。 +6. 然后累加上老板未生气时的顾客数,最后输出答案。 + +### 思路 1:代码 + +```python +class Solution: + def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int: + left = 0 + right = 0 + window_count = 0 + ans = 0 + + while right < len(customers): + if grumpy[right] == 1: + window_count += customers[right] + + if right - left + 1 > minutes: + if grumpy[left] == 1: + window_count -= customers[left] + left += 1 + + right += 1 + ans = max(ans, window_count) + + for i in range(len(customers)): + if grumpy[i] == 0: + ans += customers[i] + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $coustomer$、$grumpy$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1000-1099/height-checker.md b/docs/solutions/1000-1099/height-checker.md new file mode 100644 index 00000000..e0c12fce --- /dev/null +++ b/docs/solutions/1000-1099/height-checker.md @@ -0,0 +1,114 @@ +# [1051. 高度检查器](https://leetcode.cn/problems/height-checker/) + +- 标签:数组、计数排序、排序 +- 难度:简单 + +## 题目链接 + +- [1051. 高度检查器 - 力扣](https://leetcode.cn/problems/height-checker/) + +## 题目大意 + +**描述**:学校打算为全体学生拍一张年度纪念照。根据要求,学生需要按照 非递减 的高度顺序排成一行。 + +排序后的高度情况用整数数组 $expected$ 表示,其中 $expected[i]$ 是预计排在这一行中第 $i$ 位的学生的高度(下标从 $0$ 开始)。 + +给定一个整数数组 $heights$ ,表示当前学生站位的高度情况。$heights[i]$ 是这一行中第 $i$ 位学生的高度(下标从 $0$ 开始)。 + +**要求**:返回满足 $heights[i] \ne expected[i]$ 的下标数量 。 + +**说明**: + +- $1 \le heights.length \le 100$。 +- $1 \le heights[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:heights = [1,1,4,2,1,3] +输出:3 +解释: +高度:[1,1,4,2,1,3] +预期:[1,1,1,2,3,4] +下标 2 、4 、5 处的学生高度不匹配。 +``` + +- 示例 2: + +```python +输入:heights = [5,1,2,3,4] +输出:5 +解释: +高度:[5,1,2,3,4] +预期:[1,2,3,4,5] +所有下标的对应学生高度都不匹配。 +``` + +## 解题思路 + +### 思路 1:排序算法 + +1. 将数组 $heights$ 复制一份,记为 $expected$。 +2. 对数组 $expected$ 进行排序。 +3. 排序之后,对比并统计 $heights[i] \ne expected[i]$ 的下标数量,记为 $ans$。 +4. 返回 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def heightChecker(self, heights: List[int]) -> int: + expected = sorted(heights) + + ans = 0 + for i in range(len(heights)): + if expected[i] != heights[i]: + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $heights$ 的长度。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:计数排序 + +题目中 $heights[i]$ 的数据范围为 $[1, 100]$,所以我们可以使用计数排序。 + +### 思路 2:代码 + +```python +class Solution: + def heightChecker(self, heights: List[int]) -> int: + # 待排序数组中最大值元素 heights_max = 100 和最小值元素 heights_min = 1 + heights_min, heights_max = 1, 100 + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 + size = heights_max - heights_min + 1 + counts = [0 for _ in range(size)] + + # 统计值为 height 的元素出现的次数 + for height in heights: + counts[height - heights_min] += 1 + + ans = 0 + idx = 0 + # 从小到大遍历 counts 的元素值范围 + for height in range(heights_min, heights_max + 1): + while counts[height - heights_min]: + # 对于每个元素值,判断是否与对应位置上的 heights[idx] 相等 + if heights[idx] != height: + ans += 1 + idx += 1 + counts[height - heights_min] -= 1 + + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n + k)$,其中 $n$ 为数组 $heights$ 的长度,$k$ 为数组 $heights$ 的值域范围。 +- **空间复杂度**:$O(k)$。 + diff --git a/docs/solutions/1000-1099/index-pairs-of-a-string.md b/docs/solutions/1000-1099/index-pairs-of-a-string.md new file mode 100644 index 00000000..22997f61 --- /dev/null +++ b/docs/solutions/1000-1099/index-pairs-of-a-string.md @@ -0,0 +1,75 @@ +# [1065. 字符串的索引对](https://leetcode.cn/problems/index-pairs-of-a-string/) + +- 标签:字典树、数组、字符串、排序 +- 难度:简单 + +## 题目链接 + +- [1065. 字符串的索引对 - 力扣](https://leetcode.cn/problems/index-pairs-of-a-string/) + +## 题目大意 + +给定字符串 `text` 和单词列表 `words`。 + +要求:在 `text` 中找出所有属于单词列表 `words` 中的单词,并返回该单词在 `text` 中的索引对位置 `[i, j]`。将所有索引对存入列表中返回,并且返回的索引对可以交叉。 + +## 解题思路 + +构建字典树,将所有单词存入字典树中。 + +然后一重循环遍历 `text`,表示从第 `i` 位置开始的字符串 `text[i:]`。然后在字符串前缀中搜索对应的单词,将所有符合要求的单词末尾位置存入列表中,返回所有位置列表。对于列表中每个单词末尾位置 `index` 和 `text` 来说,每个 `[i, i + index]` 都构成了单词在 `text` 中的索引对位置,将其存入答案数组并返回即可。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, text: str) -> list: + """ + Returns if the word is in the trie. + """ + cur = self + res = [] + for i in range(len(text)): + ch = text[i] + if ch not in cur.children: + return res + cur = cur.children[ch] + if cur.isEnd: + res.append(i) + + return res + +class Solution: + def indexPairs(self, text: str, words: List[str]) -> List[List[int]]: + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + res = [] + for i in range(len(text)): + for index in trie_tree.search(text[i:]): + res.append([i, i + index]) + return res +``` + diff --git a/docs/solutions/1000-1099/index.md b/docs/solutions/1000-1099/index.md new file mode 100644 index 00000000..009d52e9 --- /dev/null +++ b/docs/solutions/1000-1099/index.md @@ -0,0 +1,34 @@ +## 本章内容 + +- [1000. 合并石头的最低成本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md) +- [1002. 查找共用字符](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/find-common-characters.md) +- [1004. 最大连续1的个数 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/max-consecutive-ones-iii.md) +- [1005. K 次取反后最大化的数组和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/maximize-sum-of-array-after-k-negations.md) +- [1008. 前序遍历构造二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/construct-binary-search-tree-from-preorder-traversal.md) +- [1009. 十进制整数的反码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/complement-of-base-10-integer.md) +- [1011. 在 D 天内送达包裹的能力](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/capacity-to-ship-packages-within-d-days.md) +- [1012. 至少有 1 位重复的数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/numbers-with-repeated-digits.md) +- [1014. 最佳观光组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/best-sightseeing-pair.md) +- [1020. 飞地的数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/number-of-enclaves.md) +- [1021. 删除最外层的括号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-outermost-parentheses.md) +- [1023. 驼峰式匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/camelcase-matching.md) +- [1025. 除数博弈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/divisor-game.md) +- [1028. 从先序遍历还原二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/recover-a-tree-from-preorder-traversal.md) +- [1029. 两地调度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-city-scheduling.md) +- [1034. 边界着色](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/coloring-a-border.md) +- [1035. 不相交的线](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/uncrossed-lines.md) +- [1037. 有效的回旋镖](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/valid-boomerang.md) +- [1038. 从二叉搜索树到更大和树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/binary-search-tree-to-greater-sum-tree.md) +- [1039. 多边形三角剖分的最低得分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md) +- [1041. 困于环中的机器人](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/robot-bounded-in-circle.md) +- [1047. 删除字符串中的所有相邻重复项](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md) +- [1049. 最后一块石头的重量 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/last-stone-weight-ii.md) +- [1051. 高度检查器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/height-checker.md) +- [1052. 爱生气的书店老板](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/grumpy-bookstore-owner.md) +- [1065. 字符串的索引对](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/index-pairs-of-a-string.md) +- [1079. 活字印刷](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/letter-tile-possibilities.md) +- [1081. 不同字符的最小子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/smallest-subsequence-of-distinct-characters.md) +- [1089. 复写零](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/duplicate-zeros.md) +- [1091. 二进制矩阵中的最短路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/shortest-path-in-binary-matrix.md) +- [1095. 山脉数组中查找目标值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/find-in-mountain-array.md) +- [1099. 小于 K 的两数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/two-sum-less-than-k.md) diff --git a/docs/solutions/1000-1099/last-stone-weight-ii.md b/docs/solutions/1000-1099/last-stone-weight-ii.md new file mode 100644 index 00000000..3eb5250f --- /dev/null +++ b/docs/solutions/1000-1099/last-stone-weight-ii.md @@ -0,0 +1,99 @@ +# [1049. 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1049. 最后一块石头的重量 II - 力扣](https://leetcode.cn/problems/last-stone-weight-ii/) + +## 题目大意 + +**描述**:有一堆石头,用整数数组 $stones$ 表示,其中 $stones[i]$ 表示第 $i$​ 块石头的重量。每一回合,从石头中选出任意两块石头,将这两块石头一起粉碎。假设石头的重量分别为 $x$ 和 $y$。且 $x \le y$,则结果如下: + +- 如果 $x = y$,则两块石头都会被完全粉碎; +- 如果 $x < y$,则重量为 $x$ 的石头被完全粉碎,而重量为 $y$ 的石头新重量为 $y - x$。 + +**要求**:最后,最多只会剩下一块石头,返回此石头的最小可能重量。如果没有石头剩下,则返回 $0$。 + +**说明**: + +- $1 \le stones.length \le 30$。 +- $1 \le stones[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:stones = [2,7,4,1,8,1] +输出:1 +解释: +组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], +组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], +组合 2 和 1,得到 1,所以数组转化为 [1,1,1], +组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。 +``` + +- 示例 2: + +```python +输入:stones = [31,26,33,21,40] +输出:5 +``` + +## 解题思路 + +### 思路 1:动态规划 + +选取两块石头,重新放回去的重量是两块石头的差值绝对值。重新放回去的石头还会进行选取,然后进行粉碎,直到最后只剩一块或者不剩石头。 + +这个问题其实可以转化为:把一堆石头尽量平均的分成两对,求两堆石头重量差的最小值。 + +这就和「[0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)」有点相似。两堆石头的重量要尽可能的接近数组总数量和的一半。 + +进一步可以变为:「0-1 背包问题」。 + +1. 假设石头总重量和为 $sum$,将一堆石头放进载重上限为 $sum / 2$ 的背包中,获得的最大价值为 $max\underline{\hspace{0.5em}}weight$(即其中一堆石子的重量)。另一堆石子的重量为 $sum - max\underline{\hspace{0.5em}}weight$。 +2. 则两者的差值为 $sum - 2 \times max\underline{\hspace{0.5em}}weight$,即为答案。 + +###### 1. 划分阶段 + +按照石头的序号进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = max \lbrace dp[w], dp[w - stones[i - 1]] + stones[i - 1] \rbrace$。 + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择石头,可以获得的最大价值一定是 $0$,即 $dp[w] = 0, 0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值,即第一堆石头的价值为 $dp[size]$,第二堆石头的价值为 $sum - dp[size]$,最终答案为两者的差值,即 $sum - dp[size] \times 2$。 + +### 思路 1:代码 + +```python +class Solution: + def lastStoneWeightII(self, stones: List[int]) -> int: + W = 1500 + size = len(stones) + dp = [0 for _ in range(W + 1)] + target = sum(stones) // 2 + for i in range(1, size + 1): + for w in range(target, stones[i - 1] - 1, -1): + dp[w] = max(dp[w], dp[w - stones[i - 1]] + stones[i - 1]) + + return sum(stones) - dp[target] * 2 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为数组 $stones$ 的元素个数,$W$ 为数组 $stones$ 中元素和的一半。 +- **空间复杂度**:$O(W)$。 diff --git a/docs/solutions/1000-1099/letter-tile-possibilities.md b/docs/solutions/1000-1099/letter-tile-possibilities.md new file mode 100644 index 00000000..0958691f --- /dev/null +++ b/docs/solutions/1000-1099/letter-tile-possibilities.md @@ -0,0 +1,79 @@ +# [1079. 活字印刷](https://leetcode.cn/problems/letter-tile-possibilities/) + +- 标签:哈希表、字符串、回溯、计数 +- 难度:中等 + +## 题目链接 + +- [1079. 活字印刷 - 力扣](https://leetcode.cn/problems/letter-tile-possibilities/) + +## 题目大意 + +**描述**:给定一个代表活字字模的字符串 $tiles$,其中 $tiles[i]$ 表示第 $i$ 个字模上刻的字母。 + +**要求**:返回你可以印出的非空字母序列的数目。 + +**说明**: + +- 本题中,每个活字字模只能使用一次。 +- $1 <= tiles.length <= 7$。 +- $tiles$ 由大写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:"AAB" +输出:8 +解释:可能的序列为 "A", "B", "AA", "AB", "BA", "AAB", "ABA", "BAA"。 +``` + +- 示例 2: + +```python +输入:"AAABBC" +输出:188 +``` + +## 解题思路 + +### 思路 1:哈希表 + 回溯算法 + +1. 使用哈希表存储每个字符的个数。 +2. 然后依次从哈希表中取出对应字符,统计排列个数,并进行回溯。 +3. 如果当前字符个数为 $0$,则不再进行回溯。 +4. 回溯之后将状态回退。 + +### 思路 1:代码 + +```python +class Solution: + ans = 0 + def backtrack(self, tile_map): + for key, value in tile_map.items(): + if value == 0: + continue + self.ans += 1 + tile_map[key] -= 1 + self.backtrack(tile_map) + tile_map[key] += 1 + + def numTilePossibilities(self, tiles: str) -> int: + tile_map = dict() + for tile in tiles: + if tile not in tile_map: + tile_map[tile] = 1 + else: + tile_map[tile] += 1 + + self.backtrack(tile_map) + + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times n!)$,其中 $n$ 表示 $tiles$ 的长度最小值。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1000-1099/max-consecutive-ones-iii.md b/docs/solutions/1000-1099/max-consecutive-ones-iii.md new file mode 100644 index 00000000..39bb3c93 --- /dev/null +++ b/docs/solutions/1000-1099/max-consecutive-ones-iii.md @@ -0,0 +1,75 @@ +# [1004. 最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) + +- 标签:数组、二分查找、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1004. 最大连续1的个数 III - 力扣](https://leetcode.cn/problems/max-consecutive-ones-iii/) + +## 题目大意 + +**描述**:给定一个由 $0$、$1$ 组成的数组 $nums$,再给定一个整数 $k$。最多可以将 $k$ 个值从 $0$ 变到 $1$。 + +**要求**:返回仅包含 $1$ 的最长连续子数组的长度。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $nums[i]$ 不是 $0$ 就是 $1$。 +- $0 \le k \le nums.length$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], K = 2 +输出:6 +解释:[1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1] +将 nums[5]、nums[10] 从 0 翻转到 1,最长的子数组长度为 6。 +``` + +- 示例 2: + +```python +输入:nums = [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1], K = 3 +输出:10 +解释:[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1] +将 nums[4]、nums[5]、nums[9] 从 0 翻转到 1,最长的子数组长度为 10。 +``` + +## 解题思路 + +### 思路 1:滑动窗口(不定长度) + +1. 使用两个指针 $left$、$right$ 指向数组开始位置。使用 $max\underline{\hspace{0.5em}}count$ 来维护仅包含 $1$ 的最长连续子数组的长度。 +2. 不断右移 $right$ 指针,扩大滑动窗口范围,并统计窗口内 $0$ 元素的个数。 +3. 直到 $0$ 元素的个数超过 $k$ 时将 $left$ 右移,缩小滑动窗口范围,并减小 $0$ 元素的个数,同时维护 $max\underline{\hspace{0.5em}}count$。 +4. 最后输出最长连续子数组的长度 $max\underline{\hspace{0.5em}}count$。 + +### 思路 1:代码 + +```python +class Solution: + def longestOnes(self, nums: List[int], k: int) -> int: + max_count = 0 + zero_count = 0 + left, right = 0, 0 + while right < len(nums): + if nums[right] == 0: + zero_count += 1 + right += 1 + if zero_count > k: + if nums[left] == 0: + zero_count -= 1 + left += 1 + max_count = max(max_count, right - left) + return max_count +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1000-1099/maximize-sum-of-array-after-k-negations.md b/docs/solutions/1000-1099/maximize-sum-of-array-after-k-negations.md new file mode 100644 index 00000000..8170dfb9 --- /dev/null +++ b/docs/solutions/1000-1099/maximize-sum-of-array-after-k-negations.md @@ -0,0 +1,42 @@ +# [1005. K 次取反后最大化的数组和](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) + +- 标签:贪心、数组、排序 +- 难度:简单 + +## 题目链接 + +- [1005. K 次取反后最大化的数组和 - 力扣](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) + +## 题目大意 + +给定一个整数数组 nums 和一个整数 k。只能用下面的方法修改数组: + +- 将数组上第 i 个位置上的值取相反数,即将 `nums[i]` 变为 `-nums[i]`。 + +用这种方式进行 K 次修改(可以多次修改同一个位置 i) 后,返回数组可能的最大和。 + +## 解题思路 + +- 先将数组按绝对值大小进行排序 +- 从绝对值大的数开始遍历数组,如果 nums[i] < 0,并且 k > 0: + - 则对 nums[i] 取相反数,并将 k 值 -1。 +- 如果最后 k 还有余值,则判断奇偶性: + - 若 k 为奇数,则将数组绝对值最小的数进行取反。 + - 若 k 为偶数,则说明可将某一位数进行偶数次取反,和原数值一致,则不需要进行操作。 +- 最后返回数组和。 + +## 代码 + +```python +class Solution: + def largestSumAfterKNegations(self, nums: List[int], k: int) -> int: + nums.sort(key=lambda x: abs(x), reverse = True) + for i in range(len(nums)): + if nums[i] < 0 and k > 0: + nums[i] *= -1 + k -= 1 + if k % 2 == 1: + nums[-1] *= -1 + return sum(nums) +``` + diff --git a/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md b/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md new file mode 100644 index 00000000..ddb901ba --- /dev/null +++ b/docs/solutions/1000-1099/minimum-cost-to-merge-stones.md @@ -0,0 +1,200 @@ +# [1000. 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) + +- 标签:数组、动态规划、前缀和 +- 难度:困难 + +## 题目链接 + +- [1000. 合并石头的最低成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) + +## 题目大意 + +**描述**:给定一个代表 $n$ 堆石头的整数数组 $stones$,其中 $stones[i]$ 代表第 $i$ 堆中的石头个数。再给定一个整数 $k$, 每次移动需要将连续的 $k$ 堆石头合并为一堆,而这次移动的成本为这 $k$ 堆中石头的总数。 + +**要求**:返回把所有石头合并成一堆的最低成本。如果无法合并成一堆,则返回 $-1$。 + +**说明**: + +- $n == stones.length$。 +- $1 \le n \le 30$。 +- $1 \le stones[i] \le 100$。 +- $2 \le k \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入:stones = [3,2,4,1], K = 2 +输出:20 +解释: +从 [3, 2, 4, 1] 开始。 +合并 [3, 2],成本为 5,剩下 [5, 4, 1]。 +合并 [4, 1],成本为 5,剩下 [5, 5]。 +合并 [5, 5],成本为 10,剩下 [10]。 +总成本 20,这是可能的最小值。 +``` + +- 示例 2: + +```python +输入:stones = [3,5,1,2,6], K = 3 +输出:25 +解释: +从 [3, 5, 1, 2, 6] 开始。 +合并 [5, 1, 2],成本为 8,剩下 [3, 8, 6]。 +合并 [3, 8, 6],成本为 17,剩下 [17]。 +总成本 25,这是可能的最小值。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 前缀和 + +每次将 $k$ 堆连续的石头合并成 $1$ 堆,石头堆数就会减少 $k - 1$ 堆。总共有 $n$ 堆石子,则: + +1. 当 $(n - 1) \mod (k - 1) == 0$ 时,一定可以经过 $\frac{n - 1}{k - 1}$ 次合并,将 $n$ 堆石头合并为 $1$ 堆。 +2. 当 $(n - 1) \mod (k - 1) \ne 0$ 时,则无法将所有的石头合并成一堆。 + +根据以上情况,我们可以先将无法将所有的石头合并成一堆的情况排除出去,接下来只考虑合法情况。 + +由于每次合并石头的成本为合并的 $k$ 堆的石子总数,即数组 $stones$ 中长度为 $k$ 的连续子数组和,因此为了快速计算数组 $stones$ 的连续子数组和,我们可以使用「前缀和」的方式,预先计算出「前 $i$ 堆的石子总数」,从而可以在 $O(1)$ 的时间复杂度内得到数组 $stones$ 的连续子数组和。 + +$k$ 堆石头合并为 $1$ 堆石头的过程,可以看做是长度为 $k$ 的连续子数组合并为长度为 $1$ 的子数组的过程,也可以看做是将长度为 $k$ 的区间合并为长度为 $1$ 的区间。 + +接下来我们就可以按照「区间 DP 问题」的基本思路来做。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 + +###### 3. 状态转移方程 + +我们将区间 $[i, j]$ 的石堆合并成 $m$ 堆,可以枚举 $i \le n \le j$,将区间 $[i, j]$ 拆分为两个区间 $[i, n]$ 和 $[n + 1, j]$。然后将 $[i, n]$ 中的石头合并为 $1$ 堆,将 $[n + 1, j]$ 中的石头合并成 $m - 1$ 堆。最后将 $1$ 堆石头和 $m - 1$ 堆石头合并成 $1$ 堆,这样就可以将 $[i, j]$ 的石堆合并成 $k$ 堆。则状态转移方程为:$dp[i][j][m] = min_{i \le n < j} \lbrace dp[i][n][1] + dp[n + 1][j][m - 1] \rbrace$。 + +我们再将区间 $[i, j]$ 的 $k$ 堆石头合并成 $1$ 堆,其成本为 区间 $[i, j]$ 的石堆合并成 $k$ 堆的成本,加上将这 $k$ 堆石头合并成 $1$ 堆的成本,即状态转移方程为:$dp[i][j][1] = dp[i][j][k] + \sum_{t = i}^{t = j} stones[t]$。 + +###### 4. 初始条件 + +- 长度为 $1$ 的区间 $[i, i]$ 合并为 $1$ 堆成本为 $0$,即:$dp[i][i][1] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 所以最终结果为 $dp[1][size][1]$,其中 $size$ 为数组 $stones$ 的长度。 + +### 思路 1:代码 + +```python +class Solution: + def mergeStones(self, stones: List[int], k: int) -> int: + size = len(stones) + if (size - 1) % (k - 1) != 0: + return -1 + + prefix = [0 for _ in range(size + 1)] + for i in range(1, size + 1): + prefix[i] = prefix[i - 1] + stones[i - 1] + + dp = [[[float('inf') for _ in range(k + 1)] for _ in range(size)] for _ in range(size)] + + for i in range(size): + dp[i][i][1] = 0 + + for l in range(2, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + for m in range(2, k + 1): + for n in range(i, j, k - 1): + dp[i][j][m] = min(dp[i][j][m], dp[i][n][1] + dp[n + 1][j][m - 1]) + dp[i][j][1] = dp[i][j][k] + prefix[j + 1] - prefix[i] + + return dp[0][size - 1][1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3 \times k)$,其中 $n$ 是数组 $stones$ 的长度。 +- **空间复杂度**:$O(n^2 \times k)$。 + +### 思路 2:动态规划 + 状态优化 + +在思路 1 中,我们使用定义状态 $dp[i][j][m]$ 表示为:将区间 $[i, j]$ 的石堆合并成 $m$ 堆的最低成本,其中 $m$ 的取值为 $[1,k]$。 + +事实上,对于固定区间 $[i, j]$,初始时堆数为 $j - i + 1$,每次合并都会减少 $k - 1$ 堆,合并到无法合并时的堆数固定为 $(j - i) \mod (k - 1) + 1$。 + +所以,我们可以直接定义状态 $dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。 + +具体步骤如下: + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。 + +###### 3. 状态转移方程 + +枚举 $i \le n \le j$,将区间 $[i, j]$ 拆分为两个区间 $[i, n]$ 和 $[n + 1, j]$。然后将区间 $[i, n]$ 合并成 $1$ 堆,$[n + 1, j]$ 合并成 $m$ 堆。 + +$dp[i][j] = min_{i \le n < j} \lbrace dp[i][n] + dp[n + 1][j] \rbrace$。 + +如果 $(j - i) \mod (k - 1) == 0$,则说明区间 $[i, j]$ 能狗合并为 1 堆,则加上区间子数组和,即 $dp[i][j] += prefix[j + 1] - prefix[i]$。 + +###### 4. 初始条件 + +- 长度为 $1$ 的区间 $[i, i]$ 合并到无法合并时的最低成本为 $0$,即:$dp[i][i] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:将区间 $[i, j]$ 的石堆合并到无法合并时的最低成本。所以最终结果为 $dp[0][size - 1]$,其中 $size$ 为数组 $stones$ 的长度。 + +### 思路 2:代码 + +```python +class Solution: + def mergeStones(self, stones: List[int], k: int) -> int: + size = len(stones) + if (size - 1) % (k - 1) != 0: + return -1 + + prefix = [0 for _ in range(size + 1)] + for i in range(1, size + 1): + prefix[i] = prefix[i - 1] + stones[i - 1] + + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + + for i in range(size): + dp[i][i] = 0 + + for l in range(2, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + # 遍历每一个可以组成 k 堆石子的分割点 n,每次递增 k - 1 个 + for n in range(i, j, k - 1): + # 判断 [i, n] 到 [n + 1, j] 是否比之前花费小 + dp[i][j] = min(dp[i][j], dp[i][n] + dp[n + 1][j]) + # 如果 [i, j] 能狗合并为 1 堆,则加上区间子数组和 + if (l - 1) % (k - 1) == 0: + dp[i][j] += prefix[j + 1] - prefix[i] + + return dp[0][size - 1] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 是数组 $stones$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + +## 参考资料 + +- 【题解】[一题一解:动态规划(区间 DP)+ 前缀和(清晰题解) - 合并石头的最低成本](https://leetcode.cn/problems/minimum-cost-to-merge-stones/solution/python3javacgo-yi-ti-yi-jie-dong-tai-gui-lr9q/) diff --git a/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md b/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md new file mode 100644 index 00000000..83550692 --- /dev/null +++ b/docs/solutions/1000-1099/minimum-score-triangulation-of-polygon.md @@ -0,0 +1,111 @@ +# [1039. 多边形三角剖分的最低得分](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1039. 多边形三角剖分的最低得分 - 力扣](https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/) + +## 题目大意 + +**描述**:有一个凸的 $n$ 边形,其每个顶点都有一个整数值。给定一个整数数组 $values$,其中 $values[i]$ 是第 $i$ 个顶点的值(即顺时针顺序)。 + +现在要将 $n$ 边形剖分为 $n - 2$ 个三角形,对于每个三角形,该三角形的值是顶点标记的乘积,$n$ 边形三角剖分的分数是进行三角剖分后所有 $n - 2$ 个三角形的值之和。 + +**要求**:返回多边形进行三角剖分可以得到的最低分。 + +**说明**: + +- $n == values.length$。 +- $3 \le n \le 50$。 +- $1 \le values[i] \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/25/shape1.jpg) + +```python +输入:values = [1,2,3] +输出:6 +解释:多边形已经三角化,唯一三角形的分数为 6。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/25/shape2.jpg) + +```python +输入:values = [3,7,4,5] +输出:144 +解释:有两种三角剖分,可能得分分别为:3*7*5 + 4*5*7 = 245,或 3*4*5 + 3*4*7 = 144。最低分数为 144。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +对于 $0 \sim n - 1$ 个顶点组成的凸多边形进行三角剖分,我们可以在 $[0, n - 1]$ 中任选 $1$ 个点 $k$,从而将凸多边形划分为: + +1. 顶点 $0 \sim k$ 组成的凸多边形。 +2. 顶点 $0$、$k$、$n - 1$ 组成的三角形。 +3. 顶点 $k \sim n - 1$ 组成的凸多边形。 + +对于顶点 $0$、$k$、$n - 1$ 组成的三角形,我们可以直接计算对应的三角剖分分数为 $values[0] \times values[k] \times values[n - 1]$。 + +而对于顶点 $0 \sim k$ 组成的凸多边形和顶点 $k \sim n - 1$ 组成的凸多边形,我们可以利用递归或者动态规划的思想,定义一个 $dp[i][j]$ 用于计算顶点 $i$ 到顶点 $j$ 组成的多边形三角剖分的最小分数。 + +具体做法如下: + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:区间 $[i, j]$ 内三角剖分后的最小分数。 + +###### 3. 状态转移方程 + +对于区间 $[i, j]$,枚举分割点 $k$,最小分数为 $min(dp[i][k] + dp[k][j] + values[i] \times values[k] \times values[j])$,即:$dp[i][j] = min(dp[i][k] + dp[k][j] + values[i] \times values[k] \times values[j])$。 + +###### 4. 初始条件 + +- 默认情况下,所有区间 $[i, j]$ 的最小分数为无穷大。 +- 当区间 $[i, j]$ 长度小于 $3$ 时,无法进行三角剖分,其最小分数为 $0$。 +- 当区间 $[i, j]$ 长度等于 $3$ 时,其三角剖分的最小分数为 $values[i] * values[i + 1] * values[i + 2]$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:区间 $[i, j]$ 内三角剖分后的最小分数。。 所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def minScoreTriangulation(self, values: List[int]) -> int: + size = len(values) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for l in range(1, size + 1): + for i in range(size): + j = i + l - 1 + if j >= size: + break + if l < 3: + dp[i][j] = 0 + elif l == 3: + dp[i][j] = values[i] * values[i + 1] * values[i + 2] + else: + for k in range(i + 1, j): + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + values[i] * values[j] * values[k]) + + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 为顶点个数。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/1000-1099/number-of-enclaves.md b/docs/solutions/1000-1099/number-of-enclaves.md new file mode 100644 index 00000000..be9e5cf4 --- /dev/null +++ b/docs/solutions/1000-1099/number-of-enclaves.md @@ -0,0 +1,99 @@ +# [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1020. 飞地的数量 - 力扣](https://leetcode.cn/problems/number-of-enclaves/) + +## 题目大意 + +**描述**:给定一个二维数组 `grid`,每个单元格为 `0`(代表海)或 `1`(代表陆地)。我们可以从一个陆地走到另一个陆地上(朝四个方向之一),然后从边界上的陆地离开网络的边界。 + +**要求**:返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。 + +**说明**: + +- $m == grid.length$。 +- $n == grid[i].length$。 +- $1 \le m, n \le 500$。 +- $grid[i][j]$ 的值为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/02/18/enclaves1.jpg) + +```python +输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]] +输出:3 +解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/02/18/enclaves2.jpg) + +```python +输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]] +输出:0 +解释:所有 1 都在边界上或可以到达边界。 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +与四条边界相连的陆地单元是肯定能离开网络边界的。 + +我们可以先通过深度优先搜索将与四条边界相关的陆地全部变为海(赋值为 `0`)。 + +然后统计网格中 `1` 的数量,即为答案。 + +### 思路 1:代码 + +```python +class Solution: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(self, grid, i, j): + rows = len(grid) + cols = len(grid[0]) + if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == 0: + return + grid[i][j] = 0 + + for direct in self.directs: + new_i = i + direct[0] + new_j = j + direct[1] + self.dfs(grid, new_i, new_j) + + def numEnclaves(self, grid: List[List[int]]) -> int: + rows = len(grid) + cols = len(grid[0]) + for i in range(rows): + if grid[i][0] == 1: + self.dfs(grid, i, 0) + if grid[i][cols - 1] == 1: + self.dfs(grid, i, cols - 1) + + for j in range(cols): + if grid[0][j] == 1: + self.dfs(grid, 0, j) + if grid[rows - 1][j] == 1: + self.dfs(grid, rows - 1, j) + + ans = 0 + for i in range(rows): + for j in range(cols): + if grid[i][j] == 1: + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git a/docs/solutions/1000-1099/numbers-with-repeated-digits.md b/docs/solutions/1000-1099/numbers-with-repeated-digits.md new file mode 100644 index 00000000..2aeb6b7b --- /dev/null +++ b/docs/solutions/1000-1099/numbers-with-repeated-digits.md @@ -0,0 +1,107 @@ +# [1012. 至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) + +- 标签:数学、动态规划 +- 难度:困难 + +## 题目链接 + +- [1012. 至少有 1 位重复的数字 - 力扣](https://leetcode.cn/problems/numbers-with-repeated-digits/) + +## 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:返回在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数。 + +**说明**: + +- $1 \le n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 20 +输出:1 +解释:具有至少 1 位重复数字的正数(<= 20)只有 11。 +``` + +- 示例 2: + +```python +输入:n = 100 +输出:10 +解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +正向求解在 $[1, n]$ 范围内具有至少 $1$ 位重复数字的正整数的个数不太容易,我们可以反向思考,先求解出在 $[1, n]$ 范围内各位数字都不重复的正整数的个数 $ans$,然后 $n - ans$ 就是题目答案。 + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 + 4. 开始时没有填写数字。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), + 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 + 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位限制和 $pos$ 位限制。 + 3. $isNum == True$ 表示 $pos$ 位选择了数字。 +6. 最后的方案数为 `n - dfs(0, 0, True, False)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def numDupDigitsAtMostN(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNumb 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + # d 不在选择的数字集合中,即之前没有选择过 d + if (state >> d) & 1 == 0: + ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) + return ans + + return n - dfs(0, 0, True, False) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$。 +- **空间复杂度**:$O(\log n \times 2^{10})$。 + diff --git a/docs/solutions/1000-1099/recover-a-tree-from-preorder-traversal.md b/docs/solutions/1000-1099/recover-a-tree-from-preorder-traversal.md new file mode 100644 index 00000000..18b71f29 --- /dev/null +++ b/docs/solutions/1000-1099/recover-a-tree-from-preorder-traversal.md @@ -0,0 +1,84 @@ +# [1028. 从先序遍历还原二叉树](https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/) + +- 标签:树、深度优先搜索、字符串、二叉树 +- 难度:困难 + +## 题目链接 + +- [1028. 从先序遍历还原二叉树 - 力扣](https://leetcode.cn/problems/recover-a-tree-from-preorder-traversal/) + +## 题目大意 + +对一棵二叉树进行深度优先搜索。在遍历的过程中,遇到节点,先输出与该节点深度相同数量的短线,再输出该节点的值。如果节点深度为 `D`,则子节点深度为 `D + 1`。根节点的深度为 `0`。如果节点只有一个子节点,则该子节点一定为左子节点。 + +现在给定深度优先搜索输出的字符串 `traversal`。 + +要求:还原二叉树,并返回其根节点 `root`。 + +## 解题思路 + +用栈存储需要构建子树的节点。并记录下上一节点深度和当前节点深度。 + +然后遍历深度优先搜索的输出字符串。 + +- 先将开始部分的数字作为根节点值,构建一个根节点 `root`,并将根节点插入到栈中。 +- 如果遇到 `-`,则更新当前节点深度。 + +- 然后如果遇到数字,则将数字逐位转为整数。并且在最后进行判断。 + - 如果当前节点深度 > 前一节点深度: + - 将栈顶节点出栈。 + - 构建一个新节点,值为当前整数。将新节点插入到栈顶节点的左子树上。 + - 将当前节点和新节点插入到栈中。 + - 如果当前节点深度 <= 前一节点深度: + - 将当前节点深度个数的节点从栈中弹出。 + - 构建一个新节点,值为当前整数。并将新节点插入到最后弹出节点的右子树上。 + - 将当前节点和新节点插入到栈中。 +- 最后输出根节点 `root`。 + +## 代码 + +```python +class Solution: + def recoverFromPreorder(self, traversal: str) -> Optional[TreeNode]: + stack = [] + + index, num = 0, 0 + pre_level, cur_level = 0, 0 + + size = len(traversal) + while index < size and traversal[index] != '-': + num = num * 10 + ord(traversal[index]) - ord('0') + index += 1 + + root = TreeNode(num) + stack.append(root) + + while index < size: + if traversal[index] == '-': + cur_level += 1 + index += 1 + else: + num = 0 + while index < size and traversal[index] != '-': + num = num * 10 + ord(traversal[index]) - ord('0') + index += 1 + + if cur_level > pre_level: + node = stack.pop() + node.left = TreeNode(num) + stack.append(node) + stack.append(node.left) + pre_level = cur_level + cur_level = 0 + else: + while len(stack) > cur_level: + stack.pop() + node = stack.pop() + node.right = TreeNode(num) + stack.append(node) + stack.append(node.right) + pre_level = cur_level + cur_level = 0 + return root +``` + diff --git a/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md b/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md new file mode 100644 index 00000000..6dfbc7fb --- /dev/null +++ b/docs/solutions/1000-1099/remove-all-adjacent-duplicates-in-string.md @@ -0,0 +1,33 @@ +# [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) + +- 标签:栈、字符串 +- 难度:简单 + +## 题目链接 + +- [1047. 删除字符串中的所有相邻重复项 - 力扣](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) + +## 题目大意 + +给定一个全部由小写字母组成的字符串 S,重复的删除相邻且相同的字母,直到相邻字母不再有相同的。 + +比如 "abbaca"。先删除相邻且相同的字母 "bb",变为 "aaca",再删除相邻且相同的字母 "aa",变为 "ca",无相邻且相同的字母,即 "ca" 为最终结果。 + +## 解题思路 + +跟括号匹配有点类似。我们可以利用「栈」来做这道题。遍历字符串,如果当前字符与栈顶字符相同,则将栈顶所有相同字符删除,否则就将当前字符入栈。 + +## 代码 + +```python +class Solution: + def removeDuplicates(self, S: str) -> str: + stack = [] + for ch in S: + if stack and stack[-1] == ch: + stack.pop() + else: + stack.append(ch) + return "".join(stack) +``` + diff --git a/docs/solutions/1000-1099/remove-outermost-parentheses.md b/docs/solutions/1000-1099/remove-outermost-parentheses.md new file mode 100644 index 00000000..3f9243fb --- /dev/null +++ b/docs/solutions/1000-1099/remove-outermost-parentheses.md @@ -0,0 +1,101 @@ +# [1021. 删除最外层的括号](https://leetcode.cn/problems/remove-outermost-parentheses/) + +- 标签:栈、字符串 +- 难度:简单 + +## 题目链接 + +- [1021. 删除最外层的括号 - 力扣](https://leetcode.cn/problems/remove-outermost-parentheses/) + +## 题目大意 + +**描述**:有效括号字符串为空 `""`、`"("` + $A$ + `")"` 或 $A + B$ ,其中 $A$ 和 $B$ 都是有效的括号字符串,$+$ 代表字符串的连接。 + +- 例如,`""`,`"()"`,`"(())()"` 和 `"(()(()))"` 都是有效的括号字符串。 + +如果有效字符串 $s$ 非空,且不存在将其拆分为 $s = A + B$ 的方法,我们称其为原语(primitive),其中 $A$ 和 $B$ 都是非空有效括号字符串。 + +给定一个非空有效字符串 $s$,考虑将其进行原语化分解,使得:$s = P_1 + P_2 + ... + P_k$,其中 $P_i$ 是有效括号字符串原语。 + +**要求**:对 $s$ 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 $s$。 + +**说明**: + +- $1 \le s.length \le 10^5$。 +- $s[i]$ 为 `'('` 或 `')'`。 +- $s$ 是一个有效括号字符串。 + +**示例**: + +- 示例 1: + +```python +输入:s = "(()())(())" +输出:"()()()" +解释: +输入字符串为 "(()())(())",原语化分解得到 "(()())" + "(())", +删除每个部分中的最外层括号后得到 "()()" + "()" = "()()()"。 +``` + +- 示例 2: + +```python +输入:s = "(()())(())(()(()))" +输出:"()()()()(())" +解释: +输入字符串为 "(()())(())(()(()))",原语化分解得到 "(()())" + "(())" + "(()(()))", +删除每个部分中的最外层括号后得到 "()()" + "()" + "()(())" = "()()()()(())"。 +``` + +## 解题思路 + +### 思路 1:计数遍历 + +题目要求我们对 $s$ 进行原语化分解,并且删除分解中每个原语字符串的最外层括号。 + +通过观察可以发现,每个原语其实就是一组有效的括号对(左右括号匹配时),此时我们需要删除这组有效括号对的最外层括号。 + +我们可以使用一个计数器 $cnt$ 来进行原语化分解,并删除每个原语的最外层括号。 + +当计数器遇到左括号时,令计数器 $cnt$ 加 $1$,当计数器遇到右括号时,令计数器 $cnt$ 减 $1$。这样当计数器为 $0$ 时表示当前左右括号匹配。 + +为了删除每个原语的最外层括号,当遇到每个原语最外侧的左括号时(此时 $cnt$ 必然等于 $0$,因为之前字符串为空或者为上一个原语字符串),因为我们不需要最外层的左括号,所以此时我们不需要将其存入答案字符串中。只有当 $cnt > 0$ 时,才将其存入答案字符串中。 + +同理,当遇到每个原语最外侧的右括号时(此时 $cnt$ 必然等于 $1$,因为之前字符串差一个右括号匹配),因为我们不需要最外层的右括号,所以此时我们不需要将其存入答案字符串中。只有当 $cnt > 1$ 时,才将其存入答案字符串中。 + +具体步骤如下: + +1. 遍历字符串 $s$。 +2. 如果遇到 `'('`,判断当前计数器是否大于 $0$: + 1. 如果 $cnt > 0$,则将 `'('` 存入答案字符串中,并令计数器加 $1$,即:`cnt += 1`。 + 2. 如果 $cnt == 0$,则令计数器加 $1$,即:`cnt += 1`。 +3. 如果遇到 `')'`,判断当前计数器是否大于 $1$: + 1. 如果 $cnt > 1$,则将 `')'` 存入答案字符串中,并令计数器减 $1$,即:`cnt -= 1`。 + 2. 如果 $cnt == 1$,则令计数器减 $1$,即:`cnt -= 1`。 +4. 遍历完返回答案字符串 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def removeOuterParentheses(self, s: str) -> str: + cnt, ans = 0, "" + + for ch in s: + if ch == '(': + if cnt > 0: + ans += ch + cnt += 1 + else: + if cnt > 1: + ans += ch + cnt -= 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1000-1099/robot-bounded-in-circle.md b/docs/solutions/1000-1099/robot-bounded-in-circle.md new file mode 100644 index 00000000..1ab38240 --- /dev/null +++ b/docs/solutions/1000-1099/robot-bounded-in-circle.md @@ -0,0 +1,106 @@ +# [1041. 困于环中的机器人](https://leetcode.cn/problems/robot-bounded-in-circle/) + +- 标签:数学、字符串、模拟 +- 难度:中等 + +## 题目链接 + +- [1041. 困于环中的机器人 - 力扣](https://leetcode.cn/problems/robot-bounded-in-circle/) + +## 题目大意 + +**描述**:在无限的平面上,机器人最初位于 $(0, 0)$ 处,面朝北方。注意: + +- 北方向 是 $y$ 轴的正方向。 +- 南方向 是 $y$ 轴的负方向。 +- 东方向 是 $x$ 轴的正方向。 +- 西方向 是 $x$ 轴的负方向。 + +机器人可以接受下列三条指令之一: + +- `"G"`:直走 $1$ 个单位 +- `"L"`:左转 $90$ 度 +- `"R"`:右转 $90$ 度 + +给定一个字符串 $instructions$,机器人按顺序执行指令 $instructions$,并一直重复它们。 + +**要求**:只有在平面中存在环使得机器人永远无法离开时,返回 $True$。否则,返回 $False$。 + +**说明**: + +- $1 \le instructions.length \le 100$。 +- $instructions[i]$ 仅包含 `'G'`,`'L'`,`'R'`。 + +**示例**: + +- 示例 1: + +```python +输入:instructions = "GGLLGG" +输出:True +解释:机器人最初在(0,0)处,面向北方。 +“G”:移动一步。位置:(0,1)方向:北。 +“G”:移动一步。位置:(0,2).方向:北。 +“L”:逆时针旋转90度。位置:(0,2).方向:西。 +“L”:逆时针旋转90度。位置:(0,2)方向:南。 +“G”:移动一步。位置:(0,1)方向:南。 +“G”:移动一步。位置:(0,0)方向:南。 +重复指令,机器人进入循环:(0,0)——>(0,1)——>(0,2)——>(0,1)——>(0,0)。 +在此基础上,我们返回 True。 +``` + +- 示例 2: + +```python +输入:instructions = "GG" +输出:False +解释:机器人最初在(0,0)处,面向北方。 +“G”:移动一步。位置:(0,1)方向:北。 +“G”:移动一步。位置:(0,2).方向:北。 +重复这些指示,继续朝北前进,不会进入循环。 +在此基础上,返回 False。 +``` + +## 解题思路 + +### 思路 1:模拟 + +设定初始位置为 $(0, 0)$,初始方向 $direction = 0$,假设按照给定字符串 $instructions$ 执行一遍之后,位于 $(x, y)$ 处,且方向为 $direction$,则可能出现的所有情况为: + +1. 方向不变($direction == 0$),且 $(x, y) == (0, 0)$,则会一直在原点,无法走出去。 +2. 方向不变($direction == 0$),且 $(x, y) \ne (0, 0)$,则可以走出去。 +3. 方向相反($direction == 2$),无论是否产生位移,则再执行 $1$ 遍将会回到原点。 +4. 方向逆时针 / 顺时针改变 $90°$($direction == 1 \text{ or } 3$),无论是否产生位移,则再执行 $3$ 遍将会回到原点。 + +综上所述,最多模拟 $4$ 次即可知道能否回到原点。 + +从上面也可以等出结论:如果不产生位移,则一定会回到原点。如果改变方向,同样一定会回到原点。 + +我们只需要根据以上结论,按照 $instructions$ 执行一遍之后,通过判断是否产生位移和改变方向,即可判断是否一定会回到原点。 + +### 思路 1:代码 + +```Python +class Solution: + def isRobotBounded(self, instructions: str) -> bool: + # 分别代表北、东、南、西 + directions = [(0, 1), (-1, 0), (0, -1), (1, 0)] + x, y = 0, 0 + # 初始方向为北 + direction = 0 + for step in instructions: + if step == 'G': + x += directions[direction][0] + y += directions[direction][1] + elif step == 'L': + direction = (direction + 1) % 4 + else: + direction = (direction + 3) % 4 + + return (x == 0 and y == 0) or direction != 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $instructions$ 的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1000-1099/shortest-path-in-binary-matrix.md b/docs/solutions/1000-1099/shortest-path-in-binary-matrix.md new file mode 100644 index 00000000..d768efd6 --- /dev/null +++ b/docs/solutions/1000-1099/shortest-path-in-binary-matrix.md @@ -0,0 +1,58 @@ +# [1091. 二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) + +- 标签:广度优先搜索、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1091. 二进制矩阵中的最短路径 - 力扣](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) + +## 题目大意 + +给定一个 `n * n` 的二进制矩阵 `grid`。 `grid` 中只含有 `0` 或者 `1`。`grid` 中的畅通路径是一条从左上角 `(0, 0)` 位置上到右下角 `(n - 1, n - 1)`位置上的路径。该路径同时满足以下要求: + +- 路径途径的所有单元格的值都是 `0`。 +- 路径中所有相邻的单元格应该在 `8` 个方向之一上连通(即相邻两单元格之间彼此不同且共享一条边或者一个角)。 +- 畅通路径的长度是该路径途径的单元格总数。 + +要求:计算出矩阵中最短畅通路径的长度。如果不存在这样的路径,返回 `-1`。 + +## 解题思路 + +使用广度优先搜索查找最短路径。具体做法如下: + +1. 使用队列 `queue` 存放当前节点位置,使用 set 集合 `visited` 存放遍历过的节点位置。使用 `count` 记录最短路径。将起始位置 `(0, 0)` 加入到 `queue` 中,并标记为访问过。 +2. 如果队列不为空,则令 `count += 1`,并将队列中的节点位置依次取出。对于每一个节点位置: + - 先判断是否为右下角节点,即 `(n - 1, n - 1)`。如果是则返回当前最短路径长度 `count`。 + - 如果不是,则继续遍历 `8` 个方向上、没有访问过、并且值为 `0` 的相邻单元格。 + - 将其加入到队列 `queue` 中,并标记为访问过。 +3. 重复进行第 2 步骤,直到队列为空时,返回 `-1`。 + +## 代码 + +```python +class Solution: + def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: + if grid[0][0] == 1: + return -1 + size = len(grid) + directions = {(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)} + visited = set((0, 0)) + queue = [(0, 0)] + count = 0 + while queue: + count += 1 + for _ in range(len(queue)): + row, col = queue.pop(0) + + if row == size - 1 and col == size - 1: + return count + for direction in directions: + new_row = row + direction[0] + new_col = col + direction[1] + if 0 <= new_row < size and 0 <= new_col < size and grid[new_row][new_col] == 0 and (new_row, new_col) not in visited: + queue.append((new_row, new_col)) + visited.add((new_row, new_col)) + return -1 +``` + diff --git a/docs/solutions/1000-1099/smallest-subsequence-of-distinct-characters.md b/docs/solutions/1000-1099/smallest-subsequence-of-distinct-characters.md new file mode 100644 index 00000000..b43002ed --- /dev/null +++ b/docs/solutions/1000-1099/smallest-subsequence-of-distinct-characters.md @@ -0,0 +1,90 @@ +# [1081. 不同字符的最小子序列](https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/) + +- 标签:栈、贪心、字符串、单调栈 +- 难度:中等 + +## 题目链接 + +- [1081. 不同字符的最小子序列 - 力扣](https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:去除字符串中重复的字母,使得每个字母只出现一次。需要保证 **「返回结果的字典序最小(要求不能打乱其他字符的相对位置)」**。 + +**说明**: + +- $1 \le s.length \le 10^4$。 +- `s` 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "bcabc" +输出:"abc" +``` + +- 示例 2: + +```python +输入:s = "cbacdcbc" +输出:"acdb" +``` + +## 解题思路 + +### 思路 1:哈希表 + 单调栈 + +针对题目的三个要求:去重、不能打乱其他字符顺序、字典序最小。我们来一一分析。 + +1. **去重**:可以通过 **「使用哈希表存储字母出现次数」** 的方式,将每个字母出现的次数统计起来,再遍历一遍,去除重复的字母。 +2. **不能打乱其他字符顺序**:按顺序遍历,将非重复的字母存储到答案数组或者栈中,最后再拼接起来,就能保证不打乱其他字符顺序。 +3. **字典序最小**:意味着字典序小的字母应该尽可能放在前面。 + 1. 对于第 `i` 个字符 `s[i]` 而言,如果第 `0` ~ `i - 1` 之间的某个字符 `s[j]` 在 `s[i]` 之后不再出现了,那么 `s[j]` 必须放到 `s[i]` 之前。 + 2. 而如果 `s[j]` 在之后还会出现,并且 `s[j]` 的字典序大于 `s[i]`,我们则可以先舍弃 `s[j]`,把 `s[i]` 尽可能的放到前面。后边再考虑使用 `s[j]` 所对应的字符。 + + +要满足第 3 条需求,我们可以使用 **「单调栈」** 来解决。我们使用单调栈存储 `s[i]` 之前出现的非重复、并且字典序最小的字符序列。整个算法步骤如下: + +1. 先遍历一遍字符串,用哈希表 `letter_counts` 统计出每个字母出现的次数。 +2. 然后使用单调递减栈保存当前字符之前出现的非重复、并且字典序最小的字符序列。 +3. 当遍历到 `s[i]` 时,如果 `s[i]` 没有在栈中出现过: + 1. 比较 `s[i]` 和栈顶元素 `stack[-1]` 的字典序。如果 `s[i]` 的字典序小于栈顶元素 `stack[-1]`,并且栈顶元素之后的出现次数大于 `0`,则将栈顶元素弹出。 + 2. 然后继续判断 `s[i]` 和栈顶元素 `stack[-1]`,并且知道栈顶元素出现次数为 `0` 时停止弹出。此时将 `s[i]` 添加到单调栈中。 +4. 从哈希表 `letter_counts` 中减去 `s[i]` 出现的次数,继续遍历。 +5. 最后将单调栈中的字符依次拼接为答案字符串,并返回。 + +### 思路 1:代码 + +```python +class Solution: + def removeDuplicateLetters(self, s: str) -> str: + stack = [] + letter_counts = dict() + for ch in s: + if ch in letter_counts: + letter_counts[ch] += 1 + else: + letter_counts[ch] = 1 + + for ch in s: + if ch not in stack: + while stack and ch < stack[-1] and stack[-1] in letter_counts and letter_counts[stack[-1]] > 0: + stack.pop() + stack.append(ch) + letter_counts[ch] -= 1 + + return ''.join(stack) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 为字符集合,$|\sum|$ 为字符种类个数。由于栈中字符不能重复,因此栈中最多有 $|\sum|$ 个字符。 + +## 参考资料 + +- 【题解】[去除重复数组 - 去除重复字母 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-duplicate-letters/solution/qu-chu-zhong-fu-shu-zu-by-lu-shi-zhe-sokp/) \ No newline at end of file diff --git a/docs/solutions/1000-1099/two-city-scheduling.md b/docs/solutions/1000-1099/two-city-scheduling.md new file mode 100644 index 00000000..f8f1d32d --- /dev/null +++ b/docs/solutions/1000-1099/two-city-scheduling.md @@ -0,0 +1,62 @@ +# [1029. 两地调度](https://leetcode.cn/problems/two-city-scheduling/) + +- 标签:贪心、数组、排序 +- 难度:中等 + +## 题目链接 + +- [1029. 两地调度 - 力扣](https://leetcode.cn/problems/two-city-scheduling/) + +## 题目大意 + +**描述**:公司计划面试 `2 * n` 人。给你一个数组 `costs`,其中 `costs[i] = [aCosti, bCosti]`,表示第 `i` 人飞往 `a` 市的费用为 `aCosti` ,飞往 `b` 市的费用为 `bCosti`。 + +**要求**:返回将每个人都飞到 `a`、`b` 中某座城市的最低费用,要求每个城市都有 `n` 人抵达。 + +**说明**: + +- $2 * n == costs.length$。 +- $2 \le costs.length \le 100$。 +- $costs.length$ 为偶数。 +- $1 \le aCosti, bCosti \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:costs = [[10,20],[30,200],[400,50],[30,20]] +输出:110 +解释: +第一个人去 a 市,费用为 10。 +第二个人去 a 市,费用为 30。 +第三个人去 b 市,费用为 50。 +第四个人去 b 市,费用为 20。 + +最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +我们先假设所有人都去了城市 `a`。然后令一半的人再去城市 `b`。现在的问题就变成了,让一半的人改变城市去向,从原本的 `a` 城市改成 `b` 城市的最低费用为多少。 + +已知第 `i` 个人更换去向的费用为「去城市 `b` 的费用 - 去城市 `a` 的费用」。所以我们可以根据「去城市 `b` 的费用 - 去城市 `a` 的费用」对数组 `costs` 进行排序,让前 `n` 个改变方向去城市 `b`,后 `n` 个人去城市 `a`。 + +最后统计所有人员的费用,将其返回即可。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def twoCitySchedCost(self, costs: List[List[int]]) -> int: + costs.sort(key=lambda x:x[1] - x[0]) + cost = 0 + size = len(costs) // 2 + for i in range(size): + cost += costs[i][ 1] + cost += costs[i + size][0] + + return cost +``` diff --git a/docs/solutions/1000-1099/two-sum-less-than-k.md b/docs/solutions/1000-1099/two-sum-less-than-k.md new file mode 100644 index 00000000..73aa5eb7 --- /dev/null +++ b/docs/solutions/1000-1099/two-sum-less-than-k.md @@ -0,0 +1,77 @@ +# [1099. 小于 K 的两数之和](https://leetcode.cn/problems/two-sum-less-than-k/) + +- 标签:数组、双指针、二分查找、排序 +- 难度:简单 + +## 题目链接 + +- [1099. 小于 K 的两数之和 - 力扣](https://leetcode.cn/problems/two-sum-less-than-k/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和整数 $k$。 + +**要求**:返回最大和 $sum$,满足存在 $i < j$ 使得 $nums[i] + nums[j] = sum$ 且 $sum < k$。如果没有满足此等式的 $i$, $j$ 存在,则返回 $-1$。 + +**说明**: + +- $1 \le nums.length \le 100$。 +- $1 \le nums[i] \le 1000$。 +- $1 \le k \le 2000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [34,23,1,24,75,33,54,8], k = 60 +输出:58 +解释:34 和 24 相加得到 58,58 小于 60,满足题意。 +``` + +- 示例 2: + +```python +输入:nums = [10,20,30], k = 15 +输出:-1 +解释:我们无法找到和小于 15 的两个元素。 +``` + +## 解题思路 + +### 思路 1:对撞指针 + +常规暴力枚举时间复杂度为 $O(n^2)$。可以通过双指针降低时间复杂度。具体做法如下: + +- 先对数组进行排序(时间复杂度为 $O(n \log n$),使用 $res$ 记录答案,初始赋值为最小值 `float('-inf')`。 +- 使用两个指针 $left$、$right$。$left$ 指向第 $0$ 个元素位置,$right$ 指向数组的最后一个元素位置。 +- 计算 $nums[left] + nums[right]$,与 $k$ 进行比较。 + - 如果 $nums[left] + nums[right] \ge k$,则将 $right$ 左移,继续查找。 + - 如果 $nums[left] + nums[rigth] < k$,则将 $left$ 右移,并更新答案值。 +- 当 $left == right$ 时,区间搜索完毕,判断 $res$ 是否等于 `float('-inf')`,如果等于,则返回 $-1$,否则返回 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def twoSumLessThanK(self, nums: List[int], k: int) -> int: + + nums.sort() + res = float('-inf') + left, right = 0, len(nums) - 1 + while left < right: + total = nums[left] + nums[right] + if total >= k: + right -= 1 + else: + res = max(res, total) + left += 1 + + return res if res != float('-inf') else -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组中元素的个数。 +- **空间复杂度**:$O(\log n)$,排序需要 $\log n$ 的栈空间。 + diff --git a/docs/solutions/1000-1099/uncrossed-lines.md b/docs/solutions/1000-1099/uncrossed-lines.md new file mode 100644 index 00000000..c046385d --- /dev/null +++ b/docs/solutions/1000-1099/uncrossed-lines.md @@ -0,0 +1,52 @@ +# [1035. 不相交的线](https://leetcode.cn/problems/uncrossed-lines/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1035. 不相交的线 - 力扣](https://leetcode.cn/problems/uncrossed-lines/) + +## 题目大意 + +有两条独立平行的水平线,按照给定的顺序写下 `nums1` 和 `nums2` 的整数。 + +现在,我们可以绘制一些直线,只要满足以下要求: + +- `nums1[i] == nums2[j]`。 +- 绘制的直线不与其他任何直线相交。 + +例如:![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/04/28/142.png) + +现在要求:计算出能绘制的最大直线数目。 + +## 解题思路 + +动态规划求解。 + +定义状态 `dp[i][j]` 表示:`nums1` 中前 `i` 个数与 `nums2` 中前 `j` 个数的最大连接数,则: + +状态转移方程为: + +- 如果 `nums1[i] == nums[j]`,则 `nums1[i]` 与 `nums2[j]` 可连线,此时 `dp[i][j] = dp[i - 1][j - 1] + 1`。 +- 如果 `nums1[i] != nums[j]`,则 `nums1[i]` 与 `nums2[j]` 不可连线,此时最大连线数取决于 `dp[i - 1][j]` 和 `dp[i][j - 1]` 的较大值,即:`dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])`。 + +最后输出 `dp[size1][size2]` 即可。 + +## 代码 + +```python +class Solution: + def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int: + size1 = len(nums1) + size2 = len(nums2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if nums1[i - 1] == nums2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + return dp[size1][size2] +``` + diff --git a/docs/solutions/1000-1099/valid-boomerang.md b/docs/solutions/1000-1099/valid-boomerang.md new file mode 100644 index 00000000..b6fa1893 --- /dev/null +++ b/docs/solutions/1000-1099/valid-boomerang.md @@ -0,0 +1,65 @@ +# [1037. 有效的回旋镖](https://leetcode.cn/problems/valid-boomerang/) + +- 标签:几何、数组、数学 +- 难度:简单 + +## 题目链接 + +- [1037. 有效的回旋镖 - 力扣](https://leetcode.cn/problems/valid-boomerang/) + +## 题目大意 + +**描述**:给定一个数组 $points$,其中 $points[i] = [xi, yi]$ 表示平面上的一个点。 + +**要求**:如果这些点构成一个回旋镖,则返回 `True`,否则,则返回 `False`。 + +**说明**: + +- **回旋镖**:定义为一组三个点,这些点各不相同且不在一条直线上。 +- $points.length == 3$。 +- $points[i].length == 2$。 +- $0 \le xi, yi \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:points = [[1,1],[2,3],[3,2]] +输出:True +``` + +- 示例 2: + +```python +输入:points = [[1,1],[2,2],[3,3]] +输出:False +``` + +## 解题思路 + +### 思路 1: + +设三点坐标为 $A = (x1, y1)$,$B = (x2, y2)$,$C = (x3, y3)$,则向量 $\overrightarrow{AB} = (x2 - x1, y2 - y1)$,$\overrightarrow{BC} = (x3 - x2, y3 - y2)$。 + +如果三点共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) = 0$。 + +如果三点不共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) \ne 0$。 + +### 思路 1:代码 + +```python +class Solution: + def isBoomerang(self, points: List[List[int]]) -> bool: + x1, y1 = points[0] + x2, y2 = points[1] + x3, y3 = points[2] + cross1 = (x2 - x1) * (y3 - y2) + cross2 = (x3 - x2) * (y2 - y1) + return cross1 - cross2 != 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1100-1199/corporate-flight-bookings.md b/docs/solutions/1100-1199/corporate-flight-bookings.md new file mode 100644 index 00000000..dd5e3104 --- /dev/null +++ b/docs/solutions/1100-1199/corporate-flight-bookings.md @@ -0,0 +1,224 @@ +# [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) + +- 标签:数组、前缀和 +- 难度:中等 + +## 题目链接 + +- [1109. 航班预订统计 - 力扣](https://leetcode.cn/problems/corporate-flight-bookings/) + +## 题目大意 + +**描述**:给定整数 `n`,代表 `n` 个航班。再给定一个包含三元组的数组 `bookings`,代表航班预订表。表中第 `i` 条预订记录 $bookings[i] = [first_i, last_i, seats_i]$ 意味着在从 $first_i$ 到 $last_i$ (包含 $first_i$ 和 $last_i$)的 每个航班上预订了 $seats_i$ 个座位。 + +**要求**:返回一个长度为 `n` 的数组 `answer`,里面元素是每个航班预定的座位总数。 + +**说明**: + +- $1 \le n \le 2 * 10^4$。 +- $1 \le bookings.length \le 2 * 10^4$。 +- $bookings[i].length == 3$。 +- $1 \le first_i \le last_i \le n$。 +- $1 \le seats_i \le 10^4$ + +**示例**: + +- 示例 1: + +```python +给定 n = 5。初始 answer = [0, 0, 0, 0, 0] + +航班编号 1 2 3 4 5 +预订记录 1 : 10 10 +预订记录 2 : 20 20 +预订记录 3 : 25 25 25 25 +总座位数: 10 55 45 25 25 + +最终 answer = [10, 55, 45, 25, 25] +``` + +## 解题思路 + +### 思路 1:线段树 + +- 初始化一个长度为 `n`,值全为 `0` 的 `nums` 数组。 +- 然后根据 `nums` 数组构建一棵线段树。每个线段树的节点类存储当前区间的左右边界和该区间的和。并且线段树使用延迟标记。 +- 然后遍历三元组操作,进行区间累加运算。 +- 最后从线段树中查询数组所有元素,返回该数组即可。 + +这样构建线段树的时间复杂度为 $O(\log n)$,单次区间更新的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(\log n)$。 + +### 思路 1 线段树代码: + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if self.tree[index].lazy_tag is not None: + self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val + else: + self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val + interval_size = (right - left + 1) # 当前节点所在区间大小 + self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val + return + + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + self.__pushdown(index) # 向下更新节点的区间值 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if lazy_tag is None: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + if self.tree[left_index].lazy_tag is not None: + self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + self.tree[left_index].lazy_tag = lazy_tag + left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) + self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag + + if self.tree[right_index].lazy_tag is not None: + self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + self.tree[right_index].lazy_tag = lazy_tag + right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) + self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag + + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 + + +class Solution: + def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: + nums = [0 for _ in range(n)] + self.STree = SegmentTree(nums, lambda x, y: x + y) + for booking in bookings: + self.STree.update_interval(booking[0] - 1, booking[1] - 1, booking[2]) + + return self.STree.get_nums() +``` + diff --git a/docs/solutions/1100-1199/defanging-an-ip-address.md b/docs/solutions/1100-1199/defanging-an-ip-address.md new file mode 100644 index 00000000..aa913376 --- /dev/null +++ b/docs/solutions/1100-1199/defanging-an-ip-address.md @@ -0,0 +1,41 @@ +# [1108. IP 地址无效化](https://leetcode.cn/problems/defanging-an-ip-address/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1108. IP 地址无效化 - 力扣](https://leetcode.cn/problems/defanging-an-ip-address/) + +## 题目大意 + +**描述**:给定一个有效的 IPv4 的地址 `address`。。 + +**要求**:返回这个 IP 地址的无效化版本。 + +**说明**: + +- **无效化 IP 地址**:其实就是用 `"[.]"` 代替了每个 `"."`。 + +**示例**: + +- 示例 1: + +```python +输入:address = "255.100.50.0" +输出:"255[.]100[.]50[.]0" +``` + +## 解题思路 + +### 思路 1:字符串替换 + +依次将字符串 `address` 中的 `"."` 替换为 `"[.]"`。这里为了方便,直接调用了 `replace` 方法。 + +### 思路 1:字符串替换代码 + +```python +class Solution: + def defangIPaddr(self, address: str) -> str: + return address.replace('.', '[.]') +``` diff --git a/docs/solutions/1100-1199/delete-nodes-and-return-forest.md b/docs/solutions/1100-1199/delete-nodes-and-return-forest.md new file mode 100644 index 00000000..af57e3fe --- /dev/null +++ b/docs/solutions/1100-1199/delete-nodes-and-return-forest.md @@ -0,0 +1,98 @@ +# [1110. 删点成林](https://leetcode.cn/problems/delete-nodes-and-return-forest/) + +- 标签:树、深度优先搜索、数组、哈希表、二叉树 +- 难度:中等 + +## 题目链接 + +- [1110. 删点成林 - 力扣](https://leetcode.cn/problems/delete-nodes-and-return-forest/) + +## 题目大意 + +**描述**:给定二叉树的根节点 $root$,树上每个节点都有一个不同的值。 + +如果节点值在 $to\underline{\hspace{0.5em}}delete$ 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。 + +**要求**:返回森林中的每棵树。你可以按任意顺序组织答案。 + +**说明**: + +- 树中的节点数最大为 $1000$。 +- 每个节点都有一个介于 $1$ 到 $1000$ 之间的值,且各不相同。 +- $to\underline{\hspace{0.5em}}delete.length \le 1000$。 +- $to\underline{\hspace{0.5em}}delete$ 包含一些从 $1$ 到 $1000$、各不相同的值。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/07/05/screen-shot-2019-07-01-at-53836-pm.png) + +```python +输入:root = [1,2,3,4,5,6,7], to_delete = [3,5] +输出:[[1,2,null,4],[6],[7]] +``` + +- 示例 2: + +```python +输入:root = [1,2,4,null,3], to_delete = [3] +输出:[[1,2,4]] +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +将待删除节点数组 $to\underline{\hspace{0.5em}}delete$ 转为集合 $deletes$,则每次能以 $O(1)$ 的时间复杂度判断节点值是否在待删除节点数组中。 + +如果当前节点值在待删除节点数组中,则删除当前节点后,我们还需要判断其左右子节点是否也在待删除节点数组中。 + +以此类推,还需要判断左右子节点的左右子节点。。。 + +因此,我们应该递归遍历处理完所有的左右子树,再判断当前节点的左右子节点是否在待删除节点数组中。如果在,则将其加入到答案数组中。 + +为此我们可以写一个深度优先搜索算法,具体步骤如下: + +1. 如果当前根节点为空,则返回 `None`。 +2. 递归遍历处理完当前根节点的左右子树,更新当前节点的左右子树(子节点被删除的情况下需要更新当前根节点的左右子树)。 +3. 如果当前根节点值在待删除节点数组中: + 1. 如果当前根节点的左子树没有在被删除节点数组中,将左子树节点加入到答案数组中。 + 2. 如果当前根节点的右子树没有在被删除节点数组中,将右子树节点加入到答案数组中。 + 3. 返回 `None`,表示当前节点被删除。 +4. 如果当前根节点值不在待删除节点数组中: + 1. 返回根节点,表示当前节点没有被删除。 + +### 思路 1:代码 + +```Python +class Solution: + def delNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]: + forest = [] + deletes = set(to_delete) + def dfs(root): + if not root: + return None + root.left = dfs(root.left) + root.right = dfs(root.right) + + if root.val in deletes: + if root.left: + forest.append(root.left) + if root.right: + forest.append(root.right) + return None + else: + return root + + + if dfs(root): + forest.append(root) + return forest +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为二叉树中节点个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1100-1199/diet-plan-performance.md b/docs/solutions/1100-1199/diet-plan-performance.md new file mode 100644 index 00000000..f6e0a309 --- /dev/null +++ b/docs/solutions/1100-1199/diet-plan-performance.md @@ -0,0 +1,88 @@ +# [1176. 健身计划评估](https://leetcode.cn/problems/diet-plan-performance/) + +- 标签:数组、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [1176. 健身计划评估 - 力扣](https://leetcode.cn/problems/diet-plan-performance/) + +## 题目大意 + +**描述**:好友给自己制定了一份健身计划。想请你帮他评估一下这份计划是否合理。 + +给定一个数组 $calories$,其中 $calories[i]$ 代表好友第 $i$ 天需要消耗的卡路里总量。再给定 $lower$ 代表较低消耗的卡路里,$upper$ 代表较高消耗的卡路里。再给定一个整数 $k$,代表连续 $k$ 天。 + +- 如果你的好友在这一天以及之后连续 $k$ 天内消耗的总卡路里 $T$ 小于 $lower$,则这一天的计划相对糟糕,并失去 $1$ 分。 +- 如果你的好友在这一天以及之后连续 $k$ 天内消耗的总卡路里 $T$ 高于 $upper$,则这一天的计划相对优秀,并得到 $1$ 分。 +- 如果你的好友在这一天以及之后连续 $k$ 天内消耗的总卡路里 $T$ 大于等于 $lower$,并且小于等于 $upper$,则这份计划普普通通,分值不做变动。 + +**要求**:输出最后评估的得分情况。 + +**说明**: + +- $1 \le k \le calories.length \le 10^5$。 +- $0 \le calories[i] \le 20000$。 +- $0 \le lower \le upper$。 + +**示例**: + +- 示例 1: + +```python +输入:calories = [1,2,3,4,5], k = 1, lower = 3, upper = 3 +输出:0 +解释:calories[0], calories[1] < lower 而 calories[3], calories[4] > upper, 总分 = 0. +``` + +- 示例 2: + +```python +输入:calories = [3,2], k = 2, lower = 0, upper = 1 +输出:1 +解释:calories[0] + calories[1] > upper, 总分 = 1. +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +固定长度为 $k$ 的滑动窗口题目。具体做法如下: + +1. $score$ 用来维护得分情况,初始值为 $0$。$window\underline{\hspace{0.5em}}sum$ 用来维护窗口中卡路里总量。 +2. $left$ 、$right$ 都指向数组的第一个元素,即:`left = 0`,`right = 0`。 +3. 向右移动 $right$,先将 $k$ 个元素填入窗口中。 +4. 当窗口元素个数为 $k$ 时,即:$right - left + 1 \ge k$ 时,计算窗口内的卡路里总量,并判断和 $upper$、$lower$ 的关系。同时维护得分情况。 +5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 +6. 重复 $4 \sim 5$ 步,直到 $right$ 到达数组末尾。 + +最后输出得分情况 $score$。 + +### 思路 1:代码 + +```python +class Solution: + def dietPlanPerformance(self, calories: List[int], k: int, lower: int, upper: int) -> int: + left, right = 0, 0 + window_sum = 0 + score = 0 + while right < len(calories): + window_sum += calories[right] + + if right - left + 1 >= k: + if window_sum < lower: + score -= 1 + elif window_sum > upper: + score += 1 + window_sum -= calories[left] + left += 1 + + right += 1 + return score +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $calories$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1100-1199/distance-between-bus-stops.md b/docs/solutions/1100-1199/distance-between-bus-stops.md new file mode 100644 index 00000000..bad07c1c --- /dev/null +++ b/docs/solutions/1100-1199/distance-between-bus-stops.md @@ -0,0 +1,71 @@ +# [1184. 公交站间的距离](https://leetcode.cn/problems/distance-between-bus-stops/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [1184. 公交站间的距离 - 力扣](https://leetcode.cn/problems/distance-between-bus-stops/) + +## 题目大意 + +**描述**:环形公交路线上有 $n$ 个站,序号为 $0 \sim n - 1$。给定一个数组 $distance$ 表示每一对相邻公交站之间的距离,其中 $distance[i]$ 表示编号为 $i$ 的车站与编号为 $(i + 1) \mod n$ 的车站之间的距离。再给定乘客的出发点编号 $start$ 和目的地编号 $destination$。 + +**要求**:返回乘客从出发点 $start$ 到目的地 $destination$ 之间的最短距离。 + +**说明**: + +- $1 \le n \le 10^4$。 +- $distance.length == n$。 +- $0 \le start, destination < n$。 +- $0 \le distance[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1.jpg) + +```python +输入:distance = [1,2,3,4], start = 0, destination = 1 +输出:1 +解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1-1.jpg) + +```python +输入:distance = [1,2,3,4], start = 0, destination = 2 +输出:3 +解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。 +``` + +## 解题思路 + +### 思路 1:简单模拟 + +1. 因为 $start$ 和 $destination$ 的先后顺序不影响结果,为了方便计算,我们先令 $start \le destination$。 +2. 遍历数组 $distance$,计算出 $[start, destination]$ 之间的距离和 $dist$。 +3. 计算出环形路线中 $[destination, start]$ 之间的距离和为 $sum(distance) - dist$。 +4. 比较 $2 \sim 3$ 中两个距离的大小,将距离最小值作为答案返回。 + +### 思路 1:代码 + +```python +class Solution: + def distanceBetweenBusStops(self, distance: List[int], start: int, destination: int) -> int: + start, destination = min(start, destination), max(start, destination) + dist = 0 + for i in range(len(distance)): + if start <= i < destination: + dist += distance[i] + + return min(dist, sum(distance) - dist) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1100-1199/distribute-candies-to-people.md b/docs/solutions/1100-1199/distribute-candies-to-people.md new file mode 100644 index 00000000..dd879a20 --- /dev/null +++ b/docs/solutions/1100-1199/distribute-candies-to-people.md @@ -0,0 +1,80 @@ +# [1103. 分糖果 II](https://leetcode.cn/problems/distribute-candies-to-people/) + +- 标签:数学、模拟 +- 难度:简单 + +## 题目链接 + +- [1103. 分糖果 II - 力扣](https://leetcode.cn/problems/distribute-candies-to-people/) + +## 题目大意 + +**描述**:给定一个整数 $candies$,代表糖果的数量。再给定一个整数 $num\underline{\hspace{0.5em}}people$,代表小朋友的数量。 + +现在开始分糖果,给第 $1$ 个小朋友分 $1$ 颗糖果,第 $2$ 个小朋友分 $2$ 颗糖果,以此类推,直到最后一个小朋友分 $n$ 颗糖果。 + +然后回到第 $1$ 个小朋友,给第 $1$ 个小朋友分 $n + 1$ 颗糖果,第 $2$ 个小朋友分 $n + 2$ 颗糖果,一次类推,直到最后一个小朋友分 $n + n$ 颗糖果。 + +重复上述过程(每次都比上一次多给出 $1$ 颗糖果,当分完第 $n$ 个小朋友时回到第 $1$ 个小朋友),直到我们分完所有的糖果。 + +> 注意:如果我们手中剩下的糖果数不够(小于等于前一次发的糖果数),则将剩下的糖果全部发给当前的小朋友。 + +**要求**:返回一个长度为 $num\underline{\hspace{0.5em}}people$、元素之和为 $candies$ 的数组,以表示糖果的最终分发情况(即 $ans[i]$ 表示第 $i$ 个小朋友分到的糖果数)。 + +**说明**: + +- $1 \le candies \le 10^9$。 +- $1 \le num\underline{\hspace{0.5em}}people \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:candies = 7, num_people = 4 +输出:[1,2,3,1] +解释: +第一次,ans[0] += 1,数组变为 [1,0,0,0]。 +第二次,ans[1] += 2,数组变为 [1,2,0,0]。 +第三次,ans[2] += 3,数组变为 [1,2,3,0]。 +第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。 +``` + +- 示例 2: + +```python +输入:candies = 10, num_people = 3 +输出:[5,2,3] +解释: +第一次,ans[0] += 1,数组变为 [1,0,0]。 +第二次,ans[1] += 2,数组变为 [1,2,0]。 +第三次,ans[2] += 3,数组变为 [1,2,3]。 +第四次,ans[0] += 4,最终数组变为 [5,2,3]。 +``` + +## 解题思路 + +### 思路 1:暴力模拟 + +不断遍历数组,将对应糖果数分给当前小朋友,直到糖果数为 $0$ 时停止。 + +### 思路 1:代码 + +```python +class Solution: + def distributeCandies(self, candies: int, num_people: int) -> List[int]: + ans = [0 for _ in range(num_people)] + idx = 0 + while candies: + ans[idx % num_people] += min(idx + 1, candies) + candies -= min(idx + 1, candies) + idx += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(max(\sqrt{m}, n))$,其中 $m$ 为糖果数量,$n$ 为小朋友数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md b/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md new file mode 100644 index 00000000..917f3e11 --- /dev/null +++ b/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md @@ -0,0 +1,87 @@ +# [1100. 长度为 K 的无重复字符子串](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1100. 长度为 K 的无重复字符子串 - 力扣](https://leetcode.cn/problems/find-k-length-substrings-with-no-repeated-characters/) + +## 题目大意 + +**描述**:给定一个字符串 `s`。 + +**要求**:找出所有长度为 `k` 且不含重复字符的子串,返回全部满足要求的子串的数目。 + +**说明**: + +- $1 \le s.length \le 10^4$。 +- $s$ 中的所有字符均为小写英文字母。 +- $1 <= k <= 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "havefunonleetcode", k = 5 +输出:6 +解释: +这里有 6 个满足题意的子串,分别是:'havef','avefu','vefun','efuno','etcod','tcode'。 +``` + +- 示例 2: + +```python +输入:s = "home", K = 5 +输出:0 +解释: +注意:k 可能会大于 s 的长度。在这种情况下,就无法找到任何长度为 k 的子串。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +固定长度滑动窗口的题目。维护一个长度为 `k` 的滑动窗口。用 `window_count` 来表示窗口内所有字符个数。可以用字典、数组来实现,也可以直接用 `collections.Counter()` 实现。然后不断向右滑动,然后进行比较。如果窗口内字符无重复,则答案数目 + 1。然后继续滑动。直到末尾时。整个解题步骤具体如下: + +1. `window_count` 用来维护窗口中 `2` 对应子串的各个字符数量。 +2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 +4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,判断窗口内各个字符数量 `window_count` 是否等于 `k`。 + 1. 如果等于,则答案 + 1。 + 2. 如果不等于,则向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 +5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。返回答案。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def numKLenSubstrNoRepeats(self, s: str, k: int) -> int: + left, right = 0, 0 + window_count = collections.Counter() + ans = 0 + + while right < len(s): + window_count[s[right]] += 1 + + if right - left + 1 >= k: + if len(window_count) == k: + ans += 1 + window_count[s[left]] -= 1 + if window_count[s[left]] == 0: + del window_count[s[left]] + left += 1 + + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 是字符集。 + diff --git a/docs/solutions/1100-1199/index.md b/docs/solutions/1100-1199/index.md new file mode 100644 index 00000000..4b5bcd0f --- /dev/null +++ b/docs/solutions/1100-1199/index.md @@ -0,0 +1,16 @@ +## 本章内容 + +- [1100. 长度为 K 的无重复字符子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/find-k-length-substrings-with-no-repeated-characters.md) +- [1103. 分糖果 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/distribute-candies-to-people.md) +- [1108. IP 地址无效化](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/defanging-an-ip-address.md) +- [1109. 航班预订统计](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/corporate-flight-bookings.md) +- [1110. 删点成林](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/delete-nodes-and-return-forest.md) +- [1122. 数组的相对排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/relative-sort-array.md) +- [1136. 并行课程](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/parallel-courses.md) +- [1137. 第 N 个泰波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/n-th-tribonacci-number.md) +- [1143. 最长公共子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/longest-common-subsequence.md) +- [1151. 最少交换次数来组合所有的 1](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md) +- [1155. 掷骰子等于目标和的方法数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md) +- [1161. 最大层内元素和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/maximum-level-sum-of-a-binary-tree.md) +- [1176. 健身计划评估](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/diet-plan-performance.md) +- [1184. 公交站间的距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/distance-between-bus-stops.md) diff --git a/docs/solutions/1100-1199/longest-common-subsequence.md b/docs/solutions/1100-1199/longest-common-subsequence.md new file mode 100644 index 00000000..8bf7dee0 --- /dev/null +++ b/docs/solutions/1100-1199/longest-common-subsequence.md @@ -0,0 +1,93 @@ +# [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [1143. 最长公共子序列 - 力扣](https://leetcode.cn/problems/longest-common-subsequence/) + +## 题目大意 + +**描述**:给定两个字符串 $text1$ 和 $text2$。 + +**要求**:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 $0$。 + +**说明**: + +- **子序列**:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 +- **公共子序列**:两个字符串所共同拥有的子序列。 +- $1 \le text1.length, text2.length \le 1000$。 +- $text1$ 和 $text2$ 仅由小写英文字符组成。 + +**示例**: + +- 示例 1: + +```python +输入:text1 = "abcde", text2 = "ace" +输出:3 +解释:最长公共子序列是 "ace",它的长度为 3。 +``` + +- 示例 2: + +```python +输入:text1 = "abc", text2 = "abc" +输出:3 +解释:最长公共子序列是 "abc",它的长度为 3。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照两个字符串的结尾位置进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度为 $dp[i][j]$。 + +###### 3. 状态转移方程 + +双重循环遍历字符串 $text1$ 和 $text2$,则状态转移方程为: + +1. 如果 $text1[i - 1] = text2[j - 1]$,说明两个子字符串的最后一位是相同的,所以最长公共子序列长度加 $1$。即:$dp[i][j] = dp[i - 1][j - 1] + 1$。 +2. 如果 $text1[i - 1] \ne text2[j - 1]$,说明两个子字符串的最后一位是不同的,则 $dp[i][j]$ 需要考虑以下两种情况,取两种情况中最大的那种:$dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$。 + 1. 「以 $text1$ 中前 $i - 1$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i - 1][j]$。 + 2. 「以 $text1$ 中前 $i$ 个元素组成的子字符串 $str1$ 」与「以 $text2$ 中前 $j - 1$ 个元素组成的子字符串 $str2$」的最长公共子序列长度,即 $dp[i][j - 1]$。 + +###### 4. 初始条件 + +1. 当 $i = 0$ 时,$str1$ 表示的是空串,空串与 $str2$ 的最长公共子序列长度为 $0$,即 $dp[0][j] = 0$。 +2. 当 $j = 0$ 时,$str2$ 表示的是空串,$str1$ 与 空串的最长公共子序列长度为 $0$,即 $dp[i][0] = 0$。 + +###### 5. 最终结果 + +根据状态定义,最后输出 $dp[sise1][size2]$(即 $text1$ 与 $text2$ 的最长公共子序列长度)即可,其中 $size1$、$size2$ 分别为 $text1$、$text2$ 的字符串长度。 + +### 思路 1:代码 + +```python +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + size1 = len(text1) + size2 = len(text2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[size1][size2] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m)$,其中 $n$、$m$ 分别是字符串 $text1$、$text2$ 的长度。两重循环遍历的时间复杂度是 $O(n \times m)$,所以总的时间复杂度为 $O(n \times m)$。 +- **空间复杂度**:$O(n \times m)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n \times m)$。 + diff --git a/docs/solutions/1100-1199/maximum-level-sum-of-a-binary-tree.md b/docs/solutions/1100-1199/maximum-level-sum-of-a-binary-tree.md new file mode 100644 index 00000000..1500f6b3 --- /dev/null +++ b/docs/solutions/1100-1199/maximum-level-sum-of-a-binary-tree.md @@ -0,0 +1,91 @@ +# [1161. 最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [1161. 最大层内元素和 - 力扣](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) + +## 题目大意 + +**描述**:给你一个二叉树的根节点 $root$。设根节点位于二叉树的第 $1$ 层,而根节点的子节点位于第 $2$ 层,依此类推。 + +**要求**:返回层内元素之和最大的那几层(可能只有一层)的层号,并返回其中层号最小的那个。 + +**说明**: + +- 树中的节点数在 $[1, 10^4]$ 范围内。 +- $-10^5 \le Node.val \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/08/17/capture.jpeg) + +```python +输入:root = [1,7,0,7,-8,null,null] +输出:2 +解释: +第 1 层各元素之和为 1, +第 2 层各元素之和为 7 + 0 = 7, +第 3 层各元素之和为 7 + -8 = -1, +所以我们返回第 2 层的层号,它的层内元素之和最大。 +``` + +- 示例 2: + +```python +输入:root = [989,null,10250,98693,-89388,null,null,null,-32127] +输出:2 +``` + +## 解题思路 + +### 思路 1:二叉树的层序遍历 + +1. 利用广度优先搜索,在二叉树的层序遍历的基础上,统计每一层节点和,并存入数组 $levels$ 中。 +2. 遍历 $levels$ 数组,从 $levels$ 数组中找到最大层和 $max\underline{\hspace{0.5em}}sum$。 +3. 再次遍历 $levels$ 数组,找出等于最大层和 $max\underline{\hspace{0.5em}}sum$ 的那一层,并返回该层序号。 + +### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + levels = [] + while queue: + level = 0 + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + level += curr.val + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + levels.append(level) + return levels + + def maxLevelSum(self, root: Optional[TreeNode]) -> int: + levels = self.levelOrder(root) + max_sum = max(levels) + for i in range(len(levels)): + if levels[i] == max_sum: + return i + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中 $n$ 是二叉树的节点数目。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md b/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md new file mode 100644 index 00000000..f2016522 --- /dev/null +++ b/docs/solutions/1100-1199/minimum-swaps-to-group-all-1s-together.md @@ -0,0 +1,93 @@ +# [1151. 最少交换次数来组合所有的 1](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) + +- 标签:数组、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1151. 最少交换次数来组合所有的 1 - 力扣](https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/) + +## 题目大意 + +**描述**:给定一个二进制数组 $data$。 + +**要求**:通过交换位置,将数组中任何位置上的 $1$ 组合到一起,并返回所有可能中所需的最少交换次数。c + +**说明**: + +- $1 \le data.length \le 10^5$。 +- $data[i] == 0 \text{ or } 1$。 + +**示例**: + +- 示例 1: + +```python +输入: data = [1,0,1,0,1] +输出: 1 +解释: +有三种可能的方法可以把所有的 1 组合在一起: +[1,1,1,0,0],交换 1 次; +[0,1,1,1,0],交换 2 次; +[0,0,1,1,1],交换 1 次。 +所以最少的交换次数为 1。 +``` + +- 示例 2: + +```python +输入:data = [0,0,0,1,0] +输出:0 +解释: +由于数组中只有一个 1,所以不需要交换。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +将数组中任何位置上的 $1$ 组合到一起,并要求最少的交换次数。也就是说交换之后,某个连续子数组中全是 $1$,数组其他位置全是 $0$。为此,我们可以维护一个固定长度为 $1$ 的个数的滑动窗口,找到滑动窗口中 $0$ 最少的个数,这样最终交换出去的 $0$ 最少,交换次数也最少。 + +求最少交换次数,也就是求滑动窗口中最少的 $0$ 的个数。具体做法如下: + +1. 统计 $1$ 的个数,并设置为窗口长度 $window\underline{\hspace{0.5em}}size$。使用 $window\underline{\hspace{0.5em}}count$ 维护窗口中 $0$ 的个数。使用 $ans$ 维护窗口中最少的 $0$ 的个数,也可以叫做最少交换次数。 +2. 如果 $window\underline{\hspace{0.5em}}size$ 为 $0$,则说明不用交换,直接返回 $0$。 +3. 使用两个指针 $left$、$right$。$left$、$right$ 都指向数组的第一个元素,即:`left = 0`,`right = 0`。 +4. 如果 $data[right] == 0$,则更新窗口中 $0$ 的个数,即 `window_count += 1`。然后向右移动 $right$。 +5. 当窗口元素个数为 $window\underline{\hspace{0.5em}}size$ 时,即:$right - left + 1 \ge window\underline{\hspace{0.5em}}size$ 时,更新窗口中最少的 $0$ 的个数。 +6. 然后如果左侧 $data[left] == 0$,则更新窗口中 $0$ 的个数,即 `window_count -= 1`。然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $window\underline{\hspace{0.5em}}size$。 +7. 重复 4 ~ 6 步,直到 $right$ 到达数组末尾。返回答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def minSwaps(self, data: List[int]) -> int: + window_size = 0 + for item in data: + if item == 1: + window_size += 1 + if window_size == 0: + return 0 + + left, right = 0, 0 + window_count = 0 + ans = float('inf') + while right < len(data): + if data[right] == 0: + window_count += 1 + + if right - left + 1 >= window_size: + ans = min(ans, window_count) + if data[left] == 0: + window_count -= 1 + left += 1 + right += 1 + return ans if ans != float('inf') else 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $data$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1100-1199/n-th-tribonacci-number.md b/docs/solutions/1100-1199/n-th-tribonacci-number.md new file mode 100644 index 00000000..8d84c1ca --- /dev/null +++ b/docs/solutions/1100-1199/n-th-tribonacci-number.md @@ -0,0 +1,121 @@ +# [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) + +- 标签:记忆化搜索、数学、动态规划 +- 难度:简单 + +## 题目链接 + +- [1137. 第 N 个泰波那契数 - 力扣](https://leetcode.cn/problems/n-th-tribonacci-number/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回第 $n$ 个泰波那契数。 + +**说明**: + +- **泰波那契数**:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 +- $0 \le n \le 37$。 +- 答案保证是一个 32 位整数,即 $answer \le 2^{31} - 1$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:4 +解释: +T_3 = 0 + 1 + 1 = 2 +T_4 = 1 + 1 + 2 = 4 +``` + +- 示例 2: + +```python +输入:n = 25 +输出:1389537 +``` + +## 解题思路 + +### 思路 1:记忆化搜索 + +1. 问题的状态定义为:第 $n$ 个泰波那契数。其状态转移方程为:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。 +2. 定义一个长度为 $n + 1$ 数组 `memo` 用于保存一斤个计算过的泰波那契数。 +3. 定义递归函数 `my_tribonacci(n, memo)`。 + 1. 当 $n = 0$ 或者 $n = 1$,或者 $n = 2$ 时直接返回结果。 + 2. 当 $n > 2$ 时,首先检查是否计算过 $T(n)$,即判断 $memo[n]$ 是否等于 $0$。 + 1. 如果 $memo[n] \ne 0$,说明已经计算过 $T(n)$,直接返回 $memo[n]$。 + 2. 如果 $memo[n] = 0$,说明没有计算过 $T(n)$,则递归调用 `my_tribonacci(n - 3, memo)`、`my_tribonacci(n - 2, memo)`、`my_tribonacci(n - 1, memo)`,并将计算结果存入 $memo[n]$ 中,并返回 $memo[n]$。 + +### 思路 1:代码 + +```python +class Solution: + def tribonacci(self, n: int) -> int: + # 使用数组保存已经求解过的 T(k) 的结果 + memo = [0 for _ in range(n + 1)] + return self.my_tribonacci(n, memo) + + def my_tribonacci(self, n: int, memo: List[int]) -> int: + if n == 0: + return 0 + if n == 1 or n == 2: + return 1 + + if memo[n] != 0: + return memo[n] + memo[n] = self.my_tribonacci(n - 3, memo) + self.my_tribonacci(n - 2, memo) + self.my_tribonacci(n - 1, memo) + return memo[n] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:动态规划 + +###### 1. 划分阶段 + +我们可以按照整数顺序进行阶段划分,将其划分为整数 $0 \sim n$。 + +###### 2. 定义状态 + +定义状态 `dp[i]` 为:第 `i` 个泰波那契数。 + +###### 3. 状态转移方程 + +根据题目中所给的泰波那契数的定义:$T_0 = 0, T_1 = 1, T_2 = 1$,且在 $n >= 0$ 的条件下,$T_{n + 3} = T_{n} + T_{n+1} + T_{n+2}$。,则直接得出状态转移方程为 $dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1]$(当 $i > 2$ 时)。 + +###### 4. 初始条件 + +根据题目中所给的初始条件 $T_0 = 0, T_1 = 1, T_2 = 1$ 确定动态规划的初始条件,即 `dp[0] = 0, dp[1] = 1, dp[2] = 1`。 + +###### 5. 最终结果 + +根据状态定义,最终结果为 `dp[n]`,即第 `n` 个泰波那契数为 `dp[n]`。 + +### 思路 2:代码 + +```python +class Solution: + def tribonacci(self, n: int) -> int: + if n == 0: + return 0 + if n == 1 or n == 2: + return 1 + dp = [0 for _ in range(n + 1)] + dp[1] = dp[2] = 1 + for i in range(3, n + 1): + dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1] + return dp[n] +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md b/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md new file mode 100644 index 00000000..c259c450 --- /dev/null +++ b/docs/solutions/1100-1199/number-of-dice-rolls-with-target-sum.md @@ -0,0 +1,93 @@ +# [1155. 掷骰子等于目标和的方法数](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) + +- 标签:动态规划 +- 难度:中等 + +## 题目链接 + +- [1155. 掷骰子等于目标和的方法数 - 力扣](https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/) + +## 题目大意 + +**描述**:有 $n$ 个一样的骰子,每个骰子上都有 $k$ 个面,分别标号为 $1 \sim k$。现在给定三个整数 $n$、$k$ 和 $target$,滚动 $n$ 个骰子。 + +**要求**:计算出使所有骰子正面朝上的数字和等于 $target$ 的方案数量。 + +**说明**: + +- $1 \le n, k \le 30$。 +- $1 \le target \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 1, k = 6, target = 3 +输出:1 +解释:你扔一个有 6 个面的骰子。 +得到 3 的和只有一种方法。 +``` + +- 示例 2: + +```python +输入:n = 2, k = 6, target = 7 +输出:6 +解释:你扔两个骰子,每个骰子有 6 个面。 +得到 7 的和有 6 种方法 1+6 2+5 3+4 4+3 5+2 6+1。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +我们可以将这道题转换为「分组背包问题」中求方案总数的问题。将每个骰子看做是一组物品,骰子每一个面上的数值当做是每组物品中的一个物品。这样问题就转换为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $target$ 的方案数。 + +###### 1. 划分阶段 + +按照总价值 $target$ 进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数。 + +###### 3. 状态转移方程 + +用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数,等于用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w - d$ 的方案数累积值,其中 $d$ 为当前骰子掷出的价值,即:$dp[w] = dp[w] + dp[w - d]$。 + +###### 4. 初始条件 + +- 用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $0$ 的方案数为 $1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[w]$ 表示为:用 $n$ 个骰子($n$ 组物品)进行投掷,投掷出总和(总价值)为 $w$ 的方案数。则最终结果为 $dp[target]$。 + +### 思路 1:代码 + +```python +class Solution: + def numRollsToTarget(self, n: int, k: int, target: int) -> int: + dp = [0 for _ in range(target + 1)] + dp[0] = 1 + MOD = 10 ** 9 + 7 + + # 枚举前 i 组物品 + for i in range(1, n + 1): + # 逆序枚举背包装载重量 + for w in range(target, -1, -1): + dp[w] = 0 + # 枚举第 i - 1 组物品能取个数 + for d in range(1, k + 1): + if w >= d: + dp[w] = (dp[w] + dp[w - d]) % MOD + + return dp[target] % MOD +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times m \times target)$。 +- **空间复杂度**:$O(target)$。 + diff --git a/docs/solutions/1100-1199/parallel-courses.md b/docs/solutions/1100-1199/parallel-courses.md new file mode 100644 index 00000000..e0db5061 --- /dev/null +++ b/docs/solutions/1100-1199/parallel-courses.md @@ -0,0 +1,59 @@ +# [1136. 并行课程](https://leetcode.cn/problems/parallel-courses/) + +- 标签:图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [1136. 并行课程 - 力扣](https://leetcode.cn/problems/parallel-courses/) + +## 题目大意 + +有 N 门课程,分别以 1 到 N 进行编号。现在给定一份课程关系表 `relations[i] = [X, Y]`,用以表示课程 `X` 和课程 `Y` 之间的先修关系:课程 `X` 必须在课程 `Y` 之前修完。假设在一个学期里,你可以学习任何数量的课程,但前提是你已经学习了将要学习的这些课程的所有先修课程。 + +要求:返回学完全部课程所需的最少学期数。如果没有办法做到学完全部这些课程的话,就返回 `-1`。 + +## 解题思路 + +拓扑排序。具体解法如下: + +1. 使用列表 `edges` 存放课程关系图,并统计每门课程节点的入度,存入入度列表 `indegrees`。使用 `ans` 表示学期数。 +2. 借助队列 `queue`,将所有入度为 `0` 的节点入队。 +3. 将队列中所有节点依次取出,学期数 +1。对于取出的每个节点: + 1. 对应课程数 -1。 + 2. 将该顶点以及该顶点为出发点的所有边的另一个节点入度 -1。如果入度 -1 后的节点入度不为 0,则将其加入队列 `queue`。 +4. 重复 3~4 的步骤,直到队列中没有节点。 +5. 最后判断剩余课程数是否为 0,如果为 0,则返回 `ans`,否则,返回 `-1`。 + +## 代码 + +```python +import collections + +class Solution: + def minimumSemesters(self, n: int, relations: List[List[int]]) -> int: + indegrees = [0 for _ in range(n + 1)] + edges = collections.defaultdict(list) + for x, y in relations: + edges[x].append(y) + indegrees[y] += 1 + queue = collections.deque([]) + for i in range(1, n + 1): + if not indegrees[i]: + queue.append(i) + ans = 0 + + while queue: + size = len(queue) + for i in range(size): + x = queue.popleft() + n -= 1 + for y in edges[x]: + indegrees[y] -= 1 + if not indegrees[y]: + queue.append(y) + ans += 1 + + return ans if n == 0 else -1 +``` + diff --git a/docs/solutions/1100-1199/relative-sort-array.md b/docs/solutions/1100-1199/relative-sort-array.md new file mode 100644 index 00000000..31ab0af8 --- /dev/null +++ b/docs/solutions/1100-1199/relative-sort-array.md @@ -0,0 +1,84 @@ +# [1122. 数组的相对排序](https://leetcode.cn/problems/relative-sort-array/) + +- 标签:数组、哈希表、计数排序、排序 +- 难度:简单 + +## 题目链接 + +- [1122. 数组的相对排序 - 力扣](https://leetcode.cn/problems/relative-sort-array/) + +## 题目大意 + +**描述**:给定两个数组,$arr1$ 和 $arr2$,其中 $arr2$ 中的元素各不相同,$arr2$ 中的每个元素都出现在 $arr1$ 中。 + +**要求**:对 $arr1$ 中的元素进行排序,使 $arr1$ 中项的相对顺序和 $arr2$ 中的相对顺序相同。未在 $arr2$ 中出现过的元素需要按照升序放在 $arr1$ 的末尾。 + +**说明**: + +- $1 \le arr1.length, arr2.length \le 1000$。 +- $0 \le arr1[i], arr2[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] +输出:[2,2,2,1,4,3,3,9,6,7,19] +``` + +- 示例 2: + +```python +输入:arr1 = [28,6,22,8,44,17], arr2 = [22,28,8,6] +输出:[22,28,8,6,17,44] +``` + +## 解题思路 + +### 思路 1:计数排序 + +因为元素值范围在 $[0, 1000]$,所以可以使用计数排序的思路来解题。 + +1. 使用数组 $count$ 统计 $arr1$ 各个元素个数。 +2. 遍历 $arr2$ 数组,将对应元素$num2$ 按照个数 $count[num2]$ 添加到答案数组 $ans$ 中,同时在 $count$ 数组中减去对应个数。 +3. 然后在处理 $count$ 中剩余元素,将 $count$ 中大于 $0$ 的元素下标依次添加到答案数组 $ans$ 中。 +4. 最后返回答案数组 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]: + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min + arr1_min, arr1_max = min(arr1), max(arr1) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 + size = arr1_max - arr1_min + 1 + counts = [0 for _ in range(size)] + + # 统计值为 num 的元素出现的次数 + for num in arr1: + counts[num - arr1_min] += 1 + + res = [] + for num in arr2: + while counts[num - arr1_min] > 0: + res.append(num) + counts[num - arr1_min] -= 1 + + for i in range(size): + while counts[i] > 0: + num = i + arr1_min + res.append(num) + counts[i] -= 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n + max(arr_1))$。其中 $m$ 是数组 $arr_1$ 的长度,$n$ 是数组 $arr_2$ 的长度,$max(arr_1)$ 是数组 $arr_1$ 的最大值。 +- **空间复杂度**:$O(max(arr_1))$。 + + + diff --git a/docs/solutions/1200-1299/airplane-seat-assignment-probability.md b/docs/solutions/1200-1299/airplane-seat-assignment-probability.md new file mode 100644 index 00000000..cc3b8bc9 --- /dev/null +++ b/docs/solutions/1200-1299/airplane-seat-assignment-probability.md @@ -0,0 +1,121 @@ +# [1227. 飞机座位分配概率](https://leetcode.cn/problems/airplane-seat-assignment-probability/) + +- 标签:脑筋急转弯、数学、动态规划、概率与统计 +- 难度:中等 + +## 题目链接 + +- [1227. 飞机座位分配概率 - 力扣](https://leetcode.cn/problems/airplane-seat-assignment-probability/) + +## 题目大意 + +**描述**:给定一个整数 $n$,代表 $n$ 位乘客即将登飞机。飞机上刚好有 $n$ 个座位。第一位乘客的票丢了,他随便选择了一个座位坐下。则剩下的乘客将会: + +- 如果自己的座位还空着,就坐到自己的座位上。 +- 如果自己的座位被占用了,就随机选择其他座位。 + +**要求**:计算出第 $n$ 位乘客坐在自己座位上的概率是多少。 + +**说明**: + +- $1 \le n \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 1 +输出:1.00000 +解释:第一个人只会坐在自己的位置上。 +``` + +- 示例 2: + +```python +输入: n = 2 +输出: 0.50000 +解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。 +``` + +## 解题思路 + +### 思路 1:数学 + +我们按照乘客的登机顺序为乘客编下号:$1 \sim n$,我们用 $f(n)$ 来表示第 $n$ 位乘客登机时,坐在自己座位上的概率。先从简单的情况开始考虑: + +当 $n = 1$ 时: + +- 第 $1$ 位乘客只能坐在第 $1$ 个座位上,$f(1) = 1$。 + +当 $n = 2$ 时: + +- 第 $1$ 位乘客有 $\frac{1}{2}$ 的概率选中自己的位置,第 $2$ 位乘客一定能坐到自己的位置上,则第 $2$ 位乘客坐在自己座位上的概率为 $\frac{1}{2} * 1.0$。 +- 第 $1$ 位乘客有 $\frac{1}{2}$ 的概率坐在第 $2$ 位乘客的位置上,第 $2$ 位乘客只能坐到第 $1$ 位乘客的位置上,那么第 $2$ 位乘客坐在自己座位上的概率为 $\frac{1}{2} * 0.0$。 +- 综上,$f(2) = \frac{1}{2} * 1.0 + \frac{1}{2} * 0.0 = 0.5$。 + +当 $n \ge 3$ 时: + +- 先来考虑第 $1$ 位乘客登机情况: + + - 第 $1$ 位乘客有 $\frac{1}{n}$ 的概率选择坐在自己位置上,这样第 $1$ 位到第 $n - 1$ 位乘客的座位都不会被占,第 n 位乘客一定能坐到自己位置上。那么第 n 位乘客坐在自己座位上的概率为 $\frac{1}{n} * 1.0$。 + + - 第 $1$ 位乘客有 $\frac{1}{n}$ 的概率选择坐在第 $n$ 位乘客的位置上,这样第 $2$ 位到第 $n - 1$ 位乘客的座位都不会被占,第 $n$ 位乘客只能坐到第 $1$ 位乘客的位置上,那么第 $n$ 位乘客坐在自己座位上的概率为 $\frac{1}{n} * 0.0$。 + + - 第 $1$ 位乘客有 $\frac{n-2}{n}$ 的概率坐在第 $i$ 号座位上,$2 \le i \le n - 1$,每个座位被选中概率为 $\frac{1}{n}$。这样第 $2$ 位到第 $i - 1$ 位乘客的座位都不会被占。此时第 $i$ 位乘客,会在剩下的 $n - (i - 1)$ 个座位中进行选择: + + - 坐在第 $1$ 位乘客的位置上,这样后面的乘客座位都不会被占,第 $n$ 位乘客一定能坐到自己位置上。 + + - 坐在第 $n$ 个乘客的位置上,这样第 $n$ 个乘客肯定无法坐到自己的位置上。 + + - 在第 $[i + 1, n - 1]$ 之间找个位置坐。 + +- 再来考虑第 $i$ 位乘客登机情况: + - 第 $i$ 为乘客所面临的情况跟第 $1$ 位乘客所面临的情况类似,只不过问题的规模数从 $n$ 减小到了 $n - (i - 1)$。 + +那么综合上面情况,可以得到 $f(n),(n \ge 3)$ 的递推式: + +$\begin{aligned} f(n) & = \frac{1}{n} * 1.0 + \frac{1}{n} * 0.0 + \frac{1}{n} * \sum_{i = 2}^{n-1} f(n - i + 1) \cr & = \frac{1}{n} (1.0 + \sum_{i = 2}^{n-1} f(n - i + 1)) \end{aligned}$ + +接下来我们从等式中寻找规律,消去 $\sum_{i = 2}^{n-1} f(n - i + 1)$ 部分。 + +将 $n$ 换为 $n - 1$,得: + +$\begin{aligned} f(n - 1) & = \frac{1}{n - 1} * 1.0 + \frac{1}{n - 1} * 0.0 + \frac{1}{n - 1} * \sum_{i = 2}^{n-2} f(n - i) \cr & = \frac{1}{n - 1} (1.0 + \sum_{i = 2}^{n-2} f(n - i)) \end{aligned} $ + +将 $f(n) * n$ 与 $f(n - 1) * (n - 1)$ 进行比较: + +$\begin{aligned} f(n) * n & = 1.0 + \sum_{i = 2}^{n-1} f(n - i + 1) & (1) \cr f(n - 1) * (n - 1) & = 1.0 + \sum_{i = 2}^{n-2} f(n - i) & (2) \end{aligned}$ + +将上述 (1)、(2) 式相减得: + +$\begin{aligned} & f(n) * n - f(n - 1) * (n - 1) & \cr = & \sum_{i = 2}^{n-1} f(n - i + 1) - \sum_{i = 2}^{n-2} f(n - i) \cr = & f(n-1) \end{aligned}$ + +整理后得:$f(n) = f(n - 1)$。 + +已知 $f(1) = 1$,$f(2) = 0.5$,因此当 $n \ge 3$ 时,$f(n) = 0.5$。 + +所以可以得出结论: + +$f(n) = \begin{cases} 1.0 & n = 1 \cr 0.5 & n \ge 2 \end{cases}$ + +### 思路 1:代码 + +```python +class Solution: + def nthPersonGetsNthSeat(self, n: int) -> float: + if n == 1: + return 1.0 + else: + return 0.5 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- [飞机座位分配概率 - 力扣(LeetCode)](https://leetcode.cn/problems/airplane-seat-assignment-probability/solution/fei-ji-zuo-wei-fen-pei-gai-lu-by-leetcod-gyw4/) + diff --git a/docs/solutions/1200-1299/check-if-it-is-a-straight-line.md b/docs/solutions/1200-1299/check-if-it-is-a-straight-line.md new file mode 100644 index 00000000..3f5017c1 --- /dev/null +++ b/docs/solutions/1200-1299/check-if-it-is-a-straight-line.md @@ -0,0 +1,50 @@ +# [1232. 缀点成线](https://leetcode.cn/problems/check-if-it-is-a-straight-line/) + +- 标签:几何、数组、数学 +- 难度:简单 + +## 题目链接 + +- [1232. 缀点成线 - 力扣](https://leetcode.cn/problems/check-if-it-is-a-straight-line/) + +## 题目大意 + +给定一系列的二维坐标点的坐标 `(xi, yi)`,判断这些点是否属于同一条直线。若属于同一条直线,则返回 True,否则返回 False。 + +## 解题思路 + +如果根据斜率来判断点是否处于同一条直线,需要处理斜率不存在(无穷大)的情况。我们可以使用叉乘来判断三个点构成的两个向量是否处于同一条直线上。 + +叉乘原理: + +设向量 P 为 `(x1, y1)` 向量,Q 为 `(x2, y2)`,则向量 P、Q 的叉积定义为:$P × Q = x_1y_2 - x_2y_1$,其几何意义表示为如果以向量 P 和向量 Q 为边构成一个平行四边形,那么这两个向量叉乘的模长与这个平行四边形的正面积相等。 + +![向量叉积](https://img.geek-docs.com/mathematical-basis/linear-algebra/220px-Cross_product_parallelogram.png) + +- 如果 `P × Q = 0`,则 P 与 Q 共线,有可能同向,也有可能反向。 +- 如果 `P × Q > 0`,则 P 在 Q 的顺时针方向。 +- 如果 `P × Q < 0`,则 P 在 Q 的逆时针方向。 + +具体求解方法: + +- 先求出第一个坐标与第二个坐标构成的向量 P。 +- 遍历所有坐标,求出所有坐标与第一个坐标构成的向量 Q。 + - 如果 `P × Q ≠ 0`,则返回 False。 +- 如果遍历完仍没有发现 `P × Q ≠ 0`,则返回 True。 + +## 代码 + +```python +class Solution: + def checkStraightLine(self, coordinates: List[List[int]]) -> bool: + x1 = coordinates[1][0] - coordinates[0][0] + y1 = coordinates[1][1] - coordinates[0][1] + + for i in range(len(coordinates)): + x2 = coordinates[i][0] - coordinates[0][0] + y2 = coordinates[i][1] - coordinates[0][1] + if x1 * y2 != x2 * y1: + return False + return True +``` + diff --git a/docs/solutions/1200-1299/count-vowels-permutation.md b/docs/solutions/1200-1299/count-vowels-permutation.md new file mode 100644 index 00000000..a1ea23bd --- /dev/null +++ b/docs/solutions/1200-1299/count-vowels-permutation.md @@ -0,0 +1,103 @@ +# [1220. 统计元音字母序列的数目](https://leetcode.cn/problems/count-vowels-permutation/) + +- 标签:动态规划 +- 难度:困难 + +## 题目链接 + +- [1220. 统计元音字母序列的数目 - 力扣](https://leetcode.cn/problems/count-vowels-permutation/) + +## 题目大意 + +**描述**:给定一个整数 `n`,我们可以按照以下规则生成长度为 `n` 的字符串: + +- 字符串中的每个字符都应当是小写元音字母(`'a'`、`'e'`、`'i'`、`'o'`、`'u'`)。 +- 每个元音 `'a'` 后面都只能跟着 `'e'`。 +- 每个元音 `'e'` 后面只能跟着 `'a'` 或者是 `'i'`。 +- 每个元音 `'i'` 后面不能再跟着另一个 `'i'`。 +- 每个元音 `'o'` 后面只能跟着 `'i'` 或者是 `'u'`。 +- 每个元音 `'u'` 后面只能跟着 `'a'`。 + +**要求**:统计一下我们可以按上述规则形成多少个长度为 `n` 的字符串。由于答案可能会很大,所以请返回模 $10^9 + 7$ 之后的结果。 + +**说明**: + +- $1 \le n \le 2 * 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:10 +解释:所有可能的字符串分别是:"ae", "ea", "ei", "ia", "ie", "io", "iu", "oi", "ou" 和 "ua"。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +根据题目给定的字符串规则,我们可以将其整理一下: + +- 元音字母 `'a'` 前面只能跟着 `'e'`、`'i'`、`'u'`。 +- 元音字母 `'e'` 前面只能跟着 `'a'`、`'i'`。 +- 元音字母 `'i'` 前面只能跟着 `'e'`、`'o'`。 +- 元音字母 `'o'` 前面只能跟着 `'i'`。 +- 元音字母 `'u'` 前面只能跟着 `'o'`、`'i'`。 + +现在我们可以按照字符串的长度以及字符结尾进行阶段划分,并按照上述规则推导状态转移方程。 + +###### 1. 划分阶段 + +按照字符串的结尾位置和结尾位置上的字符进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i][j]` 表示为:长度为 `i` 并且以字符 `j` 结尾的字符串数量。这里 $j = 0, 1, 2, 3, 4$ 分别代表元音字母 `'a'`、`'e'`、`'i'`、`'o'`、`'u'`。 + +###### 3. 状态转移方程 + +通过上面的字符规则,可以得到状态转移方程为: + + +$\begin{cases} dp[i][0] = dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4] \cr dp[i][1] = dp[i - 1][0] + dp[i - 1][2] \cr dp[i][2] = dp[i - 1][1] + dp[i - 1][3] \cr dp[i][3] = dp[i - 1][2] \cr dp[i][4] = dp[i - 1][2] + dp[i - 1][3] \end{cases}$ + +###### 4. 初始条件 + +- 长度为 `1` 并且以字符 `j` 结尾的字符串数量为 `1`,即 `dp[1][j] = 1`。 + +###### 5. 最终结果 + +根据我们之前定义的状态,`dp[i]` 表示为:长度为 `i` 并且以字符 `j` 结尾的字符串数量。则将 `dp[n]` 行所有列相加,就是长度为 `n` 的字符串数量。 + +### 思路 1:动态规划代码 + +```python +class Solution: + def countVowelPermutation(self, n: int) -> int: + mod = 10 ** 9 + 7 + dp = [[0 for _ in range(5)] for _ in range(n + 1)] + + for j in range(5): + dp[1][j] = 1 + + for i in range(2, n + 1): + dp[i][0] = (dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4]) % mod + dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod + dp[i][2] = (dp[i - 1][1] + dp[i - 1][3]) % mod + dp[i][3] = dp[i - 1][2] % mod + dp[i][4] = (dp[i - 1][2] + dp[i - 1][3]) % mod + + ans = 0 + for j in range(5): + ans += dp[n][j] % mod + ans %= mod + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md b/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md new file mode 100644 index 00000000..313bde76 --- /dev/null +++ b/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md @@ -0,0 +1,59 @@ +# [1296. 划分数组为连续数字的集合](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) + +- 标签:贪心、数组、哈希表、排序 +- 难度:中等 + +## 题目链接 + +- [1296. 划分数组为连续数字的集合 - 力扣](https://leetcode.cn/problems/divide-array-in-sets-of-k-consecutive-numbers/) + +## 题目大意 + +**描述**:给定一个整数数组 `nums` 和一个正整数 `k`。 + +**要求**:判断是否可以把这个数组划分成一些由 `k` 个连续数字组成的集合。如果可以,则返回 `True`;否则,返回 `False`。 + +**说明**: + +- $1 \le k \le nums.length \le 10^5$。 +- $1 \le nums[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,3,4,4,5,6], k = 4 +输出:True +解释:数组可以分成 [1,2,3,4] 和 [3,4,5,6]。 +``` + +## 解题思路 + +### 思路 1:哈希表 + 排序 + +1. 使用哈希表存储每个数出现的次数。 +2. 将哈希表中每个键从小到大排序。 +3. 从哈希表中最小的数开始,以它作为当前连续数字的开头,然后依次判断连续的 `k` 个数是否在哈希表中,如果在的话,则将哈希表中对应数的数量减 `1`。不在的话,说明无法满足题目要求,直接返回 `False`。 +4. 重复执行 2 ~ 3 步,直到哈希表为空。最后返回 `True`。 + +### 思路 1:哈希表 + 排序代码 + +```python +class Solution: + def isPossibleDivide(self, nums: List[int], k: int) -> bool: + hand_map = collections.defaultdict(int) + for i in range(len(nums)): + hand_map[nums[i]] += 1 + for key in sorted(hand_map.keys()): + value = hand_map[key] + if value == 0: + continue + count = 0 + for i in range(k): + hand_map[key + count] -= value + if hand_map[key + count] < 0: + return False + count += 1 + return True +``` diff --git a/docs/solutions/1200-1299/find-elements-in-a-contaminated-binary-tree.md b/docs/solutions/1200-1299/find-elements-in-a-contaminated-binary-tree.md new file mode 100644 index 00000000..646a109b --- /dev/null +++ b/docs/solutions/1200-1299/find-elements-in-a-contaminated-binary-tree.md @@ -0,0 +1,115 @@ +# [1261. 在受污染的二叉树中查找元素](https://leetcode.cn/problems/find-elements-in-a-contaminated-binary-tree/) + +- 标签:树、深度优先搜索、广度优先搜索、设计、哈希表、二叉树 +- 难度:中等 + +## 题目链接 + +- [1261. 在受污染的二叉树中查找元素 - 力扣](https://leetcode.cn/problems/find-elements-in-a-contaminated-binary-tree/) + +## 题目大意 + +**描述**:给出一满足下属规则的二叉树的根节点 $root$: + +1. $root.val == 0$。 +2. 如果 $node.val == x$ 且 $node.left \ne None$,那么 $node.left.val == 2 \times x + 1$。 +3. 如果 $node.val == x$ 且 $node.right \ne None$,那么 $node.left.val == 2 \times x + 2$​。 + +现在这个二叉树受到「污染」,所有的 $node.val$ 都变成了 $-1$。 + +**要求**:请你先还原二叉树,然后实现 `FindElements` 类: + +- `FindElements(TreeNode* root)` 用受污染的二叉树初始化对象,你需要先把它还原。 +- `bool find(int target)` 判断目标值 $target$ 是否存在于还原后的二叉树中并返回结果。 + +**说明**: + +- $node.val == -1$ +- 二叉树的高度不超过 $20$。 +- 节点的总数在 $[1, 10^4]$ 之间。 +- 调用 `find()` 的总次数在 $[1, 10^4]$ 之间。 +- $0 \le target \le 10^6$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/16/untitled-diagram-4-1.jpg) + +```python +输入: +["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: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/16/untitled-diagram-4.jpg) + +```python +输入: +["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 +``` + +## 解题思路 + +### 思路 1:哈希表 + 深度优先搜索 + +1. 从根节点开始进行还原。 +2. 然后使用深度优先搜索的方式,依次递归还原左右两个孩子节点。 +3. 递归还原的同时,将还原之后的所有节点值,存入集合 $val\underline{\hspace{0.5em}}set$ 中。 + +这样就可以在 $O(1)$ 的时间复杂度内判断目标值 $target$ 是否在还原后的二叉树中了。 + +### 思路 1:代码 + +```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class FindElements: + + def __init__(self, root: Optional[TreeNode]): + self.val_set = set() + def dfs(node, val): + if not node: + return + self.val_set.add(val) + dfs(node.left, val * 2 + 1) + dfs(node.right, val * 2 + 2) + + dfs(root, 0) + + + def find(self, target: int) -> bool: + return target in self.val_set + + + +# Your FindElements object will be instantiated and called as such: +# obj = FindElements(root) +# param_1 = obj.find(target) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:还原二叉树:$O(n)$,其中 $n$ 为二叉树中的节点个数。查找目标值:$O(1)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1200-1299/get-equal-substrings-within-budget.md b/docs/solutions/1200-1299/get-equal-substrings-within-budget.md new file mode 100644 index 00000000..89280181 --- /dev/null +++ b/docs/solutions/1200-1299/get-equal-substrings-within-budget.md @@ -0,0 +1,83 @@ +# [1208. 尽可能使字符串相等](https://leetcode.cn/problems/get-equal-substrings-within-budget/) + +- 标签:字符串、二分查找、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1208. 尽可能使字符串相等 - 力扣](https://leetcode.cn/problems/get-equal-substrings-within-budget/) + +## 题目大意 + +**描述**:给定两个长度相同的字符串,$s$ 和 $t$。将 $s$ 中的第 $i$ 个字符变到 $t$ 中的第 $i$ 个字符需要 $| s[i] - t[i] |$ 的开销(开销可能为 $0$),也就是两个字符的 ASCII 码值的差的绝对值。用于变更字符串的最大预算是 $maxCost$。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。 + +**要求**:如果你可以将 $s$ 的子字符串转化为它在 $t$ 中对应的子字符串,则返回可以转化的最大长度。如果 $s$ 中没有子字符串可以转化成 $t$ 中对应的子字符串,则返回 $0$。 + +**说明**: + +- $1 \le s.length, t.length \le 10^5$。 +- $0 \le maxCost \le 10^6$。 +- $s$ 和 $t$ 都只含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "abcd", t = "bcdf", maxCost = 3 +输出:3 +解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。 +``` + +- 示例 2: + +```python +输入:s = "abcd", t = "cdef", maxCost = 3 +输出:1 +解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +维护一个滑动窗口 $window\underline{\hspace{0.5em}}sum$ 用于记录窗口内的开销总和,保证窗口内的开销总和小于等于 $maxCost$。使用 $ans$ 记录可以转化的最大长度。具体做法如下: + +使用两个指针 $left$、$right$。分别指向滑动窗口的左右边界,保证窗口内所有元素转化开销总和小于等于 $maxCost$。 + +- 先统计出 $s$ 中第 $i$ 个字符变为 $t$ 的第 $i$ 个字符的开销,用数组 $costs$ 保存。 +- 一开始,$left$、$right$ 都指向 $0$。 +- 将最右侧字符的转变开销填入窗口中,向右移动 $right$。 +- 直到窗口内开销总和 $window\underline{\hspace{0.5em}}sum$ 大于 $maxCost$。则不断右移 $left$,缩小窗口长度。直到 $window\underline{\hspace{0.5em}}sum \le maxCost$ 时,更新可以转换的最大长度 $ans$。 +- 向右移动 $right$,直到 $right \ge len(s)$ 为止。 +- 输出答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def equalSubstring(self, s: str, t: str, maxCost: int) -> int: + size = len(s) + costs = [0 for _ in range(size)] + for i in range(size): + costs[i] = abs(ord(s[i]) - ord(t[i])) + + left, right = 0, 0 + ans = 0 + window_sum = 0 + while right < size: + window_sum += costs[right] + while window_sum > maxCost: + window_sum -= costs[left] + left += 1 + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**: +- **空间复杂度**: + diff --git a/docs/solutions/1200-1299/index.md b/docs/solutions/1200-1299/index.md new file mode 100644 index 00000000..80fddc7e --- /dev/null +++ b/docs/solutions/1200-1299/index.md @@ -0,0 +1,18 @@ +## 本章内容 + +- [1202. 交换字符串中的元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/smallest-string-with-swaps.md) +- [1208. 尽可能使字符串相等](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/get-equal-substrings-within-budget.md) +- [1217. 玩筹码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md) +- [1220. 统计元音字母序列的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/count-vowels-permutation.md) +- [1227. 飞机座位分配概率](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/airplane-seat-assignment-probability.md) +- [1229. 安排会议日程](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/meeting-scheduler.md) +- [1232. 缀点成线](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/check-if-it-is-a-straight-line.md) +- [1245. 树的直径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/tree-diameter.md) +- [1247. 交换字符使得字符串相同](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md) +- [1253. 重构 2 行二进制矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/reconstruct-a-2-row-binary-matrix.md) +- [1254. 统计封闭岛屿的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/number-of-closed-islands.md) +- [1261. 在受污染的二叉树中查找元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/find-elements-in-a-contaminated-binary-tree.md) +- [1266. 访问所有点的最小时间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/minimum-time-visiting-all-points.md) +- [1268. 搜索推荐系统](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/search-suggestions-system.md) +- [1281. 整数的各位积和之差](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer.md) +- [1296. 划分数组为连续数字的集合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/divide-array-in-sets-of-k-consecutive-numbers.md) diff --git a/docs/solutions/1200-1299/meeting-scheduler.md b/docs/solutions/1200-1299/meeting-scheduler.md new file mode 100644 index 00000000..3ebc139a --- /dev/null +++ b/docs/solutions/1200-1299/meeting-scheduler.md @@ -0,0 +1,87 @@ +# [1229. 安排会议日程](https://leetcode.cn/problems/meeting-scheduler/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [1229. 安排会议日程 - 力扣](https://leetcode.cn/problems/meeting-scheduler/) + +## 题目大意 + +**描述**:给定两位客户的空闲时间表:$slots1$ 和 $slots2$,再给定会议的预计持续时间 $duration$。 + +其中 $slots1[i] = [start_i, end_i]$ 表示空闲时间第从 $start_i$ 开始,到 $end_i$ 结束。$slots2$ 也是如此。 + +**要求**:为他们安排合适的会议时间,如果有合适的会议时间,则返回该时间的起止时刻。如果没有满足要求的会议时间,就请返回一个 空数组。 + +**说明**: + +- **会议时间**:两位客户都有空参加,并且持续时间能够满足预计时间 $duration$ 的最早的时间间隔。 +- 题目保证数据有效。同一个人的空闲时间不会出现交叠的情况,也就是说,对于同一个人的两个空闲时间 $[start1, end1]$ 和 $[start2, end2]$,要么 $start1 > end2$,要么 $start2 > end1$。 +- $1 \le slots1.length, slots2.length \le 10^4$。 +- $slots1[i].length, slots2[i].length == 2$。 +- $slots1[i][0] < slots1[i][1]$。 +- $slots2[i][0] < slots2[i][1]$。 +- $0 \le slots1[i][j], slots2[i][j] \le 10^9$。 +- $1 \le duration \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 8 +输出:[60,68] +``` + +- 示例 2: + +```python +输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 12 +输出:[] +``` + +## 解题思路 + +### 思路 1:分离双指针 + +题目保证了同一个人的空闲时间不会出现交叠。那么可以先直接对两个客户的空间时间表按照开始时间从小到大排序。然后使用分离双指针来遍历两个数组,求出重合部分,并判断重合区间是否大于等于 $duration$。具体做法如下: + +1. 先对两个数组排序。 +2. 然后使用两个指针 $left\underline{\hspace{0.5em}}1$、$left\underline{\hspace{0.5em}}2$。$left\underline{\hspace{0.5em}}1$ 指向第一个数组开始位置,$left\underline{\hspace{0.5em}}2$ 指向第二个数组开始位置。 +3. 遍历两个数组。计算当前两个空闲时间区间的重叠范围。 + 1. 如果重叠范围大于等于 $duration$,直接返回当前重叠范围开始时间和会议结束时间,即 $[start, start + duration]$,$start$ 为重叠范围开始时间。 + 2. 如果第一个客户的空闲结束时间小于第二个客户的空闲结束时间,则令 $left\underline{\hspace{0.5em}}1$ 右移,即 `left_1 += 1`,继续比较重叠范围。 + 3. 如果第一个客户的空闲结束时间大于等于第二个客户的空闲结束时间,则令 $left\underline{\hspace{0.5em}}2$ 右移,即 `left_2 += 1`,继续比较重叠范围。 +4. 直到 $left\underline{\hspace{0.5em}}1 == len(slots1)$ 或者 $left\underline{\hspace{0.5em}}2 == len(slots2)$ 时跳出循环,返回空数组 $[]$。 + +### 思路 1:代码 + +```python +class Solution: + def minAvailableDuration(self, slots1: List[List[int]], slots2: List[List[int]], duration: int) -> List[int]: + slots1.sort() + slots2.sort() + size1 = len(slots1) + size2 = len(slots2) + left_1, left_2 = 0, 0 + while left_1 < size1 and left_2 < size2: + start_1, end_1 = slots1[left_1] + start_2, end_2 = slots2[left_2] + start = max(start_1, start_2) + end = min(end_1, end_2) + if end - start >= duration: + return [start, start + duration] + if end_1 < end_2: + left_1 += 1 + else: + left_2 += 1 + return [] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n + m \times \log m)$,其中 $n$、$m$ 分别为数组 $slots1$、$slots2$ 中的元素个数。 +- **空间复杂度**:$O(\log n + \log m)$。 + diff --git a/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md b/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md new file mode 100644 index 00000000..80cfc3d5 --- /dev/null +++ b/docs/solutions/1200-1299/minimum-cost-to-move-chips-to-the-same-position.md @@ -0,0 +1,70 @@ +# [1217. 玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) + +- 标签:贪心、数组、数学 +- 难度:简单 + +## 题目链接 + +- [1217. 玩筹码 - 力扣](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position/) + +## 题目大意 + +**描述**:给定一个数组 $position$ 代表 $n$ 个筹码的位置,其中 $position[i]$ 代表第 $i$ 个筹码的位置。现在需要把所有筹码移到同一个位置。在一步中,我们可以将第 $i$ 个芯片的位置从 $position[i]$ 改变为: + +- $position[i] + 2$ 或 $position[i] - 2$,此时 $cost = 0$; +- $position[i] + 1$ 或 $position[i] - 1$,此时 $cost = 1$。 + +即移动偶数位长度的代价为 $0$,移动奇数位长度的代价为 $1$。 + +**要求**:返回将所有筹码移动到同一位置上所需要的 最小代价 。 + +**说明**: + +- $1 \le chips.length \le 100$。 +- $1 \le chips[i] \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:position = [2,2,2,3,3] +输出:2 +解释:我们可以把位置3的两个芯片移到位置 2。每一步的成本为 1。总成本 = 2。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +题目中移动偶数位长度是不需要代价的,所以奇数位移动到奇数位不需要代价,偶数位移动到偶数位也不需要代价。 + +则我们可以想将所有偶数位都移动到下标为 $0$ 的位置,奇数位都移动到下标为 $1$ 的位置。 + +这样,所有的奇数位、偶数位上的人都到相同或相邻位置了。 + +我们只需要统计一下奇数位和偶数位的数字个数。将少的数移动到多的数上边就是最小代价。 + +则这道题就可以通过以下步骤求解: + +- 遍历数组,统计数组中奇数个数和偶数个数。 +- 返回奇数个数和偶数个数中较小的数即为答案。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def minCostToMoveChips(self, position: List[int]) -> int: + odd, even = 0, 0 + for p in position: + if p & 1: + odd += 1 + else: + even += 1 + return min(odd, even) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $poition$ 的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md b/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md new file mode 100644 index 00000000..ee430f53 --- /dev/null +++ b/docs/solutions/1200-1299/minimum-swaps-to-make-strings-equal.md @@ -0,0 +1,88 @@ +# [1247. 交换字符使得字符串相同](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) + +- 标签:贪心、数学、字符串 +- 难度:中等 + +## 题目链接 + +- [1247. 交换字符使得字符串相同 - 力扣](https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/) + +## 题目大意 + +**描述**:给定两个长度相同的字符串 $s1$ 和 $s2$,并且两个字符串中只含有字符 `'x'` 和 `'y'`。现在需要通过「交换字符」的方式使两个字符串相同。 + +- 每次「交换字符」,需要分别从两个字符串中各选一个字符进行交换。 +- 「交换字符」只能发生在两个不同的字符串之间,不能发生在同一个字符串内部。 + +**要求**:返回使 $s1$ 和 $s2$ 相同的最小交换次数,如果没有方法能够使得这两个字符串相同,则返回 $-1$。 + +**说明**: + +- $1 \le s1.length, s2.length \le 1000$。 +- $s1$、$ s2$ 只包含 `'x'` 或 `'y'`。 + +**示例**: + +- 示例 1: + +```python +输入:s1 = "xy", s2 = "yx" +输出:2 +解释: +交换 s1[0] 和 s2[0],得到 s1 = "yy",s2 = "xx" 。 +交换 s1[0] 和 s2[1],得到 s1 = "xy",s2 = "xy" 。 +注意,你不能交换 s1[0] 和 s1[1] 使得 s1 变成 "yx",因为我们只能交换属于两个不同字符串的字符。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +- 如果 $s1 == s2$,则不需要交换。 +- 如果 `s1 = "xx"`,`s2 = "yy"`,则最少需要交换一次,才可以使两个字符串相等。 +- 如果 `s1 = "yy"`,`s2 = "xx"`,则最少需要交换一次,才可以使两个字符串相等。 +- 如果 `s1 = "xy"`,`s2 = "yx"`,则最少需要交换两次,才可以使两个字符串相等。 +- 如果 `s1 = "yx"`,`s2 = "xy"`,则最少需要交换两次,才可以使两个字符串相等。 + +则可以总结为: + +- `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"` 只需要交换一次。 +- `"xy"` 与 `"yx"`、`"yx"` 与 `"xy"` 需要交换两次。 + +我们把这两种情况分别进行统计。 + +- 当遇到 $s1[i] == s2[i]$ 时直接跳过。 +- 当遇到 `s1[i] == 'x'`,`s2[i] == 'y'` 时,则统计数量到变量 $xyCnt$ 中。 +- 当遇到 `s1[i] == 'y'`,`s2[i] == 'y'` 时,则统计数量到变量 $yxCnt$ 中。 + +则最后我们只需要判断 $xyCnt$ 和 $yxCnt$ 的个数即可。 + +- 如果 $xyCnt + yxCnt$ 是奇数,则说明最终会有一个位置上的两个字符无法通过交换相匹配。 +- 如果 $xyCnt + yxCnt$ 是偶数,并且 $xyCnt$ 为偶数,则 $yxCnt$ 也为偶数。则优先交换 `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"`。即每两个 $xyCnt$ 对应一次交换,每两个 $yxCnt$ 对应交换一次,则结果为 $xyCnt \div 2 + yxCnt \div 2$。 +- 如果 $xyCnt + yxCnt$ 是偶数,并且 $xyCnt$ 为奇数,则 $yxCnt$ 也为奇数。则优先交换 `"xx"` 与 `"yy"`、`"yy"` 与 `"xx"`。即每两个 $xyCnt$ 对应一次交换,每两个 $yxCnt$ 对应交换一次,则结果为 $xyCnt \div 2 + yxCnt \div 2$。最后还剩一组 `"xy"` 与 `"yx"` 或者 `"yx"` 与 `"xy"`,则再交换一次,则结果为 $xyCnt \div 2 + yxCnt \div 2 + 2$。 + +以上结果可以统一写成 $xyCnt \div 2 + yxCnt \div 2 + xyCnt \mod 2 \times 2$。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def minimumSwap(self, s1: str, s2: str) -> int: + xyCnt, yxCnt = 0, 0 + for i in range(len(s1)): + if s1[i] == s2[i]: + continue + if s1[i] == 'x': + xyCnt += 1 + else: + yxCnt += 1 + + if (xyCnt + yxCnt) & 1: + return -1 + return xyCnt // 2 + yxCnt // 2 + (xyCnt % 2 * 2) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1200-1299/minimum-time-visiting-all-points.md b/docs/solutions/1200-1299/minimum-time-visiting-all-points.md new file mode 100644 index 00000000..d0b74705 --- /dev/null +++ b/docs/solutions/1200-1299/minimum-time-visiting-all-points.md @@ -0,0 +1,90 @@ +# [1266. 访问所有点的最小时间](https://leetcode.cn/problems/minimum-time-visiting-all-points/) + +- 标签:几何、数组、数学 +- 难度:简单 + +## 题目链接 + +- [1266. 访问所有点的最小时间 - 力扣](https://leetcode.cn/problems/minimum-time-visiting-all-points/) + +## 题目大意 + +**描述**:给定 $n$ 个点的整数坐标数组 $points$。其中 $points[i] = [xi, yi]$,表示第 $i$ 个点坐标为 $(xi, yi)$。可以按照以下规则在平面上移动: + +1. 每一秒内,可以: + 1. 沿着水平方向移动一个单位长度。 + 2. 沿着竖直方向移动一个单位长度。 + 3. 沿着对角线移动 $\sqrt 2$ 个单位长度(可看做在一秒内沿着水平方向和竖直方向各移动一个单位长度)。 +2. 必须按照坐标数组 $points$ 中的顺序来访问这些点。 +3. 在访问某个点时,可以经过该点后面出现的点,但经过的那些点不算作有效访问。 + +**要求**:计算出访问这些点需要的最小时间(以秒为单位)。 + +**说明**: + +- $points.length == n$。 +- $1 \le n \le 100$。 +- $points[i].length == 2$。 +- $-1000 \le points[i][0], points[i][1] \le 1000$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/24/1626_example_1.png) + +```python +输入:points = [[1,1],[3,4],[-1,0]] +输出:7 +解释:一条最佳的访问路径是: [1,1] -> [2,2] -> [3,3] -> [3,4] -> [2,3] -> [1,2] -> [0,1] -> [-1,0] +从 [1,1] 到 [3,4] 需要 3 秒 +从 [3,4] 到 [-1,0] 需要 4 秒 +一共需要 7 秒 +``` + +```python +输入:points = [[3,2],[-2,2]] +输出:5 +``` + +## 解题思路 + +### 思路 1:数学 + +根据题意,每一秒可以沿着水平方向移动一个单位长度、或者沿着竖直方向移动一个单位长度、或者沿着对角线移动 $\sqrt 2$ 个单位长度。而沿着对角线移动 $\sqrt 2$ 个单位长度可以看做是先沿着水平方向移动一个单位长度,又沿着竖直方向移动一个单位长度,算是一秒走了两步距离。 + +现在假设从 A 点(坐标为 $(x1, y1)$)移动到 B 点(坐标为 $(x2, y2)$)。 + +那么从 A 点移动到 B 点如果要想得到最小时间,我们应该计算出沿着水平方向走的距离为 $dx = |x2 - x1|$,沿着竖直方向走的距离为 $dy = |y2 - y1|$。 + +然后比较沿着水平方向的移动距离和沿着竖直方向的移动距离。 + +- 如果 $dx > dy$,则我们可以先沿着对角线移动 $dy$ 次,再水平移动 $dx - dy$ 次,总共 $dx$ 次。 +- 如果 $dx == dy$,则我们可以直接沿着对角线移动 $dx$ 次,总共 $dx$ 次。 +- 如果 $dx < dy$,则我们可以先沿着对角线移动 $dx$ 次,再水平移动 $dy - dx$ 次,,总共 $dy$ 次。 + +根据上面观察可以发现:最小时间取决于「走的步数较多的那个方向所走的步数」,即 $max(dx, dy)$。 + +根据题目要求,需要按照坐标数组 $points$ 中的顺序来访问这些点,则我们需要按顺序遍历整个数组,计算出相邻点之间的 $max(dx, dy)$,将其累加到答案中。 + +最后将答案输出即可。 + +### 思路 1:代码 + +```python +class Solution: + def minTimeToVisitAllPoints(self, points: List[List[int]]) -> int: + ans = 0 + x1, y1 = points[0] + for point in points: + x2, y2 = point + ans += max(abs(x2 - x1), abs(y2 - y1)) + x1, y1 = point + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1200-1299/number-of-closed-islands.md b/docs/solutions/1200-1299/number-of-closed-islands.md new file mode 100644 index 00000000..bb6ef6f7 --- /dev/null +++ b/docs/solutions/1200-1299/number-of-closed-islands.md @@ -0,0 +1,91 @@ +# [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1254. 统计封闭岛屿的数目 - 力扣](https://leetcode.cn/problems/number-of-closed-islands/) + +## 题目大意 + +**描述**:给定一个二维矩阵 `grid`,每个位置要么是陆地(记号为 `0`)要么是水域(记号为 `1`)。 + +我们从一块陆地出发,每次可以往上下左右 `4` 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。 + +如果一座岛屿完全由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为「封闭岛屿」。 + +**要求**:返回封闭岛屿的数目。 + +**说明**: + +- $1 \le grid.length, grid[0].length \le 100$。 +- $0 \le grid[i][j] \le 1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2019/10/31/sample_3_1610.png) + +```python +输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]] +输出:2 +解释:灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/11/07/sample_4_1610.png) + +```python +输入:grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]] +输出:1 +``` + +## 解题思路 + +### 思路 1:深度优先搜索 + +1. 从 `grid[i][j] == 0` 的位置出发,使用深度优先搜索的方法遍历上下左右四个方向上相邻区域情况。 + 1. 如果上下左右都是 `grid[i][j] == 1`,则返回 `True`。 + 2. 如果有一个以上方向的 `grid[i][j] == 0`,则返回 `False`。 + 3. 遍历之后将当前陆地位置置为 `1`,表示该位置已经遍历过了。 +2. 最后统计出上下左右都满足 `grid[i][j] == 1` 的情况数量,即为答案。 + +### 思路 1:代码 + +```python +class Solution: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(self, grid, i, j): + n, m = len(grid), len(grid[0]) + if i < 0 or i >= n or j < 0 or j >= m: + return False + if grid[i][j] == 1: + return True + grid[i][j] = 1 + + res = True + for direct in self.directs: + new_i = i + direct[0] + new_j = j + direct[1] + if not self.dfs(grid, new_i, new_j): + res = False + return res + + def closedIsland(self, grid: List[List[int]]) -> int: + res = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == 0 and self.dfs(grid, i, j): + res += 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。 +- **空间复杂度**:$O(m \times n)$。 \ No newline at end of file diff --git a/docs/solutions/1200-1299/reconstruct-a-2-row-binary-matrix.md b/docs/solutions/1200-1299/reconstruct-a-2-row-binary-matrix.md new file mode 100644 index 00000000..c7439211 --- /dev/null +++ b/docs/solutions/1200-1299/reconstruct-a-2-row-binary-matrix.md @@ -0,0 +1,91 @@ +# [1253. 重构 2 行二进制矩阵](https://leetcode.cn/problems/reconstruct-a-2-row-binary-matrix/) + +- 标签:贪心、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1253. 重构 2 行二进制矩阵 - 力扣](https://leetcode.cn/problems/reconstruct-a-2-row-binary-matrix/) + +## 题目大意 + +**描述**:给定一个 $2$ 行 $n$ 列的二进制数组: + +- 矩阵是一个二进制矩阵,这意味着矩阵中的每个元素不是 $0$ 就是 $1$。 +- 第 $0$ 行的元素之和为 $upper$。 +- 第 $1$ 行的元素之和为 $lowe$r。 +- 第 $i$ 列(从 $0$ 开始编号)的元素之和为 $colsum[i]$,$colsum$ 是一个长度为 $n$ 的整数数组。 + +**要求**:你需要利用 $upper$,$lower$ 和 $colsum$ 来重构这个矩阵,并以二维整数数组的形式返回它。 + +**说明**: + +- 如果有多个不同的答案,那么任意一个都可以通过本题。 +- 如果不存在符合要求的答案,就请返回一个空的二维数组。 +- $1 \le colsum.length \le 10^5$。 +- $0 \le upper, lower \le colsum.length$。 +- $0 \le colsum[i] \le 2$。 + +**示例**: + +- 示例 1: + +```python +输入:upper = 2, lower = 1, colsum = [1,1,1] +输出:[[1,1,0],[0,0,1]] +解释:[[1,0,1],[0,1,0]] 和 [[0,1,1],[1,0,0]] 也是正确答案。 +``` + +- 示例 2: + +```python +输入:upper = 2, lower = 3, colsum = [2,2,1,1] +输出:[] +``` + +## 解题思路 + +### 思路 1:贪心算法 + +1. 先构建一个 $2 \times n$ 的答案数组 $ans$,其中 $ans[0]$ 表示矩阵的第 $0$ 行,$ans[1]$ 表示矩阵的第 $1$​ 行。 +2. 遍历数组 $colsum$,对于当前列的和 $colsum[i]$ 来说: + 1. 如果 $colsum[i] == 2$,则需要将 $ans[0][i]$ 和 $ans[1][i]$ 都置为 $1$,此时 $upper$ 和 $lower$ 各自减去 $1$。 + 2. 如果 $colsum[i] == 1$,则需要将 $ans[0][i]$ 置为 $1$ 或将 $ans[1][i]$ 置为 $1$。我们优先使用元素和多的那一项。 + 1. 如果 $upper > lower$,则优先使用 $upper$,将 $ans[0][i]$ 置为 $1$,并且令 $upper$ 减去 $1$。 + 2. 如果 $upper \le lower$,则优先使用 $lower$,将 $ans[1][i]$ 置为 $1$,并且令 $lower$ 减去 $1$。 + 3. 如果 $colsum[i] == 0$,则需要将 $ans[0][i]$ 和 $ans[1][i]$ 都置为 $0$。 +3. 在遍历过程中,如果出现 $upper < 0$ 或者 $lower < 0$,则说明无法构造出满足要求的矩阵,则直接返回空数组。 +4. 遍历结束后,如果 $upper$ 和 $lower$ 都为 $0$,则返回答案数组 $ans$;否则返回空数组。 + +### 思路 1:代码 + +```Python +class Solution: + def reconstructMatrix(self, upper: int, lower: int, colsum: List[int]) -> List[List[int]]: + size = len(colsum) + ans = [[0 for _ in range(size)] for _ in range(2)] + + for i in range(size): + if colsum[i] == 2: + ans[0][i] = ans[1][i] = 1 + upper -= 1 + lower -= 1 + elif colsum[i] == 1: + if upper > lower: + ans[0][i] = 1 + upper -= 1 + else: + ans[1][i] = 1 + lower -= 1 + if upper < 0 or lower < 0: + return [] + if lower != 0 or upper != 0: + return [] + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1200-1299/search-suggestions-system.md b/docs/solutions/1200-1299/search-suggestions-system.md new file mode 100644 index 00000000..c21c0e27 --- /dev/null +++ b/docs/solutions/1200-1299/search-suggestions-system.md @@ -0,0 +1,82 @@ +# [1268. 搜索推荐系统](https://leetcode.cn/problems/search-suggestions-system/) + +- 标签:字典树、数组、字符串 +- 难度:中等 + +## 题目链接 + +- [1268. 搜索推荐系统 - 力扣](https://leetcode.cn/problems/search-suggestions-system/) + +## 题目大意 + +给定一个产品数组 `products` 和一个字符串 `searchWord` ,`products` 数组中每个产品都是一个字符串。 + +要求:设计一个推荐系统,在依次输入单词 `searchWord` 的每一个字母后,推荐 `products` 数组中前缀与 `searchWord` 相同的最多三个产品(如果前缀相同的可推荐产品超过三个,请按字典序返回最小的三个)。 + +- 请你以二维列表的形式,返回在输入 `searchWord` 每个字母后相应的推荐产品的列表。 + +## 解题思路 + +先将产品数组按字典序排序。 + +然后使用字典树结构存储每个产品,并在字典树中维护一个数组,用于表示当前前缀所对应的产品列表(只保存最多 3 个产品)。 + +在查询的时候,将不同前缀所对应的产品列表加入到答案数组中。 + +最后输出答案数组。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.words = list() + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + if len(cur.words) < 3: + cur.words.append(word) + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + res = [] + flag = False + for ch in word: + if flag or ch not in cur.children: + res.append([]) + flag = True + else: + cur = cur.children[ch] + res.append(cur.words) + + return res + +class Solution: + def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]: + products.sort() + trie_tree = Trie() + for product in products: + trie_tree.insert(product) + + return trie_tree.search(searchWord) +``` + diff --git a/docs/solutions/1200-1299/smallest-string-with-swaps.md b/docs/solutions/1200-1299/smallest-string-with-swaps.md new file mode 100644 index 00000000..0ac0d952 --- /dev/null +++ b/docs/solutions/1200-1299/smallest-string-with-swaps.md @@ -0,0 +1,107 @@ +# [1202. 交换字符串中的元素](https://leetcode.cn/problems/smallest-string-with-swaps/) + +- 标签:深度优先搜索、广度优先搜索、并查集、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [1202. 交换字符串中的元素 - 力扣](https://leetcode.cn/problems/smallest-string-with-swaps/) + +## 题目大意 + +**描述**:给定一个字符串 `s`,再给定一个数组 `pairs`,其中 `pairs[i] = [a, b]` 表示字符串的第 `a` 个字符可以跟第 `b` 个字符交换。只要满足 `pairs` 中的交换关系,可以任意多次交换字符串中的字符。 + +**要求**:返回 `s` 经过若干次交换之后,可以变成的字典序最小的字符串。 + +**说明**: + +- $1 \le s.length \le 10^5$。 +- $0 \le pairs.length \le 10^5$。 +- $0 \le pairs[i][0], pairs[i][1] < s.length$。 +- `s` 中只含有小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "dcab", pairs = [[0,3],[1,2]] +输出:"bacd" +解释: +交换 s[0] 和 s[3], s = "bcad" +交换 s[1] 和 s[2], s = "bacd" +``` + +- 示例 2: + +```python +输入:s = "dcab", pairs = [[0,3],[1,2],[0,2]] +输出:"abcd" +解释: +交换 s[0] 和 s[3], s = "bcad" +交换 s[0] 和 s[2], s = "acbd" +交换 s[1] 和 s[2], s = "abcd" +``` + +## 解题思路 + +### 思路 1:并查集 + +如果第 `a` 个字符可以跟第 `b` 个字符交换,第 `b` 个字符可以跟第 `c` 个字符交换,那么第 `a` 个字符、第 `b` 个字符、第 `c` 个字符之间就可以相互交换。我们可以把可以相互交换的「位置」都放入一个集合中。然后对每个集合中的字符进行排序。然后将其放置回在字符串中原有位置即可。 + +### 思路 1:代码 + +```python +import collections + +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str: + size = len(s) + union_find = UnionFind(size) + for pair in pairs: + union_find.union(pair[0], pair[1]) + mp = collections.defaultdict(list) + + for i, ch in enumerate(s): + mp[union_find.find(i)].append(ch) + + for vec in mp.values(): + vec.sort(reverse=True) + + ans = [] + for i in range(size): + x = union_find.find(i) + ans.append(mp[x][-1]) + mp[x].pop() + + return "".join(ans) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2 n + m * \alpha(n))$。其中 $n$ 是字符串的长度,$m$ 为 $pairs$ 的索引对数量,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer.md b/docs/solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer.md new file mode 100644 index 00000000..44723f2c --- /dev/null +++ b/docs/solutions/1200-1299/subtract-the-product-and-sum-of-digits-of-an-integer.md @@ -0,0 +1,56 @@ +# [1281. 整数的各位积和之差](https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [1281. 整数的各位积和之差 - 力扣](https://leetcode.cn/problems/subtract-the-product-and-sum-of-digits-of-an-integer/) + +## 题目大意 + +**描述**:给定一个整数 `n`。 + +**要求**:计算并返回该整数「各位数字之积」与「各位数字之和」的差。 + +**说明**: + +- $1 <= n <= 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 234 +输出:15 + +解释: +各位数之积 2 * 3 * 4 = 24 +各位数之和 2 + 3 + 4 = 9 +结果 24 - 9 = 15 +``` + +## 解题思路 + +### 思路 1:数学 + +- 通过取模运算得到 `n` 的最后一位,即 `n %= 10`。 +- 然后去除 `n` 的最后一位,及`n //= 10`。 +- 一次求出各位数字之积与各位数字之和,并返回其差值。 + +### 思路 1:数学代码 + +```python +class Solution: + def subtractProductAndSum(self, n: int) -> int: + product = 1 + total = 0 + while n: + digit = n % 10 + product *= digit + total += digit + n //= 10 + return product - total +``` + diff --git a/docs/solutions/1200-1299/tree-diameter.md b/docs/solutions/1200-1299/tree-diameter.md new file mode 100644 index 00000000..bdf8ded4 --- /dev/null +++ b/docs/solutions/1200-1299/tree-diameter.md @@ -0,0 +1,98 @@ +# [1245. 树的直径](https://leetcode.cn/problems/tree-diameter/) + +- 标签:树、深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [1245. 树的直径 - 力扣](https://leetcode.cn/problems/tree-diameter/) + +## 题目大意 + +**描述**:给定一个数组 $edges$,用来表示一棵无向树。其中 $edges[i] = [u, v]$ 表示节点 $u$ 和节点 $v$ 之间的双向边。书上的节点编号为 $0 \sim edges.length$,共 $edges.length + 1$ 个节点。 + +**要求**:求出这棵无向树的直径。 + +**说明**: + +- $0 \le edges.length < 10^4$。 +- $edges[i][0] \ne edges[i][1]$。 +- $0 \le edges[i][j] \le edges.length$。 +- $edges$ 会形成一棵无向树。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/10/31/1397_example_1.png) + +```python +输入:edges = [[0,1],[0,2]] +输出:2 +解释: +这棵树上最长的路径是 1 - 0 - 2,边数为 2。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/10/31/1397_example_2.png) + +```python +输入:edges = [[0,1],[1,2],[2,3],[1,4],[4,5]] +输出:4 +解释: +这棵树上最长的路径是 3 - 2 - 1 - 4 - 5,边数为 4。 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +对于根节点为 $u$ 的树来说: + +1. 如果其最长路径经过根节点 $u$,则:**最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 +2. 如果其最长路径不经过根节点 $u$,则:**最长路径长度 = 某个子树中的最长路径长度**。 + +即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 + +对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{\hspace{0.5em}}len$。 + +1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{\hspace{0.5em}}len$。 +2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{\hspace{0.5em}}len + v\underline{\hspace{0.5em}}len + 1)$。 +3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{\hspace{0.5em}}len = max(u\underline{\hspace{0.5em}}len, \quad v\underline{\hspace{0.5em}}len + 1)$。 + +> 注意:在遍历邻接节点的过程中,为了避免造成重复遍历,我们在使用深度优先搜索时,应过滤掉父节点。 + +### 思路 1:代码 + +```python +class Solution: + def __init__(self): + self.ans = 0 + + def dfs(self, graph, u, fa): + u_len = 0 + for v in graph[u]: + if v != fa: + v_len = self.dfs(graph, v, u) + self.ans = max(self.ans, u_len + v_len + 1) + u_len = max(u_len, v_len + 1) + return u_len + + def treeDiameter(self, edges: List[List[int]]) -> int: + size = len(edges) + 1 + + graph = [[] for _ in range(size)] + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + self.dfs(graph, 0, -1) + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为无向树中的节点个数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1300-1399/all-elements-in-two-binary-search-trees.md b/docs/solutions/1300-1399/all-elements-in-two-binary-search-trees.md new file mode 100644 index 00000000..ea60cc0a --- /dev/null +++ b/docs/solutions/1300-1399/all-elements-in-two-binary-search-trees.md @@ -0,0 +1,106 @@ +# [1305. 两棵二叉搜索树中的所有元素](https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树、排序 +- 难度:中等 + +## 题目链接 + +- [1305. 两棵二叉搜索树中的所有元素 - 力扣](https://leetcode.cn/problems/all-elements-in-two-binary-search-trees/) + +## 题目大意 + +**描述**:给定两棵二叉搜索树的根节点 $root1$ 和 $root2$。 + +**要求**:返回一个列表,其中包含两棵树中所有整数并按升序排序。 + +**说明**: + +- 每棵树的节点数在 $[0, 5000]$ 范围内。 +- $-10^5 \le Node.val \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/12/29/q2-e1.png) + +```python +输入:root1 = [2,1,4], root2 = [1,0,3] +输出:[0,1,1,2,3,4] +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/12/29/q2-e5-.png) + +```python +输入:root1 = [1,null,8], root2 = [8,1] +输出:[1,1,8,8] +``` + +## 解题思路 + +### 思路 1:二叉树的中序遍历 + 快慢指针 + +根据二叉搜索树的特性,如果我们以中序遍历的方式遍历整个二叉搜索树时,就会得到一个有序递增列表。我们按照这样的方式分别对两个二叉搜索树进行中序遍历,就得到了两个有序数组,那么问题就变成了:两个有序数组的合并问题。 + +两个有序数组的合并可以参考归并排序中的归并过程,使用快慢指针将两个有序数组合并为一个有序数组。 + +具体步骤如下: + +1. 分别使用中序遍历的方式遍历两个二叉搜索树,得到两个有序数组 $nums1$、$nums2$。 +2. 使用两个指针 $index1$、$index2$ 分别指向两个有序数组的开始位置。 +3. 比较两个指针指向的元素,将两个有序数组中较小元素依次存入结果数组 $nums$ 中,并将指针移动到下一个位置。 +4. 重复步骤 $3$,直到某一指针到达数组末尾。 +5. 将另一个数组中的剩余元素依次存入结果数组 $nums$ 中。 +6. 返回结果数组 $nums$。 + +### 思路 1:代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + def inorder(root): + if not root: + return + inorder(root.left) + res.append(root.val) + inorder(root.right) + + inorder(root) + return res + def getAllElements(self, root1: TreeNode, root2: TreeNode) -> List[int]: + nums1 = self.inorderTraversal(root1) + nums2 = self.inorderTraversal(root2) + nums = [] + index1, index2 = 0, 0 + while index1 < len(nums1) and index2 < len(nums2): + if nums1[index1] < nums2[index2]: + nums.append(nums1[index1]) + index1 += 1 + else: + nums.append(nums2[index2]) + index2 += 1 + + while index1 < len(nums1): + nums.append(nums1[index1]) + index1 += 1 + + while index2 < len(nums2): + nums.append(nums2[index2]) + index2 += 1 + + return nums +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m)$,其中 $n$ 和 $m$ 分别为两棵二叉搜索树的节点个数。 +- **空间复杂度**:$O(n + m)$。 diff --git a/docs/solutions/1300-1399/angle-between-hands-of-a-clock.md b/docs/solutions/1300-1399/angle-between-hands-of-a-clock.md new file mode 100644 index 00000000..a7563efb --- /dev/null +++ b/docs/solutions/1300-1399/angle-between-hands-of-a-clock.md @@ -0,0 +1,66 @@ +# [1344. 时钟指针的夹角](https://leetcode.cn/problems/angle-between-hands-of-a-clock/) + +- 标签:数学 +- 难度:中等 + +## 题目链接 + +- [1344. 时钟指针的夹角 - 力扣](https://leetcode.cn/problems/angle-between-hands-of-a-clock/) + +## 题目大意 + +**描述**:给定两个数 $hour$ 和 $minutes$。 + +**要求**:请你返回在时钟上,由给定时间的时针和分针组成的较小角的角度($60$ 单位制)。 + +**说明**: + +- $1 \le hour \le 12$。 +- $0 \le minutes \le 59$。 +- 与标准答案误差在 $10^{-5}$ 以内的结果都被视为正确结果。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/08/sample_1_1673.png) + +```python +输入:hour = 12, minutes = 30 +输出:165 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/08/sample_2_1673.png) + +```python +输入:hour = 3, minutes = 30 +输出;75 +``` + +## 解题思路 + +### 思路 1:数学 + +1. 我们以 $00:00$ 为基准,分别计算出分针与 $00:00$ 中垂线的夹角,以及时针与 $00:00$ 中垂线的夹角。 +2. 然后计算出两者差值的绝对值 $diff$。当前差值可能为较小的角(小于 $180°$ 的角),也可能为较大的角(大于等于 $180°$ 的角)。 +3. 将差值的绝对值 $diff$ 与 $360 - diff$ 进行比较,取较小值作为答案。 + +### 思路 1:代码 + +```Python +class Solution: + def angleClock(self, hour: int, minutes: int) -> float: + mins_angle = 6 * minutes + hours_angle = (hour % 12 + minutes / 60) * 30 + + diff = abs(hours_angle - mins_angle) + return min(diff, 360 - diff) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1300-1399/closest-divisors.md b/docs/solutions/1300-1399/closest-divisors.md new file mode 100644 index 00000000..9ccf4b17 --- /dev/null +++ b/docs/solutions/1300-1399/closest-divisors.md @@ -0,0 +1,76 @@ +# [1362. 最接近的因数](https://leetcode.cn/problems/closest-divisors/) + +- 标签:数学 +- 难度:中等 + +## 题目链接 + +- [1362. 最接近的因数 - 力扣](https://leetcode.cn/problems/closest-divisors/) + +## 题目大意 + +**描述**:给定一个整数 $num$。 + +**要求**:找出同时满足下面全部要求的两个整数: + +- 两数乘积等于 $num + 1$ 或 $num + 2$。 +- 以绝对差进行度量,两数大小最接近。 + +你可以按照任意顺序返回这两个整数。 + +**说明**: + +- $1 \le num \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:num = 8 +输出:[3,3] +解释:对于 num + 1 = 9,最接近的两个因数是 3 & 3;对于 num + 2 = 10, 最接近的两个因数是 2 & 5,因此返回 3 & 3。 +``` + +- 示例 2: + +```python +输入:num = 123 +输出:[5,25] +``` + +## 解题思路 + +### 思路 1:数学 + +对于整数的任意一个范围在 $[\sqrt{n}, n]$ 的因数而言,一定存在一个范围在 $[1, \sqrt{n}]$ 的因数与其对应。因此,我们在遍历整数因数时,我们只需遍历 $[1, \sqrt{n}]$ 范围内的因数即可。 + +则这道题的具体解题步骤如下: + +1. 对于整数 $num + 1$、从 $\sqrt{num + 1}$ 的位置开始,到 $1$ 为止,以递减的顺序在 $[1, \sqrt{num + 1}]$ 范围内找到最接近的小因数 $a1$,并根据 $num // a1$ 获得另一个因数 $a2$。 +2. 用同样的方式,对于整数 $num + 2$、从 $\sqrt{num + 2}$ 的位置开始,到 $1$ 为止,以递减的顺序在 $[1, \sqrt{num + 2}]$ 范围内找到最接近的小因数 $b1$,并根据 $num // b1$ 获得另一个因数 $b2$。 +3. 判断 $abs(a1 - a2)$ 与 $abs(b1 - b2)$ 的大小,返回差值绝对值较小的一对因子数作为答案。 + +### 思路 1:代码 + +```Python +class Solution: + def disassemble(self, num): + for i in range(int(sqrt(num) + 1), 1, -1): + if num % i == 0: + return (i, num // i) + return (1, num) + + def closestDivisors(self, num: int) -> List[int]: + a1, a2 = self.disassemble(num + 1) + b1, b2 = self.disassemble(num + 2) + if abs(a1 - a2) <= abs(b1 - b2): + return [a1, a2] + return [b1, b2] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$(\sqrt{n})$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers.md b/docs/solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers.md new file mode 100644 index 00000000..593e8550 --- /dev/null +++ b/docs/solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers.md @@ -0,0 +1,65 @@ +# [1317. 将整数转换为两个无零整数的和](https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [1317. 将整数转换为两个无零整数的和 - 力扣](https://leetcode.cn/problems/convert-integer-to-the-sum-of-two-no-zero-integers/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回一个由两个整数组成的列表 $[A, B]$,满足: + +- $A$ 和 $B$ 都是无零整数。 +- $A + B = n$。 + +**说明**: + +- **无零整数**:十进制表示中不含任何 $0$ 的正整数。 +- 题目数据保证至少一个有效的解决方案。 +- 如果存在多个有效解决方案,可以返回其中任意一个。 +- $2 \le n \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:[1,1] +解释:A = 1, B = 1. A + B = n 并且 A 和 B 的十进制表示形式都不包含任何 0。 +``` + +- 示例 2: + +```python +输入:n = 11 +输出:[2,9] +``` + +## 解题思路 + +### 思路 1:枚举 + +1. 由于给定的 $n$ 范围为 $[1, 10000]$,比较小,我们可以直接在 $[1, n)$ 的范围内枚举 $A$,并通过 $n - A$ 得到 $B$。 +2. 在判断 $A$ 和 $B$ 中是否都不包含 $0$。如果都不包含 $0$,则返回 $[A, B]$。 + +### 思路 1:代码 + +```python +class Solution: + def getNoZeroIntegers(self, n: int) -> List[int]: + for A in range(1, n): + B = n - A + if '0' not in str(A) and '0' not in str(B): + return [A, B] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1300-1399/decompress-run-length-encoded-list.md b/docs/solutions/1300-1399/decompress-run-length-encoded-list.md new file mode 100644 index 00000000..06e73ebf --- /dev/null +++ b/docs/solutions/1300-1399/decompress-run-length-encoded-list.md @@ -0,0 +1,69 @@ +# [1313. 解压缩编码列表](https://leetcode.cn/problems/decompress-run-length-encoded-list/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [1313. 解压缩编码列表 - 力扣](https://leetcode.cn/problems/decompress-run-length-encoded-list/) + +## 题目大意 + +**描述**:给定一个以行程长度编码压缩的整数列表 $nums$。 + +考虑每对相邻的两个元素 $[freq, val] = [nums[2 \times i], nums[2 \times i + 1]]$ (其中 $i \ge 0$ ),每一对都表示解压后子列表中有 $freq$ 个值为 $val$ 的元素,你需要从左到右连接所有子列表以生成解压后的列表。 + +**要求**:返回解压后的列表。 + +**说明**: + +- $2 \le nums.length \le 100$。 +- $nums.length \mod 2 == 0$。 +- $1 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4] +输出:[2,4,4,4] +解释:第一对 [1,2] 代表着 2 的出现频次为 1,所以生成数组 [2]。 +第二对 [3,4] 代表着 4 的出现频次为 3,所以生成数组 [4,4,4]。 +最后将它们串联到一起 [2] + [4,4,4] = [2,4,4,4]。 +``` + +- 示例 2: + +```python +输入:nums = [1,1,2,3] +输出:[1,3,3] +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 以步长为 $2$,遍历数组 $nums$。 +2. 对于遍历到的元素 $nums[i]$、$nnums[i + 1]$,将 $nums[i]$ 个 $nums[i + 1]$ 存入答案数组中。 +3. 返回答案数组。 + +### 思路 1:代码 + +```Python +class Solution: + def decompressRLElist(self, nums: List[int]) -> List[int]: + res = [] + for i in range(0, len(nums), 2): + cnts = nums[i] + for cnt in range(cnts): + res.append(nums[i + 1]) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + s)$,其中 $n$ 为数组 $nums$ 的长度,$s$ 是数组 $nums$ 中所有偶数下标对应元素之和。 +- **空间复杂度**:$O(s)$。 + diff --git a/docs/solutions/1300-1399/design-a-stack-with-increment-operation.md b/docs/solutions/1300-1399/design-a-stack-with-increment-operation.md new file mode 100644 index 00000000..07bf7afd --- /dev/null +++ b/docs/solutions/1300-1399/design-a-stack-with-increment-operation.md @@ -0,0 +1,122 @@ +# [1381. 设计一个支持增量操作的栈](https://leetcode.cn/problems/design-a-stack-with-increment-operation/) + +- 标签:栈、设计、数组 +- 难度:中等 + +## 题目链接 + +- [1381. 设计一个支持增量操作的栈 - 力扣](https://leetcode.cn/problems/design-a-stack-with-increment-operation/) + +## 题目大意 + +**要求**:设计一个支持对其元素进行增量操作的栈。 + +实现自定义栈类 $CustomStack$: + +- `CustomStack(int maxSize)`:用 $maxSize$ 初始化对象,$maxSize$ 是栈中最多能容纳的元素数量。 +- `void push(int x)`:如果栈还未增长到 $maxSize$,就将 $x$ 添加到栈顶。 +- `int pop()`:弹出栈顶元素,并返回栈顶的值,或栈为空时返回 $-1$。 +- `void inc(int k, int val)`:栈底的 $k$ 个元素的值都增加 $val$。如果栈中元素总数小于 $k$,则栈中的所有元素都增加 $val$。 + +**说明**: + +- $1 \le maxSize, x, k \le 1000$。 +- $0 \le val \le 100$。 +- 每种方法 `increment`,`push` 以及 `pop` 分别最多调用 $1000$ 次。 + +**示例**: + +- 示例 1: + +```python +输入: +["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"] +[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]] +输出: +[null,null,null,2,null,null,null,null,null,103,202,201,-1] +解释: +CustomStack stk = new CustomStack(3); // 栈是空的 [] +stk.push(1); // 栈变为 [1] +stk.push(2); // 栈变为 [1, 2] +stk.pop(); // 返回 2 --> 返回栈顶值 2,栈变为 [1] +stk.push(2); // 栈变为 [1, 2] +stk.push(3); // 栈变为 [1, 2, 3] +stk.push(4); // 栈仍然是 [1, 2, 3],不能添加其他元素使栈大小变为 4 +stk.increment(5, 100); // 栈变为 [101, 102, 103] +stk.increment(2, 100); // 栈变为 [201, 202, 103] +stk.pop(); // 返回 103 --> 返回栈顶值 103,栈变为 [201, 202] +stk.pop(); // 返回 202 --> 返回栈顶值 202,栈变为 [201] +stk.pop(); // 返回 201 --> 返回栈顶值 201,栈变为 [] +stk.pop(); // 返回 -1 --> 栈为空,返回 -1 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 初始化: + 1. 使用空数组 $stack$ 用于表示栈。 + 2. 使用 $size$ 用于表示当前栈中元素个数, + 3. 使用 $maxSize$ 用于表示栈中允许的最大元素个数。 + 4. 使用另一个空数组 $increments$ 用于增量操作。 +2. `push(x)` 操作: + 1. 判断当前元素个数与栈中允许的最大元素个数关系。 + 2. 如果当前元素个数小于栈中允许的最大元素个数,则: + 1. 将 $x$ 添加到数组 $stack$ 中,即:`self.stack.append(x)`。 + 2. 当前元素个数加 $1$,即:`self.size += 1`。 + 3. 将 $0$ 添加到增量数组 $increments$ 中,即:`self.increments.append(0)`。 +3. `increment(k, val)` 操作: + 1. 如果增量数组不为空,则取 $k$ 与元素个数 `self.size` 的较小值,令增量数组对应位置加上 `val`(等 `pop()` 操作时,再计算出准确值)。 +4. `pop()` 操作: + 1. 如果当前元素个数为 $0$,则直接返回 $-1$。 + 2. 如果当前元素个数大于等于 $2$,则更新弹出元素后的增量数组(保证剩余元素弹出时能够正确计算出),即:`self.increments[-2] += self.increments[-1]` + 3. 令元素个数减 $1$,即:`self.size -= 1`。 + 4. 弹出数组 $stack$ 中的栈顶元素和增量数组 $increments$ 中的栈顶元素,令其相加,即为弹出元素值,将其返回。 + +### 思路 1:代码 + +```python +class CustomStack: + + def __init__(self, maxSize: int): + self.maxSize = maxSize + self.stack = [] + self.increments = [] + self.size = 0 + + + def push(self, x: int) -> None: + if self.size < self.maxSize: + self.stack.append(x) + self.increments.append(0) + self.size += 1 + + + def pop(self) -> int: + if self.size == 0: + return -1 + if self.size >= 2: + self.increments[-2] += self.increments[-1] + self.size -= 1 + + val = self.stack.pop() + self.increments.pop() + return val + + + def increment(self, k: int, val: int) -> None: + if self.increments: + self.increments[min(k, self.size) - 1] += val + + + +# Your CustomStack object will be instantiated and called as such: +# obj = CustomStack(maxSize) +# obj.push(x) +# param_2 = obj.pop() +# obj.increment(k,val) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:初始化、`push` 操作、`pop` 操作、`increment` 操作的时间复杂度为 $O(1)$。 +- **空间复杂度**:$O(maxSize)$。 diff --git a/docs/solutions/1300-1399/index.md b/docs/solutions/1300-1399/index.md new file mode 100644 index 00000000..041f59ce --- /dev/null +++ b/docs/solutions/1300-1399/index.md @@ -0,0 +1,17 @@ +## 本章内容 + +- [1300. 转变数组后最接近目标值的数组和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md) +- [1305. 两棵二叉搜索树中的所有元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/all-elements-in-two-binary-search-trees.md) +- [1310. 子数组异或查询](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/xor-queries-of-a-subarray.md) +- [1313. 解压缩编码列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/decompress-run-length-encoded-list.md) +- [1317. 将整数转换为两个无零整数的和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/convert-integer-to-the-sum-of-two-no-zero-integers.md) +- [1319. 连通网络的操作次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md) +- [1324. 竖直打印单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/print-words-vertically.md) +- [1338. 数组大小减半](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/reduce-array-size-to-the-half.md) +- [1343. 大小为 K 且平均值大于等于阈值的子数组数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md) +- [1344. 时钟指针的夹角](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/angle-between-hands-of-a-clock.md) +- [1347. 制造字母异位词的最小步骤数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram.md) +- [1349. 参加考试的最大学生数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/maximum-students-taking-exam.md) +- [1358. 包含所有三种字符的子字符串数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md) +- [1362. 最接近的因数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/closest-divisors.md) +- [1381. 设计一个支持增量操作的栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/design-a-stack-with-increment-operation.md) diff --git a/docs/solutions/1300-1399/maximum-students-taking-exam.md b/docs/solutions/1300-1399/maximum-students-taking-exam.md new file mode 100644 index 00000000..c1ee6079 --- /dev/null +++ b/docs/solutions/1300-1399/maximum-students-taking-exam.md @@ -0,0 +1,130 @@ +# [1349. 参加考试的最大学生数](https://leetcode.cn/problems/maximum-students-taking-exam/) + +- 标签:位运算、数组、动态规划、状态压缩、矩阵 +- 难度:困难 + +## 题目链接 + +- [1349. 参加考试的最大学生数 - 力扣](https://leetcode.cn/problems/maximum-students-taking-exam/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的矩阵 $seats$ 表示教室中的座位分布,其中如果座位是坏的(不可用),就用 `'#'` 表示,如果座位是好的,就用 `'.'` 表示。 + +学生可以看到左侧、右侧、左上方、右上方这四个方向上紧邻他的学生答卷,但是看不到直接坐在他前面或者后面的学生答卷。 + +**要求**:计算并返回该考场可以容纳的一期参加考试且无法作弊的最大学生人数。 + +**说明**: + +- 学生必须坐在状况良好的座位上。 +- $seats$ 只包含字符 `'.'` 和 `'#'`。 +- $m == seats.length$。 +- $n == seats[i].length$。 +- $1 \le m \le 8$。 +- $1 \le n \le 8$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/09/image.png) + +```python +输入:seats = [["#",".","#","#",".","#"], + [".","#","#","#","#","."], + ["#",".","#","#",".","#"]] +输出:4 +解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。 +``` + +- 示例 2: + +```python +输入:seats = [[".","#"], + ["#","#"], + ["#","."], + ["#","#"], + [".","#"]] +输出:3 +解释:让所有学生坐在可用的座位上。 +``` + +## 解题思路 + +### 思路 1:状态压缩 DP + +题目中给定的 $m$、$n$ 范围为 $1 \le m, n \le 8$,每一排最多有 $8$ 个座位,那么我们可以使用一个 $8$ 位长度的二进制数来表示当前排座位的选择情况(也就是「状态压缩」的方式)。 + +同时从题目中可以看出,当前排的座位与当前行左侧、右侧座位有关,并且也与上一排中左上方、右上方的座位有关,则我们可以使用一个二维数组来表示状态。其中第一维度为排数,第二维度为当前排的座位选择情况。 + +具体做法如下: + +###### 1. 划分阶段 + +按照排数、当前排的座位选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][state]$ 表示为:前 $i$ 排,并且最后一排座位选择状态为 $state$ 时,可以参加考试的最大学生数。 + +###### 3. 状态转移方程 + +因为学生可以看到左侧、右侧、左上方、右上方这四个方向上紧邻他的学生答卷,所以对于当前排的某个座位来说,其左侧、右侧、左上方、右上方都不应有人坐。我们可以根据当前排的座位选取状态 $cur\underline{\hspace{0.5em}}state$,并通过枚举的方式,找出符合要求的上一排座位选取状态 $pre\underline{\hspace{0.5em}}state$,并计算出当前排座位选择个数,即 $f(cur\underline{\hspace{0.5em}}state)$,则状态转移方程为: + + $dp[i][state] = \max \lbrace dp[i - 1][pre\underline{\hspace{0.5em}}state] \rbrace + f(state)$ + +因为所给座位中还有坏座位(不可用)的情况,我们可以使用一个 $8$ 位的二进制数 $bad\underline{\hspace{0.5em}}seat$ 来表示当前排的坏座位情况,如果 $cur\underline{\hspace{0.5em}}state \text{ \& } bad\underline{\hspace{0.5em}}seat == 1$,则说明当前状态下,选择了坏椅子,则可直接跳过这种状态。 + +我们还可以通过 $cur\underline{\hspace{0.5em}}state \text{ \& } (cur\underline{\hspace{0.5em}}state \text{ <}\text{< } 1)$ 和 $cur\underline{\hspace{0.5em}}state \& (cur\underline{\hspace{0.5em}}state \text{ >}\text{> } 1)$ 来判断当前排选择状态下,左右相邻座位上是否有人,如果有人,则可直接跳过这种状态。 + +同理,我们还可以通过 $cur\underline{\hspace{0.5em}}state \text{ \& } (pre\underline{\hspace{0.5em}}state \text{ <}\text{< } 1)$ 和 $cur\underline{\hspace{0.5em}}state \text{ \& } (pre\underline{\hspace{0.5em}}state \text{ >}\text{> } 1)$ 来判断当前排选择状态下,上一行左上、右上相邻座位上是否有人,如果有人,则可直接跳过这种状态。 + +###### 4. 初始条件 + +- 默认情况下,前 $0$ 排所有选择状态下,可以参加考试的最大学生数为 $0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][state]$ 表示为:前 $i$ 排,并且最后一排座位选择状态为 $state$ 时,可以参加考试的最大学生数。 所以最终结果为最后一排 $dp[rows]$ 中的最大值。 + +### 思路 1:代码 + +```python +class Solution: + def maxStudents(self, seats: List[List[str]]) -> int: + rows, cols = len(seats), len(seats[0]) + states = 1 << cols + dp = [[0 for _ in range(states)] for _ in range(rows + 1)] + + for i in range(1, rows + 1): # 模拟 1 ~ rows 排分配座位 + bad_seat = 0 # 当前排的坏座位情况 + for j in range(cols): + if seats[i - 1][j] == '#': # 记录坏座位情况 + bad_seat |= 1 << j + + for cur_state in range(states): # 枚举当前排的座位选取状态 + if cur_state & bad_seat: # 当前排的座位选择了换座位,跳过 + continue + if cur_state & (cur_state << 1): # 当前排左侧座位有人,跳过 + continue + if cur_state & (cur_state >> 1): # 当前排右侧座位有人,跳过 + continue + + count = bin(cur_state).count('1') # 计算当前排最多可以坐多少人 + for pre_state in range(states): # 枚举前一排情况 + if cur_state & (pre_state << 1): # 左上座位有人,跳过 + continue + if cur_state & (pre_state >> 1): # 右上座位有人,跳过 + continue + # dp[i][cur_state] 取自上一排分配情况为 pre_state 的最大值 + 当前排最多可以坐的人数 + dp[i][cur_state] = max(dp[i][cur_state], dp[i - 1][pre_state] + count) + + return max(dp[rows]) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times 2^{2n})$,其中 $m$、$n$ 分别为所给矩阵的行数、列数。 +- **空间复杂度**:$O(m \times 2^n)$。 + diff --git a/docs/solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram.md b/docs/solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram.md new file mode 100644 index 00000000..b9e51f71 --- /dev/null +++ b/docs/solutions/1300-1399/minimum-number-of-steps-to-make-two-strings-anagram.md @@ -0,0 +1,74 @@ +# [1347. 制造字母异位词的最小步骤数](https://leetcode.cn/problems/minimum-number-of-steps-to-make-two-strings-anagram/) + +- 标签:哈希表、字符串、计数 +- 难度:中等 + +## 题目链接 + +- [1347. 制造字母异位词的最小步骤数 - 力扣](https://leetcode.cn/problems/minimum-number-of-steps-to-make-two-strings-anagram/) + +## 题目大意 + +**描述**:给定两个长度相等的字符串 $s$ 和 $t$。每一个步骤中,你可以选择将 $t$ 中任一个字符替换为另一个字符。 + +**要求**:返回使 $t$ 成为 $s$ 的字母异位词的最小步骤数。 + +**说明**: + +- **字母异位词**:指字母相同,但排列不同(也可能相同)的字符串。 +- $1 \le s.length \le 50000$。 +- $s.length == t.length$。 +- $s$ 和 $t$ 只包含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输出:s = "bab", t = "aba" +输出:1 +提示:用 'b' 替换 t 中的第一个 'a',t = "bba" 是 s 的一个字母异位词。 +``` + +- 示例 2: + +```python +输出:s = "leetcode", t = "practice" +输出:5 +提示:用合适的字符替换 t 中的 'p', 'r', 'a', 'i' 和 'c',使 t 变成 s 的字母异位词。 +``` + +## 解题思路 + +### 思路 1:哈希表 + +题目要求使 $t$ 成为 $s$ 的字母异位词,则只需要 $t$ 和 $s$ 对应的每种字符数量相一致即可,无需考虑字符位置。 + +因为每一次转换都会减少一个字符,并增加另一个字符。 + +1. 我们使用两个哈希表 $cnts\underline{\hspace{0.5em}}s$、$cnts\underline{\hspace{0.5em}}t$ 分别对 $t$ 和 $s$ 中的字符进行计数,并求出两者的交集。 +2. 遍历交集中的字符种类,以及对应的字符数量。 +3. 对于当前字符 $key$,如果当前字符串 $s$ 中的字符 $key$ 的数量小于字符串 $t$ 中字符 $key$ 的数量,即 $cnts\underline{\hspace{0.5em}}s[key] < cnts\underline{\hspace{0.5em}}t[key]$。则 $s$ 中需要补齐的字符数量就是需要的最小步数,将其累加到答案中。 +4. 遍历完返回答案。 + +### 思路 1:代码 + +```Python +class Solution: + def minSteps(self, s: str, t: str) -> int: + cnts_s, cnts_t = Counter(s), Counter(t) + cnts = cnts_s | cnts_t + + ans = 0 + for key, cnt in cnts.items(): + if cnts_s[key] < cnts_t[key]: + ans += cnts_t[key] - cnts_s[key] + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$,其中 $m$、$n$ 分别为字符串 $s$、$t$ 的长度。 +- **空间复杂度**:$O(|\sum|)$,其中 $\sum$ 是字符集,本题中 $| \sum | = 26$。 + diff --git a/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md b/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md new file mode 100644 index 00000000..71e46f39 --- /dev/null +++ b/docs/solutions/1300-1399/number-of-operations-to-make-network-connected.md @@ -0,0 +1,109 @@ +# [1319. 连通网络的操作次数](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [1319. 连通网络的操作次数 - 力扣](https://leetcode.cn/problems/number-of-operations-to-make-network-connected/) + +## 题目大意 + +**描述**:$n$ 台计算机通过网线连接成一个网络,计算机的编号从 $0$ 到 $n - 1$。线缆用 $comnnections$ 表示,其中 $connections[i] = [a, b]$ 表示连接了计算机 $a$ 和 $b$。 + +给定这个计算机网络的初始布线 $connections$,可以拔除任意两台直接相连的计算机之间的网线,并用这根网线连接任意一对未直接连接的计算机。 + +**要求**:计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 $-1$。 + +**说明**: + +- $1 \le n \le 10^5$。 +- $1 \le connections.length \le min( \frac{n \times (n-1)}{2}, 10^5)$。 +- $connections[i].length == 2$。 +- $0 \le connections[i][0], connections[i][1] < n$。 +- $connections[i][0] != connections[i][1]$。 +- 没有重复的连接。 +- 两台计算机不会通过多条线缆连接。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/11/sample_1_1677.png) + +```python +输入:n = 4, connections = [[0,1],[0,2],[1,2]] +输出:1 +解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/11/sample_2_1677.png) + +```python +输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]] +输出:2 +``` + +## 解题思路 + +### 思路 1:并查集 + +$n$ 台计算机至少需要 $n - 1$ 根线才能进行连接,如果网线的数量少于 $n - 1$,那么就不可能将其连接。接下来计算最少操作次数。 + +把 $n$ 台计算机看做是 $n$ 个节点,每条网线看做是一条无向边。维护两个变量:多余电线数 $removeCount$、需要电线数 $needConnectCount$。初始 $removeCount = 1, needConnectCount = n - 1$。 + +遍历网线数组,将相连的节点 $a$ 和 $b$ 利用并查集加入到一个集合中(调用 `union` 操作)。 + +- 如果 $a$ 和 $b$ 已经在同一个集合中,说明该连接线多余,多余电线数加 $1$。 +- 如果 $a$ 和 $b$ 不在一个集合中,则将其合并,则 $a$ 和 $b$ 之间不再需要用额外的电线连接了,所以需要电线数减 $1$。 + +最后,判断多余的电线数是否满足需要电线数,不满足返回 $-1$,如果满足,则返回需要电线数。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return False + self.parent[root_x] = root_y + return True + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def makeConnected(self, n: int, connections: List[List[int]]) -> int: + union_find = UnionFind(n) + removeCount = 0 + needConnectCount = n - 1 + for connection in connections: + if union_find.union(connection[0], connection[1]): + needConnectCount -= 1 + else: + removeCount += 1 + + if removeCount < needConnectCount: + return -1 + return needConnectCount +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times \alpha(n))$,其中 $m$ 是数组 $connections$ 的长度,$\alpha$ 是反 `Ackerman` 函数。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md b/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md new file mode 100644 index 00000000..a9693581 --- /dev/null +++ b/docs/solutions/1300-1399/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold.md @@ -0,0 +1,84 @@ +# [1343. 大小为 K 且平均值大于等于阈值的子数组数目](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) + +- 标签:数组、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1343. 大小为 K 且平均值大于等于阈值的子数组数目 - 力扣](https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/) + +## 题目大意 + +**描述**:给定一个整数数组 $arr$ 和两个整数 $k$ 和 $threshold$。 + +**要求**:返回长度为 $k$ 且平均值大于等于 $threshold$ 的子数组数目。 + +**说明**: + +- $1 \le arr.length \le 10^5$。 +- $1 \le arr[i] \le 10^4$。 +- $1 \le k \le arr.length$。 +- $0 \le threshold \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 +输出:3 +解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。 +``` + +- 示例 2: + +```python +输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 +输出:6 +解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 +``` + +## 解题思路 + +### 思路 1:滑动窗口(固定长度) + +这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 `k`。具体做法如下: + +1. `ans` 用来维护答案数目。`window_sum` 用来维护窗口中元素的和。 +2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 +4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 `threshold`。 + 1. 如果满足,则答案数目 + 1。 + 2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 +5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 +6. 最后输出答案数目。 + +### 思路 1:代码 + +```python +class Solution: + def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int: + left = 0 + right = 0 + window_sum = 0 + ans = 0 + + while right < len(arr): + window_sum += arr[right] + + if right - left + 1 >= k: + if window_sum >= k * threshold: + ans += 1 + window_sum -= arr[left] + left += 1 + + right += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md b/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md new file mode 100644 index 00000000..30aad01f --- /dev/null +++ b/docs/solutions/1300-1399/number-of-substrings-containing-all-three-characters.md @@ -0,0 +1,54 @@ +# [1358. 包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1358. 包含所有三种字符的子字符串数目 - 力扣](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) + +## 题目大意 + +给你一个字符串 `s` ,`s` 只包含三种字符 `a`, `b` 和 `c`。 + +请你返回 `a`,`b` 和 `c` 都至少出现过一次的子字符串数目。 + +## 解题思路 + +只要找到首个 `a`、`b`、`c` 同时存在的子字符串,则在该子字符串后面追加字符构成的新字符串还是满足题意的。假设该子串末尾字母的位置为 `i`,则以此字符串构建的新字符串有 `len(s) - i`个。所以题目可以转换为找出 `a`、`b`、`c` 同时存在的最短子串,并记录所有满足题意的字符串数量。具体做法如下: + +用滑动窗口 `window` 来记录各个字符个数,`window` 为哈希表类型。用 `ans` 来维护 `a`,`b` 和 `c` 都至少出现过一次的子字符串数目。 + +设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中不超过 `k` 种字符。 + +- 一开始,`left`、`right` 都指向 `0`。 +- 将最右侧字符 `s[right]` 加入当前窗口 `window_counts` 中,记录该字符个数,向右移动 `right`。 +- 如果该窗口中字符的种数大于等于 `3` 种,即 `len(window) >= 3`,则累积答案个数为 `len(s) - right`,并不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `len(window) < 3`。 +- 然后继续右移 `right`,直到 `right >= len(nums)` 结束。 +- 输出答案 `ans`。 + +## 代码 + +```python +class Solution: + def numberOfSubstrings(self, s: str) -> int: + window = dict() + ans = 0 + left, right = 0, 0 + + while right < len(s): + if s[right] in window: + window[s[right]] += 1 + else: + window[s[right]] = 1 + + while len(window) >= 3: + ans += len(s) - right + window[s[left]] -= 1 + if window[s[left]] == 0: + del window[s[left]] + left += 1 + right += 1 + return ans +``` + diff --git a/docs/solutions/1300-1399/print-words-vertically.md b/docs/solutions/1300-1399/print-words-vertically.md new file mode 100644 index 00000000..6b5526f6 --- /dev/null +++ b/docs/solutions/1300-1399/print-words-vertically.md @@ -0,0 +1,87 @@ +# [1324. 竖直打印单词](https://leetcode.cn/problems/print-words-vertically/) + +- 标签:数组、字符串、模拟 +- 难度:中等 + +## 题目链接 + +- [1324. 竖直打印单词 - 力扣](https://leetcode.cn/problems/print-words-vertically/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:按照单词在 $s$ 中出现顺序将它们全部竖直返回。 + +**说明**: + +- 单词应该以字符串列表的形式返回,必要时用空格补位,但输出尾部的空格需要删除(不允许尾随空格)。 +- 每个单词只能放在一列上,每一列中也只能有一个单词。 +- $1 \le s.length \le 200$。 +- $s$ 仅含大写英文字母。 +- 题目数据保证两个单词之间只有一个空格。 + +**示例**: + +- 示例 1: + +```python +输入:s = "HOW ARE YOU" +输出:["HAY","ORO","WEU"] +解释:每个单词都应该竖直打印。 + "HAY" + "ORO" + "WEU" +``` + +- 示例 2: + +```python +输入:s = "TO BE OR NOT TO BE" +输出:["TBONTB","OEROOE"," T"] +解释:题目允许使用空格补位,但不允许输出末尾出现空格。 +"TBONTB" +"OEROOE" +" T" +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 将字符串 $s$ 按空格分割为单词数组 $words$。 +2. 计算出单词数组 $words$ 中单词的最大长度 $max\underline{\hspace{0.5em}}len$。 +3. 第一重循环遍历竖直单词的每个单词位置 $i$,第二重循环遍历当前第 $j$ 个单词。 + 1. 如果当前单词没有第 $i$ 个字符(当前单词的长度超过了单词位置 $i$),则将空格插入到竖直单词中。 + 2. 如果当前单词有第 $i$ 个字符,泽讲当前单词的第 $i$ 个字符插入到竖直单词中。 +4. 第二重循环遍历完,将竖直单词去除尾随空格,并加入到答案数组中。 +5. 第一重循环遍历完,则返回答案数组。 + +### 思路 1:代码 + +```Python +class Solution: + def printVertically(self, s: str) -> List[str]: + words = s.split(' ') + max_len = 0 + for word in words: + max_len = max(len(word), max_len) + + res = [] + for i in range(max_len): + ans = "" + for j in range(len(words)): + if i + 1 > len(words[j]): + ans += ' ' + else: + ans += words[j][i] + res.append(ans.rstrip()) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times max(|word|))$,其中 $n$ 为字符串 $s$ 中的单词个数,$max(|word|)$ 是最长的单词长度。。 +- **空间复杂度**:$O(n \times max(|word|))$。 + diff --git a/docs/solutions/1300-1399/reduce-array-size-to-the-half.md b/docs/solutions/1300-1399/reduce-array-size-to-the-half.md new file mode 100644 index 00000000..84221d8f --- /dev/null +++ b/docs/solutions/1300-1399/reduce-array-size-to-the-half.md @@ -0,0 +1,76 @@ +- [1338. 数组大小减半](https://leetcode.cn/problems/reduce-array-size-to-the-half/) + +- 标签:贪心、数组、哈希表、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [1338. 数组大小减半 - 力扣](https://leetcode.cn/problems/reduce-array-size-to-the-half/) + +## 题目大意 + +**描述**:给定过一个整数数组 $arr$。你可以从中选出一个整数集合,并在数组 $arr$ 删除所有整数集合对应的数。 + +**要求**:返回至少能删除数组中的一半整数的整数集合的最小大小。 + +**说明**: + +- $1 \le arr.length \le 10^5$。 +- $arr.length$ 为偶数。 +- $1 \le arr[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [3,3,3,3,5,5,5,2,2,7] +输出:2 +解释:选择 {3,7} 使得结果数组为 [5,5,5,2,2]、长度为 5(原数组长度的一半)。 +大小为 2 的可行集合有 {3,5},{3,2},{5,2}。 +选择 {2,7} 是不可行的,它的结果数组为 [3,3,3,3,5,5,5],新数组长度大于原数组的二分之一。 +``` + +- 示例 2: + +```python +输入:arr = [7,7,7,7,7,7] +输出:1 +解释:我们只能选择集合 {7},结果数组为空。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +对于选出的整数集合中每一个数 $x$ 来说,我们会删除数组 $arr$ 中所有值为 $x$ 的整数。 + +因为题目要求我们选出的整数集合最小,所以在每一次选择整数 $x$ 加入整数集合时,我们都应该选择数组 $arr$ 中出现次数最多的数。 + +因此,我们可以统计出数组 $arr$ 中每个整数的出现次数,用哈希表存储,并依照出现次数进行降序排序。 + +然后,依次选择出现次数最多的数进行删除,并统计个数,直到删除了至少一半的数时停止。 + +最后,将统计个数作为答案返回。 + +### 思路 1:代码 + +```Python +class Solution: + def minSetSize(self, arr: List[int]) -> int: + cnts = Counter(arr) + ans, cnt = 0, 0 + for num, freq in cnts.most_common(): + cnt += freq + ans += 1 + if cnt * 2 >= len(arr): + break + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $arr$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md b/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md new file mode 100644 index 00000000..097a42a2 --- /dev/null +++ b/docs/solutions/1300-1399/sum-of-mutated-array-closest-to-target.md @@ -0,0 +1,112 @@ +# [1300. 转变数组后最接近目标值的数组和](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) + +- 标签:数组、二分查找、排序 +- 难度:中等 + +## 题目链接 + +- [1300. 转变数组后最接近目标值的数组和 - 力扣](https://leetcode.cn/problems/sum-of-mutated-array-closest-to-target/) + +## 题目大意 + +**描述**:给定一个整数数组 $arr$ 和一个目标值 $target$。 + +**要求**:返回一个整数 $value$,使得将数组中所有大于 $value$ 的值变成 $value$ 后,数组的和最接近 $target$(最接近表示两者之差的绝对值最小)。如果有多种使得和最接近 $target$ 的方案,请你返回这些整数中的最小值。 + +**说明**: + +- 答案 $value$ 不一定是 $arr$ 中的数字。 +- $1 \le arr.length \le 10^4$。 +- $1 \le arr[i], target \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [4,9,3], target = 10 +输出:3 +解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。 +``` + +- 示例 2: + +```python +输入:arr = [60864,25176,27249,21296,20204], target = 56803 +输出:11361 +``` + +## 解题思路 + +### 思路 1:二分查找 + +题目可以理解为:在 $[0, max(arr)]$ 的区间中,查找一个值 $value$。使得「转变后的数组和」与 $target$ 最接近。 + +- 转变规则:将数组中大于 $value$ 的值变为 $value$。 + +在 $[0, max(arr)]$ 的区间中,查找一个值 $value$ 可以使用二分查找答案的方式减少时间复杂度。但是这个最接近 $target$ 应该怎么理解,或者说怎么衡量接近程度。 + +最接近 $target$ 的肯定是数组和等于 $target$ 的时候。不过更可能是出现数组和恰好比 $target$ 大一点,或数组和恰好比 $target$ 小一点。我们可以将 $target$ 上下两个值相对应的数组和与 $target$ 进行比较,输出差值更小的那一个 $value$。 + +在根据查找的值 $value$ 计算数组和时,也可以通过二分查找方法查找出数组刚好大于等于 $value$ 元素下标。还可以根据事先处理过的前缀和数组,快速得到转变后的数组和。 + +最后输出使得数组和与 $target$ 差值更小的 $value$。 + +整个算法步骤如下: + +- 先对数组排序,并计算数组的前缀和 $pre\underline{\hspace{0.5em}}sum$。 +- 通过二分查找在 $[0, arr[-1]]$ 中查找使得转变后数组和刚好大于等于 $target$ 的值 $value$。 +- 计算 $value$ 对应的数组和 $sum\underline{\hspace{0.5em}}1$,以及 $value - 1$ 对应的数组和 $sum\underline{\hspace{0.5em}}2$。并分别计算与 $target$ 的差值 $diff\underline{\hspace{0.5em}}1$、$diff\underline{\hspace{0.5em}}2$。 +- 输出差值小的那个值。 + +### 思路 1:代码 + +```python +class Solution: + # 计算 value 对应的转变后的数组 + def calc_sum(self, arr, value, pre_sum): + size = len(arr) + left, right = 0, size - 1 + while left < right: + mid = left + (right - left) // 2 + if arr[mid] < value: + left = mid + 1 + else: + right = mid + + return pre_sum[left] + (size - left) * value + + # 查找使得转变后的数组和刚好大于等于 target 的 value + def binarySearchValue(self, arr, target, pre_sum): + left, right = 0, arr[-1] + while left < right: + mid = left + (right - left) // 2 + if self.calc_sum(arr, mid, pre_sum) < target: + left = mid + 1 + else: + right = mid + return left + + def findBestValue(self, arr: List[int], target: int) -> int: + size = len(arr) + arr.sort() + pre_sum = [0 for _ in range(size + 1)] + + for i in range(size): + pre_sum[i + 1] = pre_sum[i] + arr[i] + + value = self.binarySearchValue(arr, target, pre_sum) + + sum_1 = self.calc_sum(arr, value, pre_sum) + sum_2 = self.calc_sum(arr, value - 1, pre_sum) + diff_1 = abs(sum_1 - target) + diff_2 = abs(sum_2 - target) + + return value if diff_1 < diff_2 else value - 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O((n + k) \times \log n)$。其中 $n$ 是数组 $arr$ 的长度,$k$ 是数组 $arr$ 中的最大值。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1300-1399/xor-queries-of-a-subarray.md b/docs/solutions/1300-1399/xor-queries-of-a-subarray.md new file mode 100644 index 00000000..65d58a15 --- /dev/null +++ b/docs/solutions/1300-1399/xor-queries-of-a-subarray.md @@ -0,0 +1,155 @@ +# [1310. 子数组异或查询](https://leetcode.cn/problems/xor-queries-of-a-subarray/) + +- 标签:位运算、数组、前缀和 +- 难度:中等 + +## 题目链接 + +- [1310. 子数组异或查询 - 力扣](https://leetcode.cn/problems/xor-queries-of-a-subarray/) + +## 题目大意 + +**描述**:给定一个正整数数组 `arr`,再给定一个对应的查询数组 `queries`,其中 `queries[i] = [Li, Ri]`。 + +**要求**:对于每个查询 `queries[i]`,要求计算从 `Li` 到 `Ri` 的异或值(即 `arr[Li] ^ arr[Li+1] ^ ... ^ arr[Ri]`)作为本次查询的结果。并返回一个包含给定查询 `queries` 所有结果的数组。 + +**说明**: + +- $1 \le arr.length \le 3 * 10^4$。 +- $1 \le arr[i] \le 10^9$。 +- $1 \le queries.length \le 3 * 10^4$。 +- $queries[i].length == 2$。 +- $0 \le queries[i][0] \le queries[i][1] < arr.length$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]] +输出:[2,7,14,8] +解释 + +数组中元素的二进制表示形式是: +1 = 0001 +3 = 0011 +4 = 0100 +8 = 1000 + +查询的 XOR 值为: +[0,1] = 1 xor 3 = 2 +[1,2] = 3 xor 4 = 7 +[0,3] = 1 xor 3 xor 4 xor 8 = 14 +[3,3] = 8 +``` + +## 解题思路 + +### 思路 1:线段树 + +- 使用数组 `res` 作为答案数组,用于存放每个查询的结果值。 +- 根据 `nums` 数组构建一棵线段树。 +- 然后遍历查询数组 `queries`。对于每个查询 `queries[i]`,在线段树中查询对应区间的异或值,将其结果存入答案数组 `res` 中。 +- 返回答案数组 `res` 即可。 + +这样构建线段树的时间复杂度为 $O(\log n)$,单次区间查询的时间复杂度为 $O(\log n)$。总体时间复杂度为 $O(k * \log n)$,其中 $k$ 是查询次数。 + +### 思路 1:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + +class Solution: + def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: + self.STree = SegmentTree(arr, lambda x, y: (x ^ y)) + res = [] + for query in queries: + ans = self.STree.query_interval(query[0], query[1]) + res.append(ans) + return res +``` diff --git a/docs/solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary.md b/docs/solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary.md new file mode 100644 index 00000000..ff7774c8 --- /dev/null +++ b/docs/solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary.md @@ -0,0 +1,55 @@ +# [1491. 去掉最低工资和最高工资后的工资平均值](https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/) + +- 标签:数组、排序 +- 难度:简单 + +## 题目链接 + +- [1491. 去掉最低工资和最高工资后的工资平均值 - 力扣](https://leetcode.cn/problems/average-salary-excluding-the-minimum-and-maximum-salary/) + +## 题目大意 + +**描述**:给定一个整数数组 `salary`,数组中的每一个数都是唯一的,其中 `salary[i]` 是第 `i` 个员工的工资。 + +**要求**:返回去掉最低工资和最高工资之后,剩下员工工资的平均值。 + +**说明**: + +- $3 \le salary.length \le 100$。 +- $10^3 \le salary[i] \le 10^6$。 +- $salary[i]$ 是唯一的。 +- 与真实值误差在 $10^{-5}$ 以内的结果都将视为正确答案。 + +**示例**: + +- 示例 1: + +```python +给定 salary = [1000,2000,3000] +输出 2000.00000 +解释 最低工资为 1000,最高工资为 3000,去除最低工资和最高工资之后,剩下员工工资的平均值为 2000 / 1 = 2000 +``` + +## 解题思路 + +### 思路 1: + +因为给定 $salary.length \ge 3$,并且 $salary[i]$ 是唯一的,所以无需考虑最低工资和最高工资是同一个。接下来就是按照题意模拟过程: + +- 计算出最小工资为 `min_s`,即 `min_s = min(salary)`。 +- 计算出最大工资为 `max_s`,即 `max_s = max(salary)`。 +- 计算出所有工资和之后再减去最小工资和最大工资,即 `total = sum(salary) - min_s - max_s`。 +- 求剩下工资的平均值,并返回,即 `return total / (len(salary) - 2)`。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def average(self, salary: List[int]) -> float: + min_s, max_s = min(salary), max(salary) + total = sum(salary) - min_s - max_s + return total / (len(salary) - 2) +``` + diff --git a/docs/solutions/1400-1499/consecutive-characters.md b/docs/solutions/1400-1499/consecutive-characters.md new file mode 100644 index 00000000..b582cdc7 --- /dev/null +++ b/docs/solutions/1400-1499/consecutive-characters.md @@ -0,0 +1,40 @@ +# [1446. 连续字符](https://leetcode.cn/problems/consecutive-characters/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1446. 连续字符 - 力扣](https://leetcode.cn/problems/consecutive-characters/) + +## 题目大意 + +给你一个字符串 `s` ,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。 + +要求:返回字符串的能量。 + +注意: + +- `1 <= s.length <= 500` +- `s` 只包含小写英文字母。 + +## 解题思路 + +使用 `count` 统计连续不重复子串的长度,使用 `ans` 记录最长连续不重复子串的长度。 + +## 代码 + +```python +class Solution: + def maxPower(self, s: str) -> int: + ans = 1 + count = 1 + for i in range(1, len(s)): + if s[i] == s[i - 1]: + count += 1 + else: + count = 1 + ans = max(ans, count) + return ans +``` + diff --git a/docs/solutions/1400-1499/construct-k-palindrome-strings.md b/docs/solutions/1400-1499/construct-k-palindrome-strings.md new file mode 100644 index 00000000..210aa26d --- /dev/null +++ b/docs/solutions/1400-1499/construct-k-palindrome-strings.md @@ -0,0 +1,70 @@ +# [1400. 构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) + +- 标签:贪心、哈希表、字符串、计数 +- 难度:中等 + +## 题目链接 + +- [1400. 构造 K 个回文字符串 - 力扣](https://leetcode.cn/problems/construct-k-palindrome-strings/) + +## 题目大意 + +**描述**:给定一个字符串 $s$ 和一个整数 $k$。 + +**要求**:用 $s$ 字符串中所有字符构造 $k$ 个非空回文串。如果可以用 $s$ 中所有字符构造 $k$ 个回文字符串,那么请你返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le s.length \le 10^5$。 +- $s$ 中所有字符都是小写英文字母。 +- $1 \le k \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "annabelle", k = 2 +输出:True +解释:可以用 s 中所有字符构造 2 个回文字符串。 +一些可行的构造方案包括:"anna" + "elble","anbna" + "elle","anellena" + "b" +``` + +## 解题思路 + +### 思路 1:贪心算法 + +- 用字符串 $s$ 中所有字符构造回文串最多可以构造 $len(s)$ 个(将每个字符当做一个回文串)。所以如果 $len(s) < k$,则说明字符数量不够,无法构成 $k$ 个回文串,直接返回 `False`。 +- 如果 $len(s) == k$,则可以直接使用单个字符构建回文串,直接返回 `True`。 +- 如果 $len(s) > k$,则需要判断一下字符串 $s$ 中每个字符的个数。因为当字符是偶数个时,可以直接构造成回文串。所以我们只需要考虑个数为奇数的字符即可。如果个位为奇数的字符种类小于等于 $k$,则说明可以构造 $k$ 个回文串,返回 `True`。如果个位为奇数的字符种类大于 $k$,则说明无法构造 $k$ 个回文串,返回 `Fasle`。 + +### 思路 1:贪心算法代码 + +```python +import collections + +class Solution: + def canConstruct(self, s: str, k: int) -> bool: + size = len(s) + if size < k: + return False + if size == k: + return True + letter_dict = dict() + for i in range(size): + if s[i] in letter_dict: + letter_dict[s[i]] += 1 + else: + letter_dict[s[i]] = 1 + + odd = 0 + for key in letter_dict: + if letter_dict[key] % 2 == 1: + odd += 1 + return odd <= k +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + |\sum|)$,其中 $n$ 为字符串 $s$ 的长度,$\sum$ 是字符集,本题中 $|\sum| = 26$。 +- **空间复杂度**:$O(|\sum|)$。 diff --git a/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md b/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md new file mode 100644 index 00000000..7fd832ee --- /dev/null +++ b/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md @@ -0,0 +1,123 @@ +# [1449. 数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [1449. 数位成本和为目标值的最大数字 - 力扣](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) + +## 题目大意 + +**描述**:给定一个整数数组 $cost$ 和一个整数 $target$。现在从 `""` 开始,不断通过以下规则得到一个新的整数: + +1. 给当前结果添加一个数位($i + 1$)的成本为 $cost[i]$($cost$ 数组下标从 $0$ 开始)。 +2. 总成本必须恰好等于 $target$。 +3. 添加的数位中没有数字 $0$。 + +**要求**:找到按照上述规则可以得到的最大整数。 + +**说明**: + +- 由于答案可能会很大,请你以字符串形式返回。 +- 如果按照上述要求无法得到任何整数,请你返回 `"0"`。 +- $cost.length == 9$。 +- $1 \le cost[i] \le 5000$。 +- $1 \le target \le 5000$。 + +**示例**: + +- 示例 1: + +```python +输入:cost = [4,3,2,5,6,7,2,5,5], target = 9 +输出:"7772" +解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "977" 也是满足要求的数字,但 "7772" 是较大的数字。 + 数字 成本 + 1 -> 4 + 2 -> 3 + 3 -> 2 + 4 -> 5 + 5 -> 6 + 6 -> 7 + 7 -> 2 + 8 -> 5 + 9 -> 5 +``` + +- 示例 2: + +```python +输入:cost = [7,6,5,5,5,6,8,7,8], target = 12 +输出:"85" +解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12。 + 数字 成本 + 1 -> 7 + 2 -> 6 + 3 -> 5 + 4 -> 5 + 5 -> 5 + 6 -> 6 + 7 -> 8 + 8 -> 7 + 9 -> 8 +``` + +## 解题思路 + +把每个数位($1 \sim 9$)看做是一件物品,$cost[i]$ 看做是物品的重量,一共有无数件物品可以使用,$target$ 看做是背包的载重上限,得到的最大整数可以看做是背包的最大价值。那么问题就变为了「完全背包问题」中的「恰好装满背包的最大价值问题」。 + +因为答案可能会很大,要求以字符串形式返回。这里我们可以直接令 $dp[w]$ 为字符串形式,然后定义一个 `def maxInt(a, b):` 方法用于判断两个字符串代表的数字大小。 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照背包载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大整数。 + +###### 3. 状态转移方程 + +$dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]])$ + +###### 4. 初始条件 + +1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为空字符串,即 `dp[0] = ""`。 +2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值为自定义字符 `"#"`,即 ,`dp[w] = "#"`,$0 \le w \le target$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 所以最终结果为 $dp[target]$。 + +### 思路 1:代码 + +```python +class Solution: + def largestNumber(self, cost: List[int], target: int) -> str: + def maxInt(a, b): + if len(a) == len(b): + return max(a, b) + if len(a) > len(b): + return a + return b + + size = len(cost) + dp = ["#" for _ in range(target + 1)] + dp[0] = "" + + for i in range(1, size + 1): + for w in range(cost[i - 1], target + 1): + if dp[w - cost[i - 1]] != "#": + dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]]) + if dp[target] == "#": + return "0" + return dp[target] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $cost$ 的元素个数,$target$ 为所给整数。 +- **空间复杂度**:$O(target)$。 diff --git a/docs/solutions/1400-1499/index.md b/docs/solutions/1400-1499/index.md new file mode 100644 index 00000000..f6c0e4b4 --- /dev/null +++ b/docs/solutions/1400-1499/index.md @@ -0,0 +1,20 @@ +## 本章内容 + +- [1400. 构造 K 个回文字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/construct-k-palindrome-strings.md) +- [1408. 数组中的字符串匹配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/string-matching-in-an-array.md) +- [1422. 分割字符串的最大得分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-score-after-splitting-a-string.md) +- [1423. 可获得的最大点数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md) +- [1438. 绝对差不超过限制的最长连续子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md) +- [1446. 连续字符](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/consecutive-characters.md) +- [1447. 最简分数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/simplified-fractions.md) +- [1449. 数位成本和为目标值的最大数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/form-largest-integer-with-digits-that-add-up-to-target.md) +- [1450. 在既定时间做作业的学生人数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md) +- [1451. 重新排列句子中的单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/rearrange-words-in-a-sentence.md) +- [1456. 定长子串中元音的最大数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md) +- [1476. 子矩形查询](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/subrectangle-queries.md) +- [1480. 一维数组的动态和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/running-sum-of-1d-array.md) +- [1482. 制作 m 束花所需的最少天数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md) +- [1486. 数组异或操作](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/xor-operation-in-an-array.md) +- [1491. 去掉最低工资和最高工资后的工资平均值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/average-salary-excluding-the-minimum-and-maximum-salary.md) +- [1493. 删掉一个元素以后全为 1 的最长子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md) +- [1496. 判断路径是否相交](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/path-crossing.md) diff --git a/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md b/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md new file mode 100644 index 00000000..6c9ed18d --- /dev/null +++ b/docs/solutions/1400-1499/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit.md @@ -0,0 +1,58 @@ +# [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) + +- 标签:队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [1438. 绝对差不超过限制的最长连续子数组 - 力扣](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) + +## 题目大意 + +给定一个整数数组 `nums`,和一个表示限制的整数 `limit`。 + +要求:返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 `limit`。 + +如果不存在满足条件的子数组,则返回 `0`。 + +## 解题思路 + +求最长连续子数组,可以使用滑动窗口来解决。这道题目的难点在于如何维护滑动窗口内的最大值和最小值的差值。遍历滑动窗口求最大值和最小值,每次计算的时间复杂度为 $O(k)$,时间复杂度过高。考虑使用特殊的数据结构来降低时间复杂度。可以使用堆(优先队列)来解决。这里使用 `Python` 中 `heapq` 实现。具体做法如下: + +- 使用 `left`、`right` 两个指针,分别指向滑动窗口的左右边界,保证窗口中最大值和最小值的差值不超过 `limit`。 +- 一开始,`left`、`right` 都指向 `0`。 +- 向右移动 `right`,将最右侧元素加入当前窗口和大顶堆、小顶堆中。 +- 如果大顶堆堆顶元素和小顶堆堆顶元素大于 `limit`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口内的大顶堆、小顶堆。 +- 如果大顶堆堆顶元素和小顶堆堆顶元素小于等于 `limit`,则更新最长连续子数组长度。 +- 然后继续右移 `right`,直到 `right >= len(nums)` 结束。 +- 输出答案。 + +## 代码 + +```python +import heapq + +class Solution: + def longestSubarray(self, nums: List[int], limit: int) -> int: + size = len(nums) + heap_max = [] + heap_min = [] + + ans = 0 + left, right = 0, 0 + while right < size: + heapq.heappush(heap_max, [-nums[right], right]) + heapq.heappush(heap_min, [nums[right], right]) + + while -heap_max[0][0] - heap_min[0][0] > limit: + while heap_min[0][1] <= left: + heapq.heappop(heap_min) + while heap_max[0][1] <= left: + heapq.heappop(heap_max) + left += 1 + ans = max(ans, right - left + 1) + right += 1 + + return ans +``` + diff --git a/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md b/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md new file mode 100644 index 00000000..5e9280ca --- /dev/null +++ b/docs/solutions/1400-1499/longest-subarray-of-1s-after-deleting-one-element.md @@ -0,0 +1,85 @@ +# [1493. 删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) + +- 标签:数组、动态规划、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1493. 删掉一个元素以后全为 1 的最长子数组 - 力扣](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) + +## 题目大意 + +**描述**:给定一个二进制数组 $nums$,需要从数组中删掉一个元素。 + +**要求**:返回最长的且只包含 $1$ 的非空子数组的长度。如果不存在这样的子数组,请返回 $0$。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $nums[i]$ 要么是 $0$ 要么是 $1$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,0,1] +输出:3 +解释:删掉位置 2 的数后,[1,1,1] 包含 3 个 1。 +``` + +- 示例 2: + +```python +输入:nums = [0,1,1,1,0,1,1,0,1] +输出:5 +解释:删掉位置 4 的数字后,[0,1,1,1,1,1,0,1] 的最长全 1 子数组为 [1,1,1,1,1]。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +维护一个元素值为 $0$ 的元素数量少于 $1$ 个的滑动窗口。则答案为滑动窗口长度减去窗口内 $0$ 的个数求最大值。具体做法如下: + +设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口 $0$ 的个数小于 $1$ 个。使用 $window\underline{\hspace{0.5em}}count$ 记录窗口中 $0$ 的个数,使用 $ans$ 记录删除一个元素后,最长的只包含 $1$ 的非空子数组长度。 + +- 一开始,$left$、$right$ 都指向 $0$。 + +- 如果最右侧元素等于 $0$,则 `window_count += 1` 。 + +- 如果 $window\underline{\hspace{0.5em}}count > 1$ ,则不断右移 $left$,缩小滑动窗口长度。并更新当前窗口中 $0$ 的个数,直到 $window\underline{\hspace{0.5em}}count \le 1$。 +- 更新答案值,然后向右移动 $right$,直到 $right \ge len(nums)$ 结束。 +- 输出答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def longestSubarray(self, nums: List[int]) -> int: + left, right = 0, 0 + window_count = 0 + ans = 0 + + while right < len(nums): + if nums[right] == 0: + window_count += 1 + + while window_count > 1: + if nums[left] == 0: + window_count -= 1 + left += 1 + ans = max(ans, right - left + 1 - window_count) + right += 1 + + if ans == len(nums): + return len(nums) - 1 + else: + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md b/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md new file mode 100644 index 00000000..d6799643 --- /dev/null +++ b/docs/solutions/1400-1499/maximum-number-of-vowels-in-a-substring-of-given-length.md @@ -0,0 +1,82 @@ +# [1456. 定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) + +- 标签:字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1456. 定长子串中元音的最大数目 - 力扣](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) + +## 题目大意 + +**描述**:给定字符串 $s$ 和整数 $k$。 + +**要求**:返回字符串 $s$ 中长度为 $k$ 的单个子字符串中可能包含的最大元音字母数。 + +**说明**: + +- 英文中的元音字母为($a$, $e$, $i$, $o$, $u$)。 +- $1 <= s.length <= 10^5$。 +- $s$ 由小写英文字母组成。 +- $1 <= k <= s.length$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "abciiidef", k = 3 +输出:3 +解释:子字符串 "iii" 包含 3 个元音字母。 +``` + +- 示例 2: + +```python +输入:s = "aeiou", k = 2 +输出:2 +解释:任意长度为 2 的子字符串都包含 2 个元音字母。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +固定长度的滑动窗口题目。维护一个长度为 $k$ 的窗口,并统计滑动窗口中最大元音字母数。具体做法如下: + +1. $ans$ 用来维护长度为 $k$ 的单个字符串中最大元音字母数。$window\underline{\hspace{0.5em}}count$ 用来维护窗口中元音字母数。集合 $vowel\underline{\hspace{0.5em}}set$ 用来存储元音字母。 +2. $left$ 、$right$ 都指向字符串 $s$ 的第一个元素,即:$left = 0$,$right = 0$。 +3. 判断 $s[right]$ 是否在元音字母集合中,如果在则用 $window\underline{\hspace{0.5em}}count$ 进行计数。 +4. 当窗口元素个数为 $k$ 时,即:$right - left + 1 \ge k$ 时,更新 $ans$。然后判断 $s[left]$ 是否为元音字母,如果是则 `window_count -= 1`,并向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 +5. 重复 $3 \sim 4$ 步,直到 $right$ 到达数组末尾。 +6. 最后输出 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def maxVowels(self, s: str, k: int) -> int: + left, right = 0, 0 + ans = 0 + window_count = 0 + vowel_set = ('a','e','i','o','u') + + while right < len(s): + if s[right] in vowel_set: + window_count += 1 + + if right - left + 1 >= k: + ans = max(ans, window_count) + if s[left] in vowel_set: + window_count -= 1 + left += 1 + + right += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md b/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md new file mode 100644 index 00000000..4eb5510b --- /dev/null +++ b/docs/solutions/1400-1499/maximum-points-you-can-obtain-from-cards.md @@ -0,0 +1,93 @@ +# [1423. 可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) + +- 标签:数组、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1423. 可获得的最大点数 - 力扣](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) + +## 题目大意 + +**描述**:将卡牌排成一行,给定每张卡片的点数数组 $cardPoints$,其中 $cardPoints[i]$ 表示第 $i$ 张卡牌对应点数。 + +每次行动,可以从行的开头或者末尾拿一张卡牌,最终保证正好拿到了 $k$ 张卡牌。所得点数就是你拿到手中的所有卡牌的点数之和。 + +现在给定一个整数数组 $cardPoints$ 和整数 $k$。 + +**要求**:返回可以获得的最大点数。 + +**说明**: + +- $1 \le cardPoints.length \le 10^5$。 +- $1 \le cardPoints[i] \le 10^4$ +- $1 \le k \le cardPoints.length$。 + +**示例**: + +- 示例 1: + +```python +输入:cardPoints = [1,2,3,4,5,6,1], k = 3 +输出:12 +解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12。 +``` + +- 示例 2: + +```python +输入:cardPoints = [2,2,2], k = 2 +输出:4 +解释:无论你拿起哪两张卡牌,可获得的点数总是 4。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +可以用固定长度的滑动窗口来做。 + +由于只能从开头或末尾位置拿 $k$ 张牌,则最后剩下的肯定是连续的 $len(cardPoints) - k$ 张牌。要求求出 $k$ 张牌可以获得的最大收益,我们可以反向先求出连续 $len(cardPoints) - k$ 张牌的最小点数。则答案为 $sum(cardPoints) - min\underline{\hspace{0.5em}}sum$。维护一个固定长度为 $len(cardPoints) - k$ 的滑动窗口,求最小和。具体做法如下: + +1. $window\underline{\hspace{0.5em}}sum$ 用来维护窗口内的元素和,初始值为 $0$。$min\underline{\hspace{0.5em}}sum$ 用来维护滑动窗口元素的最小和。初始值为 $sum(cardPoints)$。滑动窗口的长度为 $window\underline{\hspace{0.5em}}size$,值为 $len(cardPoints) - k$。 +2. 使用双指针 $left$、$right$。$left$ 、$right$ 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 +3. 向右移动 $right$,先将 $window\underline{\hspace{0.5em}}size$ 个元素填入窗口中。 +4. 当窗口元素个数为 $window\underline{\hspace{0.5em}}size$ 时,即:$right - left + 1 \ge window\underline{\hspace{0.5em}}size$ 时,计算窗口内的元素和,并维护子数组最小和 $min\underline{\hspace{0.5em}}sum$。 +5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$。 +6. 重复 4 ~ 5 步,直到 $right$ 到达数组末尾。 +7. 最后输出 $sum(cardPoints) - min\underline{\hspace{0.5em}}sum$ 即为答案。 + +注意:如果 $window\underline{\hspace{0.5em}}size$ 为 $0$ 时需要特殊判断,此时答案为数组和 $sum(cardPoints)$。 + +### 思路 1:代码 + +```python +class Solution: + def maxScore(self, cardPoints: List[int], k: int) -> int: + window_size = len(cardPoints) - k + window_sum = 0 + cards_sum = sum(cardPoints) + min_sum = cards_sum + + left, right = 0, 0 + if window_size == 0: + return cards_sum + + while right < len(cardPoints): + window_sum += cardPoints[right] + + if right - left + 1 >= window_size: + min_sum = min(window_sum, min_sum) + window_sum -= cardPoints[left] + left += 1 + + right += 1 + + return cards_sum - min_sum +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $cardPoints$ 中的元素数量。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1400-1499/maximum-score-after-splitting-a-string.md b/docs/solutions/1400-1499/maximum-score-after-splitting-a-string.md new file mode 100644 index 00000000..e0313efb --- /dev/null +++ b/docs/solutions/1400-1499/maximum-score-after-splitting-a-string.md @@ -0,0 +1,79 @@ +# [1422. 分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1422. 分割字符串的最大得分 - 力扣](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) + +## 题目大意 + +**描述**:给定一个由若干 $0$ 和 $1$ 组成的字符串。将字符串分割成两个非空子字符串的得分为:左子字符串中 $0$ 的数量 + 右子字符串中 $1$ 的数量。 + +**要求**:计算并返回该字符串分割成两个非空子字符串(即左子字符串和右子字符串)所能获得的最大得分。 + +**说明**: + +- $2 \le s.length \le 500$。 +- 字符串 $s$ 仅由字符 $0$ 和 $1$ 组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "011101" +输出:5 +解释: +将字符串 s 划分为两个非空子字符串的可行方案有: +左子字符串 = "0" 且 右子字符串 = "11101",得分 = 1 + 4 = 5 +左子字符串 = "01" 且 右子字符串 = "1101",得分 = 1 + 3 = 4 +左子字符串 = "011" 且 右子字符串 = "101",得分 = 1 + 2 = 3 +左子字符串 = "0111" 且 右子字符串 = "01",得分 = 1 + 1 = 2 +左子字符串 = "01110" 且 右子字符串 = "1",得分 = 2 + 1 = 3 +``` + +- 示例 2: + +```python +输入:s = "00111" +输出:5 +解释:当 左子字符串 = "00" 且 右子字符串 = "111" 时,我们得到最大得分 = 2 + 3 = 5 +``` + +## 解题思路 + +### 思路 1:前缀和 + +1. 遍历字符串 $s$,使用前缀和数组来记录每个前缀子字符串中 $1$ 的个数。 +2. 再次遍历字符串 $s$,枚举每个分割点,利用前缀和数组计算出当前分割出的左子字符串中 $1$ 的个数与右子字符串中 $0$ 的个数,并计算当前得分,然后更新最大得分。 +3. 返回最大得分作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def maxScore(self, s: str) -> int: + size = len(s) + one_cnts = [0 for _ in range(size + 1)] + + for i in range(1, size + 1): + if s[i - 1] == '1': + one_cnts[i] = one_cnts[i - 1] + 1 + else: + one_cnts[i] = one_cnts[i - 1] + + ans = 0 + for i in range(1, size): + left_score = i - one_cnts[i] + right_score = one_cnts[size] - one_cnts[i] + ans = max(ans, left_score + right_score) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md b/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md new file mode 100644 index 00000000..cc8c9e5d --- /dev/null +++ b/docs/solutions/1400-1499/minimum-number-of-days-to-make-m-bouquets.md @@ -0,0 +1,118 @@ +# [1482. 制作 m 束花所需的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [1482. 制作 m 束花所需的最少天数 - 力扣](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/) + +## 题目大意 + +**描述**:给定一个整数数组 $bloomDay$,以及两个整数 $m$ 和 $k$。$bloomDay$ 代表花朵盛开的时间,$bloomDay[i]$ 表示第 $i$ 朵花的盛开时间。盛开后就可以用于一束花中。 + +现在需要制作 $m$ 束花。制作花束时,需要使用花园中相邻的 $k$ 朵花 。 + +**要求**:返回从花园中摘 $m$ 束花需要等待的最少的天数。如果不能摘到 $m$ 束花则返回 $-1$。 + +**说明**: + +- $bloomDay.length == n$。 +- $1 \le n \le 10^5$。 +- $1 \le bloomDay[i] \le 10^9$。 +- $1 \le m \le 10^6$。 +- $1 \le k \le n$。 + +**示例**: + +- 示例 1: + +```python +输入:bloomDay = [1,10,3,10,2], m = 3, k = 1 +输出:3 +解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。 +现在需要制作 3 束花,每束只需要 1 朵。 +1 天后:[x, _, _, _, _] // 只能制作 1 束花 +2 天后:[x, _, _, _, x] // 只能制作 2 束花 +3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3 +``` + +- 示例 2: + +```python +输入:bloomDay = [1,10,3,10,2], m = 3, k = 2 +输出:-1 +解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1。 +``` + +## 解题思路 + +### 思路 1:二分查找算法 + +这道题跟「[0875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/)」、「[1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/)」有点相似。 + +根据题目可知: + +- 制作花束最少使用时间跟花朵开花最短时间有关系,即 $min(bloomDay)$。 +- 制作花束最多使用时间跟花朵开花最长时间有关系,即 $max(bloomDay)$。 +- 则制作花束所需要的天数就变成了一个区间 $[min(bloomDay), max(bloomDay)]$。 + +那么,我们就可以根据这个区间,利用二分查找算法找到一个符合题意的最少天数。而判断某个天数下能否摘到 $m$ 束花则可以写个方法判断。具体步骤如下: + +- 遍历数组 $bloomDay$。 + - 如果 $bloomDay[i] \le days$。就将花朵数量加 $1$。 + - 当能摘的花朵数等于 $k$ 时,能摘的花束数目加 $1$,花朵数量置为 $0$。 + - 如果 $bloomDay[i] > days$。就将花朵数置为 $0$。 +- 最后判断能摘的花束数目是否大于等于 $m$。 + +整个算法的步骤如下: + +- 如果 $m \times k > len(bloomDay)$,说明无法满足要求,直接返回 $-1$。 +- 使用两个指针 $left$、$right$。令 $left$ 指向 $min(bloomDay)$,$right$ 指向 $max(bloomDay)$。代表待查找区间为 $[left, right]$。 +- 取两个节点中心位置 $mid$,判断是否能在 $mid$ 天制作 $m$ 束花。 + - 如果不能,则将区间 $[left, mid]$ 排除掉,继续在区间 $[mid + 1, right]$ 中查找。 + - 如果能,说明天数还可以继续减少,则继续在区间 $[left, mid]$ 中查找。 +- 当 $left == right$ 时跳出循环,返回 $left$。 + +### 思路 1:代码 + +```python +class Solution: + def canMake(self, bloomDay, days, m, k): + count = 0 + flower = 0 + for i in range(len(bloomDay)): + if bloomDay[i] <= days: + flower += 1 + if flower == k: + count += 1 + flower = 0 + else: + flower = 0 + return count >= m + + def minDays(self, bloomDay: List[int], m: int, k: int) -> int: + if m > len(bloomDay) / k: + return -1 + + left, right = min(bloomDay), max(bloomDay) + + while left < right: + mid = left + (right - left) // 2 + if not self.canMake(bloomDay, mid, m, k): + left = mid + 1 + else: + right = mid + + return left +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log (max(bloomDay) - min(bloomDay)))$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- 【题解】[【赤小豆】为什么是二分法,思路及模板 python - 制作 m 束花所需的最少天数 - 力扣(LeetCode)](https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/solution/chi-xiao-dou-python-wei-shi-yao-shi-er-f-24p7/) + diff --git a/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md b/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md new file mode 100644 index 00000000..434ade0f --- /dev/null +++ b/docs/solutions/1400-1499/number-of-students-doing-homework-at-a-given-time.md @@ -0,0 +1,291 @@ +# [1450. 在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [1450. 在既定时间做作业的学生人数 - 力扣](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) + +## 题目大意 + +**描述**:给你两个长度相等的整数数组,一个表示开始时间的数组 $startTime$ ,另一个表示结束时间的数组 $endTime$。再给定一个整数 $queryTime$ 作为查询时间。已知第 $i$ 名学生在 $startTime[i]$ 时开始写作业并于 $endTime[i]$ 时完成作业。 + +**要求**:返回在查询时间 $queryTime$ 时正在做作业的学生人数。即能够使 $queryTime$ 处于区间 $[startTime[i], endTime[i]]$ 的学生人数。 + +**说明**: + +- $startTime.length == endTime.length$。 +- $1\le startTime.length \le 100$。 +- $1 \le startTime[i] \le endTime[i] \le 1000$。 +- $1 \le queryTime \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:startTime = [4], endTime = [4], queryTime = 4 +输出:1 +解释:在查询时间只有一名学生在做作业。 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +- 维护一个用于统计在查询时间 $queryTime$ 时正在做作业的学生人数的变量 $cnt$。然后遍历所有学生的开始时间和结束时间。 +- 如果 $queryTime$ 在区间 $[startTime[i], endTime[i]]$ 之间,即 $startTime[i] <= queryTime <= endTime[i]$,则令 $cnt$ 加 $1$。 +- 遍历完输出统计人数 $cnt$。 + +### 思路 1:枚举算法代码 + +```python +class Solution: + def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: + cnt = 0 + size = len(startTime) + for i in range(size): + if startTime[i] <= queryTime <= endTime[i]: + cnt += 1 + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组中的元素个数。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:线段树 + +- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 $[0, 1000]$ 的线段树,初始化所有区间值都为 $0$。 +- 然后遍历所有学生的开始时间和结束时间,并将区间 $[startTime[i], endTime[i]]$ 值加 $1$。 +- 在线段树中查询 $queryTime$ 对应的单点区间 $[queryTime, queryTime]$ 的最大值为多少。 + +### 思路 2:线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, val=0): + self.left = -1 # 区间左边界 + self.right = -1 # 区间右边界 + self.val = val # 节点值(区间值) + self.lazy_tag = None # 区间和问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, nums, function): + self.size = len(nums) + self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组 + self.nums = nums # 原始数据 + self.function = function # function 是一个函数,左右区间的聚合方法 + if self.size > 0: + self.__build(0, 0, self.size - 1) + + # 单点更新接口:将 nums[i] 更改为 val + def update_point(self, i, val): + self.nums[i] = val + self.__update_point(i, val, 0) + + # 区间更新接口:将区间为 [q_left, q_right] 上的所有元素值加上 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, 0) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, 0) + + # 获取 nums 数组接口:返回 nums 数组 + def get_nums(self): + for i in range(self.size): + self.nums[i] = self.query_interval(i, i) + return self.nums + + + # 以下为内部实现方法 + + # 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right] + def __build(self, index, left, right): + self.tree[index].left = left + self.tree[index].right = right + if left == right: # 叶子节点,节点值为对应位置的元素值 + self.tree[index].val = self.nums[left] + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.__build(left_index, left, mid) # 递归创建左子树 + self.__build(right_index, mid + 1, right) # 递归创建右子树 + self.__pushup(index) # 向上更新节点的区间值 + + # 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index + def __update_point(self, i, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left == right: + self.tree[index].val = val # 叶子节点,节点值修改为 val + return + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if i <= mid: # 在左子树中更新节点值 + self.__update_point(i, val, left_index) + else: # 在右子树中更新节点值 + self.__update_point(i, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + if self.tree[index].lazy_tag is not None: + self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val + else: + self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val + interval_size = (right - left + 1) # 当前节点所在区间大小 + self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val + return + + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + self.__pushdown(index) # 向下更新节点的区间值 + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + if q_left <= mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, left_index) + if q_right > mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, right_index) + + self.__pushup(index) # 向上更新节点的区间值 + + # 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, index): + left = self.tree[index].left + right = self.tree[index].right + + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return self.tree[index].val # 直接返回节点值 + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(index) + + mid = left + (right - left) // 2 # 左右节点划分点 + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, left_index) + if q_right > mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, right_index) + + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新下标为 index 的节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, index): + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val) + + # 向下更新实现方法:更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, index): + lazy_tag = self.tree[index].lazy_tag + if lazy_tag is None: + return + + left_index = index * 2 + 1 # 左子节点的存储下标 + right_index = index * 2 + 2 # 右子节点的存储下标 + + if self.tree[left_index].lazy_tag is not None: + self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记 + else: + self.tree[left_index].lazy_tag = lazy_tag + left_size = (self.tree[left_index].right - self.tree[left_index].left + 1) + self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag + + if self.tree[right_index].lazy_tag is not None: + self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记 + else: + self.tree[right_index].lazy_tag = lazy_tag + right_size = (self.tree[right_index].right - self.tree[right_index].left + 1) + self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag + + self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记 + + +class Solution: + def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: + nums = [0 for _ in range(1010)] + self.STree = SegmentTree(nums, lambda x, y: max(x, y)) + size = len(startTime) + for i in range(size): + self.STree.update_interval(startTime[i], endTime[i], 1) + + return self.STree.query_interval(queryTime, queryTime) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组元素的个数。 +- **空间复杂度**:$O(n)$。 + +### 思路 3:树状数组 + +- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 $[0, 1000]$ 的树状数组。 +- 注意: + - 树状数组中 $update(self, index, delta):$ 指的是将对应元素 $nums[index] $ 加上 $delta$。 + - $query(self, index):$ 指的是 $index$ 位置之前的元素和,即前缀和。 +- 然后遍历所有学生的开始时间和结束时间,将树状数组上 $startTime[i]$ 的值增加 $1$,再将树状数组上$endTime[i]$ 的值减少 $1$。 +- 则查询 $queryTime$ 位置的前缀和即为答案。 + +### 思路 3:树状数组代码 + +```python +class BinaryIndexTree: + + def __init__(self, n): + self.size = n + self.tree = [0 for _ in range(n + 1)] + + def lowbit(self, index): + return index & (-index) + + def update(self, index, delta): + while index <= self.size: + self.tree[index] += delta + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + +class Solution: + def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int: + bit = BinaryIndexTree(1010) + size = len(startTime) + for i in range(size): + bit.update(startTime[i], 1) + bit.update(endTime[i] + 1, -1) + return bit.query(queryTime) +``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组元素的个数。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1400-1499/path-crossing.md b/docs/solutions/1400-1499/path-crossing.md new file mode 100644 index 00000000..0850e901 --- /dev/null +++ b/docs/solutions/1400-1499/path-crossing.md @@ -0,0 +1,86 @@ +# [1496. 判断路径是否相交](https://leetcode.cn/problems/path-crossing/) + +- 标签:哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [1496. 判断路径是否相交 - 力扣](https://leetcode.cn/problems/path-crossing/) + +## 题目大意 + +**描述**:给定一个字符串 $path$,其中 $path[i]$ 的值可以是 `'N'`、`'S'`、`'E'` 或者 `'W'`,分别表示向北、向南、向东、向西移动一个单位。 + +你从二维平面上的原点 $(0, 0)$ 处开始出发,按 $path$ 所指示的路径行走。 + +**要求**:如果路径在任何位置上与自身相交,也就是走到之前已经走过的位置,请返回 $True$;否则,返回 $False$。 + +**说明**: + +- $1 \le path.length \le 10^4$。 +- $path[i]$ 为 `'N'`、`'S'`、`'E'` 或 `'W'`。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/06/28/screen-shot-2020-06-10-at-123929-pm.png) + +```python +输入:path = "NES" +输出:false +解释:该路径没有在任何位置相交。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/06/28/screen-shot-2020-06-10-at-123843-pm.png) + +```python +输入:path = "NESWW" +输出:true +解释:该路径经过原点两次。 +``` + +## 解题思路 + +### 思路 1:哈希表 + 模拟 + +1. 使用哈希表将 `'N'`、`'S'`、`'E'`、`'W'` 对应横纵坐标轴上的改变表示出来。 +2. 使用集合 $visited$ 存储走过的坐标元组。 +3. 遍历 $path$,按照 $path$ 所指示的路径模拟行走,并将所走过的坐标使用 $visited$ 存储起来。 +4. 如果在 $visited$ 遇到已经走过的坐标,则返回 $True$。 +5. 如果遍历完仍未发现已经走过的坐标,则返回 $False$。 + +### 思路 1:代码 + +```Python +class Solution: + def isPathCrossing(self, path: str) -> bool: + directions = { + "N" : (-1, 0), + "S" : (1, 0), + "W" : (0, -1), + "E" : (0, 1), + } + + x, y = 0, 0 + + visited = set() + visited.add((x, y)) + + for ch in path: + x += directions[ch][0] + y += directions[ch][1] + if (x, y) in visited: + return True + visited.add((x, y)) + + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $path$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1400-1499/rearrange-words-in-a-sentence.md b/docs/solutions/1400-1499/rearrange-words-in-a-sentence.md new file mode 100644 index 00000000..bf685290 --- /dev/null +++ b/docs/solutions/1400-1499/rearrange-words-in-a-sentence.md @@ -0,0 +1,75 @@ +# [1451. 重新排列句子中的单词](https://leetcode.cn/problems/rearrange-words-in-a-sentence/) + +- 标签:字符串、排序 +- 难度:中等 + +## 题目链接 + +- [1451. 重新排列句子中的单词 - 力扣](https://leetcode.cn/problems/rearrange-words-in-a-sentence/) + +## 题目大意 + +**描述**:「句子」是一个用空格分隔单词的字符串。给定一个满足下述格式的句子 $text$: + +- 句子的首字母大写。 +- $text$ 中的每个单词都用单个空格分隔。 + +**要求**:重新排列 $text$ 中的单词,使所有单词按其长度的升序排列。如果两个单词的长度相同,则保留其在原句子中的相对顺序。 + +请同样按上述格式返回新的句子。 + +**说明**: + +- $text$ 以大写字母开头,然后包含若干小写字母以及单词间的单个空格。 +- $1 \le text.length \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:text = "Leetcode is cool" +输出:"Is cool leetcode" +解释:句子中共有 3 个单词,长度为 8 的 "Leetcode" ,长度为 2 的 "is" 以及长度为 4 的 "cool"。 +输出需要按单词的长度升序排列,新句子中的第一个单词首字母需要大写。 +``` + +- 示例 2: + +```python +输入:text = "Keep calm and code on" +输出:"On and keep calm code" +解释:输出的排序情况如下: +"On" 2 个字母。 +"and" 3 个字母。 +"keep" 4 个字母,因为存在长度相同的其他单词,所以它们之间需要保留在原句子中的相对顺序。 +"calm" 4 个字母。 +"code" 4 个字母。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 将 $text$ 按照 `" "` 进行分割为单词数组 $words$。 +2. 将单词数组按照「单词长度」进行升序排序。 +3. 将单词数组用 `" "` 连接起来,并将首字母转为大写字母,其他字母转为小写字母,将结果存入答案字符串 $ans$ 中。 +4. 返回答案字符串 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def arrangeWords(self, text: str) -> str: + words = text.split(' ') + words.sort(key=lambda word:len(word)) + ans = " ".join(words).capitalize() + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为字符串 $text$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1400-1499/running-sum-of-1d-array.md b/docs/solutions/1400-1499/running-sum-of-1d-array.md new file mode 100644 index 00000000..575a8ad0 --- /dev/null +++ b/docs/solutions/1400-1499/running-sum-of-1d-array.md @@ -0,0 +1,74 @@ +# [1480. 一维数组的动态和](https://leetcode.cn/problems/running-sum-of-1d-array/) + +- 标签:数组、前缀和 +- 难度:简单 + +## 题目链接 + +- [1480. 一维数组的动态和 - 力扣](https://leetcode.cn/problems/running-sum-of-1d-array/) + +## 题目大意 + +**描述**:给定一个数组 $nums$。 + +**要求**:返回数组 $nums$ 的动态和。 + +**说明**: + +- **动态和**:数组前 $i$ 项元素和构成的数组,计算公式为 $runningSum[i] = \sum_{x = 0}^{x = i}(nums[i])$。 +- $1 \le nums.length \le 1000$。 +- $-10^6 \le nums[i] \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4] +输出:[1,3,6,10] +解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4]。 +``` + +- 示例 2: + +```python +输入:nums = [1,1,1,1,1] +输出:[1,2,3,4,5] +解释:动态和计算过程为 [1, 1+1, 1+1+1, 1+1+1+1, 1+1+1+1+1]。 +``` + +## 解题思路 + +### 思路 1:递推 + +根据动态和的公式 $runningSum[i] = \sum_{x = 0}^{x = i}(nums[i])$,可以推导出: + +$runningSum = \begin{cases} nums[0], & i = 0 \cr runningSum[i - 1] + nums[i], & i > 0\end{cases}$ + +则解决过程如下: + +1. 新建一个长度等于 $nums$ 的数组 $res$ 用于存放答案。 +2. 初始化 $res[0] = nums[0]$。 +3. 从下标 $1$ 开始遍历数组 $nums$,递推更新 $res$,即:`res[i] = res[i - 1] + nums[i]`。 +4. 遍历结束,返回 $res$ 作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def runningSum(self, nums: List[int]) -> List[int]: + size = len(nums) + res = [0 for _ in range(size)] + for i in range(size): + if i == 0: + res[i] = nums[i] + else: + res[i] = res[i - 1] + nums[i] + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 + diff --git a/docs/solutions/1400-1499/simplified-fractions.md b/docs/solutions/1400-1499/simplified-fractions.md new file mode 100644 index 00000000..003a4cf1 --- /dev/null +++ b/docs/solutions/1400-1499/simplified-fractions.md @@ -0,0 +1,65 @@ +# [1447. 最简分数](https://leetcode.cn/problems/simplified-fractions/) + +- 标签:数学、字符串、数论 +- 难度:中等 + +## 题目链接 + +- [1447. 最简分数 - 力扣](https://leetcode.cn/problems/simplified-fractions/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回所有 $0$ 到 $1$ 之间(不包括 $0$ 和 $1$)满足分母小于等于 $n$ 的最简分数。分数可以以任意顺序返回。 + +**说明**: + +- $1 \le n \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 2 +输出:["1/2"] +解释:"1/2" 是唯一一个分母小于等于 2 的最简分数。 +``` + +- 示例 2: + +```python +输入:n = 4 +输出:["1/2","1/3","1/4","2/3","3/4"] +解释:"2/4" 不是最简分数,因为它可以化简为 "1/2"。 +``` + +## 解题思路 + +### 思路 1:数学 + +如果分子和分母的最大公约数为 $1$ 时,则当前分数为最简分数。 + +而 $n$ 的数据范围为 $(1, 100)$。因此我们可以使用两重遍历,分别枚举分子和分母,然后通过判断分子和分母是否为最大公约数,来确定当前分数是否为最简分数。 + +### 思路 1:代码 + +```python +class Solution: + def simplifiedFractions(self, n: int) -> List[str]: + res = [] + + for i in range(1, n): + for j in range(i + 1, n + 1): + if math.gcd(i, j) == 1: + res.append(str(i) + "/" + str(j)) + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times \log n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1400-1499/string-matching-in-an-array.md b/docs/solutions/1400-1499/string-matching-in-an-array.md new file mode 100644 index 00000000..3a985c04 --- /dev/null +++ b/docs/solutions/1400-1499/string-matching-in-an-array.md @@ -0,0 +1,92 @@ +# [1408. 数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) + +- 标签:数组、字符串、字符串匹配 +- 难度:简单 + +## 题目链接 + +- [1408. 数组中的字符串匹配 - 力扣](https://leetcode.cn/problems/string-matching-in-an-array/) + +## 题目大意 + +**描述**:给定一个字符串数组 `words`,数组中的每个字符串都可以看作是一个单词。如果可以删除 `words[j]` 最左侧和最右侧的若干字符得到 `word[i]`,那么字符串 `words[i]` 就是 `words[j]` 的一个子字符串。 + +**要求**:按任意顺序返回 `words` 中是其他单词的子字符串的所有单词。 + +**说明**: + +- $1 \le words.length \le 100$。 +- $1 \le words[i].length \le 30$ +- `words[i]` 仅包含小写英文字母。 +- 题目数据保证每个 `words[i]` 都是独一无二的。 + +**示例**: + +- 示例 1: + +```python +输入:words = ["mass","as","hero","superhero"] +输出:["as","hero"] +解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。此外,["hero","as"] 也是有效的答案。 +``` + +## 解题思路 + +### 思路 1:KMP 算法 + +1. 先按照字符串长度从小到大排序,使用数组 `res` 保存答案。 +2. 使用两重循环遍历,对于 `words[i]` 和 `words[j]`,使用 `KMP` 匹配算法,如果 `wrods[j]` 包含 `words[i]`,则将其加入到答案数组中,并跳出最里层循环。 +3. 返回答案数组 `res`。 + +### 思路 1:代码 + +```python +class Solution: + # 生成 next 数组 + # next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度 + def generateNext(self, p: str): + m = len(p) + next = [0 for _ in range(m)] # 初始化数组元素全部为 0 + + left = 0 # left 表示前缀串开始所在的下标位置 + for right in range(1, m): # right 表示后缀串开始所在的下标位置 + while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退 + left = next[left - 1] # left 进行回退操作 + if p[left] == p[right]: # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度 + left += 1 + next[right] = left # 记录前缀长度,更新 next[right], 结束本次循环, right += 1 + + return next + + # KMP 匹配算法,T 为文本串,p 为模式串 + def kmp(self, T: str, p: str) -> int: + n, m = len(T), len(p) + + next = self.generateNext(p) # 生成 next 数组 + + j = 0 # j 为模式串中当前匹配的位置 + for i in range(n): # i 为文本串中当前匹配的位置 + while j > 0 and T[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退 + j = next[j - 1] + if T[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配 + j += 1 + if j == m: # 当前模式串完全匹配成功,返回匹配开始位置 + return i - j + 1 + return -1 # 匹配失败,返回 -1 + + def stringMatching(self, words: List[str]) -> List[str]: + words.sort(key=lambda x:len(x)) + + res = [] + for i in range(len(words) - 1): + for j in range(i + 1, len(words)): + if self.kmp(words[j], words[i]) != -1: + res.append(words[i]) + break + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2 \times m)$,其中字符串数组长度为 $n$,字符串数组中最长字符串长度为 $m$。 +- **空间复杂度**:$O(m)$。 diff --git a/docs/solutions/1400-1499/subrectangle-queries.md b/docs/solutions/1400-1499/subrectangle-queries.md new file mode 100644 index 00000000..42d7bb27 --- /dev/null +++ b/docs/solutions/1400-1499/subrectangle-queries.md @@ -0,0 +1,115 @@ +# [1476. 子矩形查询](https://leetcode.cn/problems/subrectangle-queries/) + +- 标签:设计、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1476. 子矩形查询 - 力扣](https://leetcode.cn/problems/subrectangle-queries/) + +## 题目大意 + +**要求**:实现一个类 SubrectangleQueries,它的构造函数的参数是一个 $rows \times cols $的矩形(这里用整数矩阵表示),并支持以下两种操作: + +1. `updateSubrectangle(int row1, int col1, int row2, int col2, int newValue)`:用 $newValue$ 更新以 $(row1,col1)$ 为左上角且以 $(row2,col2)$ 为右下角的子矩形。 + +2. `getValue(int row, int col)`:返回矩形中坐标 (row,col) 的当前值。 + +**说明**: + +- 最多有 $500$ 次 `updateSubrectangle` 和 `getValue` 操作。 +- $1 <= rows, cols <= 100$。 +- $rows == rectangle.length$。 +- $cols == rectangle[i].length$。 +- $0 <= row1 <= row2 < rows$。 +- $0 <= col1 <= col2 < cols$。 +- $1 <= newValue, rectangle[i][j] <= 10^9$。 +- $0 <= row < rows$。 +- $0 <= col < cols$。 + +**示例**: + +- 示例 1: + +```python +输入: +["SubrectangleQueries","getValue","updateSubrectangle","getValue","getValue","updateSubrectangle","getValue","getValue"] +[[[[1,2,1],[4,3,4],[3,2,1],[1,1,1]]],[0,2],[0,0,3,2,5],[0,2],[3,1],[3,0,3,2,10],[3,1],[0,2]] +输出: +[null,1,null,5,5,null,10,5] +解释: +SubrectangleQueries subrectangleQueries = new SubrectangleQueries([[1,2,1],[4,3,4],[3,2,1],[1,1,1]]); +// 初始的 (4x3) 矩形如下: +// 1 2 1 +// 4 3 4 +// 3 2 1 +// 1 1 1 +subrectangleQueries.getValue(0, 2); // 返回 1 +subrectangleQueries.updateSubrectangle(0, 0, 3, 2, 5); +// 此次更新后矩形变为: +// 5 5 5 +// 5 5 5 +// 5 5 5 +// 5 5 5 +subrectangleQueries.getValue(0, 2); // 返回 5 +subrectangleQueries.getValue(3, 1); // 返回 5 +subrectangleQueries.updateSubrectangle(3, 0, 3, 2, 10); +// 此次更新后矩形变为: +// 5 5 5 +// 5 5 5 +// 5 5 5 +// 10 10 10 +subrectangleQueries.getValue(3, 1); // 返回 10 +subrectangleQueries.getValue(0, 2); // 返回 5 +``` + +- 示例 2: + +```python +输入: +["SubrectangleQueries","getValue","updateSubrectangle","getValue","getValue","updateSubrectangle","getValue"] +[[[[1,1,1],[2,2,2],[3,3,3]]],[0,0],[0,0,2,2,100],[0,0],[2,2],[1,1,2,2,20],[2,2]] +输出: +[null,1,null,100,100,null,20] +解释: +SubrectangleQueries subrectangleQueries = new SubrectangleQueries([[1,1,1],[2,2,2],[3,3,3]]); +subrectangleQueries.getValue(0, 0); // 返回 1 +subrectangleQueries.updateSubrectangle(0, 0, 2, 2, 100); +subrectangleQueries.getValue(0, 0); // 返回 100 +subrectangleQueries.getValue(2, 2); // 返回 100 +subrectangleQueries.updateSubrectangle(1, 1, 2, 2, 20); +subrectangleQueries.getValue(2, 2); // 返回 20 + +``` + +## 解题思路 + +### 思路 1:暴力 + +矩形最大为 $row \times col == 100 \times 100$,则每次更新最多需要更新 $10000$ 个值,更新次数最多为 $500$ 次。 + +用暴力更新的方法最多需要更新 $5000000$ 次,我们可以尝试一下用暴力更新的方法解决本题(提交后发现可以通过)。 + +### 思路 1:代码 + +```Python +class SubrectangleQueries: + + def __init__(self, rectangle: List[List[int]]): + self.rectangle = rectangle + + + def updateSubrectangle(self, row1: int, col1: int, row2: int, col2: int, newValue: int) -> None: + for row in range(row1, row2 + 1): + for col in range(col1, col2 + 1): + self.rectangle[row][col] = newValue + + + def getValue(self, row: int, col: int) -> int: + return self.rectangle[row][col] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(row \times col \times 500)$。 +- **空间复杂度**:$O(row \times col)$。 diff --git a/docs/solutions/1400-1499/xor-operation-in-an-array.md b/docs/solutions/1400-1499/xor-operation-in-an-array.md new file mode 100644 index 00000000..dd548316 --- /dev/null +++ b/docs/solutions/1400-1499/xor-operation-in-an-array.md @@ -0,0 +1,88 @@ +# [1486. 数组异或操作](https://leetcode.cn/problems/xor-operation-in-an-array/) + +- 标签:位运算、数学 +- 难度:简单 + +## 题目链接 + +- [1486. 数组异或操作 - 力扣](https://leetcode.cn/problems/xor-operation-in-an-array/) + +## 题目大意 + +给定两个整数 n、start。数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)。n 为数组长度。返回数组 nums 中所有元素按位异或(XOR)后得到的结果。 + +## 解题思路 + +### 1. 模拟 + +直接按照题目要求模拟即可。 + +### 2. 规律 + +- $x \oplus x = 0$; +- $x \oplus y = y \oplus x$(交换律); +- $(x \oplus y) \oplus z = x \oplus (y \oplus z)$(结合律); +- $x \oplus y \oplus y = x$(自反性); +- $\forall i \in Z$,有 $4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0$; +- $\forall i \in Z$,有 $2i \oplus (2i+1) = 1$; +- $\forall i \in Z$,有 $2i \oplus 1 = 2i+1$。 + +本题中计算的是 $start \oplus (start + 2) \oplus (start + 4) \oplus (start + 6) \oplus … \oplus (start+(2*(n-1)))$。 + +可以看出,若 start 为奇数,则 $start+2, start + 4, …, start + (2 \times(n - 1))$ 都为奇数。若 start 为偶数,则 $start + 2, start + 4, …, start + (2 \times(n - 1))$ 都为偶数。则它们对应二进制的最低位相同,则我们可以将最低位提取处理单独处理。从而将公式转换一下。 + +令 $s = \frac{start}{2}$,则等式变为 $(s) \oplus (s+1) \oplus (s+2) \oplus (s+3) \oplus … \oplus (s+(n-1)) * 2 + e$,e 表示运算结果的最低位。 + +根据自反性,$(s) \oplus (s+1) \oplus (s+2) \oplus (s+3) \oplus … \oplus (s+(n-1)) = \\ (1 \oplus 2 \oplus … \oplus (s-1)) \oplus (1 \oplus 2 \oplus … \oplus (s-1) \oplus (s) \oplus (s+1) \oplus … \oplus (s+(n-1)))$ + +例如: $3 \oplus 4 \oplus 5 \oplus 6 \oplus 7 = (1 \oplus 2) \oplus (1 \oplus 2 \oplus 3 \oplus 4 \oplus 5 \oplus 6 \oplus7)$ + +就变为了计算前 n 项序列的异或值。假设我们定义一个函数 sumXor(x) 用于计算前 n 项数的异或结果,通过观察可得出: + +$sumXor(x) = \begin{cases} \begin{array} \ x, & x = 4i, k \in Z \cr (x-1) \oplus x, & x = 4i+1, k \in Z \cr (x-2) \oplus (x-1) \oplus x, & x = 4i+2, k \in Z \cr (x-3) \oplus (x-2) \oplus (x-3) \oplus x, & x = 4i+3, k \in Z \end{array} \end{cases}$ + +继续化简得: + +$sumXor(x) = \begin{cases} \begin{array} \ x, & x = 4i, k \in Z \cr 1, & x = 4i+1, k \in Z \cr x+1, & x = 4i+2, k \in Z \cr 0, & x = 4i+3, k \in Z \end{array} \end{cases}$ + +则最终结果为 $sumXor(s-1) \oplus sumXor(s+n-1) * 2 + e$。 + +下面还有最后一位 e 的计算。 + +- 若 start 为偶数,则最后一位 e 为 0。 +- 若 start 为奇数,最后一位 e 跟 n 有关,若 n 为奇数,则最后一位 e 为 1,若 n 为偶数,则最后一位 e 为 0。 + +总结下来就是 `e = start & n & 1`。 + +## 代码 + +1. 模拟 + +```python +class Solution: + def xorOperation(self, n: int, start: int) -> int: + ans = 0 + for i in range(n): + ans ^= (start + i * 2) + return ans +``` + +2. 规律 + +```python +class Solution: + def sumXor(self, x): + if x % 4 == 0: + return x + if x % 4 == 1: + return 1 + if x % 4 == 2: + return x + 1 + return 0 + def xorOperation(self, n: int, start: int) -> int: + s = start >> 1 + e = n & start & 1 + ans = self.sumXor(s-1) ^ self.sumXor(s + n - 1) + return ans << 1 | e +``` + diff --git a/docs/solutions/1500-1599/can-make-arithmetic-progression-from-sequence.md b/docs/solutions/1500-1599/can-make-arithmetic-progression-from-sequence.md new file mode 100644 index 00000000..a3cdf284 --- /dev/null +++ b/docs/solutions/1500-1599/can-make-arithmetic-progression-from-sequence.md @@ -0,0 +1,57 @@ +# [1502. 判断能否形成等差数列](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) + +- 标签:数组、排序 +- 难度:简单 + +## 题目链接 + +- [1502. 判断能否形成等差数列 - 力扣](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) + +## 题目大意 + +**描述**:给定一个数字数组 `arr`。如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数序就称为等差数列。 + +**要求**:如果数组 `arr` 通过重新排列可以形成等差数列,则返回 `True`;否则返回 `False`。 + +**说明**: + +- $2 \le arr.length \le 1000$ +- $-10^6 \le arr[i] \le 10^6$ + +**示例**: + +- 示例 1: + +```python +输入:arr = [3,5,1] +输出:True +解释:数组重新排序后得到 [1,3,5] 或者 [5,3,1],任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。 +``` + +## 解题思路 + +### 思路 1: + +- 如果数组元素个数小于等于 `2`,则数组肯定可以形成等差数列,直接返回 `True`。 +- 对数组进行排序。 +- 从下标为 `2` 的元素开始,遍历相邻的 `3` 个元素 `arr[i]` 、`arr[i - 1]`、`arr[i - 2]`。判断 `arr[i] - arr[i - 1]` 是否等于 `arr[i - 1] - arr[i - 2]`。如果不等于,则数组无法形成等差数列,返回 `False`。 +- 如果遍历完数组,则说明数组可以形成等差数列,返回 `True`。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def canMakeArithmeticProgression(self, arr: List[int]) -> bool: + size = len(arr) + if size <= 2: + return True + + arr.sort() + for i in range(2, size): + if arr[i] - arr[i - 1] != arr[i - 1] - arr[i - 2]: + return False + return True +``` + diff --git a/docs/solutions/1500-1599/count-good-triplets.md b/docs/solutions/1500-1599/count-good-triplets.md new file mode 100644 index 00000000..916079a4 --- /dev/null +++ b/docs/solutions/1500-1599/count-good-triplets.md @@ -0,0 +1,134 @@ +# [1534. 统计好三元组](https://leetcode.cn/problems/count-good-triplets/) + +- 标签:数组、枚举 +- 难度:简单 + +## 题目链接 + +- [1534. 统计好三元组 - 力扣](https://leetcode.cn/problems/count-good-triplets/) + +## 题目大意 + +**描述**:给定一个整数数组 $arr$,以及 $a$、$b$、$c$ 三个整数。 + +**要求**:统计其中好三元组的数量。 + +**说明**: + +- **好三元组**:如果三元组($arr[i]$、$arr[j]$、$arr[k]$)满足下列全部条件,则认为它是一个好三元组。 + - $0 \le i < j < k < arr.length$。 + - $| arr[i] - arr[j] | \le a$。 + - $| arr[j] - arr[k] | \le b$。 + - $| arr[i] - arr[k] | \le c$。 + +- $3 \le arr.length \le 100$。 +- $0 \le arr[i] \le 1000$。 +- $0 \le a, b, c \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [3,0,1,1,9,7], a = 7, b = 2, c = 3 +输出:4 +解释:一共有 4 个好三元组:[(3,0,1), (3,0,1), (3,1,1), (0,1,1)]。 +``` + +- 示例 2: + +```python +输入:arr = [1,1,2,2,3], a = 0, b = 0, c = 1 +输出:0 +解释:不存在满足所有条件的三元组。 +``` + +## 解题思路 + +### 思路 1:枚举 + +- 使用三重循环依次枚举所有的 $(i, j, k)$,判断对应 $arr[i]$、$arr[j]$、$arr[k]$ 是否满足条件。 +- 然后统计出所有满足条件的三元组的数量。 + +### 思路 1:代码 + +```python +class Solution: + def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int: + size = len(arr) + ans = 0 + + for i in range(size): + for j in range(i + 1, size): + for k in range(j + 1, size): + if abs(arr[i] - arr[j]) <= a and abs(arr[j] - arr[k]) <= b and abs(arr[i] - arr[k]) <= c: + ans += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3)$,其中 $n$ 是数组 $arr$ 的长度。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:枚举优化 + 前缀和 + +我们可以先通过二重循环遍历二元组 $(j, k)$,找出所有满足 $| arr[j] - arr[k] | \le b$ 的二元组。 + +然后在 $| arr[j] - arr[k] | \le b$ 的条件下,我们需要找到满足以下要求的 $arr[i]$ 数量: + +1. $i < j$。 +2. $| arr[i] - arr[j] | \le a$。 +3. $| arr[i] - arr[k] | \le c$。 +4. $0 \le arr[i] \le 1000$。 + +其中 $2$、$3$ 去除绝对值之后可变为: + +1. $arr[j] - a \le arr[i] \le arr[j] + a$。 +2. $arr[k] - c \le arr[i] \le arr[k] + c$。 + +将这两个条件再结合第 $4$ 个条件综合一下就变为:$max(0, arr[j] - a, arr[k] - c) \le arr[i] \le min(arr[j] + a, arr[k] + c, 1000)$。 + +假如定义 $left = max(0, arr[j] - a, arr[k] - c)$,$right = min(arr[j] + a, arr[k] + c, 1000)$。 + +现在问题就转变了如何快速获取在值域区间 $[left, right]$ 中,有多少个 $arr[i]$。 + +我们可以利用前缀和数组,先计算出 $[0, 1000]$ 范围中,满足 $arr[i] < num$ 的元素个数,即为 $prefix\underline{\hspace{0.5em}}cnts[num]$。 + +然后对于区间 $[left, right]$,通过 $prefix\underline{\hspace{0.5em}}cnts[right] - prefix\underline{\hspace{0.5em}}cnts[left - 1]$ 即可快速求解出区间 $[left, right]$ 内 $arr[i]$ 的个数。 + +因为 $i < j < k$,所以我们可以在每次 $j$ 向右移动一位的时候,更新 $arr[j]$ 对应的前缀和数组,保证枚举到 $j$ 时,$prefix\underline{\hspace{0.5em}}cnts$ 存储对应元素值的个数足够正确。 + +### 思路 2:代码 + +```python +class Solution: + def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int: + size = len(arr) + ans = 0 + prefix_cnts = [0 for _ in range(1010)] + + for j in range(size): + for k in range(j + 1, size): + if abs(arr[j] - arr[k]) <= b: + left_j, right_j = arr[j] - a, arr[j] + a + left_k, right_k = arr[k] - c, arr[k] + c + left, right = max(0, left_j, left_k), min(1000, right_j, right_k) + if left <= right: + if left == 0: + ans += prefix_cnts[right] + else: + ans += prefix_cnts[right] - prefix_cnts[left - 1] + + for k in range(arr[j], 1001): + prefix_cnts[k] += 1 + + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2 + n \times S)$,其中 $n$ 是数组 $arr$ 的长度,$S$ 为数组的值域上限。 +- **空间复杂度**:$O(S)$。 + diff --git a/docs/solutions/1500-1599/count-odd-numbers-in-an-interval-range.md b/docs/solutions/1500-1599/count-odd-numbers-in-an-interval-range.md new file mode 100644 index 00000000..fb976331 --- /dev/null +++ b/docs/solutions/1500-1599/count-odd-numbers-in-an-interval-range.md @@ -0,0 +1,50 @@ +# [1523. 在区间范围内统计奇数数目](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [1523. 在区间范围内统计奇数数目 - 力扣](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) + +## 题目大意 + +**描述**:给定两个非负整数 `low` 和 `high`。 + +**要求**:返回 `low` 与 `high` 之间(包括二者)的奇数数目。 + +**说明**: + +- $0 \le low \le high \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:low = 3, high = 7 +输出:3 +解释:3 到 7 之间奇数数字为 [3,5,7] +``` + +## 解题思路 + +### 思路 1: + +暴力枚举 `[low, high]` 之间的奇数可能会超时。我们可以通过公式直接计算出 `[0, low - 1]` 之间的奇数个数和 `[0, high]` 之间的奇数个数,然后将两者相减即为答案。 + +计算奇数个数的公式为:$pre(x) = \lfloor \frac{x + 1}{2} \rfloor$。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def pre(self, val): + return (val + 1) >> 1 + + def countOdds(self, low: int, high: int) -> int: + return self.pre(high) - self.pre(low - 1) +``` + diff --git a/docs/solutions/1500-1599/index.md b/docs/solutions/1500-1599/index.md new file mode 100644 index 00000000..e1361df0 --- /dev/null +++ b/docs/solutions/1500-1599/index.md @@ -0,0 +1,15 @@ +## 本章内容 + +- [1502. 判断能否形成等差数列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/can-make-arithmetic-progression-from-sequence.md) +- [1507. 转变日期格式](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/reformat-date.md) +- [1523. 在区间范围内统计奇数数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/count-odd-numbers-in-an-interval-range.md) +- [1534. 统计好三元组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/count-good-triplets.md) +- [1547. 切棍子的最小成本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md) +- [1551. 使数组中所有元素相等的最小操作数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-operations-to-make-array-equal.md) +- [1556. 千位分隔数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/thousand-separator.md) +- [1561. 你可以获得的最大硬币数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/maximum-number-of-coins-you-can-get.md) +- [1567. 乘积为正数的最长子数组长度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/maximum-length-of-subarray-with-positive-product.md) +- [1582. 二进制矩阵中的特殊位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/special-positions-in-a-binary-matrix.md) +- [1584. 连接所有点的最小费用](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/min-cost-to-connect-all-points.md) +- [1593. 拆分字符串使唯一子字符串的数目最大](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md) +- [1595. 连通两组点的最小成本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md) diff --git a/docs/solutions/1500-1599/maximum-length-of-subarray-with-positive-product.md b/docs/solutions/1500-1599/maximum-length-of-subarray-with-positive-product.md new file mode 100644 index 00000000..fd7d0ae1 --- /dev/null +++ b/docs/solutions/1500-1599/maximum-length-of-subarray-with-positive-product.md @@ -0,0 +1,71 @@ +# [1567. 乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) + +- 标签:贪心、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1567. 乘积为正数的最长子数组长度 - 力扣](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) + +## 题目大意 + +给定一个整数数组 `nums`。 + +要求:求出乘积为正数的最长子数组的长度。 + +- 子数组:是由原数组中零个或者更多个连续数字组成的数组。 + +## 解题思路 + +使用动态规划来做。使用数组 `pos` 表示以下标 `i` 结尾的乘积为正数的最长子数组长度。使用数组 `neg` 表示以下标 `i` 结尾的乘积为负数的最长子数组长度。 + +- 先初始化 `pos[0]`、`neg[0]`。 + - 如果 `nums[0] == 0`,则 `pos[0] = 0, neg[0] = 0`。 + - 如果 `nums[0] > 0`,则 `pos[0] = 1, neg[0] = 0`。 + - 如果 `nums[0] < 0`,则 `pos[0] = 0, neg[0] = 1`。 + +- 然后从下标 `1` 开始递推遍历数组 `nums`,对于 `nums[i - 1]` 和 `nums[i]`: + + - 如果 `nums[i - 1] == 0`,显然有 `pos[i] = 0`,`neg[i] = 0`。表示:以`i` 结尾的乘积为正数的最长子数组长度为 `0`,以`i` 结尾的乘积为负数数的最长子数组长度也为 `0`。 + + - 如果 `nums[i - 1] > 0`,则 `pos[i] = pos[i - 1] + 1`。而 `neg[i]` 需要进行判断,如果 `neg[i - 1] > 0`,则再乘以当前 `nums[i]` 后仍为负数,此时长度 +1,即 `neg[i] = neg[i - 1] + 1 `。而如果 `neg[i - 1] == 0`,则 `neg[i] = 0`。 + + - 如果 `nums[i - 1] < 0`,则 `pos[i]` 需要进行判断,如果 `neg[i - 1] > 0`,再乘以当前 `nums[i]` 后变为正数,此时长度 +1,即 `pos[i] = neg[i - 1] + 1`。而如果 `neg[i - 1] = 0`,则 `pos[i] = 0`。 + - 更新 `ans` 答案为 `pos[i]` 最大值。 + +- 最后输出答案 `ans`。 + +## 代码 + +```python +class Solution: + def getMaxLen(self, nums: List[int]) -> int: + size = len(nums) + pos = [0 for _ in range(size + 1)] + neg = [0 for _ in range(size + 1)] + + if nums[0] == 0: + pos[0], neg[0] = 0, 0 + elif nums[0] > 0: + pos[0], neg[0] = 1, 0 + else: + pos[0], neg[0] = 0, 1 + + ans = pos[0] + for i in range(1, size): + if nums[i] == 0: + pos[i] = 0 + neg[i] = 0 + elif nums[i] > 0: + pos[i] = pos[i - 1] + 1 + neg[i] = neg[i - 1] + 1 if neg[i - 1] > 0 else 0 + elif nums[i] < 0: + pos[i] = neg[i - 1] + 1 if neg[i - 1] > 0 else 0 + neg[i] = pos[i - 1] + 1 + ans = max(ans, pos[i]) + return ans +``` + +## 参考资料 + +- 【题解】[递推就完事了,巨好理解~ - 乘积为正数的最长子数组长度 - 力扣](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/solution/di-tui-jiu-wan-shi-liao-ju-hao-li-jie-by-time-limi/) diff --git a/docs/solutions/1500-1599/maximum-number-of-coins-you-can-get.md b/docs/solutions/1500-1599/maximum-number-of-coins-you-can-get.md new file mode 100644 index 00000000..e2c8c5d7 --- /dev/null +++ b/docs/solutions/1500-1599/maximum-number-of-coins-you-can-get.md @@ -0,0 +1,45 @@ +# [1561. 你可以获得的最大硬币数目](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/) + +- 标签:贪心、数组、数学、博弈、排序 +- 难度:中等 + +## 题目链接 + +- [1561. 你可以获得的最大硬币数目 - 力扣](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get/) + +## 题目大意 + +有 `3*n` 堆数目不一的硬币,三个人按照下面的规则分硬币: + +- 每一轮选出任意 3 堆硬币。 +- Alice 拿走硬币数量最多的那一堆。 +- 我们自己拿走硬币数量第二多的那一堆。 +- Bob 拿走最后一堆。 +- 重复这个过程,直到没有更多硬币。 + +现在给定一个整数数组 `piles`,代表 `3*n` 堆硬币,其中 `piles[i]` 表示第 `i` 堆中硬币的数目。 + +## 解题思路 + +每次 `3` 堆,总共取 `n` 次。Bob 每次总是选择最少的一堆,所以最终 Bob 得到 `3*n` 堆中最少的 `n` 堆才能使得另外两个人获得更多。所以先对硬币堆进行排序。Bob 拿走最少的 `n` 堆。我们接着分剩下的 `2*n` 堆。 + +按照大小顺序,每次都选取硬币数目最多的两堆, Alice 取得较大的一堆,我们取较小的一堆。 + +然后继续在剩余堆中选取硬币数目最多的两堆,同样 Alice 取得较大的一堆,我们取较小的一堆。 + +只有这样才能在满足规则的情况下,使我们所获得硬币数最多。 + +最后统计我们所获取的硬币数,并返回结果。 + +## 代码 + +```python +class Solution: + def maxCoins(self, piles: List[int]) -> int: + piles.sort() + ans = 0 + for i in range(len(piles) // 3, len(piles), 2): + ans += piles[i] + return ans +``` + diff --git a/docs/solutions/1500-1599/min-cost-to-connect-all-points.md b/docs/solutions/1500-1599/min-cost-to-connect-all-points.md new file mode 100644 index 00000000..22e51e30 --- /dev/null +++ b/docs/solutions/1500-1599/min-cost-to-connect-all-points.md @@ -0,0 +1,171 @@ +# [1584. 连接所有点的最小费用](https://leetcode.cn/problems/min-cost-to-connect-all-points/) + +- 标签:并查集、图、数组、最小生成树 +- 难度:中等 + +## 题目链接 + +- [1584. 连接所有点的最小费用 - 力扣](https://leetcode.cn/problems/min-cost-to-connect-all-points/) + +## 题目大意 + +**描述**:给定一个 $points$ 数组,表示 2D 平面上的一些点,其中 $points[i] = [x_i, y_i]$。 + +链接点 $[x_i, y_i]$ 和点 $[x_j, y_j]$ 的费用为它们之间的 **曼哈顿距离**:$|x_i - x_j| + |y_i - y_j|$。其中 $|val|$ 表示 $val$ 的绝对值。 + +**要求**:返回将所有点连接的最小总费用。 + +**说明**: + +- 只有任意两点之间有且仅有一条简单路径时,才认为所有点都已连接。 +- $1 \le points.length \le 1000$。 +- $-10^6 \le x_i, y_i \le 10^6$。 +- 所有点 $(x_i, y_i)$ 两两不同。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/08/26/d.png) + +![](https://assets.leetcode.com/uploads/2020/08/26/c.png) + +```python +输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]] +输出:20 +解释:我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。 +注意到任意两个点之间只有唯一一条路径互相到达。 +``` + +- 示例 2: + +```python +输入:points = [[3,12],[-2,5],[-4,1]] +输出:18 +``` + +## 解题思路 + +将所有点之间的费用看作是边,则所有点和边可以看作是一个无向图。每两个点之间都存在一条无向边,边的权重为两个点之间的曼哈顿距离。将所有点连接的最小总费用,其实就是求无向图的最小生成树。对此我们可以使用 Prim 算法或者 Kruskal 算法。 + +### 思路 1:Prim 算法 + +每次选择最短边来扩展最小生成树,从而保证生成树的总权重最小。算法通过不断扩展小生成树的顶点集合 $MST$,逐步构建出最小生成树。 + +### 思路 1:代码 + +```Python +class Solution: + def distance(self, point1, point2): + return abs(point1[0] - point2[0]) + abs(point1[1] - point2[1]) + + def Prim(self, points, start): + size = len(points) + vis = set() + dis = [float('inf') for _ in range(size)] + + ans = 0 # 最小生成树的边权值 + dis[start] = 0 # 起始位置到起始位置的边权值初始化为 0 + + for i in range(1, size): + dis[i] = self.distance(points[start], points[i]) + vis.add(start) + + for _ in range(size - 1): # 进行 n 轮迭代 + min_dis = float('inf') + min_dis_i = -1 + for i in range(size): + if i not in vis and dis[i] < min_dis: + min_dis = dis[i] + min_dis_i = i + if min_dis_i == -1: + return -1 + + ans += min_dis + vis.add(min_dis_i) + + + for i in range(size): + if i not in vis: + dis[i] = min(dis[i], self.distance(points[i], points[min_dis_i])) + + return ans + + def minCostConnectPoints(self, points: List[List[int]]) -> int: + return self.Prim(points, 0) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n^2)$。 + +### 思路 2:Kruskal 算法 + +通过依次选择权重最小的边并判断其两个端点是否连接在同一集合中,从而逐步构建最小生成树。这个过程保证了最终生成的树是无环的,并且总权重最小。 + +### 思路 2:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + + +class Solution: + def Kruskal(self, edges, size): + union_find = UnionFind(size) + + edges.sort(key=lambda x: x[2]) + + ans, cnt = 0, 0 + for x, y, dist in edges: + if union_find.is_connected(x, y): + continue + ans += dist + cnt += 1 + union_find.union(x, y) + if cnt == size - 1: + return ans + return ans + + def minCostConnectPoints(self, points: List[List[int]]) -> int: + size = len(points) + edges = [] + for i in range(size): + xi, yi = points[i] + for j in range(i + 1, size): + xj, yj = points[j] + dist = abs(xi - xj) + abs(yi - yj) + edges.append([i, j, dist]) + + ans = self.Kruskal(edges, size) + return ans + +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(m \times \log(n))$。其中 $m$ 为边数,$n$ 为节点数,本题中 $m = n^2$。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md b/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md new file mode 100644 index 00000000..13e97a00 --- /dev/null +++ b/docs/solutions/1500-1599/minimum-cost-to-connect-two-groups-of-points.md @@ -0,0 +1,84 @@ +# [1595. 连通两组点的最小成本](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) + +- 标签:位运算、数组、动态规划、状态压缩、矩阵 +- 难度:困难 + +## 题目链接 + +- [1595. 连通两组点的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-connect-two-groups-of-points/) + +## 题目大意 + +**描述**:有两组点,其中一组中有 $size_1$ 个点,第二组中有 $size_2$ 个点,且 $size_1 \ge size_2$。现在给定一个大小为 $size_1 \times size_2$ 的二维数组 $cost$ 用于表示两组点任意两点之间的链接成本。其中 $cost[i][j]$ 表示第一组中第 $i$ 个点与第二组中第 $j$ 个点的链接成本。 + +如果两个组中每个点都与另一个组中的一个或多个点连接,则称这两组点是连通的。 + +**要求**:返回连通两组点所需的最小成本。 + +**说明**: + +- $size_1 == cost.length$。 +- $size_2 == cost[i].length$。 +- $1 \le size_1, size_2 \le 12$。 +- $size_1 \ge size_2$。 +- $0 \le cost[i][j] \le 100$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/09/20/ex1.jpg) + +```python +输入:cost = [[15, 96], [36, 2]] +输出:17 +解释:连通两组点的最佳方法是: +1--A +2--B +总成本为 17。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/09/20/ex2.jpg) + +```python +输入:cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]] +输出:4 +解释:连通两组点的最佳方法是: +1--A +2--B +2--C +3--A +最小成本为 4。 +请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。 +``` + +## 解题思路 + +### 思路 1:状压 DP + + + +### 思路 1:代码 + +```python +class Solution: + def connectTwoGroups(self, cost: List[List[int]]) -> int: + m, n = len(cost), len(cost[0]) + states = 1 << n + dp = [[float('inf') for _ in range(states)] for _ in range(m + 1)] + dp[0][0] = 0 + for i in range(1, m + 1): + for state in range(states): + for j in range(n): + dp[i][state | (1 << j)] = min(dp[i][state | (1 << j)], dp[i - 1][state] + cost[i - 1][j], dp[i][state] + cost[i - 1][j]) + + return dp[m][states - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**: +- **空间复杂度**: + diff --git a/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md b/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md new file mode 100644 index 00000000..cba7056e --- /dev/null +++ b/docs/solutions/1500-1599/minimum-cost-to-cut-a-stick.md @@ -0,0 +1,114 @@ +# [1547. 切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) + +- 标签:数组、动态规划、排序 +- 难度:困难 + +## 题目链接 + +- [1547. 切棍子的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) + +## 题目大意 + +**描述**:给定一个整数 $n$,代表一根长度为 $n$ 个单位的木根,木棍从 $0 \sim n$ 标记了若干位置。例如,长度为 $6$ 的棍子可以标记如下: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg) + +再给定一个整数数组 $cuts$,其中 $cuts[i]$ 表示需要将棍子切开的位置。 + +我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 + +每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 + +**要求**:返回切棍子的最小总成本。 + +**说明**: + +- $2 \le n \le 10^6$。 +- $1 \le cuts.length \le min(n - 1, 100)$。 +- $1 \le cuts[i] \le n - 1$。 +- $cuts$ 数组中的所有整数都互不相同。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e1.jpg) + +```python +输入:n = 7, cuts = [1,3,4,5] +输出:16 +解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 +第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。 +``` + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e11.jpg) + +- 示例 2: + +```python +输入:n = 9, cuts = [5,6,1,4,2] +输出:22 +解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +我们可以预先在数组 $cuts$ 种添加位置 $0$ 和位置 $n$,然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 + +###### 3. 状态转移方程 + +假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k$,则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 + +而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 + +则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- 相邻位置之间没有切割点,不需要切割,最小成本为 $0$,即 $dp[i - 1][i] = 0$。 +- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 + +```python +class Solution: + def minCost(self, n: int, cuts: List[int]) -> int: + cuts.append(0) + cuts.append(n) + cuts.sort() + + size = len(cuts) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for i in range(1, size): + dp[i - 1][i] = 0 + + for l in range(3, size + 1): # 枚举区间长度 + for i in range(size): # 枚举区间起点 + j = i + l - 1 # 根据起点和长度得到终点 + if j >= size: + continue + dp[i][j] = float('inf') + for k in range(i + 1, j): # 枚举区间分割点 + # 状态转移方程,计算合并区间后的最优值 + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m^3)$,其中 $m$ 为数组 $cuts$ 的元素个数。 +- **空间复杂度**:$O(m^2)$。 diff --git a/docs/solutions/1500-1599/minimum-operations-to-make-array-equal.md b/docs/solutions/1500-1599/minimum-operations-to-make-array-equal.md new file mode 100644 index 00000000..55419448 --- /dev/null +++ b/docs/solutions/1500-1599/minimum-operations-to-make-array-equal.md @@ -0,0 +1,85 @@ +# [1551. 使数组中所有元素相等的最小操作数](https://leetcode.cn/problems/minimum-operations-to-make-array-equal/) + +- 标签:数学 +- 难度:中等 + +## 题目链接 + +- [1551. 使数组中所有元素相等的最小操作数 - 力扣](https://leetcode.cn/problems/minimum-operations-to-make-array-equal/) + +## 题目大意 + +**描述**:存在一个长度为 $n$ 的数组 $arr$,其中 $arr[i] = (2 \times i) + 1$,$(0 \le i < n)$。 + +在一次操作中,我们可以选出两个下标,记作 $x$ 和 $y$($0 \le x, y < n$),并使 $arr[x]$ 减去 $1$,$arr[y]$ 加上 $1$)。最终目标是使数组中所有元素都相等。 + +现在给定一个整数 $n$,即数组 $arr$ 的长度。 + +**要求**:返回使数组 $arr$ 中所有元素相等所需要的最小操作数。 + +**说明**: + +- 题目测试用例将会保证:在执行若干步操作后,数组中的所有元素最终可以全部相等。 +- $1 \le n \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 3 +输出:2 +解释:arr = [1, 3, 5] +第一次操作选出 x = 2 和 y = 0,使数组变为 [2, 3, 4] +第二次操作继续选出 x = 2 和 y = 0,数组将会变成 [3, 3, 3] +``` + +- 示例 2: + +```python +输入:n = 6 +输出:9 +``` + +## 解题思路 + +### 思路 1:贪心 + +通过观察可以发现,数组中所有元素构成了一个等差数列,为了使所有元素相等,在每一次操作中,尽可能让较小值增大,让较大值减小,直到到达平均值为止,这样才能得到最小操作次数。 + +在一次操作中,我们可以同时让第 $i$ 个元素增大与第 $n - 1 - i$ 个元素减小。这样,我们只需要统计出数组前半部分元素变化幅度即可。 + +### 思路 1:代码 + +```python +class Solution: + def minOperations(self, n: int) -> int: + ans = 0 + for i in range(n // 2): + ans += n - 1 - 2 * i + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:贪心 + 优化 + +数组前半部分元素变化幅度的计算可以看做是一个等差数列求和,所以我们可以直接根据高斯求和公式求出结果。 + +$\lbrace n - 1 + [n - 1 - 2 * (n \div 2 - 1)]\rbrace \times (n \div 2) \div 2 = n \times n \div 4$ + +### 思路 2:代码 + +```python +class Solution: + def minOperations(self, n: int) -> int: + return n * n // 4 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1500-1599/reformat-date.md b/docs/solutions/1500-1599/reformat-date.md new file mode 100644 index 00000000..32c41dc6 --- /dev/null +++ b/docs/solutions/1500-1599/reformat-date.md @@ -0,0 +1,74 @@ +# [1507. 转变日期格式](https://leetcode.cn/problems/reformat-date/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1507. 转变日期格式 - 力扣](https://leetcode.cn/problems/reformat-date/) + +## 题目大意 + +**描述**:给定一个字符串 $date$,它的格式为 `Day Month Year` ,其中: + +- $Day$ 是集合 `{"1st", "2nd", "3rd", "4th", ..., "30th", "31st"}` 中的一个元素。 +- $Month$ 是集合 `{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}` 中的一个元素。 +- $Year$ 的范围在 $[1900, 2100]$ 之间。 + +**要求**:将字符串转变为 `YYYY-MM-DD` 的格式,其中: + +- $YYYY$ 表示 $4$ 位的年份。 +- $MM$ 表示 $2$ 位的月份。 +- $DD$ 表示 $2$ 位的天数。 + +**说明**: + +- 给定日期保证是合法的,所以不需要处理异常输入。 + +**示例**: + +- 示例 1: + +```python +输入:date = "20th Oct 2052" +输出:"2052-10-20" +``` + +- 示例 2: + +```python +输入:date = "6th Jun 1933" +输出:"1933-06-06" +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 将字符串分割为三部分,分别按照以下规则得到日、月、年: + 1. 日:去掉末尾两位英文字母,将其转为整型数字,并且进行补零操作,使其宽度为 $2$。 + 2. 月:使用哈希表将其映射为对应两位数字。 + 3. 年:直接赋值。 +2. 将得到的年、月、日使用 `"-"` 进行链接并返回。 + +### 思路 1:代码 + +```python +class Solution: + def reformatDate(self, date: str) -> str: + months = { + "Jan" : "01", "Feb" : "02", "Mar" : "03", "Apr" : "04", "May" : "05", "Jun" : "06", + "Jul" : "07", "Aug" : "08", "Sep" : "09", "Oct" : "10", "Nov" : "11", "Dec" : "12" + } + date_list = date.split(' ') + day = "{:0>2d}".format(int(date_list[0][: -2])) + month = months[date_list[1]] + year = date_list[2] + return year + "-" + month + "-" + day +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1500-1599/special-positions-in-a-binary-matrix.md b/docs/solutions/1500-1599/special-positions-in-a-binary-matrix.md new file mode 100644 index 00000000..b995292a --- /dev/null +++ b/docs/solutions/1500-1599/special-positions-in-a-binary-matrix.md @@ -0,0 +1,83 @@ +# [1582. 二进制矩阵中的特殊位置](https://leetcode.cn/problems/special-positions-in-a-binary-matrix/) + +- 标签:数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [1582. 二进制矩阵中的特殊位置 - 力扣](https://leetcode.cn/problems/special-positions-in-a-binary-matrix/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 的二进制矩阵 $mat$。 + +**要求**:返回矩阵 $mat$ 中特殊位置的数量。 + +**说明**: + +- **特殊位置**:如果位置 $(i, j)$ 满足 $mat[i][j] == 1$ 并且行 $i$ 与列 $j$ 中的所有其他元素都是 $0$(行和列的下标从 $0$ 开始计数),那么它被称为特殊位置。 +- $m == mat.length$。 +- $n == mat[i].length$。 +- $1 \le m, n \le 100$。 +- $mat[i][j]$ 是 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/12/23/special1.jpg) + +```python +输入:mat = [[1,0,0],[0,0,1],[1,0,0]] +输出:1 +解释:位置 (1, 2) 是一个特殊位置,因为 mat[1][2] == 1 且第 1 行和第 2 列的其他所有元素都是 0。 +``` + +- 示例 2: + +![img](https://assets.leetcode.com/uploads/2021/12/24/special-grid.jpg) + +```python +输入:mat = [[1,0,0],[0,1,0],[0,0,1]] +输出:3 +解释:位置 (0, 0),(1, 1) 和 (2, 2) 都是特殊位置。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 按照行、列遍历二位数组 $mat$。 +2. 使用数组 $row\underline{\hspace{0.5em}}cnts$、$col\underline{\hspace{0.5em}}cnts$ 分别记录每行和每列所含 $1$ 的个数。 +3. 再次按照行、列遍历二维数组 $mat$。 +4. 统计满足 $mat[row][col] == 1$ 并且 $row\underline{\hspace{0.5em}}cnts[row] == col\underline{\hspace{0.5em}}cnts[col] == 1$ 的位置个数。 +5. 返回答案。 + +### 思路 1:代码 + +```Python +class Solution: + def numSpecial(self, mat: List[List[int]]) -> int: + rows, cols = len(mat), len(mat[0]) + row_cnts = [0 for _ in range(rows)] + col_cnts = [0 for _ in range(cols)] + + for row in range(rows): + for col in range(cols): + row_cnts[row] += mat[row][col] + col_cnts[col] += mat[row][col] + + ans = 0 + for row in range(rows): + for col in range(cols): + if mat[row][col] == 1 and row_cnts[row] == 1 and col_cnts[col] == 1: + ans += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别为数组 $mat$ 的行数和列数。 +- **空间复杂度**:$O(m + n)$。 + diff --git a/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md b/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md new file mode 100644 index 00000000..e34c4b63 --- /dev/null +++ b/docs/solutions/1500-1599/split-a-string-into-the-max-number-of-unique-substrings.md @@ -0,0 +1,86 @@ +# [1593. 拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) + +- 标签:哈希表、字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [1593. 拆分字符串使唯一子字符串的数目最大 - 力扣](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。将字符串 $s$ 拆分后可以得到若干非空子字符串,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是唯一的 。 + +**要求**:拆分该字符串,并返回拆分后唯一子字符串的最大数目。 + +**说明**: + +- 子字符串是字符串中的一个连续字符序列。 +- $1 \le s.length \le 16$。 +- $s$ 仅包含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "ababccc" +输出:5 +解释:一种最大拆分方法为 ['a', 'b', 'ab', 'c', 'cc'] 。像 ['a', 'b', 'a', 'b', 'c', 'cc'] 这样拆分不满足题目要求,因为其中的 'a' 和 'b' 都出现了不止一次。 +``` + +- 示例 2: + +```python +输入:s = "aba" +输出:2 +解释:一种最大拆分方法为 ['a', 'ba']。 +``` + +## 解题思路 + +### 思路 1:回溯算法 + +维护一个全局变量 $ans$ 用于记录拆分后唯一子字符串的最大数目。并使用集合 $s\underline{\hspace{0.5em}}set$ 记录不重复的子串。 + +- 从下标为 $0$ 开头的子串回溯。 +- 对于下标为 $index$ 开头的子串,我们可以在 $index + 1$ 开始到 $len(s) - 1$ 的位置上,分别进行子串拆分,将子串拆分为 $s[index: i + 1]$。 + +- 如果当前子串不在 $s\underline{\hspace{0.5em}}set$ 中,则将其存入 $s\underline{\hspace{0.5em}}set$ 中,然后记录当前拆分子串个数,并从 $i + 1$ 的位置进行下一层递归拆分。然后在拆分完,对子串进行回退操作。 +- 如果拆到字符串 $s$ 的末尾,则记录并更新 $ans$。 +- 在开始位置还可以进行以下剪枝:如果剩余字符个数 + 当前子串个数 <= 当前拆分后子字符串的最大数目,则直接返回。 + +最后输出 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + ans = 0 + def backtrack(self, s, index, count, s_set): + if len(s) - index + count <= self.ans: + return + if index >= len(s): + self.ans = max(self.ans, count) + return + + for i in range(index, len(s)): + sub_s = s[index: i + 1] + if sub_s not in s_set: + s_set.add(sub_s) + self.backtrack(s, i + 1, count + 1, s_set) + s_set.remove(sub_s) + + + def maxUniqueSplit(self, s: str) -> int: + s_set = set() + self.ans = 0 + self.backtrack(s, 0, 0, s_set) + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为字符串的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1500-1599/thousand-separator.md b/docs/solutions/1500-1599/thousand-separator.md new file mode 100644 index 00000000..fa7c57d1 --- /dev/null +++ b/docs/solutions/1500-1599/thousand-separator.md @@ -0,0 +1,67 @@ +# [1556. 千位分隔数](https://leetcode.cn/problems/thousand-separator/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1556. 千位分隔数 - 力扣](https://leetcode.cn/problems/thousand-separator/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:每隔三位田间点(即 `"."` 符号)作为千位分隔符,并将结果以字符串格式返回。 + +**说明**: + +- $0 \le n \le 2^{31}$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 987 +输出:"987" +``` + +- 示例 2: + +```python +输入:n = 123456789 +输出:"123.456.789" +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 使用字符串变量 $ans$ 用于存储答案,使用一个计数器 $idx$ 来记录当前位数的个数。 +2. 将 $n$ 转为字符串 $s$ 后,从低位向高位遍历。 +3. 将当前数字 $s[i]$ 存入 $ans$ 中,计数器加 $1$,当计数器为 $3$ 的整数倍并且当前数字位不是最高位时,将 `"."` 存入 $ans$ 中。 +4. 遍历完成后,将 $ans$ 翻转后返回。 + +### 思路 1:代码 + +```python +class Solution: + def thousandSeparator(self, n: int) -> str: + s = str(n) + ans = "" + + idx = 0 + for i in range(len(s) - 1, -1, -1): + ans += s[i] + idx += 1 + if idx % 3 == 0 and i != 0: + ans += "." + + return ''.join(reversed(ans)) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/1600-1699/count-sorted-vowel-strings.md b/docs/solutions/1600-1699/count-sorted-vowel-strings.md new file mode 100644 index 00000000..f3c8abe3 --- /dev/null +++ b/docs/solutions/1600-1699/count-sorted-vowel-strings.md @@ -0,0 +1,67 @@ +# [1641. 统计字典序元音字符串的数目](https://leetcode.cn/problems/count-sorted-vowel-strings/) + +- 标签:数学、动态规划、组合数学 +- 难度:中等 + +## 题目链接 + +- [1641. 统计字典序元音字符串的数目 - 力扣](https://leetcode.cn/problems/count-sorted-vowel-strings/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:返回长度为 $n$、仅由原音($a$、$e$、$i$、$o$、$u$)组成且按字典序排序的字符串数量。 + +**说明**: + +- 字符串 $a$ 按字典序排列需要满足:对于所有有效的 $i$,$s[i]$ 在字母表中的位置总是与 $s[i + 1]$ 相同或在 $s[i+1] $之前。 +- $1 \le n \le 50$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 1 +输出:5 +解释:仅由元音组成的 5 个字典序字符串为 ["a","e","i","o","u"] +``` + +- 示例 2: + +```python +输入:n = 2 +输出:15 +解释:仅由元音组成的 15 个字典序字符串为 +["aa","ae","ai","ao","au","ee","ei","eo","eu","ii","io","iu","oo","ou","uu"] +注意,"ea" 不是符合题意的字符串,因为 'e' 在字母表中的位置比 'a' 靠后 +``` + +## 解题思路 + +### 思路 1:组和数学 + +题目要求按照字典序排列,则如果确定了每个元音的出现次数可以确定一个序列。 + +对于长度为 $n$ 的序列,$a$、$e$、$i$、$o$、$u$ 出现次数加起来为 $n$ 次,且顺序为 $a…a \rightarrow e…e \rightarrow i…i \rightarrow o…o \rightarrow u…u$。 + +我们可以看作是将 $n$ 分隔成了 $5$ 份,每一份对应一个原音字母的数量。 + +我们可以使用「隔板法」的方式,看作有 $n$ 个球,$4$ 个板子,将 $n$ 个球分隔成 $5$ 份。 + +则一共有 $n + 4$ 个位置可以放板子,总共需要放 $4$ 个板子,则答案为 $C_{n + 4}^4$,其中 $C$ 为组和数。 + +### 思路 1:代码 + +```Python +class Solution: + def countVowelStrings(self, n: int) -> int: + return comb(n + 4, 4) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(| \sum |)$,其中 $\sum$ 为字符集,本题中 $| \sum | = 5$ 。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md b/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md new file mode 100644 index 00000000..dcf64b7b --- /dev/null +++ b/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md @@ -0,0 +1,98 @@ +# [1617. 统计子树中城市之间最大距离](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) + +- 标签:位运算、树、动态规划、状态压缩、枚举 +- 难度:困难 + +## 题目链接 + +- [1617. 统计子树中城市之间最大距离 - 力扣](https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/) + +## 题目大意 + +**描述**:给定一个整数 $n$,代表 $n$ 个城市,城市编号为 $1 \sim n$。同时给定一个大小为 $n - 1$ 的数组 $edges$,其中 $edges[i] = [u_i, v_i]$ 表示城市 $u_i$ 和 $v_i$ 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵树。 + +**要求**:返回一个大小为 $n - 1$ 的数组,其中第 $i$ 个元素(下标从 $1$ 开始)是城市间距离恰好等于 $i$ 的子树数目。 + +**说明**: + +- **两个城市间距离**:定义为它们之间需要经过的边的数目。 +- **一棵子树**:城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。 +- $2 \le n \le 15$。 +- $edges.length == n - 1$。 +- $edges[i].length == 2$。 +- $1 \le u_i, v_i \le n$。 +- 题目保证 $(ui, vi)$ 所表示的边互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4, edges = [[1,2],[2,3],[2,4]] +输出:[3,4,0] +解释: +子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。 +子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。 +不存在城市间最大距离为 3 的子树。 +``` + +- 示例 2: + +```python +输入:n = 2, edges = [[1,2]] +输出:[1] +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +因为题目中给定 $n$ 的范围为 $2 \le n \le 15$,范围比较小,我们可以通过类似「[0078. 子集](https://leetcode.cn/problems/subsets/)」中二进制枚举的方式,得到所有子树的子集。 + +而对于一个确定的子树来说,求子树中两个城市间距离就是在求子树的直径,这就跟 [「1245. 树的直径」](https://leetcode.cn/problems/tree-diameter/) 和 [「2246. 相邻字符不同的最长路径」](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) 一样了。 + +那么这道题的思路就变成了: + +1. 通过二进制枚举的方式,得到所有子树。 +2. 对于当前子树,通过树形 DP + 深度优先搜索的方式,计算出当前子树的直径。 +3. 统计所有子树直径中经过的不同边数个数,将其放入答案数组中。 + +### 思路 1:代码 + +```python +class Solution: + def countSubgraphsForEachDiameter(self, n: int, edges: List[List[int]]) -> List[int]: + graph = [[] for _ in range(n)] # 建图 + for u, v in edges: + graph[u - 1].append(v - 1) + graph[v - 1].append(u - 1) + + def dfs(mask, u): + nonlocal visited, diameter + visited |= 1 << u # 标记 u 访问过 + u_len = 0 # u 节点的最大路径长度 + for v in graph[u]: # 遍历 u 节点的相邻节点 + if (visited >> v) & 1 == 0 and mask >> v & 1: # v 没有访问过,且在子集中 + v_len = dfs(mask, v) # 相邻节点的最大路径长度 + diameter = max(diameter, u_len + v_len + 1) # 维护最大路径长度 + u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 + return u_len + + ans = [0 for _ in range(n - 1)] + + for mask in range(3, 1 << n): # 二进制枚举子集 + if mask & (mask - 1) == 0: # 子集至少需要两个点 + continue + visited = 0 + diameter = 0 + u = mask.bit_length() - 1 + dfs(mask, u) # 在子集 mask 中递归求树的直径 + if visited == mask: + ans[diameter - 1] += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 2^n)$,其中 $n$ 为给定的城市数目。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1600-1699/design-parking-system.md b/docs/solutions/1600-1699/design-parking-system.md new file mode 100644 index 00000000..d5bab91e --- /dev/null +++ b/docs/solutions/1600-1699/design-parking-system.md @@ -0,0 +1,37 @@ +# [1603. 设计停车系统](https://leetcode.cn/problems/design-parking-system/) + +- 标签:设计、计数、模拟 +- 难度:简单 + +## 题目链接 + +- [1603. 设计停车系统 - 力扣](https://leetcode.cn/problems/design-parking-system/) + +## 题目大意 + +给一个停车场设计一个停车系统。停车场总共有三种尺寸的车位:大、中、小,每种尺寸的车位分别有固定数目。 + +现在要求实现 `ParkingSystem` 类: + +- `ParkingSystem(big, medium, small)`:初始化 ParkingSystem 类,三个参数分别对应三种尺寸车位的数目。 +- `addCar(carType) -> bool:`:检测是否有 `carType` 对应的停车位,如果有,则将车停入车位,并返回 `True`,否则返回 `False`。 + +## 解题思路 + +使用不同成员变量存放车位数目。并根据给定操作进行判断。 + +## 代码 + +```python +class ParkingSystem: + + def __init__(self, big: int, medium: int, small: int): + self.park = [0, big, medium, small] + + def addCar(self, carType: int) -> bool: + if self.park[carType] == 0: + return False + self.park[carType] -= 1 + return True +``` + diff --git a/docs/solutions/1600-1699/determine-if-two-strings-are-close.md b/docs/solutions/1600-1699/determine-if-two-strings-are-close.md new file mode 100644 index 00000000..fa995021 --- /dev/null +++ b/docs/solutions/1600-1699/determine-if-two-strings-are-close.md @@ -0,0 +1,77 @@ +# [1657. 确定两个字符串是否接近](https://leetcode.cn/problems/determine-if-two-strings-are-close/) + +- 标签:哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [1657. 确定两个字符串是否接近 - 力扣](https://leetcode.cn/problems/determine-if-two-strings-are-close/) + +## 题目大意 + +**描述**:如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近 : + +- 操作 1:交换任意两个现有字符。 + - 例如,`abcde` -> `aecdb`。 +- 操作 2:将一个 现有 字符的每次出现转换为另一个现有字符,并对另一个字符执行相同的操作。 + - 例如,`aacabb` -> `bbcbaa`(所有 `a` 转化为 `b`,而所有的 `b` 转换为 `a` )。 + +给定两个字符串,$word1$ 和 $word2$。 + +**要求**:如果 $word1$ 和 $word2$ 接近 ,就返回 $True$;否则,返回 $False$。 + +**说明**: + +- $1 \le word1.length, word2.length \le 10^5$。 +- $word1$ 和 $word2$ 仅包含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:word1 = "abc", word2 = "bca" +输出:True +解释:2 次操作从 word1 获得 word2 。 +执行操作 1:"abc" -> "acb" +执行操作 1:"acb" -> "bca" +``` + +- 示例 2: + +```python +输入:word1 = "a", word2 = "aa" +输出:False +解释:不管执行多少次操作,都无法从 word1 得到 word2 ,反之亦然。 +``` + +## 解题思路 + +### 思路 1:模拟 + +无论是操作 1,还是操作 2,只是对字符位置进行交换,而不会产生或者删除字符。 + +则我们只需要检查两个字符串的字符种类以及每种字符的个数是否相同即可。 + +具体步骤如下: + +1. 分别使用哈希表 $cnts1$、$cnts2$ 统计每个字符串中的字符种类,每种字符的个数。 +2. 判断两者的字符种类是否相等,并且判断每种字符的个数是否相同。 +3. 如果字符种类相同,且每种字符的个数完全相同,则返回 $True$,否则,返回 $False$。 + +### 思路 1:代码 + +```Python +class Solution: + def closeStrings(self, word1: str, word2: str) -> bool: + cnts1 = Counter(word1) + cnts2 = Counter(word2) + + return cnts1.keys() == cnts2.keys() and sorted(cnts1.values()) == sorted(cnts2.values()) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(max(n1, n2) + |\sum| \times \log | \sum |)$,其中 $n1$、$n2$ 分别为字符串 $word1$、$word2$ 的长度,$\sum$ 为字符集,本题中 $| \sum | = 26$。 +- **空间复杂度**:$O(| \sum |)$。 + diff --git a/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md b/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md new file mode 100644 index 00000000..8892199c --- /dev/null +++ b/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md @@ -0,0 +1,67 @@ +# [1605. 给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) + +- 标签:贪心、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [1605. 给定行和列的和求可行矩阵 - 力扣](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums/) + +## 题目大意 + +**描述**:给你两个非负整数数组 `rowSum` 和 `colSum` ,其中 `rowSum[i]` 是二维矩阵中第 `i` 行元素的和,`colSum[j]` 是第 `j` 列元素的和。换句话说,我们不知道矩阵里的每个元素,只知道每一行的和,以及每一列的和。 + +**要求**:找到并返回一个大小为 `rowSum.length * colSum.length` 的任意非负整数矩阵,且该矩阵满足 `rowSum` 和 `colSum` 的要求。 + +**说明**: + +- 返回任意一个满足题目要求的二维矩阵即可,题目保证存在至少一个可行矩阵。 +- $1 \le rowSum.length, colSum.length \le 500$。 +- $0 \le rowSum[i], colSum[i] \le 10^8$。 +- $sum(rows) == sum(columns)$。 + +**示例**: + +- 示例 1: + +```python +输入:rowSum = [3,8], colSum = [4,7] +输出:[[3,0], + [1,7]] + +解释 +第 0 行:3 + 0 = 3 == rowSum[0] +第 1 行:1 + 7 = 8 == rowSum[1] +第 0 列:3 + 1 = 4 == colSum[0] +第 1 列:0 + 7 = 7 == colSum[1] +行和列的和都满足题目要求,且所有矩阵元素都是非负的。 +另一个可行的矩阵为 [[1,2], + [3,5]] +``` + +## 解题思路 + +### 思路 1:贪心算法 + +题目要求找出一个满足要求的非负整数矩阵,矩阵中元素值可以为 `0`。所以我们可以尽可能将大的值填入前面的行和列中,然后剩余位置用 `0` 补齐即可。具体做法如下: + +1. 使用二维数组 `board` 来保存答案,初始情况下,`board` 中元素全部赋值为 `0`。 +2. 遍历二维数组的每一行,每一列。当前位置下的值为当前行的和与当前列的和的较小值,即 `board[row][col] = min(rowSum[row], colSum[col])`。 +3. 更新当前行的和,将当前行的和减去 `board[row][col]`。 +4. 更新当前列的和,将当前列的和减去 `board[row][col]`。 +5. 遍历完返回二维数组 `board`。 + +### 思路 1:贪心算法代码 + +```python +class Solution: + def restoreMatrix(self, rowSum: List[int], colSum: List[int]) -> List[List[int]]: + rows, cols = len(rowSum), len(colSum) + board = [[0 for _ in range(cols)] for _ in range(rows)] + for row in range(rows): + for col in range(cols): + board[row][col] = min(rowSum[row], colSum[col]) + rowSum[row] -= board[row][col] + colSum[col] -= board[row][col] + return board +``` diff --git a/docs/solutions/1600-1699/get-maximum-in-generated-array.md b/docs/solutions/1600-1699/get-maximum-in-generated-array.md new file mode 100644 index 00000000..df12ff9c --- /dev/null +++ b/docs/solutions/1600-1699/get-maximum-in-generated-array.md @@ -0,0 +1,84 @@ +# [1646. 获取生成数组中的最大值](https://leetcode.cn/problems/get-maximum-in-generated-array/) + +- 标签:数组、动态规划、模拟 +- 难度:简单 + +## 题目链接 + +- [1646. 获取生成数组中的最大值 - 力扣](https://leetcode.cn/problems/get-maximum-in-generated-array/) + +## 题目大意 + +**描述**:给定一个整数 $n$,按照下述规则生成一个长度为 $n + 1$ 的数组 $nums$: + +- $nums[0] = 0$。 +- $nums[1] = 1$。 +- 当 $2 \le 2 \times i \le n$ 时,$nums[2 \times i] = nums[i]$。 +- 当 $2 \le 2 \times i + 1 \le n$ 时,$nums[2 \times i + 1] = nums[i] + nums[i + 1]$。 + +**要求**:返回生成数组 $nums$ 中的最大值。 + +**说明**: + +- $0 \le n \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 7 +输出:3 +解释:根据规则: + nums[0] = 0 + nums[1] = 1 + nums[(1 * 2) = 2] = nums[1] = 1 + nums[(1 * 2) + 1 = 3] = nums[1] + nums[2] = 1 + 1 = 2 + nums[(2 * 2) = 4] = nums[2] = 1 + nums[(2 * 2) + 1 = 5] = nums[2] + nums[3] = 1 + 2 = 3 + nums[(3 * 2) = 6] = nums[3] = 2 + nums[(3 * 2) + 1 = 7] = nums[3] + nums[4] = 2 + 1 = 3 +因此,nums = [0,1,1,2,1,3,2,3],最大值 3 +``` + +- 示例 2: + +```python +输入:n = 2 +输出:1 +解释:根据规则,nums[0]、nums[1] 和 nums[2] 之中的最大值是 1 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 按照题目要求,定义一个长度为 $n + 1$ 的数组 $nums$。 +2. 按照规则模拟生成对应的 $nums$ 数组元素。 +3. 求出数组 $nums$ 中最大值,并作为答案返回。 + +### 思路 1:代码 + +```python +class Solution: + def getMaximumGenerated(self, n: int) -> int: + if n <= 1: + return n + + nums = [0 for _ in range(n + 1)] + nums[1] = 1 + + for i in range(n): + if 2 * i <= n: + nums[2 * i] = nums[i] + if 2 * i + 1 <= n: + nums[2 * i + 1] = nums[i] + nums[i + 1] + + ans = max(nums) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1600-1699/index.md b/docs/solutions/1600-1699/index.md new file mode 100644 index 00000000..88784af4 --- /dev/null +++ b/docs/solutions/1600-1699/index.md @@ -0,0 +1,15 @@ +## 本章内容 + +- [1603. 设计停车系统](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/design-parking-system.md) +- [1605. 给定行和列的和求可行矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/find-valid-matrix-given-row-and-column-sums.md) +- [1614. 括号的最大嵌套深度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-nesting-depth-of-the-parentheses.md) +- [1617. 统计子树中城市之间最大距离](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-subtrees-with-max-distance-between-cities.md) +- [1631. 最小体力消耗路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/path-with-minimum-effort.md) +- [1641. 统计字典序元音字符串的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/count-sorted-vowel-strings.md) +- [1646. 获取生成数组中的最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/get-maximum-in-generated-array.md) +- [1647. 字符频次唯一的最小删除次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique.md) +- [1657. 确定两个字符串是否接近](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/determine-if-two-strings-are-close.md) +- [1658. 将 x 减到 0 的最小操作数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md) +- [1672. 最富有客户的资产总量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/richest-customer-wealth.md) +- [1695. 删除子数组的最大得分](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/maximum-erasure-value.md) +- [1698. 字符串的不同子字符串个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/number-of-distinct-substrings-in-a-string.md) diff --git a/docs/solutions/1600-1699/maximum-erasure-value.md b/docs/solutions/1600-1699/maximum-erasure-value.md new file mode 100644 index 00000000..90ef3bf5 --- /dev/null +++ b/docs/solutions/1600-1699/maximum-erasure-value.md @@ -0,0 +1,83 @@ +# [1695. 删除子数组的最大得分](https://leetcode.cn/problems/maximum-erasure-value/) + +- 标签:数组、哈希表、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1695. 删除子数组的最大得分 - 力扣](https://leetcode.cn/problems/maximum-erasure-value/) + +## 题目大意 + +**描述**:给定一个正整数数组 $nums$,从中删除一个含有若干不同元素的子数组。删除子数组的「得分」就是子数组各元素之和 。 + +**要求**:返回只删除一个子数组可获得的最大得分。 + +**说明**: + +- **子数组**:如果数组 $b$ 是数组 $a$ 的一个连续子序列,即如果它等于 $a[l],a[l+1],...,a[r]$ ,那么它就是 $a$ 的一个子数组。 +- $1 \le nums.length \le 10^5$。 +- $1 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [4,2,4,5,6] +输出:17 +解释:最优子数组是 [2,4,5,6] +``` + +- 示例 2: + +```python +输入:nums = [5,2,1,2,5,2,1,2,5] +输出:8 +解释:最优子数组是 [5,2,1] 或 [1,2,5] +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +题目要求的是含有不同元素的连续子数组最大和,我们可以用滑动窗口来做,维护一个不包含重复元素的滑动窗口,计算最大的窗口和。具体方法如下: + +- 用滑动窗口 $window$ 来记录不重复的元素个数,$window$ 为哈希表类型。用 $window\underline{\hspace{0.5em}}sum$ 来记录窗口内子数组元素和,$ans$ 用来维护最大子数组和。设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复元素。 + +- 一开始,$left$、$right$ 都指向 $0$。 +- 将最右侧数组元素 $nums[right]$ 加入当前窗口 $window$ 中,记录该元素个数。 +- 如果该窗口中该元素的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应元素的个数,直到 $window[s[right]] \le 1$。 +- 维护更新无重复元素的最大子数组和。然后右移 $right$,直到 $right \ge len(nums)$ 结束。 +- 输出无重复元素的最大子数组和。 + +### 思路 1:代码 + +```python +class Solution: + def maximumUniqueSubarray(self, nums: List[int]) -> int: + window_sum = 0 + left, right = 0, 0 + window = dict() + ans = 0 + while right < len(nums): + window_sum += nums[right] + if nums[right] not in window: + window[nums[right]] = 1 + else: + window[nums[right]] += 1 + + while window[nums[right]] > 1: + window[nums[left]] -= 1 + window_sum -= nums[left] + left += 1 + ans = max(ans, window_sum) + right += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1600-1699/maximum-nesting-depth-of-the-parentheses.md b/docs/solutions/1600-1699/maximum-nesting-depth-of-the-parentheses.md new file mode 100644 index 00000000..d8de88d8 --- /dev/null +++ b/docs/solutions/1600-1699/maximum-nesting-depth-of-the-parentheses.md @@ -0,0 +1,83 @@ +# [1614. 括号的最大嵌套深度](https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/) + +- 标签:栈、字符串 +- 难度:简单 + +## 题目链接 + +- [1614. 括号的最大嵌套深度 - 力扣](https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/) + +## 题目大意 + +**描述**:给你一个有效括号字符串 $s$。 + +**要求**:返回该字符串 $s$ 的嵌套深度 。 + +**说明**: + +- 如果字符串满足以下条件之一,则可以称之为 有效括号字符串(valid parentheses string,可以简写为 VPS): + - 字符串是一个空字符串 `""`,或者是一个不为 `"("` 或 `")"` 的单字符。 + - 字符串可以写为 $AB$($A$ 与 B 字符串连接),其中 $A$ 和 $B$ 都是有效括号字符串 。 + - 字符串可以写为 ($A$),其中 $A$ 是一个有效括号字符串。 + +- 类似地,可以定义任何有效括号字符串 $s$ 的 嵌套深度 $depth(s)$: + + - `depth("") = 0`。 + - `depth(C) = 0`,其中 $C$ 是单个字符的字符串,且该字符不是 `"("` 或者 `")"`。 + - `depth(A + B) = max(depth(A), depth(B))`,其中 $A$ 和 $B$ 都是 有效括号字符串。 + - `depth("(" + A + ")") = 1 + depth(A)`,其中 A 是一个 有效括号字符串。 +- $1 \le s.length \le 100$。 +- $s$ 由数字 $0 \sim 9$ 和字符 `'+'`、`'-'`、`'*'`、`'/'`、`'('`、`')'` 组成。 +- 题目数据保证括号表达式 $s$ 是有效的括号表达式。 + +**示例**: + +- 示例 1: + +```python +输入:s = "(1+(2*3)+((8)/4))+1" +输出:3 +解释:数字 8 在嵌套的 3 层括号中。 +``` + +- 示例 2: + +```python +输入:s = "(1)+((2))+(((3)))" +输出:3 +``` + +## 解题思路 + +### 思路 1:模拟 + +我们可以使用栈来进行模拟括号匹配。遍历字符串 $s$,如果遇到左括号,则将其入栈,如果遇到右括号,则弹出栈中的左括号,与当前右括号进行匹配。在整个过程中栈的大小的最大值,就是我们要求的 $s$ 的嵌套深度,其实也是求最大的连续左括号的数量(跳过普通字符,并且与右括号匹配后)。具体步骤如下: + +1. 使用 $ans$ 记录最大的连续左括号数量,使用 $cnt$ 记录当前栈中左括号的数量。 +2. 遍历字符串 $s$: + 1. 如果遇到左括号,则令 $cnt$ 加 $1$。 + 2. 如果遇到右括号,则令 $cnt$ 减 $1$。 + 3. 将 $cnt$ 与答案进行比较,更新最大的连续左括号数量。 +3. 遍历完字符串 $s$,返回答案 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def maxDepth(self, s: str) -> int: + ans, cnt = 0, 0 + for ch in s: + if ch == '(': + cnt += 1 + elif ch == ')': + cnt -= 1 + ans = max(ans, cnt) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique.md b/docs/solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique.md new file mode 100644 index 00000000..a83e74c3 --- /dev/null +++ b/docs/solutions/1600-1699/minimum-deletions-to-make-character-frequencies-unique.md @@ -0,0 +1,75 @@ +# [1647. 字符频次唯一的最小删除次数](https://leetcode.cn/problems/minimum-deletions-to-make-character-frequencies-unique/) + +- 标签:贪心、哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [1647. 字符频次唯一的最小删除次数 - 力扣](https://leetcode.cn/problems/minimum-deletions-to-make-character-frequencies-unique/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。 + +**要求**:返回使 $s$ 成为优质字符串需要删除的最小字符数。 + +**说明**: + +- **频次**:指的是该字符在字符串中的出现次数。例如,在字符串 `"aab"` 中,`'a'` 的频次是 $2$,而 `'b'` 的频次是 $1$。 +- **优质字符串**:如果字符串 $s$ 中不存在两个不同字符频次相同的情况,就称 $s$ 是优质字符串。 +- $1 \le s.length \le 10^5$。 +- $s$ 仅含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "aab" +输出:0 +解释:s 已经是优质字符串。 +``` + +- 示例 2: + +```python +输入:s = "aaabbbcc" +输出:2 +解释:可以删除两个 'b' , 得到优质字符串 "aaabcc" 。 +另一种方式是删除一个 'b' 和一个 'c' ,得到优质字符串 "aaabbc"。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + 哈希表 + +1. 使用哈希表 $cnts$ 统计每字符串中每个字符出现次数。 +2. 然后使用集合 $s\underline{\hspace{0.5em}}set$ 保存不同的出现次数。 +3. 遍历哈希表中所偶出现次数: + 1. 如果当前出现次数不在集合 $s\underline{\hspace{0.5em}}set$ 中,则将该次数添加到集合 $s\underline{\hspace{0.5em}}set$ 中。 + 2. 如果当前出现次数在集合 $s\underline{\hspace{0.5em}}set$ 中,则不断减少该次数,直到该次数不在集合 $s\underline{\hspace{0.5em}}set$ 中停止,将次数添加到集合 $s\underline{\hspace{0.5em}}set$ 中,同时将减少次数累加到答案 $ans$ 中。 +4. 遍历完哈希表后返回答案 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def minDeletions(self, s: str) -> int: + cnts = Counter(s) + s_set = set() + + ans = 0 + for key, value in cnts.items(): + while value > 0 and value in s_set: + value -= 1 + ans += 1 + s_set.add(value) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md b/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md new file mode 100644 index 00000000..852daf04 --- /dev/null +++ b/docs/solutions/1600-1699/minimum-operations-to-reduce-x-to-zero.md @@ -0,0 +1,87 @@ +# [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) + +- 标签:数组、哈希表、二分查找、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [1658. 将 x 减到 0 的最小操作数 - 力扣](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$ 和一个整数 $x$ 。每一次操作时,你应当移除数组 $nums$ 最左边或最右边的元素,然后从 $x$ 中减去该元素的值。请注意,需要修改数组以供接下来的操作使用。 + +**要求**:如果可以将 $x$ 恰好减到 $0$,返回最小操作数;否则,返回 $-1$。 + +**说明**: + +- $1 \le nums.length \le 10^5$。 +- $1 \le nums[i] \le 10^4$。 +- $1 \le x \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,4,2,3], x = 5 +输出:2 +解释:最佳解决方案是移除后两个元素,将 x 减到 0。 +``` + +- 示例 2: + +```python +输入:nums = [3,2,20,1,1,3], x = 10 +输出:5 +解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0。 +``` + +## 解题思路 + +### 思路 1:滑动窗口 + +将 $x$ 减到 $0$ 的最小操作数可以转换为求和等于 $sum(nums) - x$ 的最长连续子数组长度。我们可以维护一个区间和为 $sum(nums) - x$ 的滑动窗口,求出最长的窗口长度。具体做法如下: + +令 `target = sum(nums) - x`,使用 $max\underline{\hspace{0.5em}}len$ 维护和等于 $target$ 的最长连续子数组长度。然后用滑动窗口 $window\underline{\hspace{0.5em}}sum$ 来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好等于 $target$。 + +- 一开始,$left$、$right$ 都指向 $0$。 +- 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{\hspace{0.5em}}sum$ 中。 +- 如果 $window\underline{\hspace{0.5em}}sum > target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{\hspace{0.5em}}sum \le target$。 +- 如果 $window\underline{\hspace{0.5em}}sum == target$,则更新最长连续子数组长度。 +- 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。 +- 输出 $len(nums) - max\underline{\hspace{0.5em}}len$ 作为答案。 +- 注意判断题目中的特殊情况。 + +### 思路 1:代码 + +```python +class Solution: + def minOperations(self, nums: List[int], x: int) -> int: + target = sum(nums) - x + size = len(nums) + if target < 0: + return -1 + if target == 0: + return size + left, right = 0, 0 + window_sum = 0 + max_len = float('-inf') + + while right < size: + window_sum += nums[right] + + while window_sum > target: + window_sum -= nums[left] + left += 1 + if window_sum == target: + max_len = max(max_len, right - left + 1) + right += 1 + return len(nums) - max_len if max_len != float('-inf') else -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1600-1699/number-of-distinct-substrings-in-a-string.md b/docs/solutions/1600-1699/number-of-distinct-substrings-in-a-string.md new file mode 100644 index 00000000..7ba8f861 --- /dev/null +++ b/docs/solutions/1600-1699/number-of-distinct-substrings-in-a-string.md @@ -0,0 +1,62 @@ +# [1698. 字符串的不同子字符串个数](https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/) + +- 标签:字典树、字符串、后缀数组、哈希函数、滚动哈希 +- 难度:中等 + +## 题目链接 + +- [1698. 字符串的不同子字符串个数 - 力扣](https://leetcode.cn/problems/number-of-distinct-substrings-in-a-string/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:返回 `s` 的不同子字符串的个数。 + +注意:字符串的「子字符串」是由原字符串删除开头若干个字符(可能是 0 个)并删除结尾若干个字符(可能是 0 个)形成的字符串。 + +## 解题思路 + +构建一颗字典树。分别将原字符串删除开头若干个字符的子字符串依次插入到字典树中。 + +每次插入过程中碰到字典树中没有的字符节点时,说明此时插入的字符串可作为新的子字符串。 + +我们可以通过统计插入过程中新建字符节点的次数的方式来获取不同子字符串的个数。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> int: + """ + Inserts a word into the trie. + """ + cur = self + cnt = 0 + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cnt += 1 + cur = cur.children[ch] + cur.isEnd = True + return cnt + + +class Solution: + def countDistinct(self, s: str) -> int: + trie_tree = Trie() + cnt = 0 + for i in range(len(s)): + cnt += trie_tree.insert(s[i:]) + return cnt +``` + diff --git a/docs/solutions/1600-1699/path-with-minimum-effort.md b/docs/solutions/1600-1699/path-with-minimum-effort.md new file mode 100644 index 00000000..ed99a9b9 --- /dev/null +++ b/docs/solutions/1600-1699/path-with-minimum-effort.md @@ -0,0 +1,125 @@ +# [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [1631. 最小体力消耗路径 - 力扣](https://leetcode.cn/problems/path-with-minimum-effort/) + +## 题目大意 + +**描述**:给定一个 $rows \times cols$ 大小的二维数组 $heights$,其中 $heights[i][j]$ 表示为位置 $(i, j)$ 的高度。 + +现在要从左上角 $(0, 0)$ 位置出发,经过方格的一些点,到达右下角 $(n - 1, n - 1)$ 位置上。其中所经过路径的花费为「这条路径上所有相邻位置的最大高度差绝对值」。 + +**要求**:计算从 $(0, 0)$ 位置到 $(n - 1, n - 1)$ 的最优路径的花费。 + +**说明**: + +- **最优路径**:路径上「所有相邻位置最大高度差绝对值」最小的那条路径。 +- $rows == heights.length$。 +- $columns == heights[i].length$。 +- $1 \le rows, columns \le 100$。 +- $1 \le heights[i][j] \le 10^6$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/10/25/ex1.png) + +```python +输入:heights = [[1,2,2],[3,8,2],[5,3,5]] +输出:2 +解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。 +这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/10/25/ex2.png) + +```python +输入:heights = [[1,2,3],[3,8,4],[5,3,5]] +输出:1 +解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。 +``` + +## 解题思路 + +### 思路 1:并查集 + +将整个网络抽象为一个无向图,每个点与相邻的点(上下左右)之间都存在一条无向边,边的权重为两个点之间的高度差绝对值。 + +我们要找到左上角到右下角的最优路径,可以遍历所有的点,将所有的边存储到数组中,每条边的存储格式为 $[x, y, h]$,意思是编号 $x$ 的点和编号为 $y$ 的点之间的权重为 $h$。 + +然后按照权重从小到大的顺序,对所有边进行排序。 + +再按照权重大小遍历所有边,将其依次加入并查集中。并且每次都需要判断 $(0, 0)$ 点和 $(n - 1, n - 1)$ 点是否连通。 + +如果连通,则该边的权重即为答案。 + +### 思路 1:代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + row_size = len(heights) + col_size = len(heights[0]) + size = row_size * col_size + edges = [] + for row in range(row_size): + for col in range(col_size): + if row < row_size - 1: + x = row * col_size + col + y = (row + 1) * col_size + col + h = abs(heights[row][col] - heights[row + 1][col]) + edges.append([x, y, h]) + if col < col_size - 1: + x = row * col_size + col + y = row * col_size + col + 1 + h = abs(heights[row][col] - heights[row][col + 1]) + edges.append([x, y, h]) + + edges.sort(key=lambda x: x[2]) + + union_find = UnionFind(size) + + for edge in edges: + x, y, h = edge[0], edge[1], edge[2] + union_find.union(x, y) + if union_find.is_connected(0, size - 1): + return h + return 0 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n \times \alpha(m \times n))$,其中 $\alpha$ 是反 Ackerman 函数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/1600-1699/richest-customer-wealth.md b/docs/solutions/1600-1699/richest-customer-wealth.md new file mode 100644 index 00000000..8f27d192 --- /dev/null +++ b/docs/solutions/1600-1699/richest-customer-wealth.md @@ -0,0 +1,77 @@ +# [1672. 最富有客户的资产总量](https://leetcode.cn/problems/richest-customer-wealth/) + +- 标签:数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [1672. 最富有客户的资产总量 - 力扣](https://leetcode.cn/problems/richest-customer-wealth/) + +## 题目大意 + +**描述**:给定一个 $m \times n$ 的整数网格 $accounts$,其中 $accounts[i][j]$ 是第 $i$ 位客户在第 $j$ 家银行托管的资产数量。 + +**要求**:返回最富有客户所拥有的资产总量。 + +**说明**: + +- 客户的资产总量:指的是他们在各家银行托管的资产数量之和。 +- 最富有客户:资产总量最大的客户。 +- $m == accounts.length$。 +- $n == accounts[i].length$。 +- $1 \le m, n \le 50$。 +- $1 \le accounts[i][j] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:accounts = [[1,2,3],[3,2,1]] +输出:6 +解释: +第 1 位客户的资产总量 = 1 + 2 + 3 = 6 +第 2 位客户的资产总量 = 3 + 2 + 1 = 6 +两位客户都是最富有的,资产总量都是 6 ,所以返回 6。 +``` + +- 示例 2: + +```python +输入:accounts = [[1,5],[7,3],[3,5]] +输出:10 +解释: +第 1 位客户的资产总量 = 6 +第 2 位客户的资产总量 = 10 +第 3 位客户的资产总量 = 8 +第 2 位客户是最富有的,资产总量是 10,随意返回 10。 +``` + +## 解题思路 + +### 思路 1:直接模拟 + +1. 使用变量 $max\underline{\hspace{0.5em}}ans$ 存储最富有客户所拥有的资产总量。 +2. 遍历所有客户,对于当前客户 $accounts[i]$,统计其拥有的资产总量。 +3. 将当前客户的资产总量与 $max\underline{\hspace{0.5em}}ans$ 进行比较,如果大于 $max\underline{\hspace{0.5em}}ans$,则更新 $max\underline{\hspace{0.5em}}ans$ 的值。 +4. 遍历完所有客户,最终返回 $max\underline{\hspace{0.5em}}ans$ 作为结果。 + +### 思路 1:代码 + +```python +class Solution: + def maximumWealth(self, accounts: List[List[int]]) -> int: + max_ans = 0 + for i in range(len(accounts)): + total = 0 + for j in range(len(accounts[i])): + total += accounts[i][j] + if total > max_ans: + max_ans = total + return max_ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为二维数组 $accounts$ 的行数和列数。两重循环遍历的时间复杂度为 $O(m * n)$ 。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1700-1799/calculate-money-in-leetcode-bank.md b/docs/solutions/1700-1799/calculate-money-in-leetcode-bank.md new file mode 100644 index 00000000..cd031eae --- /dev/null +++ b/docs/solutions/1700-1799/calculate-money-in-leetcode-bank.md @@ -0,0 +1,97 @@ +# [1716. 计算力扣银行的钱](https://leetcode.cn/problems/calculate-money-in-leetcode-bank/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [1716. 计算力扣银行的钱 - 力扣](https://leetcode.cn/problems/calculate-money-in-leetcode-bank/) + +## 题目大意 + +**描述**:Hercy 每天都往力扣银行里存钱。 + +最开始,他在周一的时候存入 $1$ 块钱。从周二到周日,他每天都比前一天多存入 $1$ 块钱。在接下来的每个周一,他都会比前一个周一多存入 $1$ 块钱。 + +给定一个整数 $n$。 + +**要求**:计算在第 $n$ 天结束的时候,Hercy 在力扣银行中总共存了多少块钱。 + +**说明**: + +- $1 \le n \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 4 +输出:10 +解释:第 4 天后,总额为 1 + 2 + 3 + 4 = 10。 +``` + +- 示例 2: + +```python +输入:n = 10 +输出:37 +解释:第 10 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4) = 37 。注意到第二个星期一,Hercy 存入 2 块钱。 +``` + +## 解题思路 + +### 思路 1:暴力模拟 + +1. 记录当前周 $week$ 和当前周的当前天数 $day$。 +2. 按照题目要求,每天增加 $1$ 块钱,每周一比上周一增加 $1$ 块钱。这样,每天存钱数为 $week + day - 1$。 +3. 将每天存的钱数累加起来即为答案。 + +### 思路 1:代码 + +```python +class Solution: + def totalMoney(self, n: int) -> int: + weak, day = 1, 1 + ans = 0 + for i in range(n): + ans += weak + day - 1 + day += 1 + if day == 8: + day = 1 + weak += 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:等差数列计算优化 + +每周一比上周一增加 $1$ 块钱,则每周七天存钱总数比上一周多 $7$ 块钱。所以每周存的钱数是一个等差数列。我们可以通过高斯求和公式求出所有整周存的钱数,再计算出剩下天数存的钱数,两者相加即为答案。 + +### 思路 2:代码 + +```python +class Solution: + def totalMoney(self, n: int) -> int: + week_cnt = n // 7 + weak_first_money = (1 + 7) * 7 // 2 + weak_last_money = weak_first_money + 7 * (week_cnt - 1) + week_ans = (weak_first_money + weak_last_money) * week_cnt // 2 + + day_cnt = n % 7 + day_first_money = 1 + week_cnt + day_last_money = day_first_money + day_cnt - 1 + day_ans = (day_first_money + day_last_money) * day_cnt // 2 + + return week_ans + day_ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal.md b/docs/solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal.md new file mode 100644 index 00000000..600f85e1 --- /dev/null +++ b/docs/solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal.md @@ -0,0 +1,72 @@ +# [1790. 仅执行一次字符串交换能否使两个字符串相等](https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/) + +- 标签:哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [1790. 仅执行一次字符串交换能否使两个字符串相等 - 力扣](https://leetcode.cn/problems/check-if-one-string-swap-can-make-strings-equal/) + +## 题目大意 + +**描述**:给定两个长度相等的字符串 `s1` 和 `s2`。 + +已知一次「字符串交换操作」步骤如下:选出某个字符串中的两个下标(不一定要相同),并交换这两个下标所对应的字符。 + +**要求**:如果对其中一个字符串执行最多一次字符串交换可以使两个字符串相等,则返回 `True`;否则返回 `False`。 + +**说明**: + +- $1 \le s1.length, s2.length \le 100$。 +- $s1.length == s2.length$。 +- `s1` 和 `s2` 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +给定:s1 = "bank", s2 = "kanb" +输出:True +解释:交换 s1 中的第一个和最后一个字符可以得到 "kanb",与 s2 相同 +``` + +## 解题思路 + +### 思路 1: + +- 用一个变量 `diff_cnt` 记录两个字符串中对应位置上出现不同字符的次数。用 `c1`、`c2` 记录第一次出现不同字符时两个字符串对应位置上的字符。 +- 遍历两个字符串,对于第 `i` 个位置的字符 `s1[i]` 和 `s2[i]`: + - 如果 `s1[i] == s2[i]`,继续判断下一个位置。 + - 如果 `s1[i] != s2[i]`,则出现不同字符的次数加 `1`。 + - 如果出现不同字符的次数等于 `1`,则记录第一次出现不同字符时两个字符串对应位置上的字符。 + - 如果出现不同字符的次数等于 `2`,则判断第一次出现不同字符时两个字符串对应位置上的字符与当前位置字符交换之后是否相等。如果不等,则说明交换之后 `s1` 和 `s2` 不相等,返回 `False`。如果相等,则继续判断下一个位置。 + - 如果出现不同字符的次数超过 `2`,则不符合最多一次字符串交换的要求,返回 `False`。 +- 如果遍历完,出现不同字符的次数为 `0` 或者 `2`,为 `0` 说明无需交换,本身 `s1` 和 `s2` 就是相等的,为 `2` 说明交换一次字符串之后 `s1` 和 `s2` 相等,此时返回 `True`。否则返回 `False`。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def areAlmostEqual(self, s1: str, s2: str) -> bool: + size = len(s1) + diff_cnt = 0 + c1, c2 = None, None + for i in range(size): + if s1[i] == s2[i]: + continue + diff_cnt += 1 + if diff_cnt == 1: + c1 = s1[i] + c2 = s2[i] + elif diff_cnt == 2: + if c1 != s2[i] or c2 != s1[i]: + return False + else: + return False + + return diff_cnt == 0 or diff_cnt == 2 +``` + diff --git a/docs/solutions/1700-1799/decode-xored-array.md b/docs/solutions/1700-1799/decode-xored-array.md new file mode 100644 index 00000000..ac4af67b --- /dev/null +++ b/docs/solutions/1700-1799/decode-xored-array.md @@ -0,0 +1,46 @@ +# [1720. 解码异或后的数组](https://leetcode.cn/problems/decode-xored-array/) + +- 标签:位运算、数组 +- 难度:简单 + +## 题目链接 + +- [1720. 解码异或后的数组 - 力扣](https://leetcode.cn/problems/decode-xored-array/) + +## 题目大意 + +n 个非负整数构成数组 arr,经过编码后变为长度为 n-1 的整数数组 encoded,其中 `encoded[i] = arr[i] XOR arr[i+1]`。例如 arr = [1, 0, 2, 1] 经过编码后变为 encoded = [1, 2, 3]。 + +现在给定编码后的数组 encoded 和原数组 arr 的第一个元素 arr[0]。要求返回原数组 arr。 + +## 解题思路 + +首先要了解异或的性质: + +- 异或运算满足交换律和结合律。 + - 交换律:`a^b = b^a` + - 结合律:`(a^b)^c = a^(b^c)` +- 任何整数和自身做异或运算结果都为 0,即 `x^x = 0`。 +- 任何整数和 0 做异或运算结果都为其本身,即 `x^0 = 0`。 + +已知当 $1 \le i \le n$ 时,有 `encoded[i-1] = arr[i-1] XOR arr[i]`。两边同时「异或」上 arr[i-1]。得: + +- `encoded[i-1] XOR arr[i-1] = arr[i-1] XOR arr[i] XOR arr[i-1]` +- `encoded[i-1] XOR arr[i-1] = arr[i] XOR 0` +- `encoded[i-1] XOR arr[i-1] = arr[i]` + +所以就可以根据所得结论 `arr[i] = encoded[i-1] XOR arr[i-1]` 模拟得出原数组 arr。 + +## 代码 + +```python +class Solution: + def decode(self, encoded: List[int], first: int) -> List[int]: + n = len(encoded) + 1 + arr = [0] * n + arr[0] = first + for i in range(1, n): + arr[i] = encoded[i-1] ^ arr[i-1] + return arr +``` + diff --git a/docs/solutions/1700-1799/find-center-of-star-graph.md b/docs/solutions/1700-1799/find-center-of-star-graph.md new file mode 100644 index 00000000..a4bc7ca1 --- /dev/null +++ b/docs/solutions/1700-1799/find-center-of-star-graph.md @@ -0,0 +1,73 @@ +# [1791. 找出星型图的中心节点](https://leetcode.cn/problems/find-center-of-star-graph/) + +- 标签:图 +- 难度:简单 + +## 题目链接 + +- [1791. 找出星型图的中心节点 - 力扣](https://leetcode.cn/problems/find-center-of-star-graph/) + +## 题目大意 + +**描述**:有一个无向的行型图,由 $n$ 个编号 $1 \sim n$ 的节点组成。星型图有一个中心节点,并且恰好有 $n - 1$ 条边将中心节点与其他每个节点连接起来。 + +给定一个二维整数数组 $edges$,其中 $edges[i] = [u_i, v_i]$ 表示节点 $u_i$ 与节点 $v_i$ 之间存在一条边。 + +**要求**:找出并返回该星型图的中心节点。 + +**说明**: + +- $3 \le n \le 10^5$。 +- $edges.length == n - 1$。 +- $edges[i].length == 2$。 +- $1 \le ui, vi \le n$。 +- $ui \ne vi$。 +- 题目数据给出的 $edges$ 表示一个有效的星型图。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/14/star_graph.png) + +```python +输入:edges = [[1,2],[2,3],[4,2]] +输出:2 +解释:如上图所示,节点 2 与其他每个节点都相连,所以节点 2 是中心节点。 +``` + +- 示例 2: + +```python +输入:edges = [[1,2],[5,1],[1,3],[1,4]] +输出:1 +``` + +## 解题思路 + +### 思路 1:求度数 + +根据题意可知:中心节点恰好有 $n - 1$ 条边将中心节点与其他每个节点连接起来,那么中心节点的度数一定为 $n - 1$。则我们可以遍历边集数组 $edges$,统计出每个节点 $u$ 的度数 $degrees[u]$。最后返回度数为 $n - 1$ 的节点编号。 + +### 思路 1:代码 + +```python +class Solution: + def findCenter(self, edges: List[List[int]]) -> int: + n = len(edges) + 1 + degrees = collections.Counter() + + for u, v in edges: + degrees[u] += 1 + degrees[v] += 1 + + for i in range(1, n + 1): + if degrees[i] == n - 1: + return i + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate.md b/docs/solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate.md new file mode 100644 index 00000000..f89ce0be --- /dev/null +++ b/docs/solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate.md @@ -0,0 +1,61 @@ +# [1779. 找到最近的有相同 X 或 Y 坐标的点](https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [1779. 找到最近的有相同 X 或 Y 坐标的点 - 力扣](https://leetcode.cn/problems/find-nearest-point-that-has-the-same-x-or-y-coordinate/) + +## 题目大意 + +**描述**:给定两个整数 `x` 和 `y`,表示笛卡尔坐标系下的 `(x, y)` 点。再给定一个数组 `points`,其中 `points[i] = [ai, bi]`,表示在 `(ai, bi)` 处有一个点。当一个点与 `(x, y)` 拥有相同的 `x` 坐标或者拥有相同的 `y` 坐标时,我们称这个点是有效的。 + +**要求**:返回数组中距离 `(x, y)` 点出曼哈顿距离最近的有效点在 `points` 中的下标位置。如果有多个最近的有效点,则返回下标最小的一个。如果没有有效点,则返回 `-1`。 + +**说明**: + +- **曼哈顿距离**:`(x1, y1)` 和 `(x2, y2)` 之间的曼哈顿距离为 `abs(x1 - x2) + abs(y1 - y2)` 。 +- $1 \le points.length \le 10^4$。 +- $points[i].length == 2$。 +- $1 \le x, y, ai, bi \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:x = 3, y = 4, points = [[1, 2], [3, 1], [2, 4], [2, 3], [4, 4]] +输出:2 +解释:在所有点中 [3, 1]、[2, 4]、[4, 4] 为有效点。其中 [2, 4]、[4, 4] 距离 [3, 4] 曼哈顿距离最近,都为 1。[2, 4] 下标最小,所以返回 2。 +``` + +## 解题思路 + +### 思路 1: + +- 使用 `min_dist` 记录下有效点中最近的曼哈顿距离,初始化为 `float('inf')`。使用 `min_index` 记录下符合要求的最小下标。 +- 遍历 `points` 数组,遇到有效点之后计算一下当前有效点与 `(x, y)` 的曼哈顿距离,并判断更新一下有效点中最近的曼哈顿距离 `min_dist` 和符合要求的最小下标 `min_index`。 +- 遍历完之后,判断一下 `min_dist` 是否等于 `float('inf')`。如果等于,说明没有找到有效点,则返回 `-1`。如果不等于,则返回符合要求的最小下标 `min_index`。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def nearestValidPoint(self, x: int, y: int, points: List[List[int]]) -> int: + min_dist = float('inf') + min_index = 0 + for i in range(len(points)): + if points[i][0] == x or points[i][1] == y: + dist = abs(points[i][0] - x) + abs(points[i][1] - y) + if dist < min_dist: + min_dist = dist + min_index = i + + if min_dist == float('inf'): + return -1 + return min_index +``` + diff --git a/docs/solutions/1700-1799/index.md b/docs/solutions/1700-1799/index.md new file mode 100644 index 00000000..032c55e2 --- /dev/null +++ b/docs/solutions/1700-1799/index.md @@ -0,0 +1,13 @@ +## 本章内容 + +- [1710. 卡车上的最大单元数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-units-on-a-truck.md) +- [1716. 计算力扣银行的钱](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/calculate-money-in-leetcode-bank.md) +- [1720. 解码异或后的数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/decode-xored-array.md) +- [1726. 同积元组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/tuple-with-same-product.md) +- [1736. 替换隐藏数字得到的最晚时间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/latest-time-by-replacing-hidden-digits.md) +- [1742. 盒子中小球的最大数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md) +- [1749. 任意子数组和的绝对值的最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/maximum-absolute-sum-of-any-subarray.md) +- [1763. 最长的美好子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/longest-nice-substring.md) +- [1779. 找到最近的有相同 X 或 Y 坐标的点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/find-nearest-point-that-has-the-same-x-or-y-coordinate.md) +- [1790. 仅执行一次字符串交换能否使两个字符串相等](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/check-if-one-string-swap-can-make-strings-equal.md) +- [1791. 找出星型图的中心节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/find-center-of-star-graph.md) diff --git a/docs/solutions/1700-1799/latest-time-by-replacing-hidden-digits.md b/docs/solutions/1700-1799/latest-time-by-replacing-hidden-digits.md new file mode 100644 index 00000000..09b090b6 --- /dev/null +++ b/docs/solutions/1700-1799/latest-time-by-replacing-hidden-digits.md @@ -0,0 +1,87 @@ +# [1736. 替换隐藏数字得到的最晚时间](https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/) + +- 标签:贪心、字符串 +- 难度:简单 + +## 题目链接 + +- [1736. 替换隐藏数字得到的最晚时间 - 力扣](https://leetcode.cn/problems/latest-time-by-replacing-hidden-digits/) + +## 题目大意 + +**描述**:给定一个字符串 $time$,格式为 `hh:mm`(小时:分钟),其中某几位数字被隐藏(用 `?` 表示)。 + +**要求**:替换 $time$ 中隐藏的数字,返回你可以得到的最晚有效时间。 + +**说明**: + +- **有效时间**: `00:00` 到 `23:59` 之间的所有时间,包括 `00:00` 和 `23:59`。 +- $time$ 的格式为 `hh:mm`。 +- 题目数据保证你可以由输入的字符串生成有效的时间。 + +**示例**: + +- 示例 1: + +```python +输入:time = "2?:?0" +输出:"23:50" +解释:以数字 '2' 开头的最晚一小时是 23 ,以 '0' 结尾的最晚一分钟是 50。 +``` + +- 示例 2: + +```python +输入:time = "0?:3?" +输出:"09:39" +``` + +## 解题思路 + +### 思路 1:贪心算法 + +为了使有效时间尽可能晚,我们可以从高位到低位依次枚举所有符号为 `?` 的字符。在保证时间有效的前提下,每一位上取最大值,并进行保存。具体步骤如下: + +- 如果第 $1$ 位为 `?`: + - 如果第 $2$ 位已经确定,并且范围在 $[4, 9]$ 中间,则第 $1$ 位最大为 $1$; + - 否则第 $1$ 位最大为 $2$。 +- 如果第 $2$ 位为 `?`: + - 如果第 $1$ 位上值为 $2$,则第 $2$ 位最大可以为 $3$; + - 否则第 $2$ 位最大为 $9$。 +- 如果第 $3$ 位为 `?`: + - 第 $3$ 位最大可以为 $5$。 +- 如果第 $4$ 位为 `?`: + - 第 $4$ 位最大可以为 $9$。 + +### 思路 1:代码 + +```python +class Solution: + def maximumTime(self, time: str) -> str: + time_list = list(time) + if time_list[0] == '?': + if '4' <= time_list[1] <= '9': + time_list[0] = '1' + else: + time_list[0] = '2' + + if time_list[1] == '?': + if time_list[0] == '2': + time_list[1] = '3' + else: + time_list[1] = '9' + + if time_list[3] == '?': + time_list[3] = '5' + + if time_list[4] == '?': + time_list[4] = '9' + + return "".join(time_list) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1700-1799/longest-nice-substring.md b/docs/solutions/1700-1799/longest-nice-substring.md new file mode 100644 index 00000000..a0c91b95 --- /dev/null +++ b/docs/solutions/1700-1799/longest-nice-substring.md @@ -0,0 +1,80 @@ +# [1763. 最长的美好子字符串](https://leetcode.cn/problems/longest-nice-substring/) + +- 标签:位运算、哈希表、字符串、分治、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [1763. 最长的美好子字符串 - 力扣](https://leetcode.cn/problems/longest-nice-substring/) + +## 题目大意 + +**描述**: 给定一个字符串 $s$。 + +**要求**:返回 $s$ 最长的美好子字符串。 + +**说明**: + +- **美好字符串**:当一个字符串 $s$ 包含的每一种字母的大写和小写形式同时出现在 $s$ 中,就称这个字符串 $s$ 是美好字符串。 +- $1 \le s.length \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入:s = "YazaAay" +输出:"aAa" +解释:"aAa" 是一个美好字符串,因为这个子串中仅含一种字母,其小写形式 'a' 和大写形式 'A' 也同时出现了。 +"aAa" 是最长的美好子字符串。 +``` + +- 示例 2: + +```python +输入:s = "Bb" +输出:"Bb" +解释:"Bb" 是美好字符串,因为 'B' 和 'b' 都出现了。整个字符串也是原字符串的子字符串。 +``` + +## 解题思路 + +### 思路 1:枚举 + +字符串 $s$ 的范围为 $[1, 100]$,长度较小,我们可以枚举所有的子串,判断该子串是否为美好字符串。 + +由于大小写英文字母各有 $26$ 位,则我们可以利用二进制来标记某字符是否在子串中出现过,我们使用 $lower$ 标记子串中出现过的小写字母,使用 $upper$ 标记子串中出现过的大写字母。如果满足 $lower == upper$,则说明该子串为美好字符串。 + +具体解法步骤如下: + +1. 使用二重循环遍历字符串。对于子串 $s[i]…s[j]$,使用 $lower$ 标记子串中出现过的小写字母,使用 $upper$ 标记子串中出现过的大写字母。 +2. 如果 $s[j]$ 为小写字母,则 $lower$ 对应位置标记为出现过该小写字母,即:`lower |= 1 << (ord(s[j]) - ord('a'))`。 +3. 如果 $s[j]$ 为大写字母,则 $upper$ 对应位置标记为出现过该小写字母,即:`upper |= 1 << (ord(s[j]) - ord('A'))`。 +4. 判断当前子串对应 $lower$ 和 $upper$ 是否相等,如果相等,并且子串长度大于记录的最长美好字符串长度,则更新最长美好字符串长度。 +5. 遍历完返回记录的最长美好字符串长度。 + +### 思路 1:代码 + +```Python +class Solution: + def longestNiceSubstring(self, s: str) -> str: + size = len(s) + max_pos, max_len = 0, 0 + for i in range(size): + lower, upper = 0, 0 + for j in range(i, size): + if s[j].islower(): + lower |= 1 << (ord(s[j]) - ord('a')) + else: + upper |= 1 << (ord(s[j]) - ord('A')) + if lower == upper and j - i + 1 > max_len: + max_len = j - i + 1 + max_pos = i + return s[max_pos: max_pos + max_len] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1700-1799/maximum-absolute-sum-of-any-subarray.md b/docs/solutions/1700-1799/maximum-absolute-sum-of-any-subarray.md new file mode 100644 index 00000000..b5a3200f --- /dev/null +++ b/docs/solutions/1700-1799/maximum-absolute-sum-of-any-subarray.md @@ -0,0 +1,81 @@ +# [1749. 任意子数组和的绝对值的最大值](https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [1749. 任意子数组和的绝对值的最大值 - 力扣](https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:找出 $nums$ 中「和的绝对值」最大的任意子数组(可能为空),并返回最大值。 + +**说明**: + +- **子数组 $[nums_l, nums_{l+1}, ..., nums_{r-1}, nums_{r}]$ 的和的绝对值**:$abs(nums_l + nums_{l+1} + ... + nums_{r-1} + nums_{r})$。 +- $abs(x)$ 定义如下: + - 如果 $x$ 是负整数,那么 $abs(x) = -x$。 + - 如果 $x$ 是非负整数,那么 $abs(x) = x$。 + +- $1 \le nums.length \le 10^5$。 +- $-10^4 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,-3,2,3,-4] +输出:5 +解释:子数组 [2,3] 和的绝对值最大,为 abs(2+3) = abs(5) = 5。 +``` + +- 示例 2: + +```python +输入:nums = [2,-5,1,-4,3,-2] +输出:8 +解释:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +子数组和的绝对值的最大值,可能来自于「连续子数组的最大和」,也可能来自于「连续子数组的最小和」。 + +而求解「连续子数组的最大和」,我们可以参考「[0053. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/)」的做法,使用一个变量 $mmax$ 来表示以第 $i$ 个数结尾的连续子数组的最大和。使用另一个变量 $mmin$ 来表示以第 $i$ 个数结尾的连续子数组的最小和。然后取两者绝对值的最大值为答案 $ans$。 + +具体步骤如下: + +1. 遍历数组 $nums$,对于当前元素 $nums[i]$: + 1. 如果 $mmax < 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」<「第 $i$ 个数的值」,所以 $mmax$ 应取「第 $i$ 个数的值」,即:$mmax = nums[i]$。 + 2. 如果 $mmax \ge 0$ ,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 >= 第 $i$ 个数的值,所以 $mmax$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」,即:$mmax = mmax + nums[i]$。 + 3. 如果 $mmin > 0$,则「第 $i - 1$ 个数结尾的连续子数组的最大和」+「第 $i$ 个数的值」>「第 $i$ 个数的值」,所以 $mmax$ 应取「第 $i$ 个数的值」,即:$mmax = nums[i]$。 + 4. 如果 $mmin \le 0$ ,则「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」 <= 第 $i$ 个数的值,所以 $mmax$ 应取「第 $i - 1$ 个数结尾的连续子数组的最大和」 +「第 $i$ 个数的值」,即:$mmin = mmin + nums[i]$。 + 5. 维护答案 $ans$,将 $mmax$ 和 $mmin$ 绝对值的最大值与 $ans$ 进行比较,并更新 $ans$。 +2. 遍历完返回答案 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def maxAbsoluteSum(self, nums: List[int]) -> int: + ans = 0 + mmax, mmin = 0, 0 + for num in nums: + mmax = max(mmax, 0) + num + mmin = min(mmin, 0) + num + ans = max(ans, mmax, -mmin) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md b/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md new file mode 100644 index 00000000..2165d555 --- /dev/null +++ b/docs/solutions/1700-1799/maximum-number-of-balls-in-a-box.md @@ -0,0 +1,122 @@ +# [1742. 盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) + +- 标签:哈希表、数学、计数 +- 难度:简单 + +## 题目链接 + +- [1742. 盒子中小球的最大数量 - 力扣](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box/) + +## 题目大意 + +**描述**:给定两个整数 $lowLimit$ 和 $highLimt$,代表 $n$ 个小球的编号(包括 $lowLimit$ 和 $highLimit$,即 $n == highLimit = lowLimit + 1$)。另外有无限个盒子。 + +现在的工作是将每个小球放入盒子中,其中盒子的编号应当等于小球编号上每位数字的和。例如,编号 $321$ 的小球应当放入编号 $3 + 2 + 1 = 6$ 的盒子,而编号 $10$ 的小球应当放入编号 $1 + 0 = 1$ 的盒子。 + +**要求**:返回放有最多小球的盒子中的小球数量。如果有多个盒子都满足放有最多小球,只需返回其中任一盒子的小球数量。 + +**说明**: + +- $1 \le lowLimit \le highLimit \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:lowLimit = 1, highLimit = 10 +输出:2 +解释: +盒子编号:1 2 3 4 5 6 7 8 9 10 11 ... +小球数量:2 1 1 1 1 1 1 1 1 0 0 ... +编号 1 的盒子放有最多小球,小球数量为 2。 +``` + +- 示例 2: + +```python +输入:lowLimit = 5, highLimit = 15 +输出:2 +解释: +盒子编号:1 2 3 4 5 6 7 8 9 10 11 ... +小球数量:1 1 1 1 2 2 1 1 1 0 0 ... +编号 5 和 6 的盒子放有最多小球,每个盒子中的小球数量都是 2。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $lowLimit$、$highLimit$ 转为字符串 $s1$、$s2$,并将 $s1$ 补上前导 $0$,令其与 $s2$ 长度一致。定义递归函数 `def dfs(pos, remainTotal, isMaxLimit, isMinLimit):` 表示构造第 $pos$ 位及之后剩余数位和为 $remainTotal$ 的合法方案数。 + +因为数据范围为 $[1, 10^5]$,对应数位和范围为 $[1, 45]$。因此我们可以枚举所有的数位和,并递归调用 `dfs(i, remainTotal, isMaxLimit, isMinLimit)`,求出不同数位和对应的方案数,并求出最大方案数。 + +接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, i, True, True)` 开始递归。 `dfs(0, i, True, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 剩余数位和为 $i$。 + 3. 开始时当前数位最大值受到最高位数位的约束。 + 4. 开始时当前数位最小值受到最高位数位的约束。 + +2. 如果剩余数位和小于 $0$,说明当前方案不符合要求,则返回方案数 $0$。 + +3. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果剩余数位和 $remainTotal$ 等于 $0$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果剩余数位和 $remainTotal$ 不等于 $0$,说明当前方案不符合要求,则返回方案数 $0$。 + +4. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +5. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +6. 根据 $isMaxLimit$ 和 $isMinLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 + +7. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 +8. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, remainTotal - d, isMaxLimit and d == maxX, isMinLimit and d == minX)`。 + 1. `remainTotal - d` 表示当前剩余数位和减去 $d$。 + 2. `isMaxLimit and d == maxX` 表示 $pos + 1$ 位最大值受到之前 $pos$ 位限制。 + 3. `isMinLimit and d == maxX` 表示 $pos + 1$ 位最小值受到之前 $pos$ 位限制。 +9. 最后返回所有 `dfs(0, i, True, True)` 中最大的方案数即可。 + +### 思路 1:代码 + +```python +class Solution: + def countBalls(self, lowLimit: int, highLimit: int) -> int: + s1, s2 = str(lowLimit), str(highLimit) + + m, n = len(s1), len(s2) + if m < n: + s1 = '0' * (n - m) + s1 + + @cache + # pos: 第 pos 个数位 + # remainTotal: 表示剩余数位和 + # isMaxLimit: 表示是否受到上限选择限制。如果为真,则第 pos 位填入数字最多为 s2[pos];如果为假,则最大可为 9。 + # isMinLimit: 表示是否受到下限选择限制。如果为真,则第 pos 位填入数字最小为 s1[pos];如果为假,则最小可为 0。 + def dfs(pos, remainTotal, isMaxLimit, isMinLimit): + if remainTotal < 0: + return 0 + if pos == n: + # remainTotal 为 0,则表示当前方案符合要求 + return int(remainTotal == 0) + + ans = 0 + # 如果前一位没有填写数字,或受到选择限制,则最小可选择数字为 s1[pos],否则最少为 0(可以含有前导 0)。 + minX = int(s1[pos]) if isMinLimit else 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s2[pos]) if isMaxLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + ans += dfs(pos + 1, remainTotal - d, isMaxLimit and d == maxX, isMinLimit and d == minX) + return ans + + ans = 0 + for i in range(46): + ans = max(ans, dfs(0, i, True, True)) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n \times 45)$。 +- **空间复杂度**:$O(\log n)$。 diff --git a/docs/solutions/1700-1799/maximum-units-on-a-truck.md b/docs/solutions/1700-1799/maximum-units-on-a-truck.md new file mode 100644 index 00000000..0296c933 --- /dev/null +++ b/docs/solutions/1700-1799/maximum-units-on-a-truck.md @@ -0,0 +1,91 @@ +# [1710. 卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck/) + +- 标签:贪心、数组、排序 +- 难度:简单 + +## 题目链接 + +- [1710. 卡车上的最大单元数 - 力扣](https://leetcode.cn/problems/maximum-units-on-a-truck/) + +## 题目大意 + +**描述**:现在需要将一些箱子装在一辆卡车上。给定一个二维数组 $boxTypes$,其中 $boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi]$。 + +$numberOfBoxesi$ 是类型 $i$ 的箱子的数量。$numberOfUnitsPerBoxi$ 是类型 $i$ 的每个箱子可以装载的单元数量。 + +再给定一个整数 $truckSize$ 表示一辆卡车上可以装载箱子的最大数量。只要箱子数量不超过 $truckSize$,你就可以选择任意箱子装到卡车上。 + +**要求**:返回卡车可以装载的最大单元数量。 + +**说明**: + +- $1 \le boxTypes.length \le 1000$。 +- $1 \le numberOfBoxesi, numberOfUnitsPerBoxi \le 1000$。 +- $1 \le truckSize \le 106$。 + +**示例**: + +- 示例 1: + +```python +输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4 +输出:8 +解释 +箱子的情况如下: +- 1 个第一类的箱子,里面含 3 个单元。 +- 2 个第二类的箱子,每个里面含 2 个单元。 +- 3 个第三类的箱子,每个里面含 1 个单元。 +可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。 +单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8 +``` + +- 示例 2: + +```python +输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10 +输出:91 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +题目中,一辆卡车上可以装载箱子的最大数量是固定的($truckSize$),那么如果想要使卡车上装载的单元数量最大,就应该优先选取装载单元数量多的箱子。 + +所以,从贪心算法的角度来考虑,我们应该按照每个箱子可以装载的单元数量对数组 $boxTypes$ 从大到小排序。然后优先选取装载单元数量多的箱子。 + +下面我们使用贪心算法三步走的方法解决这道题。 + +1. **转换问题**:将原问题转变为,在 $truckSize$ 的限制下,当选取完装载单元数量最多的箱子 $box$ 之后,再解决剩下箱子($truckSize - box[0]$)的选择问题(子问题)。 +2. **贪心选择性质**:对于当前 $truckSize$,优先选取装载单元数量最多的箱子。 +3. **最优子结构性质**:在上面的贪心策略下,当前 $truckSize$ 的贪心选择 + 剩下箱子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得卡车可以装载的单元数量达到最大。 + +使用贪心算法的解决步骤描述如下: + +1. 对数组 $boxTypes$ 按照每个箱子可以装载的单元数量从大到小排序。使用变量 $res$ 记录卡车可以装载的最大单元数量。 +2. 遍历数组 $boxTypes$,对于当前种类的箱子 $box$: + 1. 如果 $truckSize > box[0]$,说明当前种类箱子可以全部装载。则答案数量加上该种箱子的单元总数,即 $box[0] \times box[1]$,并且最大数量 $truckSize$ 减去装载的箱子数。 + 2. 如果 $truckSize \le box[0]$,说明当前种类箱子只能部分装载。则答案数量加上 $truckSize \times box[1]$,并跳出循环。 +3. 最后返回答案 $res$。 + +### 思路 1:代码 + +```python +class Solution: + def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int: + boxTypes.sort(key=lambda x:x[1], reverse=True) + res = 0 + for box in boxTypes: + if truckSize > box[0]: + res += box[0] * box[1] + truckSize -= box[0] + else: + res += truckSize * box[1] + break + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 是数组 $boxTypes$ 的长度。 +- **空间复杂度**:$O(\log n)$。 diff --git a/docs/solutions/1700-1799/tuple-with-same-product.md b/docs/solutions/1700-1799/tuple-with-same-product.md new file mode 100644 index 00000000..86483c7b --- /dev/null +++ b/docs/solutions/1700-1799/tuple-with-same-product.md @@ -0,0 +1,78 @@ +# [1726. 同积元组](https://leetcode.cn/problems/tuple-with-same-product/) + +- 标签:数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [1726. 同积元组 - 力扣](https://leetcode.cn/problems/tuple-with-same-product/) + +## 题目大意 + +**描述**:给定一个由不同正整数组成的数组 $nums$。 + +**要求**:返回满足 $a \times b = c \times d$ 的元组 $(a, b, c, d)$ 的数量。其中 $a$、$b$、$c$ 和 $d$ 都是 $nums$ 中的元素,且 $a \ne b \ne c \ne d$。 + +**说明**: + +- $1 \le nums.length \le 1000$。 +- $1 \le nums[i] \le 10^4$。 +- $nums$ 中的所有元素互不相同。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,3,4,6] +输出:8 +解释:存在 8 个满足题意的元组: +(2,6,3,4) , (2,6,4,3) , (6,2,3,4) , (6,2,4,3) +(3,4,2,6) , (4,3,2,6) , (3,4,6,2) , (4,3,6,2) +``` + +- 示例 2: + +```python +输入:nums = [1,2,4,5,10] +输出:16 +解释:存在 16 个满足题意的元组: +(1,10,2,5) , (1,10,5,2) , (10,1,2,5) , (10,1,5,2) +(2,5,1,10) , (2,5,10,1) , (5,2,1,10) , (5,2,10,1) +(2,10,4,5) , (2,10,5,4) , (10,2,4,5) , (10,2,5,4) +(4,5,2,10) , (4,5,10,2) , (5,4,2,10) , (5,4,10,2) +``` + +## 解题思路 + +### 思路 1:哈希表 + 数学 + +1. 二重循环遍历数组 $nums$,使用哈希表 $cnts$ 记录下所有不同 $nums[i] \times nums[j]$ 的结果。 +2. 因为满足 $a \times b = c \times d$ 的元组 $(a, b, c, d)$ 可以按照不同顺序进行组和,所以对于 $x$ 个 $nums[i] \times nums[j]$,就有 $C_x^2$ 种组和方法。 +3. 遍历哈希表 $cnts$ 中所有值 $value$,将不同组和的方法数累积到答案 $ans$ 中。 +4. 遍历完返回答案 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def tupleSameProduct(self, nums: List[int]) -> int: + cnts = Counter() + size = len(nums) + for i in range(size): + for j in range(i + 1, size): + product = nums[i] * nums[j] + cnts[product] += 1 + + ans = 0 + for key, value in cnts.items(): + ans += value * (value - 1) * 4 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$,其中 $n$ 表示数组 $nums$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + diff --git a/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md b/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md new file mode 100644 index 00000000..8254aab6 --- /dev/null +++ b/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md @@ -0,0 +1,75 @@ +# [1893. 检查是否区域内所有整数都被覆盖](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) + +- 标签:数组、哈希表、前缀和 +- 难度:简单 + +## 题目链接 + +- [1893. 检查是否区域内所有整数都被覆盖 - 力扣](https://leetcode.cn/problems/check-if-all-the-integers-in-a-range-are-covered/) + +## 题目大意 + +**描述**:给定一个二维整数数组 $ranges$ 和两个整数 $left$ 和 $right$。每个 $ranges[i] = [start_i, end_i]$ 表示一个从 $start_i$ 到 $end_i$ 的 闭区间 。 + +**要求**:如果闭区间 $[left, right]$ 内每个整数都被 $ranges$ 中至少一个区间覆盖,那么请你返回 $True$ ,否则返回 $False$。 + +**说明**: + +- $1 \le ranges.length \le 50$。 +- $1 \le start_i \le end_i \le 50$。 +- $1 \le left \le right \le 50$。 + +**示例**: + +- 示例 1: + +```python +输入:ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5 +输出:True +解释:2 到 5 的每个整数都被覆盖了: +- 2 被第一个区间覆盖。 +- 3 和 4 被第二个区间覆盖。 +- 5 被第三个区间覆盖。 +``` + +- 示例 2: + +```python +输入:ranges = [[1,10],[10,20]], left = 21, right = 21 +输出:False +解释:21 没有被任何一个区间覆盖。 +``` + +## 解题思路 + +### 思路 1:暴力 + +区间的范围为 $[1, 50]$,所以我们可以使用一个长度为 $51$ 的标志数组 $flags$ 用于标记区间内的所有整数。 + +1. 遍历数组 $ranges$ 中的所有区间 $[l, r]$。 +2. 对于区间 $[l, r]$ 和区间 $[left, right]$,将两区间相交部分标记为 $True$。 +3. 遍历区间 $[left, right]$ 上的所有整数,判断对应标志位是否为 $False$。 +4. 如果对应标志位出现 $False$,则返回 $False$。 +5. 如果遍历完所有标志位都为 $True$,则返回 $True$。 + +### 思路 1:代码 + +```Python +class Solution: + def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool: + flags = [False for _ in range(51)] + for l, r in ranges: + for i in range(max(l, left), min(r, right) + 1): + flags[i] = True + + for i in range(left, right + 1): + if not flags[i]: + return False + + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(50 \times n)$。 +- **空间复杂度**:$O(50)$。 diff --git a/docs/solutions/1800-1899/index.md b/docs/solutions/1800-1899/index.md new file mode 100644 index 00000000..e7e943c0 --- /dev/null +++ b/docs/solutions/1800-1899/index.md @@ -0,0 +1,13 @@ +## 本章内容 + +- [1822. 数组元素积的符号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/sign-of-the-product-of-an-array.md) +- [1827. 最少操作使数组递增](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-operations-to-make-the-array-increasing.md) +- [1833. 雪糕的最大数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/maximum-ice-cream-bars.md) +- [1844. 将所有数字用字符替换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/replace-all-digits-with-characters.md) +- [1858. 包含所有前缀的最长单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/longest-word-with-all-prefixes.md) +- [1859. 将句子排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/sorting-the-sentence.md) +- [1876. 长度为三且各字符不同的子字符串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/substrings-of-size-three-with-distinct-characters.md) +- [1877. 数组中最大数对和的最小值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimize-maximum-pair-sum-in-array.md) +- [1879. 两个数组最小的异或值之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md) +- [1893. 检查是否区域内所有整数都被覆盖](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/check-if-all-the-integers-in-a-range-are-covered.md) +- [1897. 重新分配字符使所有字符串都相等](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/redistribute-characters-to-make-all-strings-equal.md) diff --git a/docs/solutions/1800-1899/longest-word-with-all-prefixes.md b/docs/solutions/1800-1899/longest-word-with-all-prefixes.md new file mode 100644 index 00000000..f7cb942a --- /dev/null +++ b/docs/solutions/1800-1899/longest-word-with-all-prefixes.md @@ -0,0 +1,72 @@ +# [1858. 包含所有前缀的最长单词](https://leetcode.cn/problems/longest-word-with-all-prefixes/) + +- 标签:深度优先搜索、字典树 +- 难度:中等 + +## 题目链接 + +- [1858. 包含所有前缀的最长单词 - 力扣](https://leetcode.cn/problems/longest-word-with-all-prefixes/) + +## 题目大意 + +给定一个字符串数组 `words`。 + +要求:找出 `words` 中所有前缀从都在 `words` 中的最长字符串。如果存在多个符合条件相同长度的字符串,则输出字典序中最小的字符串。如果不存在这样的字符串,返回 `' '`。 + +- 例如:令 `words = ["a", "app", "ap"]`。字符串 `"app"` 含前缀 `"ap"` 和 `"a"` ,都在 `words` 中。 + +## 解题思路 + +使用字典树存储所有单词,再将字典中单词按照长度从大到小、字典序从小到大排序。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + if not cur.isEnd: + return False + return True + + +class Solution: + def longestWord(self, words: List[str]) -> str: + tire_tree = Trie() + for word in words: + tire_tree.insert(word) + words.sort(key=lambda x:(-len(x), x)) + for word in words: + if tire_tree.search(word): + return word + return '' +``` + diff --git a/docs/solutions/1800-1899/maximum-ice-cream-bars.md b/docs/solutions/1800-1899/maximum-ice-cream-bars.md new file mode 100644 index 00000000..efc1463d --- /dev/null +++ b/docs/solutions/1800-1899/maximum-ice-cream-bars.md @@ -0,0 +1,70 @@ +# [1833. 雪糕的最大数量](https://leetcode.cn/problems/maximum-ice-cream-bars/) + +- 标签:贪心、数组、排序 +- 难度:中等 + +## 题目链接 + +- [1833. 雪糕的最大数量 - 力扣](https://leetcode.cn/problems/maximum-ice-cream-bars/) + +## 题目大意 + +**描述**:给定一个数组 $costs$ 表示不同雪糕的定价,其中 $costs[i]$ 表示第 $i$ 支雪糕的定价。再给定一个整数 $coins$ 表示 Tony 一共有的现金数量。 + +**要求**:计算并返回 Tony 用 $coins$ 现金能够买到的雪糕的最大数量。 + +**说明**: + +- $costs.length == n$。 +- $1 \le n \le 10^5$。 +- $1 \le costs[i] \le 10^5$。 +- $1 \le coins \le 10^8$。 + +**示例**: + +- 示例 1: + +```python +输入:costs = [1,3,2,4,1], coins = 7 +输出:4 +解释:Tony 可以买下标为 0、1、2、4 的雪糕,总价为 1 + 3 + 2 + 1 = 7 +``` + +- 示例 2: + +```python +输入:costs = [10,6,8,7,7,8], coins = 5 +输出:0 +解释:Tony 没有足够的钱买任何一支雪糕。 +``` + +## 解题思路 + +### 思路 1:排序 + 贪心 + +贪心思路,如果想尽可能买到多的雪糕,就应该优先选择价格便宜的雪糕。具体步骤如下: + +1. 对数组 $costs$ 进行排序。 +2. 按照雪糕价格从低到高开始买雪糕,并记录下购买雪糕的数量,知道现有钱买不起雪糕为止。 +3. 输出购买雪糕的数量作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def maxIceCream(self, costs: List[int], coins: int) -> int: + costs.sort() + ans = 0 + for cost in costs: + if coins >= cost: + ans += 1 + coins -= cost + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + + diff --git a/docs/solutions/1800-1899/minimize-maximum-pair-sum-in-array.md b/docs/solutions/1800-1899/minimize-maximum-pair-sum-in-array.md new file mode 100644 index 00000000..1e9ad367 --- /dev/null +++ b/docs/solutions/1800-1899/minimize-maximum-pair-sum-in-array.md @@ -0,0 +1,75 @@ +# [1877. 数组中最大数对和的最小值](https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/) + +- 标签:贪心、数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [1877. 数组中最大数对和的最小值 - 力扣](https://leetcode.cn/problems/minimize-maximum-pair-sum-in-array/) + +## 题目大意 + +**描述**:一个数对 $(a, b)$ 的数对和等于 $a + b$。最大数对和是一个数对数组中最大的数对和。 + +- 比如,如果我们有数对 $(1, 5)$,$(2, 3)$ 和 $(4, 4)$,最大数对和为 $max(1 + 5, 2 + 3, 4 + 4) = max(6, 5, 8) = 8$。 + +给定一个长度为偶数 $n$ 的数组 $nums$,现在将 $nums$ 中的元素分为 $n / 2$ 个数对,使得: + +- $nums$ 中每个元素恰好在一个数对中。 +- 最大数对和的值最小。 + +**要求**:在最优数对划分的方案下,返回最小的最大数对和。 + +**说明**: + +- $n == nums.length$。 +- $2 \le n \le 10^5$。 +- $n$ 是偶数。 +- $1 \le nums[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [3,5,2,3] +输出:7 +解释:数组中的元素可以分为数对 (3,3) 和 (5,2)。 +最大数对和为 max(3+3, 5+2) = max(6, 7) = 7。 +``` + +- 示例 2: + +```python +输入:nums = [3,5,4,2,4,6] +输出:8 +解释:数组中的元素可以分为数对 (3,5),(4,4) 和 (6,2)。 +最大数对和为 max(3+5, 4+4, 6+2) = max(8, 8, 8) = 8。 +``` + +## 解题思路 + +### 思路 1:排序 + 贪心 + +为了使最大数对和的值尽可能的小,我们应该尽可能的让数组中最大值与最小值组成一对,次大值与次小值组成一对。而其他任何方案都会使得最大数对和的值更大。 + +那么,我们可以先将数组进行排序,然后首尾依次进行组对,并计算这种方案下的最大数对和即为答案。 + +### 思路 1:代码 + +```python +class Solution: + def minPairSum(self, nums: List[int]) -> int: + nums.sort() + ans, size = 0, len(nums) + for i in range(len(nums) // 2): + ans = max(ans, nums[i] + nums[size - 1 - i]) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(\log n)$。 + diff --git a/docs/solutions/1800-1899/minimum-operations-to-make-the-array-increasing.md b/docs/solutions/1800-1899/minimum-operations-to-make-the-array-increasing.md new file mode 100644 index 00000000..b222ce54 --- /dev/null +++ b/docs/solutions/1800-1899/minimum-operations-to-make-the-array-increasing.md @@ -0,0 +1,75 @@ +# [1827. 最少操作使数组递增](https://leetcode.cn/problems/minimum-operations-to-make-the-array-increasing/) + +- 标签:贪心、数组 +- 难度:简单 + +## 题目链接 + +- [1827. 最少操作使数组递增 - 力扣](https://leetcode.cn/problems/minimum-operations-to-make-the-array-increasing/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$(下标从 $0$ 开始)。每一次操作中,你可以选择数组中的一个元素,并将它增加 $1$。 + +- 比方说,如果 $nums = [1,2,3]$,你可以选择增加 $nums[1]$ 得到 $nums = [1,3,3]$。 + +**要求**:请你返回使 $nums$ 严格递增的最少操作次数。 + +**说明**: + +- 我们称数组 $nums$ 是严格递增的,当它满足对于所有的 $0 \le i < nums.length - 1$ 都有 $nums[i] < nums[i + 1]$。一个长度为 $1$ 的数组是严格递增的一种特殊情况。 +- $1 \le nums.length \le 5000$。 +- $1 \le nums[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1] +输出:3 +解释:你可以进行如下操作: +1) 增加 nums[2] ,数组变为 [1,1,2]。 +2) 增加 nums[1] ,数组变为 [1,2,2]。 +3) 增加 nums[2] ,数组变为 [1,2,3]。 +``` + +- 示例 2: + +```python +输入:nums = [1,5,2,4,1] +输出:14 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +题目要求使 $nums$ 严格递增的最少操作次数。当遇到 $nums[i - 1] \ge nums[i]$ 时,我们应该在满足要求的同时,尽可能使得操作次数最少,则 $nums[i]$ 应增加到 $nums[i - 1] + 1$ 时,此时操作次数最少,并且满足 $nums[i - 1] < nums[i]$。 + +具体操作步骤如下: + +1. 从左到右依次遍历数组元素。 +2. 如果遇到 $nums[i - 1] \ge nums[i]$ 时: + 1. 本次增加的最少操作次数为 $nums[i - 1] + 1 - nums[i]$,将其计入答案中。 + 2. 将 $nums[i]$ 变为 $nums[i - 1] + 1$。 +3. 遍历完返回答案 $ans$。 + +### 思路 1:代码 + +```Python +class Solution: + def minOperations(self, nums: List[int]) -> int: + ans = 0 + for i in range(1, len(nums)): + if nums[i - 1] >= nums[i]: + ans += nums[i - 1] + 1 - nums[i] + nums[i] = nums[i - 1] + 1 + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md b/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md new file mode 100644 index 00000000..54650974 --- /dev/null +++ b/docs/solutions/1800-1899/minimum-xor-sum-of-two-arrays.md @@ -0,0 +1,115 @@ +# [1879. 两个数组最小的异或值之和](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) + +- 标签:位运算、数组、动态规划、状态压缩 +- 难度:困难 + +## 题目链接 + +- [1879. 两个数组最小的异或值之和 - 力扣](https://leetcode.cn/problems/minimum-xor-sum-of-two-arrays/) + +## 题目大意 + +**描述**:给定两个整数数组 $nums1$ 和 $nums2$,两个数组长度都为 $n$。 + +**要求**:将 $nums2$ 中的元素重新排列,使得两个数组的异或值之和最小。并返回重新排列之后的异或值之和。 + +**说明**: + +- **两个数组的异或值之和**:$(nums1[0] \oplus nums2[0]) + (nums1[1] \oplus nums2[1]) + ... + (nums1[n - 1] \oplus nums2[n - 1])$(下标从 $0$ 开始)。 +- 举个例子,$[1, 2, 3]$ 和 $[3,2,1]$ 的异或值之和 等于 $(1 \oplus 3) + (2 \oplus 2) + (3 \oplus 1) + (3 \oplus 1) = 2 + 0 + 2 = 4$。 +- $n == nums1.length$。 +- $n == nums2.length$。 +- $1 \le n \le 14$。 +- $0 \le nums1[i], nums2[i] \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入:nums1 = [1,2], nums2 = [2,3] +输出:2 +解释:将 nums2 重新排列得到 [3,2] 。 +异或值之和为 (1 XOR 3) + (2 XOR 2) = 2 + 0 = 2。 +``` + +- 示例 2: + +```python +输入:nums1 = [1,0,3], nums2 = [5,3,4] +输出:8 +解释:将 nums2 重新排列得到 [5,4,3] 。 +异或值之和为 (1 XOR 5) + (0 XOR 4) + (3 XOR 3) = 4 + 4 + 0 = 8。 +``` + +## 解题思路 + +### 思路 1:状态压缩 DP + +由于数组 $nums2$ 可以重新排列,所以我们可以将数组 $nums1$ 中的元素顺序固定,然后将数组 $nums1$ 中第 $i$ 个元素与数组 $nums2$ 中所有还没被选择的元素进行组合,找到异或值之和最小的组合。 + +同时因为两个数组长度 $n$ 的大小范围只有 $[1, 14]$,所以我们可以采用「状态压缩」的方式来表示 $nums2$ 中当前元素的选择情况。 + +「状态压缩」指的是使用一个 $n$ 位的二进制数 $state$ 来表示排列中数的选取情况。 + +如果二进制数 $state$ 的第 $i$ 位为 $1$,说明数组 $nums2$ 第 $i$ 个元素在该状态中被选取。反之,如果该二进制的第 $i$ 位为 $0$,说明数组 $nums2$ 中第 $i$ 个元素在该状态中没有被选取。 + +举个例子: + +1. $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。 +2. $nums2 = \lbrace 1, 2, 3, 4, 5, 6 \rbrace$,$state = (011010)_2$,表示选择了第 $2$ 个元素、第 $4$ 个元素、第 $5$ 个元素,也就是 $2$、$4$、$5$。 + +这样,我们就可以通过动态规划的方式来解决这道题。 + +###### 1. 划分阶段 + +按照数组 $nums$ 中元素选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义当前数组 $nums2$ 中元素选择状态为 $state$,$state$ 对应选择的元素个数为 $count(state)$。 + +则可以定义状态 $dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 + +###### 3. 状态转移方程 + +对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以组成的异或值之和最小值,赋值给 $dp[state]$。 + +举个例子 $nums2 = \lbrace 1, 2, 3, 4 \rbrace$,$state = (1001)_2$,表示选择了第 $1$ 个元素和第 $4$ 个元素,也就是 $1$、$4$。那么 $state$ 只能从 $(1000)_2$ 和 $(0001)_2$ 这两个状态转移而来,我们只需要枚举这两种状态,并求出转移过来的异或值之和最小值。 + +即状态转移方程为:$dp[state] = min(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + (nums1[i] \oplus nums2[one\underline{\hspace{0.5em}}cnt - 1]))$,其中 $state$ 第 $i$ 位一定为 $1$,$one\underline{\hspace{0.5em}}cnt$ 为 $state$ 中 $1$ 的个数。 + +###### 4. 初始条件 + +- 既然是求最小值,不妨将所有状态初始为最大值。 +- 未选择任何数时,异或值之和为 $0$,所以初始化 $dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当前数组 $nums2$ 中元素选择状态为 $state$,并且选择了 $nums1$ 中前 $count(state)$ 个元素的情况下,可以组成的最小异或值之和。 所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } n$。 + +### 思路 1:代码 + +```python +class Solution: + def minimumXORSum(self, nums1: List[int], nums2: List[int]) -> int: + ans = float('inf') + size = len(nums1) + states = 1 << size + + dp = [float('inf') for _ in range(states)] + dp[0] = 0 + for state in range(states): + one_cnt = bin(state).count('1') + for i in range(size): + if (state >> i) & 1: + dp[state] = min(dp[state], dp[state ^ (1 << i)] + (nums1[i] ^ nums2[one_cnt - 1])) + + return dp[states - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是数组 $nums1$、$nums2$ 的长度。 +- **空间复杂度**:$O(2^n)$。 + diff --git a/docs/solutions/1800-1899/redistribute-characters-to-make-all-strings-equal.md b/docs/solutions/1800-1899/redistribute-characters-to-make-all-strings-equal.md new file mode 100644 index 00000000..c6bd6f13 --- /dev/null +++ b/docs/solutions/1800-1899/redistribute-characters-to-make-all-strings-equal.md @@ -0,0 +1,78 @@ +# [1897. 重新分配字符使所有字符串都相等](https://leetcode.cn/problems/redistribute-characters-to-make-all-strings-equal/) + +- 标签:哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [1897. 重新分配字符使所有字符串都相等 - 力扣](https://leetcode.cn/problems/redistribute-characters-to-make-all-strings-equal/) + +## 题目大意 + +**描述**:给定一个字符串数组 $words$(下标从 $0$ 开始计数)。 + +在一步操作中,需先选出两个 不同 下标 $i$ 和 $j$,其中 $words[i]$ 是一个非空字符串,接着将 $words[i]$ 中的任一字符移动到 $words[j]$ 中的 任一 位置上。 + +**要求**:如果执行任意步操作可以使 $words$ 中的每个字符串都相等,返回 $True$;否则,返回 $False$。 + +**说明**: + +- $1 <= words.length <= 100$。 +- $1 <= words[i].length <= 100$ +- $words[i]$ 由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:words = ["abc","aabc","bc"] +输出:true +解释:将 words[1] 中的第一个 'a' 移动到 words[2] 的最前面。 +使 words[1] = "abc" 且 words[2] = "abc"。 +所有字符串都等于 "abc" ,所以返回 True。 +``` + +- 示例 2: + +```python +输入:words = ["ab","a"] +输出:False +解释:执行操作无法使所有字符串都相等。 +``` + +## 解题思路 + +### 思路 1:哈希表 + +如果通过重新分配字符能够使所有字符串都相等,则所有字符串的字符需要满足: + +1. 每个字符串中字符种类相同, +2. 每个字符串中各种字符的个数相同。 + +则我们可以使用哈希表来统计字符串中字符种类及个数。具体步骤如下: + +1. 遍历单词数组 $words$ 中的所有单词 $word$。 +2. 遍历所有单词 $word$ 中的所有字符 $ch$。 +3. 使用哈希表 $cnts$ 统计字符种类及个数。 +4. 如果所有字符个数都是单词个数的倍数,则说明通过重新分配字符能够使所有字符串都相等,则返回 $True$。 +5. 否则返回 $False$。 + +### 思路 1:代码 + +```Python +class Solution: + def makeEqual(self, words: List[str]) -> bool: + size = len(words) + cnts = Counter() + for word in words: + for ch in word: + cnts[ch] += 1 + + return all(value % size == 0 for key, value in cnts.items()) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(s + |\sum|)$,其中 $s$ 为数组 $words$ 中所有单词的长度之和,$\sum$ 是字符集,本题中 $|\sum| = 26$。 +- **空间复杂度**:$O(|\sum|)$。 diff --git a/docs/solutions/1800-1899/replace-all-digits-with-characters.md b/docs/solutions/1800-1899/replace-all-digits-with-characters.md new file mode 100644 index 00000000..ebb1f902 --- /dev/null +++ b/docs/solutions/1800-1899/replace-all-digits-with-characters.md @@ -0,0 +1,82 @@ +# [1844. 将所有数字用字符替换](https://leetcode.cn/problems/replace-all-digits-with-characters/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [1844. 将所有数字用字符替换 - 力扣](https://leetcode.cn/problems/replace-all-digits-with-characters/) + +## 题目大意 + +**描述**:给定一个下标从 $0$ 开始的字符串 $s$。字符串 $s$ 的偶数下标处为小写英文字母,奇数下标处为数字。 + +定义一个函数 `shift(c, x)`,其中 $c$ 是一个字符且 $x$ 是一个数字,函数返回字母表中 $c$ 后边第 $x$ 个字符。 + +- 比如,`shift('a', 5) = 'f'`,`shift('x', 0) = 'x'`。 + +对于每个奇数下标 $i$,我们需要将数字 $s[i]$ 用 `shift(s[i - 1], s[i])` 替换。 + +**要求**:替换字符串 $s$ 中所有数字以后,将字符串 $s$ 返回。 + +**说明**: + +- 题目保证 `shift(s[i - 1], s[i])` 不会超过 `'z'`。 +- $1 \le s.length \le 100$。 +- $s$ 只包含小写英文字母和数字。 +- 对所有奇数下标处的 $i$,满足 `shift(s[i - 1], s[i]) <= 'z'` 。 + +**示例**: + +- 示例 1: + +```python +输入:s = "a1c1e1" +输出:"abcdef" +解释:数字被替换结果如下: +- s[1] -> shift('a',1) = 'b' +- s[3] -> shift('c',1) = 'd' +- s[5] -> shift('e',1) = 'f' +``` + +- 示例 2: + +```python +输入:s = "a1b2c3d4e" +输出:"abbdcfdhe" +解释:数字被替换结果如下: +- s[1] -> shift('a',1) = 'b' +- s[3] -> shift('b',2) = 'd' +- s[5] -> shift('c',3) = 'f' +- s[7] -> shift('d',4) = 'h' +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 先定义一个 `shift(ch, x)` 用于替换 `s[i]`。 +2. 将字符串转为字符串列表,定义为 $res$。 +3. 以两个字符为一组遍历字符串,对 $res[i]$ 进行修改。 +4. 将字符串列表连接起来,作为答案返回。 + +### 思路 1:代码 + +```python +class Solution: + def replaceDigits(self, s: str) -> str: + def shift(ch, x): + return chr(ord(ch) + x) + + res = list(s) + for i in range(1, len(s), 2): + res[i] = shift(res[i - 1], int(res[i])) + + return "".join(res) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1800-1899/sign-of-the-product-of-an-array.md b/docs/solutions/1800-1899/sign-of-the-product-of-an-array.md new file mode 100644 index 00000000..d5578f2f --- /dev/null +++ b/docs/solutions/1800-1899/sign-of-the-product-of-an-array.md @@ -0,0 +1,70 @@ +# [1822. 数组元素积的符号](https://leetcode.cn/problems/sign-of-the-product-of-an-array/) + +- 标签:数组、数学 +- 难度:简单 + +## 题目链接 + +- [1822. 数组元素积的符号 - 力扣](https://leetcode.cn/problems/sign-of-the-product-of-an-array/) + +## 题目大意 + +**描述**:已知函数 `signFunc(x)` 会根据 `x` 的正负返回特定值: + +- 如果 `x` 是正数,返回 `1`。 +- 如果 `x` 是负数,返回 `-1`。 +- 如果 `x` 等于 `0`,返回 `0`。 + +现在给定一个整数数组 `nums`。令 `product` 为数组 `nums` 中所有元素值的乘积。 + +**要求**:返回 `signFun(product)` 的值。 + +**说明**: + +- $1 \le nums.length \le 1000$。 +- $-100 \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```python +输入 nums = [-1,-2,-3,-4,3,2,1] +输出 1 +解释 数组中所有值的乘积是 144,且 signFunc(144) = 1 +``` + +## 解题思路 + +### 思路 1: + +题目要求的是数组所有值乘积的正负性,但是我们没必要将所有数乘起来再判断正负性。只需要统计出数组中负数的个数,再加以判断即可。 + +- 使用变量 `minus_count` 记录数组中负数个数。 +- 然后遍历数组 `nums`,对于当前元素 `num`: + - 如果为 `0`,则最终乘积肯定为 `0`,直接返回 `0`。 + - 如果小于 `0`,负数个数加 `1`。 +- 最终统计出数组中负数的个数为 `minus_count`。 +- 如果 `minus_count` 是 `2` 的倍数,则说明最终乘积为正数,返回 `1`。 +- 如果 `minus_count` 不是 `2` 的倍数,则说明最终乘积为负数,返回 `-1`。 + +## 代码 + +### 思路 1 代码: + +```python +class Solution: + def arraySign(self, nums: List[int]) -> int: + minus_count = 0 + for num in nums: + if num < 0: + minus_count += 1 + elif num == 0: + return 0 + + if minus_count % 2 == 0: + return 1 + else: + return -1 +``` + diff --git a/docs/solutions/1800-1899/sorting-the-sentence.md b/docs/solutions/1800-1899/sorting-the-sentence.md new file mode 100644 index 00000000..cd09cfb6 --- /dev/null +++ b/docs/solutions/1800-1899/sorting-the-sentence.md @@ -0,0 +1,80 @@ +# [1859. 将句子排序](https://leetcode.cn/problems/sorting-the-sentence/) + +- 标签:字符串、排序 +- 难度:简单 + +## 题目链接 + +- [1859. 将句子排序 - 力扣](https://leetcode.cn/problems/sorting-the-sentence/) + +## 题目大意 + +**描述**:给定一个句子 $s$,句子中包含的单词不超过 $9$ 个。并且句子 $s$ 中每个单词末尾添加了「从 $1$ 开始的单词位置索引」,并且将句子中所有单词打乱顺序。 + +举个例子,句子 `"This is a sentence"` 可以被打乱顺序得到 `"sentence4 a3 is2 This1"` 或者 `"is2 sentence4 This1 a3"` 。 + +**要求**:重新构造并得到原本顺序的句子。 + +**说明**: + +- **一个句子**:指的是一个序列的单词用单个空格连接起来,且开头和结尾没有任何空格。每个单词都只包含小写或大写英文字母。 +- $2 \le s.length \le 200$。 +- $s$ 只包含小写和大写英文字母、空格以及从 $1$ 到 $9$ 的数字。 +- $s$ 中单词数目为 $1$ 到 $9$ 个。 +- $s$ 中的单词由单个空格分隔。 +- $s$ 不包含任何前导或者后缀空格。 + +**示例**: + +- 示例 1: + +```python +输入:s = "is2 sentence4 This1 a3" +输出:"This is a sentence" +解释:将 s 中的单词按照初始位置排序,得到 "This1 is2 a3 sentence4" ,然后删除数字。 +``` + +- 示例 2: + +```python +输入:s = "Myself2 Me1 I4 and3" +输出:"Me Myself and I" +解释:将 s 中的单词按照初始位置排序,得到 "Me1 Myself2 and3 I4" ,然后删除数字。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 将句子 $s$ 按照空格分隔成数组 $s\underline{\hspace{0.5em}}list$。 +2. 遍历数组 $s\underline{\hspace{0.5em}}list$ 中的单词: + 1. 从单词中分割出对应单词索引 $idx$ 和对应单词 $word$。 + 2. 将单词 $word$ 存入答案数组 $res$ 对应位置 $idx - 1$ 上,即:$res[int(idx) - 1] = word$。 +3. 将答案数组用空格拼接成句子字符串,并返回。 + +### 思路 1:代码 + +```python +class Solution: + def sortSentence(self, s: str) -> str: + s_list = s.split() + size = len(s_list) + res = ["" for _ in range(size)] + for sub in s_list: + idx = "" + word = "" + for ch in sub: + if '1' <= ch <= '9': + idx += ch + else: + word += ch + res[int(idx) - 1] = word + + return " ".join(res) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m)$,其中 $m$ 为给定句子 $s$ 的长度。 +- **空间复杂度**:$O(m)$。 + diff --git a/docs/solutions/1800-1899/substrings-of-size-three-with-distinct-characters.md b/docs/solutions/1800-1899/substrings-of-size-three-with-distinct-characters.md new file mode 100644 index 00000000..50aa7568 --- /dev/null +++ b/docs/solutions/1800-1899/substrings-of-size-three-with-distinct-characters.md @@ -0,0 +1,66 @@ +# [1876. 长度为三且各字符不同的子字符串](https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/) + +- 标签:哈希表、字符串、计数、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [1876. 长度为三且各字符不同的子字符串 - 力扣](https://leetcode.cn/problems/substrings-of-size-three-with-distinct-characters/) + +## 题目大意 + +**描述**:给定搞一个字符串 $s$。 + +**要求**:返回 $s$ 中长度为 $3$ 的好子字符串的数量。如果相同的好子字符串出现多次,则每一次都应该被记入答案之中。 + +**说明**: + +- **子字符串**:指的是一个字符串中连续的字符序列。 +- **好子字符串**:如果一个字符串中不含有任何重复字符,则称这个字符串为好子字符串。 +- $1 \le s.length \le 100$。 +- $s$ 只包含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "xyzzaz" +输出:1 +解释:总共有 4 个长度为 3 的子字符串:"xyz","yzz","zza" 和 "zaz" 。 +唯一的长度为 3 的好子字符串是 "xyz" 。 +``` + +- 示例 2: + +```python +输入:s = "aababcabc" +输出:4 +解释:总共有 7 个长度为 3 的子字符串:"aab","aba","bab","abc","bca","cab" 和 "abc" 。 +好子字符串包括 "abc","bca","cab" 和 "abc" 。 +``` + +## 解题思路 + +### 思路 1:模拟 + +1. 遍历字符串 $s$ 中长度为 3 的子字符串。 +2. 判断子字符串中的字符是否有重复。如果没有重复,则答案进行计数。 +3. 遍历完输出答案。 + +### 思路 1:代码 + +```python +class Solution: + def countGoodSubstrings(self, s: str) -> int: + ans = 0 + for i in range(2, len(s)): + if s[i - 2] != s[i - 1] and s[i - 1] != s[i] and s[i - 2] != s[i]: + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git a/docs/solutions/1900-1999/add-minimum-number-of-rungs.md b/docs/solutions/1900-1999/add-minimum-number-of-rungs.md new file mode 100644 index 00000000..c5e2e49f --- /dev/null +++ b/docs/solutions/1900-1999/add-minimum-number-of-rungs.md @@ -0,0 +1,73 @@ +# [1936. 新增的最少台阶数](https://leetcode.cn/problems/add-minimum-number-of-rungs/) + +- 标签:贪心、数组 +- 难度:中等 + +## 题目链接 + +- [1936. 新增的最少台阶数 - 力扣](https://leetcode.cn/problems/add-minimum-number-of-rungs/) + +## 题目大意 + +**描述**:给定一个严格递增的整数数组 $rungs$,用于表示梯子上每一台阶的高度。当前你正站在高度为 $0$ 的地板上,并打算爬到最后一个台阶。 + +另给定一个整数 $dist$。每次移动中,你可以到达下一个距离当前位置(地板或台阶)不超过 $dist$ 高度的台阶。当前,你也可以在任何正整数高度插入尚不存在的新台阶。 + +**要求**:返回爬到最后一阶时必须添加到梯子上的最少台阶数。 + +**说明**: + +- + +**示例**: + +- 示例 1: + +```python +输入:rungs = [1,3,5,10], dist = 2 +输出:2 +解释: +现在无法到达最后一阶。 +在高度为 7 和 8 的位置增设新的台阶,以爬上梯子。 +梯子在高度为 [1,3,5,7,8,10] 的位置上有台阶。 +``` + +- 示例 2: + +```python +输入:rungs = [3,4,6,7], dist = 2 +输出:1 +解释: +现在无法从地板到达梯子的第一阶。 +在高度为 1 的位置增设新的台阶,以爬上梯子。 +梯子在高度为 [1,3,4,6,7] 的位置上有台阶。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + 模拟 + +1. 遍历梯子的每一层台阶。 +2. 计算每一层台阶与上一层台阶之间的差值 $diff$。 +3. 每层最少需要新增的台阶数为 $\lfloor \frac{diff - 1}{dist} \rfloor$,将其计入答案 $ans$ 中。 +4. 遍历完返回答案。 + +### 思路 1:代码 + +```Python +class Solution: + def addRungs(self, rungs: List[int], dist: int) -> int: + ans, cur = 0, 0 + for h in rungs: + diff = h - cur + ans += (diff - 1) // dist + cur = h + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $rungs$ 的长度。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md b/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md new file mode 100644 index 00000000..0ea0090d --- /dev/null +++ b/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md @@ -0,0 +1,68 @@ +# [1941. 检查是否所有字符出现次数相同](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) + +- 标签:哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [1941. 检查是否所有字符出现次数相同 - 力扣](https://leetcode.cn/problems/check-if-all-characters-have-equal-number-of-occurrences/) + +## 题目大意 + +**描述**:给定一个字符串 $s$。如果 $s$ 中出现过的所有字符的出现次数相同,那么我们称字符串 $s$ 是「好字符串」。 + +**要求**:如果 $s$ 是一个好字符串,则返回 `True`,否则返回 `False`。 + +**说明**: + +- $1 \le s.length \le 1000$。 +- $s$ 只包含小写英文字母。 + +**示例**: + +- 示例 1: + +```python +输入:s = "abacbc" +输出:true +解释:s 中出现过的字符为 'a','b' 和 'c' 。s 中所有字符均出现 2 次。 +``` + +- 示例 2: + +```python +输入:s = "aaabb" +输出:false +解释:s 中出现过的字符为 'a' 和 'b' 。 +'a' 出现了 3 次,'b' 出现了 2 次,两者出现次数不同。 +``` + +## 解题思路 + +### 思路 1:哈希表 + +1. 使用哈希表记录字符串 $s$ 中每个字符的频数。 +2. 然后遍历哈希表中的键值对,检测每个字符的频数是否相等。 +3. 如果发现频数不相等,则直接返回 `False`。 +4. 如果检查完发现所有频数都相等,则返回 `True`。 + +### 思路 1:代码 + +```python +class Solution: + def areOccurrencesEqual(self, s: str) -> bool: + counter = Counter(s) + flag = -1 + for key in counter: + if flag == -1: + flag = counter[key] + else: + if flag != counter[key]: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/1900-1999/concatenation-of-array.md b/docs/solutions/1900-1999/concatenation-of-array.md new file mode 100644 index 00000000..03e2d99e --- /dev/null +++ b/docs/solutions/1900-1999/concatenation-of-array.md @@ -0,0 +1,89 @@ +# [1929. 数组串联](https://leetcode.cn/problems/concatenation-of-array/) + +- 标签:数组 +- 难度:简单 + +## 题目链接 + +- [1929. 数组串联 - 力扣](https://leetcode.cn/problems/concatenation-of-array/) + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的整数数组 $nums$。 + +**要求**:构建一个长度为 $2 \times n$ 的答案数组 $ans$,答案数组下标从 $0$ 开始计数 ,对于所有 $0 \le i < n$ 的 $i$ ,满足下述所有要求: + +- $ans[i] == nums[i]$。 +- $ans[i + n] == nums[i]$。 + +具体而言,$ans$ 由两个 $nums$ 数组「串联」形成。 + +**说明**: + +- $n == nums.length$。 +- $1 \le n \le 1000$。 +- $1 \le nums[i] \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,1] +输出:[1,2,1,1,2,1] +解释:数组 ans 按下述方式形成: +- ans = [nums[0],nums[1],nums[2],nums[0],nums[1],nums[2]] +- ans = [1,2,1,1,2,1] +``` + +- 示例 2: + +```python +输入:nums = [1,3,2,1] +输出:[1,3,2,1,1,3,2,1] +解释:数组 ans 按下述方式形成: +- ans = [nums[0],nums[1],nums[2],nums[3],nums[0],nums[1],nums[2],nums[3]] +- ans = [1,3,2,1,1,3,2,1] +``` + +## 解题思路 + +### 思路 1:按要求模拟 + +1. 定义一个数组变量(列表)$ans$ 作为答案数组。 +2. 然后按顺序遍历两次数组 $nums$ 中的元素,并依次添加到 $ans$ 的尾部。最后返回 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def getConcatenation(self, nums: List[int]) -> List[int]: + ans = [] + for num in nums: + ans.append(num) + for num in nums: + ans.append(num) + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 + +### 思路 2:利用运算符 + +Python 中可以直接利用 `+` 号运算符将两个列表快速进行串联。即 `return nums + nums`。 + +### 思路 2:代码 + +```python +class Solution: + def getConcatenation(self, nums: List[int]) -> List[int]: + return nums + nums +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(n)$。如果算上答案数组的空间占用,则空间复杂度为 $O(n)$。不算上则空间复杂度为 $O(1)$。 diff --git a/docs/solutions/1900-1999/count-square-sum-triples.md b/docs/solutions/1900-1999/count-square-sum-triples.md new file mode 100644 index 00000000..1a8c714c --- /dev/null +++ b/docs/solutions/1900-1999/count-square-sum-triples.md @@ -0,0 +1,68 @@ +# [1925. 统计平方和三元组的数目](https://leetcode.cn/problems/count-square-sum-triples/) + +- 标签:数学、枚举 +- 难度:简单 + +## 题目链接 + +- [1925. 统计平方和三元组的数目 - 力扣](https://leetcode.cn/problems/count-square-sum-triples/) + +## 题目大意 + +**描述**:给你一个整数 $n$。 + +**要求**:请你返回满足 $1 \le a, b, c \le n$ 的平方和三元组的数目。 + +**说明**: + +- **平方和三元组**:指的是满足 $a^2 + b^2 = c^2$ 的整数三元组 $(a, b, c)$。 +- $1 \le n \le 250$。 + +**示例**: + +- 示例 1: + +```python +输入 n = 5 +输出 2 +解释 平方和三元组为 (3,4,5) 和 (4,3,5)。 +``` + +- 示例 2: + +```python +输入:n = 10 +输出:4 +解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n$,并且是完全平方数。 + +在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 `cnt`。如果符合要求,则将计数 `cnt` 加 $1$。最终,我们返回该数目作为答案。 + +利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。 + +- 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 $1$,所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。 + +### 思路 1:代码 + +```python +class Solution: + def countTriples(self, n: int) -> int: + cnt = 0 + for a in range(1, n + 1): + for b in range(1, n + 1): + c = int(sqrt(a * a + b * b + 1)) + if c <= n and a * a + b * b == c * c: + cnt += 1 + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1900-1999/eliminate-maximum-number-of-monsters.md b/docs/solutions/1900-1999/eliminate-maximum-number-of-monsters.md new file mode 100644 index 00000000..5ddee890 --- /dev/null +++ b/docs/solutions/1900-1999/eliminate-maximum-number-of-monsters.md @@ -0,0 +1,87 @@ +# [1921. 消灭怪物的最大数量](https://leetcode.cn/problems/eliminate-maximum-number-of-monsters/) + +- 标签:贪心、数组、排序 +- 难度:中等 + +## 题目链接 + +- [1921. 消灭怪物的最大数量 - 力扣](https://leetcode.cn/problems/eliminate-maximum-number-of-monsters/) + +## 题目大意 + +**描述**:你正在玩一款电子游戏,在游戏中你需要保护城市免受怪物侵袭。给定一个下标从 $0$ 开始且大小为 $n$ 的整数数组 $dist$,其中 $dist[i]$ 是第 $i$ 个怪物与城市的初始距离(单位:米)。 + +怪物以恒定的速度走向城市。每个怪物的速度都以一个长度为 $n$ 的整数数组 $speed$ 表示,其中 $speed[i]$ 是第 $i$ 个怪物的速度(单位:千米/分)。 + +你有一种武器,一旦充满电,就可以消灭 一个 怪物。但是,武器需要 一分钟 才能充电。武器在游戏开始时是充满电的状态,怪物从 第 $0$ 分钟时开始移动。 + +一旦任一怪物到达城市,你就输掉了这场游戏。如果某个怪物 恰好 在某一分钟开始时到达城市(距离表示为 $0$),这也会被视为输掉 游戏,在你可以使用武器之前,游戏就会结束。 + +**要求**:返回在你输掉游戏前可以消灭的怪物的最大数量。如果你可以在所有怪物到达城市前将它们全部消灭,返回 $n$。 + +**说明**: + +- + +**示例**: + +- 示例 1: + +```python +输入:dist = [1,3,4], speed = [1,1,1] +输出:3 +解释: +第 0 分钟开始时,怪物的距离是 [1,3,4],你消灭了第一个怪物。 +第 1 分钟开始时,怪物的距离是 [X,2,3],你消灭了第二个怪物。 +第 3 分钟开始时,怪物的距离是 [X,X,2],你消灭了第三个怪物。 +所有 3 个怪物都可以被消灭。 +``` + +- 示例 2: + +```python +输入:dist = [1,1,2,3], speed = [1,1,1,1] +输出:1 +解释: +第 0 分钟开始时,怪物的距离是 [1,1,2,3],你消灭了第一个怪物。 +第 1 分钟开始时,怪物的距离是 [X,0,1,2],所以你输掉了游戏。 +你只能消灭 1 个怪物。 +``` + +## 解题思路 + +### 思路 1:排序 + 贪心算法 + +对于第 $i$ 个怪物,最晚可被消灭的时间为 $times[i] = \lfloor \frac{dist[i] - 1}{speed[i]} \rfloor$。我们可以根据以上公式,将所有怪物最晚可被消灭时间存入数组 $times$ 中,然后对 $times$ 进行升序排序。 + +然后遍历数组 $times$,对于第 $i$ 个怪物: + +1. 如果 $times[i] < i$,则说明第 $i$ 个怪物无法被消灭,直接返回 $i$ 即可。 +2. 如果 $times[i] \ge i$,则说明第 $i$ 个怪物可以被消灭,继续向下遍历。 + +如果遍历完数组 $times$,则说明所有怪物都可以被消灭,则返回 $n$。 + +### 思路 1:代码 + +```Python +class Solution: + def eliminateMaximum(self, dist: List[int], speed: List[int]) -> int: + times = [] + for d, s in zip(dist, speed): + time = (d - 1) // s + times.append(time) + times.sort() + + size = len(times) + for i in range(size): + if times[i] < i: + return i + + return size +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $dist$ 的长度。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/1900-1999/find-the-middle-index-in-array.md b/docs/solutions/1900-1999/find-the-middle-index-in-array.md new file mode 100644 index 00000000..e409e92d --- /dev/null +++ b/docs/solutions/1900-1999/find-the-middle-index-in-array.md @@ -0,0 +1,74 @@ +# [1991. 找到数组的中间位置](https://leetcode.cn/problems/find-the-middle-index-in-array/) + +- 标签:数组、前缀和 +- 难度:简单 + +## 题目链接 + +- [1991. 找到数组的中间位置 - 力扣](https://leetcode.cn/problems/find-the-middle-index-in-array/) + +## 题目大意 + +**描述**:给定一个下标从 $0$ 开始的整数数组 $nums$。 + +**要求**:返回最左边的中间位置 $middleIndex$(也就是所有可能中间位置下标做小的一个)。如果找不到这样的中间位置,则返回 $-1$。 + +**说明**: + +- **中间位置 $middleIndex$**:满足 $nums[0] + nums[1] + … + nums[middleIndex - 1] == nums[middleIndex + 1] + nums[middleIndex + 2] + … + nums[nums.length - 1]$ 的数组下标。 +- 如果 $middleIndex == 0$,左边部分的和定义为 $0$。类似的,如果 $middleIndex == nums.length - 1$,右边部分的和定义为 $0$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [2,3,-1,8,4] +输出:3 +解释: +下标 3 之前的数字和为:2 + 3 + -1 = 4 +下标 3 之后的数字和为:4 = 4 +``` + +- 示例 2: + +```python +输入:nums = [1,-1,4] +输出:2 +解释: +下标 2 之前的数字和为:1 + -1 = 0 +下标 2 之后的数字和为:0 +``` + +## 解题思路 + +### 思路 1:前缀和 + +1. 先遍历一遍数组,求出数组中全部元素和为 $total$。 +2. 再遍历一遍数组,使用变量 $prefix\underline{\hspace{0.5em}}sum$ 为前 $i$ 个元素和。 +3. 当遍历到第 $i$ 个元素时,其数组左侧元素之和为 $prefix\underline{\hspace{0.5em}}sum$,右侧元素和为 $total - prefix\underline{\hspace{0.5em}}sum - nums[i]$。 + 1. 如果左右元素之和相等,即 $prefix\underline{\hspace{0.5em}}sum == total - prefix\underline{\hspace{0.5em}}sum - nums[i]$($2 \times prefix\underline{\hspace{0.5em}}sum + nums[i] == total$) 时,$i$ 为中间位置。此时返回 $i$。 + 2. 如果不满足,则继续累加当前元素到 $prefix\underline{\hspace{0.5em}}sum$ 中,继续向后遍历。 +4. 如果找不到符合要求的中间位置,则返回 $-1$。 + +### 思路 1:代码 + +```python +class Solution: + def findMiddleIndex(self, nums: List[int]) -> int: + total = sum(nums) + + prefix_sum = 0 + for i in range(len(nums)): + if 2 * prefix_sum + nums[i] == total: + return i + prefix_sum += nums[i] + + return -1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/1900-1999/index.md b/docs/solutions/1900-1999/index.md new file mode 100644 index 00000000..16d32f62 --- /dev/null +++ b/docs/solutions/1900-1999/index.md @@ -0,0 +1,14 @@ +## 本章内容 + +- [1903. 字符串中的最大奇数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/largest-odd-number-in-string.md) +- [1921. 消灭怪物的最大数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/eliminate-maximum-number-of-monsters.md) +- [1925. 统计平方和三元组的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/count-square-sum-triples.md) +- [1929. 数组串联](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/concatenation-of-array.md) +- [1930. 长度为 3 的不同回文子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/unique-length-3-palindromic-subsequences.md) +- [1936. 新增的最少台阶数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/add-minimum-number-of-rungs.md) +- [1941. 检查是否所有字符出现次数相同](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/check-if-all-characters-have-equal-number-of-occurrences.md) +- [1947. 最大兼容性评分和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/maximum-compatibility-score-sum.md) +- [1984. 学生分数的最小差值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores.md) +- [1986. 完成任务的最少工作时间段](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md) +- [1991. 找到数组的中间位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/find-the-middle-index-in-array.md) +- [1994. 好子集的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/the-number-of-good-subsets.md) diff --git a/docs/solutions/1900-1999/largest-odd-number-in-string.md b/docs/solutions/1900-1999/largest-odd-number-in-string.md new file mode 100644 index 00000000..da1e2bdb --- /dev/null +++ b/docs/solutions/1900-1999/largest-odd-number-in-string.md @@ -0,0 +1,63 @@ +# [1903. 字符串中的最大奇数](https://leetcode.cn/problems/largest-odd-number-in-string/) + +- 标签:贪心、数学、字符串 +- 难度:简单 + +## 题目链接 + +- [1903. 字符串中的最大奇数 - 力扣](https://leetcode.cn/problems/largest-odd-number-in-string/) + +## 题目大意 + +**描述**:给定一个字符串 $num$,表示一个大整数。 + +**要求**:在字符串 $num$ 的所有非空子字符串中找出值最大的奇数,并以字符串形式返回。如果不存在奇数,则返回一个空字符串 `""`。 + +**说明**: + +- **子字符串**:指的是字符串中一个连续的字符序列。 +- $1 \le num.length \le 10^5$ +- $num$ 仅由数字组成且不含前导零。 + +**示例**: + +- 示例 1: + +```python +输入:num = "52" +输出:"5" +解释:非空子字符串仅有 "5"、"2" 和 "52" 。"5" 是其中唯一的奇数。 +``` + +- 示例 2: + +```python +输入:num = "4206" +输出:"" +解释:在 "4206" 中不存在奇数。 +``` + +## 解题思路 + +### 思路 1:贪心算法 + +如果某个数 $x$ 为奇数,则 $x$ 末尾位上的数字一定为奇数。那么我们只需要在末尾为奇数的字符串中考虑最大的奇数即可。显而易见的是,最大的奇数一定是长度最长的那个。所以我们只需要逆序遍历字符串,找到第一个奇数,从整个字符串开始位置到该奇数位置所代表的整数,就是最大的奇数。具体步骤如下: + +1. 逆序遍历字符串 $s$。 +2. 找到第一个奇数位置 $i$,则 $num[0: i + 1]$ 为最大的奇数,将其作为答案返回。 + +### 思路 1:代码 + +```python +class Solution: + def largestOddNumber(self, num: str) -> str: + for i in range(len(num) - 1, -1, -1): + if int(num[i]) % 2 == 1: + return num[0: i + 1] + return "" +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/1900-1999/maximum-compatibility-score-sum.md b/docs/solutions/1900-1999/maximum-compatibility-score-sum.md new file mode 100644 index 00000000..18955b88 --- /dev/null +++ b/docs/solutions/1900-1999/maximum-compatibility-score-sum.md @@ -0,0 +1,116 @@ +# [1947. 最大兼容性评分和](https://leetcode.cn/problems/maximum-compatibility-score-sum/) + +- 标签:位运算、数组、动态规划、回溯、状态压缩 +- 难度:中等 + +## 题目链接 + +- [1947. 最大兼容性评分和 - 力扣](https://leetcode.cn/problems/maximum-compatibility-score-sum/) + +## 题目大意 + +**描述**:有一份由 $n$ 个问题组成的调查问卷,每个问题的答案只有 $0$ 或 $1$。将这份调查问卷分发给 $m$ 名学生和 $m$ 名老师,学生和老师的编号都是 $0 \sim m - 1$。现在给定一个二维整数数组 $students$ 表示 $m$ 名学生给出的答案,其中 $studuents[i][j]$ 表示第 $i$ 名学生第 $j$ 个问题给出的答案。再给定一个二维整数数组 $mentors$ 表示 $m$ 名老师给出的答案,其中 $mentors[i][j]$ 表示第 $i$ 名导师第 $j$ 个问题给出的答案。 + +每个学生要和一名导师互相配对。配对的学生和导师之间的兼容性评分等于学生和导师答案相同的次数。 + +- 例如,学生答案为 $[1, 0, 1]$,而导师答案为 $[0, 0, 1]$,那么他们的兼容性评分为 $2$,因为只有第 $2$ 个和第 $3$ 个答案相同。 + +**要求**:找出最优的学生与导师的配对方案,以最大程度上提高所有学生和导师的兼容性评分和。然后返回可以得到的最大兼容性评分和。 + +**说明**: + +- $m == students.length == mentors.length$。 +- $n == students[i].length == mentors[j].length$。 +- $1 \le m, n \le 8$。 +- $students[i][k]$ 为 $0$ 或 $1$。 +- $mentors[j][k]$ 为 $0$ 或 $1$。 + +**示例**: + +- 示例 1: + +```python +输入:students = [[1,1,0],[1,0,1],[0,0,1]], mentors = [[1,0,0],[0,0,1],[1,1,0]] +输出:8 +解释:按下述方式分配学生和导师: +- 学生 0 分配给导师 2 ,兼容性评分为 3。 +- 学生 1 分配给导师 0 ,兼容性评分为 2。 +- 学生 2 分配给导师 1 ,兼容性评分为 3。 +最大兼容性评分和为 3 + 2 + 3 = 8。 +``` + +- 示例 2: + +```python +输入:students = [[0,0],[0,0],[0,0]], mentors = [[1,1],[1,1],[1,1]] +输出:0 +解释:任意学生与导师配对的兼容性评分都是 0。 +``` + +## 解题思路 + +### 思路 1:状压 DP + +因为 $m$、$n$ 的范围都是 $[1, 8]$,所以我们可以使用「状态压缩」的方式来表示学生的分配情况。即使用一个 $m$ 位长度的二进制数 $state$ 来表示每一位老师是否被分配了学生。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 位老师被分配了学生,如果 $state$ 的第 $i$ 位为 $0$,则表示第 $i$ 位老师没有分配到学生。 + +这样,我们就可以通过动态规划的方式来解决这道题。 + +###### 1. 划分阶段 + +按照学生的分配情况进行阶段划分。 + +###### 2. 定义状态 + +定义当前学生的分配情况为 $state$,$state$ 中包含 $count(state)$ 个 $1$,表示有 $count(state)$ 个老师被分配了学生。 + +则可以定义状态 $dp[state]$ 表示为:当前老师被分配学生的状态为 $state$,其中有 $count(state)$ 个老师被分配了学生的情况下,可以得到的最大兼容性评分和。 + +###### 3. 状态转移方程 + +对于当前状态 $state$,肯定是从比 $state$ 少选一个老师被分配的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以得到的最大兼容性评分和,赋值给 $dp[state]$。 + +即状态转移方程为:$dp[state] = max(dp[state], \quad dp[state \oplus (1 \text{ <}\text{< } i)] + score[i][one\underline{\hspace{0.5em}}cnt - 1])$,其中: + +1. $state$ 第 $i$ 位一定为 $1$。 +2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 +3. $scores[i][one\underline{\hspace{0.5em}}cnt - 1]$ 为第 $i$ 名老师分配到第 $one\underline{\hspace{0.5em}}cnt - 1$ 名学生的兼容性评分。 + +关于每位老师与每位同学之间的兼容性评分,我们可以事先通过一个 $m \times m \times n$ 的三重循环计算得出,并且存入到 $m \times m$ 大小的二维矩阵 $scores$ 中。 + +###### 4. 初始条件 + +- 初始每个老师都没有分配到学生的状态下,可以得到的最兼容性评分和为 $0$,即 $dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当前老师被分配学生的状态为 $state$,其中有 $count(state)$ 个老师被分配了学生的情况下,可以得到的最大兼容性评分和。所以最终结果为 $dp[states - 1]$,其中 $states = 1 \text{ <}\text{< } m$。 + +### 思路 1:代码 + +```python +class Solution: + def maxCompatibilitySum(self, students: List[List[int]], mentors: List[List[int]]) -> int: + m, n = len(students), len(students[0]) + scores = [[0 for _ in range(m)] for _ in range(m)] + + for i in range(m): + for j in range(m): + for k in range(n): + scores[i][j] += (students[i][k] == mentors[j][k]) + + states = 1 << m + dp = [0 for _ in range(states)] + + for state in range(states): + one_cnt = bin(state).count('1') + for i in range(m): + if (state >> i) & 1: + dp[state] = max(dp[state], dp[state ^ (1 << i)] + scores[i][one_cnt - 1]) + return dp[states - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m^2 \times n + m \times 2^m)$。 +- **空间复杂度**:$O(2^m)$。 + diff --git a/docs/solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores.md b/docs/solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores.md new file mode 100644 index 00000000..48b819a5 --- /dev/null +++ b/docs/solutions/1900-1999/minimum-difference-between-highest-and-lowest-of-k-scores.md @@ -0,0 +1,87 @@ +# [1984. 学生分数的最小差值](https://leetcode.cn/problems/minimum-difference-between-highest-and-lowest-of-k-scores/) + +- 标签:数组、排序、滑动窗口 +- 难度:简单 + +## 题目链接 + +- [1984. 学生分数的最小差值 - 力扣](https://leetcode.cn/problems/minimum-difference-between-highest-and-lowest-of-k-scores/) + +## 题目大意 + +**描述**:给定一个下标从 $0$ 开始的整数数组 $nums$,其中 $nums[i]$ 表示第 $i$ 名学生的分数。另给定一个整数 $k$。 + +**要求**:从数组中选出任意 $k$ 名学生的分数,使这 $k$ 个分数间最高分和最低分的差值达到最小化。返回可能的最小差值 。 + +**说明**: + +- $1 \le k \le nums.length \le 1000$。 +- $0 \le nums[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [90], k = 1 +输出:0 +解释:选出 1 名学生的分数,仅有 1 种方法: +- [90] 最高分和最低分之间的差值是 90 - 90 = 0 +可能的最小差值是 0 +``` + +- 示例 2: + +```python +输入:nums = [9,4,1,7], k = 2 +输出:2 +解释:选出 2 名学生的分数,有 6 种方法: +- [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5 +- [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8 +- [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2 +- [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3 +- [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3 +- [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6 +可能的最小差值是 2 +``` + +## 解题思路 + +### 思路 1:排序 + 滑动窗口 + +如果想要最小化选择的 $k$ 名学生中最高分与最低分的差值,我们应该在排序后的数组中连续选择 $k$ 名学生。这是因为如果将连续 $k$ 名学生中的某位学生替换成不连续的学生,其最高分 / 最低分一定会发生变化,并且一定会使最高分变得最高 / 最低分变得最低。从而导致差值增大。 + +因此,最优方案一定是在排序后的数组中连续选择 $k$ 名学生中的所有情况中的其中一种。 + +这样,我们可以先对数组 $nums$ 进行升序排序。然后使用一个固定长度为 $k$ 的滑动窗口计算连续选择 $k$ 名学生的最高分与最低分的差值。并记录下最小的差值 $ans$,最后作为答案并返回结果。 + +### 思路 1:代码 + +```Python +class Solution: + def minimumDifference(self, nums: List[int], k: int) -> int: + nums.sort() + ans = float('inf') + for i in range(k - 1, len(nums)): + ans = min(ans, nums[i] - nums[i - k + 1]) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$,其中 $n$ 为数组 $nums$ 的长度。 +- **空间复杂度**:$O(1)$。 + +### 思路 2: + +### 思路 2:代码 + +```python +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**: +- **空间复杂度**: + diff --git a/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md b/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md new file mode 100644 index 00000000..035ff4a9 --- /dev/null +++ b/docs/solutions/1900-1999/minimum-number-of-work-sessions-to-finish-the-tasks.md @@ -0,0 +1,86 @@ +# [1986. 完成任务的最少工作时间段](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) + +- 标签:位运算、数组、动态规划、回溯、状态压缩 +- 难度:中等 + +## 题目链接 + +- [1986. 完成任务的最少工作时间段 - 力扣](https://leetcode.cn/problems/minimum-number-of-work-sessions-to-finish-the-tasks/) + +## 题目大意 + +**描述**:给定一个整数数组 $tasks$ 代表需要完成的任务。 其中 $tasks[i]$ 表示第 $i$ 个任务需要花费的时长(单位为小时)。再给定一个整数 $sessionTime$,代表在一个工作时段中,最多可以连续工作的小时数。在连续工作至多 $sessionTime$ 小时后,需要进行休息。 + +现在需要按照如下条件完成给定任务: + +1. 如果你在某一个时间段开始一个任务,你需要在同一个时间段完成它。 +2. 完成一个任务后,你可以立马开始一个新的任务。 +3. 你可以按任意顺序完成任务。 + +**要求**:按照上述要求,返回完成所有任务所需要的最少数目的工作时间段。 + +**说明**: + +- $n == tasks.length$。 +- $1 \le n \le 14$。 +- $1 \le tasks[i] \le 10$。 +- $max(tasks[i]) \le sessionTime \le 15$。 + +**示例**: + +- 示例 1: + +```python +输入:tasks = [1,2,3], sessionTime = 3 +输出:2 +解释:你可以在两个工作时间段内完成所有任务。 +- 第一个工作时间段:完成第一和第二个任务,花费 1 + 2 = 3 小时。 +- 第二个工作时间段:完成第三个任务,花费 3 小时。 +``` + +- 示例 2: + +```python +输入:tasks = [3,1,3,1,1], sessionTime = 8 +输出:2 +解释:你可以在两个工作时间段内完成所有任务。 +- 第一个工作时间段:完成除了最后一个任务以外的所有任务,花费 3 + 1 + 3 + 1 = 8 小时。 +- 第二个工作时间段,完成最后一个任务,花费 1 小时。 +``` + +## 解题思路 + +### 思路 1:状压 DP + +### 思路 1:代码 + +```python +class Solution: + def minSessions(self, tasks: List[int], sessionTime: int) -> int: + size = len(tasks) + states = 1 << size + + prefix_sum = [0 for _ in range(states)] + for state in range(states): + for i in range(size): + if (state >> i) & 1: + prefix_sum[state] = prefix_sum[state ^ (1 << i)] + tasks[i] + break + + dp = [float('inf') for _ in range(states)] + dp[0] = 0 + for state in range(states): + sub = state + while sub > 0: + if prefix_sum[sub] <= sessionTime: + dp[state] = min(dp[state], dp[state ^ sub] + 1) + sub = (sub - 1) & state + + return dp[states - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**: +- **空间复杂度**: + diff --git a/docs/solutions/1900-1999/the-number-of-good-subsets.md b/docs/solutions/1900-1999/the-number-of-good-subsets.md new file mode 100644 index 00000000..fbca0c07 --- /dev/null +++ b/docs/solutions/1900-1999/the-number-of-good-subsets.md @@ -0,0 +1,140 @@ +# [1994. 好子集的数目](https://leetcode.cn/problems/the-number-of-good-subsets/) + +- 标签:位运算、数组、数学、动态规划、状态压缩 +- 难度:困难 + +## 题目链接 + +- [1994. 好子集的数目 - 力扣](https://leetcode.cn/problems/the-number-of-good-subsets/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:返回 $nums$ 中不同的好子集的数目对 $10^9 + 7$ 取余的结果。 + +**说明**: + +- **子集**:通过删除 $nums$ 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集。 + +- **好子集**:如果 $nums$ 的一个子集中,所有元素的乘积可以表示为一个或多个互不相同的质数的乘积,那么我们称它为好子集。 + - 比如,如果 $nums = [1, 2, 3, 4]$: + - $[2, 3]$,$[1, 2, 3]$ 和 $[1, 3]$ 是好子集,乘积分别为 $6 = 2 \times 3$ ,$6 = 2 \times 3$ 和 $3 = 3$。 + - $[1, 4]$ 和 $[4]$ 不是好子集,因为乘积分别为 $4 = 2 \times 2$ 和 $4 = 2 \times 2$。 + +- $1 \le nums.length \le 10^5$。 +- $1 \le nums[i] \le 30$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4] +输出:6 +解释:好子集为: +- [1,2]:乘积为 2,可以表示为质数 2 的乘积。 +- [1,2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。 +- [1,3]:乘积为 3,可以表示为质数 3 的乘积。 +- [2]:乘积为 2,可以表示为质数 2 的乘积。 +- [2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。 +- [3]:乘积为 3,可以表示为质数 3 的乘积。 +``` + +- 示例 2: + +```python +输入:nums = [4,2,3,15] +输出:5 +解释:好子集为: +- [2]:乘积为 2,可以表示为质数 2 的乘积。 +- [2,3]:乘积为 6,可以表示为互不相同质数 2 和 3 的乘积。 +- [2,15]:乘积为 30,可以表示为互不相同质数 2,3 和 5 的乘积。 +- [3]:乘积为 3,可以表示为质数 3 的乘积。 +- [15]:乘积为 15,可以表示为互不相同质数 3 和 5 的乘积。 +``` + +## 解题思路 + +### 思路 1:状态压缩 DP + +根据题意可以看出: + +1. 虽然 $nums$ 的长度是 $[1, 10^5]$,但是其值域范围只有 $[1, 30]$,则我们可以将 $[1, 30]$ 的数分为 $3$ 类: + 1. 质数:$[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]$(共 $10$ 个数)。由于好子集的乘积拆解后的质因子只能包含这 $10$ 个,我们可以使用一个数组 $primes$ 记录下这 $10$ 个质数,将好子集的乘积拆解为质因子后,每个 $primes[i]$ 最多出现一次。 + 2. 非质数:$[4, 6, 8, 9, 10, 12, 14, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30]$。非质数肯定不会出现在好子集的乘积拆解后的质因子中。 + 3. 特殊的数:$[1]$。对于一个好子集而言,无论向中间添加多少个 $1$,得到的新子集仍是好子集。 +2. 分类完成后,由于 $[1, 30]$ 中只有 $10$ 个质数,因此我们可以使用一个长度为 $10$ 的二进制数 $state$ 来表示 $primes$ 中质因数的选择情况。其中,如果 $state$ 第 $i$ 位为 $1$,则说明第 $i$ 个质因数 $primes[i]$ 被使用过;如果 $state$ 第 $i$ 位为 $0$,则说明第 $i$ 个质因数 $primes[i]$ 没有被使用过。 +3. 题目规定值相同,但是下标不同的子集视为不同子集,那么我们可先统计出 $nums$ 中每个数 $nums[i]$ 的出现次数,将其存入 $cnts$ 数组中,其中 $cnts[num]$ 表示 $num$ 出现的次数。这样在统计方案时,直接计算出 $num$ 的方案数,再乘以 $cnts[num]$ 即可。 + +接下来,我们就可以使用「动态规划」的方式来解决这道题目了。 + +###### 1. 划分阶段 + +按照质因数的选择情况进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[state]$ 表示为:当质因数选择的情况为 $state$ 时,好子集的数目。 + +###### 3. 状态转移方程 + +对于 $nums$ 中的每个数 $num$,其对应出现次数为 $cnt$。我们可以通过试除法,将 $num$ 分解为不同的质因数,并使用「状态压缩」的方式,用一个二进制数 $cur\underline{\hspace{0.5em}}state$ 来表示当前数 $num$ 中使用了哪些质因数。然后枚举所有状态,找到与 $cur\underline{\hspace{0.5em}}state$ 不冲突的状态 $state$(也就是除了 $cur\underline{\hspace{0.5em}}state$ 中选择的质因数外,选择的其他质因数情况,比如 $cur\underline{\hspace{0.5em}}state$ 选择了 $2$ 和 $5$,则枚举不选择 $2$ 和 $5$ 的状态)。 + +此时,状态转移方程为:$dp[state | cur\underline{\hspace{0.5em}}state] = \sum (dp[state] \times cnt) \mod MOD , \quad state \text{ \& } cur\underline{\hspace{0.5em}}state == 0$ + +###### 4. 初始条件 + +- 当 $state == 0$,所选质因数为空时,空集为好子集,则 $dp[0] = 1$。同时,对于一个好子集而言,无论向中间添加多少个 $1$,得到的新子集仍是好子集,所以对于空集来说,可以对应出 $2^{cnts[1]}$ 个方案,则最终 $dp[0] = 2^{cnts[1]}$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:当质因数的选择的情况为 $state$ 时,好子集的数目。 所以最终结果为所有状态下的好子集数目累积和。所以我们可以枚举所有状态,并记录下所有好子集的数目和,就是最终结果。 + +### 思路 1:代码 + +```python +class Solution: + def numberOfGoodSubsets(self, nums: List[int]) -> int: + MOD = 10 ** 9 + 7 + primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + + cnts = Counter(nums) + dp = [0 for _ in range(1 << len(primes))] + dp[0] = pow(2, cnts[1], MOD) # 计算 1 + + # num 分解质因数 + for num, cnt in cnts.items(): # 遍历 nums 中所有数及其频数 + if num == 1: # 跳过 1 + continue + + flag = True # 检查 num 的质因数是否都不超过 1 + cur_num = num + cur_state = 0 + for i, prime in enumerate(primes): # 对 num 进行试除 + cur_cnt = 0 + while cur_num % prime == 0: + cur_cnt += 1 + cur_state |= 1 << i + cur_num //= prime + if cur_cnt > 1: # 当前质因数超过 1,则 num 不能添加到子集中,跳过 + flag = False + break + if not flag: + continue + + for state in range(1 << len(primes)): + if state & cur_state == 0: # 只有当前选择状态与前一状态不冲突时,才能进行动态转移 + dp[state | cur_state] = (dp[state | cur_state] + dp[state] * cnt) % MOD + + ans = 0 # 统计所有非空集合的方案数 + for i in range(1, 1 << len(primes)): + ans = (ans + dp[i]) % MOD + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n + m \times 2^p)$,其中 $n$ 为数组 $nums$ 的元素个数,$m$ 为 $nums$ 的最大值,$p$ 为 $[1, 30]$ 中的质数个数。 +- **空间复杂度**:$O(2^p)$。 diff --git a/docs/solutions/1900-1999/unique-length-3-palindromic-subsequences.md b/docs/solutions/1900-1999/unique-length-3-palindromic-subsequences.md new file mode 100644 index 00000000..f02dd0fc --- /dev/null +++ b/docs/solutions/1900-1999/unique-length-3-palindromic-subsequences.md @@ -0,0 +1,95 @@ +# [1930. 长度为 3 的不同回文子序列](https://leetcode.cn/problems/unique-length-3-palindromic-subsequences/) + +- 标签:哈希表、字符串、前缀和 +- 难度:中等 + +## 题目链接 + +- [1930. 长度为 3 的不同回文子序列 - 力扣](https://leetcode.cn/problems/unique-length-3-palindromic-subsequences/) + +## 题目大意 + +**描述**:给定一个人字符串 $s$。 + +**要求**:返回 $s$ 中长度为 $s$ 的不同回文子序列的个数。即便存在多种方法来构建相同的子序列,但相同的子序列只计数一次。 + +**说明**: + +- **回文**:指正着读和反着读一样的字符串。 +- **子序列**:由原字符串删除其中部分字符(也可以不删除)且不改变剩余字符之间相对顺序形成的一个新字符串。 + - 例如,`"ace"` 是 `"abcde"` 的一个子序列。 + +- $3 \le s.length \le 10^5$。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```python +输入:s = "aabca" +输出:3 +解释:长度为 3 的 3 个回文子序列分别是: +- "aba" ("aabca" 的子序列) +- "aaa" ("aabca" 的子序列) +- "aca" ("aabca" 的子序列) +``` + +- 示例 2: + +```python +输入:s = "bbcbaba" +输出:4 +解释:长度为 3 的 4 个回文子序列分别是: +- "bbb" ("bbcbaba" 的子序列) +- "bcb" ("bbcbaba" 的子序列) +- "bab" ("bbcbaba" 的子序列) +- "aba" ("bbcbaba" 的子序列) +``` + +## 解题思路 + +### 思路 1:枚举 + 哈希表 + +字符集只包含 $26$ 个小写字母,所以我们可以枚举这 $26$ 个小写字母。 + +对于每个小写字母,使用对撞双指针,找到字符串 $s$ 首尾两侧与小写字母相同的最左位置和最右位置。 + +如果两个位置不同,则我们可以将两个位置中间不重复的字符当作是长度为 $3$ 的子序列最中间的那个字符。 + +则我们可以统计出两个位置中间不重复字符的个数,将其累加到答案中。 + +遍历完,返回答案。 + +### 思路 1:代码 + +```Python +class Solution: + def countPalindromicSubsequence(self, s: str) -> int: + size = len(s) + ans = 0 + + for i in range(26): + left, right = 0, size - 1 + + while left < size and ord(s[left]) - ord('a') != i: + left += 1 + + while right >= 0 and ord(s[right]) - ord('a') != i: + right -= 1 + + if right - left < 2: + continue + + char_set = set() + for j in range(left + 1, right): + char_set.add(s[j]) + ans += len(char_set) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$n \times | \sum | + | \sum |^2$,其中 $n$ 为字符串 $s$ 的长度,$\sum$ 为字符集,本题中 $| \sum | = 26$。 +- **空间复杂度**:$O(| \sum |)$。 diff --git a/docs/solutions/2000-2099/final-value-of-variable-after-performing-operations.md b/docs/solutions/2000-2099/final-value-of-variable-after-performing-operations.md new file mode 100644 index 00000000..44d72132 --- /dev/null +++ b/docs/solutions/2000-2099/final-value-of-variable-after-performing-operations.md @@ -0,0 +1,42 @@ +# [2011. 执行操作后的变量值](https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/) + +- 标签:数组、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [2011. 执行操作后的变量值 - 力扣](https://leetcode.cn/problems/final-value-of-variable-after-performing-operations/) + +## 题目大意 + +存在一种支持 `4` 种操作和 `1` 个变量 `X` 的编程语言: + +- `++X` 和 `x++` 使得变量 `X` 值加 `1`。 +- `--X` 和 `X--` 使得变脸 `X ` 值减 `1`。 + +`X` 的初始值是 `0`。现在给定一个字符串数组 `operations`,这是由操作组成的一个列表。 + +要求:返回执行所有操作后,`X` 的最终值。 + +## 解题思路 + +思路很简单,初始答案 `res` 赋值为 `0`。 + +然后遍历操作列表 `operations`,判断每一个操作 `operation` 的符号。如果操作中含有 `+`,则让答案加 `1`,否则,则让答案减 `1`。最后输出答案。 + +## 代码 + +```python +def finalValueAfterOperations(self, operations): + """ + :type operations: List[str] + :rtype: int + """ + res = 0 + + for opration in operations: + res += 1 if '+' in opration else -1 + + return res +``` + diff --git a/docs/solutions/2000-2099/index.md b/docs/solutions/2000-2099/index.md new file mode 100644 index 00000000..e3a63edb --- /dev/null +++ b/docs/solutions/2000-2099/index.md @@ -0,0 +1,5 @@ +## 本章内容 + +- [2011. 执行操作后的变量值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/final-value-of-variable-after-performing-operations.md) +- [2023. 连接后等于目标字符串的字符串对](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target.md) +- [2050. 并行课程 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/parallel-courses-iii.md) diff --git a/docs/solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target.md b/docs/solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target.md new file mode 100644 index 00000000..d89a5f66 --- /dev/null +++ b/docs/solutions/2000-2099/number-of-pairs-of-strings-with-concatenation-equal-to-target.md @@ -0,0 +1,109 @@ +# [2023. 连接后等于目标字符串的字符串对](https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/) + +- 标签:数组、字符串 +- 难度:中等 + +## 题目链接 + +- [2023. 连接后等于目标字符串的字符串对 - 力扣](https://leetcode.cn/problems/number-of-pairs-of-strings-with-concatenation-equal-to-target/) + +## 题目大意 + +**描述**:给定一个数字字符串数组 `nums` 和一个数字字符串 `target`。 + +**要求**:返回 `nums[i] + nums[j]` (两个字符串连接,其中 `i != j`)结果等于 `target` 的下标 `(i, j)` 的数目。 + +**说明**: + +- $2 \le nums.length \le 100$。 +- $1 \le nums[i].length \le 100$。 +- $2 \le target.length \le 100$。 +- `nums[i]` 和 `target` 只包含数字。 +- `nums[i]` 和 `target` 不含有任何前导 $0$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = ["777","7","77","77"], target = "7777" +输出:4 +解释:符合要求的下标对包括: +- (0, 1):"777" + "7" +- (1, 0):"7" + "777" +- (2, 3):"77" + "77" +- (3, 2):"77" + "77" +``` + +- 示例 2: + +```python +输入:nums = ["123","4","12","34"], target = "1234" +输出:2 +解释:符合要求的下标对包括 +- (0, 1):"123" + "4" +- (2, 3):"12" + "34" +``` + +## 解题思路 + +### 思路 1:暴力枚举 + +1. 双重循环遍历所有的 `i` 和 `j`,满足 `i != j` 并且 `nums[i] + nums[j] == target` 时,记入到答案数目中。 +2. 遍历完,返回答案数目。 + +### 思路 1:代码 + +```python +class Solution: + def numOfPairs(self, nums: List[str], target: str) -> int: + res = 0 + for i in range(len(nums)): + for j in range(len(nums)): + if i != j and nums[i] + nums[j] == target: + res += 1 + + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:哈希表 + +1. 使用哈希表记录字符串数组 `nums` 中所有数字字符串的数量。 +2. 遍历哈希表中的键 `num`。 +3. 将 `target` 根据 `num` 的长度分为前缀 `prefix` 和 `suffix`。 +4. 如果 `num` 等于 `prefix`,则判断后缀 `suffix` 是否在哈希表中,如果在哈希表中,则说明 `prefix` 和 `suffix` 能够拼接为 `target`。 + 1. 如果 `num` 等于 `suffix`,此时 `perfix == suffix`,则答案数目累积为 `table[prefix] * (table[suffix] - 1)`。 + 2. 如果 `num` 不等于 `suffix`,则答案数目累积为 `table[prefix] * table[suffix]`。 +5. 最后输出答案数目。 + +### 思路 2:代码 + +```python +class Solution: + def numOfPairs(self, nums: List[str], target: str) -> int: + res = 0 + table = collections.defaultdict(int) + for num in nums: + table[num] += 1 + + for num in table: + size = len(num) + prefix, suffix = target[ :size], target[size: ] + if num == prefix and suffix in table: + if num == suffix: + res += table[prefix] * (table[suffix] - 1) + else: + res += table[prefix] * table[suffix] + + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git a/docs/solutions/2000-2099/parallel-courses-iii.md b/docs/solutions/2000-2099/parallel-courses-iii.md new file mode 100644 index 00000000..63138fd9 --- /dev/null +++ b/docs/solutions/2000-2099/parallel-courses-iii.md @@ -0,0 +1,117 @@ +# [2050. 并行课程 III](https://leetcode.cn/problems/parallel-courses-iii/) + +- 标签:图、拓扑排序、数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [2050. 并行课程 III - 力扣](https://leetcode.cn/problems/parallel-courses-iii/) + +## 题目大意 + +**描述**:给定一个整数 $n$,表示有 $n$ 节课,课程编号为 $1 \sim n$。 + +再给定一个二维整数数组 $relations$,其中 $relations[j] = [prevCourse_j, nextCourse_j]$,表示课程 $prevCourse_j$ 必须在课程 $nextCourse_j$ 之前完成(先修课的关系)。 + +再给定一个下标从 $0$ 开始的整数数组 $time$,其中 $time[i]$ 表示完成第 $(i + 1)$ 门课程需要花费的月份数。 + +现在根据以下规则计算完成所有课程所需要的最少月份数: + +- 如果一门课的所有先修课都已经完成,则可以在任意时间开始这门课程。 +- 可以同时上任意门课程。 + +**要求**:返回完成所有课程所需要的最少月份数。 + +**说明**: + +- $1 \le n \le 5 * 10^4$。 +- $0 \le relations.length \le min(n * (n - 1) / 2, 5 \times 10^4)$。 +- $relations[j].length == 2$。 +- $1 \le prevCourse_j, nextCourse_j \le n$。 +- $prevCourse_j != nextCourse_j$。 +- 所有的先修课程对 $[prevCourse_j, nextCourse_j]$ 都是互不相同的。 +- $time.length == n$。 +- $1 \le time[i] \le 10^4$。 +- 先修课程图是一个有向无环图。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/10/07/ex1.png) + +```python +输入:n = 3, relations = [[1,3],[2,3]], time = [3,2,5] +输出:8 +解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 +你可以在月份 0 同时开始课程 1 和 2 。 +课程 1 花费 3 个月,课程 2 花费 2 个月。 +所以,最早开始课程 3 的时间是月份 3 ,完成所有课程所需时间为 3 + 5 = 8 个月。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2021/10/07/ex2.png) + +```python +输入:n = 5, relations = [[1,5],[2,5],[3,5],[3,4],[4,5]], time = [1,2,3,4,5] +输出:12 +解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 +你可以在月份 0 同时开始课程 1 ,2 和 3 。 +在月份 1,2 和 3 分别完成这三门课程。 +课程 4 需在课程 3 之后开始,也就是 3 个月后。课程 4 在 3 + 4 = 7 月完成。 +课程 5 需在课程 1,2,3 和 4 之后开始,也就是在 max(1,2,3,7) = 7 月开始。 +所以完成所有课程所需的最少时间为 7 + 5 = 12 个月。 +``` + +## 解题思路 + +### 思路 1:拓扑排序 + 动态规划 + +1. 使用邻接表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。定义 $dp[i]$ 为完成第 $i$ 门课程所需要的最少月份数。使用 $ans$ 表示完成所有课程所需要的最少月份数。 +2. 借助队列 $queue$,将所有入度为 $0$ 的节点入队。 +3. 将队列中入度为 $0$ 的节点依次取出。对于取出的每个节点 $u$: + 1. 遍历该节点的相邻节点 $v$,更新相邻节点 $v$ 所需要的最少月份数,即:$dp[v] = max(dp[v], dp[u] + time[v - 1])$。 + 2. 更新完成所有课程所需要的最少月份数 $ans$,即:$ans = max(ans, dp[v])$。 + 3. 相邻节点 $v$ 的入度减 $1$,如果入度减 $1$ 后的节点入度为 0,则将其加入队列 $queue$。 +4. 重复 $3$ 的步骤,直到队列中没有节点。 +5. 最后返回 $ans$。 + +### 思路 1:代码 + +```python +class Solution: + def minimumTime(self, n: int, relations: List[List[int]], time: List[int]) -> int: + graph = [[] for _ in range(n + 1)] + indegrees = [0 for _ in range(n + 1)] + + for u, v in relations: + graph[u].append(v) + indegrees[v] += 1 + + queue = collections.deque() + dp = [0 for _ in range(n + 1)] + + ans = 0 + for i in range(1, n + 1): + if indegrees[i] == 0: + queue.append(i) + dp[i] = time[i - 1] + ans = max(ans, time[i - 1]) + + while queue: + u = queue.popleft() + for v in graph[u]: + dp[v] = max(dp[v], dp[u] + time[v - 1]) + ans = max(ans, dp[v]) + indegrees[v] -= 1 + if indegrees[v] == 0: + queue.append(v) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$,其中 $m$ 为数组 $relations$ 的长度。 +- **空间复杂度**:$O(m + n)$。 diff --git a/docs/solutions/2100-2199/find-substring-with-given-hash-value.md b/docs/solutions/2100-2199/find-substring-with-given-hash-value.md new file mode 100644 index 00000000..ba9056d0 --- /dev/null +++ b/docs/solutions/2100-2199/find-substring-with-given-hash-value.md @@ -0,0 +1,104 @@ +# [2156. 查找给定哈希值的子串](https://leetcode.cn/problems/find-substring-with-given-hash-value/) + +- 标签:字符串、滑动窗口、哈希函数、滚动哈希 +- 难度:困难 + +## 题目链接 + +- [2156. 查找给定哈希值的子串 - 力扣](https://leetcode.cn/problems/find-substring-with-given-hash-value/) + +## 题目大意 + +**描述**:如果给定整数 `p` 和 `m`,一个长度为 `k` 且下标从 `0` 开始的字符串 `s` 的哈希值按照如下函数计算: + +- $hash(s, p, m) = (val(s[0]) * p^0 + val(s[1]) * p^1 + ... + val(s[k-1]) * p^{k-1}) mod m$. + +其中 `val(s[i])` 表示 `s[i]` 在字母表中的下标,从 `val('a') = 1` 到 `val('z') = 26`。 + +现在给定一个字符串 `s` 和整数 `power`,`modulo`,`k` 和 `hashValue` 。 + +**要求**:返回 `s` 中 第一个 长度为 `k` 的 子串 `sub`,满足 `hash(sub, power, modulo) == hashValue`。 + +**说明**: + +- 子串:定义为一个字符串中连续非空字符组成的序列。 +- $1 \le k \le s.length \le 2 * 10^4$。 +- $1 \le power, modulo \le 10^9$。 +- $0 \le hashValue < modulo$。 +- `s` 只包含小写英文字母。 +- 测试数据保证一定存在满足条件的子串。 + +**示例**: + +- 示例 1: + +```python +输入:s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0 +输出:"ee" +解释:"ee" 的哈希值为 hash("ee", 7, 20) = (5 * 1 + 5 * 7) mod 20 = 40 mod 20 = 0 。 +"ee" 是长度为 2 的第一个哈希值为 0 的子串,所以我们返回 "ee" 。 +``` + +## 解题思路 + +### 思路 1:Rabin Karp 算法、滚动哈希算法 + +这道题目的思想和 Rabin Karp 字符串匹配算法中用到的滚动哈希思想是一样的。不过两者计算的公式是相反的。 + +- 本题目中的子串哈希计算公式:$hash(s, p, m) = (val(s[i]) * p^0 + val(s[i+1]) * p^1 + ... + val(s[i+k-1]) * p^{k-1}) \mod m$. + +- RK 算法中的子串哈希计算公式:$hash(s, p, m) = (val(s[i]) * p^{k-1} + val(s[i+1]) * p^{k-2} + ... + val(s[i+k-1]) * p^0) \mod m$. + +可以看出两者的哈希计算公式是反的。 + +在 RK 算法中,下一个子串的哈希值计算方式为:$Hash(s_{[i + 1, i + k]}) = \{[Hash(s_{[i, i + k - 1]}) - s_i \times d^{k - 1}] \times d + s_{i + k} \times d^{0} \} \mod m$。其中 $Hash(s_{[i, i + k - 1]}$ 为当前子串的哈希值,$Hash(s_{[i + 1, i + k]})$ 为下一个子串的哈希值。 + +这个公式也可以用文字表示为:**在计算完当前子串的哈希值后,向右滚动字符串,即移除当前子串中最左侧字符的哈希值($val(s[i]) * p^{k-1}$)之后,再将整体乘以 $p$,再移入最右侧字符的哈希值 $val(s[i+k])$**。 + +我们可以参考 RK 算法中滚动哈希的计算方式,将其应用到本题中。 + +因为两者的哈希计算公式相反,所以本题中,我们可以从右侧想左侧逆向遍历字符串,当计算完当前子串的哈希值后,移除当前子串最右侧字符的哈希值($ val(s[i+k-1]) * p^{k-1}$)之后,再整体乘以 $p$,再移入最左侧字符的哈希值 $val(s[i - 1])$。 + +在本题中,对应的下一个逆向子串的哈希值计算方式为:$Hash(s_{[i - 1, i + k - 2]}) = \{ [Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d + s_{i - 1} \times d^{0} \} \mod m$。其中 $Hash(s_{[i, i + k - 1]})$ 为当前子串的哈希值,$Hash(s_{[i - 1, i + k - 2]})$ 是下一个逆向子串的哈希值。 + +利用取模运算的两个公式: + +- $(a \times b) \mod m = ((a \mod m) \times (b \mod m)) \mod m$ +- $(a + b) \mod m = (a \mod m + b \mod m) \mod m$ + +我们可以把上面的式子转变为: + +$$\begin{aligned} Hash(s_{[i - 1, i + k - 2]}) &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d + s_{i - 1} \times d^{0} \} \mod m \cr &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \times d \mod m + s_{i - 1} \times d^{0} \mod m \} \mod m \cr &= \{[Hash(s_{[i, i + k - 1]}) - s_{i + k - 1} \times d^{k - 1}] \mod m \times d \mod m + s_{i - 1} \times d^{0} \mod m \} \mod m \end{aligned}$$ + +> 注意:这里之所以用了「反向迭代」而不是「正向迭代」是因为如果使用了正向迭代,那么每次移除的最左侧字符哈希值为 $val(s[i]) * p^0$,之后整体需要除以 $p$,再移入最右侧字符哈希值为($val(s[i+k]) * p^{k-1})$)。 +> +> 这样就用到了「除法」。而除法是不满足取模运算对应的公式的,所以这里不能用这种方法进行迭代。 +> +> 而反向迭代,用到的是乘法。在整个过程中是满足取模运算相关的公式。乘法取余不影响最终结果。 + +### 思路 1:代码 + +```python +class Solution: + def subStrHash(self, s: str, power: int, modulo: int, k: int, hashValue: int) -> str: + hash_t = 0 + n = len(s) + for i in range(n - 1, n - k - 1, -1): + hash_t = (hash_t * power + (ord(s[i]) - ord('a') + 1)) % modulo # 计算最后一个子串的哈希值 + + h = pow(power, k - 1) % modulo # 计算最高位项,方便后续移除操作 + ans = "" + if hash_t == hashValue: + ans = s[n - k: n] + for i in range(n - k - 1, -1, -1): # 反向迭代,滚动计算子串的哈希值 + hash_t = (hash_t - h * (ord(s[i + k]) - ord('a') + 1)) % modulo # 移除 s[i + k] 的哈希值 + hash_t = (hash_t * power % modulo + (ord(s[i]) - ord('a') + 1) % modulo) % modulo # 添加 s[i] 的哈希值 + if hash_t == hashValue: # 如果子串哈希值等于 hashValue,则为答案 + ans = s[i: i + k] + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中字符串 $s$ 的长度为 $n$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/2100-2199/index.md b/docs/solutions/2100-2199/index.md new file mode 100644 index 00000000..91ab61f0 --- /dev/null +++ b/docs/solutions/2100-2199/index.md @@ -0,0 +1,4 @@ +## 本章内容 + +- [2156. 查找给定哈希值的子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/find-substring-with-given-hash-value.md) +- [2172. 数组的最大与和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/maximum-and-sum-of-array.md) diff --git a/docs/solutions/2100-2199/maximum-and-sum-of-array.md b/docs/solutions/2100-2199/maximum-and-sum-of-array.md new file mode 100644 index 00000000..3d1bd402 --- /dev/null +++ b/docs/solutions/2100-2199/maximum-and-sum-of-array.md @@ -0,0 +1,114 @@ +# [2172. 数组的最大与和](https://leetcode.cn/problems/maximum-and-sum-of-array/) + +- 标签:位运算、数组、动态规划、状态压缩 +- 难度:困难 + +## 题目链接 + +- [2172. 数组的最大与和 - 力扣](https://leetcode.cn/problems/maximum-and-sum-of-array/) + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的整数数组 $nums$ 和一个整数 $numSlots$ 满足 $2 \times numSlots \ge n$。一共有 $numSlots$ 个篮子,编号为 $1 \sim numSlots$。 + +现在需要将所有 $n$ 个整数分到这些篮子中,且每个篮子最多有 $2$ 个整数。 + +**要求**:返回将 $nums$ 中所有数放入 $numSlots$ 个篮子中的最大与和。 + +**说明**: + +- **与和**:当前方案中,每个数与它所在篮子编号的按位与运算结果之和。 + - 比如,将数字 $[1, 3]$ 放入篮子 $1$ 中,$[4, 6]$ 放入篮子 $2$ 中,这个方案的与和为 $(1 \text{ AND } 1) + (3 \text{ AND } 1) + (4 \text{ AND } 2) + (6 \text{ AND } 2) = 1 + 1 + 0 + 2 = 4$。 +- $n == nums.length$。 +- $1 \le numSlots \le 9$。 +- $1 \le n \le 2 \times numSlots$。 +- $1 \le nums[i] \le 15$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4,5,6], numSlots = 3 +输出:9 +解释:一个可行的方案是 [1, 4] 放入篮子 1 中,[2, 6] 放入篮子 2 中,[3, 5] 放入篮子 3 中。 +最大与和为 (1 AND 1) + (4 AND 1) + (2 AND 2) + (6 AND 2) + (3 AND 3) + (5 AND 3) = 1 + 0 + 2 + 2 + 3 + 1 = 9。 +``` + +- 示例 2: + +```python +输入:nums = [1,3,10,4,7,1], numSlots = 9 +输出:24 +解释:一个可行的方案是 [1, 1] 放入篮子 1 中,[3] 放入篮子 3 中,[4] 放入篮子 4 中,[7] 放入篮子 7 中,[10] 放入篮子 9 中。 +最大与和为 (1 AND 1) + (1 AND 1) + (3 AND 3) + (4 AND 4) + (7 AND 7) + (10 AND 9) = 1 + 1 + 3 + 4 + 7 + 8 = 24 。 +注意,篮子 2 ,5 ,6 和 8 是空的,这是允许的。 +``` + +## 解题思路 + +### 思路 1:状压 DP + +每个篮子最多可分 $2$ 个整数,则我们可以将 $1$ 个篮子分成两个篮子,这样总共有 $2 \times numSlots$ 个篮子,每个篮子中最多可以装 $1$ 个整数。 + +同时因为 $numSlots$ 的范围为 $[1, 9]$,$2 \times numSlots$ 的范围为 $[2, 19]$,范围不是很大,所以我们可以用「状态压缩」的方式来表示每个篮子中的整数放取情况。 + +即使用一个 $n \times numSlots$ 位的二进制数 $state$ 来表示每个篮子中的整数放取情况。如果 $state$ 的第 $i$ 位为 $1$,表示第 $i$ 个篮子里边放了整数,如果 $state$ 的第 $i$ 位为 $0$,表示第 $i$ 个篮子为空。 + +这样,我们就可以通过动态规划的方式来解决这道题。 + +###### 1. 划分阶段 + +按照 $2 \times numSlots$ 个篮子中的整数放取情况进行阶段划分。 + +###### 2. 定义状态 + +定义当前每个篮子中的整数放取情况为 $state$,$state$ 对应选择的整数个数为 $count(state)$。 + +则可以定义状态 $dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。 + +###### 3. 状态转移方程 + +对于当前状态 $dp[state]$,肯定是从比 $state$ 少选一个元素的状态中递推而来。我们可以枚举少选一个元素的状态,找到可以获得的最大与和,赋值给 $dp[state]$。 + +即状态转移方程为:$dp[state] = min(dp[state], dp[state \oplus (1 \text{ <}\text{< } i)] + (i // 2 + 1) \text{ \& } nums[one\underline{\hspace{0.5em}}cnt - 1])$,其中: + +1. $state$ 第 $i$ 位一定为 $1$。 +2. $state \oplus (1 \text{ <}\text{< } i)$ 为比 $state$ 少选一个元素的状态。 +3. $i // 2 + 1$ 为篮子对应编号 +4. $nums[one\underline{\hspace{0.5em}}cnt - 1]$ 为当前正在考虑的数组元素。 + +###### 4. 初始条件 + +- 初始每个篮子中都没有放整数的情况下,可以获得的最大与和为 $0$,即 $dp[0] = 0$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[state]$ 表示为:将前 $count(state)$ 个整数放到篮子里,并且每个篮子中的整数放取情况为 $state$ 时,可以获得的最大与和。所以最终结果为 $max(dp)$。 + +> 注意:当 $one\underline{\hspace{0.5em}}cnt > len(nums)$ 时,无法通过递推得到 $dp[state]$,需要跳过。 + +### 思路 1:代码 + +```python +class Solution: + def maximumANDSum(self, nums: List[int], numSlots: int) -> int: + states = 1 << (numSlots * 2) + dp = [0 for _ in range(states)] + + for state in range(states): + one_cnt = bin(state).count('1') + if one_cnt > len(nums): + continue + for i in range(numSlots * 2): + if (state >> i) & 1: + dp[state] = max(dp[state], dp[state ^ (1 << i)] + ((i // 2 + 1) & nums[one_cnt - 1])) + + return max(dp) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(2^m \times m)$,其中 $m = 2 \times numSlots$。 +- **空间复杂度**:$O(2^m)$。 + diff --git a/docs/solutions/2200-2299/add-two-integers.md b/docs/solutions/2200-2299/add-two-integers.md new file mode 100644 index 00000000..fb14746d --- /dev/null +++ b/docs/solutions/2200-2299/add-two-integers.md @@ -0,0 +1,56 @@ +# [2235. 两整数相加](https://leetcode.cn/problems/add-two-integers/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [2235. 两整数相加 - 力扣](https://leetcode.cn/problems/add-two-integers/) + +## 题目大意 + +**描述**:给定两个整数 $num1$ 和 $num2$。 + +**要求**:返回这两个整数的和。 + +**说明**: + +- $-100 \le num1, num2 \le 100$。 + +**示例**: + +- 示例 1: + +```python +示例 1: +输入:num1 = 12, num2 = 5 +输出:17 +解释:num1 是 12,num2 是 5,它们的和是 12 + 5 = 17,因此返回 17。 +``` + +- 示例 2: + +```python +输入:num1 = -10, num2 = 4 +输出:-6 +解释:num1 + num2 = -6,因此返回 -6。 +``` + +## 解题思路 + +### 思路 1:直接计算 + +1. 直接计算整数 $num1$ 与 $num2$ 的和,返回 $num1 + num2$ 即可。 + +### 思路 1:代码 + +```python +class Solution: + def sum(self, num1: int, num2: int) -> int: + return num1 + num2 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/2200-2299/count-integers-in-intervals.md b/docs/solutions/2200-2299/count-integers-in-intervals.md new file mode 100644 index 00000000..6f8888ec --- /dev/null +++ b/docs/solutions/2200-2299/count-integers-in-intervals.md @@ -0,0 +1,190 @@ +# [2276. 统计区间中的整数数目](https://leetcode.cn/problems/count-integers-in-intervals/) + +- 标签:设计、线段树、有序集合 +- 难度:困难 + +## 题目链接 + +- [2276. 统计区间中的整数数目 - 力扣](https://leetcode.cn/problems/count-integers-in-intervals/) + +## 题目大意 + +**描述**:给定一个区间的空集。 + +**要求**:设计并实现满足要求的数据结构: + +- 新增:添加一个区间到这个区间集合中。 +- 统计:计算出现在 至少一个 区间中的整数个数。 + +实现 CountIntervals 类: + +- `CountIntervals()` 使用区间的空集初始化对象 +- `void add(int left, int right)` 添加区间 `[left, right]` 到区间集合之中。 +- `int count()` 返回出现在 至少一个 区间中的整数个数。 + +**说明**: + +- 区间 `[left, right]` 表示满足 $left \le x \le right$ 的所有整数 `x`。 +- $1 \le left \le right \le 10^9$。 +- 最多调用 `add` 和 `count` 方法 **总计** $10^5$ 次。 +- 调用 `count` 方法至少一次。 + +**示例**: + +- 示例 1: + +```python +输入: +["CountIntervals", "add", "add", "count", "add", "count"] +[[], [2, 3], [7, 10], [], [5, 8], []] +输出: +[null, null, null, 6, null, 8] + +解释: +CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象 +countIntervals.add(2, 3); // 将 [2, 3] 添加到区间集合中 +countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中 +countIntervals.count(); // 返回 6 + // 整数 2 和 3 出现在区间 [2, 3] 中 + // 整数 7、8、9、10 出现在区间 [7, 10] 中 +countIntervals.add(5, 8); // 将 [5, 8] 添加到区间集合中 +countIntervals.count(); // 返回 8 + // 整数 2 和 3 出现在区间 [2, 3] 中 + // 整数 5 和 6 出现在区间 [5, 8] 中 + // 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中 + // 整数 9 和 10 出现在区间 [7, 10] 中 +``` + +## 解题思路 + +### 思路 1:动态开点线段树 + +这道题可以使用线段树来做。 + +因为区间的范围是 $[1, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。具体做法如下: + +- 初始化方法,构建一棵线段树。每个线段树的节点类存储当前区间中保存的元素个数。 + +- 在 `add` 方法中,将区间 `[left, right]` 上的每个元素值赋值为 `1`,则区间值为 `right - left + 1`。 + +- 在 `count` 方法中,返回区间 $[0, 10^9]$ 的区间值(即区间内元素个数)。 + +### 思路 1:动态开点线段树代码 + +```python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.val = val # 节点值(区间值) + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self, function): + self.tree = SegTreeNode(0, int(1e9)) + self.function = function # function 是一个函数,左右区间的聚合方法 + + # 区间更新接口:将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + + # 以下为内部实现方法 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + node.lazy_tag = val # 将当前节点的延迟标记标记为 val + interval_size = (node.right - node.left + 1) # 当前节点所在区间大小 + node.val = val * interval_size # 当前节点所在区间每个元素值改为 val + return + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.val # 直接返回节点值 + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, q_right, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(q_left, q_right, node.rightNode) + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新 node 节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.leftNode and node.rightNode: + node.val = self.function(node.leftNode.val, node.rightNode.val) + + # 向下更新实现方法:更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + + lazy_tag = node.lazy_tag + if node.lazy_tag is None: + return + + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 + left_size = (node.leftNode.right - node.leftNode.left + 1) + node.leftNode.val = lazy_tag * left_size # 更新左子节点值 + + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 + right_size = (node.rightNode.right - node.rightNode.left + 1) + node.rightNode.val = lazy_tag * right_size # 更新右子节点值 + + node.lazy_tag = None # 更新当前节点的懒惰标记 + + +class CountIntervals: + + def __init__(self): + self.STree = SegmentTree(lambda x, y: x + y) + self.left = 10 ** 9 + self.right = 0 + + + def add(self, left: int, right: int) -> None: + self.STree.update_interval(left, right, 1) + + + def count(self) -> int: + return self.STree.query_interval(0, int(1e9)) + + + +# Your CountIntervals object will be instantiated and called as such: +# obj = CountIntervals() +# obj.add(left,right) +# param_2 = obj.count() +``` + diff --git a/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md b/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md new file mode 100644 index 00000000..4830d991 --- /dev/null +++ b/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md @@ -0,0 +1,93 @@ +# [2249. 统计圆内格点数目](https://leetcode.cn/problems/count-lattice-points-inside-a-circle/) + +- 标签:几何、数组、哈希表、数学、枚举 +- 难度:中等 + +## 题目链接 + +- [2249. 统计圆内格点数目 - 力扣](https://leetcode.cn/problems/count-lattice-points-inside-a-circle/) + +## 题目大意 + +**描述**:给定一个二维整数数组 `circles`。其中 `circles[i] = [xi, yi, ri]` 表示网格上圆心为 `(xi, yi)` 且半径为 `ri` 的第 $i$ 个圆。 + +**要求**:返回出现在至少一个圆内的格点数目。 + +**说明**: + +- **格点**:指的是整数坐标对应的点。 +- 圆周上的点也被视为出现在圆内的点。 +- $1 \le circles.length \le 200$。 +- $circles[i].length == 3$。 +- $1 \le xi, yi \le 100$。 +- $1 \le ri \le min(xi, yi)$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2022/03/02/exa-11.png) + +```python +输入:circles = [[2,2,1]] +输出:5 +解释: +给定的圆如上图所示。 +出现在圆内的格点为 (1, 2)、(2, 1)、(2, 2)、(2, 3) 和 (3, 2),在图中用绿色标识。 +像 (1, 1) 和 (1, 3) 这样用红色标识的点,并未出现在圆内。 +因此,出现在至少一个圆内的格点数目是 5。 +``` + +- 示例 2: + +```python +输入:circles = [[2,2,2],[3,4,1]] +输出:16 +解释: +给定的圆如上图所示。 +共有 16 个格点出现在至少一个圆内。 +其中部分点的坐标是 (0, 2)、(2, 0)、(2, 4)、(3, 2) 和 (4, 4)。 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +题目要求中 $1 \le xi, yi \le 100$,$1 \le ri \le min(xi, yi)$。则圆中点的范围为 $1 \le x, y \le 200$。 + +我们可以枚举所有坐标和所有圆,检测该坐标是否在圆中。 + +为了优化枚举范围,我们可以先遍历一遍所有圆,计算最小、最大的 $x$、$y$ 范围,再枚举所有坐标和所有圆,并进行检测。 + +### 思路 1:代码 + +```python +class Solution: + def countLatticePoints(self, circles: List[List[int]]) -> int: + min_x, min_y = 200, 200 + max_x, max_y = 0, 0 + for circle in circles: + if circle[0] + circle[2] > max_x: + max_x = circle[0] + circle[2] + if circle[0] - circle[2] < min_x: + min_x = circle[0] - circle[2] + if circle[1] + circle[2] > max_y: + max_y = circle[1] + circle[2] + if circle[1] - circle[2] < min_y: + min_y = circle[1] - circle[2] + + ans = 0 + for x in range(min_x, max_x + 1): + for y in range(min_y, max_y + 1): + for xi, yi, ri in circles: + if (xi - x) * (xi - x) + (yi - y) * (yi - y) <= ri * ri: + ans += 1 + break + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(x \times y)$,其中 $x$、$y$ 分别为横纵坐标的个数。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/2200-2299/index.md b/docs/solutions/2200-2299/index.md new file mode 100644 index 00000000..ce4a5920 --- /dev/null +++ b/docs/solutions/2200-2299/index.md @@ -0,0 +1,6 @@ +## 本章内容 + +- [2235. 两整数相加](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/add-two-integers.md) +- [2246. 相邻字符不同的最长路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md) +- [2249. 统计圆内格点数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/count-lattice-points-inside-a-circle.md) +- [2276. 统计区间中的整数数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/count-integers-in-intervals.md) diff --git a/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md b/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md new file mode 100644 index 00000000..59192358 --- /dev/null +++ b/docs/solutions/2200-2299/longest-path-with-different-adjacent-characters.md @@ -0,0 +1,103 @@ +# [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) + +- 标签:树、深度优先搜索、图、拓扑排序、数组、字符串 +- 难度:困难 + +## 题目链接 + +- [2246. 相邻字符不同的最长路径 - 力扣](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的数组 $parent$ 来表示一棵树(即一个连通、无向、无环图)。该树的节点编号为 $0 \sim n - 1$,共 $n$ 个节点,其中根节点的编号为 $0$。其中 $parent[i]$ 表示节点 $i$ 的父节点,由于节点 $0$ 是根节点,所以 $parent[0] == -1$。再给定一个长度为 $n$ 的字符串,其中 $s[i]$ 表示分配给节点 $i$ 的字符。 + +**要求**:找出路径上任意一对相邻节点都没有分配到相同字符的最长路径,并返回该路径的长度。 + +**说明**: + +- $n == parent.length == s.length$。 +- $1 \le n \le 10^5$。 +- 对所有 $i \ge 1$ ,$0 \le parent[i] \le n - 1$ 均成立。 +- $parent[0] == -1$。 +- $parent$ 表示一棵有效的树。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2022/03/25/testingdrawio.png) + +```python +输入:parent = [-1,0,0,1,1,2], s = "abacbe" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3。 +可以证明不存在满足上述条件且比 3 更长的路径。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2022/03/25/graph2drawio.png) + +```python +输入:parent = [-1,0,0,0], s = "aabc" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3。 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +因为题目给定的是表示父子节点的 $parent$ 数组,为了方便递归遍历相邻节点,我们可以根据 $partent$ 数组,建立一个由父节点指向子节点的有向图 $graph$。 + +如果不考虑相邻节点是否为相同字符这一条件,那么这道题就是在求树的直径(树的最长路径长度)中的节点个数。 + +对于根节点为 $u$ 的树来说: + +1. 如果其最长路径经过根节点 $u$,则 **最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1**。 +2. 如果其最长路径不经过根节点 $u$,则 **最长路径长度 = 某个子树中的最长路径长度**。 + +即:**最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1,某个子树中的最长路径长度)**。 + +对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v$,并在递归遍历的同时,维护一个全局最大路径和变量 $ans$,以及当前节点 $u$ 的最大路径长度变量 $u\underline{\hspace{0.5em}}len$。 + +1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{\hspace{0.5em}}len$。 +2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{\hspace{0.5em}}len + v\underline{\hspace{0.5em}}len + 1)$。 +3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{\hspace{0.5em}}len = max(u\underline{\hspace{0.5em}}len, \quad v\underline{\hspace{0.5em}}len + 1)$。 + +因为题目限定了「相邻节点字符不同」,所以在更新全局最长路径长度和当前节点 $u$ 的最长路径长度时,我们需要判断一下节点 $u$ 与相邻节点 $v$ 的字符是否相同,只有在字符不同的条件下,才能够更新维护。 + +最后,因为题目要求的是树的直径(树的最长路径长度)中的节点个数,而:**路径的节点 = 路径长度 + 1**,所以最后我们返回 $self.ans + 1$ 作为答案。 + +### 思路 1:代码 + +```python +class Solution: + def longestPath(self, parent: List[int], s: str) -> int: + size = len(parent) + + # 根据 parent 数组,建立有向图 + graph = [[] for _ in range(size)] + for i in range(1, size): + graph[parent[i]].append(i) + + ans = 0 + def dfs(u): + nonlocal ans + u_len = 0 # u 节点的最大路径长度 + for v in graph[u]: # 遍历 u 节点的相邻节点 + v_len = dfs(v) # 相邻节点的最大路径长度 + if s[u] != s[v]: # 相邻节点字符不同 + ans = max(ans, u_len + v_len + 1) # 维护最大路径长度 + u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 + return u_len # 返回 u 节点的最大路径长度 + + dfs(0) + return ans + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 是树的节点数目。 +- **空间复杂度**:$O(n)$。 diff --git a/docs/solutions/2300-2399/count-special-integers.md b/docs/solutions/2300-2399/count-special-integers.md new file mode 100644 index 00000000..77088a36 --- /dev/null +++ b/docs/solutions/2300-2399/count-special-integers.md @@ -0,0 +1,108 @@ +# [2376. 统计特殊整数](https://leetcode.cn/problems/count-special-integers/) + +- 标签:数学、动态规划 +- 难度:困难 + +## 题目链接 + +- [2376. 统计特殊整数 - 力扣](https://leetcode.cn/problems/count-special-integers/) + +## 题目大意 + +**描述**:给定一个正整数 $n$。 + +**要求**:求区间 $[1, n]$ 内的所有整数中,特殊整数的数目。 + +**说明**: + +- **特殊整数**:如果一个正整数的每一个数位都是互不相同的,则称它是特殊整数。 +- $1 \le n \le 2 \times 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入:n = 20 +输出:19 +解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。 +``` + +- 示例 2: + +```python +输入:n = 5 +输出:5 +解释:1 到 5 所有整数都是特殊整数。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, state, isLimit, isNum):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, False)` 开始递归。 `dfs(0, 0, True, False)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始没有使用数字(即前一位所选数字集合为 $0$)。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 + 4. 开始时没有填写数字。 + +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $isNum == True$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果 $isNum == False$,说明当前方案不符合要求,则返回方案数 $0$。 + +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 根据 $isNum$ 和 $isLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$), + 2. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 3. 如果之前没有选择 $d$,即 $d$ 不在之前选择的数字集合 $state$ 中,则方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True)`。 + 1. `state | (1 << d)` 表示之前选择的数字集合 $state$ 加上 $d$。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前 $pos$ 位限制。 + 3. $isNum == True$ 表示 $pos$ 位选择了数字。 + +6. 最后的方案数为 `dfs(0, 0, True, False)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def countSpecialNumbers(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # state: 之前选过的数字集合。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isNum: 表示 pos 前面的数位是否填了数字。如果为真,则当前位不可跳过;如果为假,则当前位可跳过。 + def dfs(pos, state, isLimit, isNum): + if pos == len(s): + # isNum 为 True,则表示当前方案符合要求 + return int(isNum) + + ans = 0 + if not isNum: + # 如果 isNum 为 False,则可以跳过当前数位 + ans = dfs(pos + 1, state, False, False) + + # 如果前一位没有填写数字,则最小可选择数字为 0,否则最少为 1(不能含有前导 0)。 + minX = 0 if isNum else 1 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + # d 不在选择的数字集合中,即之前没有选择过 d + if (state >> d) & 1 == 0: + ans += dfs(pos + 1, state | (1 << d), isLimit and d == maxX, True) + return ans + + return dfs(0, 0, True, False) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n \times 10 \times 2^{10})$,其中 $n$ 为给定整数。 +- **空间复杂度**:$O(\log n \times 2^{10})$。 diff --git a/docs/solutions/2300-2399/index.md b/docs/solutions/2300-2399/index.md new file mode 100644 index 00000000..19cffb9a --- /dev/null +++ b/docs/solutions/2300-2399/index.md @@ -0,0 +1,3 @@ +## 本章内容 + +- [2376. 统计特殊整数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2300-2399/count-special-integers.md) diff --git a/docs/solutions/2400-2499/index.md b/docs/solutions/2400-2499/index.md new file mode 100644 index 00000000..a5b8d945 --- /dev/null +++ b/docs/solutions/2400-2499/index.md @@ -0,0 +1,3 @@ +## 本章内容 + +- [2427. 公因子的数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2400-2499/number-of-common-factors.md) diff --git a/docs/solutions/2400-2499/number-of-common-factors.md b/docs/solutions/2400-2499/number-of-common-factors.md new file mode 100644 index 00000000..22498e4b --- /dev/null +++ b/docs/solutions/2400-2499/number-of-common-factors.md @@ -0,0 +1,62 @@ +# [2427. 公因子的数目](https://leetcode.cn/problems/number-of-common-factors/) + +- 标签:数学、枚举、数论 +- 难度:简单 + +## 题目链接 + +- [2427. 公因子的数目 - 力扣](https://leetcode.cn/problems/number-of-common-factors/) + +## 题目大意 + +**描述**:给定两个正整数 $a$ 和 $b$。 + +**要求**:返回 $a$ 和 $b$ 的公因子数目。 + +**说明**: + +- **公因子**:如果 $x$ 可以同时整除 $a$ 和 $b$,则认为 $x$ 是 $a$ 和 $b$ 的一个公因子。 +- $1 \le a, b \le 1000$。 + +**示例**: + +- 示例 1: + +```python +输入:a = 12, b = 6 +输出:4 +解释:12 和 6 的公因子是 1、2、3、6。 +``` + +- 示例 2: + +```python +输入:a = 25, b = 30 +输出:2 +解释:25 和 30 的公因子是 1、5。 +``` + +## 解题思路 + +### 思路 1:枚举算法 + +最直接的思路就是枚举所有 $[1, min(a, b)]$ 之间的数,并检查是否能同时整除 $a$ 和 $b$。 + +当然,因为 $a$ 与 $b$ 的公因子肯定不会超过 $a$ 与 $b$ 的最大公因数,则我们可以直接枚举 $[1, gcd(a, b)]$ 之间的数即可,其中 $gcd(a, b)$ 是 $a$ 与 $b$ 的最大公约数。 + +### 思路 1:代码 + +```python +class Solution: + def commonFactors(self, a: int, b: int) -> int: + ans = 0 + for i in range(1, math.gcd(a, b) + 1): + if a % i == 0 and b % i == 0: + ans += 1 + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\sqrt{min(a, b)})$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md b/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md new file mode 100644 index 00000000..70cb637f --- /dev/null +++ b/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md @@ -0,0 +1,125 @@ +# [2538. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) + +- 标签:树、深度优先搜索、数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [2538. 最大价值和与最小价值和的差值 - 力扣](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) + +## 题目大意 + +**描述**:给定一个整数 $n$ 和一个长度为 $n - 1$ 的二维整数数组 $edges$ 用于表示一个 $n$ 个节点的无向无根图,节点编号为 $0 \sim n - 1$。其中 $edges[i] = [ai, bi]$ 表示树中节点 $ai$ 和 $bi$ 之间有一条边。再给定一个整数数组 $price$,其中 $price[i]$ 表示图中节点 $i$ 的价值。 + +一条路径的价值和是这条路径上所有节点的价值之和。 + +你可以选择树中任意一个节点作为根节点 $root$。选择 $root$ 为根的开销是以 $root$ 为起点的所有路径中,价值和最大的一条路径与最小的一条路径的差值。 + +**要求**:返回所有节点作为根节点的选择中,最大的开销为多少。 + +**说明**: + +- $1 \le n \le 10^5$。 +- $edges.length == n - 1$。 +- $0 \le ai, bi \le n - 1$。 +- $edges$ 表示一棵符合题面要求的树。 +- $price.length == n$。 +- $1 \le price[i] \le 10^5$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2022/12/01/example14.png) + +```python +输入:n = 6, edges = [[0,1],[1,2],[1,3],[3,4],[3,5]], price = [9,8,7,6,10,5] +输出:24 +解释:上图展示了以节点 2 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。 +- 第一条路径节点为 [2,1,3,4]:价值为 [7,8,6,10] ,价值和为 31 。 +- 第二条路径节点为 [2] ,价值为 [7] 。 +最大路径和与最小路径和的差值为 24 。24 是所有方案中的最大开销。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2022/11/24/p1_example2.png) + +```python +输入:n = 3, edges = [[0,1],[1,2]], price = [1,1,1] +输出:2 +解释:上图展示了以节点 0 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。 +- 第一条路径包含节点 [0,1,2]:价值为 [1,1,1] ,价值和为 3 。 +- 第二条路径节点为 [0] ,价值为 [1] 。 +最大路径和与最小路径和的差值为 2 。2 是所有方案中的最大开销。 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +1. 因为 $price$ 数组中元素都为正数,所以价值和最小的一条路径一定为「单个节点」,也就是根节点 $root$ 本身。 +2. 因为价值和最大的路径是从根节点 $root$ 出发的价值和最大的一条路径,所以「最大的开销」等于「从根节点 $root$ 出发的价值和最大的一条路径」与「路径中一个端点值」 的差值。 +3. 价值和最大的路径的两个端点中,一个端点为根节点 $root$,另一个节点为叶子节点。 + +这样问题就变为了求树中一条路径,使得路径的价值和减去其中一个端点值的权值最大。 + +对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大开销变量 $ans$。 + +然后定义函数 ` def dfs(self, u, father):` 计算以节点 $u$ 为根节点的子树中,带端点的最大路径和 $max\underline{\hspace{0.5em}}s1$,以及去掉端点的最大路径和 $max\underline{\hspace{0.5em}}s2$,其中 $father$ 表示节点 $u$ 的根节点,用于遍历邻接节点的过程中过滤父节点,避免重复遍历。 + +初始化带端点的最大路径和 $max\underline{\hspace{0.5em}}s1$ 为 $price[u]$,表示当前只有一个节点,初始化去掉端点的最大路径和 $max\underline{\hspace{0.5em}}s2$ 为 $0$,表示当前没有节点。 + +然后在遍历节点 $u$ 的相邻节点 $v$ 时,递归调用 $dfs(v, u)$,获取以节点 $v$ 为根节点的子树中,带端点的最大路径和 $s1$,以及去掉端点的最大路径和 $s2$。此时最大开销变量 $self.ans$ 有两种情况: + +1. $u$ 的子树中带端点的最大路径和,加上 $v$ 的子树中不带端点的最大路径和,即:$max\underline{\hspace{0.5em}}s1 + s2$。 +2. $u$ 的子树中去掉端点的最大路径和,加上 $v$ 的子树中带端点的最大路径和,即:$max\underline{\hspace{0.5em}}s2 + s1$。 + +此时我们更新最大开销变量 $self.ans$,即:$self.ans = max(self.ans, \quad max\underline{\hspace{0.5em}}s1 + s2, \quad max\underline{\hspace{0.5em}}s2 + s1)$。 + +然后更新 $u$ 的子树中带端点的最大路径和 $max\underline{\hspace{0.5em}}s1$,即:$max\underline{\hspace{0.5em}}s1= max(max\underline{\hspace{0.5em}}s1, \quad s1 + price[u])$。 + +再更新 $u$ 的子树中去掉端点的最大路径和 $max\underline{\hspace{0.5em}}s2$,即:$max\underline{\hspace{0.5em}}s2 = max(max\underline{\hspace{0.5em}}s2, \quad s2 + price[u])$。 + +最后返回带端点 $u$ 的最大路径和 $max\underline{\hspace{0.5em}}s1$,以及去掉端点 $u$ 的最大路径和 $。 + +最终,最大开销变量 $self.ans$ 即为答案。 + +### 思路 1:代码 + +```python +class Solution: + def __init__(self): + self.ans = 0 + + def dfs(self, graph, price, u, father): + max_s1 = price[u] + max_s2 = 0 + for v in graph[u]: + if v == father: # 过滤父节点,避免重复遍历 + continue + s1, s2 = self.dfs(graph, price, v, u) + self.ans = max(self.ans, max_s1 + s2, max_s2 + s1) + max_s1 = max(max_s1, s1 + price[u]) + max_s2 = max(max_s2, s2 + price[u]) + return max_s1, max_s2 + + def maxOutput(self, n: int, edges: List[List[int]], price: List[int]) -> int: + graph = [[] for _ in range(n)] + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + self.dfs(graph, price, 0, -1) + return self.ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中节点个数。 +- **空间复杂度**:$O(n)$。 + +## 参考链接 + +- 【题解】[二维差分模板 双指针 树形DP 树的直径【力扣周赛 328】](https://www.bilibili.com/video/BV1QT41127kJ/) +- 【题解】[2538. 最大价值和与最小价值和的差值 题解](https://github.com/doocs/leetcode/blob/main/solution/2500-2599/2538.Difference Between Maximum and Minimum Price Sum/README.md) diff --git a/docs/solutions/2500-2599/index.md b/docs/solutions/2500-2599/index.md new file mode 100644 index 00000000..7a1ae3c8 --- /dev/null +++ b/docs/solutions/2500-2599/index.md @@ -0,0 +1,4 @@ +## 本章内容 + +- [2538. 最大价值和与最小价值和的差值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/difference-between-maximum-and-minimum-price-sum.md) +- [2585. 获得分数的方法数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/number-of-ways-to-earn-points.md) diff --git a/docs/solutions/2500-2599/number-of-ways-to-earn-points.md b/docs/solutions/2500-2599/number-of-ways-to-earn-points.md new file mode 100644 index 00000000..30e9af18 --- /dev/null +++ b/docs/solutions/2500-2599/number-of-ways-to-earn-points.md @@ -0,0 +1,109 @@ +# [2585. 获得分数的方法数](https://leetcode.cn/problems/number-of-ways-to-earn-points/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目链接 + +- [2585. 获得分数的方法数 - 力扣](https://leetcode.cn/problems/number-of-ways-to-earn-points/) + +## 题目大意 + +**描述**:考试中有 $n$ 种类型的题目。给定一个整数 $target$ 和一个下标从 $0$ 开始的二维整数数组 $types$,其中 $types[i] = [count_i, marks_i]$ 表示第 $i$ 种类型的题目有 $count_i$ 道,每道题目对应 $marks_i$ 分。 + +**要求**:返回你在考试中恰好得到 $target$ 分的方法数。由于答案可能很大,结果需要对 $10^9 + 7$ 取余。 + +**说明**: + +- 同类型题目无法区分。比如说,如果有 $3$ 道同类型题目,那么解答第 $1$ 和第 $2$ 道题目与解答第 $1$ 和第 $3$ 道题目或者第 $2$ 和第 $3$ 道题目是相同的。 +- $1 \le target \le 1000$。 +- $n == types.length$。 +- $1 \le n \le 50$。 +- $types[i].length == 2$。 +- $1 \le counti, marksi \le 50$。 + +**示例**: + +- 示例 1: + +```python +输入:target = 6, types = [[6,1],[3,2],[2,3]] +输出:7 +解释:要获得 6 分,你可以选择以下七种方法之一: +- 解决 6 道第 0 种类型的题目:1 + 1 + 1 + 1 + 1 + 1 = 6 +- 解决 4 道第 0 种类型的题目和 1 道第 1 种类型的题目:1 + 1 + 1 + 1 + 2 = 6 +- 解决 2 道第 0 种类型的题目和 2 道第 1 种类型的题目:1 + 1 + 2 + 2 = 6 +- 解决 3 道第 0 种类型的题目和 1 道第 2 种类型的题目:1 + 1 + 1 + 3 = 6 +- 解决 1 道第 0 种类型的题目、1 道第 1 种类型的题目和 1 道第 2 种类型的题目:1 + 2 + 3 = 6 +- 解决 3 道第 1 种类型的题目:2 + 2 + 2 = 6 +- 解决 2 道第 2 种类型的题目:3 + 3 = 6 +``` + +- 示例 2: + +```python +输入:target = 5, types = [[50,1],[50,2],[50,5]] +输出:4 +解释:要获得 5 分,你可以选择以下四种方法之一: +- 解决 5 道第 0 种类型的题目:1 + 1 + 1 + 1 + 1 = 5 +- 解决 3 道第 0 种类型的题目和 1 道第 1 种类型的题目:1 + 1 + 1 + 2 = 5 +- 解决 1 道第 0 种类型的题目和 2 道第 1 种类型的题目:1 + 2 + 2 = 5 +- 解决 1 道第 2 种类型的题目:5 +``` + +## 解题思路 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][w]$ 表示为:前 $i$ 种题目恰好组成 $w$ 分的方案数。 + +###### 3. 状态转移方程 + +前 $i$ 种题目恰好组成 $w$ 分的方案数,等于前 $i - 1$ 种问题恰好组成 $w - k \times marks_i$ 分的方案数总和,即状态转移方程为:$dp[i][w] = \sum_{k = 0} dp[i - 1][w - k \times marks_i]$。 + +###### 4. 初始条件 + +- 前 $0$ 种题目恰好组成 $0$ 分的方案数为 $1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态, $dp[i][w]$ 表示为:前 $i$ 种题目恰好组成 $w$ 分的方案数。 所以最终结果为 $dp[size][target]$。 + +### 思路 1:代码 + +```python +class Solution: + def waysToReachTarget(self, target: int, types: List[List[int]]) -> int: + size = len(types) + group_count = [types[i][0] for i in range(len(types))] + weight = [[(types[i][1] * k) for k in range(types[i][0] + 1)] for i in range(len(types))] + mod = 1000000007 + + dp = [[0 for _ in range(target + 1)] for _ in range(size + 1)] + dp[0][0] = 1 + + # 枚举前 i 组物品 + for i in range(1, size + 1): + # 枚举背包装载重量 + for w in range(target + 1): + # 枚举第 i 组物品能取个数 + dp[i][w] = dp[i - 1][w] + for k in range(1, group_count[i - 1] + 1): + if w >= weight[i - 1][k]: + dp[i][w] += dp[i - 1][w - weight[i - 1][k]] + dp[i][w] %= mod + + return dp[size][target] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target \times m)$,其中 $n$ 为题目种类数,$target$ 为目标分数,$m$ 为每种题目的最大分数。 +- **空间复杂度**:$O(n \times target)$。 + diff --git a/docs/solutions/2700-2799/count-of-integers.md b/docs/solutions/2700-2799/count-of-integers.md new file mode 100644 index 00000000..30a70a8d --- /dev/null +++ b/docs/solutions/2700-2799/count-of-integers.md @@ -0,0 +1,110 @@ +# [2719. 统计整数数目](https://leetcode.cn/problems/count-of-integers/) + +- 标签:数学、字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [2719. 统计整数数目 - 力扣](https://leetcode.cn/problems/count-of-integers/) + +## 题目大意 + +**描述**:给定两个数字字符串 $num1$ 和 $num2$,以及两个整数 $max\underline{\hspace{0.5em}}sum$ 和 $min\underline{\hspace{0.5em}}sum$。 + +**要求**:返回好整数的数目。答案可能很大,请返回答案对 $10^9 + 7$ 取余后的结果。 + +**说明**: + +- **好整数**:如果一个整数 $x$ 满足一下条件,我们称它是一个好整数: + - $num1 \le x \le num2$。 + - $num\underline{\hspace{0.5em}}sum \le digit\underline{\hspace{0.5em}}sum(x) \le max\underline{\hspace{0.5em}}sum$。 + +- $digit\underline{\hspace{0.5em}}sum(x)$ 表示 $x$ 各位数字之和。 +- $1 \le num1 \le num2 \le 10^{22}$。 +- $1 \le min\underline{\hspace{0.5em}}sum \le max\underline{\hspace{0.5em}}sum \le 400$。 + +**示例**: + +- 示例 1: + +```python +输入:num1 = "1", num2 = "12", min_num = 1, max_num = 8 +输出:11 +解释:总共有 11 个整数的数位和在 1 到 8 之间,分别是 1,2,3,4,5,6,7,8,10,11 和 12 。所以我们返回 11。 +``` + +- 示例 2: + +```python +输入:num1 = "1", num2 = "5", min_num = 1, max_num = 5 +输出:5 +解释:数位和在 1 到 5 之间的 5 个整数分别为 1,2,3,4 和 5 。所以我们返回 5。 +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $num1$ 补上前导 $0$,补到和 $num2$ 长度一致,定义递归函数 `def dfs(pos, total, isMaxLimit, isMinLimit):` 表示构造第 $pos$ 位及之后所有数位的合法方案数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True, True)` 开始递归。 `dfs(0, 0, True, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始数位和为 $0$。 + 3. 开始时当前数位最大值受到最高位数位的约束。 + 4. 开始时当前数位最小值受到最高位数位的约束。 +2. 如果 $total > max\underline{\hspace{0.5em}}sum$,说明当前方案不符合要求,则返回方案数 $0$。 +3. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时: + 1. 如果 $min\underline{\hspace{0.5em}}sum \le total \le max\underline{\hspace{0.5em}}sum$,说明当前方案符合要求,则返回方案数 $1$。 + 2. 如果不满足,则当前方案不符合要求,则返回方案数 $0$。 +4. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +5. 根据 $isMaxLimit$ 和 $isMinLimit$ 来决定填当前位数位所能选择的最小数字($minX$)和所能选择的最大数字($maxX$)。 +6. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 +7. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, total + d, isMaxLimit and d == maxX, isMinLimit and d == minX)`。 + 1. `total + d` 表示当前数位和 $total$ 加上 $d$。 + 2. `isMaxLimit and d == maxX` 表示 $pos + 1$ 位最大值受到之前 $pos$ 位限制。 + 3. `isMinLimit and d == maxX` 表示 $pos + 1$ 位最小值受到之前 $pos$ 位限制。 +8. 最后的方案数为 `dfs(0, 0, True, True) % MOD`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def count(self, num1: str, num2: str, min_sum: int, max_sum: int) -> int: + MOD = 10 ** 9 + 7 + # 将 num1 补上前导 0,补到和 num2 长度一致 + m, n = len(num1), len(num2) + if m < n: + num1 = '0' * (n - m) + num1 + + @cache + # pos: 第 pos 个数位 + # total: 表示数位和 + # isMaxLimit: 表示是否受到上限选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + # isMaxLimit: 表示是否受到下限选择限制。如果为真,则第 pos 位填入数字最小为 s[pos];如果为假,则最小可为 0。 + def dfs(pos, total, isMaxLimit, isMinLimit): + if total > max_sum: + return 0 + + if pos == n: + # 当 min_sum <= total <= max_sum 时,当前方案符合要求 + return int(total >= min_sum) + + ans = 0 + # 如果受到选择限制,则最小可选择数字为 num1[pos],否则最大可选择数字为 0。 + minX = int(num1[pos]) if isMinLimit else 0 + # 如果受到选择限制,则最大可选择数字为 num2[pos],否则最大可选择数字为 9。 + maxX = int(num2[pos]) if isMaxLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + ans += dfs(pos + 1, total + d, isMaxLimit and d == maxX, isMinLimit and d == minX) + return ans % MOD + + return dfs(0, 0, True, True) % MOD +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times 10)$,其中 $n$ 为数组 $nums2$ 的长度。 +- **空间复杂度**:$O(n \times max\underline{\hspace{0.5em}}sum)$。 + diff --git a/docs/solutions/2700-2799/index.md b/docs/solutions/2700-2799/index.md new file mode 100644 index 00000000..24b33ddd --- /dev/null +++ b/docs/solutions/2700-2799/index.md @@ -0,0 +1,3 @@ +## 本章内容 + +- [2719. 统计整数数目](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2700-2799/count-of-integers.md) diff --git a/docs/solutions/LCR/0H97ZC.md b/docs/solutions/LCR/0H97ZC.md new file mode 100644 index 00000000..ea7e3ce5 --- /dev/null +++ b/docs/solutions/LCR/0H97ZC.md @@ -0,0 +1,52 @@ +# [LCR 075. 数组的相对排序](https://leetcode.cn/problems/0H97ZC/) + +- 标签:数组、哈希表、计数排序、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 075. 数组的相对排序 - 力扣](https://leetcode.cn/problems/0H97ZC/) + +## 题目大意 + +给定两个数组,`arr1` 和 `arr2`,其中 `arr2` 中的元素各不相同,`arr2` 中的每个元素都出现在 `arr1` 中。 + +要求:对 `arr1` 中的元素进行排序,使 `arr1` 中项的相对顺序和 `arr2` 中的相对顺序相同。未在 `arr2` 中出现过的元素需要按照升序放在 `arr1` 的末尾。 + +注意: + +- `1 <= arr1.length, arr2.length <= 1000`。 +- `0 <= arr1[i], arr2[i] <= 1000`。 + +## 解题思路 + +因为元素值范围在 `[0, 1000]`,所以可以使用计数排序的思路来解题。 + +使用数组 `count` 统计 `arr1` 各个元素个数。 + +遍历 `arr2` 数组,将对应元素`num2` 按照个数 `count[num2]` 添加到答案数组 `ans` 中,同时在 `count` 数组中减去对应个数。 + +然后在处理 `count` 中剩余元素,将 `count` 中大于 `0` 的元素下标依次添加到答案数组 `ans` 中。 + +## 代码 + +```python +class Solution: + def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]: + count = [0 for _ in range(1010)] + for num1 in arr1: + count[num1] += 1 + res = [] + for num2 in arr2: + while count[num2] > 0: + res.append(num2) + count[num2] -= 1 + + for num in range(len(count)): + while count[num] > 0: + res.append(num) + count[num] -= 1 + + return res +``` + diff --git a/docs/solutions/LCR/0on3uN.md b/docs/solutions/LCR/0on3uN.md new file mode 100644 index 00000000..1f76573f --- /dev/null +++ b/docs/solutions/LCR/0on3uN.md @@ -0,0 +1,74 @@ +# [LCR 087. 复原 IP 地址](https://leetcode.cn/problems/0on3uN/) + +- 标签:字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 087. 复原 IP 地址 - 力扣](https://leetcode.cn/problems/0on3uN/) + +## 题目大意 + +给定一个只包含数字的字符串,用来表示一个 IP 地址。 + +要求:返回所有由 `s` 构成的有效 IP 地址,可以按任何顺序返回答案。 + +- 有效 IP 地址:正好由四个整数(每个整数由 0~255 的数构成,且不能含有前导 0),整数之间用 `.` 分割。 + +例如:`0.1.2.201` 和 `192.168.1.1` 是有效 IP 地址,但是 `0.011.255.245`、`192.168.1.312` 和 `192.168@1.1` 是 无效 IP 地址。 + +## 解题思路 + +回溯算法。使用 `res` 存储所有有效 IP 地址。用 `point_num` 表示当前 IP 地址的 `.` 符号个数。 + +定义回溯方法,从 `start_index` 位置开始遍历字符串。 + +- 如果字符串中添加的 `.` 符号数量为 `3`,则判断当前字符串是否为有效 IP 地址,若为有效 IP 地址则加入到 `res` 数组中。直接返回。 +- 然后在 `[start_index, len(s) - 1]` 范围循环遍历,判断 `[start_index, i]` 范围所代表的子串是否合法。如果合法: + - 则 `point_num += 1`。 + - 然后在 i 位置后边增加 `.` 符号,继续回溯遍历。 + - 最后 `point_num -= 1` 进行回退。 +- 不符合则直接跳出循环。 +- 最后返回 `res`。 + +## 代码 + +```python +class Solution: + res = [] + + def backstrack(self, s: str, start_index: int, point_num: int): + if point_num == 3: + if self.isValid(s, start_index, len(s) - 1): + self.res.append(s) + return + for i in range(start_index, len(s)): + if self.isValid(s, start_index, i): + point_num += 1 + self.backstrack(s[:i + 1] + '.' + s[i + 1:], i + 2, point_num) + point_num -= 1 + else: + break + + def isValid(self, s: str, start: int, end: int): + if start > end: + return False + if s[start] == '0' and start != end: + return False + num = 0 + for i in range(start, end + 1): + if s[i] > '9' or s[i] < '0': + return False + num = num * 10 + ord(s[i]) - ord('0') + if num > 255: + return False + return True + + def restoreIpAddresses(self, s: str) -> List[str]: + self.res.clear() + if len(s) > 12: + return self.res + self.backstrack(s, 0, 0) + return self.res +``` + diff --git a/docs/solutions/LCR/0ynMMM.md b/docs/solutions/LCR/0ynMMM.md new file mode 100644 index 00000000..763a9886 --- /dev/null +++ b/docs/solutions/LCR/0ynMMM.md @@ -0,0 +1,49 @@ +# [LCR 039. 柱状图中最大的矩形](https://leetcode.cn/problems/0ynMMM/) + +- 标签:栈、数组、单调栈 +- 难度:困难 + +## 题目链接 + +- [LCR 039. 柱状图中最大的矩形 - 力扣](https://leetcode.cn/problems/0ynMMM/) + +## 题目大意 + +给定一个非负整数数组 `heights` ,`heights[i]` 用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 + +要求:计算出在该柱状图中,能够勾勒出来的矩形的最大面积。 + +## 解题思路 + +思路一:枚举「宽度」。一重循环枚举所有柱子,第二重循环遍历柱子右侧的柱子,所得的宽度就是两根柱子形成区间的宽度,高度就是这段区间中的最小高度。然后计算出对应面积,记录并更新最大面积。这样下来,时间复杂度为 $O(n^2)$。 + +思路二:枚举「高度」。一重循环枚举所有柱子,以柱子高度为当前矩形高度,然后向两侧延伸,遇到小于当前矩形高度的情况就停止。然后计算当前矩形面积,记录并更新最大面积。这样下来,时间复杂度也是 $O(n^2)$。 + +思路三:利用「单调栈」减少两侧延伸的复杂度。 + +- 枚举所有柱子。 +- 如果当前柱子高度较大,大于等于栈顶柱体的高度,则直接将当前柱体入栈。 +- 如果当前柱体高度较小,小于栈顶柱体的高度,则一直出栈,直到当前柱体大于等于栈顶柱体高度。 + - 出栈后,说明当前柱体是出栈柱体向右找到的第一个小于当前柱体高度的柱体,那么就可以向右将宽度扩展到当前柱体。 + - 出栈后,说明新的栈顶柱体是出栈柱体向左找到的第一个小于新的栈顶柱体高度的柱体,那么就可以向左将宽度扩展到新的栈顶柱体。 + - 以新的栈顶柱体为左边界,当前柱体为右边界,以出栈柱体为高度。计算矩形面积,然后记录并更新最大面积。 + +## 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + heights.append(0) + ans = 0 + stack = [] + for i in range(len(heights)): + while stack and heights[stack[-1]] >= heights[i]: + cur = stack.pop(-1) + left = stack[-1] + 1 if stack else 0 + right = i - 1 + ans = max(ans, (right - left + 1) * heights[cur]) + stack.append(i) + + return ans +``` + diff --git a/docs/solutions/LCR/1fGaJU.md b/docs/solutions/LCR/1fGaJU.md new file mode 100644 index 00000000..60c3a1b0 --- /dev/null +++ b/docs/solutions/LCR/1fGaJU.md @@ -0,0 +1,58 @@ +# [LCR 007. 三数之和](https://leetcode.cn/problems/1fGaJU/) + +- 标签:数组、双指针、排序 +- 难度:中等 + +## 题目链接 + +- [LCR 007. 三数之和 - 力扣](https://leetcode.cn/problems/1fGaJU/) + +## 题目大意 + +给定一个包含 `n` 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 `a`、`b`、`c`,满足 `a + b + c = 0`。 + +要求:找出所有满足要求的不重复的三元组。 + +## 解题思路 + +直接三重遍历查找 a、b、c 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 + +先将数组进行排序,以保证按顺序查找 a、b、c 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$ + +第一重循环遍历 a,对于每个 a 元素,从 a 元素的下一个位置开始,使用双指针 left,right。left 指向 a 元素的下一个位置,right 指向末尾位置。先将 left 右移、right 左移去除重复元素,再进行下边的判断。 + +- 若 `nums[a] + nums[left] + nums[right] = 0`,则得到一个解,将其加入答案数组中,并继续将 left 右移,right 左移; + +- 若 `nums[a] + nums[left] + nums[right] > 0`,说明 nums[right] 值太大,将 right 向左移; +- 若 `nums[a] + nums[left] + nums[right] < 0`,说明 nums[left] 值太小,将 left 右移。 + +## 代码 + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + n = len(nums) + nums.sort() + ans = [] + + for i in range(n): + if i > 0 and nums[i] == nums[i - 1]: + continue + left = i + 1 + right = n - 1 + while left < right: + while left < right and left > i + 1 and nums[left] == nums[left - 1]: + left += 1 + while left < right and right < n - 1 and nums[right + 1] == nums[right]: + right -= 1 + if left < right and nums[i] + nums[left] + nums[right] == 0: + ans.append([nums[i], nums[left], nums[right]]) + left += 1 + right -= 1 + elif nums[i] + nums[left] + nums[right] > 0: + right -= 1 + else: + left += 1 + return ans +``` + diff --git a/docs/solutions/LCR/21dk04.md b/docs/solutions/LCR/21dk04.md new file mode 100644 index 00000000..339b0a0d --- /dev/null +++ b/docs/solutions/LCR/21dk04.md @@ -0,0 +1,55 @@ +# [LCR 097. 不同的子序列](https://leetcode.cn/problems/21dk04/) + +- 标签:字符串、动态规划 +- 难度:困难 + +## 题目链接 + +- [LCR 097. 不同的子序列 - 力扣](https://leetcode.cn/problems/21dk04/) + +## 题目大意 + +给定两个字符串 `s` 和 `t`。 + +要求:计算在 `s` 的子序列中 `t` 出现的个数。 + +## 解题思路 + +动态规划求解。 + +定义状态 `dp[i][j]`表示为:以 `i - 1` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数。 + +则状态转移方程为: + +- 如果 `s[i - 1] == t[j - 1]`,则:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`。即 `dp[i][j]` 来源于两部分: + - 使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 2` 为结尾的 `t` 的个数,即 `dp[i - 1][j - 1]`。 + - 不使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数,即 `dp[i - 1][j]`。 +- 如果 `s[i - 1] != t[j - 1]`,那么肯定不能用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于 `dp[i - 1][j]`。 + +下面来看看初始化: + +- `dp[i][0]` 表示以 `i - 1` 为结尾的 `s` 子序列中出现空字符串的个数。把 `s` 中的元素全删除,出现空字符串的个数就是 `1`,则 `dp[i][0] = 1`。 +- `dp[0][j]` 表示空字符串中出现以 `j - 1` 结尾的 `t` 的个数,空字符串无论怎么变都不会变成 `t`,则 `dp[0][j] = 0` +- `dp[0][0]` 表示空字符串中出现空字符串的个数,这个应该是 `1`,即 `dp[0][0] = 1`。 + +然后递推求解,最后输出 `dp[size_s][size_t]`。 + +## 代码 + +```python +class Solution: + def numDistinct(self, s: str, t: str) -> int: + size_s = len(s) + size_t = len(t) + dp = [[0 for _ in range(size_t + 1)] for _ in range(size_s + 1)] + for i in range(size_s): + dp[i][0] = 1 + for i in range(1, size_s + 1): + for j in range(1, size_t + 1): + if s[i - 1] == t[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] + else: + dp[i][j] = dp[i - 1][j] + return dp[size_s][size_t] +``` + diff --git a/docs/solutions/LCR/2AoeFn.md b/docs/solutions/LCR/2AoeFn.md new file mode 100644 index 00000000..3d3d10cd --- /dev/null +++ b/docs/solutions/LCR/2AoeFn.md @@ -0,0 +1,31 @@ +# [LCR 098. 不同路径](https://leetcode.cn/problems/2AoeFn/) + +- 标签:数学、动态规划、组合数学 +- 难度:中等 + +## 题目链接 + +- [LCR 098. 不同路径 - 力扣](https://leetcode.cn/problems/2AoeFn/) + +## 题目大意 + +给定一个 `m * n` 的棋盘, 机器人在左上角的位置,机器人每次只能向右、或者向下移动一步。 + +要求:求出到达棋盘右下角共有多少条不同的路径。 + +## 解题思路 + +可以用动态规划求解,设 `dp[i][j]` 是从 `(0, 0)`到 `(i, j)` 的不同路径数。显然 `dp[i][j] = dp[i-1][j] + dp[i][j-1]`。对于第一行、第一列,因为只能超一个方向走,所以 `dp[i][0] = 1`,`dp[0][j] = 1`。 + +## 代码 + +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp = [1 for _ in range(n)] + for i in range(1, m): + for j in range(1, n): + dp[j] += dp[j - 1] + return dp[-1] +``` + diff --git a/docs/solutions/LCR/2VG8Kg.md b/docs/solutions/LCR/2VG8Kg.md new file mode 100644 index 00000000..e0dc88b9 --- /dev/null +++ b/docs/solutions/LCR/2VG8Kg.md @@ -0,0 +1,51 @@ +# [LCR 008. 长度最小的子数组](https://leetcode.cn/problems/2VG8Kg/) + +- 标签:数组、二分查找、前缀和、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [LCR 008. 长度最小的子数组 - 力扣](https://leetcode.cn/problems/2VG8Kg/) + +## 题目大意 + +给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 + +要求:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。 + +## 解题思路 + +最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 + +定义两个指针 `start` 和 `end`。`start` 代表滑动窗口开始位置,`end` 代表滑动窗口结束位置。再定义一个变量 `sum` 用来存储滑动窗口中的元素和,一个变量 `ans` 来存储满足提议的最小长度。 + +先不断移动 `end`,直到 `sum ≥ target`,则更新最小长度值 `ans`。然后再将滑动窗口的起始位置从滑动窗口中移出去,直到 `sum ≤ target`,在移出的期间,同样要更新最小长度值 `ans`。 + +然后等满足 `sum ≤ target` 时,再移动 `end`,重复上一步,直到遍历到数组末尾。 + +## 代码 + +```python +class Solution: + def minSubArrayLen(self, target: int, nums: List[int]) -> int: + if not nums: + return 0 + n = len(nums) + + start = 0 + end = 0 + sum = 0 + ans = n + 1 + while end < n: + sum += nums[end] + while sum >= target: + ans = min(ans, end - start + 1) + sum -= nums[start] + start += 1 + end += 1 + if ans == n + 1: + return 0 + else: + return ans +``` + diff --git a/docs/solutions/LCR/2bCMpM.md b/docs/solutions/LCR/2bCMpM.md new file mode 100644 index 00000000..8305c5ba --- /dev/null +++ b/docs/solutions/LCR/2bCMpM.md @@ -0,0 +1,51 @@ +# [LCR 107. 01 矩阵](https://leetcode.cn/problems/2bCMpM/) + +- 标签:广度优先搜索、数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [LCR 107. 01 矩阵 - 力扣](https://leetcode.cn/problems/2bCMpM/) + +## 题目大意 + +给定一个由 `0` 和 `1` 组成的矩阵,两个相邻元素间的距离为 `1` 。 + +要求:找出每个元素到最近的 `0` 的距离,并输出为矩阵。 + +## 解题思路 + +题目要求的是每个 `1` 到 `0`的最短曼哈顿距离。换句话也可以求每个 `0` 到 `1` 的最短曼哈顿距离。这样做的好处是,可以从所有值为 `0` 的元素开始进行搜索,可以不断累积距离,直到遇到值为 `1` 的元素时,可以直接将累积距离直接赋值。 + +具体操作如下:将所有值为 `0` 的元素坐标加入访问集合中,对所有值为`0` 的元素上下左右进行搜索。每进行一次上下左右搜索,更新新位置的距离值,并把新的位置坐标加入队列和访问集合中,直到遇见值为 `1` 的元素停止搜索。 + +## 代码 + +```python +class Solution: + def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: + row_count = len(mat) + col_count = len(mat[0]) + dist_map = [[0 for _ in range(col_count)] for _ in range(row_count)] + zeroes_pos = [] + for i in range(row_count): + for j in range(col_count): + if mat[i][j] == 0: + zeroes_pos.append((i, j)) + + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + queue = collections.deque(zeroes_pos) + visited = set(zeroes_pos) + + while queue: + i, j = queue.popleft() + for direction in directions: + new_i = i + direction[0] + new_j = j + direction[1] + if 0 <= new_i < row_count and 0 <= new_j < col_count and (new_i, new_j) not in visited: + dist_map[new_i][new_j] = dist_map[i][j] + 1 + queue.append((new_i, new_j)) + visited.add((new_i, new_j)) + return dist_map +``` + diff --git a/docs/solutions/LCR/3Etpl5.md b/docs/solutions/LCR/3Etpl5.md new file mode 100644 index 00000000..42aa525a --- /dev/null +++ b/docs/solutions/LCR/3Etpl5.md @@ -0,0 +1,35 @@ +# [LCR 049. 求根节点到叶节点数字之和](https://leetcode.cn/problems/3Etpl5/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 049. 求根节点到叶节点数字之和 - 力扣](https://leetcode.cn/problems/3Etpl5/) + +## 题目大意 + +给定一个二叉树的根节点 `root`,树中每个节点都存放有一个 `0` 到 `9` 之间的数字。每条从根节点到叶节点的路径都代表一个数字。例如,从根节点到叶节点的路径是 `1` -> `2` -> `3`,表示数字 `123`。 + +要求:计算从根节点到叶节点生成的所有数字的和。 + +## 解题思路 + +使用深度优先搜索,记录下路径上所有节点构成的数字,使用 `pretotal` 保存下当前路径上构成的数字。如果遇到叶节点直接返回当前数字,否则递归遍历左右子树,并累加对应结果。 + +## 代码 + +```python +class Solution: + def dfs(self, root, pretotal): + if not root: + return 0 + total = pretotal * 10 + root.val + if not root.left and not root.right: + return total + return self.dfs(root.left, total) + self.dfs(root.right, total) + + def sumNumbers(self, root: TreeNode) -> int: + return self.dfs(root, 0) +``` + diff --git a/docs/solutions/LCR/3u1WK4.md b/docs/solutions/LCR/3u1WK4.md new file mode 100644 index 00000000..a975b037 --- /dev/null +++ b/docs/solutions/LCR/3u1WK4.md @@ -0,0 +1,52 @@ +# [LCR 023. 相交链表](https://leetcode.cn/problems/3u1WK4/) + +- 标签:哈希表、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [LCR 023. 相交链表 - 力扣](https://leetcode.cn/problems/3u1WK4/) + +## 题目大意 + +给定 `A`、`B` 两个链表。 + +要求:判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 `None`。 + +比如:链表 A 为 [4, 1, 8, 4, 5],链表 B 为 [5, 0, 1, 8, 4, 5]。则如下图所示,两个链表相交的起始节点为 8,则输出结果为 8。 + +![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) + + + +## 解题思路 + +如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 A 的长度为 m、链表 B 的长度为 n,他们的相交序列有 k 个,则相交情况可以如下如所示: + +![](https://qcdn.itcharge.cn/images/20210401113538.png) + +现在问题是如何找到 m-k 或者 n-k 的位置。 + +考虑将链表 A 的末尾拼接上链表 B,链表 B 的末尾拼接上链表 A。 + +然后使用两个指针 pA 、PB,分别从链表 A、链表 B 的头节点开始遍历,如果走到共同的节点,则返回该节点。 + +否则走到两个链表末尾,返回 None。 + +![](https://qcdn.itcharge.cn/images/20210401114100.png) + +## 代码 + +```python +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + if headA == None or headB == None: + return None + pA = headA + pB = headB + while pA != pB: + pA = pA.next if pA != None else headB + pB = pB.next if pB != None else headA + return pA +``` + diff --git a/docs/solutions/LCR/4sjJUc.md b/docs/solutions/LCR/4sjJUc.md new file mode 100644 index 00000000..3bfc396d --- /dev/null +++ b/docs/solutions/LCR/4sjJUc.md @@ -0,0 +1,56 @@ +# [LCR 082. 组合总和 II](https://leetcode.cn/problems/4sjJUc/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 082. 组合总和 II - 力扣](https://leetcode.cn/problems/4sjJUc/) + +## 题目大意 + +给定一个数组 `candidates` 和一个目标数 `target`。 + +要求:找出 `candidates` 中所有可以使数字和为目标数 `target` 的组合。 + +数组 `candidates` 中的数字在每个组合中只能使用一次,且 `1 ≤ candidates[i] ≤ 50`。 + +## 解题思路 + +本题不能有重复组合,关键步骤在于去重。 + +在回溯遍历的时候,下一层递归的 `start_index` 要从当前节点的后一位开始遍历,即 `i + 1` 位开始。而且统一递归层不能使用相同的元素,即需要增加一句判断 `if i > start_index and candidates[i] == candidates[i - 1]: continue`。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): + if sum > target: + return + if sum == target: + self.res.append(self.path[:]) + return + + for i in range(start_index, len(candidates)): + if sum + candidates[i] > target: + break + if i > start_index and candidates[i] == candidates[i - 1]: + continue + sum += candidates[i] + self.path.append(candidates[i]) + self.backtrack(candidates, target, sum, i + 1) + sum -= candidates[i] + self.path.pop() + + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + self.res.clear() + self.path.clear() + candidates.sort() + self.backtrack(candidates, target, 0, 0) + return self.res +``` + diff --git a/docs/solutions/LCR/4ueAj6.md b/docs/solutions/LCR/4ueAj6.md new file mode 100644 index 00000000..af6458b9 --- /dev/null +++ b/docs/solutions/LCR/4ueAj6.md @@ -0,0 +1,53 @@ +# [LCR 029. 循环有序列表的插入](https://leetcode.cn/problems/4ueAj6/) + +- 标签:链表 +- 难度:中等 + +## 题目链接 + +- [LCR 029. 循环有序列表的插入 - 力扣](https://leetcode.cn/problems/4ueAj6/) + +## 题目大意 + +给定循环升序链表中的一个节点 `head` 和一个整数 `insertVal`。 + +要求:将整数 `insertVal` 插入循环升序链表中,并且满足链表仍为循环升序链表。最终返回原先给定的节点。 + +## 解题思路 + +- 先判断所给节点 `head` 是否为空,为空直接创建一个值为 `insertVal` 的新节点,并指向自己,返回即可。 + +- 如果 `head` 不为空,把 `head` 赋值给 `node` ,方便最后返回原节点 `head`。 +- 然后遍历 `node`,判断插入值 `insertVal` 与 `node.val` 和 `node.next.val` 的关系,找到插入位置,具体判断如下: + - 如果新节点值在两个节点值中间, 即 `node.val <= insertVal <= node.next.val`。则说明新节点值在最大值最小值中间,应将新节点插入到当前位置,则应将 `insertVal` 插入到这个位置。 + - 如果新节点值比当前节点值和当前节点下一节点值都大,并且当前节点值比当前节点值的下一节点值大,即 `node.next.val < node.val <= insertVal`,则说明 `insertVal` 比链表最大值都大,应插入最大值后边。 + - 如果新节点值比当前节点值和当前节点下一节点值都小,并且当前节点值比当前节点值的下一节点值大,即 `insertVal < node.next.val < node.val`,则说明 `insertVal` 比链表中最小值都小,应插入最小值前边。 +- 找到插入位置后,跳出循环,在插入位置插入值为 `insertVal` 的新节点。 + +## 代码 + +```python +class Solution: + def insert(self, head: 'Node', insertVal: int) -> 'Node': + if not head: + node = Node(insertVal) + node.next = node + return node + + node = head + while node.next != head: + if node.val <= insertVal <= node.next.val: + break + elif node.next.val < node.val <= insertVal: + break + elif insertVal < node.next.val < node.val: + break + else: + node = node.next + + insert_node = Node(insertVal) + insert_node.next = node.next + node.next = insert_node + return head +``` + diff --git a/docs/solutions/LCR/569nqc.md b/docs/solutions/LCR/569nqc.md new file mode 100644 index 00000000..f51af70e --- /dev/null +++ b/docs/solutions/LCR/569nqc.md @@ -0,0 +1,42 @@ +# [LCR 035. 最小时间差](https://leetcode.cn/problems/569nqc/) + +- 标签:数组、数学、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [LCR 035. 最小时间差 - 力扣](https://leetcode.cn/problems/569nqc/) + +## 题目大意 + +给定一个 24 小时制形式(小时:分钟 "HH:MM")的时间列表 `timePoints`。 + +要求:找出列表中任意两个时间的最小时间差并以分钟数表示。 + +## 解题思路 + +- 遍历时间列表 `timePoints`,将每个时间转换为以分钟计算的整数形式,比如时间 `14:20`,将其转换为 `14 * 60 + 20 = 860`,存放到新的时间列表 `times` 中。 +- 为了处理最早时间、最晚时间之间的时间间隔,我们将 `times` 中最小时间添加到列表末尾一起进行排序。 +- 然后将新的时间列表 `times` 按照升序排列。 +- 遍历排好序的事件列表 `times` ,找出相邻两个时间的最小间隔值即可。 + +## 代码 + +```python +class Solution: + def changeTime(self, timePoint: str): + hours, minutes = timePoint.split(':') + return int(hours) * 60 + int(minutes) + + def findMinDifference(self, timePoints: List[str]) -> int: + if not timePoints or len(timePoints) > 24 * 60: + return 0 + + times = sorted(self.changeTime(time) for time in timePoints) + times.append(times[0] + 24 * 60) + res = times[-1] + for i in range(1, len(times)): + res = min(res, times[i] - times[i - 1]) + return res +``` + diff --git a/docs/solutions/LCR/6eUYwP.md b/docs/solutions/LCR/6eUYwP.md new file mode 100644 index 00000000..8195bd14 --- /dev/null +++ b/docs/solutions/LCR/6eUYwP.md @@ -0,0 +1,44 @@ +# [LCR 050. 路径总和 III](https://leetcode.cn/problems/6eUYwP/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 050. 路径总和 III - 力扣](https://leetcode.cn/problems/6eUYwP/) + +## 题目大意 + + + +## 解题思路 + + + +## 代码 + +```python +class Solution: + prefixsum_count = dict() + def dfs(self, root, prefixsum_count, target_sum, cur_sum): + if not root: + return 0 + res = 0 + cur_sum += root.val + res += prefixsum_count.get(cur_sum - target_sum, 0) + prefixsum_count[cur_sum] = prefixsum_count.get(cur_sum, 0) + 1 + + res += self.dfs(root.left, prefixsum_count, target_sum, cur_sum) + res += self.dfs(root.right, prefixsum_count, target_sum, cur_sum) + + prefixsum_count[cur_sum] -= 1 + return res + + def pathSum(self, root: TreeNode, targetSum: int) -> int: + if not root: + return 0 + prefixsum_count = dict() + prefixsum_count[0] = 1 + return self.dfs(root, prefixsum_count, targetSum, 0) +``` + diff --git a/docs/solutions/LCR/7LpjUW.md b/docs/solutions/LCR/7LpjUW.md new file mode 100644 index 00000000..db34f9bb --- /dev/null +++ b/docs/solutions/LCR/7LpjUW.md @@ -0,0 +1,58 @@ +# [LCR 118. 冗余连接](https://leetcode.cn/problems/7LpjUW/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [LCR 118. 冗余连接 - 力扣](https://leetcode.cn/problems/7LpjUW/) + +## 题目大意 + +一个 `n` 个节点的树(节点值为 `1~n`)添加一条边后就形成了图,添加的这条边不属于树中已经存在的边。图的信息记录存储与长度为 `n` 的二维数组 `edges`,`edges[i] = [ai, bi]` 表示图中在 `ai` 和 `bi` 之间存在一条边。 + +现在给定代表边信息的二维数组 `edges`。 + +要求:找到一条可以山区的边,使得删除后的剩余部分是一个有着 `n` 个节点的树。如果有多个答案,则返回数组 `edges` 中最后出现的边。 + +## 解题思路 + +树可以看做是无环的图,这道题就是要找出那条添加边之后成环的边。可以考虑用并查集来做。 + +从前向后遍历每一条边,如果边的两个节点不在同一个集合,就加入到一个集合(链接到同一个根节点)。如果边的节点已经出现在同一个集合里,说明边的两个节点已经连在一起了,再加入这条边一定会出现环,则这条边就是所求答案。 + +## 代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + self.parent[root_x] = root_y + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: + size = len(edges) + union_find = UnionFind(size + 1) + + for edge in edges: + if union_find.is_connected(edge[0], edge[1]): + return edge + union_find.union(edge[0], edge[1]) + + return None +``` + diff --git a/docs/solutions/LCR/7WHec2.md b/docs/solutions/LCR/7WHec2.md new file mode 100644 index 00000000..90f421bf --- /dev/null +++ b/docs/solutions/LCR/7WHec2.md @@ -0,0 +1,63 @@ +# [LCR 077. 排序链表](https://leetcode.cn/problems/7WHec2/) + +- 标签:链表、双指针、分治、排序、归并排序 +- 难度:中等 + +## 题目链接 + +- [LCR 077. 排序链表 - 力扣](https://leetcode.cn/problems/7WHec2/) + +## 题目大意 + +给定链表的头节点 `head`。 + +要求:按照升序排列并返回排序后的链表。 + +## 解题思路 + +归并排序。 + +1. 利用快慢指针找到链表的中点,以中点为界限将链表拆分成两个子链表。 +2. 然后对两个子链表分别递归排序。 +3. 将排序后的子链表进行归并排序,得到完整的排序后的链表。 + +## 代码 + +```python +class Solution: + def merge_sort(self, head: ListNode, tail: ListNode) -> ListNode: + if not head: + return head + if head.next == tail: + head.next = None + return head + slow = fast = head + while fast != tail: + slow = slow.next + fast = fast.next + if fast != tail: + fast = fast.next + mid = slow + return self.merge(self.merge_sort(head, mid), self.merge_sort(mid, tail)) + + def merge(self, a: ListNode, b: ListNode) -> ListNode: + root = ListNode(-1) + cur = root + while a and b: + if a.val < b.val: + cur.next = a + a = a.next + else: + cur.next = b + b = b.next + cur = cur.next + if a: + cur.next = a + if b: + cur.next = b + return root.next + + def sortList(self, head: ListNode) -> ListNode: + return self.merge_sort(head, None) +``` + diff --git a/docs/solutions/LCR/7WqeDu.md b/docs/solutions/LCR/7WqeDu.md new file mode 100644 index 00000000..2a490326 --- /dev/null +++ b/docs/solutions/LCR/7WqeDu.md @@ -0,0 +1,58 @@ +# [LCR 057. 存在重复元素 III](https://leetcode.cn/problems/7WqeDu/) + +- 标签:数组、桶排序、有序集合、排序、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [LCR 057. 存在重复元素 III - 力扣](https://leetcode.cn/problems/7WqeDu/) + +## 题目大意 + +给定一个整数数组 `nums`,以及两个整数 `k`、`t`。判断数组中是否存在两个不同下标的 `i` 和 `j`,其对应元素满足 `abs(nums[i] - nums[j]) <= t`,同时满足 `abs(i - j) <= k`。如果满足条件则返回 `True`,不满足条件返回 `False`。 + +## 解题思路 + +对于第 `i` 个元素 `nums[i]`,需要查找的区间为 `[i - t, i + t]`。可以利用桶排序的思想。 + +桶的大小设置为 `t + 1`。我们将元素按照大小依次放入不同的桶中。 + +遍历数组 `nums` 中的元素,对于元素 `nums[i]` : + +- 如果 `nums[i]` 放入桶之前桶里已经有元素了,那么这两个元素必然满足 `abs(nums[i] - nums[j]) <= t`, +- 如果之前桶里没有元素,那么就将 `nums[i]` 放入对应桶中。 +- 然后再判断左右桶的左右两侧桶中是否有元素满足 `abs(nums[i] - nums[j]) <= t`。 +- 然后将 `nums[i - k]` 之前的桶清空,因为这些桶中的元素与 `nums[i]` 已经不满足 `abs(i - j) <= k` 了。 + +最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 + +## 代码 + +```python +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + bucket_dict = dict() + for i in range(len(nums)): + # 将 nums[i] 划分到大小为 t + 1 的不同桶中 + num = nums[i] // (t + 1) + + # 桶中已经有元素了 + if num in bucket_dict: + return True + + # 把 nums[i] 放入桶中 + bucket_dict[num] = nums[i] + + # 判断左侧桶是否满足条件 + if (num - 1) in bucket_dict and abs(bucket_dict[num - 1] - nums[i]) <= t: + return True + # 判断右侧桶是否满足条件 + if (num + 1) in bucket_dict and abs(bucket_dict[num + 1] - nums[i]) <= t: + return True + # 将 i-k 之前的旧桶清除,因为之前的桶已经不满足条件了 + if i >= k: + bucket_dict.pop(nums[i - k] // (t + 1)) + + return False +``` + diff --git a/docs/solutions/LCR/7p8L0Z.md b/docs/solutions/LCR/7p8L0Z.md new file mode 100644 index 00000000..d90c056e --- /dev/null +++ b/docs/solutions/LCR/7p8L0Z.md @@ -0,0 +1,54 @@ +# [LCR 084. 全排列 II](https://leetcode.cn/problems/7p8L0Z/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 084. 全排列 II - 力扣](https://leetcode.cn/problems/7p8L0Z/) + +## 题目大意 + +给定一个可包含重复数字的序列 `nums` 。 + +要求:按任意顺序返回所有不重复的全排列。 + +## 解题思路 + +这道题跟「[LCR 083. 全排列](https://leetcode.cn/problems/VvJkup/)」不一样的地方在于增加了序列中的元素可重复这一条件。这就涉及到了去重。先对 `nums` 进行排序,然后使用 visited 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 + +然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 + +然后进行回溯遍历。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, nums: List[int], visited: List[bool]): + if len(self.path) == len(nums): + self.res.append(self.path[:]) + return + for i in range(len(nums)): + if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: + continue + + if not visited[i]: + visited[i] = True + self.path.append(nums[i]) + self.backtrack(nums, visited) + self.path.pop() + visited[i] = False + + def permuteUnique(self, nums: List[int]) -> List[List[int]]: + self.res.clear() + self.path.clear() + nums.sort() + visited = [False for _ in range(len(nums))] + self.backtrack(nums, visited) + return self.res +``` + diff --git a/docs/solutions/LCR/8Zf90G.md b/docs/solutions/LCR/8Zf90G.md new file mode 100644 index 00000000..3d983af7 --- /dev/null +++ b/docs/solutions/LCR/8Zf90G.md @@ -0,0 +1,37 @@ +# [LCR 036. 逆波兰表达式求值](https://leetcode.cn/problems/8Zf90G/) + +- 标签:栈、数组、数学 +- 难度:中等 + +## 题目链接 + +- [LCR 036. 逆波兰表达式求值 - 力扣](https://leetcode.cn/problems/8Zf90G/) + +## 题目大意 + +给定一个字符串数组 `tokens`,表示「逆波兰表达式」,求解表达式的值。 + +## 解题思路 + +栈的典型应用。遍历字符串数组。遇到操作字符的时候,取出栈顶两个元素,进行运算之后,再将结果入栈。遇到数字,则直接入栈。 + +## 代码 + +```python +class Solution: + def evalRPN(self, tokens: List[str]) -> int: + stack = [] + for token in tokens: + if token == '+': + stack.append(stack.pop() + stack.pop()) + elif token == '-': + stack.append(-stack.pop() + stack.pop()) + elif token == '*': + stack.append(stack.pop() * stack.pop()) + elif token == '/': + stack.append(int(1 / stack.pop() * stack.pop())) + else: + stack.append(int(token)) + return stack.pop() +``` + diff --git a/docs/solutions/LCR/A1NYOS.md b/docs/solutions/LCR/A1NYOS.md new file mode 100644 index 00000000..5e9cb183 --- /dev/null +++ b/docs/solutions/LCR/A1NYOS.md @@ -0,0 +1,68 @@ +# [LCR 011. 连续数组](https://leetcode.cn/problems/A1NYOS/) + +- 标签:数组、哈希表、前缀和 +- 难度:中等 + +## 题目链接 + +- [LCR 011. 连续数组 - 力扣](https://leetcode.cn/problems/A1NYOS/) + +## 题目大意 + +给定一个二进制数组 `nums`。 + +要求:找到含有相同数量 `0` 和 `1` 的最长连续子数组,并返回该子数组的长度。 + +## 解题思路 + +「`0` 和 `1` 数量相同」等价于「`1` 的数量减去 `0` 的数量等于 `0`」。 + +我们可以使用一个变量 `pre_diff` 来记录下前 `i` 个数中,`1` 的数量比 `0` 的数量多多少个。我们把这个 `pre_diff`叫做「`1` 和 `0` 数量差」,也可以理解为变种的前缀和。 + +然后我们再用一个哈希表 `pre_dic` 来记录「`1` 和 `0` 数量差」第一次出现的下标。 + +那么,如果我们在遍历的时候,发现 `pre_diff` 相同的数量差已经在之前出现过了,则说明:这两段之间相减的 `1` 和 `0` 数量差为 `0`。 + +什么意思呢? + +比如说:`j < i`,前 `j` 个数中第一次出现 `pre_diff == 2` ,然后前 `i` 个数中个第二次又出现了 `pre_diff == 2`。那么这两段形成的子数组 `nums[j + 1: i]` 中 `1` 比 `0` 多 `0` 个,则 `0` 和 `1` 数量相同的子数组长度为 `i - j`。 + +而第二次之所以又出现 `pre_diff == 2` ,是因为前半段子数组 `nums[0: j]` 贡献了相同的差值。 + +接下来还有一个小问题,如何计算「`1` 和 `0` 数量差」? + +我们可以把数组中的 `1` 记为贡献 `+1`,`0` 记为贡献 `-1`。然后使用一个变量 `count`,只要出现 `1` 就让 `count` 加上 `1`,意思是又多出了 `1` 个 `1`。只要出现 `0`,将让 `count` 减去 `1`,意思是 `0` 和之前累积的 `1` 个 `1` 相互抵消掉了。这样遍历完数组,也就计算出了对应的「`1` 和 `0` 数量差」。 + +整个思路的具体做法如下: + +- 创建一个哈希表,键值对关系为「`1` 和 `0` 的数量差:最早出现的下标 `i`」。 +- 使用变量 `pre_diff` 来计算「`1` 和 `0` 数量差」,使用变量 `count` 来记录 `0` 和 `1` 数量相同的连续子数组的最长长度,然后遍历整个数组。 +- 如果 `nums[i] == 1`,则让 `pre_diff += 1`;如果 `nums[i] == 0`,则让 `pre_diff -= 1`。 +- 如果在哈希表中发现了相同的 `pre_diff`,则计算相应的子数组长度,与 `count` 进行比较并更新 `count` 值。 +- 如果在哈希表中没有发现相同的 `pre_diff`,则在哈希表中记录下第一次出现 `pre_diff` 的下标 `i`。 +- 最后遍历完输出 `count`。 + +> 注意:初始化哈希表为:`pre_dic = {0: -1}`,意思为空数组时,默认「`1` 和 `0` 数量差」为 `0`,且第一次出现的下标为 `-1`。 +> +> 之所以这样做,是因为在遍历过程中可能会直接出现 `pre_diff == 0` 的情况,这种情况下说明 `nums[0: i]` 中 `0` 和 `1` 数量相同,如果像上边这样初始化后,就可以直接计算出此时子数组长度为 `i - (-1) = i + 1`。 + +## 代码 + +```python +class Solution: + def findMaxLength(self, nums: List[int]) -> int: + pre_dic = {0: -1} + count = 0 + pre_sum = 0 + for i in range(len(nums)): + if nums[i]: + pre_sum += 1 + else: + pre_sum -= 1 + if pre_sum in pre_dic: + count = max(count, i - pre_dic[pre_sum]) + else: + pre_dic[pre_sum] = i + return count +``` + diff --git a/docs/solutions/LCR/D0F0SV.md b/docs/solutions/LCR/D0F0SV.md new file mode 100644 index 00000000..9f4bbc36 --- /dev/null +++ b/docs/solutions/LCR/D0F0SV.md @@ -0,0 +1,40 @@ +# [LCR 104. 组合总和 Ⅳ](https://leetcode.cn/problems/D0F0SV/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 104. 组合总和 Ⅳ - 力扣](https://leetcode.cn/problems/D0F0SV/) + +## 题目大意 + +给定一个由不同整数组成的数组 `nums` 和一个目标整数 `target`。 + +要求:从 `nums` 中找出并返回总和为 `target` 的元素组合个数。 + +## 解题思路 + +完全背包问题。题目求解的是组合数。 + +动态规划的状态 `dp[i]` 可以表示为:凑成总和 `i` 的组合数。 + +动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i - nums[j]]`,意思为凑成总和为 `i` 的组合数 = 「不使用当前 `nums[j]`,只使用之前整数凑成和为 `i` 的组合数」+「使用当前 `nums[j]` 凑成金额 `i - nums[j]` 的方案数」。 + +最终输出 `dp[target]`。 + +## 代码 + +```python +class Solution: + def combinationSum4(self, nums: List[int], target: int) -> int: + dp = [0 for _ in range(target + 1)] + dp[0] = 1 + size = len(nums) + for i in range(target + 1): + for j in range(size): + if i - nums[j] >= 0: + dp[i] += dp[i - nums[j]] + return dp[target] +``` + diff --git a/docs/solutions/LCR/FortPu.md b/docs/solutions/LCR/FortPu.md new file mode 100644 index 00000000..9bc4e8bd --- /dev/null +++ b/docs/solutions/LCR/FortPu.md @@ -0,0 +1,73 @@ +# [LCR 030. O(1) 时间插入、删除和获取随机元素](https://leetcode.cn/problems/FortPu/) + +- 标签:设计、数组、哈希表、数学、随机化 +- 难度:中等 + +## 题目链接 + +- [LCR 030. O(1) 时间插入、删除和获取随机元素 - 力扣](https://leetcode.cn/problems/FortPu/) + +## 题目大意 + +设计一个数据结构 ,支持时间复杂度为 $O(1)$ 的以下操作: + +- `insert(val)`:当元素 val 不存在时,向集合中插入该项。 +- `remove(val)`:元素 val 存在时,从集合中移除该项。 +- `getRandom`:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 + +## 解题思路 + +普通动态数组进行访问操作,需要线性时间查找解决。我们可以利用哈希表记录下每个元素的下标,这样在访问时可以做到常数时间内访问元素了。对应的插入、删除、后去随机元素需要做相应的变化。 + +- 插入操作:将元素直接插入到数组尾部,并用哈希表记录插入元素的下标位置。 +- 删除操作:使用哈希表找到待删除元素所在位置,将其与数组末尾位置元素相互交换,更新哈希表中交换后元素的下标值,并将末尾元素删除。 +- 获取随机元素:使用` random.choice` 获取。 + +## 代码 + +```python +import random + +class RandomizedSet: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.dict = dict() + self.list = list() + + + def insert(self, val: int) -> bool: + """ + Inserts a value to the set. Returns true if the set did not already contain the specified element. + """ + if val in self.dict: + return False + self.dict[val] = len(self.list) + self.list.append(val) + return True + + + def remove(self, val: int) -> bool: + """ + Removes a value from the set. Returns true if the set contained the specified element. + """ + if val in self.dict: + idx = self.dict[val] + last = self.list[-1] + self.list[idx] = last + self.dict[last] = idx + self.list.pop() + self.dict.pop(val) + return True + return False + + + def getRandom(self) -> int: + """ + Get a random element from the set. + """ + return random.choice(self.list) +``` + diff --git a/docs/solutions/LCR/Gu0c2T.md b/docs/solutions/LCR/Gu0c2T.md new file mode 100644 index 00000000..834a6c3c --- /dev/null +++ b/docs/solutions/LCR/Gu0c2T.md @@ -0,0 +1,50 @@ +# [LCR 089. 打家劫舍](https://leetcode.cn/problems/Gu0c2T/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 089. 打家劫舍 - 力扣](https://leetcode.cn/problems/Gu0c2T/) + +## 题目大意 + +给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。假如你是一名专业的小偷。 + +要求:计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 + +## 解题思路 + +可以用动态规划来解决问题,关键点在于找到状态转移方程。 + +先考虑最简单的情况。假如只有一间房,则直接偷这间屋子就能偷到最高金额,即 `dp[0] = nums[i]`。假如只有两间房屋,那么就选择金额最大的那间屋进行偷窃,就可以偷到最高金额,即 `dp[1] = max(nums[0], nums[1])`。 + +如果房屋大于两间,则偷窃第 `i` 间房屋的时候,就有两种状态: + +- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 `dp[i] = dp[i-2] + nums[i]`; +- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 `dp[i] = dp[i-1]`。 + +然后这两种状态取最大值即可,即 `dp[i] = max(dp[i-2] + nums[i], dp[i-1])`。 + +总结下就是: + +$dp[i] = \begin{cases} \begin{array} {**lr**} nums[0] & i = 0 \cr max( nums[0], nums[1]) & i = 1 \cr max( dp[i-2] + nums[i], dp[i-1]) & i \ge 2 \end{array} \end{cases}$ + +## 代码 + +```python +class Solution: + def rob(self, nums: List[int]) -> int: + size = len(nums) + dp = [0 for _ in range(size)] + for i in range(size): + if i == 0: + dp[i] = nums[i] + elif i == 1: + dp[i] = max(nums[i - 1], nums[i]) + else: + dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + + return dp[size - 1] +``` + diff --git a/docs/solutions/LCR/GzCJIP.md b/docs/solutions/LCR/GzCJIP.md new file mode 100644 index 00000000..d577b152 --- /dev/null +++ b/docs/solutions/LCR/GzCJIP.md @@ -0,0 +1,39 @@ +# [LCR 088. 使用最小花费爬楼梯](https://leetcode.cn/problems/GzCJIP/) + +- 标签:数组、动态规划 +- 难度:简单 + +## 题目链接 + +- [LCR 088. 使用最小花费爬楼梯 - 力扣](https://leetcode.cn/problems/GzCJIP/) + +## 题目大意 + +给定一个数组 `cost` 代表一段楼梯,`cost[i]` 代表爬上第 `i` 阶楼梯醒酒药花费的体力值(下标从 `0` 开始)。 + +每爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 + +要求:找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 `0` 或 `1` 的元素作为初始阶梯。 + +## 解题思路 + +使用动态规划方法。 + +状态 `dp[i]` 表示为:到达第 `i` 个台阶所花费的最少体⼒。 + +则状态转移方程为: `dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]`。 + +表示为:到达第 `i` 个台阶所花费的最少体⼒ = 到达第 `i - 1` 个台阶所花费的最小体力 与 到达第 `i - 2` 个台阶所花费的最小体力中的最小值 + 到达第 `i` 个台阶所需要花费的体力值。 + +## 代码 + +```python +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + size = len(cost) + dp = [0 for _ in range(size + 1)] + for i in range(2, size + 1): + dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]) + return dp[size] +``` + diff --git a/docs/solutions/LCR/H8086Q.md b/docs/solutions/LCR/H8086Q.md new file mode 100644 index 00000000..f8ba66de --- /dev/null +++ b/docs/solutions/LCR/H8086Q.md @@ -0,0 +1,38 @@ +# [LCR 042. 最近的请求次数](https://leetcode.cn/problems/H8086Q/) + +- 标签:设计、队列、数据流 +- 难度:简单 + +## 题目链接 + +- [LCR 042. 最近的请求次数 - 力扣](https://leetcode.cn/problems/H8086Q/) + +## 题目大意 + +要求:实现一个用来计算特定时间范围内的最近请求的 `RecentCounter` 类: + +- `RecentCounter()` 初始化计数器,请求数为 0 。 +- `int ping(int t)` 在时间 `t` 时添加一个新请求,其中 `t` 表示以毫秒为单位的某个时间,并返回在 `[t-3000, t]` 内发生的请求数。 + +## 解题思路 + +使用一个队列,用于存储 `[t - 3000, t]` 范围内的请求。 + +获取请求数时,将队首所有小于 `t - 3000` 时间的请求将其从队列中移除,然后返回队列的长度即可。 + +## 代码 + +```python +class RecentCounter: + + def __init__(self): + self.queue = [] + + + def ping(self, t: int) -> int: + self.queue.append(t) + while self.queue[0] < t - 3000: + self.queue.pop(0) + return len(self.queue) +``` + diff --git a/docs/solutions/LCR/IDBivT.md b/docs/solutions/LCR/IDBivT.md new file mode 100644 index 00000000..df0a6d8f --- /dev/null +++ b/docs/solutions/LCR/IDBivT.md @@ -0,0 +1,45 @@ +# [LCR 085. 括号生成](https://leetcode.cn/problems/IDBivT/) + +- 标签:字符串、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 085. 括号生成 - 力扣](https://leetcode.cn/problems/IDBivT/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:生成所有有可能且有效的括号组合。 + +## 解题思路 + +通过回溯算法生成所有答案。为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 + +如果在当前组合中增加一个 `(`,则 `symbol += 1`,如果增加一个 `)`,则 `symbol -= 1`。显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 + +如果最终生成 `2 * n` 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 + +最终输出最终答案数组。 + +## 代码 + +```python +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + def backtrack(parenthesis, symbol, index): + if n * 2 == index: + if symbol == 0: + parentheses.append(parenthesis) + else: + if symbol < n: + backtrack(parenthesis + '(', symbol + 1, index + 1) + if symbol > 0: + backtrack(parenthesis + ')', symbol - 1, index + 1) + + parentheses = list() + backtrack("", 0, 0) + return parentheses +``` + diff --git a/docs/solutions/LCR/JFETK5.md b/docs/solutions/LCR/JFETK5.md new file mode 100644 index 00000000..045fb5db --- /dev/null +++ b/docs/solutions/LCR/JFETK5.md @@ -0,0 +1,49 @@ +# [LCR 002. 二进制求和](https://leetcode.cn/problems/JFETK5/) + +- 标签:位运算、数学、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [LCR 002. 二进制求和 - 力扣](https://leetcode.cn/problems/JFETK5/) + +## 题目大意 + +给定两个二进制数的字符串 `a`、`b`。 + +要求:计算 `a` 和 `b` 的和,返回结果也用二进制表示。 + +## 解题思路 + +这道题可以直接将 `a`、`b` 转换为十进制数,相加后再转换为二进制数。 + +也可以利用位运算的一些知识,直接求和。 + +因为 `a`、`b` 为二进制的字符串,先将其转换为二进制数。 + +本题用到的位运算知识: + +- 异或运算 `x ^ y` :可以获得 `x + y` 无进位的加法结果。 +- 与运算 `x & y`:对应位置为 `1`,说明 `x`、`y` 该位置上原来都为 `1`,则需要进位。 +- 座椅运算 `x << 1`:将 a 对应二进制数左移 `1` 位。 + +这样,通过 `x ^ y` 运算,我们可以得到相加后无进位结果,再根据 `(x & y) << 1`,计算进位后结果。 + +进行 `x ^ y` 和 `(x & y) << 1`操作之后判断进位是否为 `0`,若不为 `0`,则继续上一步操作,直到进位为 `0`。 + +最后将其结果转为 `2` 进制返回。 + +## 代码 + +```python +class Solution: + def addBinary(self, a: str, b: str) -> str: + x = int(a, 2) + y = int(b, 2) + while y: + carry = ((x & y) << 1) + x ^= y + y = carry + return bin(x)[2:] +``` + diff --git a/docs/solutions/LCR/LGjMqU.md b/docs/solutions/LCR/LGjMqU.md new file mode 100644 index 00000000..dd50e411 --- /dev/null +++ b/docs/solutions/LCR/LGjMqU.md @@ -0,0 +1,49 @@ +# [LCR 026. 重排链表](https://leetcode.cn/problems/LGjMqU/) + +- 标签:栈、递归、链表、双指针 +- 难度:中等 + +## 题目链接 + +- [LCR 026. 重排链表 - 力扣](https://leetcode.cn/problems/LGjMqU/) + +## 题目大意 + +给定一个单链表 `L` 的头节点 `head`,单链表 `L` 表示为:$L_0$ -> $L_1$ -> $L_2$ -> ... -> $L_{n-1}$ -> $L_n$。 + +要求:将单链表 `L` 重新排列为:$L_0$ -> $L_n$ -> $L_1$ -> $L_{n-1}$ -> $L_2$ -> $L_{n-2}$ -> $L_3$ -> $L_{n-3}$ -> ...。 + +注意:需要将实际节点进行交换。 + +## 解题思路 + +链表不能像数组那样直接进行随机访问。所以我们可以先将链表转为线性表。然后直接按照提要要求的排列顺序访问对应数据元素,重新建立链表。 + +## 代码 + +```python +class Solution: + def reorderList(self, head: ListNode) -> None: + """ + Do not return anything, modify head in-place instead. + """ + if not head: + return + + vec = [] + node = head + while node: + vec.append(node) + node = node.next + + left, right = 0, len(vec) - 1 + while left < right: + vec[left].next = vec[right] + left += 1 + if left == right: + break + vec[right].next = vec[left] + right -= 1 + vec[left].next = None +``` + diff --git a/docs/solutions/LCR/LwUNpT.md b/docs/solutions/LCR/LwUNpT.md new file mode 100644 index 00000000..8028fbd6 --- /dev/null +++ b/docs/solutions/LCR/LwUNpT.md @@ -0,0 +1,43 @@ +# [LCR 045. 找树左下角的值](https://leetcode.cn/problems/LwUNpT/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 045. 找树左下角的值 - 力扣](https://leetcode.cn/problems/LwUNpT/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:找出该二叉树 「最底层」的「最左边」节点的值。 + +## 解题思路 + +这个问题拆开来看,一是如何找到「最底层」,而是在「最底层」如何找到最左边的节点。 + +通过层序遍历,我们可以直接确定最底层节点。而「最底层」的「最左边」节点可以改变层序遍历的左右节点访问顺序。 + +每层元素先访问右节点,在访问左节点,则最后一个遍历的元素就是「最底层」的「最左边」节点,即左下角的节点,返回该点对应的值即可。 + +## 代码 + +```python +import collections + +class Solution: + def findBottomLeftValue(self, root: TreeNode) -> int: + if not root: + return -1 + queue = collections.deque() + queue.append(root) + while queue: + cur = queue.popleft() + if cur.right: + queue.append(cur.right) + if cur.left: + queue.append(cur.left) + return cur.val +``` + diff --git a/docs/solutions/LCR/M1oyTv.md b/docs/solutions/LCR/M1oyTv.md new file mode 100644 index 00000000..7b2208bb --- /dev/null +++ b/docs/solutions/LCR/M1oyTv.md @@ -0,0 +1,62 @@ +# [LCR 017. 最小覆盖子串](https://leetcode.cn/problems/M1oyTv/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:困难 + +## 题目链接 + +- [LCR 017. 最小覆盖子串 - 力扣](https://leetcode.cn/problems/M1oyTv/) + +## 题目大意 + +给定一个字符串 `s`、一个字符串 `t`。 + +要求:返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""`。如果存在多个符合条件的子字符串,返回任意一个。 + +## 解题思路 + +使用滑动窗口求解。 + +`left`、`right` 表示窗口的边界,一开始都位于下标 `0` 处。`need` 用于记录短字符串需要的字符数。`window` 记录当前窗口内的字符数。 + +将 `right` 右移,直到出现了 `t` 中全部字符,开始右移 `left`,减少滑动窗口的大小,并记录下最小覆盖子串的长度和起始位置。最后输出结果。 + +## 代码 + +```python +class Solution: + def minWindow(self, s: str, t: str) -> str: + need = collections.defaultdict(int) + window = collections.defaultdict(int) + for ch in t: + need[ch] += 1 + + left, right = 0, 0 + valid = 0 + start = 0 + size = len(s) + 1 + + while right < len(s): + insert_ch = s[right] + right += 1 + + if insert_ch in need: + window[insert_ch] += 1 + if window[insert_ch] == need[insert_ch]: + valid += 1 + + while valid == len(need): + if right - left < size: + start = left + size = right - left + remove_ch = s[left] + left += 1 + if remove_ch in need: + if window[remove_ch] == need[remove_ch]: + valid -= 1 + window[remove_ch] -= 1 + if size == len(s) + 1: + return '' + return s[start:start + size] +``` + diff --git a/docs/solutions/LCR/M99OJA.md b/docs/solutions/LCR/M99OJA.md new file mode 100644 index 00000000..f6bc7863 --- /dev/null +++ b/docs/solutions/LCR/M99OJA.md @@ -0,0 +1,63 @@ +# [LCR 086. 分割回文串](https://leetcode.cn/problems/M99OJA/) + +- 标签:深度优先搜索、广度优先搜索、图、哈希表 +- 难度:中等 + +## 题目链接 + +- [LCR 086. 分割回文串 - 力扣](https://leetcode.cn/problems/M99OJA/) + +## 题目大意 + +给定一个字符串 `s`将 `s` 分割成一些子串,保证每个子串都是「回文串」。 + +要求:返回 `s` 所有可能的分割方案。 + +## 解题思路 + +回溯算法,建立两个数组 `res`、`path`。`res` 用于存放所有满足题意的组合,`path` 用于存放当前满足题意的一个组合。 + +在回溯的时候判断当前子串是否为回文串,如果不是则跳过,如果是则继续向下一层遍历。 + +定义判断是否为回文串的方法和回溯方法,从 `start_index = 0` 的位置开始回溯。 + +- 如果 `start_index >= len(s)`,则将 `path` 中的元素加入到 `res` 数组中。 +- 然后对 `[start_index, len(s) - 1]` 范围内的子串进行遍历取值。 + - 如果字符串 `s` 在范围 `[start_index, i]` 所代表的子串是回文串,则将其加入 `path` 数组。 + - 递归遍历 `[i + 1, len(s) - 1]` 范围上的子串。 + - 然后将遍历的范围 `[start_index, i]` 所代表的子串进行回退。 +- 最终返回 `res` 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, s: str, start_index: int): + if start_index >= len(s): + self.res.append(self.path[:]) + return + for i in range(start_index, len(s)): + if self.ispalindrome(s, start_index, i): + self.path.append(s[start_index: i + 1]) + self.backtrack(s, i + 1) + self.path.pop() + + def ispalindrome(self, s: str, start: int, end: int): + i, j = start, end + while i < j: + if s[i] != s[j]: + return False + i += 1 + j -= 1 + return True + + def partition(self, s: str) -> List[List[str]]: + self.res.clear() + self.path.clear() + self.backtrack(s, 0) + return self.res +``` + diff --git a/docs/solutions/LCR/N6YdxV.md b/docs/solutions/LCR/N6YdxV.md new file mode 100644 index 00000000..bde08aea --- /dev/null +++ b/docs/solutions/LCR/N6YdxV.md @@ -0,0 +1,39 @@ +# [LCR 068. 搜索插入位置](https://leetcode.cn/problems/N6YdxV/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 068. 搜索插入位置 - 力扣](https://leetcode.cn/problems/N6YdxV/) + +## 题目大意 + +给定一个排好序的数组 `nums`,以及一个目标值 `target`。 + +要求:在数组中找到目标值,并返回下标。如果找不到,则返回目标值按顺序插入数组的位置。 + +## 解题思路 + +二分查找法。利用两个指针 `left` 和 `right`,分别指向数组首尾位置。每次用 `left` 和 `right` 中间位置上的元素值与目标值做比较,如果等于目标值,则返回当前位置。如果小于目标值,则更新 `left` 位置为 `mid + 1`,继续查找。如果大于目标值,则更新 `right` 位置为 `mid - 1`,继续查找。直到查找到目标值,或者 `left > right` 值时停止查找。然后返回 `left` 所在位置,即是代插入数组的位置。 + +## 代码 + +```python +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + n = len(nums) + left = 0 + right = n - 1 + while left <= right: + mid = left + (right - left) // 2 + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + 1 + else: + right = mid - 1 + + return left +``` + diff --git a/docs/solutions/LCR/NUPfPr.md b/docs/solutions/LCR/NUPfPr.md new file mode 100644 index 00000000..d6ea4a4d --- /dev/null +++ b/docs/solutions/LCR/NUPfPr.md @@ -0,0 +1,47 @@ +# [LCR 101. 分割等和子集](https://leetcode.cn/problems/NUPfPr/) + +- 标签:数学、字符串、模拟 +- 难度:简单 + +## 题目链接 + +- [LCR 101. 分割等和子集 - 力扣](https://leetcode.cn/problems/NUPfPr/) + +## 题目大意 + +给定一个只包含正整数的非空数组 `nums`。 + +要求:判断是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 + +## 解题思路 + +动态规划求解。 + +如果两个子集和相等,则两个子集元素和刚好等于整个数组元素和的一半。这就相当于 `0-1` 背包问题。 + +定义 `dp[i][j]` 表示从 `[0, i]` 个数中任意选取一些数,放进容量为 j 的背包中,价值总和最大为多少。则 `dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i])`。 + +转换为一维 dp 就是:`dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])`。 + +然后进行递归求解。最后判断 `dp[target]` 和 `target` 是否相等即可。 + +## 代码 + +```python +class Solution: + def canPartition(self, nums: List[int]) -> bool: + size = 100010 + dp = [0 for _ in range(size)] + sum_nums = sum(nums) + if sum_nums & 1: + return False + target = sum_nums // 2 + for i in range(len(nums)): + for j in range(target, nums[i] - 1, -1): + dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) + + if dp[target] == target: + return True + return False +``` + diff --git a/docs/solutions/LCR/NYBBNL.md b/docs/solutions/LCR/NYBBNL.md new file mode 100644 index 00000000..7feddd0c --- /dev/null +++ b/docs/solutions/LCR/NYBBNL.md @@ -0,0 +1,54 @@ +# [LCR 052. 递增顺序搜索树](https://leetcode.cn/problems/NYBBNL/) + +- 标签:栈、树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 052. 递增顺序搜索树 - 力扣](https://leetcode.cn/problems/NYBBNL/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root`。 + +要求:按中序遍历顺序将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 + +## 解题思路 + +可以分为两步: + +1. 中序遍历二叉搜索树,将节点先存储到列表中。 +2. 将列表中的节点构造成一棵递增顺序搜索树。 + +中序遍历直接按照 `左 -> 根 -> 右` 的顺序递归遍历,然后将遍历的节点存储到 `res` 中。 + +构造递增顺序搜索树,则用 `head` 保存头节点位置。遍历列表中的每个节点,将其左右指针先置空,再将其连接在上一个节点的右子节点上。 + +最后返回 `head.right` 即可。 + +## 代码 + +```python +class Solution: + def inOrder(self, root, res): + if not root: + return + self.inOrder(root.left, res) + res.append(root) + self.inOrder(root.right, res) + + def increasingBST(self, root: TreeNode) -> TreeNode: + res = [] + self.inOrder(root, res) + + if not res: + return + head = TreeNode(-1) + cur = head + for node in res: + node.left = node.right = None + cur.right = node + cur = cur.right + return head.right +``` + diff --git a/docs/solutions/LCR/NaqhDT.md b/docs/solutions/LCR/NaqhDT.md new file mode 100644 index 00000000..cca716d5 --- /dev/null +++ b/docs/solutions/LCR/NaqhDT.md @@ -0,0 +1,56 @@ +# [LCR 043. 完全二叉树插入器](https://leetcode.cn/problems/NaqhDT/) + +- 标签:树、广度优先搜索、设计、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 043. 完全二叉树插入器 - 力扣](https://leetcode.cn/problems/NaqhDT/) + +## 题目大意 + +要求:设计一个用完全二叉树初始化的数据结构 `CBTInserter`,并支持以下几种操作: + +- `CBTInserter(TreeNode root)` 使用根节点为 `root` 的给定树初始化该数据结构; +- `CBTInserter.insert(int v)` 向树中插入一个新节点,节点类型为 `TreeNode`,值为 `v`。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值; +- `CBTInserter.get_root()` 返回树的根节点。 + +## 解题思路 + +使用数组标记完全二叉树中节点的序号,初始化数组为 `[None]`。完全二叉树中节点的序号从 `1` 开始,对于序号为 `k` 的节点,其左子节点序号为 `2k`,右子节点的序号为 `2k + 1`,其父节点的序号为 `k // 2`。 + +然后在初始化和插入节点的同时,按顺序向数组中插入节点。 + +## 代码 + +```python +class CBTInserter: + + def __init__(self, root: TreeNode): + self.queue = [root] + self.nodelist = [None] + + while self.queue: + node = self.queue.pop(0) + self.nodelist.append(node) + if node.left: + self.queue.append(node.left) + if node.right: + self.queue.append(node.right) + + + def insert(self, v: int) -> int: + self.nodelist.append(TreeNode(v)) + index = len(self.nodelist) - 1 + father = self.nodelist[index // 2] + if index % 2 == 0: + father.left = self.nodelist[-1] + else: + father.right = self.nodelist[-1] + return father.val + + + def get_root(self) -> TreeNode: + return self.nodelist[1] +``` + diff --git a/docs/solutions/LCR/O4NDxx.md b/docs/solutions/LCR/O4NDxx.md new file mode 100644 index 00000000..da9c6857 --- /dev/null +++ b/docs/solutions/LCR/O4NDxx.md @@ -0,0 +1,44 @@ +# [LCR 013. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/O4NDxx/) + +- 标签:设计、数组、矩阵、前缀和 +- 难度:中等 + +## 题目链接 + +- [LCR 013. 二维区域和检索 - 矩阵不可变 - 力扣](https://leetcode.cn/problems/O4NDxx/) + +## 题目大意 + +给定一个二维矩阵 `matrix`。 + +要求:满足以下多个请求: + +- ` def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:`计算以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和。 +- `def __init__(self, matrix: List[List[int]]):` 对二维矩阵 `matrix` 进行初始化操作。 + +## 解题思路 + +在进行初始化的时候做预处理,这样在多次查询时可以减少重复计算,也可以减少时间复杂度。 + +在进行初始化的时候,使用一个二维数组 `pre_sum` 记录下以 `(0, 0)` 为左上角,以当前 `(row, col)` 为右下角的子数组各个元素和,即 `pre_sum[row + 1][col + 1]`。 + +则在查询时,以 `(row1, col1)` 为左上角、`(row2, col2)` 为右下角的子矩阵中各个元素的和就等于以 `(0, 0)` 到 `(row2, col2)` 的大子矩阵减去左边 `(0, 0)` 到 `(row2, col1 - 1)`的子矩阵,再减去上边 `(0, 0)` 到 `(row1 - 1, col2)` 的子矩阵,再加上左上角 `(0, 0)` 到 `(row1 - 1, col1 - 1)` 的子矩阵(因为之前重复减了)。即 `pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1]`。 + +## 代码 + +```python +class NumMatrix: + + def __init__(self, matrix: List[List[int]]): + rows = len(matrix) + cols = len(matrix[0]) + self.pre_sum = [[0 for _ in range(cols + 1)] for _ in range(rows + 1)] + for row in range(rows): + for col in range(cols): + self.pre_sum[row + 1][col + 1] = self.pre_sum[row + 1][col] + self.pre_sum[row][col + 1] - self.pre_sum[row][col] + matrix[row][col] + + + def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int: + return self.pre_sum[row2 + 1][col2 + 1] - self.pre_sum[row2 + 1][col1] - self.pre_sum[row1][col2 + 1] + self.pre_sum[row1][col1] +``` + diff --git a/docs/solutions/LCR/OrIXps.md b/docs/solutions/LCR/OrIXps.md new file mode 100644 index 00000000..5d4cb029 --- /dev/null +++ b/docs/solutions/LCR/OrIXps.md @@ -0,0 +1,83 @@ +# [LCR 031. LRU 缓存](https://leetcode.cn/problems/OrIXps/) + +- 标签:设计、哈希表、链表、双向链表 +- 难度:中等 + +## 题目链接 + +- [LCR 031. LRU 缓存 - 力扣](https://leetcode.cn/problems/OrIXps/) + +## 题目大意 + +要求:实现一个 `LRU(最近最少使用)缓存机制`,并且在 `O(1)` 时间复杂度内完成 `get`、`put` 操作。 + +实现 `LRUCache` 类: + +- `LRUCache(int capacity)` 以正整数作为容量 `capacity` 初始化 LRU 缓存。 +- `int get(int key)` 如果关键字 `key` 存在于缓存中,则返回关键字的值,否则返回 `-1`。 +- `void put(int key, int value)` 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 + +## 解题思路 + +LRU(最近最少使用缓存)是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。LRU 更新和插入新页面都发生在链表首,删除页面都发生在链表尾。 + +## 代码 + +```python +class Node: + def __init__(self, key=None, val=None, prev=None, next=None): + self.key = key + self.val = val + self.prev = prev + self.next = next + +class LRUCache: + + def __init__(self, capacity: int): + self.capacity = capacity + self.hashmap = dict() + self.head = Node() + self.tail = Node() + self.head.next = self.tail + self.tail.prev = self.head + + + def get(self, key: int) -> int: + if key not in self.hashmap: + return -1 + node = self.hashmap[key] + self.move_node(node) + return node.val + + + def put(self, key: int, value: int) -> None: + if key in self.hashmap: + node = self.hashmap[key] + node.val = value + self.move_node(node) + return + if len(self.hashmap) == self.capacity: + self.hashmap.pop(self.head.next.key) + self.remove_node(self.head.next) + + node = Node(key=key, val=value) + self.hashmap[key] = node + self.add_node(node) + + def remove_node(self, node): + node.prev.next = node.next + node.next.prev = node.prev + + + def add_node(self, node): + self.tail.prev.next = node + node.prev = self.tail.prev + node.next = self.tail + self.tail.prev = node + + + def move_node(self, node): + self.remove_node(node) + self.add_node(node) +``` + diff --git a/docs/solutions/LCR/P5rCT8.md b/docs/solutions/LCR/P5rCT8.md new file mode 100644 index 00000000..8f4e937c --- /dev/null +++ b/docs/solutions/LCR/P5rCT8.md @@ -0,0 +1,39 @@ +# [LCR 053. 二叉搜索树中的中序后继](https://leetcode.cn/problems/P5rCT8/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 053. 二叉搜索树中的中序后继 - 力扣](https://leetcode.cn/problems/P5rCT8/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root` 和其中一个节点 `p`。 + +要求:找到该节点在树中的中序后继,即按照中序遍历的顺序节点 `p` 的下一个节点。 + +## 解题思路 + +递归遍历,具体步骤如下: + +- 如果 `root.val` 小于等于 `p.val`,则直接从 `root` 的右子树递归查找比 `p.val` 大的节点,从而找到中序后继。 +- 如果 `root.val` 大于 `p.val`,则 `root` 有可能是中序后继,也有可能是 `root` 的左子树。则从 `root` 的左子树递归查找更接近(更小的)。如果查找的值为 `None`,则当前 `root` 就是中序后继,否则继续递归查找,从而找到中序后继。 + +## 代码 + +```python +class Solution: + def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode': + if not p or not root: + return None + + if root.val <= p.val: + node = self.inorderSuccessor(root.right, p) + else: + node = self.inorderSuccessor(root.left, p) + if not node: + node = root + return node +``` + diff --git a/docs/solutions/LCR/PzWKhm.md b/docs/solutions/LCR/PzWKhm.md new file mode 100644 index 00000000..73a4db2d --- /dev/null +++ b/docs/solutions/LCR/PzWKhm.md @@ -0,0 +1,65 @@ +# [LCR 090. 打家劫舍 II](https://leetcode.cn/problems/PzWKhm/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 090. 打家劫舍 II - 力扣](https://leetcode.cn/problems/PzWKhm/) + +## 题目大意 + +给定一个数组 `nums`,`num[i]` 代表第 `i` 间房屋存放的金额,假设房屋可以围成一圈,首尾相连。相邻的房屋装有防盗系统,假如相邻的两间房屋同时被偷,系统就会报警。假如你是一名专业的小偷。 + +要求:计算在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。 + +## 解题思路 + +「[LCR 089. 打家劫舍](https://leetcode.cn/problems/Gu0c2T/)」的升级版。可以用动态规划来解决问题,关键点在于找到状态转移方程。 + +先来考虑最简单的情况。 + +假如只有一间房屋,则直接偷这间房屋就能偷到最高金额,即 $dp[0] = nums[i]$。假如有两间房屋,那么就选择金额最大的那间房屋进行偷窃,就可以偷到最高金额,即 $dp[1] = max(nums[0], nums[1])$。 + +两间屋子以下,最多只能偷窃一间房屋,则不用考虑首尾相连的情况。如果三个屋子以上,偷窃了第一间房屋,则不能偷窃最后一间房屋。同样偷窃了最后一间房屋则不能偷窃第一间房屋。 + +假设总共房屋数量为 N,这种情况可以转换为分别求解 $[0, N - 2]$ 和 $[1, N - 1]$ 范围下首尾不相连的房屋所能偷窃的最高金额,这就变成了「[LCR 089. 打家劫舍](https://leetcode.cn/problems/Gu0c2T/)」的求解问题。 + +「[LCR 089. 打家劫舍](https://leetcode.cn/problems/Gu0c2T/)」求解思路如下: + +如果房屋大于两间,则偷窃第 `i` 间房屋的时候,就有两种状态: + +- 偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋就不能偷窃了,偷窃的最高金额为:前 `i - 2` 间房屋的最高总金额 + 第 `i` 间房屋的金额,即 $dp[i] = dp[i-2] + nums[i]$; +- 不偷窃第 `i` 间房屋,那么第 `i - 1` 间房屋可以偷窃,偷窃的最高金额为:前 `i - 1` 间房屋的最高总金额,即 $dp[i] = dp[i-1]$。 + +然后这两种状态取最大值即可,即 $dp[i] = max( dp[i-2] + nums[i], dp[i-1])$。 + +总结下就是: + +$dp[i] = \begin{cases} nums[0], & i = 0 \cr max( nums[0], nums[1]) & i = 1 \cr max( dp[i-2] + nums[i], dp[i-1]) & i \ge 2 \end{cases}$ + +## 代码 + +```python +class Solution: + def rob(self, nums: List[int]) -> int: + def helper(nums): + size = len(nums) + if size == 1: + return nums[0] + dp = [0 for _ in range(size)] + for i in range(size): + if i == 0: + dp[i] = nums[0] + elif i == 1: + dp[i] = max(nums[i - 1], nums[i]) + else: + dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + return dp[-1] + + if len(nums) == 1: + return nums[0] + else: + return max(helper(nums[1:]), helper(nums[:-1])) +``` + diff --git a/docs/solutions/LCR/Q91FMA.md b/docs/solutions/LCR/Q91FMA.md new file mode 100644 index 00000000..8eb48dfd --- /dev/null +++ b/docs/solutions/LCR/Q91FMA.md @@ -0,0 +1,152 @@ +# [LCR 093. 最长的斐波那契子序列的长度](https://leetcode.cn/problems/Q91FMA/) + +- 标签:数组、哈希表、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 093. 最长的斐波那契子序列的长度 - 力扣](https://leetcode.cn/problems/Q91FMA/) + +## 题目大意 + +给定一个严格递增的正整数数组 `arr`。 + +要求:从 `arr` 中找出最长的斐波那契式的子序列的长度。如果不存斐波那契式的子序列,则返回 `0`。 + +- 斐波那契式序列:如果序列 $X_1, X_2, ..., X_n$ 满足: + + - $n \ge 3$; + - 对于所有 $i + 2 \le n$,都有 $X_i + X_{i+1} = X_{i+2}$。 + + 则称该序列为斐波那契式序列。 + +- 斐波那契式子序列:从序列 `arr` 中挑选若干元素组成子序列,并且子序列满足斐波那契式序列,则称该序列为斐波那契式子序列。例如:`arr = [3, 4, 5, 6, 7, 8]`。则 `[3, 5, 8]` 是 `arr` 的一个斐波那契式子序列。 + +## 解题思路 + +我们先从最简单的暴力做法思考。 + +**1. 暴力做法:** + +我们先来考虑暴力做法怎么做。 + +假设 `arr[i]`、`arr[j]`、`arr[k]` 是序列 `arr` 中的 3 个元素,且满足关系:`arr[i] + arr[j] == arr[k]`,则 `arr[i]`、`arr[j]`、`arr[k]` 就构成了 A 的一个斐波那契式子序列。 + +通过 `arr[i]`、`arr[j]`,我们可以确定下一个斐波那契式子序列元素的值为 `arr[i] + arr[j]`。 + +因为给定的数组是严格递增的,所以对于一个斐波那契式子序列,如果确定了 `arr[i]`、`arr[j]`,则可以顺着 `arr` 序列,从第 `j + 1` 的元素开始,查找值为 `arr[i] + arr[j]` 的元素 。找到 `arr[i] + arr[j]` 之后,然后在顺着查找子序列的下一个元素。 + +简单来说,就是确定了 `arr[i]`、`arr[j]`,就能尽可能的得到一个长的斐波那契式子序列,此时我们记录下子序列长度。然后对于不同的 `arr[i]`、`arr[j]`,统计不同的斐波那契式子序列的长度。将这些长度进行比较,其中最长的长度就是答案。 + +下面是暴力做法的代码: + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + k = j + 1 + while k < size: + if arr[temp_i] + arr[temp_j] == arr[k]: + temp_ans += 1 + temp_i = temp_j + temp_j = k + k += 1 + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +毫无意外的,超出时间限制了。 + +那么我们怎么来优化呢? + +**2. 使用哈希表优化做法:** + +我们注意到:对于 `arr[i]`、`arr[j]`,要查找的元素 `arr[i] + arr[j]` 是否在 `arr` 中,我们可以预先建立一个反向的哈希表。键值对关系为 `value : idx`,这样就能在 `O(1)` 的时间复杂度通过 `arr[i] + arr[j]` 的值查找到对应的 `k` 值,而不用像原先一样线性查找 `arr[k]` 了。 + +使用哈希表优化之后的代码如下: + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + ans = 0 + idx_map = dict() + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + temp_ans = 0 + temp_i = i + temp_j = j + while arr[temp_i] + arr[temp_j] in idx_map: + temp_ans += 1 + k = idx_map[arr[temp_i] + arr[temp_j]] + temp_i = temp_j + temp_j = k + + if temp_ans > ans: + ans = temp_ans + + if ans > 0: + return ans + 2 + else: + return ans +``` + +再次提交,通过了。 + +但是,这道题我们还可以用动态规划来做。 + +**3. 动态规划做法:** + +这道题用动态规划来做,难点在于如何「定义状态」和「定义状态转移方程」。 + +- 定义状态:`dp[i][j]` 表示以 `arr[i]`、`arr[j]` 为结尾的斐波那契式子序列的最大长度。 +- 定义状态转移方程:$dp[j][k] = max_{(arr[i] + arr[j] = arr[k], i < j < k)}(dp[i][j] + 1)$ + - 意思为:以 `arr[j]`、`arr[k]` 结尾的斐波那契式子序列的最大长度 = 满足 `arr[i] + arr[j] = arr[k]` 条件下,以 `arr[i]`、`arr[j]` 结尾的斐波那契式子序列的最大长度 + 1。 + +但是直接这样做其实跟 **1. 暴力解法** 一样仍会超时,所以我们依旧采用哈希表优化的方式来提高效率,降低算法的时间复杂度。 + +具体代码如下: + +## 代码 + +```python +class Solution: + def lenLongestFibSubseq(self, arr: List[int]) -> int: + size = len(arr) + # 初始化 dp + dp = [[0 for _ in range(size)] for _ in range(size)] + ans = 0 + idx_map = {} + # 将 value : idx 映射为哈希表,这样可以快速通过 value 获取到 idx + for idx, value in enumerate(arr): + idx_map[value] = idx + + for i in range(size): + for j in range(i + 1, size): + if arr[i] + arr[j] in idx_map: + # 获取 arr[i] + arr[j] 的 idx,即斐波那契式子序列下一项元素 + k = idx_map[arr[i] + arr[j]] + + dp[j][k] = max(dp[j][k], dp[i][j] + 1) + ans = max(ans, dp[j][k]) + + if ans > 0: + return ans + 2 + else: + return ans +``` + diff --git a/docs/solutions/LCR/QA2IGt.md b/docs/solutions/LCR/QA2IGt.md new file mode 100644 index 00000000..58c9b720 --- /dev/null +++ b/docs/solutions/LCR/QA2IGt.md @@ -0,0 +1,57 @@ +# [LCR 113. 课程表 II](https://leetcode.cn/problems/QA2IGt/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序 +- 难度:中等 + +## 题目链接 + +- [LCR 113. 课程表 II - 力扣](https://leetcode.cn/problems/QA2IGt/) + +## 题目大意 + +给定一个整数 `numCourses`,代表这学期必须选修的课程数量,课程编号为 `0` 到 `numCourses - 1`。再给定一个数组 `prerequisites` 表示先修课程关系,其中 `prerequisites[i] = [ai, bi]` 表示如果要学习课程 `ai` 则必须要学习课程 `bi`。 + +要求:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。 + +## 解题思路 + +拓扑排序。这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组即可。 + +1. 使用列表 `edges` 存放课程关系图,并统计每门课程节点的入度,存入入度列表 `indegrees`。 + +2. 借助队列 `queue`,将所有入度为 `0` 的节点入队。 + +3. 从队列中选择一个节点,并将其加入到答案数组 `res` 中,再让课程数 -1。 +4. 将该顶点以及该顶点为出发点的所有边的另一个节点入度 -1。如果入度 -1 后的节点入度不为 `0`,则将其加入队列 `queue`。 +5. 重复 3~4 的步骤,直到队列中没有节点。 +6. 最后判断剩余课程数是否为 `0`,如果为 `0`,则返回答案数组 `res`,否则,返回空数组。 + +## 代码 + +```python +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + indegrees = [0 for _ in range(numCourses)] + edges = collections.defaultdict(list) + res = [] + for x, y in prerequisites: + edges[y].append(x) + indegrees[x] += 1 + queue = collections.deque([]) + for i in range(numCourses): + if not indegrees[i]: + queue.append(i) + while queue: + y = queue.popleft() + res.append(y) + numCourses -= 1 + for x in edges[y]: + indegrees[x] -= 1 + if not indegrees[x]: + queue.append(x) + if not numCourses: + return res + else: + return [] +``` + diff --git a/docs/solutions/LCR/QC3q1f.md b/docs/solutions/LCR/QC3q1f.md new file mode 100644 index 00000000..ea15ee2f --- /dev/null +++ b/docs/solutions/LCR/QC3q1f.md @@ -0,0 +1,90 @@ +# [LCR 062. 实现 Trie (前缀树)](https://leetcode.cn/problems/QC3q1f/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 062. 实现 Trie (前缀树) - 力扣](https://leetcode.cn/problems/QC3q1f/) + +## 题目大意 + +要求:实现前缀树数据结构的相关类 `Trie` 类。 + +`Trie` 类: + +- `Trie()` 初始化前缀树对象。 +- `void insert(String word)` 向前缀树中插入字符串 `word`。 +- `boolean search(String word)` 如果字符串 `word` 在前缀树中,返回 `True`(即,在检索之前已经插入);否则,返回 `False`。 +- `boolean startsWith(String prefix)` 如果之前已经插入的字符串 `word` 的前缀之一为 `prefix`,返回 `True`;否则,返回 `False`。 + +## 解题思路 + +前缀树(字典树)是一棵多叉数,其中每个节点包含指向子节点的指针数组 `children`,以及布尔变量 `isEnd`。`children` 用于存储当前字符节点,一般长度为所含字符种类个数,也可以使用哈希表代替指针数组。`isEnd` 用于判断该节点是否为字符串的结尾。 + +下面依次讲解插入、查找前缀的具体步骤: + +插入字符串: + +- 从根节点开始插入字符串。对于待插入的字符,有两种情况: + - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续处理下一个字符。 + - 如果该字符对应的节点不存在,则创建一个新的节点,保存在 `children` 中对应位置上,然后沿着指针移动到子节点,继续处理下一个字符。 +- 重复上述步骤,直到最后一个字符,然后将该节点标记为字符串的结尾。 + +查找前缀: + +- 从跟姐点开始查找前缀,对于待查找的字符,有两种情况: + - 如果该字符对应的节点存在,则沿着指针移动到子节点,继续查找下一个字符。 + - 如果该字符对应的节点不存在,则说明字典树中不包含该前缀,直接返回空指针。 +- 重复上述步骤,直到最后一个字符搜索完毕,则说明字典树中存在该前缀。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + + + def startsWith(self, prefix: str) -> bool: + """ + Returns if there is any word in the trie that starts with the given prefix. + """ + cur = self + for ch in prefix: + if ch not in cur.children: + return False + cur = cur.children[ch] + return cur is not None +``` + diff --git a/docs/solutions/LCR/QTMn0o.md b/docs/solutions/LCR/QTMn0o.md new file mode 100644 index 00000000..797623c0 --- /dev/null +++ b/docs/solutions/LCR/QTMn0o.md @@ -0,0 +1,67 @@ +# [LCR 010. 和为 K 的子数组](https://leetcode.cn/problems/QTMn0o/) + +- 标签:数组、哈希表、前缀和 +- 难度:中等 + +## 题目链接 + +- [LCR 010. 和为 K 的子数组 - 力扣](https://leetcode.cn/problems/QTMn0o/) + +## 题目大意 + +给定一个整数数组 `nums` 和一个整数 `k`。 + +要求:找到该数组中和为 `k` 的连续子数组的个数。 + +## 解题思路 + +看到题目的第一想法是通过滑动窗口求解。但是做下来发现有些数据样例无法通过。发现这道题目中的整数不能保证都为正数,则无法通过滑动窗口进行求解。 + +先考虑暴力做法,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: + +```python +for i in range(len(nums)): + for j in range(i + 1): + sum = countSum(i, j) +``` + +这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 + +先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^3)$。 + +但是还是超时了。。 + +由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 + +`pre_sum[i]` 的定义是前 `i` 个元素和,则 `pre_sum[i]` 可以由 `pre_sum[i - 1]` 递推而来,即:`pre_sum[i] = pre_sum[i - 1] + sum[i]`。 `[j..i]` 子数组和为 `k` 可以转换为:`pre_sum[i] - pre_sum[j - 1] == k`。 + +综合一下,可得:`pre_sum[j - 1] == pre_sum[i] - k `。 + +所以,当我们考虑以 `i` 结尾和为 `k` 的连续子数组个数时,只需要统计有多少个前缀和为 `pre_sum[i] - k` (即 `pre_sum[j - 1]`)的个数即可。具体做法如下: + +- 使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。 +- 使用哈希表 `pre_dic` 记录 `pre_sum[i]` 出现的次数。键值对为 `pre_sum[i] : pre_sum_count`。 +- 从左到右遍历数组,计算当前前缀和 `pre_sum`。 +- 如果 `pre_sum - k` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum - k]`。 +- 如果 `pre_sum` 在哈希表中,则前缀和个数累加 1,即 `pre_dic[pre_sum] += 1`。 +- 最后输出答案个数。 + +## 代码 + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + pre_dic = {0: 1} + pre_sum = 0 + count = 0 + for num in nums: + pre_sum += num + if pre_sum - k in pre_dic: + count += pre_dic[pre_sum - k] + if pre_sum in pre_dic: + pre_dic[pre_sum] += 1 + else: + pre_dic[pre_sum] = 1 + return count +``` + diff --git a/docs/solutions/LCR/Qv1Da2.md b/docs/solutions/LCR/Qv1Da2.md new file mode 100644 index 00000000..50562f52 --- /dev/null +++ b/docs/solutions/LCR/Qv1Da2.md @@ -0,0 +1,65 @@ +# [LCR 028. 扁平化多级双向链表](https://leetcode.cn/problems/Qv1Da2/) + +- 标签:深度优先搜索、链表、双向链表 +- 难度:中等 + +## 题目链接 + +- [LCR 028. 扁平化多级双向链表 - 力扣](https://leetcode.cn/problems/Qv1Da2/) + +## 题目大意 + +给定一个带子链表指针 `child` 的双向链表。 + +要求:将 `child` 的子链表进行扁平化处理,使所有节点出现在单级双向链表中。 + +扁平化处理如下: + +``` +原链表: +1---2---3---4---5---6--NULL + | + 7---8---9---10--NULL + | + 11--12--NULL +扁平化之后: +1---2---3---7---8---11---12---9---10---4---5---6--NULL +``` + +## 解题思路 + +递归处理多层链表的扁平化。遍历链表,找到 `child` 非空的节点, 将其子链表链接到当前节点的 `next` 位置(自身扁平化处理)。然后继续向后遍历,不断找到 `child` 节点,并进行链接。直到处理到尾部位置。 + +## 代码 + +```python +class Solution: + def dfs(self, node: 'Node'): + # 找到链表的尾节点或 child 链表不为空的节点 + while node.next and not node.child: + node = node.next + tail = None + if node.child: + # 如果 child 链表不为空,将 child 链表扁平化 + tail = self.dfs(node.child) + + # 将扁平化的 child 链表链接在该节点之后 + temp = node.next + node.next = node.child + node.next.prev = node + node.child = None + tail.next = temp + if temp: + temp.prev = tail + # 链接之后,从 child 链表的尾节点继续向后处理链表 + return self.dfs(tail) + # child 链表为空,则该节点是尾节点,直接返回 + return node + + def flatten(self, head: 'Node') -> 'Node': + if not head: + return head + self.dfs(head) + return head +``` + diff --git a/docs/solutions/LCR/RQku0D.md b/docs/solutions/LCR/RQku0D.md new file mode 100644 index 00000000..75476cb6 --- /dev/null +++ b/docs/solutions/LCR/RQku0D.md @@ -0,0 +1,54 @@ +# [LCR 019. 验证回文串 II](https://leetcode.cn/problems/RQku0D/) + +- 标签:贪心、双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 019. 验证回文串 II - 力扣](https://leetcode.cn/problems/RQku0D/) + +## 题目大意 + +给定一个非空字符串 `s`。 + +要求:判断如果最多从字符串中删除一个字符能否得到一个回文字符串。 + +## 解题思路 + +双指针 + 贪心算法。 + +- 用两个指针 `left`、`right` 分别指向字符串的开始和结束位置。 + +- 判断 `s[left]` 是否等于 `s[right]`。 + - 如果等于,则 `left` 右移、`right`左移。 + - 如果不等于,则判断 `s[left: right - 1]` 或 `s[left + 1, right]` 是为回文串。 + - 如果是则返回 `True`。 + - 如果不是则返回 `False`,然后继续判断。 +- 如果 `right >= left`,则说明字符串 `s` 本身就是回文串,返回 `True`。 + + + +## 代码 + +```python +class Solution: + def checkPalindrome(self, s: str, left: int, right: int): + i, j = left, right + while i < j: + if s[i] != s[j]: + return False + i += 1 + j -= 1 + return True + + def validPalindrome(self, s: str) -> bool: + left, right = 0, len(s) - 1 + while left < right: + if s[left] == s[right]: + left += 1 + right -= 1 + else: + return self.checkPalindrome(s, left + 1, right) or self.checkPalindrome(s, left, right - 1) + return True +``` + diff --git a/docs/solutions/LCR/SLwz0R.md b/docs/solutions/LCR/SLwz0R.md new file mode 100644 index 00000000..b5da545e --- /dev/null +++ b/docs/solutions/LCR/SLwz0R.md @@ -0,0 +1,41 @@ +# [LCR 021. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/SLwz0R/) + +- 标签:链表、双指针 +- 难度:中等 + +## 题目链接 + +- [LCR 021. 删除链表的倒数第 N 个结点 - 力扣](https://leetcode.cn/problems/SLwz0R/) + +## 题目大意 + +给你一个链表的头节点 `head` 和一个整数 `n`。 + +要求:删除链表的倒数第 `n` 个节点,并且返回链表的头节点。并且要求使用一次遍历实现。 + +## 解题思路 + +常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,删除该位置上的节点。 + +如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 n 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 n 个节点位置。将该位置上的节点删除即可。 + +需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 + +## 代码 + +```python +class Solution: + def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: + newHead = ListNode(0, head) + fast = head + slow = newHead + while n: + fast = fast.next + n -= 1 + while fast: + fast = fast.next + slow = slow.next + slow.next = slow.next.next + return newHead.next +``` + diff --git a/docs/solutions/LCR/SsGoHC.md b/docs/solutions/LCR/SsGoHC.md new file mode 100644 index 00000000..fb2f91a8 --- /dev/null +++ b/docs/solutions/LCR/SsGoHC.md @@ -0,0 +1,37 @@ +# [LCR 074. 合并区间](https://leetcode.cn/problems/SsGoHC/) + +- 标签:数组、排序 +- 难度:中等 + +## 题目链接 + +- [LCR 074. 合并区间 - 力扣](https://leetcode.cn/problems/SsGoHC/) + +## 题目大意 + +给定一个数组 `intervals` 表示若干个区间的集合,`intervals[i] = [starti, endi]` 表示单个区间。 + +要求:合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需要恰好覆盖原数组中的所有区间。 + +## 解题思路 + +设定一个数组 `ans` 用于表示最终不重叠的区间数组,然后对原始区间先按照区间左端点大小从小到大进行排序。 + +遍历所有区间。先将第一个区间加入 `ans` 数组中。然后依次考虑后边的区间,如果第 `i` 个区间左端点在前一个区间右端点右侧,则这两个区间不会重合,直接将该区间加入 `ans` 数组中。否则的话,这两个区间重合,判断一下两个区间的右区间值,更新前一个区间的右区间值为较大值,然后继续考虑下一个区间,以此类推。 + +## 代码 + +```python +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + intervals.sort(key=lambda x: x[0]) + + ans = [] + for interval in intervals: + if not ans or ans[-1][1] < interval[0]: + ans.append(interval) + else: + ans[-1][1] = max(ans[-1][1], interval[1]) + return ans +``` + diff --git a/docs/solutions/LCR/TVdhkn.md b/docs/solutions/LCR/TVdhkn.md new file mode 100644 index 00000000..28cc4174 --- /dev/null +++ b/docs/solutions/LCR/TVdhkn.md @@ -0,0 +1,35 @@ +# [LCR 079. 子集](https://leetcode.cn/problems/TVdhkn/) + +- 标签:位运算、数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 079. 子集 - 力扣](https://leetcode.cn/problems/TVdhkn/) + +## 题目大意 + +给定一个整数数组 `nums`,数组中的元素互不相同。 + +要求:返回该数组所有可能的不重复子集。 + +## 解题思路 + +回溯算法,遍历数组 `nums`。为了使得子集不重复,每次遍历从当前位置的下一个位置进行下一层遍历。 + +## 代码 + +```python +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + def backtrack(size, subset, index): + res.append(subset) + for i in range(index, size): + backtrack(size, subset + [nums[i]], i + 1) + + size = len(nums) + res = list() + backtrack(size, [], 0) + return res +``` + diff --git a/docs/solutions/LCR/UHnkqh.md b/docs/solutions/LCR/UHnkqh.md new file mode 100644 index 00000000..4f2f3bb5 --- /dev/null +++ b/docs/solutions/LCR/UHnkqh.md @@ -0,0 +1,83 @@ +# [LCR 024. 反转链表](https://leetcode.cn/problems/UHnkqh/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [LCR 024. 反转链表 - 力扣](https://leetcode.cn/problems/UHnkqh/) + +## 题目大意 + +**描述**:给定一个单链表的头节点 `head`。 + +**要求**:将其进行反转,并返回反转后的链表的头节点。 + +## 解题思路 + +### 思路 1. 迭代 + +1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 + +2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: + 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; + 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; + 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; + 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 +3. 继续执行第 2 步中的 1、2、3、4。 +4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 + +使用迭代法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111133639.png) + +### 思路 2. 递归 + +具体做法如下: + +- 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 +- 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 +- 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 +- 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 +- 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 +- 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 +- 最后返回反转后的链表头节点 `new_head`。 + +使用递归法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111134246.png) + +## 代码 + +1. 迭代 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + pre = None + cur = head + while cur != None: + next = cur.next + cur.next = pre + pre = cur + cur = next + return pre +``` + +2. 递归 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head == None or head.next == None: + return head + new_head = self.reverseList(head.next) + head.next.next = head + head.next = None + return new_head +``` + +## 参考资料 + +- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) +- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) \ No newline at end of file diff --git a/docs/solutions/LCR/US1pGT.md b/docs/solutions/LCR/US1pGT.md new file mode 100644 index 00000000..a41bdcfd --- /dev/null +++ b/docs/solutions/LCR/US1pGT.md @@ -0,0 +1,90 @@ +# [LCR 064. 实现一个魔法字典](https://leetcode.cn/problems/US1pGT/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 064. 实现一个魔法字典 - 力扣](https://leetcode.cn/problems/US1pGT/) + +## 题目大意 + +要求:设计一个使用单词表进行初始化的数据结构。单词表中的单词互不相同。如果给出一个单词,要求判定能否将该单词中的一个字母替换成另一个字母,是的所形成的新单词已经在够构建的单词表中。 + +实现 MagicDictionary 类: + +- `MagicDictionary()` 初始化对象。 +- `void buildDict(String[] dictionary)` 使用字符串数组 `dictionary` 设定该数据结构,`dictionary` 中的字符串互不相同。 +- `bool search(String searchWord)` 给定一个字符串 `searchWord`,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 `True`;否则,返回 `False`。 + +## 解题思路 + +- 初始化使用字典树结构。 + +- `buildDict` 方法中将所有单词存入字典树中。 + +- `search` 方法中替换 `searchWord` 每一个位置上的字符,然后在字典树中查询。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class MagicDictionary: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.trie_tree = Trie() + + + def buildDict(self, dictionary: List[str]) -> None: + for word in dictionary: + self.trie_tree.insert(word) + + + def search(self, searchWord: str) -> bool: + size = len(searchWord) + for i in range(size): + for j in range(26): + new_ch = chr(ord('a') + j) + if searchWord[i] != new_ch: + new_word = searchWord[:i] + new_ch + searchWord[i + 1:] + if self.trie_tree.search(new_word): + return True + return False +``` + diff --git a/docs/solutions/LCR/UhWRSj.md b/docs/solutions/LCR/UhWRSj.md new file mode 100644 index 00000000..d9bce2db --- /dev/null +++ b/docs/solutions/LCR/UhWRSj.md @@ -0,0 +1,74 @@ +# [LCR 063. 单词替换](https://leetcode.cn/problems/UhWRSj/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 063. 单词替换 - 力扣](https://leetcode.cn/problems/UhWRSj/) + +## 题目大意 + +给定一个由许多词根组成的字典列表 `dictionary`,以及一个句子字符串 `sentence`。 + +要求:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。 + +## 解题思路 + +将所有的词根存入到前缀树(字典树)中。然后在树上查找每个单词的最短词根。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> str: + """ + Returns if the word is in the trie. + """ + cur = self + index = 0 + for ch in word: + if ch not in cur.children: + return word + cur = cur.children[ch] + index += 1 + if cur.isEnd: + break + return word[:index] + + +class Solution: + def replaceWords(self, dictionary: List[str], sentence: str) -> str: + trie_tree = Trie() + for word in dictionary: + trie_tree.insert(word) + + words = sentence.split(" ") + size = len(words) + for i in range(size): + word = words[i] + words[i] = trie_tree.search(word) + return ' '.join(words) +``` + diff --git a/docs/solutions/LCR/VvJkup.md b/docs/solutions/LCR/VvJkup.md new file mode 100644 index 00000000..9d0320f4 --- /dev/null +++ b/docs/solutions/LCR/VvJkup.md @@ -0,0 +1,42 @@ +# [LCR 083. 全排列](https://leetcode.cn/problems/VvJkup/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 083. 全排列 - 力扣](https://leetcode.cn/problems/VvJkup/) + +## 题目大意 + +给定一个不含重复数字的数组 `nums` 。 + +要求:返回其有可能的全排列,可以按任意顺序返回。 + +## 解题思路 + +回溯算法递归遍历 `nums` 元素。同时使用 `visited` 数组来标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 + +## 代码 + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + def backtrack(size, arrange, index): + if index == size: + res.append(arrange) + return + for i in range(size): + if visited[i] == True: + continue + visited[i] = True + backtrack(size, arrange + [nums[i]], index + 1) + visited[i] = False + + size = len(nums) + res = list() + visited = [False for _ in range(size)] + backtrack(size, [], 0) + return res +``` + diff --git a/docs/solutions/LCR/WGki4K.md b/docs/solutions/LCR/WGki4K.md new file mode 100644 index 00000000..18fc3783 --- /dev/null +++ b/docs/solutions/LCR/WGki4K.md @@ -0,0 +1,69 @@ +# [LCR 004. 只出现一次的数字 II](https://leetcode.cn/problems/WGki4K/) + +- 标签:位运算、数组 +- 难度:中等 + +## 题目链接 + +- [LCR 004. 只出现一次的数字 II - 力扣](https://leetcode.cn/problems/WGki4K/) + +## 题目大意 + +给定一个整数数组 `nums`,除了某个元素仅出现一次外,其余每个元素恰好出现三次。 + +要求:找到并返回那个只出现了一次的元素。 + +## 解题思路 + +### 1. 哈希表 + +朴素解法就是利用哈希表。统计出每个元素的出现次数。再遍历哈希表,找到仅出现一次的元素。 + +### 2. 位运算 + +将出现三次的元素换成二进制形式放在一起,其二进制对应位置上,出现 `1` 的个数一定是 `3` 的倍数(包括 `0`)。此时,如果在放进来只出现一次的元素,则某些二进制位置上出现 `1` 的个数就不是 `3` 的倍数了。 + +将这些二进制位置上出现 `1` 的个数不是 `3` 的倍数位置值置为 `1`,是 `3` 的倍数则置为 `0`。这样对应下来的二进制就是答案所求。 + +注意:因为 Python 的整数没有位数限制,所以不能通过最高位确定正负。所以 Python 中负整数的补码会被当做正整数。所以在遍历到最后 `31` 位时进行 `ans -= (1 << 31)` 操作,目的是将负数的补码转换为「负号 + 原码」的形式。这样就可以正常识别二进制下的负数。参考:[Two's Complement Binary in Python? - Stack Overflow](https://stackoverflow.com/questions/12946116/twos-complement-binary-in-python/12946226) + +## 代码 + +1. 哈希表 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + nums_dict = dict() + for num in nums: + if num in nums_dict: + nums_dict[num] += 1 + else: + nums_dict[num] = 1 + for key in nums_dict: + value = nums_dict[key] + if value == 1: + return key + return 0 +``` + +2. 位运算 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + ans = 0 + for i in range(32): + count = 0 + for j in range(len(nums)): + count += (nums[j] >> i) & 1 + if count % 3 != 0: + if i == 31: + ans -= (1 << 31) + else: + ans = ans | 1 << i + return ans +``` + + + diff --git a/docs/solutions/LCR/WNC0Lk.md b/docs/solutions/LCR/WNC0Lk.md new file mode 100644 index 00000000..3d33a4b3 --- /dev/null +++ b/docs/solutions/LCR/WNC0Lk.md @@ -0,0 +1,43 @@ +# [LCR 046. 二叉树的右视图](https://leetcode.cn/problems/WNC0Lk/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 046. 二叉树的右视图 - 力扣](https://leetcode.cn/problems/WNC0Lk/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:按照从顶部到底部的顺序,返回从右侧能看到的节点值。 + +## 解题思路 + +二叉树的层次遍历,不过遍历每层节点的时候,只需要将最后一个节点加入结果数组即可。 + +## 代码 + +```python +class Solution: + def rightSideView(self, root: TreeNode) -> List[int]: + if not root: + return [] + queue = [root] + order = [] + while queue: + level = [] + size = len(queue) + for i in range(size): + curr = queue.pop(0) + level.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if i == size - 1: + order.append(curr.val) + return order +``` + diff --git a/docs/solutions/LCR/WhsWhI.md b/docs/solutions/LCR/WhsWhI.md new file mode 100644 index 00000000..ec9f906d --- /dev/null +++ b/docs/solutions/LCR/WhsWhI.md @@ -0,0 +1,46 @@ +# [LCR 119. 最长连续序列](https://leetcode.cn/problems/WhsWhI/) + +- 标签:并查集、数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [LCR 119. 最长连续序列 - 力扣](https://leetcode.cn/problems/WhsWhI/) + +## 题目大意 + +给定一个未排序的整数数组 `nums`。 + +要求:找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。并且要用时间复杂度为 $O(n)$ 的算法解决此问题。 + +## 解题思路 + +暴力做法有两种思路。第 1 种思路是先排序再依次判断,这种做法时间复杂度最少是 $O(n \log n)$。第 2 种思路是枚举数组中的每个数 `num`,考虑以其为起点,不断尝试匹配 `num + 1`、`num + 2`、`...` 是否存在,最长匹配次数为 `len(nums)`。这样下来时间复杂度为 $O(n^2)$。但是可以使用集合或哈希表优化这个步骤。 + +- 先将数组存储到集合中进行去重,然后使用 `curr_streak` 维护当前连续序列长度,使用 `ans` 维护最长连续序列长度。 +- 遍历集合中的元素,对每个元素进行判断,如果该元素不是序列的开始(即 `num - 1` 在集合中),则跳过。 +- 如果 `num - 1` 不在集合中,说明 `num` 是序列的开始,判断 `num + 1` 、`nums + 2`、`...` 是否在哈希表中,并不断更新当前连续序列长度 `curr_streak`。并在遍历结束之后更新最长序列的长度。 +- 最后输出最长序列长度。 + +将数组存储到集合中进行去重的操作的时间复杂度是 $O(n)$。查询每个数是否在集合中的时间复杂度是 $O(1)$ ,并且跳过了所有不是起点的元素。更新当前连续序列长度 `curr_streak` 的时间复杂度是 $O(n)$,所以最终的时间复杂度是 $O(n)$。符合题意要求。 + +## 代码 + +```python +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + ans = 0 + nums_set = set(nums) + for num in nums_set: + if num - 1 not in nums_set: + curr_num = num + curr_streak = 1 + + while curr_num + 1 in nums_set: + curr_num += 1 + curr_streak += 1 + ans = max(ans, curr_streak) + + return ans +``` + diff --git a/docs/solutions/LCR/XagZNi.md b/docs/solutions/LCR/XagZNi.md new file mode 100644 index 00000000..128681b4 --- /dev/null +++ b/docs/solutions/LCR/XagZNi.md @@ -0,0 +1,51 @@ +# [LCR 037. 行星碰撞](https://leetcode.cn/problems/XagZNi/) + +- 标签:栈、数组 +- 难度:中等 + +## 题目链接 + +- [LCR 037. 行星碰撞 - 力扣](https://leetcode.cn/problems/XagZNi/) + +## 题目大意 + +给定一个整数数组 `asteroids`,表示在同一行的小行星。 + +数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。小行星按照下面的规则发生碰撞。 + +- 碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 + +要求:找出碰撞后剩下的所有小行星,将答案存入数组并返回。 + +## 解题思路 + +用栈模拟小行星碰撞,具体步骤如下: + +- 遍历数组 `asteroids`。 +- 如果栈为空或者当前元素 `asteroid` 为正数,将其压入栈。 +- 如果当前栈不为空并且当前元素 `asteroid` 为负数: + - 与栈中元素发生碰撞,判断当前元素和栈顶元素的大小和方向,如果栈顶元素为正数,并且当前元素的绝对值大于栈顶元素,则将栈顶元素弹出,并继续与栈中元素发生碰撞。 + - 碰撞完之后,如果栈为空并且栈顶元素为负数,则将当前元素 `asteroid` 压入栈,表示碰撞完剩下了 `asteroid`。 + - 如果栈顶元素恰好与当前元素值大小相等、方向相反,则弹出栈顶元素,表示碰撞完两者都爆炸了。 +- 最后返回栈作为答案。 + +## 代码 + +```python +class Solution: + def asteroidCollision(self, asteroids: List[int]) -> List[int]: + stack = [] + for asteroid in asteroids: + if not stack or asteroid > 0: + stack.append(asteroid) + else: + while stack and 0 < stack[-1] < -asteroid: + stack.pop() + if not stack or stack[-1] < 0: + stack.append(asteroid) + elif stack[-1] == -asteroid: + stack.pop() + + return stack +``` + diff --git a/docs/solutions/LCR/XltzEq.md b/docs/solutions/LCR/XltzEq.md new file mode 100644 index 00000000..d6ce554b --- /dev/null +++ b/docs/solutions/LCR/XltzEq.md @@ -0,0 +1,42 @@ +# [LCR 018. 验证回文串](https://leetcode.cn/problems/XltzEq/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 018. 验证回文串 - 力扣](https://leetcode.cn/problems/XltzEq/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:判断是否为回文串。(只考虑字符串中的字母和数字字符,并且忽略字母的大小写) + +## 解题思路 + +左右两个指针 `start` 和 `end`,左指针 `start` 指向字符串头部,右指针 `end` 指向字符串尾部。先过滤掉除字母和数字字符以外的字符,在判断 `s[start]` 和 `s[end]` 是否相等。不相等返回 `False`,相等则继续过滤和判断。 + +## 代码 + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + n = len(s) + start = 0 + end = n - 1 + while start < end: + if not s[start].isalnum(): + start += 1 + continue + if not s[end].isalnum(): + end -= 1 + continue + if s[start].lower() == s[end].lower(): + start += 1 + end -= 1 + else: + return False + return True +``` + diff --git a/docs/solutions/LCR/YaVDxD.md b/docs/solutions/LCR/YaVDxD.md new file mode 100644 index 00000000..76e11f0c --- /dev/null +++ b/docs/solutions/LCR/YaVDxD.md @@ -0,0 +1,46 @@ +# [LCR 102. 目标和](https://leetcode.cn/problems/YaVDxD/) + +- 标签:数组、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 102. 目标和 - 力扣](https://leetcode.cn/problems/YaVDxD/) + +## 题目大意 + +给定一个整数数组 `nums` 和一个整数 `target`。数组长度不超过 `20`。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 + +要求:返回通过上述方法构造的、运算结果等于 `target` 的不同表达式数目。 + +## 解题思路 + +暴力方法就是使用深度优先搜索对每位数字遍历 `+`、`-`,并统计符合要求的表达式数目。但是实际发现超时了。所以采用动态规划的方法来做。 + +假设数组中所有元素和为 `sum`,数组中所有符号为 `+` 的元素为 `sum_x`,符号为 `-` 的元素和为 `sum_y`。则 `target = sum_x - sum_y`。 + +而 `sum_x + sum_y = sum`。根据两个式子可以求出 `2 * sum_x = target + sum `,即 `sum_x = (target + sum) / 2`。 + +那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 `(target + sum) / 2`。这就变为了求容量为 `(target + sum) / 2` 的 `01` 背包问题。 + +动态规划的状态 `dp[i]` 表示为:填满容量为 `i` 的背包,有 `dp[i]` 种方法。 + +动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i-num]`,意思为填满容量为 `i` 的背包的方法数 = 不使用当前 `num`,只使用之前元素填满容量为 `i` 的背包的方法数 + 填满容量 `i - num` 的包的方法数,再填入 `num` 的方法数。 + +## 代码 + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + sum_nums = sum(nums) + if target > sum_nums or (target + sum_nums) % 2 == 1: + return 0 + size = (target + sum_nums) // 2 + dp = [0 for _ in range(size + 1)] + dp[0] = 1 + for num in nums: + for i in range(size, num - 1, -1): + dp[i] = dp[i] + dp[i - num] + return dp[size] +``` + diff --git a/docs/solutions/LCR/Ygoe9J.md b/docs/solutions/LCR/Ygoe9J.md new file mode 100644 index 00000000..b28319a8 --- /dev/null +++ b/docs/solutions/LCR/Ygoe9J.md @@ -0,0 +1,66 @@ +# [LCR 081. 组合总和](https://leetcode.cn/problems/Ygoe9J/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 081. 组合总和 - 力扣](https://leetcode.cn/problems/Ygoe9J/) + +## 题目大意 + +给定一个无重复元素的正整数数组 `candidates` 和一个正整数 `target`。 + +要求:找出 `candidates` 中所有可以使数字和为目标数 `target` 的唯一组合。 + +注意:数组 `candidates` 中的数字可以无限重复选取,且 `1 ≤ candidates[i] ≤ 200`。 + +## 解题思路 + +回溯算法,因为 `1 ≤ candidates[i] ≤ 200`,所以即便是 `candidates[i]` 值为 `1`,重复选取也会等于或大于 target,从而终止回溯。 + +建立两个数组 `res`、`path`。`res` 用于存放所有满足题意的组合,`path` 用于存放当前满足题意的一个组合。 + +定义回溯方法,`start_index = 1` 开始进行回溯。 + +- 如果 `sum > target`,则直接返回。 +- 如果 `sum == target`,则将 `path` 中的元素加入到 `res` 数组中。 +- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 + - 如果 `sum + candidates[i] > target`,可以直接跳出循环。 + - 将和累积,即 `sum += candidates[i]`,然后将当前元素 `i` 加入 `path` 数组。 + - 递归遍历 `[start_index, n]` 上的数。 + - 加之前的和回退,即 `sum -= candidates[i]`,然后将遍历的 `i` 元素进行回退。 +- 最终返回 `res` 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, candidates: List[int], target: int, sum: int, start_index: int): + if sum > target: + return + + if sum == target: + self.res.append(self.path[:]) + return + + for i in range(start_index, len(candidates)): + if sum + candidates[i] > target: + break + sum += candidates[i] + self.path.append(candidates[i]) + self.backtrack(candidates, target, sum, i) + sum -= candidates[i] + self.path.pop() + + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + self.res.clear() + self.path.clear() + candidates.sort() + self.backtrack(candidates, target, 0, 0) + return self.res +``` + diff --git a/docs/solutions/LCR/ZL6zAn.md b/docs/solutions/LCR/ZL6zAn.md new file mode 100644 index 00000000..c5c8ee2e --- /dev/null +++ b/docs/solutions/LCR/ZL6zAn.md @@ -0,0 +1,43 @@ +# [LCR 105. 岛屿的最大面积](https://leetcode.cn/problems/ZL6zAn/) + +- 标签:深度优先搜索、广度优先搜索、并查集、数组、矩阵 +- 难度:中等 + +## 题目链接 + +- [LCR 105. 岛屿的最大面积 - 力扣](https://leetcode.cn/problems/ZL6zAn/) + +## 题目大意 + +给定一个只包含 `0`、`1` 元素的二维数组,`1` 代表岛屿,`0` 代表水。一座岛的面积就是上下左右相邻相邻的 `1` 所组成的连通块的数目。找到最大的岛屿面积。 + +## 解题思路 + +使用深度优先搜索方法。遍历二维数组的每一个元素,对于每个值为 `1` 的元素,记下其面积。然后将该值置为 `0`(防止二次重复计算),再递归其上下左右四个位置,并将深度优先搜索搜到的值为 `1` 的元素个数,进行累积统计。 + +## 代码 + +```python +class Solution: + def dfs(self, grid, i, j): + size_n = len(grid) + size_m = len(grid[0]) + if i < 0 or i >= size_n or j < 0 or j >= size_m or grid[i][j] == 0: + return 0 + ans = 1 + grid[i][j] = 0 + ans += self.dfs(grid, i + 1, j) + ans += self.dfs(grid, i, j + 1) + ans += self.dfs(grid, i - 1, j) + ans += self.dfs(grid, i, j - 1) + return ans + + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + ans = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == 1: + ans = max(ans, self.dfs(grid, i, j)) + return ans +``` + diff --git a/docs/solutions/LCR/ZVAVXX.md b/docs/solutions/LCR/ZVAVXX.md new file mode 100644 index 00000000..b6223caa --- /dev/null +++ b/docs/solutions/LCR/ZVAVXX.md @@ -0,0 +1,51 @@ +# [LCR 009. 乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/) + +- 标签:数组、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [LCR 009. 乘积小于 K 的子数组 - 力扣](https://leetcode.cn/problems/ZVAVXX/) + +## 题目大意 + +给定一个正整数数组 `nums` 和一个整数 `k`。 + +要求:找出该数组内乘积小于 `k` 的连续子数组的个数。 + +## 解题思路 + +滑动窗口求解。 + +设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 `product` 都小于 `k`。 + +- 一开始,`left`、`right` 都指向 `0`。 + +- 向右移动 `right`,将最右侧元素加入当前子数组乘积 `product` 中。 + +- 如果 `product >= k` ,则不断右移 `left`,缩小滑动窗口,并更新当前乘积值 `product` 直到 `product < k`。 +- 累积答案个数 += 1,继续右移 `right`,直到 `right >= len(nums)` 结束。 +- 输出累积答案个数。 + +## 代码 + +```python +class Solution: + def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int: + if k <= 1: + return 0 + + size = len(nums) + left, right = 0, 0 + count = 0 + product = 1 + while right < size: + product *= nums[right] + right += 1 + while product >= k: + product /= nums[left] + left += 1 + count += (right - left) + return count +``` + diff --git a/docs/solutions/LCR/a7VOhD.md b/docs/solutions/LCR/a7VOhD.md new file mode 100644 index 00000000..2f1090ae --- /dev/null +++ b/docs/solutions/LCR/a7VOhD.md @@ -0,0 +1,59 @@ +# [LCR 020. 回文子串](https://leetcode.cn/problems/a7VOhD/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 020. 回文子串 - 力扣](https://leetcode.cn/problems/a7VOhD/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:计算 `s` 中有多少个回文子串。 + +## 解题思路 + +动态规划求解。 + +先定义状态 `dp[i][j]` 表示为区间 `[i, j]` 的子串是否为回文子串,如果是,则 `dp[i][j] = True`,如果不是,则 `dp[i][j] = False`。 + +接下来确定状态转移共识: + +如果 `s[i] == s[j]`,分为以下几种情况: + +- `i == j`,单字符肯定是回文子串,`dp[i][j] == True`。 +- `j - i == 1`,比如 `aa` 肯定也是回文子串,`dp[i][j] = True`。 +- 如果 `j - i > 1`,则需要看 `[i + 1, j - 1]` 区间是不是回文子串,`dp[i][j] = dp[i + 1][j - 1]`。 + +如果 `s[i] != s[j]`,那肯定不是回文子串,`dp[i][j] = False`。 + +下一步确定遍历方向。 + +由于 `dp[i][j]` 依赖于 `dp[i + 1][j - 1]`,所以我们可以从左下角向右上角遍历。 + +同时,在递推过程中记录下 `dp[i][j] == True` 的个数,即为最后结果。 + +## 代码 + +```python +class Solution: + def countSubstrings(self, s: str) -> int: + size = len(s) + dp = [[False for _ in range(size)] for _ in range(size)] + res = 0 + for i in range(size - 1, -1, -1): + for j in range(i, size): + if s[i] == s[j]: + if j - i <= 1: + dp[i][j] = True + else: + dp[i][j] = dp[i + 1][j - 1] + else: + dp[i][j] = False + if dp[i][j]: + res += 1 + return res +``` + diff --git a/docs/solutions/LCR/aMhZSa.md b/docs/solutions/LCR/aMhZSa.md new file mode 100644 index 00000000..2d9cc3e4 --- /dev/null +++ b/docs/solutions/LCR/aMhZSa.md @@ -0,0 +1,32 @@ +# [LCR 027. 回文链表](https://leetcode.cn/problems/aMhZSa/) + +- 标签:栈、递归、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [LCR 027. 回文链表 - 力扣](https://leetcode.cn/problems/aMhZSa/) + +## 题目大意 + +给定一个链表的头节点 `head`。 + +要求:判断该链表是否为回文链表。 + +## 解题思路 + +利用数组,将链表元素依次存入。然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置,依次判断首尾对应元素是否相等,若都相等,则为回文链表。若不相等,则不是回文链表。 + +## 代码 + +```python +class Solution: + def isPalindrome(self, head: ListNode) -> bool: + nodes = [] + p1 = head + while p1 != None: + nodes.append(p1.val) + p1 = p1.next + return nodes == nodes[::-1] +``` + diff --git a/docs/solutions/LCR/aseY1I.md b/docs/solutions/LCR/aseY1I.md new file mode 100644 index 00000000..dabd22b7 --- /dev/null +++ b/docs/solutions/LCR/aseY1I.md @@ -0,0 +1,44 @@ +# [LCR 005. 最大单词长度乘积](https://leetcode.cn/problems/aseY1I/) + +- 标签:位运算、数组、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 005. 最大单词长度乘积 - 力扣](https://leetcode.cn/problems/aseY1I/) + +## 题目大意 + +给定一个字符串数组 `words`。字符串中只包含英语的小写字母。 + +要求:计算当两个字符串 `words[i]` 和 `words[j]` 不包含相同字符时,它们长度的乘积的最大值。如果没有不包含相同字符的一对字符串,返回 0。 + +## 解题思路 + +这道题的核心难点是判断任意两个字符串之间是否包含相同字符。最直接的做法是先遍历第一个字符串的每个字符,再遍历第二个字符串查看是否有相同字符。但是这样做的话,时间复杂度过高。考虑怎么样可以优化一下。 + +题目中说字符串中只包含英语的小写字母,也就是 `26` 种字符。一个 `32` 位的 `int` 整数每一个二进制位都可以表示一种字符的有无,那么我们就可以通过一个整数来表示一个字符串中所拥有的字符种类。延伸一下,我们可以用一个整数数组来表示一个字符串数组中,每个字符串所拥有的字符种类。 + +接下来事情就简单了,两重循环遍历整数数组,遇到两个字符串不包含相同字符的情况,就计算一下他们长度的乘积,并维护一个乘积最大值。最后输出最大值即可。 + +## 代码 + +```python +class Solution: + def maxProduct(self, words: List[str]) -> int: + size = len(words) + arr = [0 for _ in range(size)] + for i in range(size): + word = words[i] + len_word = len(word) + for j in range(len_word): + arr[i] |= 1 << (ord(word[j]) - ord('a')) + ans = 0 + for i in range(size): + for j in range(i + 1, size): + if arr[i] & arr[j] == 0: + k = len(words[i]) * len(words[j]) + ans = k if ans < k else ans + return ans +``` + diff --git a/docs/solutions/LCR/bLyHh0.md b/docs/solutions/LCR/bLyHh0.md new file mode 100644 index 00000000..733b3734 --- /dev/null +++ b/docs/solutions/LCR/bLyHh0.md @@ -0,0 +1,64 @@ +# [LCR 116. 省份数量](https://leetcode.cn/problems/bLyHh0/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [LCR 116. 省份数量 - 力扣](https://leetcode.cn/problems/bLyHh0/) + +## 题目大意 + +一个班上有 `n` 个同学,其中一些彼此是朋友,另一些不是。如果 `a` 与 `b` 是直接朋友,且 `b` 与 `c` 也是直接朋友,那么 `a` 与 `c` 是间接朋友。 + +现在定义「朋友圈」是由一组直接或间接朋友组成的集合。 + +现在给定一个 `n * n` 的矩阵 `isConnected` 表示班上的朋友关系。其中 `isConnected[i][j] = 1` 表示第 `i` 个同学和第 `j` 个同学是直接朋友,`isConnected[i][j] = 0` 表示第 `i` 个同学和第 `j` 个同学不是直接朋友。 + +要求:根据给定的同学关系,返回「朋友圈」的数量。 + +## 解题思路 + +可以利用并查集来做。具体做法如下: + +遍历矩阵 `isConnected`。如果 `isConnected[i][j] = 1`,将 `i` 节点和 `j` 节点相连。然后判断每个同学节点的根节点,然后统计不重复的根节点有多少个,即为「朋友圈」的数量。 + +## 代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.count = n + + def find(self, x): + while x != self.parent[x]: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x + + def union(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + + self.parent[root_x] = root_y + self.count -= 1 + + def is_connected(self, x, y): + return self.find(x) == self.find(y) + +class Solution: + def findCircleNum(self, isConnected: List[List[int]]) -> int: + size = len(isConnected) + union_find = UnionFind(size) + for i in range(size): + for j in range(i + 1, size): + if isConnected[i][j] == 1: + union_find.union(i, j) + + return union_find.count +``` + diff --git a/docs/solutions/LCR/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof.md b/docs/solutions/LCR/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof.md new file mode 100644 index 00000000..677e007d --- /dev/null +++ b/docs/solutions/LCR/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof.md @@ -0,0 +1,43 @@ +# [LCR 165. 解密数字](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 165. 解密数字 - 力扣](https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) + +## 题目大意 + +给定一个数字 `num`,按照如下规则将其翻译为字符串:`0` 翻译为 `a`,`1` 翻译为 `b`,…,`11` 翻译为 `l`,…,`25` 翻译为 `z`。 + +要求:计算出共有多少种可能的翻译方案。 + +## 解题思路 + +可用动态规划来做。 + +将数字 `nums` 转为字符串 `s`。设 `dp[i]` 表示字符串 `s` 前 `i` 个数字 `s[0: i]` 的翻译方案数。`dp[i]` 的来源有两种情况: + +1. 第 `i - 1`、`i - 2` 构成的数字在 `[10, 25]`之间,则 `dp[i]` 来源于: `s[i - 1]` 单独翻译的方案数(即 `dp[i - 1]`) + `s[i - 2]` 和 `s[i - 1]` 连起来进行翻译的方案数(即 `dp[i - 2]`)。 +2. 第 `i - 1`、`i - 2` 构成的数字在 `[10, 25]`之外,则 `dp[i]` 来源于:`s[i]` 单独翻译的方案数。 + +## 代码 + +```python +class Solution: + def translateNum(self, num: int) -> int: + s = str(num) + size = len(s) + dp = [0 for _ in range(size + 1)] + dp[0] = 1 + dp[1] = 1 + for i in range(2, size + 1): + temp = int(s[i-2:i]) + if temp >= 10 and temp <= 25: + dp[i] = dp[i - 1] + dp[i - 2] + else: + dp[i] = dp[i - 1] + return dp[size] +``` + diff --git a/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md b/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md new file mode 100644 index 00000000..f803ce7e --- /dev/null +++ b/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md @@ -0,0 +1,76 @@ +# [LCR 164. 破解闯关密码](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) + +- 标签:贪心、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [LCR 164. 破解闯关密码 - 力扣](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/) + +## 题目大意 + +**描述**:给定一个非负整数数组 $nums$。 + +**要求**:将数组中的数字拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个。 + +**说明**: + +- $0 < nums.length \le 100$。 +- 输出结果可能非常大,所以你需要返回一个字符串而不是整数。 +- 拼接起来的数字可能会有前导 $0$,最后结果不需要去掉前导 $0$。 + +**示例**: + +- 示例 1: + +```python +输入: [10,2] +输出: "102" +``` + +- 示例 2: + +```python +输入:[3,30,34,5,9] +输出:"3033459" +``` + +## 解题思路 + +### 思路 1:自定义排序 + +本质上是给数组进行排序。假设 $x$、$y$ 是数组 $nums$ 中的两个元素。则排序的判断规则如下所示: + +- 如果拼接字符串 $x + y > y + x$,则 $x$ 大于 $y$,$y$ 应该排在 $x$ 前面,从而使拼接起来的数字尽可能的小。 +- 反之,如果拼接字符串 $x + y < y + x$,则 $x$ 小于 $y$,$x$ 应该排在 $y$ 前面,从而使拼接起来的数字尽可能的小。 + +按照上述规则,对原数组进行排序。这里使用了 `functools.cmp_to_key` 自定义排序函数。 + +### 思路 1:自定义排序代码 + +```python +import functools + +class Solution: + def minNumber(self, nums: List[int]) -> str: + def cmp(a, b): + if a + b == b + a: + return 0 + elif a + b > b + a: + return 1 + else: + return -1 + + nums_s = list(map(str, nums)) + nums_s.sort(key=functools.cmp_to_key(cmp)) + return ''.join(nums_s) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。排序算法的时间复杂度为 $O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料 + +- 【题解】[LCR 164. 破解闯关密码(自定义排序,清晰图解) - 把数组排成最小的数 - 力扣](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45-ba-shu-zu-pai-cheng-zui-xiao-de-s-4/) diff --git a/docs/solutions/LCR/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof.md b/docs/solutions/LCR/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof.md new file mode 100644 index 00000000..438f4929 --- /dev/null +++ b/docs/solutions/LCR/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof.md @@ -0,0 +1,69 @@ +# [LCR 192. 把字符串转换成整数 (atoi)](https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) + +- 标签:字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 192. 把字符串转换成整数 (atoi) - 力扣](https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) + +## 题目大意 + +给定一个字符串 `str`。 + +要求:使其能换成一个 32 位有符号整数。并且该方法满足以下要求: + +- 丢弃开头无用的空格字符,直到找到第一个非空格字符为止。 +- 当找到的第一个非空字符为正负号时,将该符号与后面尽可能多的连续数组组合起来,作为该整数的正负号。如果第一个非空字符为数字,则直接将其与之后连续的数字字符组合起来,形成整数。 +- 该字符串中除了有效的整数部分之后也可能会存在多余字符,可直接将这些字符忽略,不会对函数造成影响。 +- 如果第一个非空格字符不是一个有效整数字符、或者字符串为空、字符串仅包含空白字符时,函数不需要进行转换。 +- 需要检测有效性,无法读取返回 0。 +- 所有整数范围为 $[-2^{31}, 2^{31} - 1]$,超过这个范围,则返回 $2^{31} - 1$ 或者 $-2^{31}$。 + +## 解题思路 + +根据题意直接模拟即可。 + +1. 先去除前后空格。 +2. 检测正负号。 +3. 读入数字,并用字符串存储数字结果 +4. 将数字字符串转为整数,并根据正负号转换整数结果。 +5. 判断整数范围,并返回最终结果。 + +## 代码 + +```python +class Solution: + def strToInt(self, str: str) -> int: + num_str = "" + positive = True + start = 0 + + s = str.lstrip() + if not s: + return 0 + + if s[0] == '-': + positive = False + start = 1 + elif s[0] == '+': + positive = True + start = 1 + elif not s[0].isdigit(): + return 0 + + for i in range(start, len(s)): + if s[i].isdigit(): + num_str += s[i] + else: + break + if not num_str: + return 0 + num = int(num_str) + if not positive: + num = -num + return max(num, -2 ** 31) + else: + return min(num, 2 ** 31 - 1) +``` + diff --git a/docs/solutions/LCR/bao-han-minhan-shu-de-zhan-lcof.md b/docs/solutions/LCR/bao-han-minhan-shu-de-zhan-lcof.md new file mode 100644 index 00000000..b7d49f1d --- /dev/null +++ b/docs/solutions/LCR/bao-han-minhan-shu-de-zhan-lcof.md @@ -0,0 +1,62 @@ +# [LCR 147. 最小栈](https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/) + +- 标签:栈、设计 +- 难度:简单 + +## 题目链接 + +- [LCR 147. 最小栈 - 力扣](https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/) + +## 题目大意 + +要求:设计一个「栈」,实现 `push` ,`pop` ,`top` ,`min` 操作,并且操作时间复杂度都是 `O(1)`。 + +## 解题思路 + +使用一个栈,栈元素中除了保存当前值之外,再保存一个当前最小值。 + +- `push` 操作:如果栈不为空,则判断当前值与栈顶元素所保存的最小值,并更新当前最小值,将新元素保存到栈中。 +- `pop`操作:正常出栈 +- `top` 操作:返回栈顶元素保存的值。 +- `min` 操作:返回栈顶元素保存的最小值。 + +## 代码 + +```python +class MinStack: + + def __init__(self): + """ + initialize your data structure here. + """ + self.stack = [] + + class Node: + def __init__(self, x): + self.val = x + self.min = x + + def push(self, x: int) -> None: + node = self.Node(x) + if len(self.stack) == 0: + self.stack.append(node) + else: + topNode = self.stack[-1] + if node.min > topNode.min: + node.min = topNode.min + + self.stack.append(node) + + + def pop(self) -> None: + self.stack.pop() + + + def top(self) -> int: + return self.stack[-1].val + + + def min(self) -> int: + return self.stack[-1].min +``` + diff --git a/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md b/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md new file mode 100644 index 00000000..073fbc1b --- /dev/null +++ b/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md @@ -0,0 +1,45 @@ +# [LCR 186. 文物朝代判断](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) + +- 标签:数组、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 186. 文物朝代判断 - 力扣](https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) + +## 题目大意 + +给定一个 `5` 位数的数组 `nums` 代表扑克牌中的 `5` 张牌。其中 `2~10` 为数字本身,`A` 用 `1` 表示,`J` 用 `11` 表示,`Q` 用 `12` 表示,`K` 用 `13` 表示,大小王用 `0` 表示,且大小王可以替换任意数字。 + +要求:判断给定的 `5` 张牌是否是一个顺子,即是否为连续的`5` 个数。 + +## 解题思路 + +先不考虑牌中有大小王,如果 `5` 个数是连续的,则这 `5` 个数中最大值最小值的关系为:`最大值 - 最小值 = 4`。如果牌中有大小王可以替换这 `5` 个数中的任意数字,则除大小王之外剩下数的最大值最小值关系为 `最大值 - 最小值 <= 4`。而且剩余数不能有重复数字。于是可以这样进行判断。 + +遍历 `5` 张牌: + +- 如果出现大小王,则跳过。 +- 判断 `5` 张牌中是否有重复数,如果有则直接返回 `False`,如果没有则将其加入集合。 +- 计算 `5` 张牌的最大值,最小值。 + +最后判断 `最大值 - 最小值 <= 4` 是否成立。如果成立,返回 `True`,否则返回 `False`。 + +## 代码 + +```python +class Solution: + def isStraight(self, nums: List[int]) -> bool: + max_num, min_num = 0, 14 + repeat = set() + for num in nums: + if num == 0: + continue + if num in repeat: + return False + repeat.add(num) + max_num = max(max_num, num) + min_num = min(min_num, num) + return max_num - min_num <= 4 +``` + diff --git a/docs/solutions/LCR/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof.md b/docs/solutions/LCR/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof.md new file mode 100644 index 00000000..e1bdbfcc --- /dev/null +++ b/docs/solutions/LCR/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof.md @@ -0,0 +1,52 @@ +# [LCR 190. 加密运算](https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) + +- 标签:位运算、数学 +- 难度:简单 + +## 题目链接 + +- [LCR 190. 加密运算 - 力扣](https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) + +## 题目大意 + +给定两个整数 `a`、`b`。 + +要求:不能使用运算符 `+`、`-`、`*`、`/`,计算两整数 `a` 、`b` 之和。 + +## 解题思路 + +需要用到位运算的一些知识。 + +- 异或运算 a ^ b :可以获得 a + b 无进位的加法结果。 +- 与运算 a & b:对应位置为 1,说明 a、b 该位置上原来都为 1,则需要进位。 +- 座椅运算 a << 1:将 a 对应二进制数左移 1 位。 + +这样,通过 a^b 运算,我们可以得到相加后无进位结果,再根据 (a&b) << 1,计算进位后结果。 + +进行 a^b 和 (a&b) << 1操作之后判断进位是否为 0,若不为 0,则继续上一步操作,直到进位为 0。 + +> 注意: +> +> Python 的整数类型是无限长整数类型,负数不确定符号位是第几位。所以我们可以将输入的数字手动转为 32 位无符号整数。 +> +> 通过 a &= 0xFFFFFFFF 即可将 a 转为 32 位无符号整数。最后通过对 a 的范围判断,将其结果映射为有符号整数。 + +## 代码 + +```python +class Solution: + def getSum(self, a: int, b: int) -> int: + MAX_INT = 0x7FFFFFFF + MASK = 0xFFFFFFFF + a &= MASK + b &= MASK + while b: + carry = ((a & b) << 1) & MASK + a ^= b + b = carry + if a <= MAX_INT: + return a + else: + return ~(a ^ MASK) +``` + diff --git a/docs/solutions/LCR/c32eOV.md b/docs/solutions/LCR/c32eOV.md new file mode 100644 index 00000000..d82762af --- /dev/null +++ b/docs/solutions/LCR/c32eOV.md @@ -0,0 +1,47 @@ +# [LCR 022. 环形链表 II](https://leetcode.cn/problems/c32eOV/) + +- 标签:哈希表、链表、双指针 +- 难度:中等 + +## 题目链接 + +- [LCR 022. 环形链表 II - 力扣](https://leetcode.cn/problems/c32eOV/) + +## 题目大意 + +给定一个链表的头节点 `head`。 + +要求:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 `None`。 + +## 解题思路 + +利用两个指针,一个慢指针每次前进一步,快指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 + +如果有环,则再定义一个指针,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 + +这是因为:假设入环位置为 A,快慢指针在在 B 点相遇,则相遇时慢指针走了 a + b 步,快指针走了 $a + n(b+c) + b$ 步。 + +$2(a + b) = a + n(b + c) + b$。可以推出:$a = c + (n-1)(b + c)$。 + +我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 + +## 代码 + +```python +class Solution: + def detectCycle(self, head: ListNode) -> ListNode: + fast, slow = head, head + while True: + if not fast or not fast.next: + return None + fast = fast.next.next + slow = slow.next + if fast == slow: + break + + ans = head + while ans != slow: + ans, slow = ans.next, slow.next + return ans +``` + diff --git a/docs/solutions/LCR/chou-shu-lcof.md b/docs/solutions/LCR/chou-shu-lcof.md new file mode 100644 index 00000000..388ed365 --- /dev/null +++ b/docs/solutions/LCR/chou-shu-lcof.md @@ -0,0 +1,43 @@ +# [LCR 168. 丑数](https://leetcode.cn/problems/chou-shu-lcof/) + +- 标签:哈希表、数学、动态规划、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [LCR 168. 丑数 - 力扣](https://leetcode.cn/problems/chou-shu-lcof/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:找出并返回第 `n` 个丑数。 + +- 丑数:只包含质因数 `2`、`3`、`5` 的正整数。 + +## 解题思路 + +动态规划求解。 + +定义状态 `dp[i]` 表示第 `i` 个丑数。 + +状态转移方程为:`dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)` ,其中 `p2`、`p3`、`p5` 分别表示当前 `i` 中 `2`、`3`、`5` 的质因子数量。 + +## 代码 + +```python +class Solution: + def nthUglyNumber(self, n: int) -> int: + dp = [1 for _ in range(n)] + p2, p3, p5 = 0, 0, 0 + for i in range(1, n): + dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5) + if dp[i] == dp[p2] * 2: + p2 += 1 + if dp[i] == dp[p3] * 3: + p3 += 1 + if dp[i] == dp[p5] * 5: + p5 += 1 + return dp[n - 1] +``` + diff --git a/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof.md b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof.md new file mode 100644 index 00000000..1c5f9909 --- /dev/null +++ b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof.md @@ -0,0 +1,50 @@ +# [LCR 150. 彩灯装饰记录 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 150. 彩灯装饰记录 II - 力扣](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 + +## 解题思路 + +广度优先搜索,需要增加一些变化。普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 + +具体步骤如下: + +- 根节点入队。 +- 当队列不为空时,求出当前队列长度 $s_i$。 + - 依次从队列中取出这 $s_i$ 个元素,并将其左右子节点入队,遍历完之后将这层节点数组加入答案数组中,然后继续迭代。 +- 当队列为空时,结束。 + +## 代码 + +```python +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + while queue: + level = [] + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + level.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(level) + return order +``` + diff --git a/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md new file mode 100644 index 00000000..3448ae42 --- /dev/null +++ b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md @@ -0,0 +1,68 @@ +# [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 151. 彩灯装饰记录 III - 力扣](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:返回其之字形层序遍历。 + +- 之字形层序遍历:从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行。 + +## 解题思路 + +广度优先搜索,在二叉树的层序遍历的基础上需要增加一些变化。 + +普通广度优先搜索只取一个元素,变化后的广度优先搜索每次取出第 i 层上所有元素。 + +新增一个变量 odd,用于判断当前层数是奇数层,还是偶数层。从而判断元素遍历方向。 + +存储每层元素的 level 列表改用双端队列,如果是奇数层,则从末尾添加元素。如果是偶数层,则从头部添加元素。 + +具体步骤如下: + +- 根节点入队。 +- 当队列不为空时,求出当前队列长度 $s_i$,并判断当前层数的奇偶性。 +- 依次从队列中取出这 $s_i$ 个元素。 + - 如果为奇数层,如果是奇数层,则从 level 末尾添加元素。 + - 如果是偶数层,则从 level头部添加元素。 +- 然后保存将其左右子节点入队,然后继续迭代。 +- 当队列为空时,结束。 + +## 代码 + +```python +import collections + +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + if not root: + return [] + queue = [root] + order = [] + odd = True + while queue: + level = collections.deque() + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + if odd: + level.append(curr.val) + else: + level.appendleft(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + if level: + order.append(list(level)) + odd = not odd + return order +``` + diff --git a/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-lcof.md b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-lcof.md new file mode 100644 index 00000000..87cb98b5 --- /dev/null +++ b/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-lcof.md @@ -0,0 +1,47 @@ +# [LCR 149. 彩灯装饰记录 I](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) + +- 标签:树、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 149. 彩灯装饰记录 I - 力扣](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 + +## 解题思路 + +广度优先搜索。 + +具体步骤如下: + +- 根节点入队。 +- 当队列不为空时,求出当前队列长度 $s_i$。 + - 依次从队列中取出这 $s_i$ 个元素,将其加入答案数组,并将其左右子节点入队,然后继续迭代。 +- 当队列为空时,结束。 + +## 代码 + +```python +class Solution: + def levelOrder(self, root: TreeNode) -> List[int]: + if not root: + return [] + queue = [root] + order = [] + while queue: + size = len(queue) + for _ in range(size): + curr = queue.pop(0) + order.append(curr.val) + if curr.left: + queue.append(curr.left) + if curr.right: + queue.append(curr.right) + return order +``` + diff --git a/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md b/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md new file mode 100644 index 00000000..134bb705 --- /dev/null +++ b/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md @@ -0,0 +1,33 @@ +# [LCR 123. 图书整理 I](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) + +- 标签:栈、递归、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [LCR 123. 图书整理 I - 力扣](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) + +## 题目大意 + +给定一个链表的头节点 `head`。 + +要求:从尾到头反过来返回每个节点的值(用数组返回)。 + +## 解题思路 + +- 定义数组 `res`,从头到尾遍历链表。 +- 将每个节点值存入数组中。 +- 直接返回倒序数组。 + +## 代码 + +```python +class Solution: + def reversePrint(self, head: ListNode) -> List[int]: + res = [] + while head: + res.append(head.val) + head = head.next + return res[::-1] +``` + diff --git a/docs/solutions/LCR/dKk3P7.md b/docs/solutions/LCR/dKk3P7.md new file mode 100644 index 00000000..841495e7 --- /dev/null +++ b/docs/solutions/LCR/dKk3P7.md @@ -0,0 +1,46 @@ +# [LCR 032. 有效的字母异位词](https://leetcode.cn/problems/dKk3P7/) + +- 标签:哈希表、字符串、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 032. 有效的字母异位词 - 力扣](https://leetcode.cn/problems/dKk3P7/) + +## 题目大意 + +给定两个字符串 `s` 和 `t`。 + +要求:判断 `t` 和 `s` 是否使用了相同的字符构成(字符出现的种类和数目都相同,字符顺序不完全相同)。 + +## 解题思路 + +1. 先判断字符串 `s` 和 `t` 的长度,不一样直接返回 `False`; +2. 如果 `s` 和 `t` 相等,则直接返回 `False`,因为变位词的字符顺序不完全相同; +3. 分别遍历字符串 `s` 和 `t`。先遍历字符串 `s`,用哈希表存储字符串 `s` 中字符出现的频次; +4. 再遍历字符串 `t`,哈希表中减去对应字符的频次,出现频次小于 `0` 则输出 `False`; +5. 如果没出现频次小于 `0`,则输出 `True`。 + +## 代码 + +```python +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + if len(s) != len(t) or s == t: + return False + strDict = dict() + for ch in s: + if ch in strDict: + strDict[ch] += 1 + else: + strDict[ch] = 1 + for ch in t: + if ch in strDict: + strDict[ch] -= 1 + if strDict[ch] < 0: + return False + else: + return False + return True +``` + diff --git a/docs/solutions/LCR/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof.md b/docs/solutions/LCR/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof.md new file mode 100644 index 00000000..5f8a4dd7 --- /dev/null +++ b/docs/solutions/LCR/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof.md @@ -0,0 +1,27 @@ +# [LCR 135. 报数](https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) + +- 标签:数组、数学 +- 难度:简单 + +## 题目链接 + +- [LCR 135. 报数 - 力扣](https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) + +## 题目大意 + +给定一个数字 `n`。 + +要求:按顺序打印从 `1` 到最大 `n` 位的十进制数。 + +## 解题思路 + +直接枚举 $1 \sim 10^{n} - 1$,生成列表并返回。 + +## 代码 + +```python +class Solution: + def printNumbers(self, n: int) -> List[int]: + return [i for i in range(1, 10 ** n)] +``` + diff --git a/docs/solutions/LCR/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof.md b/docs/solutions/LCR/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof.md new file mode 100644 index 00000000..8fb09e82 --- /dev/null +++ b/docs/solutions/LCR/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof.md @@ -0,0 +1,39 @@ +# [LCR 169. 招式拆解 II](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) + +- 标签:队列、哈希表、字符串、计数 +- 难度:简单 + +## 题目链接 + +- [LCR 169. 招式拆解 II - 力扣](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:从字符串 `s` 中找到第一个只出现一次的字符。如果没有,则返回空格 ` `。 + +## 解题思路 + +遍历字符串 `s`,使用哈希表存储每个字符频数。 + +再次遍历字符串 `s`,返回第一个频数为 `1` 的字符。 + +## 代码 + +```python +class Solution: + def firstUniqChar(self, s: str) -> str: + dic = dict() + for ch in s: + if ch in dic: + dic[ch] += 1 + else: + dic[ch] = 1 + + for ch in s: + if ch in dic and dic[ch] == 1: + return ch + return ' ' +``` + diff --git a/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md b/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md new file mode 100644 index 00000000..bb452cb1 --- /dev/null +++ b/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md @@ -0,0 +1,60 @@ +# [LCR 139. 训练计划 I](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) + +- 标签:数组、双指针、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 139. 训练计划 I - 力扣](https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) + +## 题目大意 + +**描述**:给定一个整数数组 $nums$。 + +**要求**:将奇数元素位于数组的前半部分,偶数元素位于数组的后半部分。 + +**说明**: + +- $0 \le nums.length \le 50000$。 +- $0 \le nums[i] \le 10000$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,2,3,4,5] +输出:[1,3,5,2,4] +解释:为正确答案之一 +``` + +## 解题思路 + +### 思路 1:快慢指针 + +定义快慢指针 $slow$、$fast$,开始时都指向 $0$。 + +- $fast$ 向前搜索奇数位置,$slow$ 指向下一个奇数应当存放的位置。 +- $fast$ 不断进行右移,当遇到奇数时,将该奇数与 $slow$ 指向的元素进行交换,并将 $slow$ 进行右移。 +- 重复上面操作,直到 $fast$ 指向数组末尾。 + +### 思路 1:代码 + +```python +class Solution: + def exchange(self, nums: List[int]) -> List[int]: + slow, fast = 0, 0 + while fast < len(nums): + if nums[fast] % 2 == 1: + nums[slow], nums[fast] = nums[fast], nums[slow] + slow += 1 + fast += 1 + + return nums +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 中的元素个数。 +- **空间复杂度**:$O(1)$。 + diff --git a/docs/solutions/LCR/dui-cheng-de-er-cha-shu-lcof.md b/docs/solutions/LCR/dui-cheng-de-er-cha-shu-lcof.md new file mode 100644 index 00000000..63280f93 --- /dev/null +++ b/docs/solutions/LCR/dui-cheng-de-er-cha-shu-lcof.md @@ -0,0 +1,41 @@ +# [LCR 145. 判断对称二叉树](https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 145. 判断对称二叉树 - 力扣](https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:检查这课二叉树是否是左右对称的。 + +## 解题思路 + +递归遍历左右子树, 然后判断当前节点的左右子节点。如果可以直接判断的情况,则跳出递归,直接返回结果。如果无法直接判断结果,则递归检测左右子树的外侧节点是否相等,同理再递归检测左右子树的内侧节点是否相等。 + +## 代码 + +```python +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: + if not root: + return True + return self.check(root.left, root.right) + + def check(self, left: TreeNode, right: TreeNode): + if not left and not right: + return True + elif not left and right: + return False + elif left and not right: + return False + elif left.val != right.val: + return False + + return self.check(left.left, right.right) and self.check(left.right, right.left) +``` + diff --git a/docs/solutions/LCR/dui-lie-de-zui-da-zhi-lcof.md b/docs/solutions/LCR/dui-lie-de-zui-da-zhi-lcof.md new file mode 100644 index 00000000..a032fa76 --- /dev/null +++ b/docs/solutions/LCR/dui-lie-de-zui-da-zhi-lcof.md @@ -0,0 +1,58 @@ +# [LCR 184. 设计自助结算系统](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) + +- 标签:设计、队列、单调队列 +- 难度:中等 + +## 题目链接 + +- [LCR 184. 设计自助结算系统 - 力扣](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) + +## 题目大意 + +要求:设计一个「队列」,实现 `max_value` 函数,可通过 `max_value` 得到大年队列的最大值。并且要求 `max_value`、`push_back`、`pop_front` 的均摊时间复杂度都是 `O(1)`。 + +## 解题思路 + +利用空间换时间,使用两个队列。其中一个为原始队列 `queue`,另一个为递减队列 `deque`,`deque` 用来保存队列的最大值,具体做法如下: + +- `push_back` 操作:如果 `deque` 队尾元素小于即将入队的元素 `value`,则将小于 `value` 的元素全部出队,再将 `valuew` 入队。否则直接将 `value` 直接入队,这样 `deque` 队首元素保存的就是队列的最大值。 +- `pop_front` 操作:先判断 `deque`、`queue` 是否为空,如果 `deque` 或者 `queue` 为空,则说明队列为空,直接返回 `-1`。如果都不为空,从 `queue` 中取出一个元素,并跟 `deque` 队首元素进行比较,如果两者相等则需要将 `deque` 队首元素弹出。 +- `max_value` 操作:如果 `deque` 不为空,则返回 `deque` 队首元素。否则返回 `-1`。 + +## 代码 + +```python +import collections +import queue + + +class MaxQueue: + + def __init__(self): + self.queue = queue.Queue() + self.deque = collections.deque() + + + def max_value(self) -> int: + if self.deque: + return self.deque[0] + else: + return -1 + + + def push_back(self, value: int) -> None: + while self.deque and self.deque[-1] < value: + self.deque.pop() + self.deque.append(value) + self.queue.put(value) + + + def pop_front(self) -> int: + if not self.deque or not self.queue: + return -1 + ans = self.queue.get() + if ans == self.deque[0]: + self.deque.popleft() + return ans +``` + diff --git a/docs/solutions/LCR/er-cha-shu-de-jing-xiang-lcof.md b/docs/solutions/LCR/er-cha-shu-de-jing-xiang-lcof.md new file mode 100644 index 00000000..3e3de15c --- /dev/null +++ b/docs/solutions/LCR/er-cha-shu-de-jing-xiang-lcof.md @@ -0,0 +1,33 @@ +# [LCR 144. 翻转二叉树](https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 144. 翻转二叉树 - 力扣](https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:将其进行左右翻转。 + +## 解题思路 + +从根节点开始遍历,然后从叶子节点向上递归交换左右子树位置。 + +## 代码 + +```python +class Solution: + def mirrorTree(self, root: TreeNode) -> TreeNode: + if not root: + return root + left = self.mirrorTree(root.left) + right = self.mirrorTree(root.right) + root.left = right + root.right = left + return root +``` + diff --git a/docs/solutions/LCR/er-cha-shu-de-shen-du-lcof.md b/docs/solutions/LCR/er-cha-shu-de-shen-du-lcof.md new file mode 100644 index 00000000..ac21d2df --- /dev/null +++ b/docs/solutions/LCR/er-cha-shu-de-shen-du-lcof.md @@ -0,0 +1,34 @@ +# [LCR 175. 计算二叉树的深度](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 175. 计算二叉树的深度 - 力扣](https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:找出树的深度。 + +- 深度:从根节点到叶节点一次经过的节点形成一条路径,最长路径的长度为树的深度。 + +## 解题思路 + +递归遍历,先递归遍历左右子树,返回左右子树的高度,则当前节点的高度为左右子树最大深度 + 1。即 `max(left_height, right_height) + 1`。 + +## 代码 + +```python +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if root == None: + return 0 + + left_height = self.maxDepth(root.left) + right_height = self.maxDepth(root.right) + return max(left_height, right_height) + 1 +``` + diff --git a/docs/solutions/LCR/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof.md b/docs/solutions/LCR/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof.md new file mode 100644 index 00000000..e2dcac43 --- /dev/null +++ b/docs/solutions/LCR/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof.md @@ -0,0 +1,62 @@ +# [LCR 194. 二叉树的最近公共祖先](https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 194. 二叉树的最近公共祖先 - 力扣](https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) + +## 题目大意 + +给定一个二叉树的根节点 `root`,再给定两个指定节点 `p`、`q`。 + +要求:找到两个指定节点 `p`、`q` 的最近公共祖先。 + +- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 +- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先) + +## 解题思路 + +设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: + +- `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 +- `p = lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 +- `q = lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 + +下面递归求解 `lca_node`。递归需要满足以下条件: + +- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 +- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 +- 如果 `p`、`q` 都不存在,则返回存在的一个。 + +具体思路为: + +- 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 +- 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node` +- 递归遍历左子树、右子树,并判断左右子树结果。 + - 如果左子树为空,则返回右子树。 + - 如果右子树为空,则返回左子树。 + - 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 + - 如果左右子树都为空,则返回空。 + +## 代码 + +```python +class Solution: + def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: + if root == p or root == q: + return root + + if root: + node_left = self.lowestCommonAncestor(root.left, p, q) + node_right = self.lowestCommonAncestor(root.right, p, q) + if node_left and node_right: + return root + elif not node_left: + return node_right + else: + return node_left + return None +``` + diff --git a/docs/solutions/LCR/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof.md b/docs/solutions/LCR/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof.md new file mode 100644 index 00000000..72fb70ee --- /dev/null +++ b/docs/solutions/LCR/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof.md @@ -0,0 +1,41 @@ +# [LCR 153. 二叉树中和为目标值的路径](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) + +- 标签:树、深度优先搜索、回溯、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 153. 二叉树中和为目标值的路径 - 力扣](https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root` 和一个整数 `target`。 + +要求:打印出二叉树中各节点的值的和为 `target` 的所有路径。从根节点开始往下一直到叶节点所经过的节点形成一条路径。 + +## 解题思路 + +回溯求解。在回溯的同时,记录下当前路径。同时维护 `target`,每遍历到一个节点,就减去该节点值。如果遇到叶子节点,并且 `target == 0` 时,将当前路径加入答案数组中。然后递归遍历左右子树,并回退当前节点,继续遍历。 + +## 代码 + +```python +class Solution: + def pathSum(self, root: TreeNode, target: int) -> List[List[int]]: + res = [] + path = [] + def dfs(root: TreeNode, target: int): + if not root: + return + path.append(root.val) + target -= root.val + if not root.left and not root.right and target == 0: + res.append(path[:]) + dfs(root.left, target) + dfs(root.right, target) + path.pop() + dfs(root, target) + return res + +``` + diff --git a/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md b/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md new file mode 100644 index 00000000..b649faad --- /dev/null +++ b/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md @@ -0,0 +1,89 @@ +# [LCR 174. 寻找二叉搜索树中的目标节点](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 174. 寻找二叉搜索树中的目标节点 - 力扣](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) + +## 题目大意 + +**描述**:给定一棵二叉搜索树的根节点 $root$,以及一个整数 $k$。 + +**要求**:找出二叉搜索树书第 $k$ 大的节点。 + +**说明**: + +- + +**示例**: + +- 示例 1: + +![](https://pic.leetcode.cn/1695101634-kzHKZW-image.png) + +```python +输入:root = [7, 3, 9, 1, 5], cnt = 2 + 7 + / \ + 3 9 + / \ + 1 5 +输出:7 +``` + +- 示例 2: + +![](https://pic.leetcode.cn/1695101636-ESZtLa-image.png) + +```python +输入: root = [10, 5, 15, 2, 7, null, 20, 1, null, 6, 8], cnt = 4 + 10 + / \ + 5 15 + / \ \ + 2 7 20 + / / \ + 1 6 8 +输出: 8 +``` + +## 解题思路 + +### 思路 1:遍历 + +已知中序遍历「左 -> 根 -> 右」能得到递增序列。逆中序遍历「右 -> 根 -> 左」可以得到递减序列。 + +则根据「右 -> 根 -> 左」递归遍历 k 次,找到第 $k$ 个节点位置,并记录答案。 + +### 思路 1:代码 + +```python +class Solution: + res = 0 + k = 0 + def dfs(self, root): + if not root: + return + self.dfs(root.right) + if self.k == 0: + return + self.k -= 1 + if self.k == 0: + self.res = root.val + return + self.dfs(root.left) + + def kthLargest(self, root: TreeNode, k: int) -> int: + self.res = 0 + self.k = k + self.dfs(root) + return self.res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为树中节点数量。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md b/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md new file mode 100644 index 00000000..47067c60 --- /dev/null +++ b/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md @@ -0,0 +1,82 @@ +# [LCR 152. 验证二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) + +- 标签:栈、树、二叉搜索树、递归、二叉树、单调栈 +- 难度:中等 + +## 题目链接 + +- [LCR 152. 验证二叉搜索树的后序遍历序列 - 力扣](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) + +## 题目大意 + +**描述**:给定一个整数数组 $postorder$。数组的任意两个数字都互不相同。 + +**要求**:判断该数组是不是某二叉搜索树的后序遍历结果。如果是,则返回 `True`,否则返回 `False`。 + +**说明**: + +- 数组长度 <= 1000。 +- $postorder$ 中无重复数字。 + +**示例**: + +- 示例 1: + +![](https://pic.leetcode.cn/1694762751-fwHhWX-%E5%89%91%E6%8C%8733%E7%A4%BA%E4%BE%8B1.png) + +```python +输入: postorder = [4,9,6,9,8] +输出: false +解释:从上图可以看出这不是一颗二叉搜索树 +``` + +- 示例 2: + +![](https://pic.leetcode.cn/1694762510-vVpTic-%E5%89%91%E6%8C%8733.png) + +```python +输入: postorder = [4,6,5,9,8] +输出: true +解释:可构建的二叉搜索树如上图 +``` + +## 解题思路 + +### 思路 1:递归分治 + +后序遍历的顺序为:左 -> 右 -> 根。而二叉搜索树的定义是:左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值。 + +所以,可以把数组最右侧元素作为二叉搜索树的根节点值。然后判断数组的左右两侧是否符合左侧值都小于该节点值,右侧值都大于该节点值。如果不满足,则说明不是某二叉搜索树的后序遍历结果。 + +找到左右分界线位置,然后递归左右数组继续查找。 + +终止条件为数组 开始位置 > 结束位置,此时该树的子节点数目小于等于 $1$,直接返回 `True` 即可。 + +### 思路 1:代码 + +```python +class Solution: + def verifyPostorder(self, postorder: List[int]) -> bool: + def verify(left, right): + if left >= right: + return True + index = left + while postorder[index] < postorder[right]: + index += 1 + mid = index + while postorder[index] > postorder[right]: + index += 1 + + return index == right and verify(left, mid - 1) and verify(mid, right - 1) + if len(postorder) <= 2: + return True + return verify(0, len(postorder) - 1) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + + + diff --git a/docs/solutions/LCR/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof.md b/docs/solutions/LCR/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof.md new file mode 100644 index 00000000..7930c210 --- /dev/null +++ b/docs/solutions/LCR/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof.md @@ -0,0 +1,45 @@ +# [LCR 193. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 193. 二叉搜索树的最近公共祖先 - 力扣](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root` 和两个指定节点 `p`、`q`。 + +要求:找到该树中两个指定节点 `p`、`q` 的最近公共祖先。 + +- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p == node`,则称 `node` 是 `p` 的祖先。 +- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先) + +## 解题思路 + +对于节点 `p`、节点 `q`,最近公共祖先就是从根节点分别到它们路径上的分岔点,也是路径中最后一个相同的节点,现在我们的问题就是求这个分岔点。 + +使用递归遍历查找最近公共祖先。 + +- 从根节点开始遍历; + - 如果当前节点的值大于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的左子树,因此将当前节点移动到它的左子节点,继续遍历; + - 如果当前节点的值小于 `p`、`q` 的值,说明 `p` 和 `q` 应该在当前节点的右子树,因此将当前节点移动到它的右子节点,继续遍历; + - 如果当前节点不满足上面两种情况,则说明 `p` 和 `q` 分别在当前节点的左右子树上,则当前节点就是分岔点,直接返回该节点即可。 + +## 代码 + +```python +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + ancestor = root + while True: + if ancestor.val > p.val and ancestor.val > q.val: + ancestor = ancestor.left + elif ancestor.val < p.val and ancestor.val < q.val: + ancestor = ancestor.right + else: + break + return ancestor +``` + diff --git a/docs/solutions/LCR/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof.md b/docs/solutions/LCR/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof.md new file mode 100644 index 00000000..882820b0 --- /dev/null +++ b/docs/solutions/LCR/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof.md @@ -0,0 +1,58 @@ +# [LCR 155. 将二叉搜索树转化为排序的双向链表](https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/) + +- 标签:栈、树、深度优先搜索、二叉搜索树、链表、二叉树、双向链表 +- 难度:中等 + +## 题目链接 + +- [LCR 155. 将二叉搜索树转化为排序的双向链表 - 力扣](https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:将这棵二叉树转换为一个排序的循环双向链表。要求不能创建新的节点,只能调整树中节点指针的指向。 + +## 解题思路 + +通过中序递归遍历可以将二叉树升序排列输出。这道题需要在中序遍历的同时,将节点的左右指向进行改变。使用 `head`、`tail` 存放双向链表的头尾节点,然后从根节点开始,进行中序递归遍历。 + +具体做法如下: + +- 如果当前节点为空,直接返回。 +- 如果当前节点不为空: + - 递归遍历左子树。 + - 如果尾节点不为空,则将尾节点与当前节点进行连接。 + - 如果尾节点为空,则初始化头节点。 + - 将当前节点标记为尾节点。 + - 递归遍历右子树。 +- 最后将头节点和尾节点进行连接。 + +## 代码 + +```python +class Solution: + def treeToDoublyList(self, root: 'Node') -> 'Node': + def dfs(node: 'Node'): + if not node: + return + + dfs(node.left) + if self.tail: + self.tail.right = node + node.left = self.tail + else: + self.head = node + self.tail = node + dfs(node.right) + + if not root: + return None + + self.head, self.tail = None, None + dfs(root) + self.head.left = self.tail + self.tail.right = self.head + return self.head +``` + diff --git a/docs/solutions/LCR/er-jin-zhi-zhong-1de-ge-shu-lcof.md b/docs/solutions/LCR/er-jin-zhi-zhong-1de-ge-shu-lcof.md new file mode 100644 index 00000000..c9df6b42 --- /dev/null +++ b/docs/solutions/LCR/er-jin-zhi-zhong-1de-ge-shu-lcof.md @@ -0,0 +1,55 @@ +# [LCR 133. 位 1 的个数](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) + +- 标签:位运算 +- 难度:简单 + +## 题目链接 + +- [LCR 133. 位 1 的个数 - 力扣](https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) + +## 题目大意 + +给定一个无符号整数 `n`。 + +要求:统计其对应二进制表达式中 `1` 的个数。 + +## 解题思路 + +### 1. 循环按位计算 + +对整数 n 的每一位进行按位与运算,并统计结果。 + +### 2. 改进位运算 + +利用 $n \text{ \& } (n-1)$ 。这个运算刚好可以将 n 的二进制中最低位的 $1$ 变为 $0$。 比如 $n = 6$ 时,$6 = (110)_2$,$6 - 1 = (101)_2$,$(110)_2 \text{ \& } (101)_2 = (100)_2$ 。 + +利用这个位运算,不断的将 $n$ 中最低位的 $1$ 变为 $0$,直到 $n$ 变为 $0$ 即可,其变换次数就是我们要求的结果。 + +## 代码 + +1. 循环按位计算 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + ans = 0 + while n: + ans += (n & 1) + n = n >> 1 + return ans +``` + +2. 改进位运算 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + ans = 0 + while n: + n &= n-1 + ans += 1 + return ans +``` + + + diff --git a/docs/solutions/LCR/er-wei-shu-zu-zhong-de-cha-zhao-lcof.md b/docs/solutions/LCR/er-wei-shu-zu-zhong-de-cha-zhao-lcof.md new file mode 100644 index 00000000..1a4eb233 --- /dev/null +++ b/docs/solutions/LCR/er-wei-shu-zu-zhong-de-cha-zhao-lcof.md @@ -0,0 +1,90 @@ +# [LCR 121. 寻找目标值 - 二维数组](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) + +- 标签:数组、二分查找、分治、矩阵 +- 难度:中等 + +## 题目链接 + +- [LCR 121. 寻找目标值 - 二维数组 - 力扣](https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) + +## 题目大意 + +给定一个 `m * n` 大小的有序整数矩阵 `matrix`。每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 `target`。 + +要求:判断矩阵中是否可以找到 `target`,若找到 `target`,返回 `True`,否则返回 `False`。 + +## 解题思路 + +矩阵是有序的,可以考虑使用二分搜索来进行查找。 + +迭代对角线元素,假设对角线元素的坐标为 `(row, col)`。把数组元素按对角线分为右上角部分和左下角部分。 + +则对于当前对角线元素右侧第 `row` 行、对角线元素下侧第 `col` 列进行二分查找。 + +- 如果找到目标,直接返回 `True`。 +- 如果找不到目标,则缩小范围,继续查找。 +- 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 + +## 代码 + +```python +class Solution: + def diagonalBinarySearch(self, matrix, diagonal, target): + left = 0 + right = diagonal + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][mid] < target: + left = mid + 1 + else: + right = mid + return left + + def rowBinarySearch(self, matrix, begin, cols, target): + left = begin + right = cols + while left < right: + mid = left + (right - left) // 2 + if matrix[begin][mid] < target: + left = mid + 1 + elif matrix[begin][mid] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= cols and matrix[begin][left] == target + + def colBinarySearch(self, matrix, begin, rows, target): + left = begin + 1 + right = rows + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][begin] < target: + left = mid + 1 + elif matrix[mid][begin] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= rows and matrix[left][begin] == target + + def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool: + rows = len(matrix) + if rows == 0: + return False + cols = len(matrix[0]) + if cols == 0: + return False + + min_val = min(rows, cols) + index = self.diagonalBinarySearch(matrix, min_val - 1, target) + if matrix[index][index] == target: + return True + for i in range(index + 1): + row_search = self.rowBinarySearch(matrix, i, cols - 1, target) + col_search = self.colBinarySearch(matrix, i, rows - 1, target) + if row_search or col_search: + return True + return False +``` + diff --git a/docs/solutions/LCR/fan-zhuan-dan-ci-shun-xu-lcof.md b/docs/solutions/LCR/fan-zhuan-dan-ci-shun-xu-lcof.md new file mode 100644 index 00000000..99faaa61 --- /dev/null +++ b/docs/solutions/LCR/fan-zhuan-dan-ci-shun-xu-lcof.md @@ -0,0 +1,33 @@ +# [LCR 181. 字符串中的单词反转](https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/) + +- 标签:双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 181. 字符串中的单词反转 - 力扣](https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:逐个翻转字符串中所有的单词。 + +说明: + +- 数组字符串 `s` 可以再前面、后面或者单词间包含多余的空格。 +- 翻转后的单词应当只有一个空格分隔。 +- 翻转后的字符串不应该包含额外的空格。 + +## 解题思路 + +最简单的就是调用 API 进行切片,翻转。复杂一点的也可以根据 API 的思路写出模拟代码。 + +## 代码 + +```python +class Solution: + def reverseWords(self, s: str) -> str: + return " ".join(reversed(s.split())) +``` + diff --git a/docs/solutions/LCR/fan-zhuan-lian-biao-lcof.md b/docs/solutions/LCR/fan-zhuan-lian-biao-lcof.md new file mode 100644 index 00000000..40000966 --- /dev/null +++ b/docs/solutions/LCR/fan-zhuan-lian-biao-lcof.md @@ -0,0 +1,84 @@ +# [LCR 141. 训练计划 III](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [LCR 141. 训练计划 III - 力扣](https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/) + +## 题目大意 + +**描述**:给定一个链表的头节点 `head`。 + +**要求**:将该链表反转并输出反转后链表的头节点。 + +## 解题思路 + +### 思路 1. 迭代 + +1. 使用两个指针 `cur` 和 `pre` 进行迭代。`pre` 指向 `cur` 前一个节点位置。初始时,`pre` 指向 `None`,`cur` 指向 `head`。 + +2. 将 `pre` 和 `cur` 的前后指针进行交换,指针更替顺序为: + 1. 使用 `next` 指针保存当前节点 `cur` 的后一个节点,即 `next = cur.next`; + 2. 断开当前节点 `cur` 的后一节点链接,将 `cur` 的 `next` 指针指向前一节点 `pre`,即 `cur.next = pre`; + 3. `pre` 向前移动一步,移动到 `cur` 位置,即 `pre = cur`; + 4. `cur` 向前移动一步,移动到之前 `next` 指针保存的位置,即 `cur = next`。 +3. 继续执行第 2 步中的 1、2、3、4。 +4. 最后等到 `cur` 遍历到链表末尾,即 `cur == None`,时,`pre` 所在位置就是反转后链表的头节点,返回新的头节点 `pre`。 + +使用迭代法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111133639.png) + +### 思路 2. 递归 + +具体做法如下: + +- 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。 +- 然后从 `head.next` 的位置开始调用递归函数,即将 `head.next` 为头节点的链表进行反转,并返回该链表的头节点。 +- 递归到链表的最后一个节点,将其作为最终的头节点,即为 `new_head`。 +- 在每次递归函数返回的过程中,改变 `head` 和 `head.next` 的指向关系。也就是将 `head.next` 的`next` 指针先指向当前节点 `head`,即 `head.next.next = head `。 +- 然后让当前节点 `head` 的 `next` 指针指向 `None`,从而实现从链表尾部开始的局部反转。 +- 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。 +- 最后返回反转后的链表头节点 `new_head`。 + +使用递归法反转链表的示意图如下所示: + +![](https://qcdn.itcharge.cn/images/20220111134246.png) + +## 代码 + +1. 迭代 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + pre = None + cur = head + while cur != None: + next = cur.next + cur.next = pre + pre = cur + cur = next + return pre +``` + +2. 递归 + +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head == None or head.next == None: + return head + new_head = self.reverseList(head.next) + head.next.next = head + head.next = None + return new_head +``` + +## 参考资料 + +- 【题解】[反转链表 - 反转链表 - 力扣](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/) +- 【题解】[【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/) + diff --git a/docs/solutions/LCR/fei-bo-na-qi-shu-lie-lcof.md b/docs/solutions/LCR/fei-bo-na-qi-shu-lie-lcof.md new file mode 100644 index 00000000..bf28b20a --- /dev/null +++ b/docs/solutions/LCR/fei-bo-na-qi-shu-lie-lcof.md @@ -0,0 +1,39 @@ +# [LCR 126. 斐波那契数](https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/) + +- 标签:记忆化搜索、数学、动态规划 +- 难度:简单 + +## 题目链接 + +- [LCR 126. 斐波那契数 - 力扣](https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:计算斐波那契数列的第 `n` 项。 + +注意:答案需对 `1000000007` 进行取余操作。 + +## 解题思路 + +斐波那契的递推公式为:`F(n) = F(n-1) + F(n-2)`。 + +直接根据递推公式求解即可。注意答案需要取余。 + +## 代码 + +```python +class Solution: + def fib(self, n: int) -> int: + if n < 2: + return n + f1 = 0 + f2 = 0 + f3 = 1 + for i in range(2, n + 1): + f1, f2 = f2, f3 + f3 = (f1 + f2) % 1000000007 + return f3 +``` + diff --git a/docs/solutions/LCR/fpTFWP.md b/docs/solutions/LCR/fpTFWP.md new file mode 100644 index 00000000..ecf927f6 --- /dev/null +++ b/docs/solutions/LCR/fpTFWP.md @@ -0,0 +1,49 @@ +# [LCR 112. 矩阵中的最长递增路径](https://leetcode.cn/problems/fpTFWP/) + +- 标签:深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、数组、动态规划、矩阵 +- 难度:困难 + +## 题目链接 + +- [LCR 112. 矩阵中的最长递增路径 - 力扣](https://leetcode.cn/problems/fpTFWP/) + +## 题目大意 + +给定一个 `m * n` 大小的整数矩阵 `matrix`。要求:找出其中最长递增路径的长度。 + +对于每个单元格,可以往上、下、左、右四个方向移动,不能向对角线方向移动或移动到边界外。 + +## 解题思路 + +深度优先搜索。使用二维数组 `record` 存储遍历过的单元格最大路径长度,已经遍历过的单元格就不需要再次遍历了。 + +## 代码 + +```python +class Solution: + max_len = 0 + directions = {(1, 0), (-1, 0), (0, 1), (0, -1)} + + def longestIncreasingPath(self, matrix: List[List[int]]) -> int: + if not matrix: + return 0 + rows, cols = len(matrix), len(matrix[0]) + record = [[0 for _ in range(cols)] for _ in range(rows)] + + def dfs(i, j): + record[i][j] = 1 + for direction in self.directions: + new_i, new_j = i + direction[0], j + direction[1] + if 0 <= new_i < rows and 0 <= new_j < cols and matrix[new_i][new_j] > matrix[i][j]: + if record[new_i][new_j] == 0: + dfs(new_i, new_j) + record[i][j] = max(record[i][j], record[new_i][new_j] + 1) + self.max_len = max(self.max_len, record[i][j]) + + for i in range(rows): + for j in range(cols): + if record[i][j] == 0: + dfs(i, j) + return self.max_len +``` + diff --git a/docs/solutions/LCR/fu-za-lian-biao-de-fu-zhi-lcof.md b/docs/solutions/LCR/fu-za-lian-biao-de-fu-zhi-lcof.md new file mode 100644 index 00000000..f1496a06 --- /dev/null +++ b/docs/solutions/LCR/fu-za-lian-biao-de-fu-zhi-lcof.md @@ -0,0 +1,44 @@ +# [LCR 154. 复杂链表的复制](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) + +- 标签:哈希表、链表 +- 难度:中等 + +## 题目链接 + +- [LCR 154. 复杂链表的复制 - 力扣](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) + +## 题目大意 + +给定一个链表,每个节点除了 `next` 指针之后,还包含一个随机指针 `random`,该指针可以指向链表中的任何节点或者空节点。 + +要求:将该链表进行深拷贝。 + +## 解题思路 + +遍历链表,利用哈希表,以旧节点:新节点为映射关系,将节点关系存储下来。 + +再次遍历链表,将新链表的 `next` 和 `random` 指针设置好。 + +## 代码 + +```python +class Solution: + def copyRandomList(self, head: 'Node') -> 'Node': + if not head: + return None + node_dict = dict() + curr = head + while curr: + new_node = Node(curr.val, None, None) + node_dict[curr] = new_node + curr = curr.next + curr = head + while curr: + if curr.next: + node_dict[curr].next = node_dict[curr.next] + if curr.random: + node_dict[curr].random = node_dict[curr.random] + curr = curr.next + return node_dict[head] +``` + diff --git a/docs/solutions/LCR/g5c51o.md b/docs/solutions/LCR/g5c51o.md new file mode 100644 index 00000000..e4190d6b --- /dev/null +++ b/docs/solutions/LCR/g5c51o.md @@ -0,0 +1,94 @@ +# [LCR 060. 前 K 个高频元素](https://leetcode.cn/problems/g5c51o/) + +- 标签:数组、哈希表、分治、桶排序、计数、快速选择、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [LCR 060. 前 K 个高频元素 - 力扣](https://leetcode.cn/problems/g5c51o/) + +## 题目大意 + +给定一个整数数组 `nums` 和一个整数 `k`。 + +要求:返回出现频率前 `k` 高的元素。可以按任意顺序返回答案。 + +## 解题思路 + +- 使用哈希表记录下数组中各个元素的频数。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +- 然后将哈希表中的元素去重,转换为新数组。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +- 利用建立大顶堆,此时堆顶元素即为频数最高的元素。时间复杂度 $O(n)$,空间复杂度 $O(n)$。 +- 将堆顶元素加入到答案数组中,并交换堆顶元素与末尾元素,此时末尾元素已移出堆。继续调整大顶堆。时间复杂度 $O(log{n})$。 +- 调整玩大顶堆之后,此时堆顶元素为频数第二高的元素,和上一步一样,将其加入到答案数组中,继续交换堆顶元素与末尾元素,继续调整大顶堆。 +- 不断重复上步,直到 k 次结束。调整 k 次的时间复杂度 $O(nlog{n})$。 + +总体时间复杂度 $O(nlog{n})$。 + +因为用的是大顶堆,堆的规模是 N 个元素,调整 k 次,所以时间复杂度是 $O(nlog{n})$。 +如果用小顶堆,只需维护 k 个元素的小顶堆,不断向堆中替换元素即可,时间复杂度为 $O(nlog{k})$。 + +## 代码 + +```python +class Solution: + # 调整为大顶堆 + def heapify(self, nums, nums_dict, index, end): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if nums_dict[nums[left]] > nums_dict[nums[max_index]]: + max_index = left + if right <= end and nums_dict[nums[right]] > nums_dict[nums[max_index]]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(self, nums, nums_dict): + size = len(nums) + # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + self.heapify(nums, nums_dict, i, size - 1) + return nums + + # 堆排序方法(本题未用到) + def maxHeapSort(self, nums, nums_dict): + self.buildMaxHeap(nums) + size = len(nums) + for i in range(size): + nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] + self.heapify(nums, nums_dict, 0, size - i - 2) + return nums + + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + # 统计元素频数 + nums_dict = dict() + for num in nums: + if num in nums_dict: + nums_dict[num] += 1 + else: + nums_dict[num] = 1 + + # 使用 set 方法去重,得到新数组 + new_nums = list(set(nums)) + size = len(new_nums) + # 初始化大顶堆 + self.buildMaxHeap(new_nums, nums_dict) + res = list() + for i in range(k): + # 堆顶元素为当前堆中频数最高的元素,将其加入答案中 + res.append(new_nums[0]) + # 交换堆顶和末尾元素,继续调整大顶堆 + new_nums[0], new_nums[size - i - 1] = new_nums[size - i - 1], new_nums[0] + self.heapify(new_nums, nums_dict, 0, size - i - 2) + return res +``` + diff --git a/docs/solutions/LCR/gaM7Ch.md b/docs/solutions/LCR/gaM7Ch.md new file mode 100644 index 00000000..b9c93915 --- /dev/null +++ b/docs/solutions/LCR/gaM7Ch.md @@ -0,0 +1,43 @@ +# [LCR 103. 零钱兑换](https://leetcode.cn/problems/gaM7Ch/) + +- 标签:广度优先搜索、数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 103. 零钱兑换 - 力扣](https://leetcode.cn/problems/gaM7Ch/) + +## 题目大意 + +给定不同面额的硬币 `coins` 和一个总金额 `amount`。 + +乔秋:计算出凑成总金额所需的最少的硬币个数。如果无法凑出,则返回 `-1`。 + +## 解题思路 + +完全背包问题。 + +可以转换为有 `n` 枚不同的硬币,每种硬币可以无限次使用。凑成总金额为 `amount` 的背包,最少需要多少硬币。 + +动态规划的状态 `dp[i]` 可以表示为:凑成总金额为 `i` 的组合中,至少有 `dp[i]` 枚硬币。 + +动态规划的状态转移方程为:`dp[i] = min(dp[i], + dp[i-coin] + 1`,意思为凑成总金额为 `i` 最少硬币数量 = 「不使用当前 `coin`,只使用之前硬币凑成金额 `i` 的最少硬币数量」和「凑成金额 `i - num` 的最少硬币数量,再加上当前硬币」两者的较小值。 + +## 代码 + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf') for _ in range(amount + 1)] + dp[0] = 0 + + for coin in coins: + for i in range(coin, amount + 1): + dp[i] = min(dp[i], dp[i - coin] + 1) + + if dp[amount] != float('inf'): + return dp[amount] + else: + return -1 +``` + diff --git a/docs/solutions/LCR/gou-jian-cheng-ji-shu-zu-lcof.md b/docs/solutions/LCR/gou-jian-cheng-ji-shu-zu-lcof.md new file mode 100644 index 00000000..e629feba --- /dev/null +++ b/docs/solutions/LCR/gou-jian-cheng-ji-shu-zu-lcof.md @@ -0,0 +1,41 @@ +# [LCR 191. 按规则计算统计结果](https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/) + +- 标签:数组、前缀和 +- 难度:中等 + +## 题目链接 + +- [LCR 191. 按规则计算统计结果 - 力扣](https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/) + +## 题目大意 + +给定一个数组 `A`。 + +要求:构建一个数组 `B`,其中 `B[i]` 为数组 `A` 中除了 `A[i]` 之外的其他所有元素乘积。 + +要求不能使用除法。 + +## 解题思路 + +构造一个答案数组 `B`,长度和数组 `A` 长度一致。先从左到右遍历一遍 `A` 数组,将 `A[i]` 左侧的元素乘积累积起来,存储到 `B` 数组中。再从右到左遍历一遍,将 `A[i]` 右侧的元素乘积累积起来,再乘以原本 `B[i]` 的值,即为 `A` 中除了 `A[i]` 之外的其他所有元素乘积。 + +## 代码 + +```python +class Solution: + def constructArr(self, a: List[int]) -> List[int]: + size = len(a) + b = [1 for _ in range(size)] + + left = 1 + for i in range(size): + b[i] *= left + left *= a[i] + + right = 1 + for i in range(size - 1, -1, -1): + b[i] *= right + right *= a[i] + return b +``` + diff --git a/docs/solutions/LCR/gu-piao-de-zui-da-li-run-lcof.md b/docs/solutions/LCR/gu-piao-de-zui-da-li-run-lcof.md new file mode 100644 index 00000000..7096700a --- /dev/null +++ b/docs/solutions/LCR/gu-piao-de-zui-da-li-run-lcof.md @@ -0,0 +1,36 @@ +# [LCR 188. 买卖芯片的最佳时机](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/) + +- 标签:数组、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 188. 买卖芯片的最佳时机 - 力扣](https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/) + +## 题目大意 + +给定一个数组 `nums`,`nums[i]` 表示一支给定股票第 `i` 天的价格。选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。求能获取的最大利润。 + +## 解题思路 + +最简单的思路当然是两重循环暴力枚举,寻找不同天数下的最大利润。 + +但更好的做法是进行一次遍历。设置两个变量 `minprice`(用来记录买入的最小值)、`maxprofit`(用来记录可获取的最大利润)。 + +进行一次遍历,遇到当前价格比 `minprice` 还要小的,就更新 `minprice`。如果单签价格大于或者等于 `minprice`,则判断一下以当前价格卖出的话能卖多少,如果比 `maxprofit` 还要大,就更新 `maxprofit`。 + +## 代码 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + minprice = 10010 + maxprofit = 0 + for price in prices: + if price < minprice: + minprice = price + elif price - minprice > maxprofit: + maxprofit = price - minprice + return maxprofit +``` + diff --git a/docs/solutions/LCR/h54YBf.md b/docs/solutions/LCR/h54YBf.md new file mode 100644 index 00000000..81884f52 --- /dev/null +++ b/docs/solutions/LCR/h54YBf.md @@ -0,0 +1,67 @@ +# [LCR 048. 二叉树的序列化与反序列化](https://leetcode.cn/problems/h54YBf/) + +- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 +- 难度:困难 + +## 题目链接 + +- [LCR 048. 二叉树的序列化与反序列化 - 力扣](https://leetcode.cn/problems/h54YBf/) + +## 题目大意 + +要求:设计一个算法,来实现二叉树的序列化与反序列化。 + +## 解题思路 + +### 1. 序列化:将二叉树转为字符串数据表示 + +按照前序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 + +注意:如果遇到空节点,则标记为 'None',这样在反序列化时才能唯一确定一棵二叉树。 + +### 2. 反序列化:将字符串数据转为二叉树结构 + +先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 + +- 从数组左侧取出一个元素。 + - 如果当前元素为 'None',则返回 None。 + - 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 + - 最后返回当前根节点。 + +## 代码 + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + if not root: + return 'None' + return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) + + + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + + def dfs(datalist): + val = datalist.pop(0) + if val == 'None': + return None + root = TreeNode(int(val)) + root.left = dfs(datalist) + root.right = dfs(datalist) + return root + + datalist = data.split(',') + return dfs(datalist) +``` + diff --git a/docs/solutions/LCR/hPov7L.md b/docs/solutions/LCR/hPov7L.md new file mode 100644 index 00000000..9f7f4b47 --- /dev/null +++ b/docs/solutions/LCR/hPov7L.md @@ -0,0 +1,42 @@ +# [LCR 044. 在每个树行中找最大值](https://leetcode.cn/problems/hPov7L/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 044. 在每个树行中找最大值 - 力扣](https://leetcode.cn/problems/hPov7L/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:找出二叉树中每一层的最大值。 + +## 解题思路 + +利用队列进行层序遍历,并记录下每一层的最大值,将其存入答案数组中。 + +## 代码 + +```python +class Solution: + def largestValues(self, root: TreeNode) -> List[int]: + queue = [] + res = [] + if root: + queue.append(root) + while queue: + max_level = float('-inf') + size_level = len(queue) + for i in range(size_level): + node = queue.pop(0) + max_level = max(max_level, node.val) + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + res.append(max_level) + return res +``` + diff --git a/docs/solutions/LCR/he-bing-liang-ge-pai-xu-de-lian-biao-lcof.md b/docs/solutions/LCR/he-bing-liang-ge-pai-xu-de-lian-biao-lcof.md new file mode 100644 index 00000000..714c4c54 --- /dev/null +++ b/docs/solutions/LCR/he-bing-liang-ge-pai-xu-de-lian-biao-lcof.md @@ -0,0 +1,49 @@ +# [LCR 142. 训练计划 IV](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) + +- 标签:递归、链表 +- 难度:简单 + +## 题目链接 + +- [LCR 142. 训练计划 IV - 力扣](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) + +## 题目大意 + +给定两个升序链表。 + +要求:将其合并为一个升序链表。 + +## 解题思路 + +利用归并排序的思想。 + +创建一个新的链表节点作为头节点(记得保存),然后判断 l1和 l2 头节点的值,将较小值的节点添加到新的链表中。 + +当一个节点添加到新的链表中之后,将对应的 l1 或 l2 链表向后移动一位。 + +然后继续判断当前 l1 节点和当前 l2 节点的值,继续将较小值的节点添加到新的链表中,然后将对应的链表向后移动一位。 + +这样,当 l1 或 l2 遍历到最后,最多有一个链表还有节点未遍历,则直接将该节点链接到新的链表尾部即可。 + +## 代码 + +```python +class Solution: + def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: + newHead = ListNode(-1) + + curr = newHead + while l1 and l2: + if l1.val <= l2.val: + curr.next = l1 + l1 = l1.next + else: + curr.next = l2 + l2 = l2.next + curr = curr.next + + curr.next = l1 if l1 is not None else l2 + + return newHead.next +``` + diff --git a/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md b/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md new file mode 100644 index 00000000..64e0883c --- /dev/null +++ b/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md @@ -0,0 +1,116 @@ +# [LCR 180. 文件组合](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) + +- 标签:数学、双指针、枚举 +- 难度:简单 + +## 题目链接 + +- [LCR 180. 文件组合 - 力扣](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) + +## 题目大意 + +**描述**:给定一个正整数 `target`。 + +**要求**:输出所有和为 `target` 的连续正整数序列(至少含有两个数)。序列中的数字由小到大排列,不同序列按照首个数字从小到大排列。 + +**说明**: + +- $1 \le target \le 10^5$。 + +**示例**: + +- 示例 1: + +```python +输入:target = 9 +输出:[[2,3,4],[4,5]] +``` + +- 示例 2: + +```python +输入:target = 15 +输出:[[1,2,3,4,5],[4,5,6],[7,8]] +``` + +## 解题思路 + +### 思路 1:枚举算法 + +连续正整数序列中元素的最小值大于等于 `1`,而最大值不会超过 `target`。所以我们可以枚举可行的区间,并计算出区间和,将其与 `target` 进行比较,如果相等则将对应的区间元素加入答案数组中,最终返回答案数组。 + +因为题目要求至少含有两个数,则序列开始元素不会超过 `target` 的一半,所以序列开始元素可以从 `1` 开始,枚举到 `target // 2` 即可。 + +具体步骤如下: + +1. 使用列表变量 `res` 作为答案数组。 +2. 使用一重循环 `i`,用于枚举序列开始位置,枚举范围为 `[1, target // 2]`。 +3. 使用变量 `cur_sum` 维护当前区间的区间和,`cur_sum` 初始为 `0`。 +4. 使用第 `2` 重循环 `j`,用于枚举序列的结束位置,枚举范围为 `[i, target - 1]`,并累积计算当前区间的区间和,即 `cur_sum += j`。 + 1. 如果当前区间的区间和大于 `target`,则跳出循环。 + 2. 如果当前区间的区间和等于 `target`,则将区间上的元素保存为列表,并添加到答案数组中,然后跳出第 `2` 重循环。 +5. 遍历完返回答案数组。 + +### 思路 1:代码 + +```python +class Solution: + def findContinuousSequence(self, target: int) -> List[List[int]]: + res = [] + for i in range(1, target // 2 + 1): + cur_sum = 0 + for j in range(i, target): + cur_sum += j + if cur_sum > target: + break + if cur_sum == target: + cur_res = [] + for k in range(i, j + 1): + cur_res.append(k) + res.append(cur_res) + break + return res +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$target \times \sqrt{target}$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:滑动窗口 + +具体做法如下: + +- 初始化窗口,令 `left = 1`,`right = 2`。 +- 计算 `sum = (left + right) * (right - left + 1) // 2`。 +- 如果 `sum == target`,时,将其加入答案数组中。 +- 如果 `sum < target` 时,说明需要扩大窗口,则 `right += 1`。 +- 如果 `sum > target` 时,说明需要缩小窗口,则 `left += 1`。 +- 直到 `left >= right` 时停止,返回答案数组。 + +### 思路 2:滑动窗口代码 + +```python +class Solution: + def findContinuousSequence(self, target: int) -> List[List[int]]: + left, right = 1, 2 + res = [] + while left < right: + sum = (left + right) * (right - left + 1) // 2 + if sum == target: + arr = [] + for i in range(0, right - left + 1): + arr.append(i + left) + res.append(arr) + left += 1 + elif sum < target: + right += 1 + else: + left += 1 + return res +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(target)$。 +- **空间复杂度**:$O(1)$。 diff --git a/docs/solutions/LCR/he-wei-sde-liang-ge-shu-zi-lcof.md b/docs/solutions/LCR/he-wei-sde-liang-ge-shu-zi-lcof.md new file mode 100644 index 00000000..40168787 --- /dev/null +++ b/docs/solutions/LCR/he-wei-sde-liang-ge-shu-zi-lcof.md @@ -0,0 +1,41 @@ +# [LCR 179. 查找总价格为目标值的两个商品](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) + +- 标签:数组、双指针、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 179. 查找总价格为目标值的两个商品 - 力扣](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) + +## 题目大意 + +给定一个升序数组 `nums`,以及一个目标整数 `target`。 + +要求:在数组中查找两个数,使它们的和刚好等于 `target`。 + +## 解题思路 + +因为数组是升序的,可以使用双指针。`left`、`right` 分别指向数组首尾位置。 + +- 计算 `sum = nums[left] + nums[right]`。 +- 如果 `sum > target`,则 `right` 进行左移。 +- 如果 `sum < target`,则 `left` 进行右移。 +- 如果 `sum == target`,则返回 `[nums[left], nums[right]]`。 + +## 代码 + +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + left, right = 0, len(nums) - 1 + while left < right: + sum = nums[left] + nums[right] + if sum > target: + right -= 1 + elif sum < target: + left += 1 + else: + return nums[left], nums[right] + return [] +``` + diff --git a/docs/solutions/LCR/hua-dong-chuang-kou-de-zui-da-zhi-lcof.md b/docs/solutions/LCR/hua-dong-chuang-kou-de-zui-da-zhi-lcof.md new file mode 100644 index 00000000..2bb4be17 --- /dev/null +++ b/docs/solutions/LCR/hua-dong-chuang-kou-de-zui-da-zhi-lcof.md @@ -0,0 +1,42 @@ +# [LCR 183. 望远镜中最高的海拔](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) + +- 标签:队列、滑动窗口、单调队列、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [LCR 183. 望远镜中最高的海拔 - 力扣](https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) + +## 题目大意 + +给定一个整数数组 `nums` 和滑动窗口的大小 `k`。表示为大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 `k` 个数字,滑动窗口每次只能向右移动一位。 + +要求:返回滑动窗口中的最大值。 + +## 解题思路 + +暴力求解的话,二重循环,时间复杂度为 $O(n * k)$。 + +我们可以使用优先队列,每次窗口移动时想优先队列中增加一个节点,并删除一个节点。将窗口中的最大值加入到答案数组中。 + +## 代码 + +```python +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + size = len(nums) + if size == 0: + return [] + + q = [(-nums[i], i) for i in range(k)] + heapq.heapify(q) + res = [-q[0][0]] + + for i in range(k, size): + heapq.heappush(q, (-nums[i], i)) + while q[0][1] <= i - k: + heapq.heappop(q) + res.append(-q[0][0]) + return res +``` + diff --git a/docs/solutions/LCR/iIQa4I.md b/docs/solutions/LCR/iIQa4I.md new file mode 100644 index 00000000..af440845 --- /dev/null +++ b/docs/solutions/LCR/iIQa4I.md @@ -0,0 +1,44 @@ +# [LCR 038. 每日温度](https://leetcode.cn/problems/iIQa4I/) + +- 标签:栈、数组、单调栈 +- 难度:中等 + +## 题目链接 + +- [LCR 038. 每日温度 - 力扣](https://leetcode.cn/problems/iIQa4I/) + +## 题目大意 + +给定一个列表 `temperatures`,每一个位置对应每天的气温。要求输出一个列表,列表上每个位置代表如果要观测到更高的气温,至少需要等待的天数。如果之后的气温不再升高,则用 `0` 来代替。 + +## 解题思路 + +题目的意思实际上就是给定一个数组,每个位置上有整数值。对于每个位置,在该位置后侧找到第一个比当前值更高的值。求该点与该位置的距离,将所有距离保存为数组返回结果。 + +很简单的思路是对于每个温度值,向后依次进行搜索,找到比当前温度更高的值。 + +更好的方式使用「递减栈」。栈中保存元素的下标。 + +首先,将答案数组全部赋值为 0。然后遍历数组每个位置元素。 + +- 如果栈为空,则将当前元素的下标入栈。 +- 如果栈不为空,且当前数字大于栈顶元素对应数字,则栈顶元素出栈,并计算下标差。 + - 此时当前元素就是栈顶元素的下一个更高值,将其下标差存入答案数组中保存起来,判断栈顶元素。 +- 直到当前数字小于或等于栈顶元素,则停止出栈,将当前元素下标入栈。 + +## 代码 + +```python +class Solution: + def dailyTemperatures(self, temperatures: List[int]) -> List[int]: + n = len(temperatures) + stack = [] + ans = [0 for _ in range(n)] + for i in range(n): + while stack and temperatures[i] > temperatures[stack[-1]]: + index = stack.pop() + ans[index] = (i - index) + stack.append(i) + return ans +``` + diff --git a/docs/solutions/LCR/iSwD2y.md b/docs/solutions/LCR/iSwD2y.md new file mode 100644 index 00000000..7f2670ef --- /dev/null +++ b/docs/solutions/LCR/iSwD2y.md @@ -0,0 +1,78 @@ +# [LCR 065. 单词的压缩编码](https://leetcode.cn/problems/iSwD2y/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 065. 单词的压缩编码 - 力扣](https://leetcode.cn/problems/iSwD2y/) + +## 题目大意 + +给定一个单词数组 `words`。要求对 `words` 进行编码成一个助记字符串,用来帮助记忆。`words` 中拥有相同字符后缀的单词可以合并成一个单词,比如`time` 和 `me` 可以合并成 `time`。同时每个不能再合并的单词末尾以 `#` 为结束符,将所有合并后的单词排列起来就是一个助记字符串。 + +要求:返回对 `words` 进行编码的最小助记字符串 `s` 的长度。 + +## 解题思路 + +构建一个字典树。然后对字符串长度进行从小到大排序。 + +再依次将去重后的所有单词插入到字典树中。如果出现比当前单词更长的单词,则将短单词的结尾置为 `False`,意为替换掉短单词。 + +然后再依次在字典树中查询所有单词,「单词长度 + 1」就是当前不能在合并的单词,累加起来就是答案。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = False + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + +class Solution: + def minimumLengthEncoding(self, words: List[str]) -> int: + trie_tree = Trie() + words = list(set(words)) + words.sort(key=lambda i: len(i)) + + ans = 0 + for word in words: + trie_tree.insert(word[::-1]) + + for word in words: + if trie_tree.search(word[::-1]): + ans += len(word) + 1 + + return ans +``` + diff --git a/docs/solutions/LCR/index.md b/docs/solutions/LCR/index.md new file mode 100644 index 00000000..0abc9de6 --- /dev/null +++ b/docs/solutions/LCR/index.md @@ -0,0 +1,172 @@ +## 本章内容 + +- [LCR 001. 两数相除](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xoh6Oh.md) +- [LCR 002. 二进制求和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/JFETK5.md) +- [LCR 003. 比特位计数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/w3tCBm.md) +- [LCR 004. 只出现一次的数字 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WGki4K.md) +- [LCR 005. 最大单词长度乘积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/aseY1I.md) +- [LCR 006. 两数之和 II - 输入有序数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/kLl5u1.md) +- [LCR 007. 三数之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/1fGaJU.md) +- [LCR 008. 长度最小的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2VG8Kg.md) +- [LCR 009. 乘积小于 K 的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ZVAVXX.md) +- [LCR 010. 和为 K 的子数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QTMn0o.md) +- [LCR 011. 连续数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/A1NYOS.md) +- [LCR 012. 寻找数组的中心下标](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/tvdfij.md) +- [LCR 013. 二维区域和检索 - 矩阵不可变](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/O4NDxx.md) +- [LCR 016. 无重复字符的最长子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/wtcaE1.md) +- [LCR 017. 最小覆盖子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/M1oyTv.md) +- [LCR 018. 验证回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/XltzEq.md) +- [LCR 019. 验证回文串 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/RQku0D.md) +- [LCR 020. 回文子串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/a7VOhD.md) +- [LCR 021. 删除链表的倒数第 N 个结点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/SLwz0R.md) +- [LCR 022. 环形链表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/c32eOV.md) +- [LCR 023. 相交链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/3u1WK4.md) +- [LCR 024. 反转链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/UHnkqh.md) +- [LCR 025. 两数相加 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lMSNwu.md) +- [LCR 026. 重排链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/LGjMqU.md) +- [LCR 027. 回文链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/aMhZSa.md) +- [LCR 028. 扁平化多级双向链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Qv1Da2.md) +- [LCR 029. 循环有序列表的插入](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/4ueAj6.md) +- [LCR 030. O(1) 时间插入、删除和获取随机元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/FortPu.md) +- [LCR 031. LRU 缓存](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/OrIXps.md) +- [LCR 032. 有效的字母异位词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dKk3P7.md) +- [LCR 033. 字母异位词分组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/sfvd7V.md) +- [LCR 034. 验证外星语词典](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lwyVBB.md) +- [LCR 035. 最小时间差](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/569nqc.md) +- [LCR 036. 逆波兰表达式求值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/8Zf90G.md) +- [LCR 037. 行星碰撞](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/XagZNi.md) +- [LCR 038. 每日温度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/iIQa4I.md) +- [LCR 039. 柱状图中最大的矩形](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0ynMMM.md) +- [LCR 041. 数据流中的移动平均值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qIsx9U.md) +- [LCR 042. 最近的请求次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/H8086Q.md) +- [LCR 043. 完全二叉树插入器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NaqhDT.md) +- [LCR 044. 在每个树行中找最大值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/hPov7L.md) +- [LCR 045. 找树左下角的值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/LwUNpT.md) +- [LCR 046. 二叉树的右视图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WNC0Lk.md) +- [LCR 047. 二叉树剪枝](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/pOCWxh.md) +- [LCR 048. 二叉树的序列化与反序列化](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/h54YBf.md) +- [LCR 049. 求根节点到叶节点数字之和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/3Etpl5.md) +- [LCR 050. 路径总和 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/6eUYwP.md) +- [LCR 051. 二叉树中的最大路径和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jC7MId.md) +- [LCR 052. 递增顺序搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NYBBNL.md) +- [LCR 053. 二叉搜索树中的中序后继](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/P5rCT8.md) +- [LCR 054. 把二叉搜索树转换为累加树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/w6cpku.md) +- [LCR 055. 二叉搜索树迭代器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/kTOapQ.md) +- [LCR 056. 两数之和 IV - 输入二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/opLdQZ.md) +- [LCR 057. 存在重复元素 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7WqeDu.md) +- [LCR 059. 数据流中的第 K 大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jBjn9C.md) +- [LCR 060. 前 K 个高频元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/g5c51o.md) +- [LCR 062. 实现 Trie (前缀树)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QC3q1f.md) +- [LCR 063. 单词替换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/UhWRSj.md) +- [LCR 064. 实现一个魔法字典](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/US1pGT.md) +- [LCR 065. 单词的压缩编码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/iSwD2y.md) +- [LCR 066. 键值映射](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/z1R5dt.md) +- [LCR 067. 数组中两个数的最大异或值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ms70jA.md) +- [LCR 068. 搜索插入位置](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/N6YdxV.md) +- [LCR 072. x 的平方根](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jJ0w9p.md) +- [LCR 073. 爱吃香蕉的狒狒](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/nZZqjQ.md) +- [LCR 074. 合并区间](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/SsGoHC.md) +- [LCR 075. 数组的相对排序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0H97ZC.md) +- [LCR 076. 数组中的第 K 个最大元素](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xx4gT2.md) +- [LCR 077. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7WHec2.md) +- [LCR 078. 合并 K 个升序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vvXgSW.md) +- [LCR 079. 子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/TVdhkn.md) +- [LCR 080. 组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/uUsW3B.md) +- [LCR 081. 组合总和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Ygoe9J.md) +- [LCR 082. 组合总和 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/4sjJUc.md) +- [LCR 083. 全排列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/VvJkup.md) +- [LCR 084. 全排列 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7p8L0Z.md) +- [LCR 085. 括号生成](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/IDBivT.md) +- [LCR 086. 分割回文串](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/M99OJA.md) +- [LCR 087. 复原 IP 地址](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/0on3uN.md) +- [LCR 088. 使用最小花费爬楼梯](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/GzCJIP.md) +- [LCR 089. 打家劫舍](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Gu0c2T.md) +- [LCR 090. 打家劫舍 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/PzWKhm.md) +- [LCR 093. 最长的斐波那契子序列的长度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/Q91FMA.md) +- [LCR 095. 最长公共子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qJnOS7.md) +- [LCR 097. 不同的子序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/21dk04.md) +- [LCR 098. 不同路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2AoeFn.md) +- [LCR 101. 分割等和子集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/NUPfPr.md) +- [LCR 102. 目标和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/YaVDxD.md) +- [LCR 103. 零钱兑换](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gaM7Ch.md) +- [LCR 104. 组合总和 Ⅳ](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/D0F0SV.md) +- [LCR 105. 岛屿的最大面积](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ZL6zAn.md) +- [LCR 106. 判断二分图](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vEAB3K.md) +- [LCR 107. 01 矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/2bCMpM.md) +- [LCR 108. 单词接龙](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/om3reC.md) +- [LCR 109. 打开转盘锁](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zlDJc7.md) +- [LCR 111. 除法求值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/vlzXQL.md) +- [LCR 112. 矩阵中的最长递增路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fpTFWP.md) +- [LCR 113. 课程表 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/QA2IGt.md) +- [LCR 116. 省份数量](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bLyHh0.md) +- [LCR 118. 冗余连接](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/7LpjUW.md) +- [LCR 119. 最长连续序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/WhsWhI.md) +- [LCR 120. 寻找文件副本](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md) +- [LCR 121. 寻找目标值 - 二维数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-wei-shu-zu-zhong-de-cha-zhao-lcof.md) +- [LCR 122. 路径加密](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ti-huan-kong-ge-lcof.md) +- [LCR 123. 图书整理 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-wei-dao-tou-da-yin-lian-biao-lcof.md) +- [LCR 124. 推理二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zhong-jian-er-cha-shu-lcof.md) +- [LCR 125. 图书整理 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md) +- [LCR 126. 斐波那契数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fei-bo-na-qi-shu-lie-lcof.md) +- [LCR 127. 跳跃训练](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qing-wa-tiao-tai-jie-wen-ti-lcof.md) +- [LCR 128. 库存管理 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof.md) +- [LCR 129. 字母迷宫](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ju-zhen-zhong-de-lu-jing-lcof.md) +- [LCR 130. 衣橱整理](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md) +- [LCR 131. 砍竹子 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/jian-sheng-zi-lcof.md) +- [LCR 133. 位 1 的个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-jin-zhi-zhong-1de-ge-shu-lcof.md) +- [LCR 134. Pow(x, n)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zhi-de-zheng-shu-ci-fang-lcof.md) +- [LCR 135. 报数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof.md) +- [LCR 136. 删除链表的节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shan-chu-lian-biao-de-jie-dian-lcof.md) +- [LCR 139. 训练计划 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof.md) +- [LCR 140. 训练计划 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md) +- [LCR 141. 训练计划 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fan-zhuan-lian-biao-lcof.md) +- [LCR 142. 训练计划 IV](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-bing-liang-ge-pai-xu-de-lian-biao-lcof.md) +- [LCR 143. 子结构判断](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-de-zi-jie-gou-lcof.md) +- [LCR 144. 翻转二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-jing-xiang-lcof.md) +- [LCR 145. 判断对称二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dui-cheng-de-er-cha-shu-lcof.md) +- [LCR 146. 螺旋遍历二维数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shun-shi-zhen-da-yin-ju-zhen-lcof.md) +- [LCR 147. 最小栈](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bao-han-minhan-shu-de-zhan-lcof.md) +- [LCR 148. 验证图书取出顺序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zhan-de-ya-ru-dan-chu-xu-lie-lcof.md) +- [LCR 149. 彩灯装饰记录 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-lcof.md) +- [LCR 150. 彩灯装饰记录 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof.md) +- [LCR 151. 彩灯装饰记录 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof.md) +- [LCR 152. 验证二叉搜索树的后序遍历序列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof.md) +- [LCR 153. 二叉树中和为目标值的路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof.md) +- [LCR 154. 复杂链表的复制](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fu-za-lian-biao-de-fu-zhi-lcof.md) +- [LCR 155. 将二叉搜索树转化为排序的双向链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof.md) +- [LCR 156. 序列化与反序列化二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/xu-lie-hua-er-cha-shu-lcof.md) +- [LCR 157. 套餐内商品的排列顺序](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zi-fu-chuan-de-pai-lie-lcof.md) +- [LCR 158. 库存管理 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof.md) +- [LCR 159. 库存管理 III](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md) +- [LCR 160. 数据流中的中位数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-ju-liu-zhong-de-zhong-wei-shu-lcof.md) +- [LCR 161. 连续天数的最高销售额](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/lian-xu-zi-shu-zu-de-zui-da-he-lcof.md) +- [LCR 163. 找到第 k 位数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof.md) +- [LCR 164. 破解闯关密码](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof.md) +- [LCR 165. 解密数字](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof.md) +- [LCR 166. 珠宝的最高价值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/li-wu-de-zui-da-jie-zhi-lcof.md) +- [LCR 167. 招式拆解 I](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof.md) +- [LCR 168. 丑数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/chou-shu-lcof.md) +- [LCR 169. 招式拆解 II](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof.md) +- [LCR 170. 交易逆序对的总数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md) +- [LCR 171. 训练计划 V](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof.md) +- [LCR 172. 统计目标成绩的出现次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof.md) +- [LCR 173. 点名](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/que-shi-de-shu-zi-lcof.md) +- [LCR 174. 寻找二叉搜索树中的目标节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof.md) +- [LCR 175. 计算二叉树的深度](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-shen-du-lcof.md) +- [LCR 176. 判断是否为平衡二叉树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ping-heng-er-cha-shu-lcof.md) +- [LCR 177. 撞色搭配](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof.md) +- [LCR 179. 查找总价格为目标值的两个商品](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-liang-ge-shu-zi-lcof.md) +- [LCR 180. 文件组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof.md) +- [LCR 181. 字符串中的单词反转](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/fan-zhuan-dan-ci-shun-xu-lcof.md) +- [LCR 182. 动态口令](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/zuo-xuan-zhuan-zi-fu-chuan-lcof.md) +- [LCR 183. 望远镜中最高的海拔](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/hua-dong-chuang-kou-de-zui-da-zhi-lcof.md) +- [LCR 184. 设计自助结算系统](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/dui-lie-de-zui-da-zhi-lcof.md) +- [LCR 186. 文物朝代判断](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-ke-pai-zhong-de-shun-zi-lcof.md) +- [LCR 187. 破冰游戏](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md) +- [LCR 188. 买卖芯片的最佳时机](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gu-piao-de-zui-da-li-run-lcof.md) +- [LCR 189. 设计机械累加器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/qiu-12n-lcof.md) +- [LCR 190. 加密运算](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof.md) +- [LCR 191. 按规则计算统计结果](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/gou-jian-cheng-ji-shu-zu-lcof.md) +- [LCR 192. 把字符串转换成整数 (atoi)](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof.md) +- [LCR 193. 二叉搜索树的最近公共祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof.md) +- [LCR 194. 二叉树的最近公共祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof.md) diff --git a/docs/solutions/LCR/jBjn9C.md b/docs/solutions/LCR/jBjn9C.md new file mode 100644 index 00000000..6b7ad2af --- /dev/null +++ b/docs/solutions/LCR/jBjn9C.md @@ -0,0 +1,45 @@ +# [LCR 059. 数据流中的第 K 大元素](https://leetcode.cn/problems/jBjn9C/) + +- 标签:树、设计、二叉搜索树、二叉树、数据流、堆(优先队列) +- 难度:简单 + +## 题目链接 + +- [LCR 059. 数据流中的第 K 大元素 - 力扣](https://leetcode.cn/problems/jBjn9C/) + +## 题目大意 + +设计一个 ` KthLargest` 类,用于找到数据流中第 `k` 大元素。 + +- `KthLargest(int k, int[] nums)`:使用整数 `k` 和整数流 `nums` 初始化对象。 +- `int add(int val)`:将 `val` 插入数据流 `nums` 后,返回当前数据流中第 `k` 大的元素。 + +## 解题思路 + +- 建立大小为 `k` 的大顶堆,堆中元素保证不超过 k 个。 +- 每次 `add` 操作时,将新元素压入堆中,如果堆中元素超出了 `k` 个,则将堆中最小元素(堆顶)移除。 +- 此时堆中最小元素(堆顶)就是整个数据流中的第 `k` 大元素。 + +## 代码 + +```python +import heapq + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + self.min_heap = [] + self.k = k + for num in nums: + heapq.heappush(self.min_heap, num) + if len(self.min_heap) > k: + heapq.heappop(self.min_heap) + + + def add(self, val: int) -> int: + heapq.heappush(self.min_heap, val) + if len(self.min_heap) > self.k: + heapq.heappop(self.min_heap) + return self.min_heap[0] +``` + diff --git a/docs/solutions/LCR/jC7MId.md b/docs/solutions/LCR/jC7MId.md new file mode 100644 index 00000000..b8d6c92b --- /dev/null +++ b/docs/solutions/LCR/jC7MId.md @@ -0,0 +1,52 @@ +# [LCR 051. 二叉树中的最大路径和](https://leetcode.cn/problems/jC7MId/) + +- 标签:树、深度优先搜索、动态规划、二叉树 +- 难度:困难 + +## 题目链接 + +- [LCR 051. 二叉树中的最大路径和 - 力扣](https://leetcode.cn/problems/jC7MId/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:返回其最大路径和。 + +- 路径:从树中的任意节点出发,沿父节点——子节点连接,到达任意节点的序列。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 +- 路径和:路径中各节点值的总和。 + +## 解题思路 + +深度优先搜索遍历二叉树。递归的同时,维护一个最大路径和变量。定义函数 `dfs(self, root)` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 + +计算的结果可能的情况有 2 种: + +- 经过空节点的最大贡献值等于 `0`。 + +- 经过非空节点的最大贡献值等于 当前节点值 + 左右子节点的最大贡献值中较大的一个。 + +在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。 + +最终 `max_sum` 即为答案。 + +## 代码 + +```python +class Solution: + def __init__(self): + self.max_sum = float('-inf') + + def dfs(self, root): + if not root: + return 0 + left_max = max(self.dfs(root.left), 0) + right_max = max(self.dfs(root.right), 0) + self.max_sum = max(self.max_sum, root.val + left_max + right_max) + return root.val + max(left_max, right_max) + + def maxPathSum(self, root: TreeNode) -> int: + self.dfs(root) + return self.max_sum +``` + diff --git a/docs/solutions/LCR/jJ0w9p.md b/docs/solutions/LCR/jJ0w9p.md new file mode 100644 index 00000000..b0225cc5 --- /dev/null +++ b/docs/solutions/LCR/jJ0w9p.md @@ -0,0 +1,37 @@ +# [LCR 072. x 的平方根](https://leetcode.cn/problems/jJ0w9p/) + +- 标签:数学、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 072. x 的平方根 - 力扣](https://leetcode.cn/problems/jJ0w9p/) + +## 题目大意 + +要求:实现 `int sqrt(int x)` 函数。计算并返回 `x` 的平方根(只保留整数部分),其中 `x` 是非负整数。 + +## 解题思路 + +因为求解的是 x 开方的整数部分。所以我们可以从 0~x 的范围进行遍历,找到 k^2 <= x 的最大结果。 + +为了减少时间复杂度,使用二分查找的方式来搜索答案。 + +## 代码 + +```python +class Solution: + def mySqrt(self, x: int) -> int: + left = 0 + right = x + ans = -1 + while left <= right: + mid = (left + right) // 2 + if mid * mid <= x: + ans = mid + left = mid + 1 + else: + right = mid - 1 + return ans +``` + diff --git a/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md b/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md new file mode 100644 index 00000000..e212e650 --- /dev/null +++ b/docs/solutions/LCR/ji-qi-ren-de-yun-dong-fan-wei-lcof.md @@ -0,0 +1,83 @@ +# [LCR 130. 衣橱整理](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) + +- 标签:深度优先搜索、广度优先搜索、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 130. 衣橱整理 - 力扣](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) + +## 题目大意 + +**描述**:有一个 `m * n` 大小的方格,坐标从 `(0, 0)` 到 `(m - 1, n - 1)`。一个机器人从 `(0, 0)` 处的格子开始移动,每次可以向上、下、左、右移动一格(不能移动到方格外),也不能移动到行坐标和列坐标的数位之和大于 `k` 的格子。现在给定 `3` 个整数 `m`、`n`、`k`。 + +**要求**:计算并输出该机器人能够达到多少个格子。 + +**说明**: + +- $1 \le n, m \le 100$。 +- $0 \le k \le 20$。 + +**示例**: + +- 示例 1: + +```python +输入:m = 2, n = 3, k = 1 +输出:3 +``` + +- 示例 2: + +```python +输入:m = 3, n = 1, k = 0 +输出:1 +``` + +## 解题思路 + +### 思路 1:广度优先搜索 + +先定义一个计算数位和的方法 `digitsum`,该方法输入一个整数,返回该整数各个数位的总和。 + +然后我们使用广度优先搜索方法,具体步骤如下: + +- 将 `(0, 0)` 加入队列 `queue` 中。 +- 当队列不为空时,每次将队首坐标弹出,加入访问集合 `visited` 中。 +- 再将满足行列坐标的数位和不大于 `k` 的格子位置加入到队列中,继续弹出队首位置。 +- 直到队列为空时停止。输出访问集合的长度。 + +### 思路 1:代码 + +```python +import collections + +class Solution: + def digitsum(self, n: int): + ans = 0 + while n: + ans += n % 10 + n //= 10 + return ans + + def movingCount(self, m: int, n: int, k: int) -> int: + queue = collections.deque([(0, 0)]) + visited = set() + + while queue: + x, y = queue.popleft() + if (x, y) not in visited and self.digitsum(x) + self.digitsum(y) <= k: + visited.add((x, y)) + for dx, dy in [(1, 0), (0, 1)]: + nx = x + dx + ny = y + dy + if 0 <= nx < m and 0 <= ny < n: + queue.append((nx, ny)) + return len(visited) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$。其中 $m$ 为方格的行数,$n$ 为方格的列数。 +- **空间复杂度**:$O(m \times n)$。 + diff --git a/docs/solutions/LCR/jian-sheng-zi-lcof.md b/docs/solutions/LCR/jian-sheng-zi-lcof.md new file mode 100644 index 00000000..a32ed232 --- /dev/null +++ b/docs/solutions/LCR/jian-sheng-zi-lcof.md @@ -0,0 +1,45 @@ +# [LCR 131. 砍竹子 I](https://leetcode.cn/problems/jian-sheng-zi-lcof/) + +- 标签:数学、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 131. 砍竹子 I - 力扣](https://leetcode.cn/problems/jian-sheng-zi-lcof/) + +## 题目大意 + +给定一根长度为 `n` 的绳子,将绳子剪成整数长度的 `m` 段,每段绳子长度即为 `k[0]`、`k[1]`、...、`k[m - 1]`。 + +要求:计算出 `k[0] * k[1] * ... * k[m - 1]` 可能的最大乘积。 + +## 解题思路 + +可以使用动态规划求解。 + +定义状态 `dp[i]` 为:拆分长度为 `i` 的绳子,可以获得的最大乘积为 `dp[i]`。 + +将 `j` 从 `1` 遍历到 `i - 1`,通过两种方式得到 `dp[i]`: + +- `(i - j) * j` ,直接将长度为 `i` 的绳子分割为 `i - j` 和 `j`,获取两者乘积。 +- `dp[i - j] * j`,将长度为 `i`的绳子 中的 `i - j` 部分拆分,得到 `dp[i - j]`,和 `j` ,获取乘积。 + +则 `dp[i]` 取两者中的最大值。遍历 `j`,得到 `dp[i]` 的最大值。 + +则状态转移方程为:`dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j)`。 + +最终输出 `dp[n]`。 + +## 代码 + +```python +class Solution: + def cuttingRope(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + dp[1] = 1 + for i in range(2, n + 1): + for j in range(1, i): + dp[i] = max(dp[i], dp[i - j] * j, (i - j) * j) + return dp[n] +``` + diff --git a/docs/solutions/LCR/ju-zhen-zhong-de-lu-jing-lcof.md b/docs/solutions/LCR/ju-zhen-zhong-de-lu-jing-lcof.md new file mode 100644 index 00000000..700ce2e8 --- /dev/null +++ b/docs/solutions/LCR/ju-zhen-zhong-de-lu-jing-lcof.md @@ -0,0 +1,57 @@ +# [LCR 129. 字母迷宫](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/) + +- 标签:数组、回溯、矩阵 +- 难度:中等 + +## 题目链接 + +- [LCR 129. 字母迷宫 - 力扣](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/) + +## 题目大意 + +给定一个 `m * n` 大小的二维字符矩阵 `board` 和一个字符串单词 `word`。如果 `word` 存在于网格中,返回 `True`,否则返回 `False`。 + +- 单词必须按照字母顺序通过上下左右相邻的单元格字母构成。且同一个单元格内的字母不允许被重复使用。 + +## 解题思路 + +回溯算法在二维矩阵 `board` 中按照上下左右四个方向递归搜索。设函数 `backtrack(i, j, index)` 表示从 `board[i][j]` 出发,能否搜索到单词字母 `word[index]`,以及 `index` 位置之后的后缀子串。如果能搜索到,则返回 `True`,否则返回 `False`。`backtrack(i, j, index)` 执行步骤如下: + +- 如果 $board[i][j] = word[index]$,而且 `index` 已经到达 `word` 字符串末尾,则返回 `True`。 +- 如果 $board[i][j] = word[index]$,而且 `index` 未到达 `word` 字符串末尾,则遍历当前位置的所有相邻位置。如果从某个相邻位置能搜索到后缀子串,则返回 `True`,否则返回 `False`。 +- 如果 $board[i][j] \ne word[index]$,则当前字符不匹配,返回 `False`。 + +## 代码 + +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + rows = len(board) + if rows == 0: + return False + cols = len(board[0]) + visited = [[False for _ in range(cols)] for _ in range(rows)] + + def backtrack(i, j, index): + if index == len(word) - 1: + return board[i][j] == word[index] + + if board[i][j] == word[index]: + visited[i][j] = True + for direct in directs: + new_i = i + direct[0] + new_j = j + direct[1] + if 0 <= new_i < rows and 0 <= new_j < cols and visited[new_i][new_j] == False: + if backtrack(new_i, new_j, index + 1): + return True + visited[i][j] = False + return False + + for i in range(rows): + for j in range(cols): + if backtrack(i, j, 0): + return True + return False +``` + diff --git a/docs/solutions/LCR/kLl5u1.md b/docs/solutions/LCR/kLl5u1.md new file mode 100644 index 00000000..c1e0844c --- /dev/null +++ b/docs/solutions/LCR/kLl5u1.md @@ -0,0 +1,37 @@ +# [LCR 006. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/kLl5u1/) + +- 标签:数组、双指针、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 006. 两数之和 II - 输入有序数组 - 力扣](https://leetcode.cn/problems/kLl5u1/) + +## 题目大意 + +给定一个升序数组:`numbers` 和一个目标值 `target`。 + +要求:从数组中找出满足相加之和等于 `target` 的两个数,并返回两个数在数组中下的标值。 + +## 解题思路 + +因为数组是有序的,所以我们可以使用两个指针 low,high。low 指向数组开始较小元素位置,high 指向数组较大元素位置。判断两个位置上的元素和,如果和等于目标值,则返回两个元素位置。如果和大于目标值,则 high 左移,继续检测。如果和小于目标值,则 low 右移,继续检测。直到 low 和 high 移动到相同位置停止检测。若最终仍没找到,则返回 [0, 0]。 + +## 代码 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + low = 0 + high = len(numbers) - 1 + while low < high: + total = numbers[low] + numbers[high] + if total == target: + return [low, high] + elif total < target: + low += 1 + else: + high -= 1 + return [0, 0] +``` + diff --git a/docs/solutions/LCR/kTOapQ.md b/docs/solutions/LCR/kTOapQ.md new file mode 100644 index 00000000..e15e1860 --- /dev/null +++ b/docs/solutions/LCR/kTOapQ.md @@ -0,0 +1,52 @@ +# [LCR 055. 二叉搜索树迭代器](https://leetcode.cn/problems/kTOapQ/) + +- 标签:栈、树、设计、二叉搜索树、二叉树、迭代器 +- 难度:中等 + +## 题目链接 + +- [LCR 055. 二叉搜索树迭代器 - 力扣](https://leetcode.cn/problems/kTOapQ/) + +## 题目大意 + +要求:实现一个二叉搜索树的迭代器 `BSTIterator`。表示一个按中序遍历二叉搜索树(BST)的迭代器: + +- `def __init__(self, root: TreeNode):`:初始化 `BSTIterator` 类的一个对象,会给出二叉搜索树的根节点。 +- `def hasNext(self) -> bool:`:如果向右指针遍历存在数字,则返回 `True`,否则返回 `False`。 +- `def next(self) -> int:`:将指针向右移动,返回指针处的数字。 + +## 解题思路 + +中序遍历的顺序是:左、根、右。我们使用一个栈来保存节点,以便于迭代的时候取出对应节点。 + +- 初始的遍历当前节点的左子树,将其路径上的节点存储到栈中。 +- 调用 next 方法的时候,从栈顶取出节点,因为之前已经将路径上的左子树全部存入了栈中,所以此时该节点的左子树为空,这时候取出节点右子树,再将右子树的左子树进行递归遍历,并将其路径上的节点存储到栈中。 +- 调用 hasNext 的方法的时候,直接判断栈中是否有值即可。 + +## 代码 + +```python +class BSTIterator: + + def __init__(self, root: TreeNode): + self.stack = [] + self.in_order(root) + + + def in_order(self, node): + while node: + self.stack.append(node) + node = node.left + + + def next(self) -> int: + node = self.stack.pop() + if node.right: + self.in_order(node.right) + return node.val + + + def hasNext(self) -> bool: + return len(self.stack) != 0 +``` + diff --git a/docs/solutions/LCR/lMSNwu.md b/docs/solutions/LCR/lMSNwu.md new file mode 100644 index 00000000..506ee06d --- /dev/null +++ b/docs/solutions/LCR/lMSNwu.md @@ -0,0 +1,58 @@ +# [LCR 025. 两数相加 II](https://leetcode.cn/problems/lMSNwu/) + +- 标签:栈、链表、数学 +- 难度:中等 + +## 题目链接 + +- [LCR 025. 两数相加 II - 力扣](https://leetcode.cn/problems/lMSNwu/) + +## 题目大意 + +给定两个非空链表的头节点 `l1` 和 `l2` 来代表两个非负整数。数字最高位位于链表开始位置。每个节点只储存一位数字。除了数字 `0` 之外,这两个链表代表的数字都不会以 `0` 开头。 + +要求:将这两个数相加会返回一个新的链表。 + +## 解题思路 + +链表中最高位位于链表开始位置,最低位位于链表结束位置。这与我们做加法的数位顺序是相反的。为了将链表逆序,从而从低位开始处理数位,我们可以借用两个栈:将链表中所有数字分别压入两个栈中,再依次取出相加。 + +同时,在相加的时候,还要考虑进位问题。具体步骤如下: + +- 将链表 `l1` 中所有节点值压入 `stack1` 栈中,再将链表 `l2` 中所有节点值压入 `stack2` 栈中。 +- 使用 `res` 存储新的结果链表,一开始指向 `None`,`carry` 记录进位。 +- 如果 `stack1` 或 `stack2` 不为空,或着进位 `carry` 不为 `0`,则: + - 从 `stack1` 中取出栈顶元素 `num1`,如果 `stack1` 为空,则 `num1 = 0`。 + - 从 `stack2` 中取出栈顶元素 `num2`,如果 `stack2` 为空,则 `num2 = 0`。 + - 计算相加结果,并计算进位。 + - 建立新节点,存储进位后余下的值,并令其指向 `res`。 + - `res` 指向新节点,继续判断。 +- 如果 `stack1`、`stack2` 都为空,并且进位 `carry` 为 `0`,则输出 `res`。 + +## 代码 + +```python +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + stack1, stack2 = [], [] + while l1: + stack1.append(l1.val) + l1 = l1.next + while l2: + stack2.append(l2.val) + l2 = l2.next + + res = None + carry = 0 + while stack1 or stack2 or carry != 0: + num1 = stack1.pop() if stack1 else 0 + num2 = stack2.pop() if stack2 else 0 + cur_sum = num1 + num2 + carry + carry = cur_sum // 10 + cur_sum %= 10 + cur_node = ListNode(cur_sum) + cur_node.next = res + res = cur_node + return res +``` + diff --git a/docs/solutions/LCR/li-wu-de-zui-da-jie-zhi-lcof.md b/docs/solutions/LCR/li-wu-de-zui-da-jie-zhi-lcof.md new file mode 100644 index 00000000..c0797148 --- /dev/null +++ b/docs/solutions/LCR/li-wu-de-zui-da-jie-zhi-lcof.md @@ -0,0 +1,39 @@ +# [LCR 166. 珠宝的最高价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/) + +- 标签:数组、动态规划、矩阵 +- 难度:中等 + +## 题目链接 + +- [LCR 166. 珠宝的最高价值 - 力扣](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/) + +## 题目大意 + +给定一个 `m * n` 大小的二维矩阵 `grid` 代表棋盘,棋盘的每一格都放有一个礼物,每个礼物有一定的价值(价值大于 `0`)。`grid[i][j]` 表示棋盘第 `i` 行第 `j` 列的礼物价值。我们可以从左上角的格子开始拿礼物,每次只能向右或者向下移动一格,直到到达棋盘的右下角。 + +要求:计算出最多能拿多少价值的礼物。 + +## 解题思路 + +可以用动态规划求解,设 `dp[i][j]` 是从 `(0, 0)` 到 `(i - 1, j - 1)` 能得礼物的最大价值。 + +显然 `dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1]) + grid[i][j]`。 + +因为是自上而下递推 `dp[i-1][j]` 可以用 `dp[j]` 来表示,所以也可以将二维改为一位。状态转移公式为: `dp[j] = max(dp[j], dp[j - 1]) + grid[i][j]`。 + +## 代码 + +```python +class Solution: + def maxValue(self, grid: List[List[int]]) -> int: + if not grid: + return 0 + size_m = len(grid) + size_n = len(grid[0]) + dp = [0 for _ in range(size_n + 1)] + for i in range(size_m): + for j in range(size_n): + dp[j + 1] = max(dp[j], dp[j + 1]) + grid[i][j] + return dp[size_n] +``` + diff --git a/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md b/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md new file mode 100644 index 00000000..575d1b0c --- /dev/null +++ b/docs/solutions/LCR/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof.md @@ -0,0 +1,38 @@ +# [LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) + +- 标签:链表、双指针 +- 难度:简单 + +## 题目链接 + +- [LCR 140. 训练计划 II - 力扣](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) + +## 题目大意 + +给定一个链表的头节点 `head`,以及一个整数 `k`。 + +要求返回链表的倒数第 `k` 个节点。 + +## 解题思路 + +常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,返回该位置上的节点。 + +如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `k` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `k` 个节点位置。返回该该位置上的节点即可。 + +## 代码 + +```python +class Solution: + def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: + slow = head + fast = head + for _ in range(k): + if fast == None: + return fast + fast = fast.next + while fast: + slow = slow.next + fast = fast.next + return slow +``` + diff --git a/docs/solutions/LCR/lian-xu-zi-shu-zu-de-zui-da-he-lcof.md b/docs/solutions/LCR/lian-xu-zi-shu-zu-de-zui-da-he-lcof.md new file mode 100644 index 00000000..61218216 --- /dev/null +++ b/docs/solutions/LCR/lian-xu-zi-shu-zu-de-zui-da-he-lcof.md @@ -0,0 +1,39 @@ +# [LCR 161. 连续天数的最高销售额](https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) + +- 标签:数组、分治、动态规划 +- 难度:简单 + +## 题目链接 + +- [LCR 161. 连续天数的最高销售额 - 力扣](https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) + +## 题目大意 + +给定一个整数数组 `nums` 。 + +要求:找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和,要求时间复杂度为 `O(n)`。 + +## 解题思路 + +动态规划的方法,关键点是要找到状态转移方程。 + +假设 f(i) 表示第 i 个数结尾的「连续子数组的最大和」,那么 $max_{0 < i \le n-1} {f(i)} = max(f(i-1) + nums[i], nums[i])$ + +即将之前累加和加上当前值与当前值做比较。 + +- 如果将之前累加和加上当前值 > 当前值,那么加上当前值。 +- 如果将之前累加和加上当前值 < 当前值,那么 $f(i) = nums[i]$。 + +## 代码 + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + max_ans = nums[0] + ans = 0 + for num in nums: + ans = max(ans + num, num) + max_ans = max(max_ans, ans) + return max_ans +``` + diff --git a/docs/solutions/LCR/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof.md b/docs/solutions/LCR/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof.md new file mode 100644 index 00000000..c4227218 --- /dev/null +++ b/docs/solutions/LCR/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof.md @@ -0,0 +1,48 @@ +# [LCR 171. 训练计划 V](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) + +- 标签:哈希表、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [LCR 171. 训练计划 V - 力扣](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) + +## 题目大意 + +给定 A、B 两个链表,判断两个链表是否相交,返回相交的起始点。如果不相交,则返回 None。 + +比如:链表 A 为 [4, 1, 8, 4, 5],链表 B 为 [5, 0, 1, 8, 4, 5]。则如下图所示,两个链表相交的起始节点为 8,则输出结果为 8。 + +![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) + +## 解题思路 + +如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 A 的长度为 m、链表 B 的长度为 n,他们的相交序列有 k 个,则相交情况可以如下如所示: + +![](https://qcdn.itcharge.cn/images/20210401113538.png) + +现在问题是如何找到 m-k 或者 n-k 的位置。 + +考虑将链表 A 的末尾拼接上链表 B,链表 B 的末尾拼接上链表 A。 + +然后使用两个指针 pA 、PB,分别从链表 A、链表 B 的头节点开始遍历,如果走到共同的节点,则返回该节点。 + +否则走到两个链表末尾,返回 None。 + +![](https://qcdn.itcharge.cn/images/20210401114100.png) + +## 代码 + +```python +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + if not headA or not headB: + return None + pA = headA + pB = headB + while pA != pB: + pA = pA.next if pA else headB + pB = pB.next if pB else headA + return pA +``` + diff --git a/docs/solutions/LCR/lwyVBB.md b/docs/solutions/LCR/lwyVBB.md new file mode 100644 index 00000000..a4da5c53 --- /dev/null +++ b/docs/solutions/LCR/lwyVBB.md @@ -0,0 +1,50 @@ +# [LCR 034. 验证外星语词典](https://leetcode.cn/problems/lwyVBB/) + +- 标签:数组、哈希表、字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 034. 验证外星语词典 - 力扣](https://leetcode.cn/problems/lwyVBB/) + +## 题目大意 + +给定一组用外星语书写的单词字符串数组 `words`,以及表示外星字母表的顺序的字符串 `order` 。 + +要求:判断 `words` 中的单词是否都是按照 `order` 来排序的。如果是,则返回 `True`,否则返回 `False`。 + +## 解题思路 + +如果所有单词是按照 `order` 的规则升序排列,则所有单词都符合规则。而判断所有单词是升序排列,只需要两两比较相邻的单词即可。所以我们可以先用哈希表存储所有字母的顺序,然后对所有相邻单词进行两两比较,如果最终是升序排列,则符合要求。具体步骤如下: + +- 使用哈希表 `order_map` 存储字母的顺序。 +- 遍历单词数组 `words`,比较相邻单词 `word1` 和 `word2` 中所有字母在 `order_map` 中的下标,看是否满足 `word1 <= word2`。 +- 如果全部满足,则返回 `True`。如果有不满足的情况,则直接返回 `False`。 + +## 代码 + +```python +class Solution: + def isAlienSorted(self, words: List[str], order: str) -> bool: + order_map = dict() + for i in range(len(order)): + order_map[order[i]] = i + for i in range(len(words) - 1): + word1 = words[i] + word2 = words[i + 1] + + flag = True + + for j in range(min(len(word1), len(word2))): + if word1[j] != word2[j]: + if order_map[word1[j]] > order_map[word2[j]]: + return False + else: + flag = False + break + + if flag and len(word1) > len(word2): + return False + return True +``` + diff --git a/docs/solutions/LCR/ms70jA.md b/docs/solutions/LCR/ms70jA.md new file mode 100644 index 00000000..b5c1255d --- /dev/null +++ b/docs/solutions/LCR/ms70jA.md @@ -0,0 +1,78 @@ +# [LCR 067. 数组中两个数的最大异或值](https://leetcode.cn/problems/ms70jA/) + +- 标签:位运算、字典树、数组、哈希表 +- 难度:中等 + +## 题目链接 + +- [LCR 067. 数组中两个数的最大异或值 - 力扣](https://leetcode.cn/problems/ms70jA/) + +## 题目大意 + +给定一个整数数组 `nums`。 + +要求:返回 `num[i] XOR nums[j]` 的最大运算结果。其中 `0 ≤ i ≤ j < n`。 + +## 解题思路 + +最直接的想法暴力求解。两层循环计算两两之间的异或结果,记录并更新最大异或结果。 + +更好的做法可以减少一重循环。首先,要取得异或结果的最大值,那么从二进制的高位到低位,尽可能的让每一位异或结果都为 `1`。 + +将数组中所有数字的二进制形式从高位到低位依次存入字典树中。然后是利用异或运算交换律:如果 `a ^ b = max` 成立,那么 `a ^ max = b` 与 `b ^ max = a` 均成立。这样当我们知道 `a` 和 `max` 时,可以通过交换律求出 `b`。`a` 是我们遍历的每一个数,`max` 是我们想要尝试的最大值,从 `111111...` 开始,从高位到低位依次填 `1`。 + +对于 `a` 和 `max`,如果我们所求的 `b` 也在字典树中,则表示 `max` 是可以通过 `a` 和 `b` 得到的,那么 `max` 就是所求最大的异或。如果 `b` 不在字典树中,则减小 `max` 值继续判断,或者继续查询下一个 `a`。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, num: int, max_bit: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for i in range(max_bit, -1, -1): + bit = num >> i & 1 + if bit not in cur.children: + cur.children[bit] = Trie() + cur = cur.children[bit] + cur.isEnd = True + + def search(self, num: int, max_bit: int) -> int: + """ + Returns if the word is in the trie. + """ + cur = self + res = 0 + for i in range(max_bit, -1, -1): + bit = num >> i & 1 + if 1 - bit not in cur.children: + res = res * 2 + cur = cur.children[bit] + else: + res = res * 2 + 1 + cur = cur.children[1 - bit] + return res + +class Solution: + def findMaximumXOR(self, nums: List[int]) -> int: + trie_tree = Trie() + max_bit = len(format(max(nums), 'b')) - 1 + ans = 0 + for num in nums: + trie_tree.insert(num, max_bit) + ans = max(ans, trie_tree.search(num, max_bit)) + + return ans +``` + diff --git a/docs/solutions/LCR/nZZqjQ.md b/docs/solutions/LCR/nZZqjQ.md new file mode 100644 index 00000000..e9d09e54 --- /dev/null +++ b/docs/solutions/LCR/nZZqjQ.md @@ -0,0 +1,51 @@ +# [LCR 073. 爱吃香蕉的狒狒](https://leetcode.cn/problems/nZZqjQ/) + +- 标签:数组、二分查找 +- 难度:中等 + +## 题目链接 + +- [LCR 073. 爱吃香蕉的狒狒 - 力扣](https://leetcode.cn/problems/nZZqjQ/) + +## 题目大意 + +给定一个数组 `piles` 代表 `n` 堆香蕉。其中 `piles[i]` 表示第 `i` 堆香蕉的个数。再给定一个整数 `h` ,表示最多可以在 `h` 小时内吃完所有香蕉。狒狒决定以速度每小时 `k`(未知)根的速度吃香蕉。每一个小时,她将选择其中一堆香蕉,从中吃掉 `k` 根。如果这堆香蕉少于 `k` 根,狒狒将在这一小时吃掉这堆的所有香蕉,并且这一小时不会再吃其他堆的香蕉。 + +要求:返回狒狒可以在 `h` 小时内吃掉所有香蕉的最小速度 `k`(`k` 为整数)。 + +## 解题思路 + +先来看 `k` 的取值范围,因为 `k` 是整数,且速度肯定不能为 `0` 吧,为 `0` 的话就永远吃不完了。所以`k` 的最小值可以取 `1`。`k` 的最大值根香蕉中最大堆的香蕉个数有关,因为 `1` 个小时内只能选择一堆吃,不能再吃其他堆的香蕉,则 `k` 的最大值取香蕉堆的最大值即可。即 `k` 的最大值为 `max(piles)`。 + +我们的目标是求出 `h` 小时内吃掉所有香蕉的最小速度 `k`。现在有了区间「`[1, max(piles)]`」,有了目标「最小速度 `k`」。接下来使用二分查找算法来查找「最小速度 `k`」。至于计算 `h` 小时内能否以 `k` 的速度吃完香蕉,我们可以再写一个方法 `canEat` 用于判断。如果能吃完就返回 `True`,不能吃完则返回 `False`。下面说一下算法的具体步骤。 + +- 使用两个指针 `left`、`right`。令 `left` 指向 `1`,`right` 指向 `max(piles)`。代表待查找区间为 `[left, right]` + +- 取两个节点中心位置 `mid`,判断是否能在 `h` 小时内以 `k` 的速度吃完香蕉。 + - 如果不能吃完,则将区间 `[left, mid]` 排除掉,继续在区间 `[mid + 1, right]` 中查找。 + - 如果能吃完,说明 `k` 还可以继续减小,则继续在区间 `[left, mid]` 中查找。 +- 当 `left == right` 时跳出循环,返回 `left`。 + +## 代码 + +```python +class Solution: + def canEat(self, piles, hour, speed): + time = 0 + for pile in piles: + time += (pile + speed - 1) // speed + return time <= hour + + def minEatingSpeed(self, piles: List[int], h: int) -> int: + left, right = 1, max(piles) + + while left < right: + mid = left + (right - left) // 2 + if not self.canEat(piles, h, mid): + left = mid + 1 + else: + right = mid + + return left +``` + diff --git a/docs/solutions/LCR/om3reC.md b/docs/solutions/LCR/om3reC.md new file mode 100644 index 00000000..5500ac16 --- /dev/null +++ b/docs/solutions/LCR/om3reC.md @@ -0,0 +1,54 @@ +# [LCR 108. 单词接龙](https://leetcode.cn/problems/om3reC/) + +- 标签:广度优先搜索、哈希表、字符串 +- 难度:困难 + +## 题目链接 + +- [LCR 108. 单词接龙 - 力扣](https://leetcode.cn/problems/om3reC/) + +## 题目大意 + +给定两个单词 `beginWord` 和 `endWord`,以及一个字典 `wordList`。找到从 `beginWord` 到 `endWord` 的最短转换序列中的单词数目。如果不存在这样的转换序列,则返回 0。 + +转换需要遵守的规则如下: + +- 每次转换只能改变一个字母。 +- 转换过程中的中间单词必须为字典中的单词。 + +## 解题思路 + +广度优先搜索。使用队列存储将要遍历的单词和单词数目。 + +从 `beginWord` 开始变换,把单词的每个字母都用 `a ~ z` 变换一次,变换后的单词是否是 `endWord`,如果是则直接返回。 + +否则查找变换后的词是否在 `wordList` 中。如果在 `wordList` 中找到就加入队列,找不到就输出 `0`。然后按照广度优先搜索的算法急需要遍历队列中的节点,直到所有单词都出队时结束。 + +## 代码 + +```python +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + if not wordList or endWord not in wordList: + return 0 + word_set = set(wordList) + if beginWord in word_set: + word_set.remove(beginWord) + + queue = collections.deque() + queue.append((beginWord, 1)) + while queue: + word, level = queue.popleft() + if word == endWord: + return level + + for i in range(len(word)): + for j in range(26): + new_word = word[:i] + chr(ord('a') + j) + word[i + 1:] + if new_word in word_set: + word_set.remove(new_word) + queue.append((new_word, level + 1)) + + return 0 +``` + diff --git a/docs/solutions/LCR/opLdQZ.md b/docs/solutions/LCR/opLdQZ.md new file mode 100644 index 00000000..95443742 --- /dev/null +++ b/docs/solutions/LCR/opLdQZ.md @@ -0,0 +1,45 @@ +# [LCR 056. 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/opLdQZ/) + +- 标签:树、深度优先搜索、广度优先搜索、二叉搜索树、哈希表、双指针、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 056. 两数之和 IV - 输入二叉搜索树 - 力扣](https://leetcode.cn/problems/opLdQZ/) + +## 题目大意 + +给定一个二叉搜索树的根节点 `root` 和一个整数 `k`。 + +要求:判断该二叉搜索树是否存在两个节点值的和等于 `k`。如果存在,则返回 `True`,不存在则返回 `False`。 + +## 解题思路 + +二叉搜索树中序遍历的结果是从小到大排序,所以我们可以先对二叉搜索树进行中序遍历,将中序遍历结果存储到列表中。再使用左右指针查找节点值和为 `k` 的两个节点。 + +## 代码 + +```python +class Solution: + def inOrder(self, root, nums): + if not root: + return + self.inOrder(root.left, nums) + nums.append(root.val) + self.inOrder(root.right, nums) + + def findTarget(self, root: TreeNode, k: int) -> bool: + nums = [] + self.inOrder(root, nums) + left, right = 0, len(nums) - 1 + while left < right: + sum = nums[left] + nums[right] + if sum == k: + return True + elif sum < k: + left += 1 + else: + right -= 1 + return False +``` + diff --git a/docs/solutions/LCR/pOCWxh.md b/docs/solutions/LCR/pOCWxh.md new file mode 100644 index 00000000..ad9714bd --- /dev/null +++ b/docs/solutions/LCR/pOCWxh.md @@ -0,0 +1,47 @@ +# [LCR 047. 二叉树剪枝](https://leetcode.cn/problems/pOCWxh/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 047. 二叉树剪枝 - 力扣](https://leetcode.cn/problems/pOCWxh/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`,树的每个节点值要么是 `0`,要么是 `1`。 + +要求:剪除该二叉树中所有节点值为 `0` 的子树。 + +- 节点 `node` 的子树为: `node` 本身,以及所有 `node` 的后代。 + +## 解题思路 + +定义辅助方法 `containsOnlyZero(root)` 递归判断以 `root` 为根的子树中是否只包含 `0`。如果子树中只包含 `0`,则返回 `True`。如果子树中含有 `1`,则返回 `False`。当 `root` 为空时,也返回 `True`。 + +然后递归遍历二叉树,判断当前节点 `root` 是否只包含 `0`。如果只包含 `0`,则将其置空,返回 `None`。否则递归遍历左右子树,并设置对应的左右指针。 + +最后返回根节点 `root`。 + +## 代码 + +```python +class Solution: + def containsOnlyZero(self, root: TreeNode): + if not root: + return True + if root.val == 1: + return False + return self.containsOnlyZero(root.left) and self.containsOnlyZero(root.right) + + def pruneTree(self, root: TreeNode) -> TreeNode: + if not root: + return root + if self.containsOnlyZero(root): + return None + + root.left = self.pruneTree(root.left) + root.right = self.pruneTree(root.right) + return root +``` + diff --git a/docs/solutions/LCR/ping-heng-er-cha-shu-lcof.md b/docs/solutions/LCR/ping-heng-er-cha-shu-lcof.md new file mode 100644 index 00000000..006cfa56 --- /dev/null +++ b/docs/solutions/LCR/ping-heng-er-cha-shu-lcof.md @@ -0,0 +1,43 @@ +# [LCR 176. 判断是否为平衡二叉树](https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:简单 + +## 题目链接 + +- [LCR 176. 判断是否为平衡二叉树 - 力扣](https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:判断该树是不是平衡二叉树。如果是平衡二叉树,返回 `True`,否则,返回 `False`。 + +- 平衡二叉树:任意节点的左右子树深度不超过 `1`。 + +## 解题思路 + +递归遍历二叉树。先递归遍历左右子树,判断左右子树是否平衡,再判断以当前节点为根节点的左右子树是否平衡。 + +如果遍历的子树是平衡的,则返回它的高度,否则返回 `-1`。 + +只要出现不平衡的子树,则该二叉树一定不是平衡二叉树。 + +## 代码 + +```python +class Solution: + def isBalanced(self, root: TreeNode) -> bool: + def height(root: TreeNode) -> int: + if root == None: + return False + leftHeight = height(root.left) + rightHeight = height(root.right) + if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1: + return -1 + else: + return max(leftHeight, rightHeight) + 1 + + return height(root) >= 0 +``` + diff --git a/docs/solutions/LCR/qIsx9U.md b/docs/solutions/LCR/qIsx9U.md new file mode 100644 index 00000000..a49080c3 --- /dev/null +++ b/docs/solutions/LCR/qIsx9U.md @@ -0,0 +1,82 @@ +# [LCR 041. 数据流中的移动平均值](https://leetcode.cn/problems/qIsx9U/) + +- 标签:设计、队列、数组、数据流 +- 难度:简单 + +## 题目链接 + +- [LCR 041. 数据流中的移动平均值 - 力扣](https://leetcode.cn/problems/qIsx9U/) + +## 题目大意 + +**描述**:给定一个整数数据流和一个窗口大小 `size`。 + +**要求**:根据滑动窗口的大小,计算滑动窗口里所有数字的平均值。要求实现 `MovingAverage` 类: + +- `MovingAverage(int size)`:用窗口大小 `size` 初始化对象。 +- `double next(int val)`:成员函数 `next` 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 `size` 个值的移动平均值,即滑动窗口里所有数字的平均值。 + +**说明**: + +- $1 \le size \le 1000$。 +- $-10^5 \le val \le 10^5$。 +- 最多调用 `next` 方法 $10^4$ 次。 + +**示例**: + +- 示例 1: + +```python +输入: +inputs = ["MovingAverage", "next", "next", "next", "next"] +inputs = [[3], [1], [10], [3], [5]] +输出: +[null, 1.0, 5.5, 4.66667, 6.0] + +解释: +MovingAverage movingAverage = new MovingAverage(3); +movingAverage.next(1); // 返回 1.0 = 1 / 1 +movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2 +movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3 +movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3 +``` + +## 解题思路 + +### 思路 1:队列 + +1. 使用队列保存滑动窗口的元素,并记录对应窗口大小和元素和。 +2. 当队列长度小于窗口大小的时候,直接向队列中添加元素,并记录当前窗口中的元素和。 +3. 当队列长度等于窗口大小的时候,先将队列头部元素弹出,再添加元素,并记录当前窗口中的元素和。 +4. 然后根据元素和和队列中元素个数计算出平均值。 + +### 思路 1:代码 + +```python +class MovingAverage: + + def __init__(self, size: int): + """ + Initialize your data structure here. + """ + self.queue = [] + self.size = size + self.sum = 0 + + + def next(self, val: int) -> float: + if len(self.queue) < self.size: + self.queue.append(val) + else: + if self.queue: + self.sum -= self.queue[0] + self.queue.pop(0) + self.queue.append(val) + self.sum += val + return self.sum / len(self.queue) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。初始化方法和每次调用 `next` 方法的时间复杂度都是 $O(1)$。 +- **空间复杂度**:$O(size)$。其中 $size$ 就是给定的滑动窗口的大小。 \ No newline at end of file diff --git a/docs/solutions/LCR/qJnOS7.md b/docs/solutions/LCR/qJnOS7.md new file mode 100644 index 00000000..a447f25a --- /dev/null +++ b/docs/solutions/LCR/qJnOS7.md @@ -0,0 +1,51 @@ +# [LCR 095. 最长公共子序列](https://leetcode.cn/problems/qJnOS7/) + +- 标签:字符串、动态规划 +- 难度:中等 + +## 题目链接 + +- [LCR 095. 最长公共子序列 - 力扣](https://leetcode.cn/problems/qJnOS7/) + +## 题目大意 + +给定两个字符串 `text1` 和 `text2`。 + +要求:返回两个字符串的最长公共子序列的长度。如果不存在公共子序列,则返回 `0`。 + +- 子序列:原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 +- 公共子序列:两个字符串所共同拥有的子序列。 + +## 解题思路 + +用动态规划来做。 + +动态规划的状态 `dp[i][j]` 表示为:前 `i` 个字符组成的字符串 `str1` 与前 `j` 个字符组成的字符串 `str2` 的最长公共子序列长度为 `dp[i][j]`。 + +遍历字符串 `text1` 和 `text2`,则状态转移方程为: + +- 如果 `text1[i - 1] == text2[j - 1]`,则找到了一个公共元素,则 `dp[i][j] = dp[i - 1][j - 1] + 1`。 +- 如果 `text1[i - 1] != text2[j - 1]`,则 `dp[i][j]` 需要考虑两种情况,取其中最大的那种: + - `text1` 前 `i - 1` 个字符组成的字符串 `str1` 与 `text2` 前 `j` 个字符组成的 `str2` 的最长公共子序列长度,即 `dp[i - 1][j]`。 + - `text1` 前 `i` 个字符组成的字符串 `str1` 与 `text2` 前 `j - 1` 个字符组成的 `str2` 的最长公共子序列长度,即 `dp[i][j - 1]`。 + +最后输出 `dp[sise1][size2]` 即可,`size1`、`size2` 分别为 `text1`、`text2` 的字符串长度。 + +## 代码 + +```python +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + size1 = len(text1) + size2 = len(text2) + dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)] + for i in range(1, size1 + 1): + for j in range(1, size2 + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[size1][size2] +``` + diff --git a/docs/solutions/LCR/qing-wa-tiao-tai-jie-wen-ti-lcof.md b/docs/solutions/LCR/qing-wa-tiao-tai-jie-wen-ti-lcof.md new file mode 100644 index 00000000..6f261465 --- /dev/null +++ b/docs/solutions/LCR/qing-wa-tiao-tai-jie-wen-ti-lcof.md @@ -0,0 +1,47 @@ +# [LCR 127. 跳跃训练](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) + +- 标签:记忆化搜索、数学、动态规划 +- 难度:简单 + +## 题目链接 + +- [LCR 127. 跳跃训练 - 力扣](https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/) + +## 题目大意 + +一直青蛙一次可以跳上 `1` 级台阶,也可以跳上 `2` 级台阶。 + +要求:求该青蛙跳上 `n` 级台阶共有多少中跳法。答案需要对 `1000000007` 取余。 + +## 解题思路 + +先来看一下规律: + +第 0 级台阶:1 种方法(比较特殊) + +第 1 级台阶:1 种方法(从 0 阶爬 1 阶) + +第 2 阶台阶:2 种方法(从 0 阶爬 2 阶,从 1 阶爬 1 阶) + +第 i 阶台阶:从第 i-1 阶台阶爬 1 阶,或者从第 i-2 阶台阶爬 2 阶。 + +则推出递推公式为: + +- 当 `n = 0` 时,`F(i) = 1`。 +- 当 `n > 0` 时,`F(i) = F(i-1) + F(i-2)`。 + +## 代码 + +```python +class Solution: + def numWays(self, n: int) -> int: + if n == 0: + return 1 + + f1, f2, f3 = 0, 1, 1 + for i in range(2, n + 1): + f1, f2 = f2, f3 + f3 = (f1 + f2) % 1000000007 + return f3 +``` + diff --git a/docs/solutions/LCR/qiu-12n-lcof.md b/docs/solutions/LCR/qiu-12n-lcof.md new file mode 100644 index 00000000..fa207a81 --- /dev/null +++ b/docs/solutions/LCR/qiu-12n-lcof.md @@ -0,0 +1,27 @@ +# [LCR 189. 设计机械累加器](https://leetcode.cn/problems/qiu-12n-lcof/) + +- 标签:位运算、递归、脑筋急转弯 +- 难度:中等 + +## 题目链接 + +- [LCR 189. 设计机械累加器 - 力扣](https://leetcode.cn/problems/qiu-12n-lcof/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:计算 `1 + 2 + ... + n`,并且不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。 + +## 解题思路 + +Python 中的逻辑运算最终返回的是最后一个非空值。比如 `3 and 2 and 'a'` 最终返回的是 `'a'`。利用这个特性可以递归求解。 + +## 代码 + +```python +class Solution: + def sumNums(self, n: int) -> int: + return n and n + self.sumNums(n - 1) +``` + diff --git a/docs/solutions/LCR/que-shi-de-shu-zi-lcof.md b/docs/solutions/LCR/que-shi-de-shu-zi-lcof.md new file mode 100644 index 00000000..4f0f8478 --- /dev/null +++ b/docs/solutions/LCR/que-shi-de-shu-zi-lcof.md @@ -0,0 +1,41 @@ +# [LCR 173. 点名](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) + +- 标签:位运算、数组、哈希表、数学、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 173. 点名 - 力扣](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) + +## 题目大意 + +给定一个 `n - 1` 个数的升序数组,数组中元素值都在 `0 ~ n - 1` 之间。 `nums` 中有且只有一个数字不在该数组中。 + +要求:找出这个缺失的数字。 + +## 解题思路 + +可以用二分查找解决。 + +对于中间值,判断元素值与索引值是否一致,如果一致,则说明缺失数字在索引的右侧。如果不一致,则可能为当前索引或者索引的左侧。 + +## 代码 + +```python +class Solution: + def missingNumber(self, nums: List[int]) -> int: + if len(nums) == 0: + return 0 + left, right = 0, len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + if mid == nums[mid]: + left = mid + 1 + else: + right = mid + if left == nums[left]: + return left + 1 + else: + return left +``` + diff --git a/docs/solutions/LCR/sfvd7V.md b/docs/solutions/LCR/sfvd7V.md new file mode 100644 index 00000000..8d0949b4 --- /dev/null +++ b/docs/solutions/LCR/sfvd7V.md @@ -0,0 +1,38 @@ +# [LCR 033. 字母异位词分组](https://leetcode.cn/problems/sfvd7V/) + +- 标签:数组、哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [LCR 033. 字母异位词分组 - 力扣](https://leetcode.cn/problems/sfvd7V/) + +## 题目大意 + +给定一个字符串数组 `strs`。 + +要求:将包含字母相同的字符串组合在一起,不需要考虑输出顺序。 + +## 解题思路 + +使用哈希表记录字母相同的字符串。对每一个字符串进行排序,按照 排序字符串:字母相同的字符串数组 的键值顺序进行存储。最终将哈希表的值转换为对应数组返回结果。 + +## 代码 + +```python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + str_dict = dict() + res = [] + for s in strs: + sort_s = str(sorted(s)) + if sort_s in str_dict: + str_dict[sort_s] += [s] + else: + str_dict[sort_s] = [s] + + for sort_s in str_dict: + res += [str_dict[sort_s]] + return res +``` + diff --git a/docs/solutions/LCR/shan-chu-lian-biao-de-jie-dian-lcof.md b/docs/solutions/LCR/shan-chu-lian-biao-de-jie-dian-lcof.md new file mode 100644 index 00000000..908cd592 --- /dev/null +++ b/docs/solutions/LCR/shan-chu-lian-biao-de-jie-dian-lcof.md @@ -0,0 +1,39 @@ +# [LCR 136. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) + +- 标签:链表 +- 难度:简单 + +## 题目链接 + +- [LCR 136. 删除链表的节点 - 力扣](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) + +## 题目大意 + +给定一个链表。 + +要求:删除链表中值为 `val` 的节点,并返回新的链表头节点。 + +## 解题思路 + +用两个指针 `prev` 和 `curr`。`prev` 指向前一节点和当前节点,`curr` 指向当前节点。从前向后遍历链表,遇到值为 `val` 的节点时,将 `prev` 指向当前节点的下一个节点,继续递归遍历。遇不到则更新 `prev` 指针,并继续遍历。 + +需要注意的是要删除的节点可能包含了头节点。我们可以考虑在遍历之前,新建一个头节点,让其指向原来的头节点。这样,最终如果删除的是头节点,则删除原头节点即可。返回结果的时候,可以直接返回新建头节点的下一位节点。 + +## 代码 + +```python +class Solution: + def deleteNode(self, head: ListNode, val: int) -> ListNode: + newHead = ListNode(0, head) + newHead.next = head + + prev, curr = newHead, head + while curr: + if curr.val == val: + prev.next = curr.next + else: + prev = curr + curr = curr.next + return newHead.next +``` + diff --git a/docs/solutions/LCR/shu-de-zi-jie-gou-lcof.md b/docs/solutions/LCR/shu-de-zi-jie-gou-lcof.md new file mode 100644 index 00000000..bd7c58da --- /dev/null +++ b/docs/solutions/LCR/shu-de-zi-jie-gou-lcof.md @@ -0,0 +1,51 @@ +# [LCR 143. 子结构判断](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 143. 子结构判断 - 力扣](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/) + +## 题目大意 + +给定两棵二叉树的根节点 `A`、`B`。 + +要求:判断 `B` 是不是 `A` 的子结构。(空树不是任意一棵树的子结构)。 + +- `B` 是 `A` 的子结构:`A` 中有出现和 `B` 相同的结构和节点值。 + +## 解题思路 + +深度优先搜索。 + +- 先判断特例,如果 `A`、`B` 都为空树,则直接返回 `False`。 +- 然后递归判断 `A`、`B` 是否相等。 + - 如果 `A`、`B` 相等,则返回 `True`。 + - 如果 `A`、`B` 不相等,则递归判断 `B` 是否是 `A` 的左子树的子结构,或者 `B` 是否是 `A` 的右子树的子结构,如果有一种满足,则返回 `True`,如果都不满足,则返回 `False`。 + +递归判断 `A`、`B` 是否相等的具体方法如下: + +- 如果 `B` 为空树,则直接返回 `False`,因为空树不是任意一棵树的子结构。 +- 如果 `A` 为空树或者 `A` 节点的值不等于 `B` 节点的值,则返回 `False`。 +- 如果 `A`、`B` 都不为空,且节点值相同,则递归判断 `A` 的左子树和 `B` 的左子树是否相等,判断 `A` 的右子树和 `B` 的右子树是否相等。如果都相等,则返回 `True`,否则返回 `False`。 + +## 代码 + +```python +class Solution: + def hasSubStructure(self, A: TreeNode, B: TreeNode) -> bool: + if not B: + return True + if not A or A.val != B.val: + return False + return self.hasSubStructure(A.left, B.left) and self.hasSubStructure(A.right, B.right) + + def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool: + if not A or not B: + return False + if self.hasSubStructure(A, B): + return True + return self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B) +``` + diff --git a/docs/solutions/LCR/shu-ju-liu-zhong-de-zhong-wei-shu-lcof.md b/docs/solutions/LCR/shu-ju-liu-zhong-de-zhong-wei-shu-lcof.md new file mode 100644 index 00000000..cd20280b --- /dev/null +++ b/docs/solutions/LCR/shu-ju-liu-zhong-de-zhong-wei-shu-lcof.md @@ -0,0 +1,60 @@ +# [LCR 160. 数据流中的中位数](https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) + +- 标签:设计、双指针、数据流、排序、堆(优先队列) +- 难度:困难 + +## 题目链接 + +- [LCR 160. 数据流中的中位数 - 力扣](https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) + +## 题目大意 + +要求:设计一个支持一下两种操作的数组结构: + +- `void addNum(int num)`:从数据流中添加一个整数到数据结构中。 +- `double findMedian()`:返回目前所有元素的中位数。 + +## 解题思路 + +使用一个大顶堆 `queMax` 记录大于中位数的数,使用一个小顶堆 `queMin` 小于中位数的数。 + +- 当添加元素数量为偶数: `queMin` 和 `queMax` 中元素数量相同,则中位数为它们队头的平均值。 +- 当添加元素数量为奇数:`queMin` 中的数比 `queMax` 多一个,此时中位数为 `queMin` 的队头。 + +为了满足上述条件,在进行 `addNum` 操作时,我们应当分情况处理: + +- `num > max{queMin}`:此时 `num` 大于中位数,将该数添加到大顶堆 `queMax` 中。新的中位数将大于原来的中位数,所以可能需要将 `queMax` 中的最小数移动到 `queMin` 中。 +- `num ≤ max{queMin}`:此时 `num` 小于中位数,将该数添加到小顶堆 `queMin` 中。新的中位数将小于等于原来的中位数,所以可能需要将 `queMin` 中最大数移动到 `queMax` 中。 + +## 代码 + +```python +import heapq + +class MedianFinder: + + def __init__(self): + """ + initialize your data structure here. + """ + self.queMin = list() + self.queMax = list() + + + def addNum(self, num: int) -> None: + if not self.queMin or num < -self.queMin[0]: + heapq.heappush(self.queMin, -num) + if len(self.queMax) + 1 < len(self.queMin): + heapq.heappush(self.queMax, -heapq.heappop(self.queMin)) + else: + heapq.heappush(self.queMax, num) + if len(self.queMax) > len(self.queMin): + heapq.heappush(self.queMin, -heapq.heappop(self.queMax)) + + + def findMedian(self) -> float: + if len(self.queMin) > len(self.queMax): + return -self.queMin[0] + return (-self.queMin[0] + self.queMax[0]) / 2 +``` + diff --git a/docs/solutions/LCR/shu-zhi-de-zheng-shu-ci-fang-lcof.md b/docs/solutions/LCR/shu-zhi-de-zheng-shu-ci-fang-lcof.md new file mode 100644 index 00000000..13fac5bf --- /dev/null +++ b/docs/solutions/LCR/shu-zhi-de-zheng-shu-ci-fang-lcof.md @@ -0,0 +1,46 @@ +# [LCR 134. Pow(x, n)](https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) + +- 标签:递归、数学 +- 难度:中等 + +## 题目链接 + +- [LCR 134. Pow(x, n) - 力扣](https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) + +## 题目大意 + +给定浮点数 `x` 和整数 `n`。 + +要求:实现 `pow(x, n)`,即计算 $x^n$,不能使用库函数,不需要考虑大数问题。 + +## 解题思路 + +常规方法是直接将 x 累乘 n 次得出结果,时间复杂度为 $O(n)$。可以利用快速幂来减少时间复杂度。 + +如果 n 为偶数,$x^n = x^{n/2} * x^{n/2}$。如果 n 为奇数,$x^n = x * x^{(n-1)/2} * x^{(n-1)/2}$。 + +$x^(n/2)$ 又可以继续向下递归划分。则我们可以利用低纬度的幂计算结果,来得到高纬度的幂计算结果。 + +这样递归求解,时间复杂度为 $O(logn)$,并且递归也可以转为递推来做。 + +需要注意如果 n 为负数,可以转换为 $\frac{1}{x} ^{(-n)}$。 + +## 代码 + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if x == 0.0: + return 0.0 + res = 1 + if n < 0: + x = 1 / x + n = -n + while n: + if n & 1: + res *= x + x *= x + n >>= 1 + return res +``` + diff --git a/docs/solutions/LCR/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof.md b/docs/solutions/LCR/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof.md new file mode 100644 index 00000000..2f33abb6 --- /dev/null +++ b/docs/solutions/LCR/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof.md @@ -0,0 +1,52 @@ +# [LCR 163. 找到第 k 位数字](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) + +- 标签:数学、二分查找 +- 难度:中等 + +## 题目链接 + +- [LCR 163. 找到第 k 位数字 - 力扣](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) + +## 题目大意 + +数字以 `0123456789101112131415…` 的格式序列化到一个字符序列中。在这个序列中,第 `5` 位(从下标 `0` 开始计数)是 `5`,第 `13` 位是 `1`,第 `19` 位是 `4`,等等。 + +要求:返回任意第 `n` 位对应的数字。 + +## 解题思路 + +根据题意中的字符串,找数学规律: + +- `123456789`:是 `9` 个 `1` 位数字。 +- `10111213...9899`:是 `90` 个 `2` 位数字。 +- `100...999`:是 `900` 个 `3` 位数字。 +- `1000...9999` 是 `9000` 个 `4` 位数字。 + +- 我们可以先找到对应的数字对应的位数 `digits`。 +- 然后找到该位数 `digits` 的起始数字 `start`。 +- 再计算出 `n` 所在的数字 `number`。`number` 等于从起始数字 `start` 开始的第 $\lfloor(n - 1) / digits\rfloor$ 个数字。即 `number = start + (n - 1) // digits`。 +- 然后确定 `n` 对应的是数字 `number` 中的哪一位。即 `idx = (n - 1) % digits`。 +- 最后返回结果。 + +## 代码 + +```python +class Solution: + def findNthDigit(self, n: int) -> int: + digits = 1 + start = 1 + base = 9 + while n > base: + n -= base + digits += 1 + start *= 10 + base = start * digits * 9 + + number = start + (n - 1) // digits + idx = (n - 1) % digits + return int(str(number)[idx]) +``` + +## 参考资料 + +- 【题解】[面试题44. 数字序列中某一位的数字(迭代 + 求整 / 求余,清晰图解) - 数字序列中某一位的数字 - 力扣](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/mian-shi-ti-44-shu-zi-xu-lie-zhong-mou-yi-wei-de-6/) diff --git a/docs/solutions/LCR/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof.md b/docs/solutions/LCR/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof.md new file mode 100644 index 00000000..eda53db6 --- /dev/null +++ b/docs/solutions/LCR/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof.md @@ -0,0 +1,39 @@ +# [LCR 158. 库存管理 II](https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) + +- 标签:数组、哈希表、分治、计数、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 158. 库存管理 II - 力扣](https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/) + +## 题目大意 + +给定一个数组 `nums`,其中有一个数字出现次数超过数组长度一半。 + +要求:找到出现次数超过数组长度一半的数字。 + +## 解题思路 + +可以利用哈希表。遍历一遍数组 `nums`,用哈希表统计每个元素 `num` 出现的次数,再遍历一遍哈希表,找出元素个数最多的元素即可。 + +## 代码 + +```python +class Solution: + def majorityElement(self, nums: List[int]) -> int: + numDict = dict() + for num in nums: + if num in numDict: + numDict[num] += 1 + else: + numDict[num] = 1 + max = 0 + max_index = -1 + for num in numDict: + if numDict[num] > max: + max = numDict[num] + max_index = num + return max_index +``` + diff --git a/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md b/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md new file mode 100644 index 00000000..2563d853 --- /dev/null +++ b/docs/solutions/LCR/shu-zu-zhong-de-ni-xu-dui-lcof.md @@ -0,0 +1,162 @@ +# [LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) + +- 标签:树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 +- 难度:困难 + +## 题目链接 + +- [LCR 170. 交易逆序对的总数 - 力扣](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) + +## 题目大意 + +**描述**:给定一个数组 $nums$。 + +**要求**:计算出数组中的逆序对的总数。 + +**说明**: + +- **逆序对**:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 +- $0 \le nums.length \le 50000$。 + +**示例**: + +- 示例 1: + +```python +输入: [7,5,6,4] +输出: 5 +``` + +## 解题思路 + +### 思路 1:归并排序 + +归并排序主要分为:「分解过程」和「合并过程」。其中「合并过程」实质上是两个有序数组的合并过程。 + +![](https://qcdn.itcharge.cn/images/20220414204405.png) + +每当遇到 左子数组当前元素 > 右子树组当前元素时,意味着「左子数组从当前元素开始,一直到左子数组末尾元素」与「右子树组当前元素」构成了若干个逆序对。 + +比如上图中的左子数组 $[0, 3, 5, 7]$ 与右子树组 $[1, 4, 6, 8]$,遇到左子数组中元素 $3$ 大于右子树组中元素 $1$。则左子数组从 $3$ 开始,经过 $5$ 一直到 $7$,与右子数组当前元素 $1$ 都构成了逆序对。即 $[3, 1]$、$[5, 1]$、$[7, 1]$ 都构成了逆序对。 + +因此,我们可以在合并两个有序数组的时候计算逆序对。具体做法如下: + +1. 使用全局变量 $cnt$ 来存储逆序对的个数。然后进行归并排序。 +2. **分割过程**:先递归地将当前序列平均分成两半,直到子序列长度为 $1$。 + 1. 找到序列中心位置 $mid$,从中心位置将序列分成左右两个子序列 $left\underline{\hspace{0.5em}}arr$、$right\underline{\hspace{0.5em}}arr$。 + 2. 对左右两个子序列 $left\underline{\hspace{0.5em}}arr$、$right\underline{\hspace{0.5em}}arr$ 分别进行递归分割。 + 3. 最终将数组分割为 $n$ 个长度均为 $1$ 的有序子序列。 +3. **归并过程**:从长度为 $1$ 的有序子序列开始,依次进行两两归并,直到合并成一个长度为 $n$ 的有序序列。 + 1. 使用数组变量 $arr$ 存放归并后的有序数组。 + 2. 使用两个指针 $left\underline{\hspace{0.5em}}i$、$right\underline{\hspace{0.5em}}i$ 分别指向两个有序子序列 $left\underline{\hspace{0.5em}}arr$、$right\underline{\hspace{0.5em}}arr$ 的开始位置。 + 3. 比较两个指针指向的元素: + 1. 如果 $left\underline{\hspace{0.5em}}arr[left\underline{\hspace{0.5em}}i] \le right\underline{\hspace{0.5em}}arr[right\underline{\hspace{0.5em}}i]$,则将 $left\underline{\hspace{0.5em}}arr[left\underline{\hspace{0.5em}}i]$ 存入到结果数组 $arr$ 中,并将指针移动到下一位置。 + 2. 如果 $left\underline{\hspace{0.5em}}arr[left\underline{\hspace{0.5em}}i] > right\underline{\hspace{0.5em}}arr[right\underline{\hspace{0.5em}}i]$,则 **记录当前左子序列中元素与当前右子序列元素所形成的逆序对的个数,并累加到 $cnt$ 中,即 `self.cnt += len(left_arr) - left_i`**,然后将 $right\underline{\hspace{0.5em}}arr[right\underline{\hspace{0.5em}}i]$ 存入到结果数组 $arr$ 中,并将指针移动到下一位置。 + 4. 重复步骤 $3$,直到某一指针到达子序列末尾。 + 5. 将另一个子序列中的剩余元素存入到结果数组 $arr$ 中。 + 6. 返回归并后的有序数组 $arr$。 +4. 返回数组中的逆序对的总数,即 $self.cnt$。 + +### 思路 1:代码 + +```python +class Solution: + cnt = 0 + def merge(self, left_arr, right_arr): # 归并过程 + arr = [] + left_i, right_i = 0, 0 + while left_i < len(left_arr) and right_i < len(right_arr): + # 将两个有序子序列中较小元素依次插入到结果数组中 + if left_arr[left_i] <= right_arr[right_i]: + arr.append(left_arr[left_i]) + left_i += 1 + else: + self.cnt += len(left_arr) - left_i + arr.append(right_arr[right_i]) + right_i += 1 + + while left_i < len(left_arr): + # 如果左子序列有剩余元素,则将其插入到结果数组中 + arr.append(left_arr[left_i]) + left_i += 1 + + while right_i < len(right_arr): + # 如果右子序列有剩余元素,则将其插入到结果数组中 + arr.append(right_arr[right_i]) + right_i += 1 + + return arr # 返回排好序的结果数组 + + def mergeSort(self, arr): # 分割过程 + if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 + return arr + + mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 + left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序 + right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序 + return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 + + def reversePairs(self, nums: List[int]) -> int: + self.cnt = 0 + self.mergeSort(nums) + return self.cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:树状数组 + +数组 $tree[i]$ 表示数字 $i$ 是否在序列中出现过,如果数字 $i$ 已经存在于序列中,$tree[i] = 1$,否则 $tree[i] = 0$。 + +1. 按序列从左到右将值为 $nums[i]$ 的元素当作下标为$nums[i]$,赋值为 $1$ 插入树状数组里,这时,比 $nums[i]$ 大的数个数就是 $i + 1 - query(a)$。 +2. 将全部结果累加起来就是逆序数了。 + +### 思路 2:代码 + +```python +import bisect + +class BinaryIndexTree: + + def __init__(self, n): + self.size = n + self.tree = [0 for _ in range(n + 1)] + + def lowbit(self, index): + return index & (-index) + + def update(self, index, delta): + while index <= self.size: + self.tree[index] += delta + index += self.lowbit(index) + + def query(self, index): + res = 0 + while index > 0: + res += self.tree[index] + index -= self.lowbit(index) + return res + +class Solution: + def reversePairs(self, nums: List[int]) -> int: + size = len(nums) + sort_nums = sorted(nums) + for i in range(size): + nums[i] = bisect.bisect_left(sort_nums, nums[i]) + 1 + + bit = BinaryIndexTree(size) + ans = 0 + for i in range(size): + bit.update(nums[i], 1) + ans += (i + 1 - bit.query(nums[i])) + return ans +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(n)$。 + diff --git a/docs/solutions/LCR/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof.md b/docs/solutions/LCR/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof.md new file mode 100644 index 00000000..9a6ff8cf --- /dev/null +++ b/docs/solutions/LCR/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof.md @@ -0,0 +1,45 @@ +# [LCR 177. 撞色搭配](https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) + +- 标签:位运算、数组 +- 难度:中等 + +## 题目链接 + +- [LCR 177. 撞色搭配 - 力扣](https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) + +## 题目大意 + +给定一个整型数组 `nums` 。`nums` 里除两个数字之外,其他数字都出现了两次。 + +要求:找出这两个只出现一次的数字。要求时间复杂度是 $O(n)$,空间复杂度是 $O(1)$。 + +## 解题思路 + +- 求解这道题之前,我们先来看看如何求解「一个数组中除了某个元素只出现一次以外,其余每个元素均出现两次。」即「[136. 只出现一次的数字](https://leetcode.cn/problems/single-number/)」问题。我们可以对所有数不断进行异或操作,最终可得到单次出现的元素。 + +- 如果数组中有两个数字只出现一次,其余每个元素均出现两次。那么经过全部异或运算。我们可以得到只出现一次的两个数字的异或结果。 +- 根据异或结果的性质,异或运算中如果某一位上为 `1`,则说明异或的两个数在该位上是不同的。根据这个性质,我们将数字分为两组:一组是和该位为 `0` 的数字,另一组是该位为 `1` 的数字。然后将这两组分别进行异或运算,就可以得到最终要求的两个数字。 + +## 代码 + +```python +class Solution: + def singleNumbers(self, nums: List[int]) -> List[int]: + all_xor = 0 + for num in nums: + all_xor ^= num + # 获取所有异或中最低位的 1 + mask = 1 + while all_xor & mask == 0: + mask <<= 1 + + a_xor, b_xor = 0, 0 + for num in nums: + if num & mask == 0: + a_xor ^= num + else: + b_xor ^= num + + return a_xor, b_xor +``` + diff --git a/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md b/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md new file mode 100644 index 00000000..4e33e1e9 --- /dev/null +++ b/docs/solutions/LCR/shu-zu-zhong-zhong-fu-de-shu-zi-lcof.md @@ -0,0 +1,30 @@ +# [LCR 120. 寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) + +- 标签:数组、哈希表、排序 +- 难度:简单 + +## 题目链接 + +- [LCR 120. 寻找文件副本 - 力扣](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) + +## 题目大意 + +给定一个包含 `n + 1` 个整数的数组 `nums`,里边包含的值都在 `1 ~ n` 之间。假设 `nums` 中只存在一个重复的整数,要求找出这个重复的数。 + +## 解题思路 + +使用哈希表存储数组每个元素,遇到重复元素则直接返回该元素。 + +## 代码 + +```python +class Solution: + def findRepeatNumber(self, nums: List[int]) -> int: + nums_dict = dict() + for num in nums: + if num in nums_dict: + return num + nums_dict[num] = 1 + return -1 +``` + diff --git a/docs/solutions/LCR/shun-shi-zhen-da-yin-ju-zhen-lcof.md b/docs/solutions/LCR/shun-shi-zhen-da-yin-ju-zhen-lcof.md new file mode 100644 index 00000000..996646e6 --- /dev/null +++ b/docs/solutions/LCR/shun-shi-zhen-da-yin-ju-zhen-lcof.md @@ -0,0 +1,59 @@ +# [LCR 146. 螺旋遍历二维数组](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) + +- 标签:数组、矩阵、模拟 +- 难度:简单 + +## 题目链接 + +- [LCR 146. 螺旋遍历二维数组 - 力扣](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) + +## 题目大意 + +给定一个 `m * n` 大小的二维矩阵 `matrix`。 + +要求:按照顺时针旋转的顺序,返回矩阵中的所有元素。 + +## 解题思路 + +按照题意进行模拟。可以实现定义一下上、下、左、右的边界,然后按照逆时针的顺序从边界上依次访问元素。 + +当访问完当前边界之后,要更新一下边界位置,缩小范围,方便下一轮进行访问。 + +## 代码 + +```python +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + size_m = len(matrix) + if size_m == 0: + return [] + size_n = len(matrix[0]) + if size_n == 0: + return [] + + up, down, left, right = 0, size_m - 1, 0, size_n - 1 + ans = [] + while True: + for i in range(left, right + 1): + ans.append(matrix[up][i]) + up += 1 + if up > down: + break + for i in range(up, down + 1): + ans.append(matrix[i][right]) + right -= 1 + if right < left: + break + for i in range(right, left - 1, -1): + ans.append(matrix[down][i]) + down -= 1 + if down < up: + break + for i in range(down, up - 1, -1): + ans.append(matrix[i][left]) + left += 1 + if left > right: + break + return ans +``` + diff --git a/docs/solutions/LCR/ti-huan-kong-ge-lcof.md b/docs/solutions/LCR/ti-huan-kong-ge-lcof.md new file mode 100644 index 00000000..172e6e1e --- /dev/null +++ b/docs/solutions/LCR/ti-huan-kong-ge-lcof.md @@ -0,0 +1,38 @@ +# [LCR 122. 路径加密](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) + +- 标签:字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 122. 路径加密 - 力扣](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:将字符串 `s` 中的每个空格换成 `%20`。 + +## 解题思路 + +Python 的字符串是不可变类型,所以需要先用数组存储答案,再将其转为字符串返回。具体操作如下。 + +- 定义数组 `res`,遍历字符串 `s`。 + - 如果当前字符 `ch` 为空格,则将 ` %20` 加入到数组中。 + - 如果当前字符 `ch` 不为空格,则直接加入到数组中。 +- 遍历完之后,通过 `join` 将其转为字符串返回。 + +## 代码 + +```python +class Solution: + def replaceSpace(self, s: str) -> str: + res = [] + for ch in s: + if ch == ' ': + res.append("%20") + else: + res.append(ch) + return "".join(res) +``` + diff --git a/docs/solutions/LCR/tvdfij.md b/docs/solutions/LCR/tvdfij.md new file mode 100644 index 00000000..07ab138b --- /dev/null +++ b/docs/solutions/LCR/tvdfij.md @@ -0,0 +1,35 @@ +# [LCR 012. 寻找数组的中心下标](https://leetcode.cn/problems/tvdfij/) + +- 标签:数组、前缀和 +- 难度:简单 + +## 题目链接 + +- [LCR 012. 寻找数组的中心下标 - 力扣](https://leetcode.cn/problems/tvdfij/) + +## 题目大意 + +给定一个数组 `nums`。 + +要求:找到「左侧元素和」与「右侧元素和相等」的位置,若找不到,则返回 `-1`。 + +## 解题思路 + +两次遍历,第一次遍历先求出数组全部元素和。第二次遍历找到左侧元素和恰好为全部元素和一半的位置。 + +## 代码 + +```python +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + sum = 0 + for i in range(len(nums)): + sum += nums[i] + curr_sum = 0 + for i in range(len(nums)): + if curr_sum * 2 + nums[i] == sum: + return i + curr_sum += nums[i] + return -1 +``` + diff --git a/docs/solutions/LCR/uUsW3B.md b/docs/solutions/LCR/uUsW3B.md new file mode 100644 index 00000000..5215ce75 --- /dev/null +++ b/docs/solutions/LCR/uUsW3B.md @@ -0,0 +1,51 @@ +# [LCR 080. 组合](https://leetcode.cn/problems/uUsW3B/) + +- 标签:数组、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 080. 组合 - 力扣](https://leetcode.cn/problems/uUsW3B/) + +## 题目大意 + +给定两个整数 `n` 和 `k`。 + +要求:返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。可以按任何顺序返回答案。 + +## 解题思路 + +组合问题通常可以用回溯算法来解决。定义两个数组 `res`、`path`。`res` 用来存放最终答案,`path` 用来存放当前符合条件的一个结果。再使用一个变量 `start_index` 来表示从哪一个数开始遍历。 + +定义回溯方法,`start_index = 1` 开始进行回溯。 + +- 如果 `path` 数组的长度等于 `k`,则将 `path` 中的元素加入到 `res` 数组中。 +- 然后对 `[start_index, n]` 范围内的数进行遍历取值。 + - 将当前元素 `i` 加入 `path` 数组。 + - 递归遍历 `[start_index, n]` 上的数。 + - 将遍历的 `i` 元素进行回退。 +- 最终返回 `res` 数组。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, n: int, k: int, start_index: int): + if len(self.path) == k: + self.res.append(self.path[:]) + return + for i in range(start_index, n - (k - len(self.path)) + 2): + self.path.append(i) + self.backtrack(n, k, i + 1) + self.path.pop() + + def combine(self, n: int, k: int) -> List[List[int]]: + self.res.clear() + self.path.clear() + self.backtrack(n, k, 1) + return self.res +``` + diff --git a/docs/solutions/LCR/vEAB3K.md b/docs/solutions/LCR/vEAB3K.md new file mode 100644 index 00000000..1c0df49c --- /dev/null +++ b/docs/solutions/LCR/vEAB3K.md @@ -0,0 +1,57 @@ +# [LCR 106. 判断二分图](https://leetcode.cn/problems/vEAB3K/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图 +- 难度:中等 + +## 题目链接 + +- [LCR 106. 判断二分图 - 力扣](https://leetcode.cn/problems/vEAB3K/) + +## 题目大意 + +给定一个代表 n 个节点的无向图的二维数组 `graph`,其中 `graph[u]` 是一个节点数组,由节点 `u` 的邻接节点组成。对于 `graph[u]` 中的每个 `v`,都存在一条位于节点 `u` 和节点 `v` 之间的无向边。 + +该无向图具有以下属性: + +- 不存在自环(`graph[u]` 不包含 `u`)。 +- 不存在平行边(`graph[u]` 不包含重复值)。 +- 如果 `v` 在 `graph[u]` 内,那么 `u` 也应该在 `graph[v]` 内(该图是无向图)。 +- 这个图可能不是连通图,也就是说两个节点 `u` 和 `v` 之间可能不存在一条连通彼此的路径。 + +要求:判断该图是否是二分图,如果是二分图,则返回 `True`;否则返回 `False`。 + +- 二分图:如果能将一个图的节点集合分割成两个独立的子集 `A` 和 `B`,并使图中的每一条边的两个节点一个来自 `A` 集合,一个来自 `B` 集合,就将这个图称为 二分图 。 + +## 解题思路 + +对于图中的任意节点 `u` 和 `v`,如果 `u` 和 `v` 之间有一条无向边,那么 `u` 和 `v` 必然属于不同的集合。 + +我们可以通过在深度优先搜索中对邻接点染色标记的方式,来识别该图是否是二分图。具体做法如下: + +- 找到一个没有染色的节点 `u`,将其染成红色。 +- 然后遍历该节点直接相连的节点 `v`,如果该节点没有被染色,则将该节点直接相连的节点染成蓝色,表示两个节点不是同一集合。如果该节点已经被染色并且颜色跟 `u` 一样,则说明该图不是二分图,直接返回 `False`。 +- 从上面染成蓝色的节点 `v` 出发,遍历该节点直接相连的节点。。。依次类推的递归下去。 +- 如果所有节点都顺利染上色,则说明该图为二分图,返回 `True`。否则,如果在途中不能顺利染色,则返回 `False`。 + +## 代码 + +```python +class Solution: + def dfs(self, graph, colors, i, color): + colors[i] = color + for j in graph[i]: + if colors[j] == colors[i]: + return False + if colors[j] == 0 and not self.dfs(graph, colors, j, -color): + return False + return True + + def isBipartite(self, graph: List[List[int]]) -> bool: + size = len(graph) + colors = [0 for _ in range(size)] + for i in range(size): + if colors[i] == 0 and not self.dfs(graph, colors, i, 1): + return False + return True +``` + diff --git a/docs/solutions/LCR/vlzXQL.md b/docs/solutions/LCR/vlzXQL.md new file mode 100644 index 00000000..f774a008 --- /dev/null +++ b/docs/solutions/LCR/vlzXQL.md @@ -0,0 +1,115 @@ +# [LCR 111. 除法求值](https://leetcode.cn/problems/vlzXQL/) + +- 标签:深度优先搜索、广度优先搜索、并查集、图、数组、最短路 +- 难度:中等 + +## 题目链接 + +- [LCR 111. 除法求值 - 力扣](https://leetcode.cn/problems/vlzXQL/) + +## 题目大意 + +给定一个变量对数组 `equations` 和一个实数数组 `values` 作为已知条件,其中 `equations[i] = [Ai, Bi]` 和 `values[i]` 共同表示 `Ai / Bi = values[i]`。每个 `Ai` 或 `Bi` 是一个表示单个变量的字符串。 + +再给定一个表示多个问题的数组 `queries`,其中 `queries[j] = [Cj, Dj]` 表示第 `j` 个问题,要求:根据已知条件找出 `Cj / Dj = ?` 的结果作为答案。返回所有问题的答案。如果某个答案无法确定,则用 `-1.0` 代替,如果问题中出现了给定的已知条件中没有出现的表示变量的字符串,则也用 `-1.0` 代替这个答案。 + +## 解题思路 + +在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」的基础上增加了倍数关系。在「[等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations)」中我们处理传递关系使用了并查集,这道题也是一样,不过在使用并查集的同时还要维护倍数关系。 + +举例说明: + +- `a / b = 2.0`:说明 `a = 2b`,`a` 和 `b` 在同一个集合。 +- `b / c = 3.0`:说明 `b = 3c`,`b` 和 `c` 在同一个集合。 + +根据上述两式可得:`a`、`b`、`c` 都在一个集合中,且 `a = 2b = 6c`。 + +我们可以将同一集合中的变量倍数关系都转换为与根节点变量的倍数关系,比如上述例子中都转变为与 `a` 的倍数关系。 + +具体操作如下: + +- 定义并查集结构,并在并查集中定义一个表示倍数关系的 `multiples` 数组。 +- 遍历 `equations` 数组、`values` 数组,将每个变量按顺序编号,并使用 `union` 将其并入相同集合。 +- 遍历 `queries` 数组,判断两个变量是否在并查集中,并且是否在同一集合。如果找到对应关系,则将计算后的倍数关系存入答案数组,否则则将 `-1` 存入答案数组。 +- 最终输出答案数组。 + +并查集中维护倍数相关方法说明: + +- `find` 方法: + - 递推寻找根节点,并将倍数累乘,然后进行路径压缩,并且更新当前节点的倍数关系。 +- `union` 方法: + - 如果两个节点属于同一集合,则直接返回。 + - 如果两个节点不属于同一个集合,合并之前当前节点的倍数关系更新,然后再进行更新。 +- `is_connect` 方法: + - 如果两个节点不属于同一集合,返回 `-1`。 + - 如果两个节点属于同一集合,则返回倍数关系。 + +## 代码 + +```python +class UnionFind: + + def __init__(self, n): + self.parent = [i for i in range(n)] + self.multiples = [1 for _ in range(n)] + + def find(self, x): + multiple = 1.0 + origin = x + while x != self.parent[x]: + multiple *= self.multiples[x] + x = self.parent[x] + self.parent[origin] = x + self.multiples[origin] = multiple + return x + + def union(self, x, y, multiple): + root_x = self.find(x) + root_y = self.find(y) + if root_x == root_y: + return + self.parent[root_x] = root_y + self.multiples[root_x] = multiple * self.multiples[y] / self.multiples[x] + return + + def is_connected(self, x, y): + root_x = self.find(x) + root_y = self.find(y) + if root_x != root_y: + return -1.0 + + return self.multiples[x] / self.multiples[y] + +class Solution: + def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: + equations_size = len(equations) + hash_map = dict() + union_find = UnionFind(2 * equations_size) + + id = 0 + for i in range(equations_size): + equation = equations[i] + var1, var2 = equation[0], equation[1] + if var1 not in hash_map: + hash_map[var1] = id + id += 1 + if var2 not in hash_map: + hash_map[var2] = id + id += 1 + union_find.union(hash_map[var1], hash_map[var2], values[i]) + + queries_size = len(queries) + res = [] + for i in range(queries_size): + query = queries[i] + var1, var2 = query[0], query[1] + if var1 not in hash_map or var2 not in hash_map: + res.append(-1.0) + else: + id1 = hash_map[var1] + id2 = hash_map[var2] + res.append(union_find.is_connected(id1, id2)) + + return res +``` + diff --git a/docs/solutions/LCR/vvXgSW.md b/docs/solutions/LCR/vvXgSW.md new file mode 100644 index 00000000..4c6728ea --- /dev/null +++ b/docs/solutions/LCR/vvXgSW.md @@ -0,0 +1,55 @@ +# [LCR 078. 合并 K 个升序链表](https://leetcode.cn/problems/vvXgSW/) + +- 标签:链表、分治、堆(优先队列)、归并排序 +- 难度:困难 + +## 题目链接 + +- [LCR 078. 合并 K 个升序链表 - 力扣](https://leetcode.cn/problems/vvXgSW/) + +## 题目大意 + +给定一个链表数组 `lists`,每个链表都已经按照升序排列。 + +要求:将所有链表合并到一个升序链表中,返回合并后的链表。 + +## 解题思路 + +分而治之的思想。将链表数组不断二分,转为规模为二分之一的子问题,然后再进行归并排序。 + +## 代码 + +```python +class Solution: + def merge_sort(self, lists: List[ListNode], left: int, right: int) -> ListNode: + if left == right: + return lists[left] + mid = left + (right - left) // 2 + node_left = self.merge_sort(lists, left, mid) + node_right = self.merge_sort(lists, mid + 1, right) + return self.merge(node_left, node_right) + + def merge(self, a: ListNode, b: ListNode) -> ListNode: + root = ListNode(-1) + cur = root + while a and b: + if a.val < b.val: + cur.next = a + a = a.next + else: + cur.next = b + b = b.next + cur = cur.next + if a: + cur.next = a + if b: + cur.next = b + return root.next + + def mergeKLists(self, lists: List[ListNode]) -> ListNode: + if not lists: + return None + size = len(lists) + return self.merge_sort(lists, 0, size - 1) +``` + diff --git a/docs/solutions/LCR/w3tCBm.md b/docs/solutions/LCR/w3tCBm.md new file mode 100644 index 00000000..fa303277 --- /dev/null +++ b/docs/solutions/LCR/w3tCBm.md @@ -0,0 +1,39 @@ +# [LCR 003. 比特位计数](https://leetcode.cn/problems/w3tCBm/) + +- 标签:位运算、动态规划 +- 难度:简单 + +## 题目链接 + +- [LCR 003. 比特位计数 - 力扣](https://leetcode.cn/problems/w3tCBm/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 + +## 解题思路 + +可以根据整数的二进制特点将其分为两类: + +- 奇数:一定比前面相邻的偶数多一个 `1`。 +- 偶数:一定和除以 `2` 之后的数一样多。 +- 边界 `0`:`1` 的个数为 `0`。 + +于是可以根据规律,从 `0` 开始到 `n` 进行递推求解。 + +## 代码 + +```python +class Solution: + def countBits(self, n: int) -> List[int]: + dp = [0 for _ in range(n + 1)] + for i in range(1, n + 1): + if i % 2 == 1: + dp[i] = dp[i - 1] + 1 + else: + dp[i] = dp[i // 2] + return dp +``` + diff --git a/docs/solutions/LCR/w6cpku.md b/docs/solutions/LCR/w6cpku.md new file mode 100644 index 00000000..dd25e693 --- /dev/null +++ b/docs/solutions/LCR/w6cpku.md @@ -0,0 +1,49 @@ +# [LCR 054. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/w6cpku/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 054. 把二叉搜索树转换为累加树 - 力扣](https://leetcode.cn/problems/w6cpku/) + +## 题目大意 + +给定一棵二叉搜索树(BST)的根节点 `root`,且二叉搜索树的节点值各不相同。要求将其转化为「累加树」,使其每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和。 + +二叉搜索树的定义: + +- 若左子树不为空,则左子树上所有节点值均小于它的根节点值; +- 若右子树不为空,则右子树上所有节点值均大于它的根节点值; +- 任意节点的左、右子树也分别为二叉搜索树。 + +## 解题思路 + +题目要求将每个节点的值修改为原来的节点值加上大于它的节点值之和。已知二叉搜索树的中序遍历可以得到一个升序数组。 + +题目就可以变为:修改升序数组中每个节点值为末尾元素累加和。由于末尾元素累加和的求和过程和遍历顺序相反,所以我们可以考虑换种思路。 + +二叉搜索树的中序遍历顺序为:左 -> 根 -> 右,从而可以得到一个升序数组,那么我们将左右反着遍历,即顺序为:右 -> 根 -> 左,就可以得到一个降序数组,这样就可以在遍历的同时求前缀和。 + +当然我们在计算前缀和的时候,需要用到前一个节点的值,所以需要用变量 `pre` 存储前一节点的值。 + +## 代码 + +```python +class Solution: + pre = 0 + + def createBinaryTree(self, root: TreeNode): + if not root: + return + self.createBinaryTree(root.right) + root.val += self.pre + self.pre = root.val + self.createBinaryTree(root.left) + + def convertBST(self, root: TreeNode) -> TreeNode: + self.pre = 0 + self.createBinaryTree(root) + return root +``` + diff --git a/docs/solutions/LCR/wtcaE1.md b/docs/solutions/LCR/wtcaE1.md new file mode 100644 index 00000000..e2b91a16 --- /dev/null +++ b/docs/solutions/LCR/wtcaE1.md @@ -0,0 +1,40 @@ +# [LCR 016. 无重复字符的最长子串](https://leetcode.cn/problems/wtcaE1/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [LCR 016. 无重复字符的最长子串 - 力扣](https://leetcode.cn/problems/wtcaE1/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:找出其中不含有重复字符的 最长子串 的长度。 + +## 解题思路 + +利用集合来存储不重复的字符。用两个指针分别指向最长子串的左右节点。遍历字符串,右指针不断右移,利用集合来判断有没有重复的字符,如果没有,就持续向右扩大右边界。如果出现重复字符,就缩小左侧边界。每次移动终止,都要计算一下当前不含重复字符的子串长度,并判断一下是否需要更新最大长度。 + +## 代码 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + if not s: + return 0 + + letterSet = set() + right = 0 + ans = 0 + for i in range(len(s)): + if i != 0: + letterSet.remove(s[i - 1]) + while right < len(s) and s[right] not in letterSet: + letterSet.add(s[right]) + right += 1 + ans = max(ans, right - i) + return ans +``` + diff --git a/docs/solutions/LCR/xoh6Oh.md b/docs/solutions/LCR/xoh6Oh.md new file mode 100644 index 00000000..11ed6615 --- /dev/null +++ b/docs/solutions/LCR/xoh6Oh.md @@ -0,0 +1,62 @@ +# [LCR 001. 两数相除](https://leetcode.cn/problems/xoh6Oh/) + +- 标签:位运算、数学 +- 难度:简单 + +## 题目链接 + +- [LCR 001. 两数相除 - 力扣](https://leetcode.cn/problems/xoh6Oh/) + +## 题目大意 + +给定两个整数,被除数 dividend 和除数 divisor。要求返回两数相除的商,并且不能使用乘法,除法和取余运算。取值范围在 $[-2^{31}, 2^{31}-1]$。如果结果溢出,则返回 $2^{31} - 1$。 + +## 解题思路 + +题目要求不能使用乘法,除法和取余运算。 + +可以把被除数和除数当做二进制,这样进行运算的时候,就可以通过移位运算来实现二进制的乘除。 + +- 先将除数不断左移,移位到位数大于或等于被除数。记录其移位次数 count。 + +- 然后再将除数右移 count 次,模拟二进制除法运算。 + - 如果当前被除数大于等于除数,则将 1 左移 count 位,即为当前位的商,并将其累加答案上。再用除数减去被除数,进行下一次运算。 + +## 代码 + +```python + +添加备注 + + +class Solution: + def divide(self, a: int, b: int) -> int: + MIN_INT, MAX_INT = -2147483648, 2147483647 + symbol = True if (a ^ b) < 0 else False + if a < 0: + a = -a + if b < 0: + b = -b + + # 除数不断左移,移位到位数大于或等于被除数 + count = 0 + while a >= b: + count += 1 + b <<= 1 + + # 向右移位,不断模拟二进制除法运算 + res = 0 + while count > 0: + count -= 1 + b >>= 1 + if a >= b: + res += (1 << count) + a -= b + if symbol: + res = -res + if MIN_INT <= res <= MAX_INT: + return res + else: + return MAX_INT +``` + diff --git a/docs/solutions/LCR/xu-lie-hua-er-cha-shu-lcof.md b/docs/solutions/LCR/xu-lie-hua-er-cha-shu-lcof.md new file mode 100644 index 00000000..48ddfbd0 --- /dev/null +++ b/docs/solutions/LCR/xu-lie-hua-er-cha-shu-lcof.md @@ -0,0 +1,66 @@ +# [LCR 156. 序列化与反序列化二叉树](https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/) + +- 标签:树、深度优先搜索、广度优先搜索、设计、字符串、二叉树 +- 难度:困难 + +## 题目链接 + +- [LCR 156. 序列化与反序列化二叉树 - 力扣](https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/) + +## 题目大意 + +给定一棵二叉树的根节点 `root`。 + +要求:设计一个算法,来实现二叉树的序列化与反序列化。 + +## 解题思路 + +1. 序列化:将二叉树转为字符串数据表示 + +按照前序递归遍历二叉树,并将根节点跟左右子树的值链接起来(中间用 `,` 隔开)。 + +注意:如果遇到空节点,则标记为 'None',这样在反序列化时才能唯一确定一棵二叉树。 + +2. 反序列化:将字符串数据转为二叉树结构 + +先将字符串按 `,` 分割成数组。然后递归处理每一个元素。 + +- 从数组左侧取出一个元素。 + - 如果当前元素为 'None',则返回 None。 + - 如果当前元素不为空,则新建一个二叉树节点作为根节点,保存值为当前元素值。并递归遍历左右子树,不断重复从数组中取出元素,进行判断。 + - 最后返回当前根节点。 + +## 代码 + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + if not root: + return 'None' + return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right)) + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + def dfs(datalist): + val = datalist.pop(0) + if val == 'None': + return None + root = TreeNode(int(val)) + root.left = dfs(datalist) + root.right = dfs(datalist) + return root + + datalist = data.split(',') + return dfs(datalist) +``` + diff --git a/docs/solutions/LCR/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof.md b/docs/solutions/LCR/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof.md new file mode 100644 index 00000000..84571ab0 --- /dev/null +++ b/docs/solutions/LCR/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof.md @@ -0,0 +1,69 @@ +# [LCR 128. 库存管理 I](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 128. 库存管理 I - 力扣](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) + +## 题目大意 + +给定一个数组 `numbers`,`numbers` 是有升序数组经过「旋转」得到的。但是旋转次数未知。数组中可能存在重复元素。 + +要求:找出数组中的最小元素。 + +- 旋转:将数组整体右移。 + +## 解题思路 + +数组经过「旋转」之后,会有两种情况,第一种就是原先的升序序列,另一种是两段升序的序列。 + +第一种的最小值在最左边。第二种最小值在第二段升序序列的第一个元素。 + +``` + * + * + * + * + * +* +``` + + + +``` + * + * +* + * + * + * +``` + +最直接的办法就是遍历一遍,找到最小值。但是还可以有更好的方法。考虑用二分查找来降低算法的时间复杂度。 + +创建两个指针 left、right,分别指向数组首尾。让后计算出两个指针中间值 mid。将 mid 与右边界进行比较。 + +1. 如果 `numbers[mid] > numbers[right]`,则最小值不可能在 `mid` 左侧,一定在 `mid` 右侧,则将 `left` 移动到 `mid + 1` 位置,继续查找右侧区间。 +2. 如果 `numbers[mid] < numbers[right]`,则最小值一定在 `mid` 左侧,将 `right` 移动到 `mid` 位置上,继续查找左侧区间。 +3. 当 `numbers[mid] == numbers[right]`,无法判断在 `mid` 的哪一侧,可以采用 `right = right - 1` 逐步缩小区域。 + +## 代码 + +```python +class Solution: + def minArray(self, numbers: List[int]) -> int: + left = 0 + right = len(numbers) - 1 + while left < right: + mid = left + (right - left) // 2 + if numbers[mid] > numbers[right]: + left = mid + 1 + elif numbers[mid] < numbers[right]: + right = mid + else: + right = right - 1 + return numbers[left] +``` + diff --git a/docs/solutions/LCR/xx4gT2.md b/docs/solutions/LCR/xx4gT2.md new file mode 100644 index 00000000..16470ff7 --- /dev/null +++ b/docs/solutions/LCR/xx4gT2.md @@ -0,0 +1,157 @@ +# [LCR 076. 数组中的第 K 个最大元素](https://leetcode.cn/problems/xx4gT2/) + +- 标签:数组、分治、快速选择、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [LCR 076. 数组中的第 K 个最大元素 - 力扣](https://leetcode.cn/problems/xx4gT2/) + +## 题目大意 + +给定一个未排序的数组 `nums`,从中找到第 `k` 个最大的数字。 + +## 解题思路 + +很不错的一道题,面试常考。 + +直接可以想到的思路是:排序后输出数组上对应第 k 位大的数。所以问题关键在于排序方法的复杂度。 + +冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 + +可考虑堆排序、归并排序、快速排序。 + +这道题的要求是找到第 k 大的元素,使用归并排序只有到最后排序完毕才能返回第 k 大的数。而堆排序每次排序之后,就会确定一个元素的准确排名,同理快速排序也是如此。 + +### 1. 堆排序 + +升序堆排序的思路如下: + +1. 先建立大顶堆 + +2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值 + +3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值 + +4. 以此类推,直到最后一个元素交换之后完毕。 + +这道题我们只需进行 1 次建立大顶堆, k-1 次调整即可得到第 k 大的数。 + +时间复杂度:$O(n^2)$ + +### 2. 快速排序 + +快速排序每次调整,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了两个数组,前一个数组元素都比该元素小,后一个元素都比该元素大。 + +这样,只要某次划分的元素恰好是第 k 个下标就找到了答案。并且我们只需关注 k 元素所在区间的排序情况,与 k 元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 + +### 3. 借用标准库(不建议) + +提交代码中的最快代码是调用了 Python 的 heapq 库,或者 sort 方法。 +这样的确可以通过,但是不建议这样做。借用标准库实现,只能说对这个库的 API 和相关数据结构的用途相对熟悉,而不代表着掌握了这个数据结构。可以问问自己,如果换一种语言,自己还能不能实现对应的数据结构?刷题的本质目的是为了把算法学会学透,而不仅仅是调 API。 + +## 代码 + +1. 堆排序 + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + # 调整为大顶堆 + def heapify(nums, index, end): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if nums[left] > nums[max_index]: + max_index = left + if right <= end and nums[right] > nums[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(nums): + size = len(nums) + # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((size - 2) // 2, -1, -1): + heapify(nums, i, size - 1) + return nums + + buildMaxHeap(nums) + size = len(nums) + for i in range(k-1): + nums[0], nums[size-i-1] = nums[size-i-1], nums[0] + heapify(nums, 0, size-i-2) + return nums[0] +``` + +2. 快速排序 + +```python +import random +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + def randomPartition(nums, low, high): + i = random.randint(low, high) + nums[i], nums[high] = nums[high], nums[i] + return partition(nums, low, high) + + def partition(nums, low, high): + x = nums[high] + i = low-1 + for j in range(low, high): + if nums[j] <= nums[high]: + i += 1 + nums[i], nums[j] = nums[j], nums[i] + nums[i+1], nums[high] = nums[high], nums[i+1] + return i+1 + + def quickSort(nums, low, high, k): + n = len(nums) + if low < high: + pi = randomPartition(nums, low, high) + if pi == n-k: + return nums[len(nums)-k] + if pi > n-k: + quickSort(nums, low, pi-1, k) + if pi < n-k: + quickSort(nums, pi+1, high, k) + + return nums[len(nums)-k] + + return quickSort(nums, 0, len(nums)-1, k) +``` + +3. 借用标准库 + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + nums.sort() + return nums[len(nums)-k] +``` + +```python +import heapq +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + res = [] + for n in nums: + if len(res) < k: + heapq.heappush(res, n) + elif n > res[0]: + heapq.heappop(res) + heapq.heappush(res, n) + return heapq.heappop(res) +``` + + + diff --git a/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md b/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md new file mode 100644 index 00000000..636e2751 --- /dev/null +++ b/docs/solutions/LCR/yong-liang-ge-zhan-shi-xian-dui-lie-lcof.md @@ -0,0 +1,51 @@ +# [LCR 125. 图书整理 II](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) + +- 标签:栈、设计、队列 +- 难度:简单 + +## 题目链接 + +- [LCR 125. 图书整理 II - 力扣](https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/) + +## 题目大意 + +要求:使用两个栈实现先入先出队列。需要实现对应的两个函数: + +- `appendTail`:在队列尾部插入整数。 +- `deleteHead`:在队列头部删除整数(如果队列中没有元素,`deleteHead` 返回 -1)。 + +## 解题思路 + +使用两个栈,inStack 用于输入,outStack 用于输出。 + +- `appendTail` 操作:将元素压入 inStack 中 +- `deleteHead` 操作: + - 先判断 `inStack` 和 `outStack` 是否都为空,如果都为空则说明队列中没有元素,直接返回 `-1`。 + - 如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 + +## 代码 + +```python +class CQueue: + + def __init__(self): + self.inStack = [] + self.outStack = [] + + + def appendTail(self, value: int) -> None: + self.inStack.append(value) + + + def deleteHead(self) -> int: + if len(self.outStack) == 0 and len(self.inStack) == 0: + return -1 + if (len(self.outStack) == 0): + while (len(self.inStack) != 0): + self.outStack.append(self.inStack[-1]) + self.inStack.pop() + top = self.outStack[-1] + self.outStack.pop() + return top +``` + diff --git a/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md b/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md new file mode 100644 index 00000000..5fcd7d09 --- /dev/null +++ b/docs/solutions/LCR/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof.md @@ -0,0 +1,87 @@ +# [LCR 187. 破冰游戏](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) + +- 标签:递归、数学 +- 难度:简单 + +## 题目链接 + +- [LCR 187. 破冰游戏 - 力扣](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) + +## 题目大意 + +**描述**:$0$、$1$、…、$n - 1$ 这 $n$ 个数字排成一个圆圈,从数字 $0$ 开始,每次从圆圈里删除第 $m$ 个数字。现在给定整数 $n$ 和 $m$。 + +**要求**:求出这个圆圈中剩下的最后一个数字。 + +**说明**: + +- $1 \le num \le 10^5$。 +- $1 \le target \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:num = 7, target = 4 +输出:1 +``` + +- 示例 2: + +```python +输入:num = 12, target = 5 +输出:0 +``` + +## 解题思路 + +### 思路 1:枚举 + 模拟 + +模拟循环删除,需要进行 $n - 1$ 轮,每轮需要对节点进行 $m$ 次访问操作。总体时间复杂度为 $O(n \times m)$。 + +可以通过找规律来做,以 $n = 5$、$m = 3$ 为例。 + +- 刚开始为 $0$、$1$、$2$、$3$、$4$。 +- 第一次从 $0$ 开始数,数 $3$ 个数,于是 $2$ 出圈,变为 $3$、$4$、$0$、$1$。 +- 第二次从 $3$ 开始数,数 $3$ 个数,于是 $0$ 出圈,变为 $1$、$3$、$4$。 +- 第三次从 $1$ 开始数,数 $3$ 个数,于是 $4$ 出圈,变为 $1$、$3$。 +- 第四次从 $1$ 开始数,数 $3$ 个数,于是 $1$ 出圈,变为 $3$。 +- 所以最终为 $3$。 + +通过上面的流程可以发现:每隔 $m$ 个数就要删除一个数,那么被删除的这个数的下一个数就会成为新的起点。就相当于数组进行左移了 $m$ 位。反过来思考的话,从最后一步向前推,则每一步都向右移动了 $m$ 位(包括胜利者)。 + +如果用 $f(n, m)$ 表示: $n$ 个数构成环没删除 $m$ 个数后,最终胜利者的位置,则 $f(n, m) = f(n - 1, m) + m$。 + +即等于 $n - 1$ 个数构成的环没删除 $m$ 个数后最终胜利者的位置,像右移动 $m$ 次。 + +问题是现在并不是真的进行了右移,因为当前数组右移后超过数组容量的部分应该重新放到数组头部位置。所以公式应为:$f(n, m) = [f(n - 1, m) + m] \mod n$,$n$ 为反过来向前推的时候,每一步剩余的数字个数(比如第二步推回第一步,n $4$),则反过来递推公式为: + +- $f(1, m) = 0$。 +- $f(2, m) = [f(1, m) + m] \mod 2$。 +- $f(3, m) = [f(2, m) + m] \mod 3$。 +- 。。。。。。 + +- $f(n, m) = [f(n - 1, m) + m] \mod n $。 + +接下来就是递推求解了。 + +### 思路 1:代码 + +```python +class Solution: + def lastRemaining(self, n: int, m: int) -> int: + ans = 0 + for i in range(2, n + 1): + ans = (m + ans) % i + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + +## 参考资料: + +- [字节题库 - #剑62 - 简单 - 圆圈中最后剩下的数字 - 1刷](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/zi-jie-ti-ku-jian-62-jian-dan-yuan-quan-3hlji/) diff --git a/docs/solutions/LCR/z1R5dt.md b/docs/solutions/LCR/z1R5dt.md new file mode 100644 index 00000000..f833f895 --- /dev/null +++ b/docs/solutions/LCR/z1R5dt.md @@ -0,0 +1,91 @@ +# [LCR 066. 键值映射](https://leetcode.cn/problems/z1R5dt/) + +- 标签:设计、字典树、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 066. 键值映射 - 力扣](https://leetcode.cn/problems/z1R5dt/) + +## 题目大意 + +要求:实现一个 MapSum 类,支持两个方法,`insert` 和 `sum`: + +- `MapSum()` 初始化 MapSum 对象。 +- `void insert(String key, int val)` 插入 `key-val` 键值对,字符串表示键 `key`,整数表示值 `val`。如果键 `key` 已经存在,那么原来的键值对将被替代成新的键值对。 +- `int sum(string prefix)` 返回所有以该前缀 `prefix` 开头的键 `key` 的值的总和。 + +## 解题思路 + +可以构造前缀树(字典树)解题。 + +- 初始化时,构建一棵前缀树(字典树),并增加 `val` 变量。 + +- 调用插入方法时,用字典树存储 `key`,并在对应字母节点存储对应的 `val`。 +- 在调用查询总和方法时,先查找该前缀 `prefix` 对应的前缀树节点,从该节点开始,递归遍历该节点的子节点,并累积子节点的 `val`,进行求和,并返回求和累加结果。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.value = 0 + + + def insert(self, word: str, value: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.value = value + + + def search(self, word: str) -> int: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return 0 + cur = cur.children[ch] + return self.dfs(cur) + + def dfs(self, root) -> int: + if not root: + return 0 + res = root.value + for node in root.children.values(): + res += self.dfs(node) + return res + + + +class MapSum: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.trie_tree = Trie() + + + def insert(self, key: str, val: int) -> None: + self.trie_tree.insert(key, val) + + + def sum(self, prefix: str) -> int: + return self.trie_tree.search(prefix) +``` + diff --git a/docs/solutions/LCR/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof.md b/docs/solutions/LCR/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof.md new file mode 100644 index 00000000..73ea3076 --- /dev/null +++ b/docs/solutions/LCR/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof.md @@ -0,0 +1,62 @@ +# [LCR 172. 统计目标成绩的出现次数](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) + +- 标签:数组、二分查找 +- 难度:简单 + +## 题目链接 + +- [LCR 172. 统计目标成绩的出现次数 - 力扣](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) + +## 题目大意 + +给定一个排序数组 `nums`,以及一个整数 `target`。 + +要求:统计 `target` 在排序数组 `nums` 中出现的次数。 + +## 解题思路 + +两次二分查找。 + +- 先查找 `target` 第一次出现的位置(下标):`left`。 +- 再查找 `target` 最后一次出现的位置(下标):`right`。 +- 最终答案为 `right - left + 1`。 + +## 代码 + +```python +class Solution: + def searchLeft(self, nums, target): + left, right = 0, len(nums) - 1 + while left < right: + mid = left + (right - left) // 2 + if nums[mid] < target: + left = mid + 1 + else: + right = mid + if nums[left] == target: + return left + else: + return -1 + + def searchRight(self, nums, target): + left, right = 0, len(nums) - 1 + while left < right: + mid = left + (right - left + 1) // 2 + if nums[mid] <= target: + left = mid + else: + right = mid - 1 + return left + + def search(self, nums: List[int], target: int) -> int: + if len(nums) == 0: + return 0 + left = self.searchLeft(nums, target) + right = self.searchRight(nums, target) + + if left == -1: + return 0 + + return right - left + 1 +``` + diff --git a/docs/solutions/LCR/zhan-de-ya-ru-dan-chu-xu-lie-lcof.md b/docs/solutions/LCR/zhan-de-ya-ru-dan-chu-xu-lie-lcof.md new file mode 100644 index 00000000..ba715cab --- /dev/null +++ b/docs/solutions/LCR/zhan-de-ya-ru-dan-chu-xu-lie-lcof.md @@ -0,0 +1,35 @@ +# [LCR 148. 验证图书取出顺序](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) + +- 标签:栈、数组、模拟 +- 难度:中等 + +## 题目链接 + +- [LCR 148. 验证图书取出顺序 - 力扣](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) + +## 题目大意 + +给定连个整数序列 `pushed` 和 `popped`,其中 `pushed` 表示栈的压入顺序。 + +要求:判断第二个序列 `popped` 是否为栈的压出序列。 + +## 解题思路 + +借助一个栈来模拟压入、压出的操作。检测最后是否能模拟成功。 + +## 代码 + +```python +class Solution: + def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: + stack = [] + index = 0 + for item in pushed: + stack.append(item) + while(stack and stack[-1] == popped[index]): + stack.pop() + index += 1 + + return len(stack) == 0 +``` + diff --git a/docs/solutions/LCR/zhong-jian-er-cha-shu-lcof.md b/docs/solutions/LCR/zhong-jian-er-cha-shu-lcof.md new file mode 100644 index 00000000..d8c6b348 --- /dev/null +++ b/docs/solutions/LCR/zhong-jian-er-cha-shu-lcof.md @@ -0,0 +1,43 @@ +# [LCR 124. 推理二叉树](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/) + +- 标签:树、数组、哈希表、分治、二叉树 +- 难度:中等 + +## 题目链接 + +- [LCR 124. 推理二叉树 - 力扣](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/) + +## 题目大意 + +给定一棵二叉树的前序遍历结果和中序遍历结果。 + +要求:构建该二叉树,并返回其根节点。假设树中没有重复的元素。 + +## 解题思路 + +前序遍历的顺序是:根 -> 左 -> 右。中序遍历的顺序是:左 -> 根 -> 右。根据前序遍历的顺序,可以找到根节点位置。然后在中序遍历的结果中可以找到对应的根节点位置,就可以从根节点位置将二叉树分割成左子树、右子树。同时能得到左右子树的节点个数。此时构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历进行上述步骤,直到节点为空,具体操作步骤如下: + +- 从前序遍历顺序中当前根节点的位置在 `postorder[0]`。 +- 通过在中序遍历中查找上一步根节点对应的位置 `inorder[k]`,从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。 +- 从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。 +- 构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。 + +## 代码 + +```python +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: + def createTree(preorder, inorder, n): + if n == 0: + return None + k = 0 + while preorder[0] != inorder[k]: + k += 1 + node = TreeNode(inorder[k]) + node.left = createTree(preorder[1: k + 1], inorder[0: k], k) + node.right = createTree(preorder[k + 1:], inorder[k + 1:], n - k - 1) + return node + + return createTree(preorder, inorder, len(inorder)) +``` + diff --git a/docs/solutions/LCR/zi-fu-chuan-de-pai-lie-lcof.md b/docs/solutions/LCR/zi-fu-chuan-de-pai-lie-lcof.md new file mode 100644 index 00000000..c5cdf821 --- /dev/null +++ b/docs/solutions/LCR/zi-fu-chuan-de-pai-lie-lcof.md @@ -0,0 +1,54 @@ +# [LCR 157. 套餐内商品的排列顺序](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) + +- 标签:字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [LCR 157. 套餐内商品的排列顺序 - 力扣](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组,但里边不能有重复元素。 + +## 解题思路 + +因为原字符串可能含有重复元素,所以在回溯的时候需要进行去重。先将字符串 `s` 转为 `list` 列表,再对列表进行排序,然后使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 + +然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 + +然后进行回溯遍历。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + def backtrack(self, ls, visited): + if len(self.path) == len(ls): + self.res.append(''.join(self.path)) + return + for i in range(len(ls)): + if i > 0 and ls[i] == ls[i - 1] and not visited[i - 1]: + continue + + if not visited[i]: + visited[i] = True + self.path.append(ls[i]) + self.backtrack(ls, visited) + self.path.pop() + visited[i] = False + + def permutation(self, s: str) -> List[str]: + self.res.clear() + self.path.clear() + ls = list(s) + ls.sort() + visited = [False for _ in range(len(s))] + self.backtrack(ls, visited) + return self.res +``` + diff --git a/docs/solutions/LCR/zlDJc7.md b/docs/solutions/LCR/zlDJc7.md new file mode 100644 index 00000000..b70ff05c --- /dev/null +++ b/docs/solutions/LCR/zlDJc7.md @@ -0,0 +1,77 @@ +# [LCR 109. 打开转盘锁](https://leetcode.cn/problems/zlDJc7/) + +- 标签:广度优先搜索、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [LCR 109. 打开转盘锁 - 力扣](https://leetcode.cn/problems/zlDJc7/) + +## 题目大意 + +有一把带有四个数字的密码锁,每个位置上有 0~9 共 10 个数字。每次只能将其中一个位置上的数字转动一下。可以向上转,也可以向下转。比如:1 -> 2、2 -> 1。 + +密码锁的初始数字为:`0000`。现在给定一组表示死亡数字的字符串数组 `deadends`,和一个带有四位数字的目标字符串 `target`。 + +如果密码锁转动到 `deadends` 中任一字符串状态,则锁就会永久锁定,无法再次旋转。 + +要求:求出最小的选择次数,使得锁的状态由 `0000` 转动到 `target`。 + +## 解题思路 + +使用宽度优先搜索遍历,将`0000` 状态入队。 + +- 将队列中的元素出队,判断是否为死亡字符串 +- 如果为死亡字符串,则跳过该状态,否则继续执行。 + +- 如果为目标字符串,则返回当前路径长度,否则继续执行。 +- 枚举当前状态所有位置所能到达的所有状态,并判断是否访问过该状态。 + +- 如果之前出现过该状态,则继续执行,否则将其存入队列,并标记访问。 + +## 代码 + +```python +class Solution: + def openLock(self, deadends: List[str], target: str) -> int: + queue = collections.deque(['0000']) + visited = set(['0000']) + deadset = set(deadends) + level = 0 + while queue: + size = len(queue) + for _ in range(size): + cur = queue.popleft() + if cur in deadset: + continue + if cur == target: + return level + for i in range(len(cur)): + up = self.upward_adjust(cur, i) + if up not in visited: + queue.append(up) + visited.add(up) + down = self.downward_adjust(cur, i) + if down not in visited: + queue.append(down) + visited.add(down) + level += 1 + return -1 + + def upward_adjust(self, s, i): + s_list = list(s) + if s_list[i] == '9': + s_list[i] = '0' + else: + s_list[i] = chr(ord(s_list[i]) + 1) + return "".join(s_list) + + def downward_adjust(self, s, i): + s_list = list(s) + if s_list[i] == '0': + s_list[i] = '9' + else: + s_list[i] = chr(ord(s_list[i]) - 1) + return "".join(s_list) +``` + diff --git a/docs/solutions/LCR/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof.md b/docs/solutions/LCR/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof.md new file mode 100644 index 00000000..55f46821 --- /dev/null +++ b/docs/solutions/LCR/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof.md @@ -0,0 +1,40 @@ +# [LCR 167. 招式拆解 I](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) + +- 标签:哈希表、字符串、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [LCR 167. 招式拆解 I - 力扣](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) + +## 题目大意 + +给定一个字符串 `s`。 + +要求:找出其中不含有重复字符的最长子串的长度。 + +## 解题思路 + +利用集合来存储不重复的字符。用两个指针分别指向最长子串的左右节点。遍历字符串,右指针不断右移,利用集合来判断有没有重复的字符,如果没有,就持续向右扩大右边界。如果出现重复字符,就缩小左侧边界。每次移动终止,都要计算一下当前不含重复字符的子串长度,并判断一下是否需要更新最大长度。 + +## 代码 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + if not s: + return 0 + + letterSet = set() + right = 0 + ans = 0 + for i in range(len(s)): + if i != 0: + letterSet.remove(s[i - 1]) + while right < len(s) and s[right] not in letterSet: + letterSet.add(s[right]) + right += 1 + ans = max(ans, right - i) + return ans +``` + diff --git a/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md b/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md new file mode 100644 index 00000000..475b6eb6 --- /dev/null +++ b/docs/solutions/LCR/zui-xiao-de-kge-shu-lcof.md @@ -0,0 +1,170 @@ +# [LCR 159. 库存管理 III](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) + +- 标签:数组、分治、快速选择、排序、堆(优先队列) +- 难度:简单 + +## 题目链接 + +- [LCR 159. 库存管理 III - 力扣](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/) + +## 题目大意 + +**描述**:给定整数数组 $arr$,再给定一个整数 $k$。 + +**要求**:返回数组 $arr$ 中最小的 $k$ 个数。 + +**说明**: + +- $0 \le k \le arr.length \le 10000$。 +- $0 \le arr[i] \le 10000$。 + +**示例**: + +- 示例 1: + +```python +输入:arr = [3,2,1], k = 2 +输出:[1,2] 或者 [2,1] +``` + +- 示例 2: + +```python +输入:arr = [0,1,2,1], k = 1 +输出:[0] +``` + +## 解题思路 + +直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。 + +冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 + +可考虑堆排序、归并排序、快速排序。 + +### 思路 1:堆排序(基于大顶堆) + +具体做法如下: + +1. 使用数组前 $k$ 个元素,维护一个大小为 $k$ 的大顶堆。 +2. 遍历数组 $[k, size - 1]$ 的元素,判断其与堆顶元素关系,如果遇到比堆顶元素小的元素,则将与堆顶元素进行交换。再将这 $k$ 个元素调整为大顶堆。 +3. 最后输出大顶堆的 $k$ 个元素。 + +### 思路 1:代码 + +```python +class Solution: + def heapify(self, nums: [int], index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if nums[left] > nums[max_index]: + max_index = left + if right <= end and nums[right] > nums[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(self, nums: [int], k: int): + # (k-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((k - 2) // 2, -1, -1): + self.heapify(nums, i, k - 1) + return nums + + def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: + size = len(arr) + if k <= 0 or not arr: + return [] + if size <= k: + return arr + + self.buildMaxHeap(arr, k) + + for i in range(k, size): + if arr[i] < arr[0]: + arr[i], arr[0] = arr[0], arr[i] + self.heapify(arr, 0, k - 1) + + return arr[:k] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n\log_2k)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:快速排序 + +使用快速排序在每次调整时,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了左右两个子数组,左子数组中的元素都比该元素小,右子树组中的元素都比该元素大。 + +这样,只要某次划分的元素恰好是第 $k$ 个元素下标,就找到了数组中最小的 $k$ 个数所对应的区间,即 $[0, k - 1]$。 并且我们只需关注第 $k$ 个最小元素所在区间的排序情况,与第 $k$ 个最小元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。 + +### 思路 2:代码 + +```python +import random + +class Solution: + # 从 arr[low: high + 1] 中随机挑选一个基准数,并进行移动排序 + def randomPartition(self, arr: [int], low: int, high: int): + # 随机挑选一个基准数 + i = random.randint(low, high) + # 将基准数与最低位互换 + arr[i], arr[low] = arr[low], arr[i] + # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + return self.partition(arr, low, high) + + # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + def partition(self, arr: [int], low: int, high: int): + pivot = arr[low] # 以第 1 为为基准数 + i = low + 1 # 从基准数后 1 位开始遍历,保证位置 i 之前的元素都小于基准数 + + for j in range(i, high + 1): + # 发现一个小于基准数的元素 + if arr[j] < pivot: + # 将小于基准数的元素 arr[j] 与当前 arr[i] 进行换位,保证位置 i 之前的元素都小于基准数 + arr[i], arr[j] = arr[j], arr[i] + # i 之前的元素都小于基准数,所以 i 向右移动一位 + i += 1 + # 将基准节点放到正确位置上 + arr[i - 1], arr[low] = arr[low], arr[i - 1] + # 返回基准数位置 + return i - 1 + + def quickSort(self, arr, low, high, k): + size = len(arr) + if low < high: + # 按照基准数的位置,将序列划分为左右两个子序列 + pi = self.randomPartition(arr, low, high) + if pi == k: + return arr[:k] + if pi > k: + # 对左子序列进行递归快速排序 + self.quickSort(arr, low, pi - 1, k) + if pi < k: + # 对右子序列进行递归快速排序 + self.quickSort(arr, pi + 1, high, k) + + return arr[:k] + + def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: + size = len(arr) + if k >= size: + return arr + return self.quickSort(arr, 0, size - 1, k) +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。证明过程可参考「算法导论 9.2:期望为线性的选择算法」。 +- **空间复杂度**:$O(\log n)$。递归使用栈空间的空间代价期望为 $O(\log n)$。 + diff --git a/docs/solutions/LCR/zuo-xuan-zhuan-zi-fu-chuan-lcof.md b/docs/solutions/LCR/zuo-xuan-zhuan-zi-fu-chuan-lcof.md new file mode 100644 index 00000000..6b661c62 --- /dev/null +++ b/docs/solutions/LCR/zuo-xuan-zhuan-zi-fu-chuan-lcof.md @@ -0,0 +1,37 @@ +# [LCR 182. 动态口令](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) + +- 标签:数学、双指针、字符串 +- 难度:简单 + +## 题目链接 + +- [LCR 182. 动态口令 - 力扣](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) + +## 题目大意 + +给定一个字符串 `s` 和一个整数 `n`。 + +要求:将字符串 `s` 每个字符向左旋转 `n` 位。 + +- 左旋转:将字符串前面的若干字符转移到字符串的尾部。 + +## 解题思路 + +- 使用数组 `res` 存放答案。 +- 先遍历 `[n, len(s) - 1]` 范围的字符,将其存入数组。 +- 再遍历 `[0, n - 1]` 范围的字符,将其存入数组。 +- 将数组转为字符串返回。 + +## 代码 + +```python +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + res = [] + for i in range(n, len(s)): + res.append(s[i]) + for i in range(n): + res.append(s[i]) + return "".join(res) +``` + diff --git a/docs/solutions/index.md b/docs/solutions/index.md new file mode 100644 index 00000000..2b2745ad --- /dev/null +++ b/docs/solutions/index.md @@ -0,0 +1,31 @@ +## 本章内容 + +- [第 1 ~ 99 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0001-0099/) +- [第 100 ~ 199 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/) +- [第 200 ~ 299 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0200-0299/) +- [第 300 ~ 399 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0300-0399/) +- [第 400 ~ 499 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0400-0499/) +- [第 500 ~ 599 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0500-0599/) +- [第 600 ~ 699 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0600-0699/) +- [第 700 ~ 799 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0700-0799/) +- [第 800 ~ 899 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0800-0899/) +- [第 900 ~ 999 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0900-0999/) +- [第 1000 ~ 1099 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1000-1099/) +- [第 1100 ~ 1199 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1100-1199/) +- [第 1200 ~ 1299 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1200-1299/) +- [第 1300 ~ 1399 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1300-1399/) +- [第 1400 ~ 1499 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1400-1499/) +- [第 1500 ~ 1599 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1500-1599/) +- [第 1600 ~ 1699 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1600-1699/) +- [第 1700 ~ 1799 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1700-1799/) +- [第 1800 ~ 1899 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1800-1899/) +- [第 1900 ~ 1999 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/1900-1999/) +- [第 2000 ~ 2099 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2000-2099/) +- [第 2100 ~ 2199 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2100-2199/) +- [第 2200 ~ 2299 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2200-2299/) +- [第 2300 ~ 2399 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2300-2399/) +- [第 2400 ~ 2499 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2400-2499/) +- [第 2500 ~ 2599 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2500-2599/) +- [第 2700 ~ 2799 题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/2700-2799/) +- [LCR 系列](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/LCR/) +- [面试题](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/) diff --git a/docs/solutions/interviews/bracket-lcci.md b/docs/solutions/interviews/bracket-lcci.md new file mode 100644 index 00000000..afb671f1 --- /dev/null +++ b/docs/solutions/interviews/bracket-lcci.md @@ -0,0 +1,45 @@ +# [面试题 08.09. 括号](https://leetcode.cn/problems/bracket-lcci/) + +- 标签:字符串、动态规划、回溯 +- 难度:中等 + +## 题目链接 + +- [面试题 08.09. 括号 - 力扣](https://leetcode.cn/problems/bracket-lcci/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:生成所有有可能且有效的括号组合。 + +## 解题思路 + +通过回溯算法生成所有答案。为了生成的括号组合是有效的,回溯的时候,使用一个标记变量 `symbol` 来表示是否当前组合是否成对匹配。 + +如果在当前组合中增加一个 `(`,则 `symbol += 1`,如果增加一个 `)`,则 `symbol -= 1`。显然只有在 `symbol < n` 的时候,才能增加 `(`,在 `symbol > 0` 的时候,才能增加 `)`。 + +如果最终生成 `2 * n` 的括号组合,并且 `symbol == 0`,则说明当前组合是有效的,将其加入到最终答案数组中。 + +最终输出最终答案数组。 + +## 代码 + +```python +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + def backtrack(parenthesis, symbol, index): + if n * 2 == index: + if symbol == 0: + parentheses.append(parenthesis) + else: + if symbol < n: + backtrack(parenthesis + '(', symbol + 1, index + 1) + if symbol > 0: + backtrack(parenthesis + ')', symbol - 1, index + 1) + + parentheses = list() + backtrack("", 0, 0) + return parentheses +``` + diff --git a/docs/solutions/interviews/calculator-lcci.md b/docs/solutions/interviews/calculator-lcci.md new file mode 100644 index 00000000..ce90dafb --- /dev/null +++ b/docs/solutions/interviews/calculator-lcci.md @@ -0,0 +1,66 @@ +# [面试题 16.26. 计算器](https://leetcode.cn/problems/calculator-lcci/) + +- 标签:栈、数学、字符串 +- 难度:中等 + +## 题目链接 + +- [面试题 16.26. 计算器 - 力扣](https://leetcode.cn/problems/calculator-lcci/) + +## 题目大意 + +给定一个包含正整数、加(`+`)、减(`-`)、乘(`*`)、除(`/`)的算出表达式(括号除外)。表达式仅包含非负整数,`+`、`-`、`*`、`/` 四种运算符和空格 ` `。整数除法仅保留整数部分。 + +要求:计算其结果。 + +## 解题思路 + +计算表达式中,乘除运算优先于加减运算。我们可以先进行乘除运算,再将进行乘除运算后的整数值放入原表达式中相应位置,再依次计算加减。 + +可以考虑使用一个栈来保存进行乘除运算后的整数值。正整数直接压入栈中,负整数,则将对应整数取负号,再压入栈中。这样最终计算结果就是栈中所有元素的和。 + +具体做法: + +- 遍历字符串 s,使用变量 op 来标记数字之前的运算符,默认为 `+`。 +- 如果遇到数字,继续向后遍历,将数字进行累积,得到完整的整数 num。判断当前 op 的符号。 + - 如果 op 为 `+`,则将 num 压入栈中。 + - 如果 op 为 `-`,则将 -num 压入栈中。 + - 如果 op 为 `*`,则将栈顶元素 top 取出,计算 top * num,并将计算结果压入栈中。 + - 如果 op 为 `/`,则将栈顶元素 top 取出,计算 int(top / num),并将计算结果压入栈中。 +- 如果遇到 `+`、`-`、`*`、`/` 操作符,则更新 op。 +- 最后将栈中整数进行累加,并返回结果。 + +## 代码 + +```python +class Solution: + def calculate(self, s: str) -> int: + size = len(s) + stack = [] + op = '+' + index = 0 + while index < size: + if s[index] == ' ': + index += 1 + continue + if s[index].isdigit(): + num = ord(s[index]) - ord('0') + while index + 1 < size and s[index + 1].isdigit(): + index += 1 + num = 10 * num + ord(s[index]) - ord('0') + if op == '+': + stack.append(num) + elif op == '-': + stack.append(-num) + elif op == '*': + top = stack.pop() + stack.append(top * num) + elif op == '/': + top = stack.pop() + stack.append(int(top / num)) + elif s[index] in "+-*/": + op = s[index] + index += 1 + return sum(stack) +``` + diff --git a/docs/solutions/interviews/color-fill-lcci.md b/docs/solutions/interviews/color-fill-lcci.md new file mode 100644 index 00000000..0581382e --- /dev/null +++ b/docs/solutions/interviews/color-fill-lcci.md @@ -0,0 +1,66 @@ +# [面试题 08.10. 颜色填充](https://leetcode.cn/problems/color-fill-lcci/) + +- 标签:深度优先搜索、广度优先搜索、数组、矩阵 +- 难度:简单 + +## 题目链接 + +- [面试题 08.10. 颜色填充 - 力扣](https://leetcode.cn/problems/color-fill-lcci/) + +## 题目大意 + +给定一个二维整数矩阵 `image`,其中 `image[i][j]` 表示矩阵第 `i` 行、第 `j` 列上网格块的颜色值。再给定一个起始位置 `(sr, sc)`,以及一个目标颜色 `newColor`。 + +要求:对起始位置 `(sr, sc)` 所在位置周围区域填充颜色为 `newColor`。并返回填充后的图像 `image`。 + +- 周围区域:颜色相同且在上、下、左、右四个方向上存在相连情况的若干元素。 + +## 解题思路 + +深度优先搜索。使用二维数组 `visited` 标记访问过的节点。遍历上、下、左、右四个方向上的点。如果下一个点位置越界,或者当前位置与下一个点位置颜色不一样,则对该节点进行染色。 + +在遍历的过程中注意使用 `visited` 标记访问过的节点,以免重复遍历。 + +## 代码 + +```python +class Solution: + directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(self, image, i, j, origin_color, color, visited): + rows, cols = len(image), len(image[0]) + + for direct in self.directs: + new_i = i + direct[0] + new_j = j + direct[1] + + # 下一个位置越界,则当前点在边界,对其进行着色 + if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols: + image[i][j] = color + continue + + # 如果访问过,则跳过 + if visited[new_i][new_j]: + continue + + # 如果下一个位置颜色与当前颜色相同,则继续搜索 + if image[new_i][new_j] == origin_color: + visited[new_i][new_j] = True + self.dfs(image, new_i, new_j, origin_color, color, visited) + # 下一个位置颜色与当前颜色不同,则当前位置为连通区域边界,对其进行着色 + else: + image[i][j] = color + + def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: + if not image: + return image + + rows, cols = len(image), len(image[0]) + visited = [[False for _ in range(cols)] for _ in range(rows)] + visited[sr][sc] = True + + self.dfs(image, sr, sc, image[sr][sc], newColor, visited) + + return image +``` + diff --git a/docs/solutions/interviews/eight-queens-lcci.md b/docs/solutions/interviews/eight-queens-lcci.md new file mode 100644 index 00000000..d1447849 --- /dev/null +++ b/docs/solutions/interviews/eight-queens-lcci.md @@ -0,0 +1,77 @@ +# [面试题 08.12. 八皇后](https://leetcode.cn/problems/eight-queens-lcci/) + +- 标签:数组、回溯 +- 难度:困难 + +## 题目链接 + +- [面试题 08.12. 八皇后 - 力扣](https://leetcode.cn/problems/eight-queens-lcci/) + +## 题目大意 + +- n 皇后问题:将 n 个皇后放置在 `n * n` 的棋盘上,并且使得皇后彼此之间不能攻击。 +- 皇后彼此不能相互攻击:指的是任何两个皇后都不能处于同一条横线、纵线或者斜线上。 + +现在给定一个整数 `n`,返回所有不同的「n 皇后问题」的解决方案。每一种解法包含一个不同的「n 皇后问题」的棋子放置方案,该方案中的 `Q` 和 `.` 分别代表了皇后和空位。 + +## 解题思路 + +经典的回溯问题。使用 `chessboard` 来表示棋盘,`Q` 代表皇后,`.` 代表空位,初始都为 `.`。然后使用 `res` 存放最终答案。 + +先定义棋盘合理情况判断方法,判断同一条横线、纵线或者斜线上是否存在两个以上的皇后。 + +再定义回溯方法,从第一行开始进行遍历。 + +- 如果当前行 `row` 等于 `n`,则当前棋盘为一个可行方案,将其拼接加入到 `res` 数组中。 +- 遍历 `[0, n]` 列元素,先验证棋盘是否可行,如果可行: + - 将当前行当前列尝试换为 `Q`。 + - 然后继续递归下一行。 + - 再将当前行回退为 `.`。 +- 最终返回 `res` 数组。 + +## 代码 + +```python +class Solution: + res = [] + def backtrack(self, n: int, row: int, chessboard: List[List[str]]): + if row == n: + temp_res = [] + for temp in chessboard: + temp_str = ''.join(temp) + temp_res.append(temp_str) + self.res.append(temp_res) + return + for col in range(n): + if self.isValid(n, row, col, chessboard): + chessboard[row][col] = 'Q' + self.backtrack(n, row + 1, chessboard) + chessboard[row][col] = '.' + + def isValid(self, n: int, row: int, col: int, chessboard: List[List[str]]): + for i in range(row): + if chessboard[i][col] == 'Q': + return False + + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j -= 1 + i, j = row - 1, col + 1 + while i >= 0 and j < n: + if chessboard[i][j] == 'Q': + return False + i -= 1 + j += 1 + + return True + + def solveNQueens(self, n: int) -> List[List[str]]: + self.res.clear() + chessboard = [['.' for _ in range(n)] for _ in range(n)] + self.backtrack(n, 0, chessboard) + return self.res +``` + diff --git a/docs/solutions/interviews/factorial-zeros-lcci.md b/docs/solutions/interviews/factorial-zeros-lcci.md new file mode 100644 index 00000000..6f3f8770 --- /dev/null +++ b/docs/solutions/interviews/factorial-zeros-lcci.md @@ -0,0 +1,33 @@ +# [面试题 16.05. 阶乘尾数](https://leetcode.cn/problems/factorial-zeros-lcci/) + +- 标签:数学 +- 难度:简单 + +## 题目链接 + +- [面试题 16.05. 阶乘尾数 - 力扣](https://leetcode.cn/problems/factorial-zeros-lcci/) + +## 题目大意 + +给定一个整数 `n`。 + +要求:计算 `n` 的阶乘中尾随零的数量。 + +注意:$0 <= n <= 10^4$。 + +## 解题思路 + +阶乘中,末尾 `0` 的来源只有 `2 * 5`。所以尾随 `0` 的个数为 `2` 的倍数个数和 `5` 的倍数个数的最小值。又因为 `2 < 5`,`2` 的倍数个数肯定小于等于 `5` 的倍数,所以直接统计 `5` 的倍数个数即可。 + +## 代码 + +```python +class Solution: + def trailingZeroes(self, n: int) -> int: + count = 0 + while n > 0: + count += n // 5 + n = n // 5 + return count +``` + diff --git a/docs/solutions/interviews/first-common-ancestor-lcci.md b/docs/solutions/interviews/first-common-ancestor-lcci.md new file mode 100644 index 00000000..f1fb498b --- /dev/null +++ b/docs/solutions/interviews/first-common-ancestor-lcci.md @@ -0,0 +1,61 @@ +# [面试题 04.08. 首个共同祖先](https://leetcode.cn/problems/first-common-ancestor-lcci/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [面试题 04.08. 首个共同祖先 - 力扣](https://leetcode.cn/problems/first-common-ancestor-lcci/) + +## 题目大意 + +给定一个二叉树,要求找到该树中指定节点 `p`、`q` 的最近公共祖先: + +- 祖先:若节点 `p` 在节点 `node` 的左子树或右子树中,或者 `p = node`,则称 `node` 是 `p` 的祖先。 + +- 最近公共祖先:对于树的两个节点 `p`、`q`,最近公共祖先表示为一个节点 `lca_node`,满足 `lca_node` 是 `p`、`q` 的祖先且 `lca_node` 的深度尽可能大(一个节点也可以是自己的祖先)。 + +## 解题思路 + +设 `lca_node` 为节点 `p`、`q` 的最近公共祖先。则 `lca_node` 只能是下面几种情况: + +- `p`、`q` 在 `lca_node` 的子树中,且分别在 `lca_node` 的两侧子树中。 +- `p == lca_node`,且 `q` 在 `lca_node` 的左子树或右子树中。 +- `q == lca_node`,且 `p` 在 `lca_node` 的左子树或右子树中。 + +下面递归求解 `lca_node`。递归需要满足以下条件: + +- 如果 `p`、`q` 都不为空,则返回 `p`、`q` 的公共祖先。 +- 如果 `p`、`q` 只有一个存在,则返回存在的一个。 +- 如果 `p`、`q` 都不存在,则返回存在的一个。 + +具体思路为: + +- 如果当前节点 `node` 为 `None`,则说明 `p`、`q` 不在 `node` 的子树中,不可能为公共祖先,直接返回 `None`。 +- 如果当前节点 `node` 等于 `p` 或者 `q`,那么 `node` 就是 `p`、`q` 的最近公共祖先,直接返回 `node`。 +- 递归遍历左子树、右子树,并判断左右子树结果。 + - 如果左子树为空,则返回右子树。 + - 如果右子树为空,则返回左子树。 + - 如果左右子树都不为空,则说明 `p`、`q` 在当前根节点的两侧,当前根节点就是他们的最近公共祖先。 + - 如果左右子树都为空,则返回空。 + +## 代码 + +```python +class Solution: + def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: + if root == p or root == q: + return root + + if root: + node_left = self.lowestCommonAncestor(root.left, p, q) + node_right = self.lowestCommonAncestor(root.right, p, q) + if node_left and node_right: + return root + elif not node_left: + return node_right + else: + return node_left + return None +``` + diff --git a/docs/solutions/interviews/group-anagrams-lcci.md b/docs/solutions/interviews/group-anagrams-lcci.md new file mode 100644 index 00000000..ccde9231 --- /dev/null +++ b/docs/solutions/interviews/group-anagrams-lcci.md @@ -0,0 +1,42 @@ +# [面试题 10.02. 变位词组](https://leetcode.cn/problems/group-anagrams-lcci/) + +- 标签:数组、哈希表、字符串、排序 +- 难度:中等 + +## 题目链接 + +- [面试题 10.02. 变位词组 - 力扣](https://leetcode.cn/problems/group-anagrams-lcci/) + +## 题目大意 + +给定一个字符串数组 `strs`。 + +要求:将所有变位词组合在一起。不需要考虑输出顺序。 + +- 变位词:字母相同,但排列不同的字符串。 + +## 解题思路 + +使用哈希表记录变位词。对每一个字符串进行排序,按照 `排序字符串:变位词数组` 的键值顺序进行存储。 + +最终将哈希表的值转换为对应数组返回结果。 + +## 代码 + +```python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + str_dict = dict() + res = [] + for s in strs: + sort_s = str(sorted(s)) + if sort_s in str_dict: + str_dict[sort_s] += [s] + else: + str_dict[sort_s] = [s] + + for sort_s in str_dict: + res += [str_dict[sort_s]] + return res +``` + diff --git a/docs/solutions/interviews/implement-queue-using-stacks-lcci.md b/docs/solutions/interviews/implement-queue-using-stacks-lcci.md new file mode 100644 index 00000000..64993a2f --- /dev/null +++ b/docs/solutions/interviews/implement-queue-using-stacks-lcci.md @@ -0,0 +1,74 @@ +# [面试题 03.04. 化栈为队](https://leetcode.cn/problems/implement-queue-using-stacks-lcci/) + +- 标签:栈、设计、队列 +- 难度:简单 + +## 题目链接 + +- [面试题 03.04. 化栈为队 - 力扣](https://leetcode.cn/problems/implement-queue-using-stacks-lcci/) + +## 题目大意 + +要求:实现一个 MyQueue 类,要求仅使用两个栈实现先入先出队列。 + +## 解题思路 + +使用两个栈,`inStack` 用于输入,`outStack` 用于输出。 + +- `push` 操作:将元素压入 `inStack` 中。 +- `pop` 操作:如果 `outStack` 输出栈为空,将 `inStack` 输入栈元素依次取出,按顺序压入 `outStack` 栈。这样 `outStack` 栈的元素顺序和之前 `inStack` 元素顺序相反,`outStack` 顶层元素就是要取出的队头元素,将其移出,并返回该元素。如果 `outStack` 输出栈不为空,则直接取出顶层元素。 +- `peek` 操作:和 `pop` 操作类似,只不过最后一步不需要取出顶层元素,直接将其返回即可。 +- `empty` 操作:如果 `inStack` 和 `outStack` 都为空,则队列为空,否则队列不为空。 + +## 代码 + +```python +class MyQueue: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.inStack = [] + self.outStack = [] + + + def push(self, x: int) -> None: + """ + Push element x to the back of queue. + """ + self.inStack.append(x) + + + def pop(self) -> int: + """ + Removes the element from in front of queue and returns that element. + """ + if (len(self.outStack) == 0): + while (len(self.inStack) != 0): + self.outStack.append(self.inStack[-1]) + self.inStack.pop() + top = self.outStack[-1] + self.outStack.pop() + return top + + + def peek(self) -> int: + """ + Get the front element. + """ + if (len(self.outStack) == 0): + while (len(self.inStack) != 0): + self.outStack.append(self.inStack[-1]) + self.inStack.pop() + top = self.outStack[-1] + return top + + + def empty(self) -> bool: + """ + Returns whether the queue is empty. + """ + return len(self.outStack) == 0 and len(self.inStack) == 0 +``` + diff --git a/docs/solutions/interviews/index.md b/docs/solutions/interviews/index.md new file mode 100644 index 00000000..3410fb8b --- /dev/null +++ b/docs/solutions/interviews/index.md @@ -0,0 +1,32 @@ +## 本章内容 + +- [面试题 01.07. 旋转矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/rotate-matrix-lcci.md) +- [面试题 01.08. 零矩阵](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/zero-matrix-lcci.md) +- [面试题 02.02. 返回倒数第 k 个节点](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/kth-node-from-end-of-list-lcci.md) +- [面试题 02.05. 链表求和](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sum-lists-lcci.md) +- [面试题 02.06. 回文链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/palindrome-linked-list-lcci.md) +- [面试题 02.07. 链表相交](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/intersection-of-two-linked-lists-lcci.md) +- [面试题 02.08. 环路检测](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/linked-list-cycle-lcci.md) +- [面试题 03.02. 栈的最小值](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/min-stack-lcci.md) +- [面试题 03.04. 化栈为队](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/implement-queue-using-stacks-lcci.md) +- [面试题 04.02. 最小高度树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/minimum-height-tree-lcci.md) +- [面试题 04.05. 合法二叉搜索树](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/legal-binary-search-tree-lcci.md) +- [面试题 04.06. 后继者](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/successor-lcci.md) +- [面试题 04.08. 首个共同祖先](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/first-common-ancestor-lcci.md) +- [面试题 04.12. 求和路径](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/paths-with-sum-lcci.md) +- [面试题 08.04. 幂集](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/power-set-lcci.md) +- [面试题 08.07. 无重复字符串的排列组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/permutation-i-lcci.md) +- [面试题 08.08. 有重复字符串的排列组合](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/permutation-ii-lcci.md) +- [面试题 08.09. 括号](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/bracket-lcci.md) +- [面试题 08.10. 颜色填充](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/color-fill-lcci.md) +- [面试题 08.12. 八皇后](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/eight-queens-lcci.md) +- [面试题 10.01. 合并排序的数组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sorted-merge-lcci.md) +- [面试题 10.02. 变位词组](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/group-anagrams-lcci.md) +- [面试题 10.09. 排序矩阵查找](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/sorted-matrix-search-lcci.md) +- [面试题 16.02. 单词频率](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/words-frequency-lcci.md) +- [面试题 16.05. 阶乘尾数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/factorial-zeros-lcci.md) +- [面试题 16.26. 计算器](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/calculator-lcci.md) +- [面试题 17.06. 2出现的次数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/number-of-2s-in-range-lcci.md) +- [面试题 17.14. 最小K个数](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/smallest-k-lcci.md) +- [面试题 17.15. 最长单词](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/longest-word-lcci.md) +- [面试题 17.17. 多次搜索](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/interviews/multi-search-lcci.md) diff --git a/docs/solutions/interviews/intersection-of-two-linked-lists-lcci.md b/docs/solutions/interviews/intersection-of-two-linked-lists-lcci.md new file mode 100644 index 00000000..a985f070 --- /dev/null +++ b/docs/solutions/interviews/intersection-of-two-linked-lists-lcci.md @@ -0,0 +1,54 @@ +# [面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) + +- 标签:哈希表、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [面试题 02.07. 链表相交 - 力扣](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) + +## 题目大意 + +给定两个链表的头节点 `headA`、`headB`。 + +要求:找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 `None` 。 + +比如:链表 A 为 `[4, 1, 8, 4, 5]`,链表 B 为 `[5, 0, 1, 8, 4, 5]`。则如下图所示,两个链表相交的起始节点为 `8`,则输出结果为 `8`。 + +![](https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png) + + + + + +## 解题思路 + +如果两个链表相交,那么从相交位置开始,到结束,必有一段等长且相同的节点。假设链表 `A` 的长度为 `m`、链表 `B` 的长度为 `n`,他们的相交序列有 `k` 个,则相交情况可以如下如所示: + +![](https://qcdn.itcharge.cn/images/20210401113538.png) + +现在问题是如何找到 `m - k` 或者 `n - k` 的位置。 + +考虑将链表 `A` 的末尾拼接上链表 `B`,链表 `B` 的末尾拼接上链表 `A`。 + +然后使用两个指针 `pA` 、`pB`,分别从链表 `A`、链表 `B` 的头节点开始遍历,如果走到共同的节点,则返回该节点。 + +否则走到两个链表末尾,返回 `None`。 + +![](https://qcdn.itcharge.cn/images/20210401114100.png) + +## 代码 + +```python +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + if headA == None or headB == None: + return None + pA = headA + pB = headB + while pA != pB : + pA = pA.next if pA != None else headB + pB = pB.next if pB != None else headA + return pA +``` + diff --git a/docs/solutions/interviews/kth-node-from-end-of-list-lcci.md b/docs/solutions/interviews/kth-node-from-end-of-list-lcci.md new file mode 100644 index 00000000..adec3b65 --- /dev/null +++ b/docs/solutions/interviews/kth-node-from-end-of-list-lcci.md @@ -0,0 +1,38 @@ +# [面试题 02.02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) + +- 标签:链表、双指针 +- 难度:简单 + +## 题目链接 + +- [面试题 02.02. 返回倒数第 k 个节点 - 力扣](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) + +## 题目大意 + +给定一个链表的头节点 `head`,以及一个整数 `k`。 + +要求:返回链表的倒数第 `k` 个节点的值。 + +## 解题思路 + +常规思路是遍历一遍链表,求出链表长度,再遍历一遍到对应位置,返回该位置上的节点。 + +如果用一次遍历实现的话,可以使用快慢指针。让快指针先走 `k` 步,然后快慢指针、慢指针再同时走,每次一步,这样等快指针遍历到链表尾部的时候,慢指针就刚好遍历到了倒数第 `k` 个节点位置。返回该该位置上的节点即可。 + +## 代码 + +```python +class Solution: + def kthToLast(self, head: ListNode, k: int) -> int: + slow = head + fast = head + for _ in range(k): + if fast == None: + return fast + fast = fast.next + while fast: + slow = slow.next + fast = fast.next + return slow.val +``` + diff --git a/docs/solutions/interviews/legal-binary-search-tree-lcci.md b/docs/solutions/interviews/legal-binary-search-tree-lcci.md new file mode 100644 index 00000000..d78f5baf --- /dev/null +++ b/docs/solutions/interviews/legal-binary-search-tree-lcci.md @@ -0,0 +1,46 @@ +# [面试题 04.05. 合法二叉搜索树](https://leetcode.cn/problems/legal-binary-search-tree-lcci/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [面试题 04.05. 合法二叉搜索树 - 力扣](https://leetcode.cn/problems/legal-binary-search-tree-lcci/) + +## 题目大意 + +给定一个二叉树的根节点 `root`。 + +要求:检查该二叉树是否为二叉搜索树。 + +二叉搜索树特征: + +- 节点的左子树只包含小于当前节点的数。 +- 节点的右子树只包含大于当前节点的数。 +- 所有左子树和右子树自身必须也是二叉搜索树。 + +## 解题思路 + +根据题意进行递归遍历即可。前序、中序、后序遍历都可以。 + +以前序遍历为例,递归函数为:`preorderTraversal(root, min_v, max_v)` + +前序遍历时,先判断根节点的值是否在 `(min_v, max_v)` 之间。如果不在则直接返回 `False`。在区间内,则继续递归检测左右子树是否满足,都满足才是一棵二叉搜索树。 + +递归遍历左子树的时候,要将上界 `max_v` 改为左子树的根节点值,因为左子树上所有节点的值均小于根节点的值。同理,遍历右子树的时候,要将下界 `min_v` 改为右子树的根节点值,因为右子树上所有节点的值均大于根节点。 + +## 代码 + +```python +class Solution: + def isValidBST(self, root: TreeNode) -> bool: + def preorderTraversal(root, min_v, max_v): + if root == None: + return True + if root.val >= max_v or root.val <= min_v: + return False + return preorderTraversal(root.left, min_v, root.val) and preorderTraversal(root.right, root.val, max_v) + + return preorderTraversal(root, float('-inf'), float('inf')) +``` + diff --git a/docs/solutions/interviews/linked-list-cycle-lcci.md b/docs/solutions/interviews/linked-list-cycle-lcci.md new file mode 100644 index 00000000..bc98e81f --- /dev/null +++ b/docs/solutions/interviews/linked-list-cycle-lcci.md @@ -0,0 +1,47 @@ +# [面试题 02.08. 环路检测](https://leetcode.cn/problems/linked-list-cycle-lcci/) + +- 标签:哈希表、链表、双指针 +- 难度:中等 + +## 题目链接 + +- [面试题 02.08. 环路检测 - 力扣](https://leetcode.cn/problems/linked-list-cycle-lcci/) + +## 题目大意 + +给定一个链表的头节点 `head`。 + +要求:判断链表中是否有环,如果有环则返回入环的第一个节点,无环则返回 None。 + +## 解题思路 + +利用两个指针,一个慢指针每次前进一步,快指针每次前进两步(两步或多步效果是等价的)。如果两个指针在链表头节点以外的某一节点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了某个没有后继指针的节点时,那么说明没环。 + +如果有环,则再定义一个指针,和慢指针一起每次移动一步,两个指针相遇的位置即为入口节点。 + +这是因为:假设入环位置为 A,快慢指针在在 B 点相遇,则相遇时慢指针走了 $a + b$ 步,快指针走了 $a + n(b+c) + b$ 步。 + +$2(a + b) = a + n(b + c) + b$。可以推出:$a = c + (n-1)(b + c)$。 + +我们可以发现:从相遇点到入环点的距离 $c$ 加上 $n-1$ 圈的环长 $b + c$ 刚好等于从链表头部到入环点的距离。 + +## 代码 + +```python +class Solution: + def detectCycle(self, head: ListNode) -> ListNode: + fast, slow = head, head + while True: + if not fast or not fast.next: + return None + fast = fast.next.next + slow = slow.next + if fast == slow: + break + + ans = head + while ans != slow: + ans, slow = ans.next, slow.next + return ans +``` + diff --git a/docs/solutions/interviews/longest-word-lcci.md b/docs/solutions/interviews/longest-word-lcci.md new file mode 100644 index 00000000..015aab7e --- /dev/null +++ b/docs/solutions/interviews/longest-word-lcci.md @@ -0,0 +1,95 @@ +# [面试题 17.15. 最长单词](https://leetcode.cn/problems/longest-word-lcci/) + +- 标签:字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [面试题 17.15. 最长单词 - 力扣](https://leetcode.cn/problems/longest-word-lcci/) + +## 题目大意 + +给定一组单词 `words`。 + +要求:找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。若有多个长度相同的结果,返回其中字典序最小的一项,若没有符合要求的单词则返回空字符串。 + +## 解题思路 + +先将所有单词按照长度从长到短排序,相同长度的字典序小的排在前面。然后将所有单词存入字典树中。 + +然后一重循环遍历所有单词 `word`,二重循环遍历单词中所有字符 `word[i]`。 + +如果当前遍历的字符为单词末尾,递归判断从 `i + 1` 位置开始,剩余部分是否可以切分为其他单词组合,如果可以切分,则返回当前单词 `word`。如果不可以切分,则返回空字符串 `""`。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return False + cur = cur.children[ch] + + return cur is not None and cur.isEnd + + def splitToWord(self, remain): + if not remain or remain == "": + return True + cur = self + for i in range(len(remain)): + ch = remain[i] + if ch not in cur.children: + return False + if cur.children[ch].isEnd and self.splitToWord(remain[i + 1:]): + return True + cur = cur.children[ch] + return False + + def dfs(self, words): + for word in words: + cur = self + size = len(word) + for i in range(size): + ch = word[i] + if i < size - 1 and cur.children[ch].isEnd and self.splitToWord(word[i+1:]): + return word + cur = cur.children[ch] + return "" + +class Solution: + def longestWord(self, words: List[str]) -> str: + words.sort(key=lambda x: (-len(x), x)) + trie_tree = Trie() + for word in words: + trie_tree.insert(word) + + ans = trie_tree.dfs(words) + return ans +``` + diff --git a/docs/solutions/interviews/min-stack-lcci.md b/docs/solutions/interviews/min-stack-lcci.md new file mode 100644 index 00000000..44a538ff --- /dev/null +++ b/docs/solutions/interviews/min-stack-lcci.md @@ -0,0 +1,59 @@ +# [面试题 03.02. 栈的最小值](https://leetcode.cn/problems/min-stack-lcci/) + +- 标签:栈、设计 +- 难度:简单 + +## 题目链接 + +- [面试题 03.02. 栈的最小值 - 力扣](https://leetcode.cn/problems/min-stack-lcci/) + +## 题目大意 + +设计一个「栈」,要求实现 `push` ,`pop` ,`top` ,`getMin` 操作,其中 `getMin` 要求能在常数时间内实现。 + +## 解题思路 + +使用一个栈,栈元素中除了保存当前值之外,再保存一个当前最小值。 + +- `push` 操作:如果栈不为空,则判断当前值与栈顶元素所保存的最小值,并更新当前最小值,将新元素保存到栈中。 +- `pop`操作:正常出栈 +- `top` 操作:返回栈顶元素保存的值。 +- `getMin` 操作:返回栈顶元素保存的最小值。 + +## 代码 + +```python +class MinStack: + + def __init__(self): + """ + initialize your data structure here. + """ + self.stack = [] + + class Node: + def __init__(self, x): + self.val = x + self.min = x + + def push(self, x: int) -> None: + node = self.Node(x) + if len(self.stack) == 0: + self.stack.append(node) + else: + topNode = self.stack[-1] + if node.min > topNode.min: + node.min = topNode.min + + self.stack.append(node) + + def pop(self) -> None: + self.stack.pop() + + def top(self) -> int: + return self.stack[-1].val + + def getMin(self) -> int: + return self.stack[-1].min +``` + diff --git a/docs/solutions/interviews/minimum-height-tree-lcci.md b/docs/solutions/interviews/minimum-height-tree-lcci.md new file mode 100644 index 00000000..7b22bf8f --- /dev/null +++ b/docs/solutions/interviews/minimum-height-tree-lcci.md @@ -0,0 +1,34 @@ +# [面试题 04.02. 最小高度树](https://leetcode.cn/problems/minimum-height-tree-lcci/) + +- 标签:树、二叉搜索树、数组、分治、二叉树 +- 难度:简单 + +## 题目链接 + +- [面试题 04.02. 最小高度树 - 力扣](https://leetcode.cn/problems/minimum-height-tree-lcci/) + +## 题目大意 + +给定一个升序的有序数组 `nums`。 + +要求:创建一棵高度最小的二叉搜索树(高度平衡的二叉搜索树)。 + +## 解题思路 + +直观上,如果把数组的中间元素当做根,那么数组左侧元素都小于根节点,右侧元素都大于根节点,且左右两侧元素个数相同,或最多相差 `1` 个。那么构建的树高度差也不会超过 `1`。所以猜想出:如果左右子树约平均,树就越平衡。这样我们就可以每次取中间元素作为当前的根节点,两侧的元素作为左右子树递归建树,左侧区间 `[L, mid - 1]` 作为左子树,右侧区间 `[mid + 1, R]` 作为右子树。 + +## 代码 + +```python +class Solution: + def sortedArrayToBST(self, nums: List[int]) -> TreeNode: + size = len(nums) + if size == 0: + return None + mid = size // 2 + root = TreeNode(nums[mid]) + root.left = Solution.sortedArrayToBST(self, nums[:mid]) + root.right = Solution.sortedArrayToBST(self, nums[mid + 1:]) + return root +``` + diff --git a/docs/solutions/interviews/multi-search-lcci.md b/docs/solutions/interviews/multi-search-lcci.md new file mode 100644 index 00000000..12891e29 --- /dev/null +++ b/docs/solutions/interviews/multi-search-lcci.md @@ -0,0 +1,80 @@ +# [面试题 17.17. 多次搜索](https://leetcode.cn/problems/multi-search-lcci/) + +- 标签:字典树、数组、哈希表、字符串、字符串匹配、滑动窗口 +- 难度:中等 + +## 题目链接 + +- [面试题 17.17. 多次搜索 - 力扣](https://leetcode.cn/problems/multi-search-lcci/) + +## 题目大意 + +给定一个较长字符串 `big` 和一个包含较短字符串的数组 `smalls`。 + +要求:设计一个方法,根据 `smalls` 中的每一个较短字符串,对 `big` 进行搜索。输出 `smalls` 中的字符串在 `big` 里出现的所有位置 `positions`,其中 `positions[i]` 为 `smalls[i]` 出现的所有位置。 + +## 解题思路 + +构建字典树,将 `smalls` 中所有字符串存入字典树中,并在字典树中记录下插入字符串的顺序下标。 + +然后一重循环遍历 `big`,表示从第 `i` 位置开始的字符串 `big[i:]`。然后在字符串前缀中搜索对应的单词,将所有符合要求的单词插入顺序位置存入列表中,返回列表。 + +对于列表中每个单词插入下标顺序 `index` 和 `big[i:]` 来说, `i` 就是 `smalls` 中第 `index` 个字符串所对应在 `big` 中的开始位置,将其存入答案数组并返回即可。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.index = -1 + + + def insert(self, word: str, index: int) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.index = index + + + def search(self, text: str) -> list: + """ + Returns if the word is in the trie. + """ + cur = self + res = [] + for i in range(len(text)): + ch = text[i] + if ch not in cur.children: + return res + cur = cur.children[ch] + if cur.isEnd: + res.append(cur.index) + return res + +class Solution: + def multiSearch(self, big: str, smalls: List[str]) -> List[List[int]]: + trie_tree = Trie() + for i in range(len(smalls)): + word = smalls[i] + trie_tree.insert(word, i) + + res = [[] for _ in range(len(smalls))] + + for i in range(len(big)): + for index in trie_tree.search(big[i:]): + res[index].append(i) + return res +``` + diff --git a/docs/solutions/interviews/number-of-2s-in-range-lcci.md b/docs/solutions/interviews/number-of-2s-in-range-lcci.md new file mode 100644 index 00000000..3fe09ff1 --- /dev/null +++ b/docs/solutions/interviews/number-of-2s-in-range-lcci.md @@ -0,0 +1,85 @@ +# [面试题 17.06. 2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) + +- 标签:递归、数学、动态规划 +- 难度:困难 + +## 题目链接 + +- [面试题 17.06. 2出现的次数 - 力扣](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) + +## 题目大意 + +**描述**:给定一个整数 $n$。 + +**要求**:计算从 $0$ 到 $n$ (包含 $n$) 中数字 $2$ 出现的次数。 + +**说明**: + +- $n \le 10^9$。 + +**示例**: + +- 示例 1: + +```python +输入: 25 +输出: 9 +解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次) +``` + +## 解题思路 + +### 思路 1:动态规划 + 数位 DP + +将 $n$ 转换为字符串 $s$,定义递归函数 `def dfs(pos, cnt, isLimit):` 表示构造第 $pos$ 位及之后所有数位中数字 $2$ 出现的个数。接下来按照如下步骤进行递归。 + +1. 从 `dfs(0, 0, True)` 开始递归。 `dfs(0, 0, True)` 表示: + 1. 从位置 $0$ 开始构造。 + 2. 初始数字 $2$ 出现的个数为 $0$。 + 3. 开始时受到数字 $n$ 对应最高位数位的约束。 +2. 如果遇到 $pos == len(s)$,表示到达数位末尾,此时:返回数字 $2$ 出现的个数 $cnt$。 +3. 如果 $pos \ne len(s)$,则定义方案数 $ans$,令其等于 $0$,即:`ans = 0`。 +4. 如果遇到 $isNum == False$,说明之前位数没有填写数字,当前位可以跳过,这种情况下方案数等于 $pos + 1$ 位置上没有受到 $pos$ 位的约束,并且之前没有填写数字时的方案数,即:`ans = dfs(i + 1, state, False, False)`。 +5. 如果 $isNum == True$,则当前位必须填写一个数字。此时: + 1. 因为不需要考虑前导 $0$ 所以当前位数位所能选择的最小数字($minX$)为 $0$。 + 2. 根据 $isLimit$ 来决定填当前位数位所能选择的最大数字($maxX$)。 + 3. 然后根据 $[minX, maxX]$ 来枚举能够填入的数字 $d$。 + 4. 方案数累加上当前位选择 $d$ 之后的方案数,即:`ans += dfs(pos + 1, cnt + (d == 2), isLimit and d == maxX)`。 + 1. `cnt + (d == 2)` 表示之前数字 $2$ 出现的个数加上当前位为数字 $2$ 的个数。 + 2. `isLimit and d == maxX` 表示 $pos + 1$ 位受到之前位 $pos$ 位限制。 +6. 最后的方案数为 `dfs(0, 0, True)`,将其返回即可。 + +### 思路 1:代码 + +```python +class Solution: + def numberOf2sInRange(self, n: int) -> int: + # 将 n 转换为字符串 s + s = str(n) + + @cache + # pos: 第 pos 个数位 + # cnt: 之前数字 2 出现的个数。 + # isLimit: 表示是否受到选择限制。如果为真,则第 pos 位填入数字最多为 s[pos];如果为假,则最大可为 9。 + def dfs(pos, cnt, isLimit): + if pos == len(s): + return cnt + + ans = 0 + # 不需要考虑前导 0,则最小可选择数字为 0 + minX = 0 + # 如果受到选择限制,则最大可选择数字为 s[pos],否则最大可选择数字为 9。 + maxX = int(s[pos]) if isLimit else 9 + + # 枚举可选择的数字 + for d in range(minX, maxX + 1): + ans += dfs(pos + 1, cnt + (d == 2), isLimit and d == maxX) + return ans + + return dfs(0, 0, True) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\log n)$。 +- **空间复杂度**:$O(\log n)$。 diff --git a/docs/solutions/interviews/palindrome-linked-list-lcci.md b/docs/solutions/interviews/palindrome-linked-list-lcci.md new file mode 100644 index 00000000..9907bb09 --- /dev/null +++ b/docs/solutions/interviews/palindrome-linked-list-lcci.md @@ -0,0 +1,32 @@ +# [面试题 02.06. 回文链表](https://leetcode.cn/problems/palindrome-linked-list-lcci/) + +- 标签:栈、递归、链表、双指针 +- 难度:简单 + +## 题目链接 + +- [面试题 02.06. 回文链表 - 力扣](https://leetcode.cn/problems/palindrome-linked-list-lcci/) + +## 题目大意 + +给定一个链表的头节点 `head`。 + +要求:判断该链表是否为回文链表。 + +## 解题思路 + +利用数组,将链表元素依次存入。然后再使用两个指针,一个指向数组开始位置,一个指向数组结束位置,依次判断首尾对应元素是否相等,若都相等,则为回文链表。若不相等,则不是回文链表。 + +## 代码 + +```python +class Solution: + def isPalindrome(self, head: ListNode) -> bool: + nodes = [] + p1 = head + while p1 != None: + nodes.append(p1.val) + p1 = p1.next + return nodes == nodes[::-1] +``` + diff --git a/docs/solutions/interviews/paths-with-sum-lcci.md b/docs/solutions/interviews/paths-with-sum-lcci.md new file mode 100644 index 00000000..0cec25c8 --- /dev/null +++ b/docs/solutions/interviews/paths-with-sum-lcci.md @@ -0,0 +1,45 @@ +# [面试题 04.12. 求和路径](https://leetcode.cn/problems/paths-with-sum-lcci/) + +- 标签:树、深度优先搜索、二叉树 +- 难度:中等 + +## 题目链接 + +- [面试题 04.12. 求和路径 - 力扣](https://leetcode.cn/problems/paths-with-sum-lcci/) + +## 题目大意 + +给定一个二叉树的根节点 `root`,和一个整数 `targetSum`。 + +要求:求出该二叉树里节点值之和等于 `targetSum` 的路径的数目。 + +- 路径:不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 + +## 解题思路 + +直观想法是: + +以每一个节点 `node` 为起始节点,向下检测延伸的路径。递归遍历每一个节点所有可能的路径,然后将这些路径数目加起来即为答案。 + +但是这样会存在许多重复计算。我们可以定义节点的前缀和来减少重复计算。 + +- 节点的前缀和:从根节点到当前节点路径上所有节点的和。 + +有了节点的前缀和,我们就可以通过前缀和来计算两节点之间的路劲和。即:`则两节点之间的路径和 = 两节点之间的前缀和之差`。 + +为了计算符合要求的路径数量,我们用哈希表存储「前缀和的节点数量」。哈希表以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值。这样就能通过哈希表直接计算出符合要求的路径数量,从而累加到答案上。 + +整个算法的具体步骤如下: + +- 通过先序遍历方式递归遍历二叉树,计算每一个节点的前缀和 `cur_sum`。 +- 从哈希表中取出 `cur_sum - target_sum` 的路径数量(也就是表示存在从前缀和为 `cur_sum - target_sum` 所对应的节点到前缀和为 `cur_sum` 所对应的节点的路径个数)累加到答案 `res` 中。 +- 然后以「当前节点的前缀和」为键,以「该前缀和的节点数量」为值,存入哈希表中。 +- 递归遍历二叉树,并累加答案值。 +- 恢复哈希表「当前前缀和的节点数量」,返回答案。 + +## 代码 + +```python + +``` + diff --git a/docs/solutions/interviews/permutation-i-lcci.md b/docs/solutions/interviews/permutation-i-lcci.md new file mode 100644 index 00000000..f5cd4488 --- /dev/null +++ b/docs/solutions/interviews/permutation-i-lcci.md @@ -0,0 +1,46 @@ +# [面试题 08.07. 无重复字符串的排列组合](https://leetcode.cn/problems/permutation-i-lcci/) + +- 标签:字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [面试题 08.07. 无重复字符串的排列组合 - 力扣](https://leetcode.cn/problems/permutation-i-lcci/) + +## 题目大意 + +给定一个字符串 `S`。 + +要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组,但里边不能有重复元素。 + +## 解题思路 + +使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。然后进行回溯遍历。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, S, visited): + if len(self.path) == len(S): + self.res.append(''.join(self.path)) + return + for i in range(len(S)): + if not visited[i]: + visited[i] = True + self.path.append(S[i]) + self.backtrack(S, visited) + self.path.pop() + visited[i] = False + + def permutation(self, S: str) -> List[str]: + self.res.clear() + self.path.clear() + visited = [False for _ in range(len(S))] + self.backtrack(S, visited) + return self.res +``` + diff --git a/docs/solutions/interviews/permutation-ii-lcci.md b/docs/solutions/interviews/permutation-ii-lcci.md new file mode 100644 index 00000000..8f9edeb5 --- /dev/null +++ b/docs/solutions/interviews/permutation-ii-lcci.md @@ -0,0 +1,55 @@ +# [面试题 08.08. 有重复字符串的排列组合](https://leetcode.cn/problems/permutation-ii-lcci/) + +- 标签:字符串、回溯 +- 难度:中等 + +## 题目链接 + +- [面试题 08.08. 有重复字符串的排列组合 - 力扣](https://leetcode.cn/problems/permutation-ii-lcci/) + +## 题目大意 + +给定一个字符串 `s`,字符串中包含有重复字符。 + +要求:打印出该字符串中字符的所有排列。可以以任意顺序返回这个字符串数组。 + +## 解题思路 + +因为原字符串可能含有重复元素,所以在回溯的时候需要进行去重。先将字符串 `s` 转为 `list` 列表,再对列表进行排序,然后使用 `visited` 数组标记该元素在当前排列中是否被访问过。若未被访问过则将其加入排列中,并在访问后将该元素变为未访问状态。 + +然后再递归遍历下一层元素之前,增加一句语句进行判重:`if i > 0 and nums[i] == nums[i - 1] and not visited[i - 1]: continue`。 + +然后进行回溯遍历。 + +## 代码 + +```python +class Solution: + res = [] + path = [] + + def backtrack(self, ls, visited): + if len(self.path) == len(ls): + self.res.append(''.join(self.path)) + return + for i in range(len(ls)): + if i > 0 and ls[i] == ls[i - 1] and not visited[i - 1]: + continue + + if not visited[i]: + visited[i] = True + self.path.append(ls[i]) + self.backtrack(ls, visited) + self.path.pop() + visited[i] = False + + def permutation(self, S: str) -> List[str]: + self.res.clear() + self.path.clear() + ls = list(S) + ls.sort() + visited = [False for _ in range(len(S))] + self.backtrack(ls, visited) + return self.res +``` + diff --git a/docs/solutions/interviews/power-set-lcci.md b/docs/solutions/interviews/power-set-lcci.md new file mode 100644 index 00000000..b6f4d166 --- /dev/null +++ b/docs/solutions/interviews/power-set-lcci.md @@ -0,0 +1,35 @@ +# [面试题 08.04. 幂集](https://leetcode.cn/problems/power-set-lcci/) + +- 标签:位运算、数组、回溯 +- 难度:中等 + +## 题目链接 + +- [面试题 08.04. 幂集 - 力扣](https://leetcode.cn/problems/power-set-lcci/) + +## 题目大意 + +给定一个集合 `nums`,集合中不包含重复元素。 + +压枪欧秋:返回该集合的所有子集。 + +## 解题思路 + +回溯算法,遍历集合 `nums`。为了使得子集不重复,每次遍历从当前位置的下一个位置进行下一层遍历。 + +## 代码 + +```python +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + def backtrack(size, subset, index): + res.append(subset) + for i in range(index, size): + backtrack(size, subset + [nums[i]], i + 1) + + size = len(nums) + res = list() + backtrack(size, [], 0) + return res +``` + diff --git a/docs/solutions/interviews/rotate-matrix-lcci.md b/docs/solutions/interviews/rotate-matrix-lcci.md new file mode 100644 index 00000000..275cbe8f --- /dev/null +++ b/docs/solutions/interviews/rotate-matrix-lcci.md @@ -0,0 +1,67 @@ +# [面试题 01.07. 旋转矩阵](https://leetcode.cn/problems/rotate-matrix-lcci/) + +- 标签:数组、数学、矩阵 +- 难度:中等 + +## 题目链接 + +- [面试题 01.07. 旋转矩阵 - 力扣](https://leetcode.cn/problems/rotate-matrix-lcci/) + +## 题目大意 + +给定一个 `n * n` 大小的二维矩阵用来表示图像,其中每个像素的大小为 4 字节。 + +要求:设计一种算法,将图像旋转 90 度。并且要不占用额外内存空间。 + +## 解题思路 + +题目要求不占用额外内存空间,就是要在原二维矩阵上直接进行旋转操作。我们可以用翻转操作代替旋转操作。具体可以分为两步: + +1. 上下翻转。 + +2. 主对角线翻转。 + +举个例子: + +``` + 1 2 3 4 + 5 6 7 8 + 9 10 11 12 +13 14 15 16 +``` + +上下翻转后变为: + +``` +13 14 15 16 + 9 10 11 12 + 5 6 7 8 + 1 2 3 4 +``` + +在经过主对角线翻转后变为: + +``` +13 9 5 1 +14 10 6 2 +15 11 7 3 +16 12 8 4 +``` + +## 代码 + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + size = len(matrix) + for i in range(size // 2): + for j in range(size): + matrix[i][j], matrix[size - i - 1][j] = matrix[size - i - 1][j], matrix[i][j] + for i in range(size): + for j in range(i): + matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] +``` + diff --git a/docs/solutions/interviews/smallest-k-lcci.md b/docs/solutions/interviews/smallest-k-lcci.md new file mode 100644 index 00000000..cc05a832 --- /dev/null +++ b/docs/solutions/interviews/smallest-k-lcci.md @@ -0,0 +1,73 @@ +# [面试题 17.14. 最小K个数](https://leetcode.cn/problems/smallest-k-lcci/) + +- 标签:数组、分治、快速选择、排序、堆(优先队列) +- 难度:中等 + +## 题目链接 + +- [面试题 17.14. 最小K个数 - 力扣](https://leetcode.cn/problems/smallest-k-lcci/) + +## 题目大意 + +给定整数数组 `arr`,再给定一个整数 `k`。 + +要求:返回数组 `arr` 中最小的 `k` 个数。 + +## 解题思路 + +直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。 + +冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。 + +可考虑堆排序、归并排序、快速排序。本题使用堆排序。具体做法如下: + +1. 利用数组前 `k` 个元素,建立大小为 `k` 的大顶堆。 +2. 遍历数组 `[k, size - 1]` 的元素,判断其与堆顶元素关系,如果比堆顶元素小,则将其赋值给堆顶元素,再对大顶堆进行调整。 +3. 最后输出前调整过后的大顶堆的前 `k` 个元素。 + +## 代码 + +```python +class Solution: + def heapify(self, nums: [int], index: int, end: int): + left = index * 2 + 1 + right = left + 1 + while left <= end: + # 当前节点为非叶子节点 + max_index = index + if nums[left] > nums[max_index]: + max_index = left + if right <= end and nums[right] > nums[max_index]: + max_index = right + if index == max_index: + # 如果不用交换,则说明已经交换结束 + break + nums[index], nums[max_index] = nums[max_index], nums[index] + # 继续调整子树 + index = max_index + left = index * 2 + 1 + right = left + 1 + + # 初始化大顶堆 + def buildMaxHeap(self, nums: [int], k: int): + # (k-2) // 2 是最后一个非叶节点,叶节点不用调整 + for i in range((k - 2) // 2, -1, -1): + self.heapify(nums, i, k - 1) + return nums + + def smallestK(self, arr: List[int], k: int) -> List[int]: + size = len(arr) + if k <= 0 or not arr: + return [] + if size <= k: + return arr + + self.buildMaxHeap(arr, k) + for i in range(k, size): + if arr[i] < arr[0]: + arr[i], arr[0] = arr[0], arr[i] + self.heapify(arr, 0, k - 1) + + return arr[:k] +``` + diff --git a/docs/solutions/interviews/sorted-matrix-search-lcci.md b/docs/solutions/interviews/sorted-matrix-search-lcci.md new file mode 100644 index 00000000..61c554e6 --- /dev/null +++ b/docs/solutions/interviews/sorted-matrix-search-lcci.md @@ -0,0 +1,90 @@ +# [面试题 10.09. 排序矩阵查找](https://leetcode.cn/problems/sorted-matrix-search-lcci/) + +- 标签:数组、二分查找、分治、矩阵 +- 难度:中等 + +## 题目链接 + +- [面试题 10.09. 排序矩阵查找 - 力扣](https://leetcode.cn/problems/sorted-matrix-search-lcci/) + +## 题目大意 + +给定一个 `m * n` 大小的有序整数矩阵。每一行、每一列都按升序排列。再给定一个目标值 `target`。 + +要求:判断矩阵中是否可以找到 `target`,若找到 `target`,返回 `True`,否则返回 `False`。 + +## 解题思路 + +矩阵是有序的,可以考虑使用二分搜索来进行查找。 + +迭代对角线元素,假设对角线元素的坐标为 `(row, col)`。把数组元素按对角线分为右上角部分和左下角部分。 + +则对于当前对角线元素右侧第 `row` 行、对角线元素下侧第 `col` 列进行二分查找。 + +- 如果找到目标,直接返回 `True`。 +- 如果找不到目标,则缩小范围,继续查找。 +- 直到所有对角线元素都遍历完,依旧没找到,则返回 `False`。 + +## 代码 + +```python +class Solution: + def diagonalBinarySearch(self, matrix, diagonal, target): + left = 0 + right = diagonal + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][mid] < target: + left = mid + 1 + else: + right = mid + return left + + def rowBinarySearch(self, matrix, begin, cols, target): + left = begin + right = cols + while left < right: + mid = left + (right - left) // 2 + if matrix[begin][mid] < target: + left = mid + 1 + elif matrix[begin][mid] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= cols and matrix[begin][left] == target + + def colBinarySearch(self, matrix, begin, rows, target): + left = begin + 1 + right = rows + while left < right: + mid = left + (right - left) // 2 + if matrix[mid][begin] < target: + left = mid + 1 + elif matrix[mid][begin] > target: + right = mid - 1 + else: + left = mid + break + return begin <= left <= rows and matrix[left][begin] == target + + def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: + rows = len(matrix) + if rows == 0: + return False + cols = len(matrix[0]) + if cols == 0: + return False + + min_val = min(rows, cols) + index = self.diagonalBinarySearch(matrix, min_val - 1, target) + if matrix[index][index] == target: + return True + for i in range(index + 1): + row_search = self.rowBinarySearch(matrix, i, cols - 1, target) + col_search = self.colBinarySearch(matrix, i, rows - 1, target) + if row_search or col_search: + return True + return False +``` + diff --git a/docs/solutions/interviews/sorted-merge-lcci.md b/docs/solutions/interviews/sorted-merge-lcci.md new file mode 100644 index 00000000..ea05bd85 --- /dev/null +++ b/docs/solutions/interviews/sorted-merge-lcci.md @@ -0,0 +1,72 @@ +# [面试题 10.01. 合并排序的数组](https://leetcode.cn/problems/sorted-merge-lcci/) + +- 标签:数组、双指针、排序 +- 难度:简单 + +## 题目链接 + +- [面试题 10.01. 合并排序的数组 - 力扣](https://leetcode.cn/problems/sorted-merge-lcci/) + +## 题目大意 + +**描述**:给定两个排序后的数组 `A` 和 `B`,以及 `A` 的元素数量 `m` 和 `B` 的元素数量 `n`。 `A` 的末端有足够的缓冲空间容纳 `B`。 + +**要求**:编写一个方法,将 `B` 合并入 `A` 并排序。 + +**说明**: + +- $A.length == n + m$。 + +**示例**: + +- 示例 1: + +```python +输入: +A = [1,2,3,0,0,0], m = 3 +B = [2,5,6], n = 3 + +输出: [1,2,2,3,5,6] +``` + +## 解题思路 + +### 思路 1:归并排序 + +可以利用归并排序算法的归并步骤思路。 + +1. 使用两个指针分别表示`A`、`B` 正在处理的元素下标。 +2. 对 `A`、`B` 进行归并操作,将结果存入新数组中。归并之后,再将所有元素赋值到数组 `A` 中。 + +### 思路 1:代码 + +```python +class Solution: + def merge(self, A: List[int], m: int, B: List[int], n: int) -> None: + """ + Do not return anything, modify A in-place instead. + """ + arr = [] + index_A, index_B = 0, 0 + while index_A < m and index_B < n: + if A[index_A] <= B[index_B]: + arr.append(A[index_A]) + index_A += 1 + else: + arr.append(B[index_B]) + index_B += 1 + while index_A < m: + arr.append(A[index_A]) + index_A += 1 + while index_B < n: + arr.append(B[index_B]) + index_B += 1 + for i in range(m + n): + A[i] = arr[i] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m + n)$。 +- **空间复杂度**:$O(m + n)$。 + diff --git a/docs/solutions/interviews/successor-lcci.md b/docs/solutions/interviews/successor-lcci.md new file mode 100644 index 00000000..0576afea --- /dev/null +++ b/docs/solutions/interviews/successor-lcci.md @@ -0,0 +1,39 @@ +# [面试题 04.06. 后继者](https://leetcode.cn/problems/successor-lcci/) + +- 标签:树、深度优先搜索、二叉搜索树、二叉树 +- 难度:中等 + +## 题目链接 + +- [面试题 04.06. 后继者 - 力扣](https://leetcode.cn/problems/successor-lcci/) + +## 题目大意 + +给定一棵二叉搜索树的根节点 `root` 和其中一个节点 `p`。 + +要求:找出该节点在树中的中序后继,即按照中序遍历的顺序节点 `p` 的下一个节点。如果节点 `p` 没有对应的下一个节点,则返回 `None`。 + +## 解题思路 + +递归遍历,具体步骤如下: + +- 如果 `root.val` 小于等于 `p.val`,则直接从 `root` 的右子树递归查找比 `p.val` 大的节点,从而找到中序后继。 +- 如果 `root.val` 大于 `p.val`,则 `root` 有可能是中序后继,也有可能是 `root` 的左子树。则从 `root` 的左子树递归查找更接近(更小的)。如果查找的值为 `None`,则当前 `root` 就是中序后继,否则继续递归查找,从而找到中序后继。 + +## 代码 + +```python +class Solution: + def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode: + if not p or not root: + return None + + if root.val <= p.val: + node = self.inorderSuccessor(root.right, p) + else: + node = self.inorderSuccessor(root.left, p) + if not node: + node = root + return node +``` + diff --git a/docs/solutions/interviews/sum-lists-lcci.md b/docs/solutions/interviews/sum-lists-lcci.md new file mode 100644 index 00000000..7962b73d --- /dev/null +++ b/docs/solutions/interviews/sum-lists-lcci.md @@ -0,0 +1,47 @@ +# [面试题 02.05. 链表求和](https://leetcode.cn/problems/sum-lists-lcci/) + +- 标签:递归、链表、数学 +- 难度:中等 + +## 题目链接 + +- [面试题 02.05. 链表求和 - 力扣](https://leetcode.cn/problems/sum-lists-lcci/) + +## 题目大意 + +给定两个非空的链表 `l1` 和 `l2`,表示两个非负整数,每位数字都是按照逆序的方式存储的,每个节点存储一位数字。 + +要求:计算两个整数的和,并逆序返回表示和的链表。 + +## 解题思路 + +模拟大数加法,按位相加,将结果添加到新链表上。需要注意进位和对 `10` 取余。 + +## 代码 + +```python +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + head = curr = ListNode(0) + carry = 0 + while l1 or l2 or carry: + if l1: + num1 = l1.val + l1 = l1.next + else: + num1 = 0 + if l2: + num2 = l2.val + l2 = l2.next + else: + num2 = 0 + + sum = num1 + num2 + carry + carry = sum // 10 + + curr.next = ListNode(sum % 10) + curr = curr.next + + return head.next +``` + diff --git a/docs/solutions/interviews/words-frequency-lcci.md b/docs/solutions/interviews/words-frequency-lcci.md new file mode 100644 index 00000000..6211c194 --- /dev/null +++ b/docs/solutions/interviews/words-frequency-lcci.md @@ -0,0 +1,78 @@ +# [面试题 16.02. 单词频率](https://leetcode.cn/problems/words-frequency-lcci/) + +- 标签:设计、字典树、数组、哈希表、字符串 +- 难度:中等 + +## 题目链接 + +- [面试题 16.02. 单词频率 - 力扣](https://leetcode.cn/problems/words-frequency-lcci/) + +## 题目大意 + +要求:设计一个方法,找出任意指定单词在一本书中的出现频率。 + +支持如下操作: + +- `WordsFrequency(book)` 构造函数,参数为字符串数组构成的一本书。 +- `get(word)` 查询指定单词在书中出现的频率。 + +## 解题思路 + +使用字典树统计单词频率。 + +构造函数时,构建一个字典树,并将所有单词存入字典树中,同时在字典树中记录并维护单词频率。 + +查询时,调用字典树查询方法,查询单词频率。 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.children = dict() + self.isEnd = False + self.count = 0 + + + def insert(self, word: str) -> None: + """ + Inserts a word into the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + cur.children[ch] = Trie() + cur = cur.children[ch] + cur.isEnd = True + cur.count += 1 + + + def search(self, word: str) -> bool: + """ + Returns if the word is in the trie. + """ + cur = self + for ch in word: + if ch not in cur.children: + return 0 + cur = cur.children[ch] + if cur and cur.isEnd: + return cur.count + return 0 + +class WordsFrequency: + + def __init__(self, book: List[str]): + self.tire_tree = Trie() + for word in book: + self.tire_tree.insert(word) + + + def get(self, word: str) -> int: + return self.tire_tree.search(word) +``` + diff --git a/docs/solutions/interviews/zero-matrix-lcci.md b/docs/solutions/interviews/zero-matrix-lcci.md new file mode 100644 index 00000000..6f9f855b --- /dev/null +++ b/docs/solutions/interviews/zero-matrix-lcci.md @@ -0,0 +1,70 @@ +# [面试题 01.08. 零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) + +- 标签:数组、哈希表、矩阵 +- 难度:中等 + +## 题目链接 + +- [面试题 01.08. 零矩阵 - 力扣](https://leetcode.cn/problems/zero-matrix-lcci/) + +## 题目大意 + +给定一个 `m * n` 大小的二维矩阵 `matrix`。 + +要求:编写一种算法,如果矩阵中某个元素为 `0`,增将其所在行与列清零。 + +## 解题思路 + +直观上可以使用两个数组或者集合来标记行和列出现 `0` 的情况,但更好的做法是不用开辟新的数组或集合,直接原本二维矩阵 `matrix` 的空间。使用数组原本的元素进行记录出现 0 的情况。 + +设定两个变量 `flag_row0`、`flag_col0` 来标记第一行、第一列是否出现了 `0`。 + +接下来我们使用数组第一行、第一列来标记 `0` 的情况。 + +对数组除第一行、第一列之外的每个元素进行遍历,如果某个元素出现 `0` 了,则使用数组的第一行、第一列对应位置来存储 `0` 的标记。 + +再对数组除第一行、第一列之外的每个元素进行遍历,通过对第一行、第一列的标记 0 情况,进行置为 `0` 的操作。 + +最后再根据 `flag_row0`、`flag_col0` 的标记情况,对第一行、第一列进行置为 `0` 的操作。 + +## 代码 + +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + rows = len(matrix) + cols = len(matrix[0]) + flag_col0 = False + flag_row0 = False + for i in range(rows): + if matrix[i][0] == 0: + flag_col0 = True + break + + for j in range(cols): + if matrix[0][j] == 0: + flag_row0 = True + break + + for i in range(1, rows): + for j in range(1, cols): + if matrix[i][j] == 0: + matrix[i][0] = matrix[0][j] = 0 + + for i in range(1, rows): + for j in range(1, cols): + if matrix[i][0] == 0 or matrix[0][j] == 0: + matrix[i][j] = 0 + + if flag_col0: + for i in range(rows): + matrix[i][0] = 0 + + if flag_row0: + for j in range(cols): + matrix[0][j] = 0 +``` +